diff --git a/src/SampSharp.OpenMp.Core/Extensions/Extension.cs b/src/SampSharp.OpenMp.Core/Extensions/Extension.cs index 82dc60e9..058b8c14 100644 --- a/src/SampSharp.OpenMp.Core/Extensions/Extension.cs +++ b/src/SampSharp.OpenMp.Core/Extensions/Extension.cs @@ -43,10 +43,26 @@ protected Extension() /// public void Dispose() { + if (IsDisposed) + { + return; + } + Detach(); FreeUnmanagedResources(); GC.SuppressFinalize(this); + + Dispose(true); + } + + /// + /// 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 method and the finalizer. + /// + /// if called from ; if called from the finalizer. + protected virtual void Dispose(bool disposing) + { + } /// @@ -57,6 +73,8 @@ public void Dispose() // 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); } /// diff --git a/src/SampSharp.OpenMp.Core/IStartupContext.cs b/src/SampSharp.OpenMp.Core/IStartupContext.cs index a5f4e59c..ac53394c 100644 --- a/src/SampSharp.OpenMp.Core/IStartupContext.cs +++ b/src/SampSharp.OpenMp.Core/IStartupContext.cs @@ -42,8 +42,18 @@ public interface IStartupContext /// event EventHandler? Initialized; + /// + /// Occurs when the server is ready to run the gamemode. + /// + event EventHandler? Ready; + /// /// Occurs when an open.mp component is being freed. The component which is being freed is passed as an argument. /// event EventHandler? ComponentFreed; + + /// + /// Resets the unhandled exception handler to the default handler provided by SampSharp. + /// + void ResetExceptionHandler(); } \ No newline at end of file diff --git a/src/SampSharp.OpenMp.Core/SampSharpInitParams.cs b/src/SampSharp.OpenMp.Core/SampSharpInitParams.cs index 4cbd988d..cd1ee998 100644 --- a/src/SampSharp.OpenMp.Core/SampSharpInitParams.cs +++ b/src/SampSharp.OpenMp.Core/SampSharpInitParams.cs @@ -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. /// public readonly unsafe delegate* unmanaged[Cdecl] SetOnFreeComponent; + + /// + /// Gets a pointer to an unmanaged function that configures a ready callback to be invoked. + /// + public readonly unsafe delegate* unmanaged[Cdecl] SetOnReady; } diff --git a/src/SampSharp.OpenMp.Core/StartupContext.cs b/src/SampSharp.OpenMp.Core/StartupContext.cs index a3f134f8..2b9b05bb 100644 --- a/src/SampSharp.OpenMp.Core/StartupContext.cs +++ b/src/SampSharp.OpenMp.Core/StartupContext.cs @@ -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 @@ -21,7 +22,6 @@ public sealed class StartupContext : IStartupContext private const int SupportedApiVersion = 1; private IStartup? _configurator; - private ExceptionHandler _unhandledExceptionHandler; /// /// Initializes a new instance of the class. @@ -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); } } @@ -69,17 +67,26 @@ public StartupContext(SampSharpInitParams init) /// public ExceptionHandler UnhandledExceptionHandler { - get => _unhandledExceptionHandler; + get; set { - _unhandledExceptionHandler = value; + field = value; SampSharpExceptionHandler.SetExceptionHandler(value); } } + /// + public void ResetExceptionHandler() + { + UnhandledExceptionHandler = FallbackExceptionHandler; + } + /// public event EventHandler? Cleanup; + /// + public event EventHandler? Ready; + /// public event EventHandler? Initialized; @@ -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); diff --git a/src/SampSharp.OpenMp.Entities/Events/EventDispatcher.cs b/src/SampSharp.OpenMp.Entities/Events/EventDispatcher.cs index f1c53a45..7f091bde 100644 --- a/src/SampSharp.OpenMp.Entities/Events/EventDispatcher.cs +++ b/src/SampSharp.OpenMp.Entities/Events/EventDispatcher.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; @@ -26,7 +27,8 @@ internal sealed partial class EventDispatcher : IEventDispatcher, IEventService /// /// Initializes a new instance of the class. /// - public EventDispatcher(IServiceProvider serviceProvider, IEntityManager entityManager, ILogger logger, ISystemRegistry systemRegistry, IUnhandledExceptionHandler unhandledExceptionHandler) + public EventDispatcher(IServiceProvider serviceProvider, IEntityManager entityManager, ILogger logger, ISystemRegistry systemRegistry, + IUnhandledExceptionHandler unhandledExceptionHandler) { _serviceProvider = serviceProvider; _entityManager = entityManager; @@ -54,7 +56,7 @@ public void Invoke(string name, params ReadOnlySpan 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); @@ -62,7 +64,7 @@ public void Invoke(string name, params ReadOnlySpan arguments) var result = invoke(arguments); - if(result is Task task) + if (result is Task task) { HandleTask(task); } @@ -79,7 +81,7 @@ public T InvokeAs(string name, T defaultValue, params ReadOnlySpan 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); @@ -98,12 +100,12 @@ public T InvokeAs(string name, T defaultValue, params ReadOnlySpan ar return resultAsT; } - if(result is Task { IsCompleted: true } taskT) + if (result is Task { IsCompleted: true } taskT) { return taskT.Result; } - if(result is Task task) + if (result is Task task) { HandleTask(task); } @@ -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); @@ -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 { @@ -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); } @@ -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; @@ -318,6 +322,7 @@ private sealed record TargetSiteData(Type TargetType, FuncsetOnFreeComponent((on_free_component_fn)cb); } +static void __CDECL __setOnReadyComponent(void** cb) +{ + SampSharpComponent::getInstance()->setOnReady((on_ready_fn)cb); +} + StringView SampSharpComponent::componentName() const { return "SampSharp"; @@ -115,13 +120,17 @@ void SampSharpComponent::onInit(IComponentList* components) } SampSharpInfo info{VERSION_API, componentVersion()}; - SampSharpInitParams init{core_, components, &info, __setOnCleanup, __setOnFreeComponent}; + SampSharpInitParams init{core_, components, &info, __setOnCleanup, __setOnFreeComponent, __setOnReadyComponent}; on_init(init); } void SampSharpComponent::onReady() { + if (on_ready_) + { + on_ready_(); + } } void SampSharpComponent::free() diff --git a/src/sampsharp-component/sampsharp-component.hpp b/src/sampsharp-component/sampsharp-component.hpp index 2a5b9985..f0bd2792 100644 --- a/src/sampsharp-component/sampsharp-component.hpp +++ b/src/sampsharp-component/sampsharp-component.hpp @@ -17,6 +17,7 @@ class SampSharpComponent final : public ISampSharpComponent ManagedHost managed_host_{}; inline static SampSharpComponent* instance_ = nullptr; on_cleanup_fn on_cleanup_ = nullptr; + on_ready_fn on_ready_ = nullptr; on_free_component_fn on_free_component_ = nullptr; public: @@ -40,6 +41,8 @@ class SampSharpComponent final : public ISampSharpComponent void setOnCleanup(on_cleanup_fn cb) { on_cleanup_ = cb; } + void setOnReady(on_ready_fn cb) { on_ready_ = cb; } + void setOnFreeComponent(on_free_component_fn cb) { on_free_component_ = cb; } static SampSharpComponent* getInstance(); diff --git a/test/TestMode.OpenMp.Entities/Systems/TestGameModeSystem.cs b/test/TestMode.OpenMp.Entities/Systems/TestGameModeSystem.cs new file mode 100644 index 00000000..14caea39 --- /dev/null +++ b/test/TestMode.OpenMp.Entities/Systems/TestGameModeSystem.cs @@ -0,0 +1,18 @@ +using SampSharp.Entities; + +namespace TestMode.OpenMp.Entities.Systems; + +public class TestGameModeSystem : ISystem +{ + [Event] + public void OnGameModeInit() + { + Console.WriteLine("OnGameModeInit"); + } + + [Event] + public void OnGameModeExit() + { + Console.WriteLine("GameModeExit"); + } +} \ No newline at end of file diff --git a/test/TestMode.OpenMp.Entities/Systems/TestVehicleSystem.cs b/test/TestMode.OpenMp.Entities/Systems/TestVehicleSystem.cs index 8c685a56..a9e50ca1 100644 --- a/test/TestMode.OpenMp.Entities/Systems/TestVehicleSystem.cs +++ b/test/TestMode.OpenMp.Entities/Systems/TestVehicleSystem.cs @@ -1,5 +1,4 @@ using System.Numerics; -using Microsoft.Extensions.Logging; using SampSharp.Entities; using SampSharp.Entities.SAMP; using TestMode.OpenMp.Entities.Components;