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.
2. Menu Registration
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
-
menuandsubmenuare identifiers (not necessarily the displayed text). -titleis what the end user sees. -autoUnregisterallows 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)
);
BuiltinConnectorShapeA 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:
Preview Processor (requires the
PreviewProcessorflag)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
MyImageDatainstance into aSystem.Drawing.Bitmap.
Custom Preview Handler (requires the
CustomPreviewHandlerflag)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
previewHandlerwhenever 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
);
AssemblyToolboxDefinitionDefines a whole assembly of nodes—Divooka can scan an assembly at runtime to find classes annotated as nodes, then automatically populate the toolbox.FrontendToolboxDefinitionDescribes how you want to group and label nodes in Divooka’s UI (e.g., folder icons, categories).TypeToolboxDefinitionExplicitly registers a single .NET type as a node—useful if you want fine‑grained control over the name, icon, and where it appears.SpecificToolboxEndPointDefinitionAllows you to place a node in more than one location or under a specialized context menu.ProceduralContextBaseTypeDefinitionIf 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
baseTypeneeds execution, use thisinstantiatorTypeto 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
- At Startup
- Query
GetFeatureSet(). - Conditionally call
RegisterMenu/RegisterSubmenuif menus are supported. - If you have custom data types, call
RegisterTypeConnectionAppearanceto 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.
- Query
- 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.
- If you need to transform node output for preview, register a processor with
- 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 callUnregisterMenu,UnregisterSubMenu, and any custom cleanup needed.
- If you set
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/RegisterSubmenuto add single-click actions. - Always supply a unique key (e.g.
"MyPlugin/Tools/DoSomething").
- Use
Node Appearance
- Use
RegisterTypeConnectionAppearance(typeof(MyType), BuiltinConnectorShape.SolidCircle, Color.Blue)to give nodes forMyTypea blue circular pin.
- Use
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.
- If your node outputs a custom data type, implement
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, useTypeToolboxDefinition.
- Supply one or more toolbox definitions in
Execution
- If your plugin includes nodes that must run in a specialized context, register a
ProceduralContextExecutionRunnermapping your graph’s base type to a runner/factory.
- If your plugin includes nodes that must run in a specialized context, register a
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.