diff --git a/NWN.Anvil.TestRunner/NWN.Anvil.TestRunner.csproj b/NWN.Anvil.TestRunner/NWN.Anvil.TestRunner.csproj index 757ac1581..91d75bf9b 100644 --- a/NWN.Anvil.TestRunner/NWN.Anvil.TestRunner.csproj +++ b/NWN.Anvil.TestRunner/NWN.Anvil.TestRunner.csproj @@ -69,7 +69,7 @@ - + diff --git a/NWN.Anvil.Tests/NWN.Anvil.Tests.csproj b/NWN.Anvil.Tests/NWN.Anvil.Tests.csproj index cab1c50be..8605ef181 100644 --- a/NWN.Anvil.Tests/NWN.Anvil.Tests.csproj +++ b/NWN.Anvil.Tests/NWN.Anvil.Tests.csproj @@ -42,7 +42,7 @@ - + diff --git a/NWN.Anvil/NWN.Anvil.csproj b/NWN.Anvil/NWN.Anvil.csproj index 45c10d4ef..7d3873ae7 100644 --- a/NWN.Anvil/NWN.Anvil.csproj +++ b/NWN.Anvil/NWN.Anvil.csproj @@ -65,7 +65,7 @@ - + diff --git a/NWN.Anvil/src/main/AnvilCore.FunctionHandlers.cs b/NWN.Anvil/src/main/AnvilCore.FunctionHandlers.cs new file mode 100644 index 000000000..3acded81a --- /dev/null +++ b/NWN.Anvil/src/main/AnvilCore.FunctionHandlers.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Anvil.API; +using Anvil.Internal; +using Anvil.Services; +using NWN.Core; +using NWN.Native.API; +using Action = System.Action; + +namespace Anvil +{ + public sealed partial class AnvilCore : ICoreFunctionHandler + { + [Inject] + private static ScriptDispatchService? ScriptDispatchService { get; set; } + + [Inject] + private static ServerUpdateLoopService? ServerUpdateLoopService { get; set; } + + private static readonly Dictionary Closures = new Dictionary(); + private static readonly Stack ScriptContexts = new Stack(); + + private static ulong nextEventId; + private static uint objectSelf; + + uint ICoreFunctionHandler.ObjectSelf => objectSelf; + + void ICoreFunctionHandler.ClosureActionDoCommand(uint obj, Action func) + { + if (VM.ClosureActionDoCommand(obj, nextEventId) != 0) + { + Closures.Add(nextEventId++, func); + } + } + + void ICoreFunctionHandler.ClosureAssignCommand(uint obj, Action func) + { + if (VM.ClosureAssignCommand(obj, nextEventId) != 0) + { + Closures.Add(nextEventId++, func); + } + } + + void ICoreFunctionHandler.ClosureDelayCommand(uint obj, float duration, Action func) + { + if (VM.ClosureDelayCommand(obj, duration, nextEventId) != 0) + { + Closures.Add(nextEventId++, func); + } + } + + [UnmanagedCallersOnly] + private static void OnNWNXSignal(IntPtr signalPtr) + { + string signal = signalPtr.ReadNullTerminatedString(); + + switch (signal) + { + case "ON_NWNX_LOADED": + instance.Init(); + break; + case "ON_MODULE_LOAD_FINISH": + instance.LoadAndStart(); + break; + case "ON_DESTROY_SERVER": + Log.Info("Server is shutting down..."); + instance.Unload(); + break; + case "ON_DESTROY_SERVER_AFTER": + instance.Shutdown(); + break; + } + } + + [UnmanagedCallersOnly] + private static int OnRunScript(IntPtr scriptPtr, uint oidSelf) + { + int retVal; + string script = scriptPtr.ReadNullTerminatedString(); + objectSelf = oidSelf; + ScriptContexts.Push(oidSelf); + + try + { + retVal = (int)(ScriptDispatchService?.TryExecuteScript(script, oidSelf) ?? ScriptHandleResult.Handled); + } + catch (Exception e) + { + retVal = 0; + Log.Error(e, "An exception occured while executing script {Script}", script); + } + + ScriptContexts.Pop(); + objectSelf = ScriptContexts.Count == 0 ? NWScript.OBJECT_INVALID : ScriptContexts.Peek(); + return retVal; + } + + [UnmanagedCallersOnly] + private static void OnClosure(ulong eid, uint oidSelf) + { + uint old = objectSelf; + objectSelf = oidSelf; + + try + { + Closures[eid].Invoke(); + } + catch (Exception e) + { + Log.Error(e); + } + + Closures.Remove(eid); + objectSelf = old; + } + + [UnmanagedCallersOnly] + private static void OnLoop(ulong _) + { + ServerUpdateLoopService?.Update(); + } + + [UnmanagedCallersOnly] + private static void OnAssertFail(IntPtr messagePtr, IntPtr nativeStackTracePtr) + { + string message = messagePtr.ReadNullTerminatedString(); + string nativeStackTrace = nativeStackTracePtr.ReadNullTerminatedString(); + + StackTrace stackTrace = new StackTrace(true); + Log.Error("An assertion failure occurred in native code.\n" + + $"{message}{nativeStackTrace}\n" + + $"{stackTrace}"); + } + + [UnmanagedCallersOnly] + private static void OnServerCrash(int signal, IntPtr nativeStackTracePtr) + { + string stackTrace = nativeStackTracePtr.ReadNullTerminatedString(); + Version serverVersion = NwServer.Instance.ServerVersion; + + string error = signal switch + { + 4 => "Illegal instruction", + 6 => "Program aborted", + 8 => "Floating point exception", + 11 => "Segmentation fault", + _ => "Unknown error", + }; + + Log.Fatal("\n==============================================================\n" + + " Please file a bug at https://github.com/nwn-dotnet/Anvil/issues\n" + + $" {Assemblies.Anvil.GetName().Name} {AssemblyInfo.VersionInfo.InformationalVersion} has crashed. Fatal error: {error} ({signal})\n" + + $" Using: NWN {serverVersion}, NWN.Core {Assemblies.Core.GetName().Version}, NWN.Native {Assemblies.Native.GetName().Version}\n" + + "==============================================================\n" + + " Managed Backtrace:\n" + + $"{new StackTrace(true)}" + + $"{stackTrace}"); + } + } +} diff --git a/NWN.Anvil/src/main/AnvilCore.cs b/NWN.Anvil/src/main/AnvilCore.cs index 61ed33fd0..d41968308 100644 --- a/NWN.Anvil/src/main/AnvilCore.cs +++ b/NWN.Anvil/src/main/AnvilCore.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -17,15 +16,12 @@ namespace Anvil /// Handles bootstrap and interop between %NWN, %NWN.Core and the %Anvil %API. The entry point of the implementing module should point to this class.
/// Until is called, all APIs are unavailable for usage. /// - public sealed class AnvilCore + public sealed partial class AnvilCore { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); private static AnvilCore instance = null!; - [Inject] - private VirtualMachineFunctionHandler VirtualMachineFunctionHandler { get; init; } = null!; - private readonly IServiceManager serviceManager; private AnvilCore(IServiceManager serviceManager) @@ -53,22 +49,22 @@ private AnvilCore(IServiceManager serviceManager) /// A custom service manager to use instead of the default . For advanced users only. /// The init result code to return back to NWNX. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Init(IntPtr arg, int argLength, IServiceManager? serviceManager = default) + public static unsafe int Init(IntPtr arg, int argLength, IServiceManager? serviceManager = default) { serviceManager ??= new AnvilServiceManager(); instance = new AnvilCore(serviceManager); - NWNCore.NativeEventHandles eventHandles = new NWNCore.NativeEventHandles + NWNCore.NativeEventHandlesUnmanaged eventHandles = new NWNCore.NativeEventHandlesUnmanaged { - Signal = instance.OnNWNXSignal, - RunScript = instance.VirtualMachineFunctionHandler.OnRunScript, - Closure = instance.VirtualMachineFunctionHandler.OnClosure, - MainLoop = instance.VirtualMachineFunctionHandler.OnLoop, - AssertFail = instance.OnAssertFail, - CrashHandler = instance.OnServerCrash, + Signal = &OnNWNXSignal, + RunScript = &OnRunScript, + Closure = &OnClosure, + MainLoop = &OnLoop, + AssertFail = &OnAssertFail, + CrashHandler = &OnServerCrash, }; - return NWNCore.Init(arg, argLength, instance.VirtualMachineFunctionHandler, eventHandles); + return NWNCore.Init(arg, argLength, instance, eventHandles); } /// @@ -137,57 +133,6 @@ private void LoadAndStart() serviceManager.Start(); } - private void OnNWNXSignal(string signal) - { - switch (signal) - { - case "ON_NWNX_LOADED": - Init(); - break; - case "ON_MODULE_LOAD_FINISH": - LoadAndStart(); - break; - case "ON_DESTROY_SERVER": - Log.Info("Server is shutting down..."); - Unload(); - break; - case "ON_DESTROY_SERVER_AFTER": - Shutdown(); - break; - } - } - - private void OnAssertFail(string message, string nativeStackTrace) - { - StackTrace stackTrace = new StackTrace(true); - Log.Error("An assertion failure occurred in native code.\n" + - $"{message}{nativeStackTrace}\n" + - $"{stackTrace}"); - } - - private void OnServerCrash(int signal, string stackTrace) - { - Version serverVersion = NwServer.Instance.ServerVersion; - - string error = signal switch - { - 4 => "Illegal instruction", - 6 => "Program aborted", - 8 => "Floating point exception", - 11 => "Segmentation fault", - _ => "Unknown error", - }; - - Log.Fatal("\n==============================================================\n" + - " Please file a bug at https://github.com/nwn-dotnet/Anvil/issues\n" + - $" {Assemblies.Anvil.GetName().Name} {AssemblyInfo.VersionInfo.InformationalVersion} has crashed. Fatal error: {error} ({signal})\n" + - $" Using: NWN {serverVersion}, NWN.Core {Assemblies.Core.GetName().Version}, NWN.Native {Assemblies.Native.GetName().Version}\n" + - "==============================================================\n" + - " Managed Backtrace:\n" + - $"{new StackTrace(true)}" + - $"{stackTrace}"); - } - private void PrelinkNative() { if (!EnvironmentConfig.NativePrelinkEnabled) diff --git a/NWN.Anvil/src/main/Services/Core/VirtualMachineFunctionHandler.cs b/NWN.Anvil/src/main/Services/Core/VirtualMachineFunctionHandler.cs deleted file mode 100644 index 19e823aed..000000000 --- a/NWN.Anvil/src/main/Services/Core/VirtualMachineFunctionHandler.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using NLog; -using NWN.Core; -using Action = System.Action; - -namespace Anvil.Services -{ - internal sealed class VirtualMachineFunctionHandler : ICoreService, ICoreFunctionHandler - { - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - - [Inject] - private static ScriptDispatchService? ScriptDispatchService { get; set; } - - [Inject] - private static ServerUpdateLoopService? ServerUpdateLoopService { get; set; } - - private readonly Dictionary closures = new Dictionary(); - private readonly Stack scriptContexts = new Stack(); - - private ulong nextEventId; - private uint objectSelf; - - uint ICoreFunctionHandler.ObjectSelf => objectSelf; - - public void OnLoop(ulong _) - { - ServerUpdateLoopService?.Update(); - } - - void ICoreFunctionHandler.ClosureActionDoCommand(uint obj, Action func) - { - if (VM.ClosureActionDoCommand(obj, nextEventId) != 0) - { - closures.Add(nextEventId++, func); - } - } - - void ICoreFunctionHandler.ClosureAssignCommand(uint obj, Action func) - { - if (VM.ClosureAssignCommand(obj, nextEventId) != 0) - { - closures.Add(nextEventId++, func); - } - } - - void ICoreFunctionHandler.ClosureDelayCommand(uint obj, float duration, Action func) - { - if (VM.ClosureDelayCommand(obj, duration, nextEventId) != 0) - { - closures.Add(nextEventId++, func); - } - } - - void ICoreService.Init() {} - - void ICoreService.Load() {} - - void ICoreService.Shutdown() {} - - void ICoreService.Start() {} - - void ICoreService.Unload() {} - - internal void OnClosure(ulong eid, uint oidSelf) - { - uint old = objectSelf; - objectSelf = oidSelf; - - try - { - closures[eid].Invoke(); - } - catch (Exception e) - { - Log.Error(e); - } - - closures.Remove(eid); - objectSelf = old; - } - - internal int OnRunScript(string script, uint oidSelf) - { - int retVal = 0; - objectSelf = oidSelf; - scriptContexts.Push(oidSelf); - - try - { - if (ScriptDispatchService != null) - { - retVal = (int)ScriptDispatchService.TryExecuteScript(script, oidSelf); - } - } - catch (Exception e) - { - Log.Error(e, "An exception occured while executing script {Script}", script); - } - - scriptContexts.Pop(); - objectSelf = scriptContexts.Count == 0 ? NWScript.OBJECT_INVALID : scriptContexts.Peek(); - return retVal; - } - } -} diff --git a/NWN.Anvil/src/main/Services/GameLoop/ServerUpdateLoopService.cs b/NWN.Anvil/src/main/Services/GameLoop/ServerUpdateLoopService.cs index ee497799c..6904a8d76 100644 --- a/NWN.Anvil/src/main/Services/GameLoop/ServerUpdateLoopService.cs +++ b/NWN.Anvil/src/main/Services/GameLoop/ServerUpdateLoopService.cs @@ -11,25 +11,25 @@ internal sealed class ServerUpdateLoopService : IDisposable { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private IUpdateable[] updateables; + private IUpdateable[] updateItems; - public ServerUpdateLoopService(IEnumerable updateables) + public ServerUpdateLoopService(IEnumerable updateItems) { - this.updateables = updateables.OrderBy(updateable => updateable.GetType().GetServicePriority()).ToArray(); + this.updateItems = updateItems.OrderBy(updateable => updateable.GetType().GetServicePriority()).ToArray(); } public void Dispose() { - updateables = Array.Empty(); + updateItems = Array.Empty(); } internal void Update() { - for (int i = 0; i < updateables.Length; i++) + for (int i = 0; i < updateItems.Length; i++) { try { - updateables[i].Update(); + updateItems[i].Update(); } catch (Exception e) { diff --git a/NWN.Anvil/src/main/Services/ScriptDispatch/ScriptDispatchService.cs b/NWN.Anvil/src/main/Services/ScriptDispatch/ScriptDispatchService.cs index c0ec31a1a..6ef4c1b45 100644 --- a/NWN.Anvil/src/main/Services/ScriptDispatch/ScriptDispatchService.cs +++ b/NWN.Anvil/src/main/Services/ScriptDispatch/ScriptDispatchService.cs @@ -1,15 +1,11 @@ -using System; using System.Collections.Generic; using System.Linq; -using NLog; namespace Anvil.Services { [ServiceBinding(typeof(ScriptDispatchService))] internal sealed class ScriptDispatchService { - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private readonly List dispatchers; public ScriptDispatchService(IReadOnlyList dispatchers) @@ -20,25 +16,17 @@ public ScriptDispatchService(IReadOnlyList dispatchers) public ScriptHandleResult TryExecuteScript(string script, uint objectSelf) { - try + ScriptHandleResult result = ScriptHandleResult.NotHandled; + foreach (IScriptDispatcher dispatcher in dispatchers) { - ScriptHandleResult result = ScriptHandleResult.NotHandled; - foreach (IScriptDispatcher dispatcher in dispatchers) + result = dispatcher.ExecuteScript(script, objectSelf); + if (result != ScriptHandleResult.NotHandled) { - result = dispatcher.ExecuteScript(script, objectSelf); - if (result != ScriptHandleResult.NotHandled) - { - break; - } + break; } - - return result; - } - catch (Exception e) - { - Log.Error(e); - return (int)ScriptHandleResult.Handled; } + + return result; } } } diff --git a/NWN.Anvil/src/main/Services/Services/AnvilServiceManager.cs b/NWN.Anvil/src/main/Services/Services/AnvilServiceManager.cs index 346d06a18..e5634ddc5 100644 --- a/NWN.Anvil/src/main/Services/Services/AnvilServiceManager.cs +++ b/NWN.Anvil/src/main/Services/Services/AnvilServiceManager.cs @@ -227,7 +227,6 @@ private void InstallCoreContainer() RegisterCoreService(); RegisterCoreService(); RegisterCoreService(); - RegisterCoreService(); RegisterCoreService(); RegisterCoreService(); RegisterCoreService(); diff --git a/docs/NWN.Anvil.Samples.csproj b/docs/NWN.Anvil.Samples.csproj index 50f1d0292..f7c68d25a 100644 --- a/docs/NWN.Anvil.Samples.csproj +++ b/docs/NWN.Anvil.Samples.csproj @@ -30,7 +30,7 @@ - +