Skip to content

Commit

Permalink
Adds dynamic loading/unloading of modules
Browse files Browse the repository at this point in the history
  • Loading branch information
RainOrigami committed Aug 15, 2023
1 parent 4eb215c commit 4c2574c
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 26 deletions.
16 changes: 11 additions & 5 deletions BattleBitAPIRunner/ModuleProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static ModuleContext LoadModule(string modulePath)
string[] modulesCsprojFiles = Directory.GetFiles(modulePath, "*.csproj").ToArray();
if (modulesCsprojFiles.Length != 1)
{
throw new Exception($"Module {Path.GetDirectoryName(modulePath)} does not contain one singular csproj file");
throw new Exception($"Module {Path.GetFileName(modulePath)} does not contain one singular csproj file");
}

compileProject(modulesCsprojFiles.First());
Expand All @@ -37,13 +37,13 @@ public static ModuleContext LoadModule(string modulePath)
throw new FileNotFoundException("Module dll not found", moduleDllPath);
}

AssemblyLoadContext assemblyContext = new(Path.GetFileNameWithoutExtension(moduleDllPath));
AssemblyLoadContext assemblyContext = new(Path.GetFileNameWithoutExtension(moduleDllPath), true);
Assembly assembly = assemblyContext.LoadFromAssemblyPath(moduleDllPath);

IEnumerable<Type> moduleTypes = assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(BattleBitModule)));
if (moduleTypes.Count() != 1)
{
throw new Exception($"Module {Path.GetDirectoryName(modulePath)} does not contain a class that inherits from {nameof(BattleBitModule)}");
throw new Exception($"Module {Path.GetFileName(modulePath)} does not contain a class that inherits from {nameof(BattleBitModule)}");
}

return new ModuleContext(assemblyContext, moduleTypes.First());
Expand All @@ -52,6 +52,7 @@ public static ModuleContext LoadModule(string modulePath)
private static void compileProject(string csprojFilePath)
{
Console.Write($"Compiling module {Path.GetFileNameWithoutExtension(csprojFilePath)}... ");
Stopwatch stopwatch = Stopwatch.StartNew();

var processInfo = new ProcessStartInfo
{
Expand All @@ -76,11 +77,16 @@ private static void compileProject(string csprojFilePath)
if (process.ExitCode != 0)
{
Console.WriteLine();
throw new Exception($"Failed to compile module {Path.GetDirectoryName(csprojFilePath)}. Errors:{Environment.NewLine}{errors}");
throw new Exception($"Failed to compile module {Path.GetFileName(csprojFilePath)} (took {Math.Round(stopwatch.Elapsed.TotalSeconds, 1)}s). Errors:{Environment.NewLine}{errors}");
}
}

Console.WriteLine("Done");
Console.WriteLine($"Completed in {Math.Round(stopwatch.Elapsed.TotalSeconds, 1)}s");
}

public static void UnloadModule(ModuleContext moduleContext)
{
moduleContext.Context.Unload();
}
}
}
47 changes: 26 additions & 21 deletions BattleBitAPIRunner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private void consoleCommandHandler()
}

string moduleName = commandParts[1];
ModuleContext? moduleContext = this.modules.FirstOrDefault(x => x.Module.Name.Equals(moduleName, StringComparison.OrdinalIgnoreCase));
ModuleContext? moduleContext = this.modules.FirstOrDefault(x => x.Context.Name.Equals(moduleName, StringComparison.OrdinalIgnoreCase));


if (commandParts[0].Equals("load", StringComparison.OrdinalIgnoreCase))
Expand All @@ -67,7 +67,14 @@ private void consoleCommandHandler()

try
{
moduleContext = ModuleProvider.LoadModule(Directory.GetDirectories(this.configuration.ModulePath).Union(this.configuration.Modules).First(m => Path.GetDirectoryName(m).Equals(moduleName, StringComparison.OrdinalIgnoreCase)));
string? modulePath = Directory.GetDirectories(this.configuration.ModulePath).Union(this.configuration.Modules).FirstOrDefault(m => Path.GetFileName(m).Equals(moduleName, StringComparison.OrdinalIgnoreCase));

if (string.IsNullOrEmpty(modulePath))
{
throw new FileNotFoundException("Module not found", modulePath);
}

moduleContext = ModuleProvider.LoadModule(modulePath);
this.modules.Add(moduleContext);

foreach (RunnerServer server in this.servers)
Expand All @@ -78,7 +85,7 @@ private void consoleCommandHandler()
BattleBitModule module = Activator.CreateInstance(moduleContext.Module, server) as BattleBitModule;
if (module is null)
{
throw new Exception($"Module {moduleContext.Module.Name} does not inherit from {nameof(BattleBitModule)}");
throw new Exception($"Module {moduleContext.Context.Name} does not inherit from {nameof(BattleBitModule)}");
}
if (server.IsConnected)
{
Expand All @@ -87,15 +94,15 @@ private void consoleCommandHandler()
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load module {moduleContext.Module.Name} for server {server.GameIP}:{server.GamePort}: {ex.Message}");
Console.WriteLine($"Failed to load module {moduleContext.Context.Name} for server {server.GameIP}:{server.GamePort}: {ex.Message}");
}
}

Console.WriteLine($"Module {moduleContext.Module.Name} loaded.");
Console.WriteLine($"Module {moduleContext.Context.Name} loaded.");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load module {moduleContext.Module.Name}: {ex.Message}");
Console.WriteLine($"Failed to load module {moduleContext.Context.Name}: {ex.Message}");
continue;
}
}
Expand All @@ -110,7 +117,7 @@ private void consoleCommandHandler()

unloadModule(moduleContext);

Console.WriteLine($"Module {moduleContext.Module.Name} unloaded.");
Console.WriteLine($"Module {moduleContext.Context.Name} unloaded.");
}
break;
default:
Expand All @@ -134,8 +141,7 @@ private void unloadModule(ModuleContext moduleContext)
server.RemoveModule(module);
}

// TODO: does this work?
moduleContext.Context.Unload();
ModuleProvider.UnloadModule(moduleContext);
}

private void loadModules()
Expand All @@ -149,12 +155,12 @@ private void loadModules()
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load module {moduleDirectory}: {ex.Message}");
Console.WriteLine($"Failed to load module {Path.GetFileName(moduleDirectory)}: {ex.Message}");
continue;
}

this.modules.Add(moduleContext);
Console.WriteLine($"Loaded module {moduleContext.Module.Name}");
Console.WriteLine($"Loaded module {moduleContext.Context.Name}");
}
}

Expand All @@ -175,12 +181,12 @@ private Task initializeGameServer(GameServer<RunnerPlayer> server)
BattleBitModule module = Activator.CreateInstance(moduleContext.Module, server) as BattleBitModule;
if (module is null)
{
throw new Exception($"Module {moduleContext.Module.Name} does not inherit from {nameof(BattleBitModule)}");
throw new Exception($"Module {moduleContext.Context.Name} does not inherit from {nameof(BattleBitModule)}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load module {moduleContext.Module.Name}: {ex.Message}");
Console.WriteLine($"Failed to load module {moduleContext.Context.Name}: {ex.Message}");
}
}

Expand All @@ -205,25 +211,24 @@ private void validateConfiguration()
{
List<ValidationResult> validationResults = new();
IPAddress? ipAddress = null;
if (!Validator.TryValidateObject(this.configuration, new ValidationContext(this.configuration), validationResults, true) || !IPAddress.TryParse(this.configuration.IP, out ipAddress) || !Directory.Exists(this.configuration.ModulePath))
if (!Validator.TryValidateObject(this.configuration, new ValidationContext(this.configuration), validationResults, true) || !IPAddress.TryParse(this.configuration.IP, out ipAddress))
{
StringBuilder error = new();
error.AppendLine($"Invalid configuration:{Environment.NewLine}{string.Join(Environment.NewLine, validationResults.Select(x => x.ErrorMessage))}");

if (ipAddress is null)
{
error.AppendLine("IP address is invalid");
}

// TODO: this sucks.
if (!Directory.Exists(this.configuration.ModulePath))
{
error.AppendLine("Module path does not exist");
error.AppendLine("IP address is invalid.");
}

throw new Exception(error.ToString());
}

if (!Directory.Exists(this.configuration.ModulePath))
{
Directory.CreateDirectory(this.configuration.ModulePath);
}

// TODO: this sucks.
configuration.IPAddress = ipAddress;
}
Expand Down

0 comments on commit 4c2574c

Please sign in to comment.