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