Skip to content

ScriptEngine: added FileSystemWatcher, optionally including subdirectories and… #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 8, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 59 additions & 7 deletions src/ScriptEngine/ScriptEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,40 +27,67 @@ public class ScriptEngine : BaseUnityPlugin

ConfigEntry<bool> LoadOnStart { get; set; }
ConfigEntry<KeyboardShortcut> ReloadKey { get; set; }
ConfigEntry<bool> QuietMode { get; set; }
ConfigEntry<bool> EnableFileSystemWatcher { get; set; }
ConfigEntry<bool> IncludeSubdirectories { get; set; }
ConfigEntry<float> AutoReloadDelay { get; set; }

private FileSystemWatcher fileSystemWatcher;
private bool shouldReload;
private float autoReloadTimer;

void Awake()
{
LoadOnStart = Config.Bind("General", "LoadOnStart", false, new ConfigDescription("Load all plugins from the scripts folder when starting the application"));
ReloadKey = Config.Bind("General", "ReloadKey", new KeyboardShortcut(KeyCode.F6), new ConfigDescription("Press this key to reload all the plugins from the scripts folder"));
QuietMode = Config.Bind("General", "QuietMode", false, new ConfigDescription("Suppress sending log messages to console except for the error ones."));
EnableFileSystemWatcher = Config.Bind("General", "EnableFileSystemWatcher", false, new ConfigDescription("Watches the scripts directory for file changes and reloads all plugins if any of them gets changed."));
IncludeSubdirectories = Config.Bind("General", "IncludeSubdirectories", false, new ConfigDescription("Also include subdirectories under the scripts folder."));
AutoReloadDelay = Config.Bind("General", "AutoReloadDelay", 3.0f, new ConfigDescription("Time to wait, in seconds, from detecting a change to files in the scripts directory until all plugins start reloading."));

if (LoadOnStart.Value)
ReloadPlugins();

if (EnableFileSystemWatcher.Value)
StartFileSystemWatcher();
}

void Update()
{
if (ReloadKey.Value.IsDown())
{
ReloadPlugins();
}
else if (shouldReload)
{
autoReloadTimer -= Time.unscaledDeltaTime;
if (autoReloadTimer <= .0f)
ReloadPlugins();
}
}

void ReloadPlugins()
{
Logger.Log(LogLevel.Info, "Unloading old plugin instances");
shouldReload = false;
if (!QuietMode.Value)
Logger.Log(LogLevel.Info, "Unloading old plugin instances");
Destroy(scriptManager);
scriptManager = new GameObject($"ScriptEngine_{DateTime.Now.Ticks}");
DontDestroyOnLoad(scriptManager);

var files = Directory.GetFiles(ScriptDirectory, "*.dll");
var files = Directory.GetFiles(ScriptDirectory, "*.dll", IncludeSubdirectories.Value ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
if (files.Length > 0)
{
foreach (string path in Directory.GetFiles(ScriptDirectory, "*.dll"))
foreach (string path in Directory.GetFiles(ScriptDirectory, "*.dll", IncludeSubdirectories.Value ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
LoadDLL(path, scriptManager);

Logger.LogMessage("Reloaded all plugins!");
if (!QuietMode.Value)
Logger.LogMessage("Reloaded all plugins!");
}
else
{
Logger.LogMessage("No plugins to reload");
if (!QuietMode.Value)
Logger.LogMessage("No plugins to reload");
}
}

Expand All @@ -71,7 +98,8 @@ void LoadDLL(string path, GameObject obj)
defaultResolver.AddSearchDirectory(Paths.ManagedPath);
defaultResolver.AddSearchDirectory(Paths.BepInExAssemblyDirectory);

Logger.Log(LogLevel.Info, $"Loading plugins from {path}");
if (!QuietMode.Value)
Logger.Log(LogLevel.Info, $"Loading plugins from {path}");

using (var dll = AssemblyDefinition.ReadAssembly(path, new ReaderParameters { AssemblyResolver = defaultResolver }))
{
Expand All @@ -95,7 +123,8 @@ void LoadDLL(string path, GameObject obj)
var typeInfo = Chainloader.ToPluginInfo(typeDefinition);
Chainloader.PluginInfos[metadata.GUID] = typeInfo;

Logger.Log(LogLevel.Info, $"Loading {metadata.GUID}");
if (!QuietMode.Value)
Logger.Log(LogLevel.Info, $"Loading {metadata.GUID}");
StartCoroutine(DelayAction(() =>
{
try
Expand All @@ -119,6 +148,29 @@ void LoadDLL(string path, GameObject obj)
}
}

private void StartFileSystemWatcher()
{
fileSystemWatcher = new FileSystemWatcher(ScriptDirectory)
{
IncludeSubdirectories = IncludeSubdirectories.Value
};
fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
fileSystemWatcher.Filter = "*.dll";
fileSystemWatcher.Changed += FileChangedEventHandler;
fileSystemWatcher.Deleted += FileChangedEventHandler;
fileSystemWatcher.Created += FileChangedEventHandler;
fileSystemWatcher.Renamed += FileChangedEventHandler;
fileSystemWatcher.EnableRaisingEvents = true;
}

private void FileChangedEventHandler(object sender, FileSystemEventArgs args)
{
if (!QuietMode.Value)
Logger.LogInfo($"File {Path.GetFileName(args.Name)} changed. Delayed recompiling...");
shouldReload = true;
autoReloadTimer = AutoReloadDelay.Value;
}

private IEnumerable<Type> GetTypesSafe(Assembly ass)
{
try
Expand Down