Extensions let you add components to the editor without rebuilding it. An extension is a class library that implements ISkiaExtension; the host discovers it, registers its components in the palette, and exposes its commands.

The Contract

A minimal extension

public sealed class GaugeExtension : ISkiaExtension
{
    public string Id => "acme.gauge";
    public string Name => "Acme Gauge";
    public string Version => "1.2.0";
    public string Description => "Gauge and dial components";

    public IEnumerable<Type> GetComponentTypes()
    {
        yield return typeof(GaugeNode);      // any concrete SkiaComponent
    }

    public void Initialize(ISkiaExtensionContext context)
    {
        context.RegisterComponent(typeof(StatusLampNode), category: "Acme", displayName: "Status Lamp");
        context.RegisterCommand("acme.reset", target => (target as GaugeNode)?.Reset());
        context.Log("Acme gauge ready");
    }
}

The host validates what an extension registers: types must be concrete SkiaComponent subclasses, and anything invalid is skipped with a diagnostic instead of breaking loading.

Loading Extensions

SkiaExtensionHost loads extensions from assemblies or from a folder, and reports what it found:

var host = new SkiaExtensionHost();
host.LoadFromDirectory(Path.Combine(AppContext.BaseDirectory, "Extensions"));

foreach (var extension in host.Extensions)
    Console.WriteLine($"{extension.Name} {extension.Version} ok={extension.Success}");

foreach (var component in host.Components)
    Console.WriteLine($"palette entry: {component.DisplayName} ({component.Category})");
  • Extensions are keyed by Id; loading the same extension twice is ignored.
  • An extension whose constructor or Initialize throws is recorded with its errors — other extensions still load.
  • Every action is written to host.Log for diagnostics.

Palette Integration

The WinForms host scans the extension folder at startup and adds each component to the palette under its declared category, so extension components behave exactly like built-in ones: they can be dragged onto the canvas, selected, configured in the property grid, and saved with the diagram.

Commands

Extensions can also contribute commands. The host invokes them with the current selection, which makes it easy to add context-menu style behaviour (reset, refresh, import) that only applies to the extension's own components.

host.InvokeCommand("acme.reset", selectedComponent);

Related

Extension Lifecycle

The host drives every extension through the same sequence:

  1. Discover — assemblies are scanned for types implementing ISkiaExtension.
  2. Instantiate — each extension type is created with its parameterless constructor.
  3. Initialize — Initialize(context) runs once; register components, commands and diagnostics here.
  4. Register — RegisterComponent adds the type to the registry with an optional palette category and display name; RegisterCommand adds a named action.
  5. Create — components are created on demand through the registry or SkiaExtensionHost.CreateComponent.
  6. Clear — Clear() drops the loaded extensions and their registrations (host shutdown or tests).
Host memberDescription
LoadFromAssemblies(assemblies)Loads every extension found in the given assemblies; returns how many were loaded.
LoadFromDirectory(directory, pattern)Scans a folder for plugin assemblies (default *.dll).
CreateComponent(assemblyQualifiedName)Creates an instance of a contributed component type.
InvokeCommand(name, target)Runs a command registered by an extension against the current selection.
Clear()Unloads extensions and clears registrations.

Packaging and Distribution

Extensions ship as .beepkg packages and are installed through the marketplace, which tracks versions and provenance:

var packages = new ExtensionPackageManager(extensionsRoot);

var result = packages.Install(@"C:\downloads\acme.pnid-1.0.0.beepkg");
if (!result.Success)
    Console.WriteLine(result.Error);

var host = new SkiaExtensionHost();
foreach (var dir in packages.GetLoadDirectories())
    host.LoadFromDirectory(dir);

// Later: upgrade or remove
packages.Update("com.acme.beep.pnid");
packages.Uninstall("com.acme.beep.pnid");

Installation validates the archive (path traversal and decompression-bomb protection), package version compatibility and declared dependencies before anything is copied. See the Extension Marketplace page for the package format.

Extension Checklist

  • Stable reverse-DNS Id, semantic Version, meaningful Name and Description.
  • Every component type derives from SkiaComponent and has a public parameterless constructor.
  • GetComponentTypes() and RegisterComponent agree — the first is what hosts scan, the second is what the palette shows.
  • Component class names never change after release; saved diagrams resolve types by name.
  • Commands are idempotent and safe to call with an empty selection.
  • Use context.Log for diagnostics instead of writing to the console.