#if NET_4_6 && !NET_STANDARD_2_0 #define QC_SUPPORTED #endif using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; #if QC_SUPPORTED namespace CSharpCompiler { public class ScriptBundleLoader { public Func createInstance = (Type type) => { return Activator.CreateInstance(type); }; public Action destroyInstance = delegate { }; public TextWriter logWriter = Console.Out; ISynchronizeInvoke synchronizedInvoke; List allFilesBundle = new List(); public ScriptBundleLoader(ISynchronizeInvoke synchronizedInvoke) { this.synchronizedInvoke = synchronizedInvoke; } /// /// /// /// /// true on success, false on failure public ScriptBundle LoadAndWatchScriptsBundle(IEnumerable fileSources) { var bundle = new ScriptBundle(this, fileSources); allFilesBundle.Add(bundle); return bundle; } /// /// Manages a bundle of files which form one assembly, if one file changes entire assembly is recompiled. /// public class ScriptBundle { Assembly assembly; IEnumerable filePaths; List fileSystemWatchers = new List(); List instances = new List(); ScriptBundleLoader manager; string[] assemblyReferences; public ScriptBundle(ScriptBundleLoader manager, IEnumerable filePaths) { this.filePaths = filePaths.Select(x => Path.GetFullPath(x)); this.manager = manager; var domain = System.AppDomain.CurrentDomain; this.assemblyReferences = domain .GetAssemblies() .Where(a => !(a is System.Reflection.Emit.AssemblyBuilder) && !string.IsNullOrEmpty(a.Location)) .Select(a => a.Location) .ToArray(); manager.logWriter.WriteLine("loading " + string.Join(", ", filePaths.ToArray())); CompileFiles(); CreateFileWatchers(); CreateNewInstances(); } void CompileFiles() { filePaths = filePaths.Where(x => File.Exists(x)).ToArray(); var options = new CompilerParameters(); options.GenerateExecutable = false; options.GenerateInMemory = true; options.ReferencedAssemblies.AddRange(assemblyReferences); var compiler = new CodeCompiler(); var result = compiler.CompileAssemblyFromFileBatch(options, filePaths.ToArray()); foreach (var err in result.Errors) { manager.logWriter.WriteLine(err); } this.assembly = result.CompiledAssembly; } void CreateFileWatchers() { foreach (var filePath in filePaths) { FileSystemWatcher watcher = new FileSystemWatcher(); fileSystemWatchers.Add(watcher); watcher.Path = Path.GetDirectoryName(filePath); /* Watch for changes in LastAccess and LastWrite times, and the renaming of files or directories. */ watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; watcher.Filter = Path.GetFileName(filePath); // Add event handlers. watcher.Changed += new FileSystemEventHandler((object o, FileSystemEventArgs a) => { Reload(recreateWatchers: false); }); //watcher.Created += new FileSystemEventHandler((object o, FileSystemEventArgs a) => { }); watcher.Deleted += new FileSystemEventHandler((object o, FileSystemEventArgs a) => { Reload(recreateWatchers: false); }); watcher.Renamed += new RenamedEventHandler((object o, RenamedEventArgs a) => { filePaths = filePaths.Select(x => { if (x == a.OldFullPath) return a.FullPath; else return x; }); Reload(recreateWatchers: true); }); watcher.SynchronizingObject = manager.synchronizedInvoke; // Begin watching. watcher.EnableRaisingEvents = true; } } void StopFileWatchers() { foreach (var w in fileSystemWatchers) { w.EnableRaisingEvents = false; w.Dispose(); } fileSystemWatchers.Clear(); } void Reload(bool recreateWatchers = false) { manager.logWriter.WriteLine("reloading " + string.Join(", ", filePaths.ToArray())); StopInstances(); CompileFiles(); CreateNewInstances(); if (recreateWatchers) { StopFileWatchers(); CreateFileWatchers(); } } void CreateNewInstances() { if (assembly == null) return; foreach (var type in assembly.GetTypes()) { manager.synchronizedInvoke.Invoke((System.Action)(() => { instances.Add(manager.createInstance(type)); }), null); } } void StopInstances() { foreach (var instance in instances) { manager.synchronizedInvoke.Invoke((System.Action)(() => { manager.destroyInstance(instance); }), null); } instances.Clear(); } } } } #endif