Skip to content
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
18 changes: 18 additions & 0 deletions src/SampSharp.OpenMp.Core/Extensions/Extension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,26 @@
/// </summary>
public void Dispose()
{
if (IsDisposed)
{
return;
}

Detach();

FreeUnmanagedResources();
GC.SuppressFinalize(this);

Dispose(true);
}

/// <summary>
/// When overridden in a derived class, performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. This method is called by the public <see cref="Dispose" /> method and the finalizer.

Check warning on line 60 in src/SampSharp.OpenMp.Core/Extensions/Extension.cs

View workflow job for this annotation

GitHub Actions / build

Ambiguous reference in cref attribute: 'Dispose'. Assuming 'SampSharp.OpenMp.Core.Extension.Dispose()', but could have also matched other overloads including 'SampSharp.OpenMp.Core.Extension.Dispose(bool)'.

Check warning on line 60 in src/SampSharp.OpenMp.Core/Extensions/Extension.cs

View workflow job for this annotation

GitHub Actions / build

Ambiguous reference in cref attribute: 'Dispose'. Assuming 'SampSharp.OpenMp.Core.Extension.Dispose()', but could have also matched other overloads including 'SampSharp.OpenMp.Core.Extension.Dispose(bool)'.
/// </summary>
/// <param name="disposing"><see langword="true" /> if called from <see cref="Dispose" />; <see langword="false" /> if called from the finalizer.</param>

Check warning on line 62 in src/SampSharp.OpenMp.Core/Extensions/Extension.cs

View workflow job for this annotation

GitHub Actions / build

Ambiguous reference in cref attribute: 'Dispose'. Assuming 'SampSharp.OpenMp.Core.Extension.Dispose()', but could have also matched other overloads including 'SampSharp.OpenMp.Core.Extension.Dispose(bool)'.

Check warning on line 62 in src/SampSharp.OpenMp.Core/Extensions/Extension.cs

View workflow job for this annotation

GitHub Actions / build

Ambiguous reference in cref attribute: 'Dispose'. Assuming 'SampSharp.OpenMp.Core.Extension.Dispose()', but could have also matched other overloads including 'SampSharp.OpenMp.Core.Extension.Dispose(bool)'.
protected virtual void Dispose(bool disposing)
{

}

/// <summary>
Expand All @@ -57,6 +73,8 @@
// Theoretically, this should never be called, as one of the unmanaged resources is a GC handle pointing to this
// object. But just to be sure, we'll free the resources here as well.
FreeUnmanagedResources();

Dispose(false);
}

/// <summary>
Expand Down
10 changes: 10 additions & 0 deletions src/SampSharp.OpenMp.Core/IStartupContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,18 @@ public interface IStartupContext
/// </summary>
event EventHandler? Initialized;

/// <summary>
/// Occurs when the server is ready to run the gamemode.
/// </summary>
event EventHandler? Ready;

/// <summary>
/// Occurs when an open.mp component is being freed. The component which is being freed is passed as an argument.
/// </summary>
event EventHandler<IComponent>? ComponentFreed;

/// <summary>
/// Resets the unhandled exception handler to the default handler provided by SampSharp.
/// </summary>
void ResetExceptionHandler();
}
5 changes: 5 additions & 0 deletions src/SampSharp.OpenMp.Core/SampSharpInitParams.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,9 @@ public readonly ref struct SampSharpInitParams
/// Gets a pointer to an unmanaged function that configures a callback to be invoked when a component is being freed.
/// </summary>
public readonly unsafe delegate* unmanaged[Cdecl]<nint, void> SetOnFreeComponent;

/// <summary>
/// Gets a pointer to an unmanaged function that configures a ready callback to be invoked.
/// </summary>
public readonly unsafe delegate* unmanaged[Cdecl]<nint, void> SetOnReady;
}
36 changes: 27 additions & 9 deletions src/SampSharp.OpenMp.Core/StartupContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public sealed class StartupContext : IStartupContext
// Delegates must be kept in memory to prevent GC
// ReSharper disable PrivateFieldCanBeConvertedToLocalVariable
private readonly Action _cleanup;
private readonly Action _ready;
private readonly OnFreeComponentDelegate _freeComponent;
// ReSharper restore PrivateFieldCanBeConvertedToLocalVariable

Expand All @@ -21,7 +22,6 @@ public sealed class StartupContext : IStartupContext
private const int SupportedApiVersion = 1;

private IStartup? _configurator;
private ExceptionHandler _unhandledExceptionHandler;

/// <summary>
/// Initializes a new instance of the <see cref="StartupContext" /> class.
Expand All @@ -33,23 +33,21 @@ public StartupContext(SampSharpInitParams init)
Core = init.Core;
ComponentList = init.ComponentList;
Info = init.Info;
_unhandledExceptionHandler = (context, ex) =>
{
Core.LogLine(LogLevel.Error, $"Unhandled exception during {context}:");
Core.LogLine(LogLevel.Error, ex.ToString());
};
SampSharpExceptionHandler.SetExceptionHandler(_unhandledExceptionHandler);
UnhandledExceptionHandler = FallbackExceptionHandler;

// Keep delegates in memory to prevent GC
_cleanup = OnCleanup;
_ready = OnReady;
_freeComponent = OnFreeComponent;

var cleanupPtr = Marshal.GetFunctionPointerForDelegate(_cleanup);
var readyPtr = Marshal.GetFunctionPointerForDelegate(_ready);
var freeComponentPtr = Marshal.GetFunctionPointerForDelegate(_freeComponent);

unsafe
{
init.SetOnCleanup(cleanupPtr);
init.SetOnReady(readyPtr);
init.SetOnFreeComponent(freeComponentPtr);
}
}
Expand All @@ -69,17 +67,26 @@ public StartupContext(SampSharpInitParams init)
/// <inheritdoc />
public ExceptionHandler UnhandledExceptionHandler
{
get => _unhandledExceptionHandler;
get;
set
{
_unhandledExceptionHandler = value;
field = value;
SampSharpExceptionHandler.SetExceptionHandler(value);
}
}

/// <inheritdoc />
public void ResetExceptionHandler()
{
UnhandledExceptionHandler = FallbackExceptionHandler;
}

/// <inheritdoc />
public event EventHandler? Cleanup;

/// <inheritdoc />
public event EventHandler? Ready;

/// <inheritdoc />
public event EventHandler? Initialized;

Expand Down Expand Up @@ -131,11 +138,22 @@ private static void VersionCheck(SampSharpInitParams init)
}
}

private void FallbackExceptionHandler(string context, Exception ex)
{
Core.LogLine(LogLevel.Error, $"Unhandled exception during {context}:");
Core.LogLine(LogLevel.Error, ex.ToString());
}

private void OnCleanup()
{
Cleanup?.Invoke(this, EventArgs.Empty);
}

private void OnReady()
{
Ready?.Invoke(this, EventArgs.Empty);
}

private void OnFreeComponent(IComponent component)
{
ComponentFreed?.Invoke(this, component);
Expand Down
33 changes: 19 additions & 14 deletions src/SampSharp.OpenMp.Entities/Events/EventDispatcher.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -26,7 +27,8 @@ internal sealed partial class EventDispatcher : IEventDispatcher, IEventService
/// <summary>
/// Initializes a new instance of the <see cref="EventDispatcher" /> class.
/// </summary>
public EventDispatcher(IServiceProvider serviceProvider, IEntityManager entityManager, ILogger<EventDispatcher> logger, ISystemRegistry systemRegistry, IUnhandledExceptionHandler unhandledExceptionHandler)
public EventDispatcher(IServiceProvider serviceProvider, IEntityManager entityManager, ILogger<EventDispatcher> logger, ISystemRegistry systemRegistry,
IUnhandledExceptionHandler unhandledExceptionHandler)
{
_serviceProvider = serviceProvider;
_entityManager = entityManager;
Expand Down Expand Up @@ -54,15 +56,15 @@ public void Invoke(string name, params ReadOnlySpan<object> arguments)
return;
}

if(!@event.Cache.TryGetValue(NullValue.Instance, out var invoke))
if (!@event.Cache.TryGetValue(NullValue.Instance, out var invoke))
{
invoke = CreateEventInvoke(@event, null);
@event.Cache.TryAdd(NullValue.Instance, invoke);
}

var result = invoke(arguments);

if(result is Task task)
if (result is Task task)
{
HandleTask(task);
}
Expand All @@ -79,7 +81,7 @@ public T InvokeAs<T>(string name, T defaultValue, params ReadOnlySpan<object> ar
}

var defaultKey = defaultValue ?? (object)NullValue.Instance;
if(!@event.Cache.TryGetValue(defaultKey, out var invoke))
if (!@event.Cache.TryGetValue(defaultKey, out var invoke))
{
invoke = CreateEventInvoke(@event, defaultValue);
@event.Cache.TryAdd(defaultKey, invoke);
Expand All @@ -98,12 +100,12 @@ public T InvokeAs<T>(string name, T defaultValue, params ReadOnlySpan<object> ar
return resultAsT;
}

if(result is Task<T> { IsCompleted: true } taskT)
if (result is Task<T> { IsCompleted: true } taskT)
{
return taskT.Result;
}

if(result is Task task)
if (result is Task task)
{
HandleTask(task);
}
Expand Down Expand Up @@ -148,10 +150,11 @@ private Event GetOrCreateEvent(string name)
return @event;
}

[DebuggerHidden]
private object? InnerInvoke(EventContext context, Event @event, object? defaultResult)
{
object? result = null;

foreach (var targetSite in @event.TargetSites)
{
targetSite.Target ??= _serviceProvider.GetService(targetSite.TargetType);
Expand Down Expand Up @@ -240,7 +243,7 @@ private TargetSiteData CreateTargetSite(Type target, MethodInfo method, MethodPa
var compiled = MethodInvokerFactory.Compile(method, parameterInfos);
var targetSiteName = $"{method.DeclaringType?.FullName}.{method.Name}";

return new TargetSiteData(target, (instance, eventContext) =>
return new TargetSiteData(target, [DebuggerHidden](instance, eventContext) =>
{
try
{
Expand All @@ -250,9 +253,10 @@ private TargetSiteData CreateTargetSite(Type target, MethodInfo method, MethodPa
return compiled.Invoke(instance, args, eventContext.EventServices, _entityManager);
}

LogEventArgumentsCountMismatch(eventContext.Name, args.Length, targetSiteName, string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}")), sourceParamCount);
LogEventArgumentsCountMismatch(eventContext.Name, args.Length, targetSiteName,
string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}")), sourceParamCount);
}
catch(Exception ex)
catch (Exception ex)
{
SampSharpExceptionHandler.HandleException(targetSiteName, ex);
}
Expand All @@ -269,20 +273,20 @@ private EventInvokeDelegate CreateEventInvoke(Event @event, object? defaultResul
var context = new EventContextImpl(@event.Name, _serviceProvider);

// In order to chain the middleware from first to last, the middleware must be nested from last to first
EventDelegate invoke = ctx => InnerInvoke(ctx, @event, defaultResult);
EventDelegate invoke = [DebuggerHidden](ctx) => InnerInvoke(ctx, @event, defaultResult);
for (var i = @event.Middleware.Count - 1; i >= 0; i--)
{
invoke = @event.Middleware[i](invoke);
}

return args =>
return [DebuggerHidden](args) =>
{
try
{
context.SetArguments(args);
return invoke(context);
}
catch(Exception ex)
catch (Exception ex)
{
SampSharpExceptionHandler.HandleException(@event.Name, ex);
return null;
Expand Down Expand Up @@ -318,6 +322,7 @@ private sealed record TargetSiteData(Type TargetType, Func<object, EventContext,
[LoggerMessage(LogLevel.Debug, "Adding event listener on {Type}.{Method}.")]
private partial void LogAddingEventListener(Type? type, string method);

[LoggerMessage(LogLevel.Error, "Event \"{EventName}\" argument count mismatch: dispatcher passed {ArgsLength} arg(s), handler {TargetSite}({HandlerParams}) expects {SourceParamCount}")]
[LoggerMessage(LogLevel.Error,
"Event \"{EventName}\" argument count mismatch: dispatcher passed {ArgsLength} arg(s), handler {TargetSite}({HandlerParams}) expects {SourceParamCount}")]
partial void LogEventArgumentsCountMismatch(string eventName, int argsLength, string targetSite, string handlerParams, int sourceParamCount);
}
22 changes: 11 additions & 11 deletions src/SampSharp.OpenMp.Entities/Hosting/EcsHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,27 @@

context.UnhandledExceptionHandler = UnhandledExceptionHandler;

context.Cleanup += ContextOnCleanup;

LoadSystems();

// Fire initial event
OnGameModeInit();
}

private void ContextOnCleanup(object? sender, EventArgs e)
protected override void Dispose(bool disposing)

Check warning on line 29 in src/SampSharp.OpenMp.Entities/Hosting/EcsHost.cs

View workflow job for this annotation

GitHub Actions / build

Ensure that method 'void EcsHost.Dispose(bool disposing)' calls 'base.Dispose(bool)' in all possible control flow paths (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2215)

Check warning on line 29 in src/SampSharp.OpenMp.Entities/Hosting/EcsHost.cs

View workflow job for this annotation

GitHub Actions / build

Ensure that method 'void EcsHost.Dispose(bool disposing)' calls 'base.Dispose(bool)' in all possible control flow paths (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2215)
{
_context?.Cleanup -= ContextOnCleanup;
_context = null;
if (disposing)
{
OnGameModeExit();

OnGameModeExit();
if (_serviceProvider is IDisposable disposable)
{
disposable.Dispose();
}

if (_serviceProvider is IDisposable disposable)
{
disposable.Dispose();
_context?.ResetExceptionHandler();
_context = null;
_serviceProvider = null;
}

_serviceProvider = null;
}

private void UnhandledExceptionHandler(string context, Exception exception)
Expand Down
8 changes: 7 additions & 1 deletion src/SampSharp.OpenMp.Entities/Hosting/HostContextBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal sealed class HostContextBinder(IStartupContext context, EcsHostBuilder
public void Dispose()
{
context.Initialized -= OnContextInitialized;
context.Ready -= OnContextReady;
context.Cleanup -= OnContextCleanup;

_host?.Dispose();
Expand All @@ -20,14 +21,19 @@ public void Dispose()
public void Bind()
{
context.Initialized += OnContextInitialized;
context.Ready += OnContextReady;
context.Cleanup += OnContextCleanup;
}

private void OnContextInitialized(object? sender, EventArgs e)
{
_host = hostBuilder.Build(context);
context.Core.AddExtension(_host);
_host.Start(context);
}

private void OnContextReady(object? sender, EventArgs e)
{
_host?.Start(context);
}

private void OnContextCleanup(object? sender, EventArgs e)
Expand Down
Loading
Loading