插件开发
最简单的插件形式就是一个普通的 C# 类。如果你只想公开一些简单的函数,请使用静态类:
public static class MyPlugin
{
public static int GetValue() => 15;
}
1. 插件功能标志
宿主通过 PluginFeature 标志公开一组能力。在尝试使用某个子系统之前,插件应调用 GetFeatureSet() 并检查相关标志。可用的功能如下:
- Menus (1) 宿主可以创建新的顶级菜单和嵌套子菜单。
- DynamicPackages (2) 宿主可以在运行时向 Divooka 的工具箱中添加新的节点类型(通常称为“包”)。
- EditorQuery (4) 宿主允许查询和修改图的运行时状态(例如读取节点值、连接/断开引脚)。
- ConnectorVisualCustomizations (8) 宿主允许自定义节点引脚连线的外观(形状 + 颜色)。
- CustomNodeSurface (16) [已过时] 已计划但尚未实现:完全自定义节点表面(超出内置的引脚/连接器渲染范围)。
- PreviewProcessor (32) 宿主支持注册例程,将任意数据转换为 Divooka 可以原生预览的类型(例如图像、文本、图)。
- CustomPreviewControl (64) [已过时] 已计划但尚未实现:嵌入自定义预览控件(例如自定义的 WPF 或 Godot 控件)。
- CustomPreviewHandler (128) 宿主支持为任何节点类型提供完全自定义的“弹出式”预览处理程序(插件完全控制预览在单独窗口中的渲染方式)。
插件应通过按位运算组合这些标志(例如 if ((features & PluginFeature.Menus) != 0) { … }),以确定在运行时要注册或启用哪些内容。
2. 菜单注册
插件确认 PluginFeature.Menus 可用后,就可以向 Divooka 的主菜单栏添加菜单项并注册嵌套子菜单。每个菜单项在被单击时调用一个回调。
// 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);
- 要点
menu和submenu是标识符(不一定是显示的文字)。title是最终用户看到的内容。autoUnregister让宿主在插件卸载时进行清理。
3. 节点连接器外观
如果支持 ConnectorVisualCustomizations,插件就可以为任意给定的数据类型自定义引脚(输入/输出端口)和连线的外观。
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一个包含内置形状的小型枚举(HollowCircle、HollowRectangle、HollowTriangle、HollowArrow、HollowSquare,以及对应的“Solid”形状)。- 用法 尽早注册(例如在插件初始化期间)。之后,Divooka 会使用你选择的形状 + 颜色来渲染所有承载该 CLR 类型的引脚。
4. 预览处理
Divooka 允许节点为其数据提供一个小型预览(例如显示图像、文本片段或图)。有两种不同的扩展点:
Preview Processor(需要
PreviewProcessor标志)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.) );- 何时使用: 如果你的插件的节点输出 Divooka 无法原生渲染的自定义数据类型,请实现一个处理器,把它转换为宿主已经知道如何显示的类型。
- 示例:
将
MyImageData实例转换为System.Drawing.Bitmap。
Custom Preview Handler(需要
CustomPreviewHandler标志)void RegisterCustomPreviewHandler( Type instanceType, // the .NET type for which you supply full control Action<ProcessorNode, object> previewHandler );- 何时使用: 如果你需要一个宿主无法处理的完全自定义的弹出式预览(例如交互式图表或 3D 视图)。
- 行为:
每当用户请求预览时,Divooka 都会调用你的
previewHandler,并将节点实例以及原始数据对象传给你。你的代码负责显示窗口或控件、渲染内容、挂接关闭事件等。注意:这只能以单独的弹出窗口形式工作,不能内嵌在画布上。
5. 动态类型 / 工具箱注册
如果启用了 DynamicPackages,插件就可以向 Divooka 告知新的节点类型——既包括后端功能(节点做什么),也包括前端工具箱条目(它在界面中出现在哪里)。RegisterPackages 调用统一处理以下几类定义集合:
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定义整个程序集的节点——Divooka 可以在运行时扫描程序集,找到被注释为节点的类,然后自动填充工具箱。FrontendToolboxDefinition描述你希望如何在 Divooka 的界面中对节点进行分组和标注(例如文件夹图标、类别)。TypeToolboxDefinition将单个 .NET 类型显式注册为节点——如果你希望精细控制其名称、图标和出现位置,这会很有用。SpecificToolboxEndPointDefinition让你可以把一个节点放在多个位置,或放在专门的上下文菜单下。ProceduralContextBaseTypeDefinition如果你的插件提供了一个“过程式上下文”(一种以自定义方式运行一组节点的有状态环境),就在这里注册其基类型,让 Divooka 了解你的运行时执行模型。
注册完成后,Divooka 的界面会在工具箱中显示这些新的节点类型;用户可以像使用内置节点一样,将它们拖放到画布上。
6. 过程式上下文执行运行器
对于引入自定义过程式执行上下文的插件,请使用:
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
);
- 用途
告诉 Divooka:“每当一个以派生自
baseType的类型为根的节点图需要执行时,就使用这个instantiatorType来运行它。” - 行为 Divooka 会在运行时实例化你的运行器并把节点图交给它;你的运行器负责遍历节点、按正确的顺序对它们求值并产生输出。
7. 总结
- 启动时
- 查询
GetFeatureSet()。 - 如果支持菜单,则按条件调用
RegisterMenu/RegisterSubmenu。 - 如果你有自定义数据类型,调用
RegisterTypeConnectionAppearance为它们设置独特的引脚形状/颜色。 - 如果你想添加新节点,请准备好工具箱定义(程序集扫描、类型描述符等)并调用
RegisterPackages。
- 查询
- 插件初始化期间
- 如果需要为预览转换节点输出,请使用
RegisterPreviewProcessor注册一个处理器。 - 如果需要完全控制预览(例如在浮动窗口中显示 3D 模型),请使用
RegisterCustomPreviewHandler注册一个处理程序。 - 如果你的节点需要特殊的执行环境,请注册一个 ProceduralContextExecutionRunner。
- 如果需要为预览转换节点输出,请使用
- 关闭(或卸载)时
- 如果你设置了
autoUnregister = true,那么当 Divooka 卸载插件时,大多数注册内容(菜单、包、预览处理程序)都会被自动清理。否则,请显式调用UnregisterMenu、UnregisterSubMenu以及所需的任何自定义清理操作。
- 如果你设置了
插件作者检查清单
功能检测
var features = host.GetFeatureSet(); if ((features & PluginFeature.Menus) != 0) RegisterMenus(); if ((features & PluginFeature.DynamicPackages) != 0) RegisterPackages(...); // etc.菜单
- 使用
RegisterMenu/RegisterSubmenu添加单击即执行的操作。 - 始终提供唯一的键(例如
"MyPlugin/Tools/DoSomething")。
- 使用
节点外观
- 使用
RegisterTypeConnectionAppearance(typeof(MyType), BuiltinConnectorShape.SolidCircle, Color.Blue)为MyType的节点设置蓝色圆形引脚。
- 使用
预览支持
- 如果你的节点输出自定义数据类型,请实现
Func<object, object>,将其转换为现有的可预览对象(字符串、Bitmap、数组等)。 - 如果你需要弹出式编辑器,或者需要 Divooka 无法原生预览的内容,请实现自定义预览处理程序。
- 如果你的节点输出自定义数据类型,请实现
动态类型(工具箱)
- 在
RegisterPackages中提供一个或多个工具箱定义。 - 如果只想扫描整个程序集,请使用
AssemblyToolboxDefinition。如果想严格控制单个节点的元数据,请使用TypeToolboxDefinition。
- 在
执行
- 如果你的插件包含必须在专门上下文中运行的节点,请注册一个
ProceduralContextExecutionRunner,将你的图的基类型映射到某个运行器/工厂。
- 如果你的插件包含必须在专门上下文中运行的节点,请注册一个
上述各部分就绪后,Divooka 会自动显示你的菜单、渲染你的节点引脚、让用户把你的自定义节点类型拖入图中,并且在用户单击“Run”或请求预览时,在底层调用你的转换器或自定义预览代码。
通过把功能归入这六个方面(功能标志、菜单、节点外观、预览、动态类型注册和执行运行器),你可以确保插件与 Divooka 的宿主环境顺畅集成,并遵循预期的扩展点。
另请参阅
更多 C# 技巧。