diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/Actor.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/Actor.cs index 308258cc..0f95bb5c 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/Actor.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/Actor.cs @@ -13,7 +13,7 @@ public class Actor : WorldEntity /// /// Initializes a new instance of the class. /// - protected Actor(IActorsComponent actors, IActor actor) : base((IEntity)actor) + protected Actor(IActorsComponent actors, IActor actor) : base(actor.HasValue ? (IEntity)actor : default) { _actors = actors; Resource = actor; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/BaseGangZone.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/BaseGangZone.cs index 85c4fd1e..f3efedb2 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/BaseGangZone.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/BaseGangZone.cs @@ -15,7 +15,7 @@ public abstract class BaseGangZone : IdProvider /// /// Initializes a new instance of the class. /// - protected BaseGangZone(IOmpEntityProvider entityProvider, IGangZonesComponent gangZones, IGangZone gangZone) : base((IIDProvider)gangZone) + protected BaseGangZone(IOmpEntityProvider entityProvider, IGangZonesComponent gangZones, IGangZone gangZone) : base(gangZone.HasValue ? (IIDProvider)gangZone : default) { _entityProvider = entityProvider; Resource = gangZone; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/BasePickup.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/BasePickup.cs index f02f52a7..7da5dd9e 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/BasePickup.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/BasePickup.cs @@ -14,7 +14,7 @@ public abstract class BasePickup : WorldEntity /// /// Initializes a new instance of the class. /// - protected BasePickup(IPickupsComponent pickups, IPickup pickup) : base((IEntity)pickup) + protected BasePickup(IPickupsComponent pickups, IPickup pickup) : base(pickup.HasValue ? (IEntity)pickup : default) { _pickups = pickups; Resource = pickup; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/Class.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/Class.cs index 2f771687..c06bc117 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/Class.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/Class.cs @@ -13,7 +13,7 @@ public class Class : IdProvider /// /// Initializes a new instance of the class. /// - protected Class(IClassesComponent classes, IClass playerClass) : base((IIDProvider)playerClass) + protected Class(IClassesComponent classes, IClass playerClass) : base(playerClass.HasValue ? (IIDProvider)playerClass : default) { _classes = classes; Resource = playerClass; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/GlobalObject.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/GlobalObject.cs index f26c29a8..d5daceaa 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/GlobalObject.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/GlobalObject.cs @@ -14,7 +14,7 @@ public class GlobalObject : WorldEntity /// /// Initializes a new instance of the class. /// - protected GlobalObject(IOmpEntityProvider entityProvider, IObjectsComponent objects, IObject @object) : base((IEntity)@object) + protected GlobalObject(IOmpEntityProvider entityProvider, IObjectsComponent objects, IObject @object) : base(@object.HasValue ? (IEntity)@object : default) { _entityProvider = entityProvider; _objects = objects; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/IdProvider.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/IdProvider.cs index 43747ee1..b971883a 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/IdProvider.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/IdProvider.cs @@ -5,9 +5,17 @@ namespace SampSharp.Entities.SAMP; /// /// Represents a component which provides an identifier. /// -/// The open.mp id provider this component represents. -public abstract class IdProvider(IIDProvider idProvider) : Component +public abstract class IdProvider : Component { + /// + /// Initializes a new instance of the class. + /// + /// The open.mp id provider this component represents. + protected IdProvider(IIDProvider idProvider) + { + Resource = idProvider; + } + private IIDProvider Resource { get @@ -15,7 +23,7 @@ private IIDProvider Resource ObjectDisposedException.ThrowIf(!IsComponentAlive, typeof(IdProvider)); return field; } - } = idProvider; + } /// /// Gets the identifier of this component. diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/Menu.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/Menu.cs index da0afaa2..d4ddd4bc 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/Menu.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/Menu.cs @@ -13,7 +13,7 @@ public class Menu : IdProvider /// /// Initializes a new instance of the class. /// - protected Menu(IMenusComponent menus, IMenu menu, string title) : base((IIDProvider)menu) + protected Menu(IMenusComponent menus, IMenu menu, string title) : base(menu.HasValue ? (IIDProvider)menu : default) { _menus = menus; Resource = menu; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/Npc.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/Npc.cs index 9e904e7b..4a753f1b 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/Npc.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/Npc.cs @@ -27,7 +27,7 @@ public class Npc : IdProvider /// /// Initializes a new instance of the class. /// - protected Npc(INPCComponent npcs, INPC npc) : base((IIDProvider)npc) + protected Npc(INPCComponent npcs, INPC npc) : base(npc.HasValue ? (IIDProvider)npc : default) { _npcs = npcs; Resource = npc; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/Player.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/Player.cs index 142adddc..7a04f91d 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/Player.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/Player.cs @@ -17,7 +17,7 @@ public class Player : WorldEntity /// /// Constructs an instance of , should be used internally. /// - protected Player(IOmpEntityProvider entityProvider, IPlayer player) : base((IEntity)player) + protected Player(IOmpEntityProvider entityProvider, IPlayer player) : base(player.HasValue ? (IEntity)player : default) { _entityProvider = entityProvider; Resource = player; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerObject.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerObject.cs index 31086b5a..ff31f4f8 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerObject.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerObject.cs @@ -14,7 +14,9 @@ public class PlayerObject : WorldEntity /// /// Initializes a new instance of the class. /// - protected PlayerObject(IOmpEntityProvider entityProvider, IPlayerObjectData playerObjects, IPlayerObject playerObject) : base((IEntity)playerObject) + protected PlayerObject(IOmpEntityProvider entityProvider, IPlayerObjectData playerObjects, IPlayerObject playerObject) : base(playerObject.HasValue + ? (IEntity)playerObject + : default) { _entityProvider = entityProvider; _playerObjects = playerObjects; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerTextDraw.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerTextDraw.cs index b801f6c1..ee83950f 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerTextDraw.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerTextDraw.cs @@ -13,7 +13,7 @@ public class PlayerTextDraw : IdProvider /// /// Initializes a new instance of the class. /// - protected PlayerTextDraw(IPlayerTextDrawData playerTextDraws, IPlayerTextDraw playerTextDraw) : base((IIDProvider)playerTextDraw) + protected PlayerTextDraw(IPlayerTextDrawData playerTextDraws, IPlayerTextDraw playerTextDraw) : base(playerTextDraw.HasValue ? (IIDProvider)playerTextDraw : default) { _playerTextDraws = playerTextDraws; Resource = playerTextDraw; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerTextLabel.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerTextLabel.cs index 434e97dc..8cca572d 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerTextLabel.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/PlayerTextLabel.cs @@ -14,7 +14,7 @@ public class PlayerTextLabel : WorldEntity /// /// Initializes a new instance of the class. /// - protected PlayerTextLabel(IOmpEntityProvider entityProvider, IPlayerTextLabelData playerTextLabels, IPlayerTextLabel playerTextLabel) : base((IEntity)playerTextLabel) + protected PlayerTextLabel(IOmpEntityProvider entityProvider, IPlayerTextLabelData playerTextLabels, IPlayerTextLabel playerTextLabel) : base(playerTextLabel.HasValue ? (IEntity)playerTextLabel : default) { _entityProvider = entityProvider; _playerTextLabels = playerTextLabels; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/TextDraw.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/TextDraw.cs index c7fc4e47..1b16a04f 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/TextDraw.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/TextDraw.cs @@ -13,7 +13,7 @@ public class TextDraw : IdProvider /// /// Initializes a new instance of the class. /// - protected TextDraw(ITextDrawsComponent textDraws, ITextDraw textDraw) : base((IIDProvider)textDraw) + protected TextDraw(ITextDrawsComponent textDraws, ITextDraw textDraw) : base(textDraw.HasValue ? (IIDProvider)textDraw : default) { _textDraws = textDraws; Resource = textDraw; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/TextLabel.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/TextLabel.cs index 6b7d368f..5b46c34f 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/TextLabel.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/TextLabel.cs @@ -14,7 +14,7 @@ public class TextLabel : WorldEntity /// /// Initializes a new instance of the class. /// - protected TextLabel(IOmpEntityProvider entityProvider, ITextLabelsComponent textLabels, ITextLabel textLabel) : base((IEntity)textLabel) + protected TextLabel(IOmpEntityProvider entityProvider, ITextLabelsComponent textLabels, ITextLabel textLabel) : base(textLabel.HasValue ? (IEntity)textLabel : default) { _entityProvider = entityProvider; _textLabels = textLabels; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/Vehicle.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/Vehicle.cs index 273ba726..71204d00 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/Vehicle.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/Vehicle.cs @@ -14,7 +14,7 @@ public class Vehicle : WorldEntity /// /// Initializes a new instance of the class. /// - protected Vehicle(IOmpEntityProvider entityProvider, IVehiclesComponent vehicles, IVehicle vehicle) : base((IEntity)vehicle) + protected Vehicle(IOmpEntityProvider entityProvider, IVehiclesComponent vehicles, IVehicle vehicle) : base(vehicle.HasValue ? (IEntity)vehicle : default) { _entityProvider = entityProvider; _vehicles = vehicles; diff --git a/src/SampSharp.OpenMp.Entities/SAMP/Components/WorldEntity.cs b/src/SampSharp.OpenMp.Entities/SAMP/Components/WorldEntity.cs index 2b8a601f..f686f213 100644 --- a/src/SampSharp.OpenMp.Entities/SAMP/Components/WorldEntity.cs +++ b/src/SampSharp.OpenMp.Entities/SAMP/Components/WorldEntity.cs @@ -12,7 +12,7 @@ public abstract class WorldEntity : IdProvider /// Initializes a new instance of the class. /// /// The open.mp entity this component represents. - protected WorldEntity(IEntity entity) : base((IIDProvider)entity) + protected WorldEntity(IEntity entity) : base(entity.HasValue ? (IIDProvider)entity : default) { Resource = entity; } diff --git a/src/sampsharp-component/compat.hpp b/src/sampsharp-component/compat.hpp index f0c0ac27..b56d81dc 100644 --- a/src/sampsharp-component/compat.hpp +++ b/src/sampsharp-component/compat.hpp @@ -1,4 +1,3 @@ - #pragma once #include diff --git a/src/sampsharp-component/sampsharp-api.hpp b/src/sampsharp-component/sampsharp-api.hpp new file mode 100644 index 00000000..ed60965f --- /dev/null +++ b/src/sampsharp-component/sampsharp-api.hpp @@ -0,0 +1,72 @@ +#pragma once + +// This header defines the API for the SampSharp managed components. Through this API the managed component is provided +// with access to the open.mp infrastructure and ties the managed component to the lifecycle of this unmanaged component. +// This API is implemented by the SampSharp.Sdk/SampSharp.OpenMp.Core packages. +#include +#include + +#include "platform.hpp" + +// Provides version information about the SampSharp component and the hosted API. This is used to check for version +// mismatches that would cause launch failure. +struct SampSharpInfo +{ + SampSharpInfo(int api_version, SemanticVersion version) : + size(sizeof(SampSharpInfo)), + api_version(api_version), + version(version) + { + } + + // sizeof(SampSharpInfo) for backwards compatibility + size_t size; + // version of SampSharp component <> hosted API. Version mismatch will cause launch failure. + int api_version; + // version of the SampSharp component + SemanticVersion version; +}; + +// Delegate type for the cleanup callback, called when the component is unloaded during shutdown. +typedef void(API_CALLTYPE* on_cleanup_fn)(); + +// Delegate type for the on_free_component callback, called when a component is freed. +typedef void(API_CALLTYPE* on_free_component_fn)(IComponent* component); + +// Delegate type for the configure callback function, used to set the on_cleanup and on_free_component callbacks. +typedef void(API_CALLTYPE* configure_callback_fn)(void** cb); + +// Parameters for initializing the managed component, passed to the on_init callback. +// This is a struct instead of parameters for backwards compatibility, so parameters can be added without breaking the +// API. The size field is used to check which version of the struct is being used, so new fields should only be added +// at the end. +struct SampSharpInitParams +{ + SampSharpInitParams(ICore* core, IComponentList* componentList, SampSharpInfo* info, + configure_callback_fn setOnCleanup, configure_callback_fn setOnFreeComponent) : + size(sizeof(SampSharpInitParams)), + info(info), + core(core), + componentList(componentList), + setOnCleanup(setOnCleanup), + setOnFreeComponent(setOnFreeComponent) + { + } + + // sizeof(SampSharpInitParams) for backwards compatibility + size_t size; + // Version info about the SampSharp component + SampSharpInfo* info; + // Pointer to open.mp ICore + ICore* core; + // Pointer to open.mp IComponentList + IComponentList* componentList; + // Function to configure a cleanup callback. Callback should have signature on_cleanup_fn. + configure_callback_fn setOnCleanup; + // Function to configure a onFreeComponent callback. Callback should have signature configure_callback_fn. + configure_callback_fn setOnFreeComponent; +}; + +// Delegate type for the on_init callback, called when the component is loaded. The callback should initialize the +// managed component and optionally set the on_cleanup and on_free_component callbacks. +typedef void(CORECLR_DELEGATE_CALLTYPE* on_init_fn)(SampSharpInitParams); \ No newline at end of file diff --git a/src/sampsharp-component/sampsharp-component.cpp b/src/sampsharp-component/sampsharp-component.cpp index 7993d9d7..c3d82f8d 100644 --- a/src/sampsharp-component/sampsharp-component.cpp +++ b/src/sampsharp-component/sampsharp-component.cpp @@ -78,6 +78,10 @@ void SampSharpComponent::onInit(IComponentList* components) const char* error = nullptr; + // 1) Load hostfxr: The .NET host framework resolver provides functionality for resolving and loading the correct + // .NET runtime version based on the runtime configuration provided for the gamemode assembly. Hostfxr is part of + // the .NET runtime and is loaded from the installation directory of the runtime. + // The path to hostfxr is resolved using the thin nethost static library we include in our component. if (!managed_host_.initialize(&error)) { core_->logLn(Error, @@ -86,16 +90,21 @@ void SampSharpComponent::onInit(IComponentList* components) return; } + // 2) Load .NET runtime: Load the .NET runtime using hostfxr using the .runtimeconfig.json of the gamemode assembly. if (!managed_host_.loadFor(directory, assembly, &error)) { core_->logLn(Error, - "Failed to initialize the .NET runtime for '%s/%s'. Is the '*.runtimeconfig.json' file available? " - "Is the .NET runtime available?", + "Failed to initialize the .NET runtime for '%s/%s'. " + "This may happen when the configured gamemode assembly or directory is incorrect, when the runtime" + " configuration file (.runtimeconfig.json) for the gamemode assembly cannot be found or when the " + "required .NET runtime version ins not installed. ", directory.to_string().c_str(), assembly.to_string().c_str()); - core_->logLn(Error, "Error message: %s", error); + core_->logLn(Error, "Error message returned by the .NET host framework resolver: %s", error); return; } + // 3) Load gamemode: Load the gamemode assembly and locate our SampSharp entrypoint. A function pointer to the + // managed entry point is retrieved. on_init_fn on_init; if (!managed_host_.getEntryPoint(full_entry_point, entry_point_method, reinterpret_cast(&on_init), &error)) { diff --git a/src/sampsharp-component/sampsharp-component.hpp b/src/sampsharp-component/sampsharp-component.hpp index 7b9f681f..2a5b9985 100644 --- a/src/sampsharp-component/sampsharp-component.hpp +++ b/src/sampsharp-component/sampsharp-component.hpp @@ -3,53 +3,7 @@ #include #include "managed-host.hpp" -#include "platform.hpp" - -struct SampSharpInfo -{ - SampSharpInfo(int api_version, SemanticVersion version) : - size(sizeof(SampSharpInfo)), - api_version(api_version), - version(version) - { - } - - // sizeof(SampSharpInfo) for backwards compatibility - size_t size; - // version of SampSharp component <> hosted API. Version mismatch will cause launch failure. - int api_version; - // version of the SampSharp component - SemanticVersion version; -}; - -typedef void(API_CALLTYPE* on_cleanup_fn)(); -typedef void(API_CALLTYPE* on_free_component_fn)(IComponent* component); - -typedef void(API_CALLTYPE* configure_callback_fn)(void** cb); - -struct SampSharpInitParams -{ - SampSharpInitParams(ICore* core, IComponentList* componentList, SampSharpInfo* info, - configure_callback_fn setOnCleanup, configure_callback_fn setOnFreeComponent) : - size(sizeof(SampSharpInitParams)), - info(info), - core(core), - componentList(componentList), - setOnCleanup(setOnCleanup), - setOnFreeComponent(setOnFreeComponent) - { - } - - // sizeof(SampSharpInitParams) for backwards compatibility - size_t size; - SampSharpInfo* info; - ICore* core; - IComponentList* componentList; - configure_callback_fn setOnCleanup; - configure_callback_fn setOnFreeComponent; -}; - -typedef void(CORECLR_DELEGATE_CALLTYPE* on_init_fn)(SampSharpInitParams); +#include "sampsharp-api.hpp" struct ISampSharpComponent : IComponent { diff --git a/test/SampSharp.OpenMp.Entities.Commands.Tests/TestExtensions.cs b/test/SampSharp.OpenMp.Entities.Commands.Tests/TestExtensions.cs index d29d87ac..95ab1a3a 100644 --- a/test/SampSharp.OpenMp.Entities.Commands.Tests/TestExtensions.cs +++ b/test/SampSharp.OpenMp.Entities.Commands.Tests/TestExtensions.cs @@ -54,4 +54,4 @@ public static void ShouldHaveCount(this IReadOnlyDictionary