Plugin Development

The simplest form of plugin is just a plain C# class. Use a static class if you just wish to expose simple functions:

public static class MyPlugin
{
	public static int GetValue() => 15;
}

1. Plugin Feature Flags

The host exposes a set of capabilities through the PluginFeature flags. Before attempting to use a particular subsystem, a plugin should call GetFeatureSet() and check for the relevant flag. The available features are:

  • Menus (1) The host can create new top‑level menus and nested submenus.
  • DynamicPackages (2) The host can add new node‑types (often called “packages”) into Divooka’s toolbox at runtime.
  • EditorQuery (4) The host allows querying and modifying the runtime state of graphs (e.g., reading node values, connecting/disconnecting pins).
  • ConnectorVisualCustomizations (8) The host permits customizing the appearance of node pin connections (shape + color).
  • CustomNodeSurface (16) [Obsolete] Planned but not yet implemented: full node surface customization (beyond built‑in pin/connector rendering).
  • PreviewProcessor (32) The host supports registering routines that transform arbitrary data into a type that Divooka can preview natively (e.g., image, text, graph).
  • CustomPreviewControl (64) [Obsolete] Planned but not yet implemented: embedding a custom preview control (e.g., custom WPF or Godot control).
  • CustomPreviewHandler (128) The host supports a completely custom “popup” preview handler for any node type (plugin takes full control of how preview is rendered in a separate window).

A plugin should combine these flags with bitwise operations (e.g., if ((features & PluginFeature.Menus) != 0) { … }) to determine what to register or enable at runtime.

Once the plugin has verified that PluginFeature.Menus is available, it can add items to Divooka’s main menu bar and register nested submenus. Each menu entry invokes a callback when clicked.

// Add or remove a top‑level menu item:
void RegisterMenu(
    string menu,                  // unique identifier (e.g. "MyPlugin/Tools")
    string title,                 // user‑facing text
    string? tooltip,              // optional hover text
    Action<IDivookaEngineHostEnvironment> clickCallback,
    bool autoUnregister           // if true, host will remove it on plugin unload
);

void UnregisterMenu(string menu, string title);

// Add or remove a submenu under an existing menu:
void RegisterSubmenu(
    string menu,                  // same “menu” key as above
    string submenu,               // unique identifier for the submenu entry
    string title,
    string? tooltip,
    Action<IDivookaEngineHostEnvironment> clickCallback,
    bool autoUnregister
);

void UnregisterSubMenu(string menu, string submenu, string title);
  • Key points - menu and submenu are identifiers (not necessarily the displayed text). - title is what the end user sees. - autoUnregister allows the host to clean up when the plugin is unloaded.

3. Node Connector Appearance

If ConnectorVisualCustomizations is supported, a plugin can customize how pins (input/output ports) and connector lines look for any given data type.

void RegisterTypeConnectionAppearance(
    Type type,                    // the .NET type to style (e.g., typeof(MyCustomData))
    BuiltinConnectorShape shape,  // one of: HollowCircle, SolidSquare, etc.
    Color color                   // base color for the connector (e.g., System.Drawing.Color.Red)
);
  • BuiltinConnectorShape A small enum of built‑in shapes (HollowCircle, HollowRectangle, HollowTriangle, HollowArrow, HollowSquare, and their “Solid” counterparts).
  • Usage Register this early (e.g. during plugin initialization). Divooka will then render all pins carrying that CLR type using your chosen shape + color.

4. Preview Processing

Divooka allows nodes to provide a small preview of their data (e.g., showing an image, a text snippet, or a graph). Two different extension points exist:

  1. Preview Processor (requires the PreviewProcessor flag)

    void RegisterPreviewProcessor(
        Type type,                     // the data type your plugin wants to handle
        Func<object, object> processor // a function that converts “type” into a built‑in previewable type (e.g., Bitmap, string, List<float>, etc.)
    );
    
    • When to use: If your plugin’s node outputs a custom data type that Divooka cannot render natively, implement a processor that converts it into something the host already knows how to display.
    • Example: Converting a MyImageData instance into a System.Drawing.Bitmap.
  2. Custom Preview Handler (requires the CustomPreviewHandler flag)

    void RegisterCustomPreviewHandler(
        Type instanceType,                 // the .NET type for which you supply full control
        Action<ProcessorNode, object> previewHandler
    );
    
    • When to use: If you need a completely custom popup preview (e.g., an interactive chart or 3D view) that the host cannot handle.
    • Behavior: Divooka will invoke your previewHandler whenever the user requests a preview; it passes you the node instance plus the raw data object. Your code is responsible for showing a window or control, rendering content, wiring up close events, etc. Note: this only works as a separate popup, not inline on the canvas.

5. Dynamic Type / Toolbox Registration

If DynamicPackages is enabled, the plugin can tell Divooka about new node types—both in terms of back‑end functionality (what the node does) and front‑end toolbox entries (where it appears in the UI). The RegisterPackages call unifies several collections of definitions:

void RegisterPackages(
    IEnumerable<ToolboxIndexer.AssemblyToolboxDefinition>? assemblyToolboxes,
    IEnumerable<ToolboxIndexer.FrontendToolboxDefinition>? frontendToolboxes,
    IEnumerable<ToolboxIndexer.TypeToolboxDefinition>? typedToolboxes,
    IEnumerable<ToolboxIndexer.SpecificToolboxEndPointDefinition>? specificEndpoints,
    IEnumerable<ToolboxIndexer.ProceduralContextBaseTypeDefinition>? proceduralContextTypes,
    bool autoUnregister = true
);
  • AssemblyToolboxDefinition Defines a whole assembly of nodes—Divooka can scan an assembly at runtime to find classes annotated as nodes, then automatically populate the toolbox.
  • FrontendToolboxDefinition Describes how you want to group and label nodes in Divooka’s UI (e.g., folder icons, categories).
  • TypeToolboxDefinition Explicitly registers a single .NET type as a node—useful if you want fine‑grained control over the name, icon, and where it appears.
  • SpecificToolboxEndPointDefinition Allows you to place a node in more than one location or under a specialized context menu.
  • ProceduralContextBaseTypeDefinition If your plugin provides a “procedural context” (a stateful environment for running a set of nodes in a custom way), you register the base type here so Divooka knows about your runtime execution model.

Once registered, Divooka’s UI will show these new node types in the toolbox; users can drag and drop them onto the canvas just like built‑in nodes.

6. Procedural Context Execution Runner

For plugins that introduce a custom procedural execution context, use:

void RegisterProceduralContextExecutionRunner(
    Type baseType,            // the abstract base for all of your procedural contexts
    Type instantiatorType     // a factory/runner class that knows how to execute that context
);
  • Purpose Tells Divooka: “Whenever a node graph rooted in a type derived from baseType needs execution, use this instantiatorType to run it.”
  • Behavior Divooka will instantiate your runner at runtime and hand it the node graph; your runner is responsible for traversing nodes, evaluating them in the correct order, and producing outputs.

7. Summary

  1. At Startup
    • Query GetFeatureSet().
    • Conditionally call RegisterMenu/RegisterSubmenu if menus are supported.
    • If you have custom data types, call RegisterTypeConnectionAppearance to give them distinct pin shapes/colors.
    • If you want to add new nodes, prepare your toolbox definitions (assembly scans, type descriptors, etc.) and call RegisterPackages.
  2. During Plugin Initialization
    • If you need to transform node output for preview, register a processor with RegisterPreviewProcessor.
    • If you need full control over preview (e.g., display a 3D model in a floating window), register a handler with RegisterCustomPreviewHandler.
    • If your nodes require a special execution environment, register a ProceduralContextExecutionRunner.
  3. At Shutdown (or Unload)
    • If you set autoUnregister = true, most registrations (menus, packages, preview handlers) will be cleaned up automatically when Divooka unloads the plugin. Otherwise, explicitly call UnregisterMenu, UnregisterSubMenu, and any custom cleanup needed.

Checklist for Plugin Authors

  • Feature Detection

    var features = host.GetFeatureSet();
    if ((features & PluginFeature.Menus) != 0)
        RegisterMenus();
    if ((features & PluginFeature.DynamicPackages) != 0)
        RegisterPackages(...);
    // etc.
    
  • Menus

    • Use RegisterMenu/RegisterSubmenu to add single-click actions.
    • Always supply a unique key (e.g. "MyPlugin/Tools/DoSomething").
  • Node Appearance

    • Use RegisterTypeConnectionAppearance(typeof(MyType), BuiltinConnectorShape.SolidCircle, Color.Blue) to give nodes for MyType a blue circular pin.
  • Preview Support

    • If your node outputs a custom data type, implement Func<object, object> to convert it into an existing previewable object (string, Bitmap, array, etc.).
    • If you need a popup editor or something that Divooka can’t preview natively, implement a custom preview handler.
  • Dynamic Types (Toolbox)

    • Supply one or more toolbox definitions in RegisterPackages.
    • If you simply want to scan an entire assembly, use AssemblyToolboxDefinition. If you want tight control over individual node metadata, use TypeToolboxDefinition.
  • Execution

    • If your plugin includes nodes that must run in a specialized context, register a ProceduralContextExecutionRunner mapping your graph’s base type to a runner/factory.

Once the above pieces are in place, Divooka will automatically show your menus, render your node pins, let users drag your custom node types into graphs, and—when a user clicks “Run” or requests a preview—invoke your converters or custom preview code under the hood.

By grouping functionality under these six areas (feature flags, menus, node appearance, previews, dynamic type registration, and execution runners), you ensure that your plugin integrates smoothly with Divooka’s host environment and follows the intended extension points.

See Also

Additional C# tricks.