Skip to content
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

Implement unmanaged event handlers. Optimizations #738

Merged
merged 5 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion NWN.Anvil.TestRunner/NWN.Anvil.TestRunner.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="NWN.Core" Version="8193.35.20" PrivateAssets="compile" />
<PackageReference Include="NWN.Core" Version="8193.35.21" PrivateAssets="compile" />
<PackageReference Include="NWN.Native" Version="8193.35.9" PrivateAssets="compile" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion NWN.Anvil.Tests/NWN.Anvil.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="NWN.Core" Version="8193.35.20" PrivateAssets="compile" />
<PackageReference Include="NWN.Core" Version="8193.35.21" PrivateAssets="compile" />
<PackageReference Include="NWN.Native" Version="8193.35.9" PrivateAssets="compile" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion NWN.Anvil/NWN.Anvil.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.5" />
<PackageReference Include="Paket.Core" Version="7.2.1" PrivateAssets="all" />
<PackageReference Include="NWN.Core" Version="8193.35.20" PrivateAssets="compile" />
<PackageReference Include="NWN.Core" Version="8193.35.21" PrivateAssets="compile" />
<PackageReference Include="NWN.Native" Version="8193.35.9" PrivateAssets="compile" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" />
</ItemGroup>
Expand Down
162 changes: 162 additions & 0 deletions NWN.Anvil/src/main/AnvilCore.FunctionHandlers.cs
Original file line number Diff line number Diff line change
@@ -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<ulong, Action> Closures = new Dictionary<ulong, Action>();
private static readonly Stack<uint> ScriptContexts = new Stack<uint>();

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}");
}
}
}
75 changes: 10 additions & 65 deletions NWN.Anvil/src/main/AnvilCore.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand All @@ -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.<br/>
/// Until <see cref="Init(IntPtr, int, IServiceManager)"/> is called, all APIs are unavailable for usage.
/// </summary>
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)
Expand Down Expand Up @@ -53,22 +49,22 @@ private AnvilCore(IServiceManager serviceManager)
/// <param name="serviceManager">A custom service manager to use instead of the default <see cref="AnvilServiceManager"/>. For advanced users only.</param>
/// <returns>The init result code to return back to NWNX.</returns>
[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);
}

/// <summary>
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading