From 6b6a93f2c5783c8f005b49fd310f2531db3cde80 Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Wed, 30 Jul 2025 11:59:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86README.md=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 242 ------------------------------------------ flow_visualizer_cn.md | 215 +++++++++++++++++++++++++++++++++++++ flow_visualizer_en.md | 222 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 437 insertions(+), 242 deletions(-) delete mode 100644 README.md create mode 100644 flow_visualizer_cn.md create mode 100644 flow_visualizer_en.md diff --git a/README.md b/README.md deleted file mode 100644 index 4306c74..0000000 --- a/README.md +++ /dev/null @@ -1,242 +0,0 @@ -# 自述 -基于Dotnet 8 的流程可视化框架,运行依赖、运行环境、编辑环境全部开源,可二次开发。 -不定期在Bilibili个人空间上更新相关的视频。 -https://space.bilibili.com/33526379 - - -# 计划任务 2025年7月28日更新 -* 完善“流程图转c#代码”功能,正在构思热更新的实现方式。 -* 仍在构思如何实现多客户端的远程控制逻辑。 - -# 如何绘制流程? - * 准备阶段 - 1. 编译 Serein.Workbench 项目,确保你有一个可运行的工作台。 - 2. 新增一个类库项目,添加 **Serein.Library** 项目引用(也可在Negut上下载)。 - * 开始 - 1. 类库项目中,添加一个类,使用 **DynamicFlow** 特性标记你的类。 - 2. 类中,使用 **NodeAction** 特性标记你的方法。 - 3. 编译项目,将生成的Dll文件以拖拽方式,放入工作台左侧的“类库”面板中,工作台会自动加载该Dll文件,并显示出你标记的节点方法。 - * 绘制流程 - 1. 在工作台中,新建一个画布。 - 2. 右键按住左侧的“类库”面板中的方法,拖拽到画布上,工作台会自动生成一个节点。 - 3. 创建第二个节点,鼠标放在节点的“连接器”上,按住鼠标左键拖拽到第一个节点的“连接器”上,工作台会自动生成一条连线,表示两个节点之间的连接关系。 - 4. 创建更多的节点,在它们之间创建链接。 - * 运行流程 - 1. 在当前画布中,确保你有一个起始节点(右键点击节点,选择“设置为起始节点”)。 - 2. 点击工作台顶部的“运行”按钮,选择从当前画布运行,工作台会自动运行当前画布中的流程(从起始节点开始)。 - 3. 其它方法: - * 需要单独从某个节点开始运行时,在画布空白区域按住右键移动进行框选。选择你想开始运行的节点(只能选取一个),按下 F5 即可开始运行。 - * 在工作台菜单栏的“运行”按钮下拉菜单中,选择“运行所有画布”,工作台会自动运行所有画布中的流程(从每个画布的起始节点开始)。 - * 保存项目与加载项目 - 1. 流程图绘制完成后,在工作台菜单栏的“视图”按钮下拉菜单中,选择“保存项目”,工作台将弹出文件选择器,由你选定本地路径进行保存。 - 2. 需要加载其它项目时,在工作台菜单栏的“视图”按钮下拉菜单中,选择“加载本地项目”,工作台将弹出文件选择器,由你选定本地路径进行加载。 - * 查看输出 - 1. 在工作台菜单栏的“视图”按钮下拉菜单中,选择“输出窗口”,工作台会自动打开一个输出窗口,显示当前工作台输出的信息(包含绘制异常、运行异常等等)。 - -# 如何让我的方法成为节点? -使用 **NodeAction** 特性标记你的方法。 -* 动作节点 - Action -* 触发器节点 - Flipflop -* UI节点 - UI -# 关于 IFlowContext 说明(重要) - * 基本说明:IFlowContext 是节点之间传递数据的接口、载体,其实例由 FlowEnvironment 运行环境自动实现,内部提供全局单例的环境接口,用以注册、获取实例(单例模式),一般情况下,你无须关注 FlowEnvironment 对外暴露的属性方法。 - * 重要概念: - * 每个节点其实对应类库中的某一个方法,这些方法组合起来,加上预先设置的逻辑分支,就是一个完整的节点流。在这个节点流当中,从第一个节点开始、直到所有可达的节点,都会通过同一个流程上下文传递、共享数据。为了符合多线程操作的理念,每个运行起来的节点流之间,流程数据并不互通,从根本隔绝了“脏数据”的产生。 - * 一些重要的属性: - * RunState - 流程状态: - * 简述:枚举,标识流程运行的状态(初始化,运行中,运行完成) - * 场景:类库代码中创建了运行时间较长的异步任务、或开辟了另一个线程进行循环操作时,可以在方法入参定义一个 IFlowContext 类型入参,然后在代码中使用形成闭包,以及时判断流程是否已经结束。另外的,如果想监听项目停止运行,可以订阅 context.Env.OnFlowRunComplete 事件。 - * NextOrientation - 即将进入的分支: - * 简述:流程分支枚举, Upstream(上游分支)、IsSucceed(真分支)、IsFail(假分支),IsError(异常分支)。 - * 场景:允许你在类库代码中操作该属性,手动控制当前节点运行完成后,下一个会执行哪一个类别的节点。 - * Exit() - 结束流程 - * 简述:顾名思义,能够让你在类库代码中提前结束当前流程运行 - -# 关于 DynamicNodeType 枚举的补充说明。 -## 1. 不生成节点控件的枚举值: -* **Init - 初始化方法** - * 入参:**IFlowContext**(有且只有一个参数)。 - * 返回值:自定义,但不会处理返回值,支持异步等待。 - * 描述:在运行时首先被调用。语义类似于构造方法。建议在Init方法内初始化类、注册类等一切需要在构造函数中执行的方法。 -* **Loading - 加载方法** - * 入参:**IFlowContext**(有且只有一个参数)。 - * 返回值:自定义,但不会处理返回值,支持异步等待。 - * 描述:当所有Dll的Init方法调用完成后,首先调用、也才会调用DLL的Loading方法。建议在Loading方法内进行业务上的初始化(例如启动Web,启动第三方服务)。 -* **Exit - 结束方法** - * 入参:**IFlowContext**(有且只有一个参数)。 - * 返回值:自定义,但不会处理返回值,支持异步等待。 - * 描述:当结束/手动结束运行时,会调用所有Dll的Exit方法。使用场景类似于:终止内部的其它线程,通知其它进程关闭,例如停止第三方服务。 -## 2. 基础节点 -* **FLowCall - 流程接口** - * 入参:根据目标节点变化。 - * 描述:有时我们需要将流程图的逻辑解耦,单独使用某个画布编写逻辑,抽取出一个通用的处理模板,在其它流程图中像接口一样进行调用。 - * 使用方式: - 1. 创建两个画布,分别命名画布A,画布B。 - 2. 在画布A上,选择希望暴露出去的一个节点,勾选“全局公开”。 - 3. 在画布B上,拖拽创建“流程接口”节点,画布选择“画布A”,节点选择公开的节点。 - 4. 在画布B上正常调用即可。 - * 关于“参数共享”设置的说明: - * 如果启用了,那么流程接口对应的节点,入参参数将在全局保持一致,一处地方修改后,处处同步(仅限于启用了这一设置的相同的流程接口节点)。 - * 如果没有启用,那么流程接口将会从目标节点拷贝相同的入参,单独使用,并不受目标节点的入参参数修改而修改。 -* **Script - 脚本节点** - * 入参:可选可变 - * 描述:有时我们需要定义一个临时的类对象,但又不想在代码中写死属性,又或者某些流程操作中,因为业务场景需要布置大量的逻辑判断,导致流程图变得极为臃肿不堪入目,于是引入了脚本节点。脚本节点动态能力强,不同于表达式使用递归下降,而是基于AST抽象语法树调用相应的C#代码,性能至少差强人意。 - * 使用方式: - ``` - // 入参是一个对象,入参每次是 info ,有 Var 、 Data属性。 - class Info{ - string PlcName; - string Content; - string LogType; - DateTime LogTime; - } - - plc = global("JC-PLC"); // 获取全局数据节点中的数据 - - log = new Info() { // 创建一个类 - Content = plc + " " + info.Var + " - 状态 Value : " + info.Data, - PlcName = plc.Name, - LogType = "info", - LogTime = now(), // 脚本默认挂载的方法,获取当前时间 - }; - return log; // 返回对象 - ``` -* **GlobalData - 全局数据节点** - * 入参:KeyName ,在整个流程环境中标识某个数据的key值。 - * 描述:有时需要获取其它节点的数据,但如果强行在两个节点之间进行连线,会让项目流程图变得无比丑陋,如果在类库代码中自己对全局数据进行维护,可能也不太优雅,所以引入了全局数据节点(全局变量) - * 使用方式:全局数据节点实质上只是一个节点容器,这意味着你能将任意节点拖拽到该容器节点上,当流程执行到这个容器节点(全局数据节点)时,会自动调用容器内部的节点对应的方法,并将返回的数据保存在运行环境维护的Map中。 - * 其它获取到全局数据的方式: - 1. 表达式 : - ~~~ - @Get global("DataName") // 使用global表达全局数据 DataName 的标识符 - ~~~ - 2. Script代码: - ~~~~ - data = global("DataName"); // 获取全局数据节点中 DataName 的数据 - ~~~~ -## 3. 从DLL生成控件的枚举值: -* **Action - 动作** - * 入参:自定义。如果入参类型为IFlowContext,会传入当前的上下文;如果入参类型为IFlowNode,会传入节点对应的实体Model。如果不显式指定参数来源,参数会尝试获取运行时上一节点返回值,并根据当前入参类型尝试进行类型转换。 - * 返回值:自定义,支持异步等待。 - * 描述:同步执行对应的方法。 -* **Flipflop - 触发器** - * 全局触发器 - * 入参:依照Action节点。 - * 返回值:Task`>` - * 描述:运行开始时,所有无上级节点的触发器节点(在当前分支中作为起始节点),分别建立新的线程运行,然后异步等待触发(如果有)。这种触发器拥有独自的IFlowContext上下文(共用同一个Ioc),执行完成之后,会重新从分支起点的触发器开始等待。 - * 分支中的触发器 - * 入参:依照Action节点。 - * 返回值:Task`>` - * 描述:接收上一节点传递的上下文,同样进入异步等待,但执行完成后不会再次等待自身(只会触发一次)。 - * 关于 IFlipflopContext`` 接口 - * 基本说明:IFlipflopContext是一个接口,你无须关心内部实现。 - * 参数描述:State,状态枚举描述(Succeed、Cancel、Error、Cancel),如果返回Cancel,则不会执行后继分支,如果返回其它状态,则会获取对应的后继分支,开始执行。 - * 参数描述:Type,触发状态描述(External外部触发,Overtime超时触发),当你在代码中的其他地方主动触发了触发器,则该次触发类型为External,当你在创建触发器后超过了指定时间(创建触发器时会要求声明超时时间),则会自动触发,但触发类型为Overtime,触发参数未你在创建触发器时指定的值) - * 参数描述:Value,触发时传递的参数。 - * 使用场景:配合 FlowTrigger`` 使用,例如定时从PLC中获取状态,当某个变量发生改变时,会通知相应的触发器,如果需要,可以传递对应的数据。 - -* * **ExpOp- 表达式节点** - * 入参: 自定义的表达式。 - * 取值表达式:@Get - * 描述:有时节点返回了object,但下一个节点只需要对象中某个属性,而非整个对象。如果修改节点的定义,有可能破坏了代码的封装,为了解决这个痛点,于是增加了表达式节点。 - * 使用方法: - 1. 获取对象的属性成员: - ~~~~ - @Get .[property]/[field] - ~~~~ - 2. 获取对象的数组成员中下标为22的项: - ~~~~ - @Get .array[22] - ~~~~ - 3. 获取对象的字典成员中键为“zhangsan”的值: - ~~~ - @Get .dict["zhangsan"] - ~~~ -* **ExpCondition - 条件表达式节点** - * 入参: 自定义。 - * 描述:与表达式节点不同,条件表达式节点是判断条件是否成立,如果成立,返回true,否则返回false,如果表达式执行失败,而进入 error 分支。 - * 使用方式: - * 入参说明:默认从上一节点获取,也可以显式设定值,也可以参考表达式节点,使用“@Get .[property]/[field]”的方式重新定义入参数据,用以条件表达式判断。 - * 条件表达式:默认“PASS”,代表跳过判断直接进入下一个分支。 - * 条件表达式格式“.[property]/[field]< type> [op] value”,注意,开头必须使用“.”符号,这有助于显然的表达需要从入参对象中取内部某个值。 - * 表达式符号说明: - * [property] /[field] : 属性/字段 - * [op] : 操作符 - 1. bool表达式:== - 2. 数值表达式 :==、>=、 <=、in a-b (表示判断是否在a至b的数值范围内), !in a-b(取反); - 3. 文本表达式:==/equals(等于)、!=/notequals(不等于)、c/contains(出现过)、nc/doesnotcontain(没有出现过)、sw/startswith(开头等于)、ew/endswith(结尾等于) - * < type> :指定需要转换为某个类型,可不选。 - * [value] : 条件值 - * 使用示例: - ~~~ - 场景1:上一个节点传入了该对象(伪代码): - class Data - { - string Name; // 性能 - string Age; // 年龄,外部传入了文本类型 - string IdentityCardNumber; // 身份证号 - - } - 需求:需要判断年龄是否在某个区间,例如需要大于18岁,小于35岁。注意,这里的“data”是入参数据的默认名称。意味着假如你需要在表达式里使用入参数据时,就可以使用“data”。 - 条件表达式:.Age > 18 && data.Age < 35 - - - 需求:需要判断是否是北京身份证(开头为”1100”),可以使用string类型中的StartsWith方法,这里的匹配的是StartsWith(String)重载。 - 条件表达式:.IdentityCardNumber.StartsWith("1100") - 另一种方法: - 入参使用表达式:@Get .IdentityCardNumber - 条件表达式:sw 1100 - - ~~~ -* **UI - 自定义控件** - * 入参:默认使用上一节点返回值。 - * 返回值:IEmbeddedContent 接口 - * 描述:将类库中的WPF UserControl嵌入并显示在一个节点上,显示在工作台UI中。例如在视觉处理流程中,需要即时的显示图片。 - * 关于 IEmbeddedContent 接口 - * IEmbeddedContent 需要由你实现,框架并不负责 - ``` - [DynamicFlow("[界面显示]")] - internal class FlowControl - { - [NodeAction(NodeType.UI)] - public async Task CreateImageControl(FlowContext context) - { - WpfUserControlAdapter adapter = null; - // 其实你也可以直接创建实例 - // 但如果你的实例化操作涉及到了对UI元素修改,还是建议像这里一样使用异步方法 - await context.Env.UIContextOperation.InvokeAsync(() => - { - var userControl = new UserControl(); - adapter = new WpfUserControlAdapter(userControl, userControl); - }); - return adapter; - } - } - public class WpfUserControlAdapter : IEmbeddedContent - { - private readonly UserControl userControl; - private readonly IFlowControl flowControl; - - public WpfUserControlAdapter(UserControl userControl, IFlowControl flowControl) - { - this.userControl = userControl; - this.flowControl= flowControl; - } - - public IFlowControl GetFlowControl() - { - return flowControl; - } - - public object GetUserControl() - { - return userControl; - } - } - ``` - -## 演示: - ![image1](https://github.com/fhhyyp/serein-flow/blob/cc5f8255135b96c6bb3669bc4aa8d8167a71c262/Image/%E6%BC%94%E7%A4%BA%20-%201.png) - ![image2](https://github.com/fhhyyp/serein-flow/blob/cc5f8255135b96c6bb3669bc4aa8d8167a71c262/Image/%E6%BC%94%E7%A4%BA%20-%202.png) - ![image3](https://github.com/fhhyyp/serein-flow/blob/8f17b786f3585cabfeef60d9ab871d43b69e5461/Image/%E6%BC%94%E7%A4%BA%20-%203.png) - ![image4](https://github.com/fhhyyp/serein-flow/blob/8f17b786f3585cabfeef60d9ab871d43b69e5461/Image/%E6%BC%94%E7%A4%BA%20-%204.png) diff --git a/flow_visualizer_cn.md b/flow_visualizer_cn.md new file mode 100644 index 0000000..639a716 --- /dev/null +++ b/flow_visualizer_cn.md @@ -0,0 +1,215 @@ +# 📘 自述文档:基于 .NET 8 的流程可视化框架 + +## 🧩 项目介绍 + +本项目是一个基于 **.NET 8** 的流程可视化框架, + +- 全部运行依赖、运行环境、编辑器均为 **开源**, +- 支持 **二次开发**,适合用于嵌入式、工控、数据流程自动化等场景。 + +📺 **Bilibili 视频更新**:不定期更新项目相关内容,可前往我的 [Bilibili 主页](https://space.bilibili.com/33526379) 查看。 + +--- + +## 🗓️ 计划任务(更新于 2025 年 7 月 28 日) + +- ✅ 正在完善“**流程图转 C# 代码**”功能 +- 🔥 构思 **热更新机制** +- 🔧 多客户端 **远程控制逻辑**设计中 + +--- + +## 🖌️ 如何绘制流程? + +### 🛠️ 准备工作 + +1. 编译 `Serein.Workbench` 项目,确保工作台可运行。 +2. 新建一个类库项目,并添加对 `Serein.Library` 的引用(也可通过 Negut 获取)。 + +### ✨ 开始绘图 + +1. 在类库项目中添加一个类,使用 `[DynamicFlow]` 特性标记类。 +2. 使用 `[NodeAction]` 特性标记方法。 +3. 编译项目并将生成的 DLL 拖入工作台左侧“类库”面板中。 +4. 工作台自动解析并加载所有节点方法。 + +### 🎨 绘制节点流程 + +1. 在工作台中新建一个画布。 +2. 从左侧类库面板中拖拽方法到画布上生成节点。 +3. 在节点连接器之间拖拽生成连线,表示数据/控制流。 +4. 可重复添加、连接多个节点构建完整流程。 + +### ▶️ 运行流程 + +- **从起始节点运行**: + - 设置某节点为“起始节点” → 点击“运行” → 选择“从当前画布运行” +- **从指定节点运行**: + - 框选单个节点 → 按 `F5` 运行 +- **运行所有画布**: + - 在“运行”菜单中选择“运行所有画布” + +### 💾 保存/加载项目 + +- **保存项目**:`视图` → `保存项目` → 选择保存路径 +- **加载项目**:`视图` → `加载本地项目` → 选择项目文件 + +### 🪵 查看输出日志 + +- 在 `视图` → `输出窗口` 中查看异常、日志与调试信息 + +--- + +## 🧱 如何让方法成为流程节点? + +- 使用 `[NodeAction]` 特性标记方法 +- 指定合适的节点类型(如 Action、Flipflop、Script 等) + +--- + +## 📚 关键接口说明 + +### IFlowContext(流程上下文) + +> 节点之间数据传递的核心接口,由运行环境自动注入。 + +#### 🔑 核心特点 + +- 全局单例环境接口,支持注册/获取实例(IoC 模式) +- 每个流程实例中上下文隔离,防止脏数据产生 + +#### 🔍 常用属性 + +| 属性 | 类型 | 描述 | +| ----------------- | -- | ---------------------- | +| `RunState` | 枚举 | 表示流程运行状态(初始化、运行中、运行完成) | +| `NextOrientation` | 枚举 | 指定下一个分支(成功、失败、异常) | +| `Exit()` | 方法 | 提前终止当前流程 | + +--- + +## 🧾 DynamicNodeType 枚举说明 + +### 1️⃣ 控件不生成节点(生命周期方法) + +| 枚举值 | 描述 | +| --------- | ----------------------------- | +| `Init` | 程序启动时最先执行,适合进行类初始化等操作 | +| `Loading` | 所有 DLL 的 Init 执行完毕后调用,适合业务初始化 | +| `Exit` | 程序关闭时调用,适合释放线程/关闭服务 | + +> 参数均为 `IFlowContext`,返回值支持异步,但框架不处理其结果。 + +### 2️⃣ 基础节点类型 + +#### 📎 FlowCall(流程接口节点) + +- 实现跨画布复用逻辑 +- 支持参数共享模式(全局同步)或参数独立模式(本地隔离) + +#### 💡 Script(脚本节点) + +- 使用 AST 抽象语法树动态编译运行 +- 内置 `global()`、`now()` 等函数,参考 `Serein.Library.ScriptBaseFunc` 类中成员方法。 +- 在代码中调用 `SereinScript.AddStaticFunction("your function name", MethodInfo);` 注册更多的内置方法。 + +示例: + +```csharp +// 入参是一个对象,入参名称是 "info" ,包含Var、Data属性。 +// 自定义类 +class Info{ + string PlcName; + string Content; + string LogType; + DateTime LogTime; +} +plc = global("JC-PLC"); // 获取全局数据节点中的数据 +log = new Info() { + Content = plc + " " + info.Var + " - 状态 Value : " + info.Data, + PlcName = plc.Name, + LogType = "info", + LogTime = now(), +}; +return log; +``` + +#### 🌐 GlobalData(全局数据节点) + +- 获取/共享运行时跨节点数据 +- 表达式使用:`global("DataKey")` + +### 3️⃣ 从 DLL 生成控件的节点 + +#### ⚙️ Action + +- 最基础的节点类型,入参自动匹配,返回值支持异步 + +#### 🔁 Flipflop(触发器) + +- 可响应外部事件或超时触发 +- 支持状态返回(成功、失败、取消)与触发源类型(外部、超时) + +#### 🧠 ExpOp(表达式节点) + +- 提取对象的子属性、字段、数组、字典值 +- 示例:`@Get .Array[22]` 或 `@Get .Dict["key"]` + +#### 🔍 ExpCondition(条件表达式节点) + +- 条件判断节点,支持布尔/数值/文本逻辑判断 + +示例: + +```text +条件表达式:.Age > 18 && data.Age < 35 +注意,右侧出现的“data”是表达式数据的默认名称,假如你需要在表达式里其他地方使用入参数据时,就可以使用“data”。 + +或 .Text.StartsWith("1100") // 判断文本是否以1100开头 +``` + +#### 🖼️ UI(嵌入控件) + +- 显示自定义 `UserControl` 到工作台 +- 返回实现 `IEmbeddedContent` 接口的对象 + +示例代码片段: + +```csharp +await context.Env.UIContextOperation.InvokeAsync(() => { + var userControl = new UserControl(); + adapter = new WpfUserControlAdapter(userControl, userControl); +}); +return adapter; +public class WpfUserControlAdapter : IEmbeddedContent +{ + private readonly UserControl userControl; + private readonly IFlowControl flowControl; + public WpfUserControlAdapter(UserControl userControl, IFlowControl flowControl) + { + this.userControl = userControl; + this.flowControl= flowControl; + } + public IFlowControl GetFlowControl() + { + return flowControl; + } + public object GetUserControl() + { + return userControl; + } +} +``` + +--- + +## ✅ 总结 + +- 该框架提供灵活的流程编辑、运行与二次扩展能力 +- 支持脚本、全局数据、子流程等高级功能 +- 适用于 AGV 调度、PLC 自动化、图形化逻辑控制等场景 + +--- + +> 本文档适用于开发者快速上手和参考具体接口用法。如需进一步了解,请关注项目演示视频或参与社区讨论。 + diff --git a/flow_visualizer_en.md b/flow_visualizer_en.md new file mode 100644 index 0000000..4f88b47 --- /dev/null +++ b/flow_visualizer_en.md @@ -0,0 +1,222 @@ +# 📘 Documentation: Flow Visualization Framework Based on .NET 8 + +## 🧩 Project Overview + +This project is a **flow visualization framework** based on **.NET 8**. + +- All runtime dependencies, environment, and editor are **open-source** +- Supports **secondary development**, suitable for scenarios such as embedded systems, industrial control, and data flow automation + +📺 **Bilibili Video Updates**: Project-related videos are periodically updated. Visit [my Bilibili profile](https://space.bilibili.com/33526379) for more. + +--- + +## 🗓️ Planned Tasks (Updated July 28, 2025) + +- ✅ Improving **"flowchart to C# code"** feature +- 🔥 Designing **hot update mechanism** +- 🔧 Designing **remote control logic** for multiple clients + +--- + +## 🖌️ How to Draw a Flow? + +### 🛠️ Preparation + +1. Compile the `Serein.Workbench` project to ensure the workbench is operational +2. Create a class library project and reference `Serein.Library` (can also be acquired via Negut) + +### ✨ Start Drawing + +1. In the class library, add a class and mark it with the `[DynamicFlow]` attribute +2. Mark methods with the `[NodeAction]` attribute +3. Compile the project and drag the generated DLL into the left "Library" panel of the workbench +4. The workbench will automatically parse and load all node methods + +### 🎨 Draw Node Flows + +1. Create a new canvas in the workbench +2. Drag methods from the left library panel onto the canvas to generate nodes +3. Drag between node connectors to create links, representing data/control flow +4. Repeat to build a complete flow + +### ▶️ Run the Flow + +- **Run from start node**: + - Set a node as "start node" → Click "Run" → Select "Run from current canvas" +- **Run from a specific node**: + - Select a single node → Press `F5` +- **Run all canvases**: + - In the "Run" menu, choose "Run all canvases" + +### 💾 Save/Load Projects + +- **Save Project**: `View` → `Save Project` → Choose save path +- **Load Project**: `View` → `Load Local Project` → Choose project file + +### 🩵 View Output Logs + +- View logs, exceptions, and debug info via `View` → `Output Window` + +--- + +## 🧱 How to Make a Method a Flow Node? + +- Mark methods with `[NodeAction]` attribute +- Specify the appropriate node type (e.g., Action, Flipflop, Script, etc.) + +--- + +## 📚 Key Interface Overview + +### `IFlowContext` (Flow Context) + +> Core interface for data transfer between nodes, automatically injected by the runtime environment. + +#### 🔑 Key Features + +- Global singleton environment interface, supports registration/retrieval (IoC pattern) +- Context isolation per flow instance, preventing dirty data + +#### 🔍 Common Properties + +| Property | Type | Description | +| ----------------- | ------ | --------------------------------------------------- | +| `RunState` | Enum | Indicates flow run state (Init, Running, Completed) | +| `NextOrientation` | Enum | Specifies next branch (Success, Failure, Exception) | +| `Exit()` | Method | Terminates the current flow early | + +--- + +## 🧾 `DynamicNodeType` Enum Explained + +### 1⃣ Nodes not Generated (Lifecycle Methods) + +| Enum Value | Description | +| ---------- | -------------------------------------------------------- | +| `Init` | Executes first at program start, suitable for init logic | +| `Loading` | Called after all DLL `Init`s run, good for business init | +| `Exit` | Invoked on shutdown, suitable for resource release | + +> Parameters are all `IFlowContext`. Return values can be async but are not handled by the framework. + +### 2⃣ Basic Node Types + +#### 📌 `FlowCall` (Flow Interface Node) + +- Enables logic reuse across canvases +- Supports shared (global sync) or isolated (local) parameter modes + +#### 💡 `Script` (Script Node) + +- Executes dynamically via AST (Abstract Syntax Tree) +- Built-in functions like `global()`, `now()` (see `Serein.Library.ScriptBaseFunc`) +- Register more functions via: + +```csharp +SereinScript.AddStaticFunction("your function name", MethodInfo); +``` + +**Example:** + +```csharp +// Input is an object named "info" with Var, Data properties +class Info { + string PlcName; + string Content; + string LogType; + DateTime LogTime; +} +plc = global("JC-PLC"); +log = new Info() { + Content = plc + " " + info.Var + " - Status Value: " + info.Data, + PlcName = plc.Name, + LogType = "info", + LogTime = now(), +}; +return log; +``` + +#### 🌐 `GlobalData` (Global Data Node) + +- Get/share runtime data across nodes +- Expression usage: `global("DataKey")` + +### 3⃣ Nodes Generated from DLL Controls + +#### ⚙️ `Action` + +- Basic node type, input parameters auto-matched, supports async return + +#### 🔁 `Flipflop` (Trigger Node) + +- Can be triggered by external events or timeouts +- Supports state returns (Success, Failure, Cancelled) and trigger source type + +#### 🧠 `ExpOp` (Expression Node) + +- Extracts sub-properties/fields/array/dictionary values +- Examples: + - `@Get .Array[22]` + - `@Get .Dict["key"]` + +#### 🔍 `ExpCondition` (Conditional Expression Node) + +- Conditional logic based on bool/number/string + +**Example:** + +```text +.Age > 18 && data.Age < 35 +or +.Text.StartsWith("1100") +``` + +> `data` refers to the default name of the input object in expressions. + +#### 🖼️ `UI` (Embedded Control) + +- Display custom `UserControl` on the workbench +- Must return object implementing `IEmbeddedContent` + +**Sample Code:** + +```csharp +await context.Env.UIContextOperation.InvokeAsync(() => { + var userControl = new UserControl(); + adapter = new WpfUserControlAdapter(userControl, userControl); +}); +return adapter; + +public class WpfUserControlAdapter : IEmbeddedContent +{ + private readonly UserControl userControl; + private readonly IFlowControl flowControl; + public WpfUserControlAdapter(UserControl userControl, IFlowControl flowControl) + { + this.userControl = userControl; + this.flowControl= flowControl; + } + public IFlowControl GetFlowControl() + { + return flowControl; + } + public object GetUserControl() + { + return userControl; + } +} +``` + +--- + +## ✅ Summary + +- This framework provides flexible flow editing, execution, and extension capabilities +- Supports advanced features such as scripting, global data, and sub-flows +- Suitable for AGV scheduling, PLC automation, visual logic control, and more + +--- + +> This documentation helps developers get started quickly and understand interface usage. For more information, refer to video demos or join the community discussion. +