Extensibility & Plugins
Plugin system, AssemblyHandler integration, and dynamic component loading
Beep.Skia extensibility lets third-party developers package diagram families, custom components and commands as standalone extensions. Extensions are loaded at runtime, registered with the component registry, and can be distributed as .beepkg packages through the marketplace.
Table of Contents
Extension Model
An extension is a class implementing ISkiaExtension. It declares an identity and the component types it contributes, then optionally registers them with palette categories and commands:
Minimal Extension
using Beep.Skia.Extensions;
public sealed class PnIDExtension : ISkiaExtension
{
public string Id => "com.acme.beep.pnid";
public string Name => "Piping & Instrumentation";
public string Version => "1.0.0";
public string Description => "ISA S5.1 P&ID node family";
public IEnumerable<Type> GetComponentTypes() => new[]
{
typeof(ValveNode),
typeof(PumpNode),
typeof(TankNode),
typeof(PipeNode)
};
public void Initialize(ISkiaExtensionContext context)
{
context.RegisterComponent(typeof(ValveNode), "P&ID", "Valve");
context.RegisterComponent(typeof(PumpNode), "P&ID", "Pump");
context.RegisterCommand("Reverse Flow", component => { /* ... */ });
context.Log("P&ID extension initialized");
}
}
Component types must derive from SkiaComponent and expose a public parameterless constructor. The ISkiaExtensionContext surface is deliberately small: RegisterComponent, RegisterCommand and Log. See Extension SDK for the full walkthrough.
SkiaComponentRegistry
SkiaComponentRegistry is the central catalog of component types. It stores discovered AssemblyClassDefinition entries and serves the palette, serialization and instantiation paths:
| Member | Purpose |
|---|---|
AddComponent / AddComponents / SetComponents | Register discovered component definitions |
GetComponents() / GetComponentsOrdered() | Enumerate all registered components (ordered for palette display) |
GetComponentByName / GetComponentByGuid | Lookups by class name or identifier |
GetComponentsByAssembly / GetComponentsByType | Filter registrations |
CreateComponentInstance | Instantiate a component from a definition or class name |
IsComponentRegistered / GetAvailableNamespaces | Diagnostics and validation |
Clear() | Reset the registry (used by tests) |
Registry Usage
// Enumerate everything the palette will show
foreach (var def in SkiaComponentRegistry.GetComponentsOrdered())
Console.WriteLine(def.className);
// Create an instance on demand
var component = SkiaComponentRegistry.CreateComponentInstance("ProcessNode");
if (SkiaComponentRegistry.IsComponentRegistered("ProcessNode"))
Console.WriteLine("ProcessNode is available");
Extension Host
SkiaExtensionHost discovers and activates extensions. It scans assemblies or a plugin directory, instantiates each ISkiaExtension, calls Initialize with a registration context, and exposes the aggregate surface to the host application:
Loading Extensions
var host = new SkiaExtensionHost();
// From already-referenced assemblies
int loaded = host.LoadFromAssemblies(new[] { typeof(PnIDExtension).Assembly });
// Or scan a plugin folder
loaded += host.LoadFromDirectory(@"C:\plugins\beep-skia", "*.dll");
// Create a component contributed by an extension
var gauge = host.CreateComponent("Acme.Widgets.CustomGaugeNode, Acme.Widgets");
// Invoke a command registered by an extension
host.InvokeCommand("Reverse Flow", gauge);
AssemblyHandler Integration
Desktop hosts can combine the extension SDK with the shared BeepDM AssemblyHandler to resolve and load plugin assemblies (including NuGet-backed packages) before calling SkiaExtensionHost.LoadFromAssemblies. The extension SDK itself has no dependency on that infrastructure, so headless and server hosts can load extensions directly from files.
ComponentPalette
ComponentPalette is the visual toolbox. It reads the registry, groups components into categories and exposes drag-and-drop events for the host canvas:
LoadComponentsFromRegistry()builds theCategoriescollection fromSkiaComponentRegistry.GetComponentsOrdered().- Each
ComponentCategoryexposes itsName,Iconand a collection ofComponentItementries. - Dragging raises
ComponentDragStarted,ComponentDraggingandComponentDragEndedwith the item and pointer position. - The host creates the instance with
SkiaComponentRegistry.CreateComponentInstance(), positions it and adds it to theDrawingManager.
Wiring the Palette to the Canvas
var palette = new ComponentPalette { X = 10, Y = 50, Width = 320, Height = 600 };
palette.LoadComponentsFromRegistry();
palette.ComponentDragEnded += (s, args) =>
{
if (args.State != ComponentDragState.Ended) return;
var component = SkiaComponentRegistry.CreateComponentInstance(args.Component.ClassDefinition);
if (component == null) return;
component.X = args.MousePosition.X;
component.Y = args.MousePosition.Y;
drawingManager.AddComponent(component);
drawingManager.RequestRedraw();
};
drawingManager.AddComponent(palette);
Marketplace & Packages
Extensions are distributed as .beepkg packages and installed through ExtensionPackageManager. The manager validates the package (including archive safety checks), tracks installed versions and exposes the directories that the extension host should scan:
| Member | Purpose |
|---|---|
Install(packagePath, force) | Install a .beepkg from disk |
InstallFromRegistry(id, version, force) | Install from the local registry |
Update(id, force) | Upgrade an installed extension |
Uninstall(id) | Remove an installed extension |
IsInstalled(id, version) / GetInstalled(id) | Query installation state |
GetLoadDirectories() | Directories to pass to the extension host |
Installing an Extension Package
var packages = new ExtensionPackageManager(extensionsRoot);
var result = packages.Install(@"C:\downloads\acme.pnid-1.0.0.beepkg");
if (!result.Success)
Console.WriteLine(result.Error);
// Load everything that is installed
var host = new SkiaExtensionHost();
foreach (var dir in packages.GetLoadDirectories())
host.LoadFromDirectory(dir);
Packaging Best Practices
- Give the package a stable reverse-DNS id (for example
com.acme.beep.pnid) and use semantic versions. - Keep extension assemblies pure managed code so they load on every supported TFM.
- Declare component types in
GetComponentTypes()and register them inInitialize()so the palette has categories on first load. - Never rename component classes after release — serialized diagrams resolve types by name.
For the full packaging format, dependency rules and safety gates, see the Extension Marketplace guide.