swiftlys2/docs/api/ai/

Extensions

SwiftlyS2's extension service loads and unloads native extensions - native libraries that exchange raw pointers with plugins - and holds the pointers they share.

Access it through Core.Extension (APIISwiftlyCore.Extension, type APIIExtensionService).

Loading and Unloading by Id

APIIExtensionService.Load loads the extension whose library sits at extensions/{id}/{id}.dll or extensions/{id}/{id}.so; APIIExtensionService.Unload unloads an already loaded extension and invokes the unload callback it registered. Both take the extension's id and return whether the operation succeeded.

public override void Load(bool hotReload)
{
    if (!Core.Extension.Load("MyExtension"))
    {
        Core.Logger.LogWarning("MyExtension could not be loaded.");
    }
}
 
public override void Unload()
{
    Core.Extension.Unload("MyExtension");
}

Loading and Unloading by Path

APIIExtensionService.LoadFromPath and APIIExtensionService.UnloadFromPath do the same by an absolute path to the library, for extensions that aren't loaded from their own extensions/{id}/ folder. The library's file name without its extension becomes the extension ID, so the example below loads an extension with the ID MyExtension:

string libraryPath = Path.Combine(Core.PluginPath, "MyExtension", "MyExtension.dll");
 
Core.Extension.LoadFromPath(libraryPath);
// ... later ...
Core.Extension.UnloadFromPath(libraryPath);

Both throw ArgumentException when the path is empty or not fully qualified, so pass the full path - a bare file name is rejected.

Shared Pointers

The service also holds a string-keyed registry of native pointers, which is how a plugin and an extension hand pointers to each other. Both sides address an entry by the same string key, and the stored values are nint:

MemberBehavior
APIIExtensionService.SetSharedPointerInserts or replaces the pointer stored under the key. The pointer can be 0.
APIIExtensionService.GetSharedPointerReturns the stored pointer, or 0 when the key does not exist.
APIIExtensionService.HasSharedPointerReports whether the key exists, including keys whose value is 0.
APIIExtensionService.RemoveSharedPointerRemoves the key. Missing keys are ignored.

A missing key and a key holding a zero pointer both read as 0, so check presence with HasSharedPointer whenever the difference matters:

nint extensionApi = Core.Extension.GetSharedPointer("MyExtension.Api");
 
if (!Core.Extension.HasSharedPointer("MyExtension.Api"))
{
    Core.Logger.LogWarning("The extension hasn't published its API yet.");
    return;
}
 
// The key exists here - `extensionApi` can still be 0 if the extension stored a zero pointer.
 
// Publish a pointer of our own for the extension to pick up (an address resolved earlier).
Core.Extension.SetSharedPointer("MyPlugin.Api", pluginPointer);
Core.Extension.RemoveSharedPointer("MyPlugin.Api");

The registry only stores pointer values. It doesn't own the objects they point at and never frees them: storing a pointer over an existing key doesn't free the replaced value, and removing a key doesn't free the stored one. Freeing the object stays the responsibility of whichever side allocated it.

Members

MemberSignatureReturns
APIIExtensionService.Loadbool Load(string id)whether the extension was loaded successfully
APIIExtensionService.Unloadbool Unload(string id)whether the extension was unloaded successfully
APIIExtensionService.LoadFromPathbool LoadFromPath(string absolutePath)whether the extension was loaded successfully; throws ArgumentException when the path is empty or not fully qualified
APIIExtensionService.UnloadFromPathbool UnloadFromPath(string absolutePath)whether the extension was unloaded successfully; throws ArgumentException when the path is empty or not fully qualified
APIIExtensionService.GetSharedPointernint GetSharedPointer(string key)the stored pointer, or 0 when the key does not exist
APIIExtensionService.SetSharedPointervoid SetSharedPointer(string key, nint pointer)-
APIIExtensionService.HasSharedPointerbool HasSharedPointer(string key)whether the key exists, including keys with a 0 value
APIIExtensionService.RemoveSharedPointervoid RemoveSharedPointer(string key)-

GetSharedPointer and RemoveSharedPointer are deliberately lenient: a key that isn't registered reads as 0, and removing one is a no-op instead of an error.

Writing an Extension

Extensions are native libraries, not C# plugins. The API reference for writing one - both the C and the C++ API - is the SwiftlyS2 Extension SDK, whose API.md documents the C and C++ APIs.

Beta Changes

These are available on the beta branch only.

The extension service itself is new on beta: Core.Extension (APIIExtensionService) doesn't exist on stable, so everything on this page - Load, Unload, LoadFromPath, UnloadFromPath and the shared pointer registry - requires a beta build.