diff --git a/Library.Core/NodeFlow/DynamicContext.cs b/Library.Core/NodeFlow/DynamicContext.cs index 5e9020d..b29e27c 100644 --- a/Library.Core/NodeFlow/DynamicContext.cs +++ b/Library.Core/NodeFlow/DynamicContext.cs @@ -9,13 +9,17 @@ namespace Serein.Library.Core.NodeFlow /// public class DynamicContext: IDynamicContext { - public DynamicContext(ISereinIoc sereinIoc) + public DynamicContext(ISereinIoc sereinIoc, IFlowEnvironment flowEnvironment) { SereinIoc = sereinIoc; + FlowEnvironment = flowEnvironment; + } public NodeRunCts NodeRunCts { get; set; } public ISereinIoc SereinIoc { get; } + public IFlowEnvironment FlowEnvironment { get; } + public Task CreateTimingTask(Action action, int time = 100, int count = -1) { NodeRunCts ??= SereinIoc.GetOrInstantiate(); diff --git a/Library.Core/NodeFlow/FlipflopContext.cs b/Library.Core/NodeFlow/FlipflopContext.cs index 81c0e1b..3819260 100644 --- a/Library.Core/NodeFlow/FlipflopContext.cs +++ b/Library.Core/NodeFlow/FlipflopContext.cs @@ -66,7 +66,7 @@ namespace Serein.Library.Core.NodeFlow public class FlipflopContext : IFlipflopContext { public FlowStateType State { get; set; } - //public TResult? Data { get; set; } + public object Data { get; set; } public FlipflopContext(FlowStateType ffState) diff --git a/Library.Framework/NodeFlow/DynamicContext.cs b/Library.Framework/NodeFlow/DynamicContext.cs index 5260c33..1335ee5 100644 --- a/Library.Framework/NodeFlow/DynamicContext.cs +++ b/Library.Framework/NodeFlow/DynamicContext.cs @@ -12,13 +12,15 @@ namespace Serein.Library.Framework.NodeFlow /// public class DynamicContext : IDynamicContext { - public DynamicContext(ISereinIoc sereinIoc) + public DynamicContext(ISereinIoc sereinIoc, IFlowEnvironment flowEnvironment) { SereinIoc = sereinIoc; + FlowEnvironment = flowEnvironment; } public NodeRunCts NodeRunCts { get; set; } public ISereinIoc SereinIoc { get; } + public IFlowEnvironment FlowEnvironment { get; } public Task CreateTimingTask(Action action, int time = 100, int count = -1) { if(NodeRunCts == null) diff --git a/Library/Api/IDynamicContext.cs b/Library/Api/IDynamicContext.cs index 991bc47..266319e 100644 --- a/Library/Api/IDynamicContext.cs +++ b/Library/Api/IDynamicContext.cs @@ -6,8 +6,9 @@ namespace Serein.Library.Api { public interface IDynamicContext { - NodeRunCts NodeRunCts { get; set; } + IFlowEnvironment FlowEnvironment { get; } ISereinIoc SereinIoc { get; } + NodeRunCts NodeRunCts { get; set; } Task CreateTimingTask(Action action, int time = 100, int count = -1); } } diff --git a/NodeFlow/Api.cs b/Library/Api/IDynamicFlowNode.cs similarity index 85% rename from NodeFlow/Api.cs rename to Library/Api/IDynamicFlowNode.cs index c5e1f56..1cf5511 100644 --- a/NodeFlow/Api.cs +++ b/Library/Api/IDynamicFlowNode.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.NodeFlow +namespace Serein.Library.Api { public interface IDynamicFlowNode { diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs new file mode 100644 index 0000000..19ceb72 --- /dev/null +++ b/Library/Api/IFlowEnvironment.cs @@ -0,0 +1,206 @@ +using Serein.Library.Entity; +using Serein.Library.Enums; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; + +namespace Serein.Library.Api +{ + + public class FlowEventArgs : EventArgs + { + public bool IsSucceed { get; protected set; } = true; + public string ErrorTips { get; protected set; } = string.Empty; + } + + + public delegate void FlowRunCompleteHandler(FlowEventArgs eventArgs); + + + /// + /// 加载节点 + /// + public delegate void LoadNodeHandler(LoadNodeEventArgs eventArgs); + public class LoadNodeEventArgs : FlowEventArgs + { + public LoadNodeEventArgs(NodeInfo NodeInfo, MethodDetails MethodDetailss) + { + this.NodeInfo = NodeInfo; + this.MethodDetailss = MethodDetailss; + } + public NodeInfo NodeInfo { get; protected set; } + public MethodDetails MethodDetailss { get; protected set; } + } + + + /// + /// 加载DLL + /// + /// + public delegate void LoadDLLHandler(LoadDLLEventArgs eventArgs); + public class LoadDLLEventArgs : FlowEventArgs + { + public LoadDLLEventArgs(Assembly Assembly, List MethodDetailss) + { + this.Assembly = Assembly; + this.MethodDetailss = MethodDetailss; + } + public Assembly Assembly { get; protected set; } + public List MethodDetailss { get; protected set; } + } + + /// + /// 运行环境节点连接发生了改变 + /// + /// + /// + /// + public delegate void NodeConnectChangeHandler(NodeConnectChangeEventArgs eventArgs); + public class NodeConnectChangeEventArgs : FlowEventArgs + { + public enum ChangeTypeEnum + { + Create, + Remote, + } + public NodeConnectChangeEventArgs(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType, ChangeTypeEnum changeType) + { + this.FromNodeGuid = fromNodeGuid; + this.ToNodeGuid = toNodeGuid; + this.ConnectionType = connectionType; + this.ChangeType = changeType; + } + public string FromNodeGuid { get; protected set; } + public string ToNodeGuid { get; protected set; } + public ConnectionType ConnectionType { get; protected set; } + public ChangeTypeEnum ChangeType { get; protected set; } + } + + /// + /// 添加了节点 + /// + /// + /// + /// + public delegate void NodeCreateHandler(NodeCreateEventArgs eventArgs); + public class NodeCreateEventArgs : FlowEventArgs + { + public NodeCreateEventArgs(object nodeModel) + { + this.NodeModel = nodeModel; + } + public object NodeModel { get; private set; } + } + + + + public delegate void NodeRemoteHandler(NodeRemoteEventArgs eventArgs); + public class NodeRemoteEventArgs : FlowEventArgs + { + public NodeRemoteEventArgs(string nodeGuid) + { + this.NodeGuid = nodeGuid; + } + public string NodeGuid { get; private set; } + } + + + public delegate void StartNodeChangeHandler(StartNodeChangeEventArgs eventArgs); + + + public class StartNodeChangeEventArgs: FlowEventArgs + { + public StartNodeChangeEventArgs(string oldNodeGuid, string newNodeGuid) + { + this.OldNodeGuid = oldNodeGuid; + this.NewNodeGuid = newNodeGuid; ; + } + public string OldNodeGuid { get; private set; } + public string NewNodeGuid { get; private set; } + } + + + public interface IFlowEnvironment + { + event FlowRunCompleteHandler OnFlowRunComplete; + event LoadNodeHandler OnLoadNode; + event LoadDLLHandler OnDllLoad; + event NodeConnectChangeHandler OnNodeConnectChange; + event NodeCreateHandler OnNodeCreate; + event NodeRemoteHandler OnNodeRemote; + event StartNodeChangeHandler OnStartNodeChange; + + /// + /// 保存当前项目 + /// + /// + SereinOutputFileData SaveProject(); + /// + /// 加载项目文件 + /// + /// + /// + void LoadProject(SereinOutputFileData projectFile, string filePath); + /// + /// 从文件中加载Dll + /// + /// + void LoadDll(string dllPath); + /// + /// 清理加载的DLL(待更改) + /// + void ClearAll(); + /// + /// 获取方法描述 + /// + /// + /// + /// + bool TryGetMethodDetails(string methodName,out MethodDetails md); + + /// + /// 开始运行 + /// + Task StartAsync(); + /// + /// 结束运行 + /// + void Exit(); + + /// + /// 设置流程起点节点 + /// + /// + void SetStartNode(string nodeGuid); + /// + /// 在两个节点之间创建连接关系 + /// + /// 起始节点Guid + /// 目标节点Guid + /// 连接类型 + void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType); + /// + /// 创建节点/区域/基础控件 + /// + /// 节点/区域/基础控件 + /// 节点绑定的方法说明( + void CreateNode(NodeControlType nodeBase, MethodDetails methodDetails = null); + /// + /// 移除两个节点之间的连接关系 + /// + /// 起始节点 + /// 目标节点 + /// 连接类型 + void RemoteConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType); + /// + /// 移除节点/区域/基础控件 + /// + /// 待移除的节点Guid + void RemoteNode(string nodeGuid); + + + + } + +} diff --git a/Library/Base/NodeBase.cs b/Library/Base/NodeBase.cs new file mode 100644 index 0000000..fe8ac44 --- /dev/null +++ b/Library/Base/NodeBase.cs @@ -0,0 +1,83 @@ +using Serein.Library.Entity; +using Serein.Library.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Serein.Library.Base +{ + public abstract class NodeBase + { + /// + /// 节点类型 + /// + public abstract NodeControlType ControlType { get; set; } + + /// + /// 方法描述,对应DLL的方法 + /// + public abstract MethodDetails MethodDetails { get; set; } + + /// + /// 节点guid + /// + public abstract string Guid { get; set; } + + /// + /// 显示名称 + /// + public abstract string DisplayName { get; set; } + + /// + /// 是否为起点控件 + /// + public abstract bool IsStart { get; set; } + + /// + /// 运行时的上一节点 + /// + public abstract NodeBase PreviousNode { get; set; } + + /// + /// 上一节点集合 + /// + public abstract List PreviousNodes { get; set; } + + /// + /// 下一节点集合(真分支) + /// + public abstract List SucceedBranch { get; set; } + + /// + /// 下一节点集合(假分支) + /// + public abstract List FailBranch { get; set; } + + /// + /// 异常分支 + /// + public abstract List ErrorBranch { get; set; } + + /// + /// 上游分支 + /// + public abstract List UpstreamBranch { get; set; } + + /// + /// 当前执行状态(进入真分支还是假分支,异常分支在异常中确定) + /// + public abstract FlowStateType FlowState { get; set; } + + /// + /// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值) + /// + public abstract Exception RuningException { get; set; } + + /// + /// 当前传递数据(执行了节点对应的方法,才会存在值) + /// + public abstract object FlowData { get; set; } + + } +} diff --git a/Library/Base/NodeModelBaseData.cs b/Library/Base/NodeModelBaseData.cs new file mode 100644 index 0000000..7c8e260 --- /dev/null +++ b/Library/Base/NodeModelBaseData.cs @@ -0,0 +1,110 @@ +using Serein.Library.Api; +using Serein.Library.Entity; +using Serein.Library.Enums; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Serein.Library.Base +{ + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + public abstract partial class NodeModelBase : IDynamicFlowNode + { + public NodeControlType ControlType { get; set; } + + /// + /// 方法描述,对应DLL的方法 + /// + public MethodDetails MethodDetails { get; set; } + + /// + /// 节点guid + /// + public string Guid { get; set; } + + /// + /// 显示名称 + /// + public string DisplayName { get; set; } + + /// + /// 是否为起点控件 + /// + public bool IsStart { get; set; } + + /// + /// 运行时的上一节点 + /// + public NodeModelBase PreviousNode { get; set; } + + /// + /// 上一节点集合 + /// + public List PreviousNodes { get; set; } = new List(); + + /// + /// 下一节点集合(真分支) + /// + public List SucceedBranch { get; set; } = new List(); + + /// + /// 下一节点集合(假分支) + /// + public List FailBranch { get; set; } = new List(); + + /// + /// 异常分支 + /// + public List ErrorBranch { get; set; } = new List(); + + /// + /// 上游分支 + /// + public List UpstreamBranch { get; set; } = new List(); + + /// + /// 当前执行状态(进入真分支还是假分支,异常分支在异常中确定) + /// + public FlowStateType FlowState { get; set; } = FlowStateType.None; + + /// + /// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值) + /// + public Exception RuningException { get; set; } = null; + + /// + /// 当前传递数据(执行了节点对应的方法,才会存在值) + /// + public object FlowData { get; set; } = null; + + public abstract Parameterdata[] GetParameterdatas(); + public virtual NodeInfo ToInfo() + { + if (MethodDetails == null) return null; + + var trueNodes = SucceedBranch.Select(item => item.Guid); // 真分支 + var falseNodes = FailBranch.Select(item => item.Guid);// 假分支 + var upstreamNodes = UpstreamBranch.Select(item => item.Guid);// 上游分支 + var errorNodes = ErrorBranch.Select(item => item.Guid);// 异常分支 + + // 生成参数列表 + Parameterdata[] parameterData = GetParameterdatas(); + + return new NodeInfo + { + Guid = Guid, + MethodName = MethodDetails?.MethodName, + Label = DisplayName ?? "", + Type = this.GetType().ToString(), + TrueNodes = trueNodes.ToArray(), + FalseNodes = falseNodes.ToArray(), + UpstreamNodes = upstreamNodes.ToArray(), + ParameterData = parameterData.ToArray(), + ErrorNodes = errorNodes.ToArray(), + }; + } + + } +} diff --git a/Library/Base/NodeModelBaseFunc.cs b/Library/Base/NodeModelBaseFunc.cs new file mode 100644 index 0000000..1c53181 --- /dev/null +++ b/Library/Base/NodeModelBaseFunc.cs @@ -0,0 +1,480 @@ +using Newtonsoft.Json; +using Serein.Library.Api; +using Serein.Library.Entity; +using Serein.Library.Enums; +using Serein.Library.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Serein.Library.Base +{ + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + public abstract partial class NodeModelBase : IDynamicFlowNode + { + /// + /// 执行节点对应的方法 + /// + /// 流程上下文 + /// 节点传回数据对象 + public virtual object Execute(IDynamicContext context) + { + MethodDetails md = MethodDetails; + object result = null; + var del = md.MethodDelegate; + try + { + if (md.ExplicitDatas.Length == 0) + { + if (md.ReturnType == typeof(void)) + { + ((Action)del).Invoke(md.ActingInstance); + } + else + { + result = ((Func)del).Invoke(md.ActingInstance); + } + } + else + { + object[] parameters = GetParameters(context, MethodDetails); + if (md.ReturnType == typeof(void)) + { + ((Action)del).Invoke(md.ActingInstance, parameters); + } + else + { + result = ((Func)del).Invoke(md.ActingInstance, parameters); + } + } + + return result; + } + catch (Exception ex) + { + FlowState = FlowStateType.Error; + RuningException = ex; + } + + return result; + } + + /// + /// 执行等待触发器的方法 + /// + /// + /// 节点传回数据对象 + /// + public virtual async Task ExecuteAsync(IDynamicContext context) + { + MethodDetails md = MethodDetails; + object result = null; + + IFlipflopContext flipflopContext = null; + try + { + // 调用委托并获取结果 + if (md.ExplicitDatas.Length == 0) + { + flipflopContext = await ((Func>)md.MethodDelegate).Invoke(MethodDetails.ActingInstance); + } + else + { + object[] parameters = GetParameters(context, MethodDetails); + flipflopContext = await ((Func>)md.MethodDelegate).Invoke(MethodDetails.ActingInstance, parameters); + } + + if (flipflopContext != null) + { + FlowState = flipflopContext.State; + if (flipflopContext.State == FlowStateType.Succeed) + { + result = flipflopContext.Data; + } + else + { + result = null; + } + } + } + catch (Exception ex) + { + FlowState = FlowStateType.Error; + RuningException = ex; + } + + return result; + } + + /// + /// 开始执行 + /// + /// + /// + public async Task StartExecution(IDynamicContext context) + { + var cts = context.SereinIoc.GetOrInstantiate(); + + Stack stack = new Stack(); + stack.Push(this); + + while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环 + { + // 从栈中弹出一个节点作为当前节点进行处理 + var currentNode = stack.Pop(); + + // 设置方法执行的对象 + if (currentNode.MethodDetails != null) + { + if(currentNode.MethodDetails.ActingInstance == null) + { + currentNode.MethodDetails.ActingInstance = context.SereinIoc.GetOrInstantiate(MethodDetails.ActingInstanceType); + + } + } + + // 获取上游分支,首先执行一次 + var upstreamNodes = currentNode.UpstreamBranch; + for (int i = upstreamNodes.Count - 1; i >= 0; i--) + { + upstreamNodes[i].PreviousNode = currentNode; + await upstreamNodes[i].StartExecution(context); + } + + if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == NodeType.Flipflop) + { + // 触发器节点 + currentNode.FlowData = await currentNode.ExecuteAsync(context); + } + else + { + // 动作节点 + currentNode.FlowData = currentNode.Execute(context); + } + + List nextNodes = null ; + switch (currentNode.FlowState) + { + case FlowStateType.Succeed: + nextNodes = currentNode.SucceedBranch; + break; + case FlowStateType.Fail : + nextNodes = currentNode.FailBranch; + break; + case FlowStateType.Error : + nextNodes = currentNode.ErrorBranch; + break; + } + if(nextNodes != null) + { + for (int i = nextNodes.Count - 1; i >= 0; i--) + { + nextNodes[i].PreviousNode = currentNode; + stack.Push(nextNodes[i]); + } + } + /*var nextNodes = currentNode.FlowState switch + { + FlowStateType.Succeed => currentNode.SucceedBranch, + FlowStateType.Fail => currentNode.FailBranch, + FlowStateType.Error => currentNode.ErrorBranch, + _ => throw new Exception("非预期的枚举值") + };*/ + + // 将下一个节点集合中的所有节点逆序推入栈中 + + } + } + + /// + /// 获取对应的参数数组 + /// + public object[] GetParameters(IDynamicContext context, MethodDetails md) + { + // 用正确的大小初始化参数数组 + var types = md.ExplicitDatas.Select(it => it.DataType).ToArray(); + if (types.Length == 0) + { + return new object[] { md.ActingInstance }; + } + + object[] parameters = new object[types.Length]; + + for (int i = 0; i < types.Length; i++) + { + + var mdEd = md.ExplicitDatas[i]; + Type type = mdEd.DataType; + + var f1 = PreviousNode?.FlowData?.GetType(); + var f2 = mdEd.DataType; + if (type == typeof(IDynamicContext)) + { + parameters[i] = context; + } + else if (type == typeof(MethodDetails)) + { + parameters[i] = md; + } + else if (type == typeof(NodeModelBase)) + { + parameters[i] = this; + } + else if (mdEd.IsExplicitData) // 显式参数 + { + // 判断是否使用表达式解析 + if (mdEd.DataValue[0] == '@') + { + var expResult = SerinExpressionEvaluator.Evaluate(mdEd.DataValue, PreviousNode?.FlowData, out bool isChange); + + + if (mdEd.DataType.IsEnum) + { + var enumValue = Enum.Parse(mdEd.DataType, mdEd.DataValue); + parameters[i] = enumValue; + } + else if (mdEd.ExplicitType == typeof(string)) + { + parameters[i] = Convert.ChangeType(expResult, typeof(string)); + } + else if (mdEd.ExplicitType == typeof(bool)) + { + parameters[i] = Convert.ChangeType(expResult, typeof(bool)); + } + else if (mdEd.ExplicitType == typeof(int)) + { + parameters[i] = Convert.ChangeType(expResult, typeof(int)); + } + else if (mdEd.ExplicitType == typeof(double)) + { + parameters[i] = Convert.ChangeType(expResult, typeof(double)); + } + else + { + parameters[i] = expResult; + //parameters[i] = ConvertValue(mdEd.DataValue, mdEd.ExplicitType); + } + } + else + { + if (mdEd.DataType.IsEnum) + { + var enumValue = Enum.Parse(mdEd.DataType, mdEd.DataValue); + parameters[i] = enumValue; + } + else if (mdEd.ExplicitType == typeof(string)) + { + parameters[i] = mdEd.DataValue; + } + else if (mdEd.ExplicitType == typeof(bool)) + { + parameters[i] = bool.Parse(mdEd.DataValue); + } + else if (mdEd.ExplicitType == typeof(int)) + { + parameters[i] = int.Parse(mdEd.DataValue); + } + else if (mdEd.ExplicitType == typeof(double)) + { + parameters[i] = double.Parse(mdEd.DataValue); + } + else + { + parameters[i] = ""; + + //parameters[i] = ConvertValue(mdEd.DataValue, mdEd.ExplicitType); + } + } + + + } + else if (f1 != null && f2 != null) + { + if (f2.IsAssignableFrom(f1) || f2.FullName.Equals(f1.FullName)) + { + parameters[i] = PreviousNode?.FlowData; + + } + } + else + { + + + var tmpParameter = PreviousNode?.FlowData?.ToString(); + if (mdEd.DataType.IsEnum) + { + + var enumValue = Enum.Parse(mdEd.DataType, tmpParameter); + + parameters[i] = enumValue; + } + else if (mdEd.DataType == typeof(string)) + { + + parameters[i] = tmpParameter; + + } + else if (mdEd.DataType == typeof(bool)) + { + + parameters[i] = bool.Parse(tmpParameter); + + } + else if (mdEd.DataType == typeof(int)) + { + + parameters[i] = int.Parse(tmpParameter); + + } + else if (mdEd.DataType == typeof(double)) + { + + parameters[i] = double.Parse(tmpParameter); + + } + else + { + if (tmpParameter != null && mdEd.DataType != null) + { + + parameters[i] = ConvertValue(tmpParameter, mdEd.DataType); + + } + } + } + + } + return parameters; + } + + /// + /// json文本反序列化为对象 + /// + /// + /// + /// + private dynamic ConvertValue(string value, Type targetType) + { + try + { + if (!string.IsNullOrEmpty(value)) + { + return JsonConvert.DeserializeObject(value, targetType); + } + else + { + return null; + } + } + catch (JsonReaderException ex) + { + Console.WriteLine(ex); + return value; + } + catch (JsonSerializationException ex) + { + // 如果无法转为对应的JSON对象 + int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引 + int endIndex = ex.Message.IndexOf('\''); // 查找类型信息结束的索引 + var typeInfo = ex.Message.Substring(startIndex,endIndex); // 提取出错类型信息,该怎么传出去? + Console.WriteLine("无法转为对应的JSON对象:" + typeInfo); + return null; + } + catch // (Exception ex) + { + return value; + } + } + + + + + + #region 完整的ExecuteAsync调用方法(不要删除) + //public virtual async Task ExecuteAsync(DynamicContext context) + //{ + // MethodDetails md = MethodDetails; + // object? result = null; + // if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) + // { + // if (md.ExplicitDatas.Length == 0) + // { + // if (md.ReturnType == typeof(void)) + // { + // ((Action)del).Invoke(md.ActingInstance); + // } + // else if (md.ReturnType == typeof(Task)) + // { + // // 调用委托并获取结果 + // FlipflopContext flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance); + + // if (flipflopContext != null) + // { + // if (flipflopContext.State == FfState.Cancel) + // { + // throw new Exception("this async task is cancel."); + // } + // else + // { + // if (flipflopContext.State == FfState.Succeed) + // { + // CurrentState = true; + // result = flipflopContext.Data; + // } + // else + // { + // CurrentState = false; + // } + // } + // } + // } + // else + // { + // result = ((Func)del).Invoke(md.ActingInstance); + // } + // } + // else + // { + // object?[]? parameters = GetParameters(context, MethodDetails); + // if (md.ReturnType == typeof(void)) + // { + // ((Action)del).Invoke(md.ActingInstance, parameters); + // } + // else if (md.ReturnType == typeof(Task)) + // { + // // 调用委托并获取结果 + // FlipflopContext flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance, parameters); + + // if (flipflopContext != null) + // { + // if (flipflopContext.State == FfState.Cancel) + // { + // throw new Exception("取消此异步"); + // } + // else + // { + // CurrentState = flipflopContext.State == FfState.Succeed; + // result = flipflopContext.Data; + // } + // } + // } + // else + // { + // result = ((Func)del).Invoke(md.ActingInstance, parameters); + // } + // } + // context.SetFlowData(result); + // } + // return result; + //} + #endregion + + + + + } +} diff --git a/Library/Entity/ExplicitData.cs b/Library/Entity/ExplicitData.cs new file mode 100644 index 0000000..8f46811 --- /dev/null +++ b/Library/Entity/ExplicitData.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Serein.Library.Entity +{ + + /// + /// 参数 + /// + public class ExplicitData + { + /// + /// 索引 + /// + public int Index { get; set; } + /// + /// 是否为显式参数 + /// + public bool IsExplicitData { get; set; } + /// + /// 显式类型 + /// + public Type ExplicitType { get; set; } + + /// + /// 显示类型编号> + /// + public string ExplicitTypeName { get; set; } + + /// + /// 方法需要的类型 + /// + public Type DataType { get; set; } + + /// + /// 方法入参参数名称 + /// + public string ParameterName { get; set; } + + /// + /// 入参值(在UI上输入的文本内容) + /// + + public string DataValue { get; set; } + + + + public string[] Items { get; set; } + + + + + public ExplicitData Clone() => new ExplicitData() + { + Index = Index, + IsExplicitData = IsExplicitData, + ExplicitType = ExplicitType, + DataType = DataType, + ParameterName = ParameterName, + ExplicitTypeName = ExplicitTypeName, + DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue, + Items = Items.Select(it => it).ToArray(), + }; + } + + +} diff --git a/Library/Entity/MethodDetails.cs b/Library/Entity/MethodDetails.cs new file mode 100644 index 0000000..f534ab0 --- /dev/null +++ b/Library/Entity/MethodDetails.cs @@ -0,0 +1,183 @@ +using Serein.Library.Api; +using Serein.Library.Enums; +using System; +using System.Linq; + +namespace Serein.Library.Entity +{ + + + + public class MethodDetails + { + /// + /// 拷贝 + /// + /// + public MethodDetails Clone() + { + return new MethodDetails + { + ActingInstance = ActingInstance, + ActingInstanceType = ActingInstanceType, + MethodDelegate = MethodDelegate, + MethodDynamicType = MethodDynamicType, + MethodGuid = Guid.NewGuid().ToString(), + MethodTips = MethodTips, + ReturnType = ReturnType, + MethodName = MethodName, + MethodLockName = MethodLockName, + IsNetFramework = IsNetFramework, + ExplicitDatas = ExplicitDatas.Select(it => it.Clone()).ToArray(), + }; + } + + /// + /// 作用实例 + /// + + public Type ActingInstanceType { get; set; } + + /// + /// 作用实例 + /// + + public object ActingInstance { get; set; } + + /// + /// 方法GUID + /// + + public string MethodGuid { get; set; } + + /// + /// 方法名称 + /// + + public string MethodName { get; set; } + + /// + /// 方法委托 + /// + + public Delegate MethodDelegate { get; set; } + + /// + /// 节点类型 + /// + public NodeType MethodDynamicType { get; set; } + /// + /// 锁名称 + /// + + public string MethodLockName { get; set; } + + + /// + /// 方法说明 + /// + + public string MethodTips { get; set; } + + + /// + /// 参数内容 + /// + + public ExplicitData[] ExplicitDatas { get; set; } + + /// + /// 出参类型 + /// + + public Type ReturnType { get; set; } + + public bool IsNetFramework { get; set; } + + + + + + //public bool IsCanConnect(Type returnType) + //{ + // if (ExplicitDatas.Length == 0) + // { + // // 目标不需要传参,可以舍弃结果? + // return true; + // } + // var types = ExplicitDatas.Select(it => it.DataType).ToArray(); + // // 检查返回类型是否是元组类型 + // if (returnType.IsGenericType && IsValueTuple(returnType)) + // { + + // return CompareGenericArguments(returnType, types); + // } + // else + // { + // int index = 0; + // if (types[index] == typeof(DynamicContext)) + // { + // index++; + // if (types.Length == 1) + // { + // return true; + // } + // } + // // 被连接节点检查自己需要的参数类型,与发起连接的节点比较返回值类型 + // if (returnType == types[index]) + // { + // return true; + // } + // } + // return false; + //} + + ///// + ///// 检查元组类型 + ///// + ///// + ///// + //private bool IsValueTuple(Type type) + //{ + // if (!type.IsGenericType) return false; + + // var genericTypeDef = type.GetGenericTypeDefinition(); + // return genericTypeDef == typeof(ValueTuple<>) || + // genericTypeDef == typeof(ValueTuple<,>) || + // genericTypeDef == typeof(ValueTuple<,,>) || + // genericTypeDef == typeof(ValueTuple<,,,>) || + // genericTypeDef == typeof(ValueTuple<,,,,>) || + // genericTypeDef == typeof(ValueTuple<,,,,,>) || + // genericTypeDef == typeof(ValueTuple<,,,,,,>) || + // genericTypeDef == typeof(ValueTuple<,,,,,,,>); + //} + + //private bool CompareGenericArguments(Type returnType, Type[] parameterTypes) + //{ + // var genericArguments = returnType.GetGenericArguments(); + // var length = parameterTypes.Length; + + // for (int i = 0; i < genericArguments.Length; i++) + // { + // if (i >= length) return false; + + // if (IsValueTuple(genericArguments[i])) + // { + // // 如果当前参数也是 ValueTuple,递归检查嵌套的泛型参数 + // if (!CompareGenericArguments(genericArguments[i], parameterTypes.Skip(i).ToArray())) + // { + // return false; + // } + // } + // else if (genericArguments[i] != parameterTypes[i]) + // { + // return false; + // } + // } + + // return true; + //} + } + + +} diff --git a/Library/Entity/SereinOutputFileData.cs b/Library/Entity/SereinOutputFileData.cs new file mode 100644 index 0000000..12e4421 --- /dev/null +++ b/Library/Entity/SereinOutputFileData.cs @@ -0,0 +1,202 @@ +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Entity +{ + + /// + /// 输出文件 + /// + public class SereinOutputFileData + { + /// + /// 基础 + /// + + public Basic Basic { get; set; } + + /// + /// 依赖的DLL + /// + + public Library[] Librarys { get; set; } + + /// + /// 起始节点GUID + /// + + public string StartNode { get; set; } + + /// + /// 节点集合 + /// + + public NodeInfo[] Nodes { get; set; } + + ///// + ///// 区域集合 + ///// + + //public Region[] Regions { get; set; } + + } + + /// + /// 基础 + /// + public class Basic + { + /// + /// 画布 + /// + + public FlowCanvas canvas { get; set; } + + /// + /// 版本 + /// + + public string versions { get; set; } + + // 预览位置 + + // 缩放比例 + } + /// + /// 画布 + /// + public class FlowCanvas + { + /// + /// 宽度 + /// + public float width { get; set; } + /// + /// 高度 + /// + public float lenght { get; set; } + } + + /// + /// DLL + /// + public class Library + { + /// + /// DLL名称 + /// + + public string Name { get; set; } + + /// + /// 路径 + /// + + public string Path { get; set; } + + + } + /// + /// 节点 + /// + public class NodeInfo + { + /// + /// GUID + /// + + public string Guid { get; set; } + + /// + /// 名称 + /// + + public string MethodName { get; set; } + + /// + /// 显示标签 + /// + + public string Label { get; set; } + + /// + /// 类型 + /// + public string Type { get; set; } + + /// + /// 真分支节点GUID + /// + + public string[] TrueNodes { get; set; } + + /// + /// 假分支节点 + /// + + public string[] FalseNodes { get; set; } + /// + /// 上游分支 + /// + public string[] UpstreamNodes { get; set; } + /// + /// 异常分支 + /// + public string[] ErrorNodes { get; set; } + + /// + /// 参数 + /// + public Parameterdata[] ParameterData { get; set; } + + /// + /// 如果是区域控件,则会存在子项。 + /// + public NodeInfo[] ChildNodes { get; set; } + + + /// + /// 于画布中的位置 + /// + + public Position Position { get; set; } + + /// + /// 是否选中 + /// + public bool IsSelect { get; set; } + } + + public class Parameterdata + { + public bool state { get; set; } + public string value { get; set; } + public string expression { get; set; } + + } + + + /// + /// 节点于画布中的位置 + /// + public class Position + { + public float X { get; set; } + public float Y { get; set; } + } + + + /// + /// 区域 + /// + public class Region + { + public string guid { get; set; } + public NodeInfo[] ChildNodes { get; set; } + + } +} diff --git a/Library/Enums/ConnectionType.cs b/Library/Enums/ConnectionType.cs new file mode 100644 index 0000000..8c40635 --- /dev/null +++ b/Library/Enums/ConnectionType.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Serein.Library.Enums +{ + public enum ConnectionType + { + /// + /// 真分支 + /// + IsSucceed, + /// + /// 假分支 + /// + IsFail, + /// + /// 异常发生分支 + /// + IsError, + /// + /// 上游分支(执行当前节点前会执行一次上游分支) + /// + Upstream, + } + + +} diff --git a/Library/Enums/FlowStateType.cs b/Library/Enums/FlowStateType.cs index a80c395..f142e27 100644 --- a/Library/Enums/FlowStateType.cs +++ b/Library/Enums/FlowStateType.cs @@ -9,6 +9,10 @@ namespace Serein.Library.Enums public enum FlowStateType { + /// + /// 待执行 + /// + None, /// /// 成功(方法成功执行) /// diff --git a/Library/Enums/NodeType.cs b/Library/Enums/NodeType.cs index 8307133..a3f1719 100644 --- a/Library/Enums/NodeType.cs +++ b/Library/Enums/NodeType.cs @@ -9,15 +9,15 @@ namespace Serein.Library.Enums public enum NodeType { /// - /// 初始化 + /// 初始化(事件,不生成节点) /// Init, /// - /// 开始载入 + /// 开始载入(事件,不生成节点) /// Loading, /// - /// 结束 + /// 结束(事件,不生成节点) /// Exit, @@ -26,13 +26,39 @@ namespace Serein.Library.Enums /// Flipflop, /// - /// 条件节点 + /// 条件 /// Condition, /// - /// 动作节点 + /// 动作 /// Action, } + + public enum NodeControlType + { + None, + /// + /// 动作节点 + /// + Action, + /// + /// 触发器节点 + /// + Flipflop, + /// + /// 表达式操作节点 + /// + ExpOp, + /// + /// 表达式操作节点 + /// + ExpCondition, + /// + /// 条件节点区域 + /// + ConditionRegion, + } + } diff --git a/Library/NodeAttribute.cs b/Library/NodeAttribute.cs index 7ebde65..a0aab98 100644 --- a/Library/NodeAttribute.cs +++ b/Library/NodeAttribute.cs @@ -24,6 +24,8 @@ namespace Serein.Library.Attributes public bool Scan { get; set; } = true; } + + /// /// 标记一个方法是什么类型,加载dll后用来拖拽到画布中 /// @@ -40,10 +42,14 @@ namespace Serein.Library.Attributes MethodTips = methodTips; LockName = lockName; } - public bool Scan { get; set; } - public string MethodTips { get; } - public NodeType MethodDynamicType { get; } - public string LockName { get; } + public bool Scan; + public string MethodTips; + public NodeType MethodDynamicType; + /// + /// 推荐触发器手动设置返回类型 + /// + public Type ReturnType; + public string LockName; } } diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index 756371e..d21c12d 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -4,4 +4,24 @@ netstandard2.0;net461 + + + + + + + + + + + + + + + + + + + + diff --git a/Library/Utils/SerinExpression/ConditionResolver.cs b/Library/Utils/SerinExpression/ConditionResolver.cs new file mode 100644 index 0000000..70cda34 --- /dev/null +++ b/Library/Utils/SerinExpression/ConditionResolver.cs @@ -0,0 +1,338 @@ +using System; +using System.Reflection; + +namespace Serein.NodeFlow.Tool.SerinExpression +{ + /// + /// 条件解析抽象类 + /// + public abstract class ConditionResolver + { + public abstract bool Evaluate(object obj); + } + + public class PassConditionResolver : ConditionResolver + { + public Operator Op { get; set; } + public override bool Evaluate(object obj) + { + return Op switch + { + Operator.Pass => true, + Operator.NotPass => false, + _ => throw new NotSupportedException("不支持的条件类型") + }; + } + + public enum Operator + { + Pass, + NotPass, + } + + } + + public class ValueTypeConditionResolver : ConditionResolver where T : struct, IComparable + { + public enum Operator + { + /// + /// 不进行任何操作 + /// + Node, + /// + /// 大于 + /// + GreaterThan, + /// + /// 小于 + /// + LessThan, + /// + /// 等于 + /// + Equal, + /// + /// 大于或等于 + /// + GreaterThanOrEqual, + /// + /// 小于或等于 + /// + LessThanOrEqual, + /// + /// 在两者之间 + /// + InRange, + /// + /// 不在两者之间 + /// + OutOfRange + } + + public Operator Op { get; set; } + public T Value { get; set; } + public T RangeStart { get; set; } + public T RangeEnd { get; set; } + + public string ArithmeticExpression { get; set; } + + + public override bool Evaluate(object obj) + { + if (obj is T typedObj) + { + double numericValue = Convert.ToDouble(typedObj); + if (!string.IsNullOrEmpty(ArithmeticExpression)) + { + numericValue = SerinArithmeticExpressionEvaluator.Evaluate(ArithmeticExpression, numericValue); + } + + T evaluatedValue = (T)Convert.ChangeType(numericValue, typeof(T)); + + return Op switch + { + Operator.GreaterThan => evaluatedValue.CompareTo(Value) > 0, + Operator.LessThan => evaluatedValue.CompareTo(Value) < 0, + Operator.Equal => evaluatedValue.CompareTo(Value) == 0, + Operator.GreaterThanOrEqual => evaluatedValue.CompareTo(Value) >= 0, + Operator.LessThanOrEqual => evaluatedValue.CompareTo(Value) <= 0, + Operator.InRange => evaluatedValue.CompareTo(RangeStart) >= 0 && evaluatedValue.CompareTo(RangeEnd) <= 0, + Operator.OutOfRange => evaluatedValue.CompareTo(RangeStart) < 0 || evaluatedValue.CompareTo(RangeEnd) > 0, + _ => throw new NotSupportedException("不支持的条件类型") + }; + /* switch (Op) + { + case Operator.GreaterThan: + return evaluatedValue.CompareTo(Value) > 0; + case Operator.LessThan: + return evaluatedValue.CompareTo(Value) < 0; + case Operator.Equal: + return evaluatedValue.CompareTo(Value) == 0; + case Operator.GreaterThanOrEqual: + return evaluatedValue.CompareTo(Value) >= 0; + case Operator.LessThanOrEqual: + return evaluatedValue.CompareTo(Value) <= 0; + case Operator.InRange: + return evaluatedValue.CompareTo(RangeStart) >= 0 && evaluatedValue.CompareTo(RangeEnd) <= 0; + case Operator.OutOfRange: + return evaluatedValue.CompareTo(RangeStart) < 0 || evaluatedValue.CompareTo(RangeEnd) > 0; + }*/ + } + return false; + } + } + + public class BoolConditionResolver : ConditionResolver + { + public enum Operator + { + /// + /// 是 + /// + Is + } + + public Operator Op { get; set; } + public bool Value { get; set; } + + public override bool Evaluate(object obj) + { + + if (obj is bool boolObj) + { + return boolObj == Value; + /*switch (Op) + { + case Operator.Is: + return boolObj == Value; + }*/ + } + return false; + } + } + + public class StringConditionResolver : ConditionResolver + { + public enum Operator + { + /// + /// 出现过 + /// + Contains, + /// + /// 没有出现过 + /// + DoesNotContain, + /// + /// 相等 + /// + Equal, + /// + /// 不相等 + /// + NotEqual, + /// + /// 起始字符串等于 + /// + StartsWith, + /// + /// 结束字符串等于 + /// + EndsWith + } + + public Operator Op { get; set; } + + public string Value { get; set; } + + + public override bool Evaluate(object obj) + { + if (obj is string strObj) + { + return Op switch + { + Operator.Contains => strObj.Contains(Value), + Operator.DoesNotContain => !strObj.Contains(Value), + Operator.Equal => strObj == Value, + Operator.NotEqual => strObj != Value, + Operator.StartsWith => strObj.StartsWith(Value), + Operator.EndsWith => strObj.EndsWith(Value), + _ => throw new NotSupportedException("不支持的条件类型"), + }; + + /* switch (Op) + { + case Operator.Contains: + return strObj.Contains(Value); + case Operator.DoesNotContain: + return !strObj.Contains(Value); + case Operator.Equal: + return strObj == Value; + case Operator.NotEqual: + return strObj != Value; + case Operator.StartsWith: + return strObj.StartsWith(Value); + case Operator.EndsWith: + return strObj.EndsWith(Value); + }*/ + } + return false; + } + } + public class MemberConditionResolver : ConditionResolver where T : struct, IComparable + { + //public string MemberPath { get; set; } + public ValueTypeConditionResolver.Operator Op { get; set; } + public object? TargetObj { get; set; } + public T Value { get; set; } + + public string ArithmeticExpression { get; set; } + + public override bool Evaluate(object? obj) + { + //object? memberValue = GetMemberValue(obj, MemberPath); + if (TargetObj is T typedObj) + { + return new ValueTypeConditionResolver + { + Op = Op, + Value = Value, + ArithmeticExpression = ArithmeticExpression, + }.Evaluate(typedObj); + } + return false; + } + + //private object? GetMemberValue(object? obj, string memberPath) + //{ + // string[] members = memberPath[1..].Split('.'); + // foreach (var member in members) + // { + // if (obj == null) return null; + // Type type = obj.GetType(); + // PropertyInfo? propertyInfo = type.GetProperty(member); + // FieldInfo? fieldInfo = type.GetField(member); + // if (propertyInfo != null) + // obj = propertyInfo.GetValue(obj); + // else if (fieldInfo != null) + // obj = fieldInfo.GetValue(obj); + // else + // throw new ArgumentException($"Member {member} not found in type {type.FullName}"); + // } + // return obj; + //} + } + + public class MemberStringConditionResolver : ConditionResolver + { + + public string MemberPath { get; set; } + + public StringConditionResolver.Operator Op { get; set; } + + public string Value { get; set; } + + + public override bool Evaluate(object obj) + { + object memberValue = GetMemberValue(obj, MemberPath); + if (memberValue is string strObj) + { + return new StringConditionResolver + { + Op = Op, + Value = Value + }.Evaluate(strObj); + } + return false; + } + + private object GetMemberValue(object? obj, string memberPath) + { + string[] members = memberPath[1..].Split('.'); + foreach (var member in members) + { + + if (obj == null) return null; + + Type type = obj.GetType(); + PropertyInfo? propertyInfo = type.GetProperty(member); + FieldInfo? fieldInfo = type.GetField(member); + if (propertyInfo != null) + obj = propertyInfo.GetValue(obj); + else if (fieldInfo != null) + obj = fieldInfo.GetValue(obj); + else + throw new ArgumentException($"Member {member} not found in type {type.FullName}"); + } + + return obj; + + } + + + + + + private static string GetArithmeticExpression(string part) + { + int startIndex = part.IndexOf('['); + int endIndex = part.IndexOf(']'); + if (startIndex >= 0 && endIndex > startIndex) + { + return part.Substring(startIndex + 1, endIndex - startIndex - 1); + } + + return null; + + } + + + + + + } + +} diff --git a/Library/Utils/SerinExpression/SerinConditionParser.cs b/Library/Utils/SerinExpression/SerinConditionParser.cs new file mode 100644 index 0000000..7f3e92f --- /dev/null +++ b/Library/Utils/SerinExpression/SerinConditionParser.cs @@ -0,0 +1,342 @@ +using System; +using System.Globalization; +using System.Reflection; + +namespace Serein.NodeFlow.Tool.SerinExpression +{ + + public class SerinConditionParser + { + public static bool To(T data, string expression) + { + try + { + + return ConditionParse(data, expression).Evaluate(data); + + } + catch (Exception ex) + { + Console.WriteLine(ex); + throw; + } + } + + public static ConditionResolver ConditionParse(object data, string expression) + { + if (expression.StartsWith('.')) // 表达式前缀属于从上一个节点数据对象获取成员值 + { + return ParseObjectExpression(data, expression); + } + else + { + return ParseSimpleExpression(data, expression); + } + + + //bool ContainsArithmeticOperators(string expression) + //{ + // return expression.Contains('+') || expression.Contains('-') || expression.Contains('*') || expression.Contains('/'); + //} + + } + + /// + /// 获取计算表达式的部分 + /// + /// + /// + private static string GetArithmeticExpression(string part) + { + int startIndex = part.IndexOf('['); + int endIndex = part.IndexOf(']'); + if (startIndex >= 0 && endIndex > startIndex) + { + return part.Substring(startIndex + 1, endIndex - startIndex - 1); + } + + return null; + + } + /// + /// 获取对象指定名称的成员 + /// + private static object? GetMemberValue(object? obj, string memberPath) + { + string[] members = memberPath[1..].Split('.'); + foreach (var member in members) + { + if (obj == null) return null; + Type type = obj.GetType(); + PropertyInfo? propertyInfo = type.GetProperty(member); + FieldInfo? fieldInfo = type.GetField(member); + if (propertyInfo != null) + obj = propertyInfo.GetValue(obj); + else if (fieldInfo != null) + obj = fieldInfo.GetValue(obj); + else + throw new ArgumentException($"Member {member} not found in type {type.FullName}"); + } + return obj; + } + /// + /// 解析对象表达式 + /// + private static ConditionResolver ParseObjectExpression(object data, string expression) + { + var parts = expression.Split(' '); + string operatorStr = parts[0]; + string valueStr = string.Join(' ', parts, 1, parts.Length - 1); + + int typeStartIndex = expression.IndexOf('<'); + int typeEndIndex = expression.IndexOf('>'); + + string memberPath; + Type type; + object? targetObj; + if (typeStartIndex + typeStartIndex == -2) + { + memberPath = operatorStr; + targetObj = GetMemberValue(data, operatorStr); + + type = targetObj.GetType(); + + operatorStr = parts[1].ToLower(); + valueStr = string.Join(' ', parts.Skip(2)); + } + else + { + if (typeStartIndex >= typeEndIndex) + { + throw new ArgumentException("无效的表达式格式"); + } + memberPath = expression.Substring(0, typeStartIndex).Trim(); + string typeStr = expression.Substring(typeStartIndex + 1, typeEndIndex - typeStartIndex - 1).Trim().ToLower(); + parts = expression.Substring(typeEndIndex + 1).Trim().Split(' '); + if (parts.Length == 3) + { + operatorStr = parts[1].ToLower(); + valueStr = string.Join(' ', parts.Skip(2)); + } + else + { + operatorStr = parts[0].ToLower(); + valueStr = string.Join(' ', parts.Skip(1)); + } + targetObj = GetMemberValue(data, memberPath); + + Type? tempType = typeStr switch + { + "int" => typeof(int), + "double" => typeof(double), + "bool" => typeof(bool), + "string" => typeof(string), + _ => Type.GetType(typeStr) + }; + type = tempType ?? throw new ArgumentException("对象表达式无效的类型声明"); + } + + + + if (type == typeof(int)) + { + int value = int.Parse(valueStr, CultureInfo.InvariantCulture); + return new MemberConditionResolver + { + TargetObj = targetObj, + //MemberPath = memberPath, + Op = ParseValueTypeOperator(operatorStr), + Value = value, + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + } + else if (type == typeof(double)) + { + double value = double.Parse(valueStr, CultureInfo.InvariantCulture); + return new MemberConditionResolver + { + //MemberPath = memberPath, + TargetObj = targetObj, + Op = ParseValueTypeOperator(operatorStr), + Value = value, + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + + } + else if (type == typeof(bool)) + { + return new MemberConditionResolver + { + //MemberPath = memberPath, + TargetObj = targetObj, + Op = (ValueTypeConditionResolver.Operator)ParseBoolOperator(operatorStr) + }; + } + else if (type == typeof(string)) + { + return new MemberStringConditionResolver + { + MemberPath = memberPath, + Op = ParseStringOperator(operatorStr), + Value = valueStr + }; + } + + throw new NotSupportedException($"Type {type} is not supported."); + } + + private static ConditionResolver ParseSimpleExpression(object data, string expression) + { + if ("pass".Equals(expression.ToLower())) + { + return new PassConditionResolver + { + Op = PassConditionResolver.Operator.Pass, + }; + } + else + { + if ("not pass".Equals(expression.ToLower())) + { + return new PassConditionResolver + { + Op = PassConditionResolver.Operator.NotPass, + }; + } + if ("!pass".Equals(expression.ToLower())) + { + return new PassConditionResolver + { + Op = PassConditionResolver.Operator.NotPass, + }; + } + } + + + var parts = expression.Split(' '); + + if (parts.Length < 2) + throw new ArgumentException("无效的表达式格式。"); + + //string typeStr = parts[0]; + string operatorStr = parts[0]; + string valueStr = string.Join(' ', parts, 1, parts.Length - 1); + + Type type = data.GetType();//Type.GetType(typeStr); + if (type == typeof(int)) + { + var op = ParseValueTypeOperator(operatorStr); + if (op == ValueTypeConditionResolver.Operator.InRange || op == ValueTypeConditionResolver.Operator.OutOfRange) + { + var temp = valueStr.Split('-'); + if (temp.Length < 2) + throw new ArgumentException($"范围无效:{valueStr}。"); + int rangeStart = int.Parse(temp[0], CultureInfo.InvariantCulture); + int rangeEnd = int.Parse(temp[1], CultureInfo.InvariantCulture); + return new ValueTypeConditionResolver + { + Op = op, + RangeStart = rangeStart, + RangeEnd = rangeEnd, + ArithmeticExpression = GetArithmeticExpression(parts[0]), + }; + } + else + { + int value = int.Parse(valueStr, CultureInfo.InvariantCulture); + return new ValueTypeConditionResolver + { + Op = op, + Value = value, + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + + } + + } + else if (type == typeof(double)) + { + double value = double.Parse(valueStr, CultureInfo.InvariantCulture); + return new ValueTypeConditionResolver + { + Op = ParseValueTypeOperator(operatorStr), + Value = value, + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + } + else if (type == typeof(bool)) + { + bool value = bool.Parse(valueStr); + return new BoolConditionResolver + { + Op = ParseBoolOperator(operatorStr), + Value = value, + }; + } + else if (type == typeof(string)) + { + return new StringConditionResolver + { + Op = ParseStringOperator(operatorStr), + Value = valueStr + }; + } + + throw new NotSupportedException($"Type {type} is not supported."); + } + + + private static ValueTypeConditionResolver.Operator ParseValueTypeOperator(string operatorStr) where T : struct, IComparable + { + return operatorStr switch + { + ">" => ValueTypeConditionResolver.Operator.GreaterThan, + "<" => ValueTypeConditionResolver.Operator.LessThan, + "=" => ValueTypeConditionResolver.Operator.Equal, + "==" => ValueTypeConditionResolver.Operator.Equal, + ">=" => ValueTypeConditionResolver.Operator.GreaterThanOrEqual, + "≥" => ValueTypeConditionResolver.Operator.GreaterThanOrEqual, + "<=" => ValueTypeConditionResolver.Operator.LessThanOrEqual, + "≤" => ValueTypeConditionResolver.Operator.LessThanOrEqual, + "equals" => ValueTypeConditionResolver.Operator.Equal, + "in" => ValueTypeConditionResolver.Operator.InRange, + "!in" => ValueTypeConditionResolver.Operator.OutOfRange, + _ => throw new ArgumentException($"Invalid operator {operatorStr} for value type.") + }; + } + + private static BoolConditionResolver.Operator ParseBoolOperator(string operatorStr) + { + return operatorStr switch + { + "is" => BoolConditionResolver.Operator.Is, + "==" => BoolConditionResolver.Operator.Is, + "equals" => BoolConditionResolver.Operator.Is, + //"isFalse" => BoolConditionNode.Operator.IsFalse, + _ => throw new ArgumentException($"Invalid operator {operatorStr} for bool type.") + }; + } + + private static StringConditionResolver.Operator ParseStringOperator(string operatorStr) + { + return operatorStr switch + { + "c" => StringConditionResolver.Operator.Contains, + "nc" => StringConditionResolver.Operator.DoesNotContain, + "sw" => StringConditionResolver.Operator.StartsWith, + "ew" => StringConditionResolver.Operator.EndsWith, + + "contains" => StringConditionResolver.Operator.Contains, + "doesNotContain" => StringConditionResolver.Operator.DoesNotContain, + "equals" => StringConditionResolver.Operator.Equal, + "==" => StringConditionResolver.Operator.Equal, + "notEquals" => StringConditionResolver.Operator.NotEqual, + "!=" => StringConditionResolver.Operator.NotEqual, + "startsWith" => StringConditionResolver.Operator.StartsWith, + "endsWith" => StringConditionResolver.Operator.EndsWith, + _ => throw new ArgumentException($"Invalid operator {operatorStr} for string type.") + }; + } + } + +} diff --git a/Library/Utils/SerinExpression/SerinExpressionEvaluator.cs b/Library/Utils/SerinExpression/SerinExpressionEvaluator.cs new file mode 100644 index 0000000..99666ea --- /dev/null +++ b/Library/Utils/SerinExpression/SerinExpressionEvaluator.cs @@ -0,0 +1,216 @@ +using System.Data; + +namespace Serein.NodeFlow.Tool.SerinExpression +{ + public class SerinArithmeticExpressionEvaluator + { + private static readonly DataTable table = new DataTable(); + + public static double Evaluate(string expression, double inputValue) + { + // 替换占位符@为输入值 + expression = expression.Replace("@", inputValue.ToString()); + try + { + // 使用 DataTable.Compute 方法计算表达式 + var result = table.Compute(expression, string.Empty); + return Convert.ToDouble(result); + } + catch + { + throw new ArgumentException("Invalid arithmetic expression."); + } + } + } + + public class SerinExpressionEvaluator + { + /// + /// + /// + /// 表达式 + /// 操作对象 + /// 是否改变了对象(get语法) + /// + /// + /// + public static object Evaluate(string expression, object targetObJ, out bool isChange) + { + var parts = expression.Split([' '], 2); + if (parts.Length != 2) + { + throw new ArgumentException("Invalid expression format."); + } + + var operation = parts[0].ToLower(); + var operand = parts[1][0] == '.' ? parts[1][1..] : parts[1]; + + var result = operation switch + { + "@num" => ComputedNumber(targetObJ, operand), + "@call" => InvokeMethod(targetObJ, operand), + "@get" => GetMember(targetObJ, operand), + "@set" => SetMember(targetObJ, operand), + _ => throw new NotSupportedException($"Operation {operation} is not supported.") + }; + + isChange = operation switch + { + "@num" => true, + "@call" => true, + "@get" => true, + "@set" => false, + _ => throw new NotSupportedException($"Operation {operation} is not supported.") + }; + + return result; + } + + + private static readonly char[] separator = ['(', ')']; + private static readonly char[] separatorArray = [',']; + + private static object InvokeMethod(object target, string methodCall) + { + var methodParts = methodCall.Split(separator, StringSplitOptions.RemoveEmptyEntries); + if (methodParts.Length != 2) + { + throw new ArgumentException("Invalid method call format."); + } + + var methodName = methodParts[0]; + var parameterList = methodParts[1]; + var parameters = parameterList.Split(separatorArray, StringSplitOptions.RemoveEmptyEntries) + .Select(p => p.Trim()) + .ToArray(); + + var method = target.GetType().GetMethod(methodName); + if (method == null) + { + throw new ArgumentException($"Method {methodName} not found on target."); + } + + var parameterValues = method.GetParameters() + .Select((p, index) => Convert.ChangeType(parameters[index], p.ParameterType)) + .ToArray(); + + + return method.Invoke(target, parameterValues); + + } + + private static object GetMember(object target, string memberPath) + { + var members = memberPath.Split('.'); + foreach (var member in members) + { + + if (target == null) return null; + + + var property = target.GetType().GetProperty(member); + if (property != null) + { + + target = property.GetValue(target); + + } + else + { + var field = target.GetType().GetField(member); + if (field != null) + { + + target = field.GetValue(target); + + } + else + { + throw new ArgumentException($"Member {member} not found on target."); + } + } + } + + + return target; + + } + + private static object SetMember(object target, string assignment) + { + var parts = assignment.Split(new[] { '=' }, 2); + if (parts.Length != 2) + { + throw new ArgumentException("Invalid assignment format."); + } + + var memberPath = parts[0].Trim(); + var value = parts[1].Trim(); + + var members = memberPath.Split('.'); + for (int i = 0; i < members.Length - 1; i++) + { + var member = members[i]; + + var property = target.GetType().GetProperty(member); + + if (property != null) + { + + target = property.GetValue(target); + + } + else + { + var field = target.GetType().GetField(member); + if (field != null) + { + + target = field.GetValue(target); + + } + else + { + throw new ArgumentException($"Member {member} not found on target."); + } + } + } + + var lastMember = members.Last(); + + var lastProperty = target.GetType().GetProperty(lastMember); + + if (lastProperty != null) + { + var convertedValue = Convert.ChangeType(value, lastProperty.PropertyType); + lastProperty.SetValue(target, convertedValue); + } + else + { + var lastField = target.GetType().GetField(lastMember); + if (lastField != null) + { + var convertedValue = Convert.ChangeType(value, lastField.FieldType); + lastField.SetValue(target, convertedValue); + } + else + { + throw new ArgumentException($"Member {lastMember} not found on target."); + } + } + + return target; + } + + private static double ComputedNumber(object value, string expression) + { + double numericValue = Convert.ToDouble(value); + if (!string.IsNullOrEmpty(expression)) + { + numericValue = SerinArithmeticExpressionEvaluator.Evaluate(expression, numericValue); + } + + return numericValue; + } + } +} diff --git a/Library/Utils/SerinExpressionEvaluator.cs b/Library/Utils/SerinExpressionEvaluator.cs new file mode 100644 index 0000000..e1bc6e8 --- /dev/null +++ b/Library/Utils/SerinExpressionEvaluator.cs @@ -0,0 +1,9 @@ +using System; + +namespace Serein.Library.Utils +{ + //public abstract class SerinExpressionEvaluator + //{ + // public abstract string Evaluate(string expression ,object obj , out bool isChange); + //} +} \ No newline at end of file diff --git a/MyDll/SampleCondition.cs b/MyDll/SampleCondition.cs index 9dd30c8..c1487b8 100644 --- a/MyDll/SampleCondition.cs +++ b/MyDll/SampleCondition.cs @@ -136,7 +136,7 @@ namespace MyDll #region 触发器 - [NodeAction(NodeType.Flipflop, "等待信号触发")] + [NodeAction(NodeType.Flipflop, "等待信号触发",ReturnType = typeof(int))] public async Task WaitTask(SignalType triggerType = SignalType.光电1) { /*if (!Enum.TryParse(triggerValue, out SignalType triggerType) && Enum.IsDefined(typeof(SignalType), triggerType)) diff --git a/NodeFlow/Base/NodeModelBaseData.cs b/NodeFlow/Base/NodeModelBaseData.cs new file mode 100644 index 0000000..6b3aab1 --- /dev/null +++ b/NodeFlow/Base/NodeModelBaseData.cs @@ -0,0 +1,188 @@ +using Newtonsoft.Json; +using Serein.Library.Api; +using Serein.Library.Entity; +using Serein.Library.Enums; +using Serein.NodeFlow.Model; +using Serein.NodeFlow.Tool.SerinExpression; +using System.Xml.Linq; + +namespace Serein.NodeFlow.Base +{ + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + public abstract partial class NodeModelBase :IDynamicFlowNode + { + public NodeModelBase() + { + ConnectionType[] ct = [ConnectionType.IsSucceed, + ConnectionType.IsFail, + ConnectionType.IsError, + ConnectionType.Upstream]; + PreviousNodes = []; + SuccessorNodes = []; + foreach (ConnectionType ctType in ct) + { + PreviousNodes[ctType] = []; + SuccessorNodes[ctType] = []; + } + } + /// + /// 节点对应的控件类型 + /// + public NodeControlType ControlType { get; set; } + + /// + /// 方法描述,对应DLL的方法 + /// + public MethodDetails MethodDetails { get; set; } + + /// + /// 节点guid + /// + public string Guid { get; set; } + + /// + /// 显示名称 + /// + public string DisplayName { get; set; } + + /// + /// 是否为起点控件 + /// + public bool IsStart { get; set; } + + /// + /// 运行时的上一节点 + /// + public NodeModelBase? PreviousNode { get; set; } + + /// + /// 不同分支的父节点 + /// + public Dictionary> PreviousNodes { get; } + + /// + /// 不同分支的子节点 + /// + public Dictionary> SuccessorNodes { get; } + + /// + /// 当前执行状态(进入真分支还是假分支,异常分支在异常中确定) + /// + public FlowStateType FlowState { get; set; } = FlowStateType.None; + + /// + /// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值) + /// + public Exception RuningException { get; set; } = null; + + /// + /// 当前传递数据(执行了节点对应的方法,才会存在值) + /// + public object? FlowData { get; set; } = null; + + // public NodeModelBaseBuilder Build() => new NodeModelBaseBuilder(this); + } + + + + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + //public class NodeModelBaseBuilder + //{ + // public NodeModelBaseBuilder(NodeModelBase builder) + // { + // this.ControlType = builder.ControlType; + // this.MethodDetails = builder.MethodDetails; + // this.Guid = builder.Guid; + // this.DisplayName = builder.DisplayName; + // this.IsStart = builder.IsStart; + // this.PreviousNode = builder.PreviousNode; + // this.PreviousNodes = builder.PreviousNodes; + // this.SucceedBranch = builder.SucceedBranch; + // this.FailBranch = builder.FailBranch; + // this.ErrorBranch = builder.ErrorBranch; + // this.UpstreamBranch = builder.UpstreamBranch; + // this.FlowState = builder.FlowState; + // this.RuningException = builder.RuningException; + // this.FlowData = builder.FlowData; + // } + + + + // /// + // /// 节点对应的控件类型 + // /// + // public NodeControlType ControlType { get; } + + // /// + // /// 方法描述,对应DLL的方法 + // /// + // public MethodDetails MethodDetails { get; } + + // /// + // /// 节点guid + // /// + // public string Guid { get; } + + // /// + // /// 显示名称 + // /// + // public string DisplayName { get;} + + // /// + // /// 是否为起点控件 + // /// + // public bool IsStart { get; } + + // /// + // /// 运行时的上一节点 + // /// + // public NodeModelBase? PreviousNode { get; } + + // /// + // /// 上一节点集合 + // /// + // public List PreviousNodes { get; } = []; + + // /// + // /// 下一节点集合(真分支) + // /// + // public List SucceedBranch { get; } = []; + + // /// + // /// 下一节点集合(假分支) + // /// + // public List FailBranch { get; } = []; + + // /// + // /// 异常分支 + // /// + // public List ErrorBranch { get; } = []; + + // /// + // /// 上游分支 + // /// + // public List UpstreamBranch { get; } = []; + + // /// + // /// 当前执行状态(进入真分支还是假分支,异常分支在异常中确定) + // /// + // public FlowStateType FlowState { get; set; } = FlowStateType.None; + + // /// + // /// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值) + // /// + // public Exception RuningException { get; set; } = null; + + // /// + // /// 当前传递数据(执行了节点对应的方法,才会存在值) + // /// + // public object? FlowData { get; set; } = null; + //} + + +} + diff --git a/NodeFlow/Model/NodeBase.cs b/NodeFlow/Base/NodeModelBaseFunc.cs similarity index 79% rename from NodeFlow/Model/NodeBase.cs rename to NodeFlow/Base/NodeModelBaseFunc.cs index 499a0d0..6fe0e04 100644 --- a/NodeFlow/Model/NodeBase.cs +++ b/NodeFlow/Base/NodeModelBaseFunc.cs @@ -1,90 +1,48 @@ using Newtonsoft.Json; using Serein.Library.Api; +using Serein.Library.Entity; using Serein.Library.Enums; -using Serein.Library.Core.NodeFlow; -using Serein.NodeFlow.Tool; using Serein.NodeFlow.Tool.SerinExpression; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Base { - public enum ConnectionType - { - /// - /// 真分支 - /// - IsSucceed, - /// - /// 假分支 - /// - IsFail, - /// - /// 异常发生分支 - /// - IsError, - /// - /// 上游分支(执行当前节点前会执行一次上游分支) - /// - Upstream, - } - - /// /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 /// - public abstract class NodeBase : IDynamicFlowNode + public abstract partial class NodeModelBase : IDynamicFlowNode { + public abstract Parameterdata[] GetParameterdatas(); + public virtual NodeInfo ToInfo() + { + if (MethodDetails == null) return null; - public MethodDetails MethodDetails { get; set; } + var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 + var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 + var upstreamNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 上游分支 + var errorNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 异常分支 + // 生成参数列表 + Parameterdata[] parameterData = GetParameterdatas(); - public string Guid { get; set; } - - - public string DisplayName { get; set; } - - public bool IsStart { get; set; } - - public string DelegateName { get; set; } - - - /// - /// 运行时的上一节点 - /// - public NodeBase? PreviousNode { get; set; } - - /// - /// 上一节点集合 - /// - public List PreviousNodes { get; set; } = []; - /// - /// 下一节点集合(真分支) - /// - public List SucceedBranch { get; set; } = []; - /// - /// 下一节点集合(假分支) - /// - public List FailBranch { get; set; } = []; - /// - /// 异常分支 - /// - public List ErrorBranch { get; set; } = []; - /// - /// 上游分支 - /// - public List UpstreamBranch { get; set; } = []; - - /// - /// 当前状态(进入真分支还是假分支,异常分支在异常中确定) - /// - public FlowStateType FlowState { get; set; } = FlowStateType.Succeed; - public Exception Exception { get; set; } = null; - - /// - /// 当前传递数据 - /// - public object? FlowData { get; set; } = null; - + return new NodeInfo + { + Guid = Guid, + MethodName = MethodDetails?.MethodName, + Label = DisplayName ?? "", + Type = this.GetType().ToString(), + TrueNodes = trueNodes.ToArray(), + FalseNodes = falseNodes.ToArray(), + UpstreamNodes = upstreamNodes.ToArray(), + ParameterData = parameterData.ToArray(), + ErrorNodes = errorNodes.ToArray(), + }; + } /// /// 执行节点对应的方法 @@ -95,11 +53,7 @@ namespace Serein.NodeFlow.Model { MethodDetails md = MethodDetails; object? result = null; - if (!DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) - { - return result; - } - + var del = md.MethodDelegate; try { if (md.ExplicitDatas.Length == 0) @@ -131,7 +85,7 @@ namespace Serein.NodeFlow.Model catch (Exception ex) { FlowState = FlowStateType.Error; - Exception = ex; + RuningException = ex; } return result; @@ -142,29 +96,24 @@ namespace Serein.NodeFlow.Model /// /// /// 节点传回数据对象 - /// + /// public virtual async Task ExecuteAsync(IDynamicContext context) { MethodDetails md = MethodDetails; object? result = null; - if (!DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) - { - return result; - } - IFlipflopContext flipflopContext = null; try { // 调用委托并获取结果 if (md.ExplicitDatas.Length == 0) { - flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance); + flipflopContext = await ((Func>)md.MethodDelegate).Invoke(MethodDetails.ActingInstance); } else { object?[]? parameters = GetParameters(context, MethodDetails); - flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance, parameters); + flipflopContext = await ((Func>)md.MethodDelegate).Invoke(MethodDetails.ActingInstance, parameters); } if (flipflopContext != null) @@ -183,7 +132,7 @@ namespace Serein.NodeFlow.Model catch (Exception ex) { FlowState = FlowStateType.Error; - Exception = ex; + RuningException = ex; } return result; @@ -198,7 +147,7 @@ namespace Serein.NodeFlow.Model { var cts = context.SereinIoc.GetOrInstantiate(); - Stack stack = []; + Stack stack = []; stack.Push(this); while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环 @@ -213,7 +162,7 @@ namespace Serein.NodeFlow.Model } // 获取上游分支,首先执行一次 - var upstreamNodes = currentNode.UpstreamBranch; + var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream]; for (int i = upstreamNodes.Count - 1; i >= 0; i--) { upstreamNodes[i].PreviousNode = currentNode; @@ -231,13 +180,14 @@ namespace Serein.NodeFlow.Model currentNode.FlowData = currentNode.Execute(context); } - var nextNodes = currentNode.FlowState switch + ConnectionType connection = currentNode.FlowState switch { - FlowStateType.Succeed => currentNode.SucceedBranch, - FlowStateType.Fail => currentNode.FailBranch, - FlowStateType.Error => currentNode.ErrorBranch, + FlowStateType.Succeed => ConnectionType.IsSucceed, + FlowStateType.Fail => ConnectionType.IsFail, + FlowStateType.Error => ConnectionType.IsError, _ => throw new Exception("非预期的枚举值") }; + var nextNodes = currentNode.SuccessorNodes[connection]; // 将下一个节点集合中的所有节点逆序推入栈中 for (int i = nextNodes.Count - 1; i >= 0; i--) @@ -278,7 +228,7 @@ namespace Serein.NodeFlow.Model { parameters[i] = md; } - else if (type == typeof(NodeBase)) + else if (type == typeof(NodeModelBase)) { parameters[i] = this; } @@ -288,7 +238,7 @@ namespace Serein.NodeFlow.Model if (mdEd.DataValue[0] == '@') { var expResult = SerinExpressionEvaluator.Evaluate(mdEd.DataValue, PreviousNode?.FlowData, out bool isChange); - + if (mdEd.DataType.IsEnum) { @@ -348,11 +298,11 @@ namespace Serein.NodeFlow.Model } } - + } else if (f1 != null && f2 != null) { - if(f2.IsAssignableFrom(f1) || f2.FullName.Equals(f1.FullName)) + if (f2.IsAssignableFrom(f1) || f2.FullName.Equals(f1.FullName)) { parameters[i] = PreviousNode?.FlowData; @@ -534,37 +484,5 @@ namespace Serein.NodeFlow.Model - - } - - } - - -/* while (stack.Count > 0) // 循环中直到栈为空才会退出 - { - // 从栈中弹出一个节点作为当前节点进行处理 - var currentNode = stack.Pop(); - - if(currentNode is CompositeActionNode || currentNode is CompositeConditionNode) - { - currentNode.currentState = true; - } - else if (currentNode is CompositeConditionNode) - { - - } - currentNode.Execute(context); - // 根据当前节点的执行结果选择下一节点集合 - // 如果 currentState 为真,选择 TrueBranchNextNodes;否则选择 FalseBranchNextNodes - var nextNodes = currentNode.currentState ? currentNode.TrueBranchNextNodes - : currentNode.FalseBranchNextNodes; - - // 将下一个节点集合中的所有节点逆序推入栈中 - for (int i = nextNodes.Count - 1; i >= 0; i--) - { - stack.Push(nextNodes[i]); - } - - }*/ \ No newline at end of file diff --git a/NodeFlow/ConnectionType.cs b/NodeFlow/ConnectionType.cs new file mode 100644 index 0000000..1c8d050 --- /dev/null +++ b/NodeFlow/ConnectionType.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow +{ + public enum ConnectionType + { + /// + /// 真分支 + /// + IsSucceed, + /// + /// 假分支 + /// + IsFail, + /// + /// 异常发生分支 + /// + IsError, + /// + /// 上游分支(执行当前节点前会执行一次上游分支) + /// + Upstream, + } + + +} diff --git a/NodeFlow/DynamicNodeFrameworkType.cs b/NodeFlow/DynamicNodeFrameworkType.cs deleted file mode 100644 index 1d68608..0000000 --- a/NodeFlow/DynamicNodeFrameworkType.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Serein.NodeFlow -{ - //public enum DynamicNodeType - //{ - // /// - // /// 初始化 - // /// - // Init, - // /// - // /// 开始载入 - // /// - // Loading, - // /// - // /// 结束 - // /// - // Exit, - - // /// - // /// 触发器 - // /// - // Flipflop, - // /// - // /// 条件节点 - // /// - // Condition, - // /// - // /// 动作节点 - // /// - // Action, - //} - -} diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/FlowEnvironment.cs new file mode 100644 index 0000000..555ec60 --- /dev/null +++ b/NodeFlow/FlowEnvironment.cs @@ -0,0 +1,613 @@ +using Serein.Library.Api; +using Serein.Library.Attributes; +using Serein.Library.Entity; +using Serein.Library.Enums; +using Serein.Library.Utils; +using Serein.NodeFlow.Base; +using Serein.NodeFlow.Model; +using Serein.NodeFlow.Tool; +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Emit; +using System.Xml.Linq; + +namespace Serein.NodeFlow +{ + /* + + 脱离wpf平台独立运行。 + 加载文件。 + 创建节点对象,设置节点属性,确定连接关系,设置起点。 + + ↓抽象↓ + + wpf依赖于运行环境,而不是运行环境依赖于wpf。 + + 运行环境实现以下功能: + ①从项目文件加载数据,生成项目文件对象。 + ②运行项目,调试项目,中止项目,终止项目。 + ③自动包装数据类型,在上下文中传递数据。 + + */ + + + /// + /// 运行环境 + /// + + + /// + /// 运行环境 + /// + public class FlowEnvironment : IFlowEnvironment + { + /// + /// 加载Dll + /// + public event LoadDLLHandler OnDllLoad; + /// + /// 加载节点 + /// + public event LoadNodeHandler OnLoadNode; + /// + /// 连接节点 + /// + public event NodeConnectChangeHandler OnNodeConnectChange; + /// + /// 创建节点 + /// + public event NodeCreateHandler OnNodeCreate; + public event NodeRemoteHandler OnNodeRemote; + public event StartNodeChangeHandler OnStartNodeChange; + public event FlowRunCompleteHandler OnFlowRunComplete; + + private FlowStarter? nodeFlowStarter = null; + + /// + /// 节点的命名空间 + /// + public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; + + /// + /// 一种轻量的IOC容器 + /// + public SereinIoc SereinIoc { get; } = new SereinIoc(); + + /// + /// 存储加载的程序集路径 + /// + public List LoadedAssemblyPaths { get; } = []; + + /// + /// 存储加载的程序集 + /// + public List LoadedAssemblies { get; } = []; + + /// + /// 存储所有方法信息 + /// + public List MethodDetailss { get; } = []; + + + public Dictionary Nodes { get; } = []; + + public List Regions { get; } = []; + + /// + /// 存放触发器节点(运行时全部调用) + /// + public List FlipflopNodes { get; } = []; + + private NodeModelBase? _startNode = null; + + public NodeModelBase StartNode { + get + { + return _startNode; + } + set { + if(_startNode is null) + { + value.IsStart = true; + _startNode = value; + } + else + { + _startNode.IsStart = false; + value.IsStart = true; + _startNode = value; + } + } } + + public async Task StartAsync() + { + nodeFlowStarter = new FlowStarter(SereinIoc, MethodDetailss); + var nodes = Nodes.Values.ToList(); + var flipflopNodes = nodes.Where(it => it.MethodDetails?.MethodDynamicType == NodeType.Flipflop + && it.PreviousNodes.Count == 0 + && it.IsStart != true) + .Select(it => it as SingleFlipflopNode) + .ToList(); + await nodeFlowStarter.RunAsync(StartNode, this, flipflopNodes); + OnFlowRunComplete?.Invoke(new FlowEventArgs()); + } + public void Exit() + { + nodeFlowStarter?.Exit(); + nodeFlowStarter = null; + OnFlowRunComplete?.Invoke(new FlowEventArgs()); + } + + /// + /// 清除所有 + /// + public void ClearAll() + { + LoadedAssemblyPaths.Clear(); + LoadedAssemblies.Clear(); + MethodDetailss.Clear(); + + } + + + #region 数据交互 + + /// + /// 获取方法描述 + /// + public bool TryGetMethodDetails(string name, out MethodDetails? md) + { + md = MethodDetailss.FirstOrDefault(it => it.MethodName == name); + if(md == null) + { + return false; + } + return true; + } + + /// + /// 加载项目文件 + /// + /// + /// + public void LoadProject(SereinOutputFileData projectFile, string filePath) + { + // 加载项目配置文件 + var dllPaths = projectFile.Librarys.Select(it => it.Path).ToList(); + List methodDetailss = []; + + // 遍历依赖项中的特性注解,生成方法详情 + foreach (var dll in dllPaths) + { + var dllFilePath = System.IO.Path.GetFullPath(System.IO.Path.Combine(filePath, dll)); + (var assembly, var list) = LoadAssembly(dllFilePath); + if (assembly is not null && list.Count > 0) + { + methodDetailss.AddRange(methodDetailss); // 暂存方法描述 + OnDllLoad?.Invoke(new LoadDLLEventArgs(assembly, methodDetailss)); // 通知UI创建dll面板显示 + } + } + // 方法加载完成,缓存到运行环境中。 + MethodDetailss.AddRange(methodDetailss); + methodDetailss.Clear(); + + + // 加载节点 + foreach (var nodeInfo in projectFile.Nodes) + { + if (TryGetMethodDetails(nodeInfo.MethodName, out MethodDetails? methodDetails)) + { + OnLoadNode?.Invoke(new LoadNodeEventArgs(nodeInfo, methodDetails)); + } + } + + // 确定节点之间的连接关系 + foreach (var nodeInfo in projectFile.Nodes) + { + if (!Nodes.TryGetValue(nodeInfo.Guid, out NodeModelBase fromNode)) + { + // 不存在对应的起始节点 + continue; + } + + + List<(ConnectionType, string[])> nodeGuids = [(ConnectionType.IsSucceed,nodeInfo.TrueNodes), + (ConnectionType.IsFail, nodeInfo.FalseNodes), + (ConnectionType.IsError, nodeInfo.ErrorNodes), + (ConnectionType.Upstream, nodeInfo.UpstreamNodes)]; + + List<(ConnectionType, NodeModelBase[])> nodes = nodeGuids.Where(info => info.Item2.Length > 0) + .Select(info => (info.Item1, + info.Item2.Select(guid => Nodes[guid]) + .ToArray())) + .ToList(); + // 遍历每种类型的节点分支(四种) + foreach ((ConnectionType connectionType, NodeModelBase[] nodeBases) item in nodes) + { + // 遍历当前类型分支的节点(确认连接关系) + foreach (var node in item.nodeBases) + { + ConnectNode(fromNode, node, item.connectionType); // 加载时确定节点间的连接关系 + } + } + + + } + + } + + /// + /// 连接节点 + /// + /// 起始节点 + /// 目标节点 + /// 连接关系 + public void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + { + // 获取起始节点与目标节点 + if (!Nodes.TryGetValue(fromNodeGuid, out NodeModelBase? fromNode) || !Nodes.TryGetValue(toNodeGuid, out NodeModelBase? toNode)) + { + return; + } + if(fromNode is null || toNode is null) + { + return ; + } + // 开始连接 + ConnectNode(fromNode, toNode, connectionType); // 外部调用连接方法 + + } + + /// + /// 创建节点 + /// + /// + public void CreateNode(NodeControlType nodeControlType, MethodDetails? methodDetails = null) + { + // 确定创建的节点类型 + Type? nodeType = nodeControlType switch + { + NodeControlType.Action => typeof(SingleActionNode), + NodeControlType.Flipflop => typeof(SingleFlipflopNode), + + NodeControlType.ExpOp => typeof(SingleExpOpNode), + NodeControlType.ExpCondition => typeof(SingleConditionNode), + NodeControlType.ConditionRegion => typeof(CompositeConditionNode), + _ => null + }; + if(nodeType == null) + { + return; + } + // 生成实例 + var nodeObj = Activator.CreateInstance(nodeType); + if (nodeObj is not NodeModelBase nodeBase) + { + return; + } + + // 配置基础的属性 + nodeBase.ControlType = nodeControlType; + nodeBase.Guid = Guid.NewGuid().ToString(); + if (methodDetails != null) + { + var md = methodDetails.Clone(); + nodeBase.DisplayName = md.MethodTips; + nodeBase.MethodDetails = md; + } + Nodes[nodeBase.Guid] = nodeBase; + + // 如果是触发器,则需要添加到专属集合中 + if (nodeControlType == NodeControlType.Flipflop && nodeBase is SingleFlipflopNode flipflopNode ) + { + var guid = flipflopNode.Guid; + if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid))) + { + FlipflopNodes.Add(flipflopNode); + } + } + + // 通知UI更改 + OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeBase)); + // 因为需要UI先布置了元素,才能通知UI变更特效 + // 如果不存在流程起始控件,默认设置为流程起始控件 + if (StartNode is null) + { + SetStartNode(nodeBase); + } + } + + + /// + /// 从文件路径中加载DLL + /// + /// + /// + public void LoadDll(string dllPath) + { + (var assembly, var list) = LoadAssembly(dllPath); + if (assembly is not null && list.Count > 0) + { + OnDllLoad?.Invoke(new LoadDLLEventArgs(assembly, list)); + } + } + + /// + /// 保存项目为项目文件 + /// + /// + public SereinOutputFileData SaveProject() + { + var projectData = new SereinOutputFileData() + { + Librarys = LoadedAssemblies.Select(assemblies => assemblies.ToLibrary()).ToArray(), + Nodes = Nodes.Values.Select(node => node.ToInfo()).Where(info => info is not null).ToArray(), + StartNode = Nodes.Values.FirstOrDefault(it => it.IsStart)?.Guid, + }; + return projectData; + } + + /// + /// 移除连接关系 + /// + /// + /// + /// + /// + public void RemoteConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + { + // 获取起始节点与目标节点 + if (!Nodes.TryGetValue(fromNodeGuid, out NodeModelBase? fromNode) || !Nodes.TryGetValue(toNodeGuid, out NodeModelBase? toNode)) + { + return; + } + if (fromNode is null || toNode is null) + { + return; + } + + fromNode.SuccessorNodes[connectionType].Remove(toNode); + toNode.PreviousNodes[connectionType].Remove(fromNode); + OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNodeGuid, + toNodeGuid, + connectionType, + NodeConnectChangeEventArgs.ChangeTypeEnum.Remote)); + } + + /// + /// 移除节点 + /// + /// + /// + public void RemoteNode(string nodeGuid) + { + if (!Nodes.TryGetValue(nodeGuid, out NodeModelBase? remoteNode)) + { + return; + } + if (remoteNode is null) + { + return; + } + if (remoteNode.IsStart) + { + return; + } + + + // 遍历所有父节点,从那些父节点中的子节点集合移除该节点 + foreach(var pnc in remoteNode.PreviousNodes) + { + var pCType = pnc.Key; // 连接类型 + for (int i = 0; i < pnc.Value.Count; i++) + { + NodeModelBase? pNode = pnc.Value[i]; + pNode.SuccessorNodes[pCType].RemoveAt(i); + OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(pNode.Guid, + remoteNode.Guid, + pCType, + NodeConnectChangeEventArgs.ChangeTypeEnum.Remote)); // 通知UI + } + } + + // 遍历所有子节点,从那些子节点中的父节点集合移除该节点 + foreach (var snc in remoteNode.SuccessorNodes) + { + var sCType = snc.Key; // 连接类型 + for (int i = 0; i < snc.Value.Count; i++) + { + NodeModelBase? sNode = snc.Value[i]; + remoteNode.SuccessorNodes[sCType].RemoveAt(i); + OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(remoteNode.Guid, + sNode.Guid, + sCType, + NodeConnectChangeEventArgs.ChangeTypeEnum.Remote)); // 通知UI + + } + } + + // 从集合中移除节点 + Nodes.Remove(nodeGuid); + OnNodeRemote?.Invoke(new NodeRemoteEventArgs(nodeGuid)); + } + + /// + /// 设置起点控件 + /// + /// + public void SetStartNode(string newNodeGuid) + { + if(Nodes.TryGetValue(newNodeGuid, out NodeModelBase? newStartNodeModel)) + { + if(newStartNodeModel != null) + { + SetStartNode(newStartNodeModel); + //var oldNodeGuid = ""; + //if(StartNode != null) + //{ + // oldNodeGuid = StartNode.Guid; + // StartNode.IsStart = false; + //} + //newStartNodeModel.IsStart = true; + //StartNode = newStartNodeModel; + //OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(oldNodeGuid, newNodeGuid)); + } + } + } + + #endregion + + #region 私有方法 + + /// + /// 加载指定路径的DLL文件 + /// + /// + private (Assembly?, List) LoadAssembly(string dllPath) + { + try + { + Assembly assembly = Assembly.LoadFrom(dllPath); // 加载DLL文件 + Type[] types = assembly.GetTypes(); // 获取程序集中的所有类型 + + List scanTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute()?.Scan == true).ToList(); + if (scanTypes.Count == 0) + { + return (null, []); + } + + List methodDetails = new List(); + // 遍历扫描的类型 + foreach (var item in scanTypes) + { + //加载DLL,创建 MethodDetails、实例作用对象、委托方法 + var itemMethodDetails = MethodDetailsHelperTmp.GetList(item, false); + methodDetails.AddRange(itemMethodDetails); + } + + LoadedAssemblies.Add(assembly); // 将加载的程序集添加到列表中 + LoadedAssemblyPaths.Add(dllPath); // 记录加载的DLL路径 + return (assembly, methodDetails); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + return (null, []); + } + } + + + /// + /// 连接节点 + /// + /// 起始节点 + /// 目标节点 + /// 连接关系 + private void ConnectNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType) + { + if (fromNode == null || toNode == null || fromNode == toNode) + { + return; + } + + var ToExistOnFrom = true; + var FromExistInTo = true; + ConnectionType[] ct = [ConnectionType.IsSucceed, + ConnectionType.IsFail, + ConnectionType.IsError, + ConnectionType.Upstream]; + foreach (ConnectionType ctType in ct) + { + var FToTo = fromNode.SuccessorNodes[ctType].Where(it => it.Guid.Equals(toNode.Guid)).ToArray(); + var ToOnF = toNode.PreviousNodes[ctType].Where(it => it.Guid.Equals(fromNode.Guid)).ToArray(); + ToExistOnFrom = FToTo.Length > 0; + FromExistInTo = ToOnF.Length > 0; + if (ToExistOnFrom && FromExistInTo) + { + Console.WriteLine("起始节点已与目标节点存在连接"); + return; + } + else + { + // 检查是否可能存在异常 + if (!ToExistOnFrom && FromExistInTo) + { + Console.WriteLine("目标节点不是起始节点的子节点,起始节点却是目标节点的父节点"); + return; + } + else if (ToExistOnFrom && !FromExistInTo) + { + // + Console.WriteLine(" 起始节点不是目标节点的父节点,目标节点却是起始节点的子节点"); + return; + } + else // if (!ToExistOnFrom && !FromExistInTo) + { + // 可以正常连接 + } + } + } + + + + fromNode.SuccessorNodes[connectionType].Add(toNode); // 添加到起始节点的子分支 + toNode.PreviousNodes[connectionType].Add(fromNode); // 添加到目标节点的父分支 + OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, + toNode.Guid, + connectionType, + NodeConnectChangeEventArgs.ChangeTypeEnum.Create)); // 通知UI + } + + /// + /// 更改起点节点 + /// + /// + /// + private void SetStartNode(NodeModelBase newStartNode) + { + var oldNodeGuid = StartNode?.Guid; + StartNode = newStartNode; + OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(oldNodeGuid, StartNode.Guid)); + } + #endregion + + + + #region 视觉元素交互 + + #region 本地交互(WPF) + + #endregion + + #region 网络交互(通过Socket的方式进行操作) + + #endregion + + + #endregion + + + + + + + + + + } + + public static class FlowFunc + { + public static Library.Entity.Library ToLibrary(this Assembly assembly) + { + return new Library.Entity.Library + { + Name = assembly.GetName().Name, + Path = assembly.Location, + }; + } + } + + + +} diff --git a/NodeFlow/NodeFlowStarter.cs b/NodeFlow/FlowStarter.cs similarity index 70% rename from NodeFlow/NodeFlowStarter.cs rename to NodeFlow/FlowStarter.cs index 3be8f4c..5a62511 100644 --- a/NodeFlow/NodeFlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -1,56 +1,51 @@ using Serein.Library.Api; -using Serein.Library.Enums; using Serein.Library.Core.NodeFlow; +using Serein.Library.Entity; +using Serein.Library.Enums; +using Serein.Library.Utils; +using Serein.NodeFlow.Base; using Serein.NodeFlow.Model; -using Serein.NodeFlow.Tool; namespace Serein.NodeFlow { - public class NodeRunTcs : CancellationTokenSource - { - - } - - - public class NodeFlowStarter(ISereinIoc serviceContainer, List methodDetails) + /// + /// 流程启动器 + /// + /// + /// + public class FlowStarter(ISereinIoc serviceContainer, List methodDetails) { private readonly ISereinIoc ServiceContainer = serviceContainer; private readonly List methodDetails = methodDetails; - - private Action ExitAction = null; - - - private IDynamicContext context = null; - - - public NodeRunTcs MainCts; + private Action ExitAction = null; //退出方法 + private IDynamicContext context = null; //上下文 + public NodeRunCts MainCts; /// - /// 运行测试 + /// 开始运行 /// - /// - /// - //public async Task RunAsync1(List nodes) - //{ - // await Task.Run(async ()=> await StartRunAsync(nodes)); - //} - - public async Task RunAsync(List nodes) + /// + /// + // public async Task RunAsync(List nodes, IFlowEnvironment flowEnvironment) + public async Task RunAsync(NodeModelBase startNode, IFlowEnvironment flowEnvironment, List flipflopNodes) { - var startNode = nodes.FirstOrDefault(p => p.IsStart); + // var startNode = nodes.FirstOrDefault(p => p.IsStart); if (startNode == null) { return; } - if (false) + + var isNetFramework = true; + + if (isNetFramework) { - context = new Serein.Library.Core.NodeFlow.DynamicContext(ServiceContainer); + context = new Serein.Library.Framework.NodeFlow.DynamicContext(ServiceContainer, flowEnvironment); } else { - context = new Serein.Library.Framework.NodeFlow.DynamicContext(ServiceContainer); + context = new Serein.Library.Core.NodeFlow.DynamicContext(ServiceContainer, flowEnvironment); } - MainCts = ServiceContainer.CreateServiceInstance(); + MainCts = ServiceContainer.CreateServiceInstance(); var initMethods = methodDetails.Where(it => it.MethodDynamicType == NodeType.Init).ToList(); var loadingMethods = methodDetails.Where(it => it.MethodDynamicType == NodeType.Loading).ToList(); @@ -75,10 +70,8 @@ namespace Serein.NodeFlow ServiceContainer.Reset(); }; - foreach (var md in initMethods) // 初始化 - 调用方法 { - //md.ActingInstance = context.ServiceContainer.Get(md.ActingInstanceType); object?[]? args = [context]; object?[]? data = [md.ActingInstance, args]; md.MethodDelegate.DynamicInvoke(data); @@ -87,25 +80,20 @@ namespace Serein.NodeFlow foreach (var md in loadingMethods) // 加载 { - //md.ActingInstance = context.ServiceContainer.Get(md.ActingInstanceType); object?[]? args = [context]; object?[]? data = [md.ActingInstance, args]; md.MethodDelegate.DynamicInvoke(data); } - var flipflopNodes = nodes.Where(it => it.MethodDetails?.MethodDynamicType == NodeType.Flipflop - && it.PreviousNodes.Count == 0 - && it.IsStart != true).ToArray(); - + // 运行触发器节点 var singleFlipflopNodes = flipflopNodes.Select(it => (SingleFlipflopNode)it).ToArray(); // 使用 TaskCompletionSource 创建未启动的任务 var tasks = singleFlipflopNodes.Select(async node => { - await FlipflopExecute(node); + await FlipflopExecute(node, flowEnvironment); }).ToArray(); - try { await Task.Run(async () => @@ -120,19 +108,17 @@ namespace Serein.NodeFlow } - private async Task FlipflopExecute(SingleFlipflopNode singleFlipFlopNode) + /// + /// 启动触发器 + /// + private async Task FlipflopExecute(SingleFlipflopNode singleFlipFlopNode, IFlowEnvironment flowEnvironment) { - DynamicContext context = new DynamicContext(ServiceContainer); + DynamicContext context = new DynamicContext(ServiceContainer, flowEnvironment); MethodDetails md = singleFlipFlopNode.MethodDetails; - + var del = md.MethodDelegate; try { - if (!DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) - { - return; - } - //var func = md.ExplicitDatas.Length == 0 ? (Func>>)del : (Func>>)del; var func = md.ExplicitDatas.Length == 0 ? (Func>)del : (Func>)del; @@ -144,30 +130,22 @@ namespace Serein.NodeFlow IFlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters); - if (flipflopContext == null) - { - break; - } - else if (flipflopContext.State == FlowStateType.Error) - { - break; - } - else if (flipflopContext.State == FlowStateType.Fail) - { - break; - } - else if (flipflopContext.State == FlowStateType.Succeed) + if (flipflopContext.State == FlowStateType.Succeed) { singleFlipFlopNode.FlowState = FlowStateType.Succeed; singleFlipFlopNode.FlowData = flipflopContext.Data; - var tasks = singleFlipFlopNode.SucceedBranch.Select(nextNode => + var tasks = singleFlipFlopNode.PreviousNodes[ConnectionType.IsSucceed].Select(nextNode => { - var context = new DynamicContext(ServiceContainer); + var context = new DynamicContext(ServiceContainer,flowEnvironment); nextNode.PreviousNode = singleFlipFlopNode; return nextNode.StartExecution(context); }).ToArray(); Task.WaitAll(tasks); } + else + { + break; + } } } catch (Exception ex) diff --git a/NodeFlow/FlowStateType.cs b/NodeFlow/FlowStateType.cs deleted file mode 100644 index 33debd0..0000000 --- a/NodeFlow/FlowStateType.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Serein.NodeFlow -{ - //public enum FlowStateType - //{ - // /// - // /// 成功(方法成功执行) - // /// - // Succeed, - // /// - // /// 失败(方法没有成功执行,不过执行时没有发生非预期的错误) - // /// - // Fail, - // /// - // /// 异常(节点没有成功执行,执行时发生非预期的错误) - // /// - // Error, - //} -} diff --git a/NodeFlow/MethodDetails.cs b/NodeFlow/MethodDetails.cs index 4c6f0c4..1da2138 100644 --- a/NodeFlow/MethodDetails.cs +++ b/NodeFlow/MethodDetails.cs @@ -1,4 +1,5 @@ -using Serein.Library.Enums; +using Serein.Library.Api; +using Serein.Library.Enums; namespace Serein.NodeFlow { @@ -65,7 +66,7 @@ namespace Serein.NodeFlow - public class MethodDetails + public class MethodDetails : IMethodDetails { /// /// 拷贝 diff --git a/NodeFlow/Model/CompositeActionNode.cs b/NodeFlow/Model/CompositeActionNode.cs index cb06f4c..853d3a9 100644 --- a/NodeFlow/Model/CompositeActionNode.cs +++ b/NodeFlow/Model/CompositeActionNode.cs @@ -1,10 +1,14 @@ -namespace Serein.NodeFlow.Model +using Serein.Library.Entity; +using Serein.Library.Enums; +using Serein.NodeFlow.Base; + +namespace Serein.NodeFlow.Model { /// /// 组合动作节点(用于动作区域) /// - public class CompositeActionNode : NodeBase + public class CompositeActionNode : NodeModelBase { public List ActionNodes; /// @@ -14,12 +18,42 @@ { ActionNodes = actionNodes; } - public void AddNode(SingleActionNode node) - { - ActionNodes.Add(node); - MethodDetails ??= node.MethodDetails; - } + + + public override Parameterdata[] GetParameterdatas() + { + return []; + } + public override NodeInfo ToInfo() + { + if (MethodDetails == null) return null; + + //var trueNodes = SucceedBranch.Select(item => item.Guid); // 真分支 + //var falseNodes = FailBranch.Select(item => item.Guid);// 假分支 + //var upstreamNodes = UpstreamBranch.Select(item => item.Guid);// 上游分支 + //var errorNodes = ErrorBranch.Select(item => item.Guid);// 异常分支 + var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 + var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 + var upstreamNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 上游分支 + var errorNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 异常分支 + // 生成参数列表 + Parameterdata[] parameterData = GetParameterdatas(); + + return new NodeInfo + { + Guid = Guid, + MethodName = MethodDetails?.MethodName, + Label = DisplayName ?? "", + Type = this.GetType().ToString(), + TrueNodes = trueNodes.ToArray(), + FalseNodes = falseNodes.ToArray(), + UpstreamNodes = upstreamNodes.ToArray(), + ParameterData = parameterData.ToArray(), + ErrorNodes = errorNodes.ToArray(), + ChildNodes = ActionNodes.Select(node => node.ToInfo()).ToArray(), + }; + } } } diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index 8ad69c6..ff937ae 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -1,13 +1,14 @@ using Serein.Library.Api; +using Serein.Library.Entity; using Serein.Library.Enums; -using Serein.Library.Core.NodeFlow; +using Serein.NodeFlow.Base; namespace Serein.NodeFlow.Model { /// /// 组合条件节点(用于条件区域) /// - public class CompositeConditionNode : NodeBase + public class CompositeConditionNode : NodeModelBase { public List ConditionNodes { get; } = []; @@ -54,6 +55,10 @@ namespace Serein.NodeFlow.Model // } //} } + + + + private FlowStateType Judge(IDynamicContext context, SingleConditionNode node) { try @@ -68,7 +73,41 @@ namespace Serein.NodeFlow.Model } } + public override Parameterdata[] GetParameterdatas() + { + return []; + } + public override NodeInfo ToInfo() + { + if (MethodDetails == null) return null; + + //var trueNodes = SucceedBranch.Select(item => item.Guid); // 真分支 + //var falseNodes = FailBranch.Select(item => item.Guid);// 假分支 + //var upstreamNodes = UpstreamBranch.Select(item => item.Guid);// 上游分支 + //var errorNodes = ErrorBranch.Select(item => item.Guid);// 异常分支 + var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 + var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 + var upstreamNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 上游分支 + var errorNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 异常分支 + + // 生成参数列表 + Parameterdata[] parameterData = GetParameterdatas(); + + return new NodeInfo + { + Guid = Guid, + MethodName = MethodDetails?.MethodName, + Label = DisplayName ?? "", + Type = this.GetType().ToString(), + TrueNodes = trueNodes.ToArray(), + FalseNodes = falseNodes.ToArray(), + UpstreamNodes = upstreamNodes.ToArray(), + ParameterData = parameterData.ToArray(), + ErrorNodes = errorNodes.ToArray(), + ChildNodes = ConditionNodes.Select(node => node.ToInfo()).ToArray(), + }; + } } diff --git a/NodeFlow/Model/CompositeLoopNode.cs b/NodeFlow/Model/CompositeLoopNode.cs index faa9bfe..acf3822 100644 --- a/NodeFlow/Model/CompositeLoopNode.cs +++ b/NodeFlow/Model/CompositeLoopNode.cs @@ -1,6 +1,6 @@ namespace Serein.NodeFlow.Model { - public class CompositeLoopNode : NodeBase - { - } + //public class CompositeLoopNode : NodeBase + //{ + //} } diff --git a/NodeFlow/Model/SingleActionNode.cs b/NodeFlow/Model/SingleActionNode.cs index daa8af4..22219c2 100644 --- a/NodeFlow/Model/SingleActionNode.cs +++ b/NodeFlow/Model/SingleActionNode.cs @@ -1,9 +1,12 @@ -namespace Serein.NodeFlow.Model +using Serein.Library.Entity; +using Serein.NodeFlow.Base; + +namespace Serein.NodeFlow.Model { /// /// 单动作节点(用于动作控件) /// - public class SingleActionNode : NodeBase + public class SingleActionNode : NodeModelBase { //public override void Execute(DynamicContext context) //{ @@ -61,7 +64,23 @@ // context.SetFlowData(result); // } //} - + public override Parameterdata[] GetParameterdatas() + { + if (base.MethodDetails.ExplicitDatas.Length > 0) + { + return MethodDetails.ExplicitDatas + .Select(it => new Parameterdata + { + state = it.IsExplicitData, + value = it.DataValue, + }) + .ToArray(); + } + else + { + return []; + } + } } diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index 9e86fe1..04c5d11 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -1,7 +1,8 @@  using Serein.Library.Api; +using Serein.Library.Entity; using Serein.Library.Enums; -using Serein.Library.Core.NodeFlow; +using Serein.NodeFlow.Base; using Serein.NodeFlow.Tool.SerinExpression; namespace Serein.NodeFlow.Model @@ -9,7 +10,7 @@ namespace Serein.NodeFlow.Model /// /// 条件节点(用于条件控件) /// - public class SingleConditionNode : NodeBase + public class SingleConditionNode : NodeModelBase { /// @@ -47,13 +48,40 @@ namespace Serein.NodeFlow.Model catch (Exception ex) { FlowState = FlowStateType.Error; - Exception = ex; + RuningException = ex; } Console.WriteLine($"{result} {Expression} -> " + FlowState); return result; } + public override Parameterdata[] GetParameterdatas() + { + if (base.MethodDetails.ExplicitDatas.Length > 0) + { + return MethodDetails.ExplicitDatas + .Select(it => new Parameterdata + { + state = IsCustomData, + expression = Expression, + value = CustomData switch + { + Type when CustomData.GetType() == typeof(int) + && CustomData.GetType() == typeof(double) + && CustomData.GetType() == typeof(float) + => ((double)CustomData).ToString(), + Type when CustomData.GetType() == typeof(bool) => ((bool)CustomData).ToString(), + _ => CustomData?.ToString()!, + } + }) + .ToArray(); + } + else + { + return []; + } + } + //public override void Execute(DynamicContext context) //{ // CurrentState = Judge(context, base.MethodDetails); diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index 38f13ad..72193cd 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -1,6 +1,7 @@ using Serein.Library.Api; +using Serein.Library.Entity; using Serein.Library.Enums; -using Serein.Library.Core.NodeFlow; +using Serein.NodeFlow.Base; using Serein.NodeFlow.Tool.SerinExpression; namespace Serein.NodeFlow.Model @@ -8,7 +9,7 @@ namespace Serein.NodeFlow.Model /// /// Expression Operation - 表达式操作 /// - public class SingleExpOpNode : NodeBase + public class SingleExpOpNode : NodeModelBase { /// /// 表达式 @@ -34,5 +35,24 @@ namespace Serein.NodeFlow.Model } } + + public override Parameterdata[] GetParameterdatas() + { + if (base.MethodDetails.ExplicitDatas.Length > 0) + { + return MethodDetails.ExplicitDatas + .Select(it => new Parameterdata + { + state = it.IsExplicitData, + // value = it.DataValue, + expression = Expression, + }) + .ToArray(); + } + else + { + return []; + } + } } } diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index ae026e7..4b58ca5 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -1,15 +1,33 @@ using Serein.Library.Api; -using Serein.Library.Core.NodeFlow; +using Serein.Library.Entity; +using Serein.NodeFlow.Base; namespace Serein.NodeFlow.Model { - public class SingleFlipflopNode : NodeBase + public class SingleFlipflopNode : NodeModelBase { public override object Execute(IDynamicContext context) { throw new NotImplementedException("无法以非await/async的形式调用触发器"); } + public override Parameterdata[] GetParameterdatas() + { + if (base.MethodDetails.ExplicitDatas.Length > 0) + { + return MethodDetails.ExplicitDatas + .Select(it => new Parameterdata + { + state = it.IsExplicitData, + value = it.DataValue + }) + .ToArray(); + } + else + { + return []; + } + } } } diff --git a/NodeFlow/MoveNodeData.cs b/NodeFlow/MoveNodeData.cs new file mode 100644 index 0000000..785bc2b --- /dev/null +++ b/NodeFlow/MoveNodeData.cs @@ -0,0 +1,16 @@ +using Serein.Library.Entity; +using Serein.Library.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow +{ + public class MoveNodeData + { + public NodeControlType NodeControlType { get; set; } + public MethodDetails MethodDetails { get; set; } + } +} diff --git a/NodeFlow/Serein.NodeFlow.csproj b/NodeFlow/Serein.NodeFlow.csproj index ff1efaa..452a921 100644 --- a/NodeFlow/Serein.NodeFlow.csproj +++ b/NodeFlow/Serein.NodeFlow.csproj @@ -17,9 +17,12 @@ + + + diff --git a/WorkBench/SereinOutputFileData.cs b/NodeFlow/SereinOutputFileData.cs similarity index 93% rename from WorkBench/SereinOutputFileData.cs rename to NodeFlow/SereinOutputFileData.cs index d44f348..e714650 100644 --- a/WorkBench/SereinOutputFileData.cs +++ b/NodeFlow/SereinOutputFileData.cs @@ -1,13 +1,16 @@ -using System; +using Serein.Library.Api; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.WorkBench +namespace Serein.NodeFlow { - - public class SereinOutputFileData + /* /// + /// 输出文件 + /// + public class SereinOutputFileData { /// /// 基础 @@ -58,6 +61,9 @@ namespace Serein.WorkBench public string versions { get; set; } + // 预览位置 + + // 缩放比例 } /// /// 画布 @@ -179,5 +185,5 @@ namespace Serein.WorkBench public string guid { get; set; } public NodeInfo[] childNodes { get; set; } - } + }*/ } diff --git a/NodeFlow/Tool/DelegateGenerator.cs b/NodeFlow/Tool/MethodDetailsHelper.cs similarity index 50% rename from NodeFlow/Tool/DelegateGenerator.cs rename to NodeFlow/Tool/MethodDetailsHelper.cs index e4e9d3c..d22e8b7 100644 --- a/NodeFlow/Tool/DelegateGenerator.cs +++ b/NodeFlow/Tool/MethodDetailsHelper.cs @@ -1,34 +1,218 @@ using Serein.Library.Api; using Serein.Library.Attributes; using Serein.Library.Core.NodeFlow; +using Serein.Library.Entity; using System.Collections.Concurrent; using System.Reflection; namespace Serein.NodeFlow.Tool; -public static class DelegateCache +//public static class DelegateCache +//{ +// /// +// /// 委托缓存全局字典 +// /// +// //public static ConcurrentDictionary GlobalDicDelegates { get; } = new ConcurrentDictionary(); +//} + + +public static class MethodDetailsHelperTmp { /// - /// 委托缓存全局字典 + /// 生成方法信息 /// - public static ConcurrentDictionary GlobalDicDelegates { get; } = new ConcurrentDictionary(); + /// + /// + /// + public static List GetList(Type type, bool isNetFramework) + { + var methodDetailsDictionary = new List(); + var assemblyName = type.Assembly.GetName().Name; + var methods = GetMethodsToProcess(type, isNetFramework); + + foreach (var method in methods) + { + + var methodDetails = CreateMethodDetails(type, method, assemblyName, isNetFramework); + methodDetailsDictionary.Add(methodDetails); + } + + return methodDetailsDictionary.OrderBy(it => it.MethodName).ToList(); + } + /// + /// 获取处理方法 + /// + private static IEnumerable GetMethodsToProcess(Type type, bool isNetFramework) + { + if (isNetFramework) + { + + return type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(m => m.GetCustomAttribute()?.Scan == true); + } + else + { + + return type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(m => m.GetCustomAttribute()?.Scan == true); + } + } + /// + /// 创建方法信息 + /// + /// + private static MethodDetails CreateMethodDetails(Type type, MethodInfo method, string assemblyName, bool isNetFramework) + { + + var methodName = method.Name; + var attribute = method.GetCustomAttribute(); + var explicitDataOfParameters = GetExplicitDataOfParameters(method.GetParameters()); + // 生成委托 + var methodDelegate = GenerateMethodDelegate(type, // 方法所在的对象类型 + method, // 方法信息 + method.GetParameters(),// 方法参数 + method.ReturnType);// 返回值 + + + var dllTypeName = $"{assemblyName}.{type.Name}"; + object instance = Activator.CreateInstance(type); + var dllTypeMethodName = $"{assemblyName}.{type.Name}.{method.Name}"; + + return new MethodDetails + { + ActingInstanceType = type, + ActingInstance = instance, + MethodName = dllTypeMethodName, + MethodDelegate = methodDelegate, + MethodDynamicType = attribute.MethodDynamicType, + MethodLockName = attribute.LockName, + MethodTips = attribute.MethodTips, + ExplicitDatas = explicitDataOfParameters, + ReturnType = method.ReturnType, + }; + + } + + private static ExplicitData[] GetExplicitDataOfParameters(ParameterInfo[] parameters) + { + + return parameters.Select((it, index) => + { + //Console.WriteLine($"{it.Name}-{it.HasDefaultValue}-{it.DefaultValue}"); + string explicitTypeName = GetExplicitTypeName(it.ParameterType); + var items = GetExplicitItems(it.ParameterType, explicitTypeName); + if ("Bool".Equals(explicitTypeName)) explicitTypeName = "Select"; // 布尔值 转为 可选类型 + + + + return new ExplicitData + { + IsExplicitData = it.HasDefaultValue, + Index = index, + ExplicitType = it.ParameterType, + ExplicitTypeName = explicitTypeName, + DataType = it.ParameterType, + ParameterName = it.Name, + DataValue = it.HasDefaultValue ? it.DefaultValue.ToString() : "", + Items = items.ToArray(), + }; + + + + }).ToArray(); + } + + private static string GetExplicitTypeName(Type type) + { + return type switch + { + Type t when t.IsEnum => "Select", + Type t when t == typeof(bool) => "Bool", + Type t when t == typeof(string) => "Value", + Type t when t == typeof(int) => "Value", + Type t when t == typeof(double) => "Value", + _ => "Value" + }; + } + + private static IEnumerable GetExplicitItems(Type type, string explicitTypeName) + { + return explicitTypeName switch + { + "Select" => Enum.GetNames(type), + "Bool" => ["True", "False"], + _ => [] + }; + + } + + private static Delegate GenerateMethodDelegate(Type type, MethodInfo methodInfo, ParameterInfo[] parameters, Type returnType) + { + var parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); + var parameterCount = parameters.Length; + + if (returnType == typeof(void)) + { + if (parameterCount == 0) + { + // 无返回值,无参数 + return ExpressionHelper.MethodCaller(type, methodInfo); + } + else + { + // 无返回值,有参数 + return ExpressionHelper.MethodCaller(type, methodInfo, parameterTypes); + } + } + // else if (returnType == typeof(Task DynamicInstanceToType { get; } = new ConcurrentDictionary(); + // public static ConcurrentDictionary DynamicInstanceToType { get; } = new ConcurrentDictionary(); // 缓存的实例对象 (键:生成的方法名称) // public static ConcurrentDictionary DynamicInstance { get; } = new ConcurrentDictionary(); + /// /// 生成方法信息 /// /// /// /// - public static ConcurrentDictionary GenerateMethodDetails(ISereinIoc serviceContainer, Type type, bool isNetFramework) + public static ConcurrentDictionary GetDict(ISereinIoc serviceContainer, Type type, bool isNetFramework) { var methodDetailsDictionary = new ConcurrentDictionary(); var assemblyName = type.Assembly.GetName().Name; @@ -44,7 +228,6 @@ public static class DelegateGenerator return methodDetailsDictionary; } - /// /// 获取处理方法 /// diff --git a/NodeFlow/Tool/NodeModelBaseFunc.cs b/NodeFlow/Tool/NodeModelBaseFunc.cs new file mode 100644 index 0000000..45eaf05 --- /dev/null +++ b/NodeFlow/Tool/NodeModelBaseFunc.cs @@ -0,0 +1,460 @@ +using Newtonsoft.Json; +using Serein.Library.Api; +using Serein.Library.Entity; +using Serein.Library.Enums; +using Serein.NodeFlow.Tool.SerinExpression; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Base +{ + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + // public abstract partial class NodeModelBase : IDynamicFlowNode + public static class NodeFunc + { + /// + /// 执行节点对应的方法 + /// + /// 流程上下文 + /// 节点传回数据对象 + public static object? Execute(this NodeModelBase nodeModelBase, IDynamicContext context) + { + MethodDetails md = nodeModelBase.MethodDetails; + object? result = null; + var del = md.MethodDelegate; + try + { + if (md.ExplicitDatas.Length == 0) + { + if (md.ReturnType == typeof(void)) + { + ((Action)del).Invoke(md.ActingInstance); + } + else + { + result = ((Func)del).Invoke(md.ActingInstance); + } + } + else + { + object?[]? parameters = nodeModelBase.GetParameters(context, md); + if (md.ReturnType == typeof(void)) + { + ((Action)del).Invoke(md.ActingInstance, parameters); + } + else + { + result = ((Func)del).Invoke(md.ActingInstance, parameters); + } + } + + return result; + } + catch (Exception ex) + { + nodeModelBase.FlowState = FlowStateType.Error; + nodeModelBase.RuningException = ex; + } + + return result; + } + + /// + /// 执行等待触发器的方法 + /// + /// + /// 节点传回数据对象 + /// + public static async Task ExecuteAsync(this NodeModelBase nodeModel, IDynamicContext context) + { + MethodDetails md = nodeModel.MethodDetails; + object? result = null; + + IFlipflopContext flipflopContext = null; + try + { + // 调用委托并获取结果 + if (md.ExplicitDatas.Length == 0) + { + flipflopContext = await ((Func>)md.MethodDelegate).Invoke(md.ActingInstance); + } + else + { + object?[]? parameters = nodeModel.GetParameters(context, md); + flipflopContext = await ((Func>)md.MethodDelegate).Invoke(md.ActingInstance, parameters); + } + + if (flipflopContext != null) + { + nodeModel.FlowState = flipflopContext.State; + if (flipflopContext.State == FlowStateType.Succeed) + { + result = flipflopContext.Data; + } + else + { + result = null; + } + } + } + catch (Exception ex) + { + nodeModel.FlowState = FlowStateType.Error; + nodeModel.RuningException = ex; + } + + return result; + } + + /// + /// 开始执行 + /// + /// + /// + public static async Task StartExecution(this NodeModelBase nodeModel, IDynamicContext context) + { + var cts = context.SereinIoc.GetOrInstantiate(); + + Stack stack = []; + stack.Push(nodeModel); + var md = nodeModel.MethodDetails; + while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环 + { + // 从栈中弹出一个节点作为当前节点进行处理 + var currentNode = stack.Pop(); + + // 设置方法执行的对象 + if (currentNode.MethodDetails != null) + { + currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrInstantiate(md.ActingInstanceType); + } + + // 获取上游分支,首先执行一次 + var upstreamNodes = currentNode.UpstreamBranch; + for (int i = upstreamNodes.Count - 1; i >= 0; i--) + { + upstreamNodes[i].PreviousNode = currentNode; + await upstreamNodes[i].StartExecution(context); + } + + if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == NodeType.Flipflop) + { + // 触发器节点 + currentNode.FlowData = await currentNode.ExecuteAsync(context); + } + else + { + // 动作节点 + currentNode.FlowData = currentNode.Execute(context); + } + + var nextNodes = currentNode.FlowState switch + { + FlowStateType.Succeed => currentNode.SucceedBranch, + FlowStateType.Fail => currentNode.FailBranch, + FlowStateType.Error => currentNode.ErrorBranch, + _ => throw new Exception("非预期的枚举值") + }; + + // 将下一个节点集合中的所有节点逆序推入栈中 + for (int i = nextNodes.Count - 1; i >= 0; i--) + { + nextNodes[i].PreviousNode = currentNode; + stack.Push(nextNodes[i]); + } + } + } + + /// + /// 获取对应的参数数组 + /// + public static object[]? GetParameters(this NodeModelBase nodeModel, IDynamicContext context, MethodDetails md) + { + // 用正确的大小初始化参数数组 + var types = md.ExplicitDatas.Select(it => it.DataType).ToArray(); + if (types.Length == 0) + { + return [md.ActingInstance]; + } + + object[]? parameters = new object[types.Length]; + + for (int i = 0; i < types.Length; i++) + { + + var mdEd = md.ExplicitDatas[i]; + Type type = mdEd.DataType; + + var f1 = nodeModel.PreviousNode?.FlowData?.GetType(); + var f2 = mdEd.DataType; + if (type == typeof(IDynamicContext)) + { + parameters[i] = context; + } + else if (type == typeof(MethodDetails)) + { + parameters[i] = md; + } + else if (type == typeof(NodeModelBase)) + { + parameters[i] = nodeModel; + } + else if (mdEd.IsExplicitData) // 显式参数 + { + // 判断是否使用表达式解析 + if (mdEd.DataValue[0] == '@') + { + var expResult = SerinExpressionEvaluator.Evaluate(mdEd.DataValue, nodeModel.PreviousNode?.FlowData, out bool isChange); + + + if (mdEd.DataType.IsEnum) + { + var enumValue = Enum.Parse(mdEd.DataType, mdEd.DataValue); + parameters[i] = enumValue; + } + else if (mdEd.ExplicitType == typeof(string)) + { + parameters[i] = Convert.ChangeType(expResult, typeof(string)); + } + else if (mdEd.ExplicitType == typeof(bool)) + { + parameters[i] = Convert.ChangeType(expResult, typeof(bool)); + } + else if (mdEd.ExplicitType == typeof(int)) + { + parameters[i] = Convert.ChangeType(expResult, typeof(int)); + } + else if (mdEd.ExplicitType == typeof(double)) + { + parameters[i] = Convert.ChangeType(expResult, typeof(double)); + } + else + { + parameters[i] = expResult; + //parameters[i] = ConvertValue(mdEd.DataValue, mdEd.ExplicitType); + } + } + else + { + if (mdEd.DataType.IsEnum) + { + var enumValue = Enum.Parse(mdEd.DataType, mdEd.DataValue); + parameters[i] = enumValue; + } + else if (mdEd.ExplicitType == typeof(string)) + { + parameters[i] = mdEd.DataValue; + } + else if (mdEd.ExplicitType == typeof(bool)) + { + parameters[i] = bool.Parse(mdEd.DataValue); + } + else if (mdEd.ExplicitType == typeof(int)) + { + parameters[i] = int.Parse(mdEd.DataValue); + } + else if (mdEd.ExplicitType == typeof(double)) + { + parameters[i] = double.Parse(mdEd.DataValue); + } + else + { + parameters[i] = ""; + + //parameters[i] = ConvertValue(mdEd.DataValue, mdEd.ExplicitType); + } + } + + + } + else if (f1 != null && f2 != null) + { + if (f2.IsAssignableFrom(f1) || f2.FullName.Equals(f1.FullName)) + { + parameters[i] = nodeModel.PreviousNode?.FlowData; + + } + } + else + { + + + var tmpParameter = nodeModel.PreviousNode?.FlowData?.ToString(); + if (mdEd.DataType.IsEnum) + { + + var enumValue = Enum.Parse(mdEd.DataType, tmpParameter); + + parameters[i] = enumValue; + } + else if (mdEd.DataType == typeof(string)) + { + + parameters[i] = tmpParameter; + + } + else if (mdEd.DataType == typeof(bool)) + { + + parameters[i] = bool.Parse(tmpParameter); + + } + else if (mdEd.DataType == typeof(int)) + { + + parameters[i] = int.Parse(tmpParameter); + + } + else if (mdEd.DataType == typeof(double)) + { + + parameters[i] = double.Parse(tmpParameter); + + } + else + { + if (tmpParameter != null && mdEd.DataType != null) + { + + parameters[i] = ConvertValue(tmpParameter, mdEd.DataType); + + } + } + } + + } + return parameters; + } + + /// + /// json文本反序列化为对象 + /// + /// + /// + /// + private static dynamic? ConvertValue(string value, Type targetType) + { + try + { + if (!string.IsNullOrEmpty(value)) + { + return JsonConvert.DeserializeObject(value, targetType); + } + else + { + return null; + } + } + catch (JsonReaderException ex) + { + Console.WriteLine(ex); + return value; + } + catch (JsonSerializationException ex) + { + // 如果无法转为对应的JSON对象 + int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引 + int endIndex = ex.Message.IndexOf('\''); // 查找类型信息结束的索引 + var typeInfo = ex.Message[startIndex..endIndex]; // 提取出错类型信息,该怎么传出去? + Console.WriteLine("无法转为对应的JSON对象:" + typeInfo); + return null; + } + catch // (Exception ex) + { + return value; + } + } + + + + + + #region 完整的ExecuteAsync调用方法(不要删除) + //public virtual async Task ExecuteAsync(DynamicContext context) + //{ + // MethodDetails md = MethodDetails; + // object? result = null; + // if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) + // { + // if (md.ExplicitDatas.Length == 0) + // { + // if (md.ReturnType == typeof(void)) + // { + // ((Action)del).Invoke(md.ActingInstance); + // } + // else if (md.ReturnType == typeof(Task)) + // { + // // 调用委托并获取结果 + // FlipflopContext flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance); + + // if (flipflopContext != null) + // { + // if (flipflopContext.State == FfState.Cancel) + // { + // throw new Exception("this async task is cancel."); + // } + // else + // { + // if (flipflopContext.State == FfState.Succeed) + // { + // CurrentState = true; + // result = flipflopContext.Data; + // } + // else + // { + // CurrentState = false; + // } + // } + // } + // } + // else + // { + // result = ((Func)del).Invoke(md.ActingInstance); + // } + // } + // else + // { + // object?[]? parameters = GetParameters(context, MethodDetails); + // if (md.ReturnType == typeof(void)) + // { + // ((Action)del).Invoke(md.ActingInstance, parameters); + // } + // else if (md.ReturnType == typeof(Task)) + // { + // // 调用委托并获取结果 + // FlipflopContext flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance, parameters); + + // if (flipflopContext != null) + // { + // if (flipflopContext.State == FfState.Cancel) + // { + // throw new Exception("取消此异步"); + // } + // else + // { + // CurrentState = flipflopContext.State == FfState.Succeed; + // result = flipflopContext.Data; + // } + // } + // } + // else + // { + // result = ((Func)del).Invoke(md.ActingInstance, parameters); + // } + // } + // context.SetFlowData(result); + // } + // return result; + //} + #endregion + + + + + } +} diff --git a/README.md b/README.md index a95e9b2..4ba4c2e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ 不定期在Bilibili个人空间上更新相关的视频。 https://space.bilibili.com/33526379 +# 当前任务 2024年9月12日22:32:10 +1. 将运行环境从UI(WPF)中独立出来,方便控制台程序直接运行项目文件。 +2. 包装数据类型, 优化传递效率(尽可能避免拆箱、装箱) +3. 优化触发器节点(显示传出类型) + # 如何加载我的DLL? 使用 **DynamicFlow** 特性标记你的类,可以参照 **MyDll** 与 **SereinWAT** 的实现。编译为 Dll文件 后,拖入到软件中即可。 diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index e9f9efd..73d2c73 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -1,4 +1,6 @@ using Newtonsoft.Json; +using Serein.Library.Entity; +using Serein.NodeFlow; using System.Diagnostics; using System.Windows; @@ -115,8 +117,10 @@ namespace Serein.WorkBench window.Close(); } } - - public static SereinOutputFileData? FData; + /// + /// 成功加载的工程文件 + /// + public static SereinOutputFileData? FData { get; set; } public static string FileDataPath = ""; private void Application_Startup(object sender, StartupEventArgs e) { diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index d5695eb..9d36334 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -5,7 +5,6 @@ xmlns:custom="clr-namespace:Serein.WorkBench.Node.View" Title="Dynamic Node Flow" Height="700" Width="1200" AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver" - SizeChanged ="Window_SizeChanged" Loaded="Window_Loaded" Closing="Window_Closing"> @@ -55,9 +54,9 @@ --> - - - + + + diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index fd208ff..04fcc6f 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -1,22 +1,23 @@ using Microsoft.Win32; using Newtonsoft.Json.Linq; -using Serein.Library.Attributes; +using Serein.Library.Api; +using Serein.Library.Entity; +using Serein.Library.Enums; using Serein.Library.Utils; using Serein.NodeFlow; +using Serein.NodeFlow.Base; using Serein.NodeFlow.Model; -using Serein.NodeFlow.Tool; using Serein.WorkBench.Node.View; using Serein.WorkBench.Node.ViewModel; using Serein.WorkBench.Themes; using Serein.WorkBench.tool; -using System.Collections.Concurrent; -using System.Diagnostics; using System.IO; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; +using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; @@ -29,99 +30,21 @@ namespace Serein.WorkBench /// public static class MouseNodeType { - public static string RegionType { get; } = nameof(RegionType); - public static string BaseNodeType { get; } = nameof(BaseNodeType); - public static string DllNodeType { get; } = nameof(DllNodeType); + public static string CreateDllNodeInCanvas { get; } = nameof(CreateDllNodeInCanvas); + public static string CreateBaseNodeInCanvas { get; } = nameof(CreateBaseNodeInCanvas); + //public static string RegionType { get; } = nameof(RegionType); + //public static string BaseNodeType { get; } = nameof(BaseNodeType); + //public static string DllNodeType { get; } = nameof(DllNodeType); } - /// - /// 表示两个节点之间的连接关系(UI层面) - /// - //public class Connection - //{ - // public required NodeControlBase Start { get; set; } // 起始TextBlock - // public required NodeControlBase End { get; set; } // 结束TextBlock - // public required Line Line { get; set; } // 连接的线 - // public ConnectionType Type { get; set; } // 连接的线是否为真分支或者假分支 - - // private Storyboard? _animationStoryboard; // 动画Storyboard - - // /// - // /// 从Canvas中移除连接线 - // /// - // /// - // public void RemoveFromCanvas(Canvas canvas) - // { - // canvas.Children.Remove(Line); // 移除线 - // _animationStoryboard?.Stop(); // 停止动画 - // } - - // /// - // /// 开始动画 - // /// - // public void StartAnimation() - // { - // // 停止现有的动画 - // _animationStoryboard?.Stop(); - - // // 计算线条的长度 - // double length = Math.Sqrt(Math.Pow(Line.X2 - Line.X1, 4) + Math.Pow(Line.Y2 - Line.Y1, 4)); - // double dashLength = length / 200; - - // // 创建新的 DoubleAnimation 反转方向 - // var animation = new DoubleAnimation - // { - // From = dashLength, - // To = 0, - // Duration = TimeSpan.FromSeconds(0.5), - // RepeatBehavior = RepeatBehavior.Forever - // }; - - // // 设置线条的样式 - // Line.Stroke = Type == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) - // : Type == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) - // : Type == ConnectionType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B")) - // : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")); - // Line.StrokeDashArray = [dashLength, dashLength]; - - // // 创建新的 Storyboard - // _animationStoryboard = new Storyboard(); - // _animationStoryboard.Children.Add(animation); - // Storyboard.SetTarget(animation, Line); - // Storyboard.SetTargetProperty(animation, new PropertyPath(Line.StrokeDashOffsetProperty)); - - // // 开始动画 - // _animationStoryboard.Begin(); - // } - - // /// - // /// 停止动画 - // /// - // public void StopAnimation() - // { - // if (_animationStoryboard != null) - // { - // _animationStoryboard.Stop(); - // Line.Stroke = Type == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) - // : Type == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) - // : Type == ConnectionType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B")) - // : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")); - // Line.StrokeDashArray = null; - // } - // } - //} - + /// /// Interaction logic for MainWindow.xaml,第一次用git,不太懂 /// public partial class MainWindow : Window { - /// - /// 节点的命名空间 - /// - public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; /// /// 一种轻量的IOC容器 /// @@ -135,36 +58,44 @@ namespace Serein.WorkBench /// /// 存储加载的程序集 /// - private readonly List loadedAssemblies = []; + // private readonly List loadedAssemblies = []; /// /// 存储加载的程序集路径 /// - private readonly List loadedAssemblyPaths = []; + // private readonly List loadedAssemblyPaths = []; /// /// 存储所有方法信息 /// - ConcurrentDictionary DictMethodDetail = []; + // private static ConcurrentDictionary DllMethodDetails { get; } = []; /// /// 存储所有与节点有关的控件 /// - private readonly List nodeControls = []; + private readonly Dictionary NodeControls = []; /// /// 存储所有的连接 /// - private readonly List connections = []; + private readonly List Connections = []; + /// /// 存放触发器节点(运行时全部调用) /// - private readonly List flipflopNodes = []; - + // private readonly List flipflopNodes = []; + /// + /// 节点的命名空间 + /// + public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; + /// + /// 流程运行环境 + /// + private IFlowEnvironment FlowEnvironment; /// /// 流程启动器 /// - private NodeFlowStarter nodeFlowStarter; + // private FlowStarter nodeFlowStarter; #region 与画布相关的字段 @@ -180,11 +111,11 @@ namespace Serein.WorkBench /// /// 流程图起点的控件 /// - private NodeControlBase? flowStartBlock; + // private NodeControlBase? flowStartBlock; /// /// 记录开始连接的文本块 /// - private NodeControlBase? startConnectBlock; + private NodeControlBase? startConnectNodeControl; /// /// 当前正在绘制的连接线 /// @@ -220,26 +151,46 @@ namespace Serein.WorkBench /// /// 平移画布 /// - private TranslateTransform translateTransform; + private TranslateTransform translateTransform; #endregion + private Point canvasDropPosition; + // private MainWindowViewModel mainWindowViewModel { get; } public MainWindow() - { + // mainWindowViewModel = new MainWindowViewModel(this); InitializeComponent(); + + + logWindow = new LogWindow(); logWindow.Show(); // 重定向 Console 输出 var logTextWriter = new LogTextWriter(WriteLog); Console.SetOut(logTextWriter); - - UIInit(); + InitFlowEvent(); + InitUI(); } - private void UIInit() + private void InitFlowEvent() + { + FlowEnvironment = new FlowEnvironment(); + FlowEnvironment.OnDllLoad += FlowEnvironment_DllLoadEvent; + FlowEnvironment.OnLoadNode += FlowEnvironment_NodeLoadEvent; + FlowEnvironment.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent; + FlowEnvironment.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt; + FlowEnvironment.OnNodeCreate += FlowEnvironment_NodeCreateEvent; + FlowEnvironment.OnNodeRemote += FlowEnvironment_NodeRemoteEvent; + FlowEnvironment.OnFlowRunComplete += FlowEnvironment_OnFlowRunComplete; + + } + + + + private void InitUI() { canvasTransformGroup = new TransformGroup(); scaleTransform = new ScaleTransform(); @@ -264,293 +215,345 @@ namespace Serein.WorkBench logWindow.AppendText(message); } - #region 加载 DynamicNodeFlow 文件 - private void Window_Loaded(object sender, RoutedEventArgs e) - { - var nf = App.FData; - if (nf != null) - { - InitializeCanvas(nf.basic.canvas.width, nf.basic.canvas.lenght); - LoadDll(nf); // 加载DLL - LoadNodeControls(nf); // 加载节点 - - var startNode = nodeControls.FirstOrDefault(control => control.ViewModel.Node.Guid.Equals(nf.startNode)); - if (startNode != null) - { - startNode.ViewModel.Node.IsStart = true; - SetIsStartBlock(startNode); - } - } - } - + #region 运行环境事件 /// - /// 加载配置文件时加载DLL + /// 运行完成 /// - /// - private void LoadDll(SereinOutputFileData nf) + /// + /// + private void FlowEnvironment_OnFlowRunComplete(FlowEventArgs eventArgs) { - var dllPaths = nf.library.Select(it => it.path).ToList(); - foreach (var dll in dllPaths) - { - var filePath = System.IO.Path.GetFullPath(System.IO.Path.Combine(App.FileDataPath, dll)); - LoadAssembly(filePath); - } - } - - /// - /// 加载配置文件时加载节点/区域 - /// - /// - private void LoadNodeControls(SereinOutputFileData nf) - { - var nodeControls = new Dictionary(); - var regionControls = new Dictionary(); - - foreach (var nodeInfo in nf.nodes) - { - NodeControlBase? nodeControl = CreateNodeControl(nodeInfo);// 加载DLL时创建控件 - if (nodeControl != null) - { - ConfigureNodeControl(nodeInfo, nodeControl, nodeControls, regionControls); - } - else - { - WriteLog($"无法为节点类型创建节点控件: {nodeInfo.name}\r\n"); - } - } - - FlowChartCanvas.UpdateLayout(); - LoadRegionChildNodes(nf, regionControls); - StartConnectNodeControls(nf, nodeControls); + WriteLog("-------运行完成---------\r\n"); } /// - /// 加载配置文件时加载区域子项 + /// 加载了DLL文件,dll内容 /// - /// - /// - private void LoadRegionChildNodes(SereinOutputFileData nf, Dictionary regionControls) + private void FlowEnvironment_DllLoadEvent(LoadDLLEventArgs eventArgs) { - foreach (var region in nf.regions) + Assembly assembly = eventArgs.Assembly; + List methodDetailss = eventArgs.MethodDetailss; + + var dllControl = new DllControl { - foreach (var childNode in region.childNodes) - { - if (regionControls.TryGetValue(region.guid, out var regionControl)) - { - var nodeControl = CreateNodeControl(childNode); // 加载区域的子项 - if (nodeControl != null) - { - if (regionControl is ConditionRegionControl conditionRegion) - { - conditionRegion.AddCondition(nodeControl); - } - else if (regionControl is ActionRegionControl actionRegionControl) - { - //actionRegionControl.AddAction(nodeControl); - } - } - else - { - WriteLog($"无法为节点类型创建节点控件: {childNode.name}\r\n"); - } - } - } - } - } - - /// - /// 加载配置文件时开始连接节点/区域 - /// - /// - /// - private void StartConnectNodeControls(SereinOutputFileData nf, Dictionary nodeControls) - { - foreach (var node in nf.nodes) - { - if (nodeControls.TryGetValue(node.guid, out var fromNode)) - { - ConnectNodeControlChildren(fromNode, node.trueNodes, nodeControls, ConnectionType.IsSucceed); - ConnectNodeControlChildren(fromNode, node.falseNodes, nodeControls, ConnectionType.IsFail); - //ConnectNodeControlChildren(fromNode, node.errorNodes, nodeControls, ConnectionType.IsError); - ConnectNodeControlChildren(fromNode, node.upstreamNodes, nodeControls, ConnectionType.Upstream); - } - } - } - /// - /// 加载配置文件时递归连接节点/区域 1 - /// - /// - /// - /// - /// - /// - private void ConnectNodeControlChildren(NodeControlBase fromNode, - string[] childNodeGuids, - Dictionary nodeControls, - ConnectionType connectionType) - { - foreach (var childNodeGuid in childNodeGuids) - { - if (nodeControls.TryGetValue(childNodeGuid, out var toNode)) - { - ConnectNodeControls(fromNode, toNode, connectionType); - } - } - } - - /// - /// 加载配置文件时递归连接节点/区域 2 - /// - /// - /// - /// - private void ConnectNodeControls(NodeControlBase fromNode, NodeControlBase toNode, ConnectionType connectionType) - { - if (fromNode != null && toNode != null && fromNode != toNode) - { - if (connectionType == ConnectionType.IsSucceed) - { - fromNode.ViewModel.Node.SucceedBranch.Add(toNode.ViewModel.Node); - } - else if (connectionType == ConnectionType.IsFail) - { - fromNode.ViewModel.Node.FailBranch.Add(toNode.ViewModel.Node); - } - else if (connectionType == ConnectionType.IsError) - { - fromNode.ViewModel.Node.ErrorBranch.Add(toNode.ViewModel.Node); - } - else if (connectionType == ConnectionType.Upstream) - { - fromNode.ViewModel.Node.UpstreamBranch.Add(toNode.ViewModel.Node); - } - var connection = new Connection { Start = fromNode, End = toNode, Type = connectionType }; - toNode.ViewModel.Node.PreviousNodes.Add(fromNode.ViewModel.Node); - BsControl.Draw(FlowChartCanvas, connection); - ConfigureLineContextMenu(connection); - connections.Add(connection); - } - EndConnection(); - } - - - /// - /// 配置节点(加载配置文件时) - /// - /// 节点配置数据 - /// 需要配置的节点 - /// 节点列表 - /// 区域列表 - private void ConfigureNodeControl(NodeInfo nodeInfo, - NodeControlBase nodeControl, - Dictionary nodeControls, - Dictionary regionControls) - { - FlowChartCanvas.Dispatcher.Invoke(() => - { - FlowChartCanvas.Children.Add(nodeControl); - Canvas.SetLeft(nodeControl, nodeInfo.position.x); - Canvas.SetTop(nodeControl, nodeInfo.position.y); - nodeControls[nodeInfo.guid] = nodeControl; - this.nodeControls.Add(nodeControl); - - if (nodeControl is ActionRegionControl || nodeControl is ConditionRegionControl)//如果是区域,则需要创建区域 - { - regionControls[nodeInfo.guid] = nodeControl; - } - - ConfigureContextMenu(nodeControl); // 创建区域 - ConfigureNodeEvents(nodeControl); // 创建区域事件(UI相关) - }); - } - - /// - /// 创建控件并配置节点数据(加载配置文件时) - /// - /// - /// - private NodeControlBase CreateNodeControl(NodeInfo nodeInfo) - { - MethodDetails md = null; - if (!string.IsNullOrWhiteSpace(nodeInfo.name)) - { - DllMethodDetails.TryGetValue(nodeInfo.name, out md); - } - - NodeControlBase control = nodeInfo.type switch - { - $"{NodeSpaceName}.{nameof(SingleActionNode)}" => CreateNodeControl(md), - $"{NodeSpaceName}.{nameof(SingleFlipflopNode)}" => CreateNodeControl(md), - - $"{NodeSpaceName}.{nameof(SingleConditionNode)}" => CreateNodeControl(), // 条件表达式控件 - $"{NodeSpaceName}.{nameof(SingleExpOpNode)}" => CreateNodeControl(), // 操作表达式控件 - - //$"{NodeSpaceName}.{nameof(CompositeActionNode)}" => CreateNodeControl(), - $"{NodeSpaceName}.{nameof(CompositeConditionNode)}" => CreateNodeControl(), - _ => throw new NotImplementedException($"非预期的节点类型{nodeInfo.type}"), + Header = "DLL name : " + assembly.GetName().Name // 设置控件标题为程序集名称 }; - // 如果是触发器,则需要添加到集合中 - if (control is FlipflopNodeControl flipflopNodeControl && flipflopNodeControl.ViewModel.Node is SingleFlipflopNode flipflopNode) + foreach (var methodDetails in methodDetailss) { - var guid = flipflopNode.Guid; - if (!flipflopNodes.Exists(it => it.Guid.Equals(guid))) + switch (methodDetails.MethodDynamicType) { - flipflopNodes.Add(flipflopNode); + case Library.Enums.NodeType.Action: + dllControl.AddAction(methodDetails.Clone()); // 添加动作类型到控件 + break; + case Library.Enums.NodeType.Flipflop: + dllControl.AddFlipflop(methodDetails.Clone()); // 添加触发器方法到控件 + break; } } - var node = control.ViewModel.Node; - if (node != null) + DllStackPanel.Children.Add(dllControl); // 将控件添加到界面上显示 + } + + /// + /// 运行环境成功加载了节点,需要在画布上创建节点控件 + /// + /// + /// + private void FlowEnvironment_NodeLoadEvent(LoadNodeEventArgs eventArgs) + { + if (!eventArgs.IsSucceed) { - node.Guid = nodeInfo.guid; - for (int i = 0; i < nodeInfo.parameterData.Length; i++) - { - Parameterdata? pd = nodeInfo.parameterData[i]; - if (control is ConditionNodeControl conditionNodeControl && conditionNodeControl.ViewModel is ConditionNodeControlViewModel conditionNodeControlViewModel) - { - conditionNodeControlViewModel.IsCustomData = pd.state; - conditionNodeControlViewModel.CustomData = pd.value; - conditionNodeControlViewModel.Expression = pd.expression; - } - else if (control is ExpOpNodeControl expOpNodeControl && expOpNodeControl.ViewModel is ExpOpNodeViewModel expOpNodeViewModel) - { - expOpNodeViewModel.Expression = pd.expression; - } - else - { - node.MethodDetails.ExplicitDatas[i].IsExplicitData = pd.state; - node.MethodDetails.ExplicitDatas[i].DataValue = pd.value; - } - } + MessageBox.Show(eventArgs.ErrorTips); + return; + } + NodeInfo nodeInfo = eventArgs.NodeInfo; + MethodDetails methodDetailss = eventArgs.MethodDetailss; + + // 创建对应的实例(包含NodeModel,NodeControl,NodeControlViewModel) + NodeControlBase? nodeControl = CreateNodeControlOfNodeInfo(nodeInfo, methodDetailss); + if (nodeControl == null) + { + WriteLog($"无法为节点类型创建节点控件: {nodeInfo.MethodName}\r\n"); + return; + // ConfigureNodeControl(nodeInfo, nodeControl, nodeControls, regionControls); + } + + // 判断是否属于区域控件,如果是,则加载区域子项 + if (nodeControl is ActionRegionControl || nodeControl is ConditionRegionControl) + { + AddNodeControlInRegeionControl(nodeControl, nodeInfo.ChildNodes); } - - + NodeControls.TryAdd(nodeInfo.Guid, nodeControl); // 存放对应的控件 + PlaceNodeOnCanvas(nodeControl, nodeInfo.Position.X, nodeInfo.Position.Y); // 配置节点,并放置在画布上 - return control;// DNF文件加载时创建 - /* NodeControl? nodeControl = nodeInfo.type switch - { - $"{NodeSpaceName}.{nameof(SingleActionNode)}" => CreateActionNodeControl(md), - $"{NodeSpaceName}.{nameof(SingleConditionNode)}" => CreateConditionNodeControl(md), - $"{NodeSpaceName}.{nameof(CompositeActionNode)}" => CreateCompositeActionNodeControl(md), - $"{NodeSpaceName}.{nameof(CompositeConditionNode)}" => CreateCompositeConditionNodeControl(md), - _ => null - };*/ + // 添加到画布 + //FlowChartCanvas.Dispatcher.Invoke(() => + //{ + // // 添加控件到画布 + // FlowChartCanvas.Children.Add(nodeControl); + // Canvas.SetLeft(nodeControl, nodeInfo.Position.x); + // Canvas.SetTop(nodeControl, nodeInfo.Position.y); + // nodeControls.TryAdd(nodeInfo.Guid, nodeControl); // 存放对应的控件 + // ConfigureContextMenu(nodeControl); // 配置节点右键菜单 + // ConfigureNodeEvents(nodeControl); // 配置节点事件 + //}); + + } + + + /// + /// 节点连接关系变更 + /// + /// + /// + /// + private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs) + { + string fromNodeGuid = eventArgs.FromNodeGuid; + string toNodeGuid = eventArgs.ToNodeGuid; + if (!NodeControls.TryGetValue(fromNodeGuid, out var fromNode) || !NodeControls.TryGetValue(toNodeGuid, out var toNode)) + { + return; + } + ConnectionType connectionType = eventArgs.ConnectionType; + if(eventArgs.ChangeType == NodeConnectChangeEventArgs.ChangeTypeEnum.Create) + { + // 添加连接 + var connection = new Connection + { + Start = fromNode, + End = toNode, + Type = connectionType + }; + + BsControl.Draw(FlowChartCanvas, connection); // 添加贝塞尔曲线显示 + ConfigureLineContextMenu(connection); // 设置连接右键事件 + Connections.Add(connection); + EndConnection(); + } + else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ChangeTypeEnum.Remote) + { + // 需要移除连接 + var removeConnections = Connections.Where(c => c.Start.ViewModel.Node.Guid.Equals(fromNodeGuid) + && c.End.ViewModel.Node.Guid.Equals(toNodeGuid)) + .ToList(); + foreach(var connection in removeConnections) + { + connection.RemoveFromCanvas(FlowChartCanvas); + Connections.Remove(connection); + } + } + } + + /// + /// 节点移除事件 + /// + /// + private void FlowEnvironment_NodeRemoteEvent(NodeRemoteEventArgs eventArgs) + { + var nodeGuid = eventArgs.NodeGuid; + if (!NodeControls.TryGetValue(nodeGuid, out var nodeControl)) + { + return; + } + FlowChartCanvas.Children.Remove(nodeControl); + NodeControls.Remove(nodeControl.ViewModel.Node.Guid); + + } + + /// + /// 编辑项目时添加了节点 + /// + /// + /// + private void FlowEnvironment_NodeCreateEvent(NodeCreateEventArgs eventArgs) + { + if (eventArgs.NodeModel is not NodeModelBase nodeModelBase) + { + return; + } + + // 创建对应控件 + NodeControlBase? nodeControl = nodeModelBase.ControlType switch + { + NodeControlType.Action => CreateNodeControl(nodeModelBase), //typeof(ActionNodeControl), + NodeControlType.Flipflop => CreateNodeControl(nodeModelBase), + NodeControlType.ExpCondition => CreateNodeControl(nodeModelBase), + NodeControlType.ExpOp => CreateNodeControl(nodeModelBase), + NodeControlType.ConditionRegion => CreateNodeControl(nodeModelBase), + _ => null, + }; + if(nodeControl == null) + { + return; + } + + + NodeControls.TryAdd(nodeModelBase.Guid, nodeControl); + + if (!TryPlaceNodeInRegion(nodeControl)) + { + PlaceNodeOnCanvas(nodeControl, canvasDropPosition.X, canvasDropPosition.Y); + } + + + } + + + /// + /// 设置了流程起始控件 + /// + /// + /// + private void FlowEnvironment_StartNodeChangeEvent(StartNodeChangeEventArgs eventArgs) + { + string oldNodeGuid = eventArgs.OldNodeGuid; + string newNodeGuid = eventArgs.NewNodeGuid; + if (!NodeControls.TryGetValue(newNodeGuid, out var newStartNodeControl)) + { + return; + } + if (newStartNodeControl == null) + { + return; + } + if (!string.IsNullOrEmpty(oldNodeGuid)) + { + NodeControls.TryGetValue(oldNodeGuid, out var oldStartNodeControl); + if (oldStartNodeControl != null) + { + oldStartNodeControl.BorderBrush = Brushes.Black; + oldStartNodeControl.BorderThickness = new Thickness(0); + } + + } + + newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")); + newStartNodeControl.BorderThickness = new Thickness(2); + } #endregion + + + #region 加载 DynamicNodeFlow 文件 + private void Window_Loaded(object sender, RoutedEventArgs e) + { + var project = App.FData; + if (project == null) + { + return; + } + + InitializeCanvas(project.Basic.canvas.width, project.Basic.canvas.lenght);// 设置画布大小 + FlowEnvironment.LoadProject(project, App.FileDataPath); // 加载项目 + + + //LoadDll(project); // 加载DLL + //LoadNodeControls(project); // 加载节点 + + //var startNode = nodeControls.Values.FirstOrDefault(control => control.ViewModel.Node.Guid.Equals(project.StartNode)); + //var startNodeGuid = nodeControls.Keys.FirstOrDefault(guid => guid.Equals(project.StartNode)); + //if (!string.IsNullOrEmpty(startNodeGuid)) + //{ + // FlowEnvironment.SetStartNode(startNodeGuid); + //} + + } + + + /// + /// 运行环节加载了项目文件,需要创建节点控件 + /// + /// + /// + /// + /// + private NodeControlBase? CreateNodeControlOfNodeInfo(NodeInfo nodeInfo, MethodDetails methodDetailss) + { + // 创建控件实例 + NodeControlBase nodeControl = nodeInfo.Type switch + { + $"{NodeSpaceName}.{nameof(SingleActionNode)}" => + CreateNodeControl(methodDetailss),// 动作节点控件 + $"{NodeSpaceName}.{nameof(SingleFlipflopNode)}" => + CreateNodeControl(methodDetailss), // 触发器节点控件 + + $"{NodeSpaceName}.{nameof(SingleConditionNode)}" => + CreateNodeControl(), // 条件表达式控件 + $"{NodeSpaceName}.{nameof(SingleExpOpNode)}" => + CreateNodeControl(), // 操作表达式控件 + + $"{NodeSpaceName}.{nameof(CompositeConditionNode)}" => + CreateNodeControl(), // 条件区域控件 + _ => throw new NotImplementedException($"非预期的节点类型{nodeInfo.Type}"), + }; + return nodeControl; + } + /// + /// 加载文件时,添加节点到区域中 + /// + /// + /// + private void AddNodeControlInRegeionControl(NodeControlBase regionControl, NodeInfo[] childNodes) + { + foreach (var childNode in childNodes) + { + if (FlowEnvironment.TryGetMethodDetails(childNode.MethodName, out MethodDetails md)) + { + var childNodeControl = CreateNodeControlOfNodeInfo(childNode, md); + if (childNodeControl == null) + { + WriteLog($"无法为节点类型创建节点控件: {childNode.MethodName}\r\n"); + continue; + } + + if (regionControl is ConditionRegionControl conditionRegion) + { + conditionRegion.AddCondition(childNodeControl); + } + } + } + } + + #endregion + #region 节点控件的创建 - private static TControl CreateNodeControl(MethodDetails? methodDetails = null) - where TNode : NodeBase + private static TControl CreateNodeControl(NodeModelBase model) where TControl : NodeControlBase where TViewModel : NodeControlViewModelBase { + + if (model == null) + { + throw new Exception("无法创建节点控件"); + } + + var viewModel = Activator.CreateInstance(typeof(TViewModel), [model]); + var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]); + if (controlObj is TControl control) + { + return control; + } + else + { + throw new Exception("无法创建节点控件"); + } + } + + private static TControl CreateNodeControl(MethodDetails? methodDetails = null) + where TNode : NodeModelBase + where TControl : NodeControlBase + where TViewModel : NodeControlViewModelBase + { + var nodeObj = Activator.CreateInstance(typeof(TNode)); - var nodeBase = nodeObj as NodeBase; + var nodeBase = nodeObj as NodeModelBase; if (nodeBase == null) { throw new Exception("无法创建节点控件"); @@ -562,7 +565,6 @@ namespace Serein.WorkBench if (methodDetails != null) { var md = methodDetails.Clone(); - nodeBase.DelegateName = md.MethodName; nodeBase.DisplayName = md.MethodTips; nodeBase.MethodDetails = md; } @@ -579,6 +581,25 @@ namespace Serein.WorkBench } } + /// + /// 创建了节点,添加到画布。配置默认事件 + /// + /// + /// + private void PlaceNodeOnCanvas(NodeControlBase nodeControl, double x, double y) + { + FlowChartCanvas.Dispatcher.Invoke(() => + { + // 添加控件到画布 + FlowChartCanvas.Children.Add(nodeControl); + Canvas.SetLeft(nodeControl, x); + Canvas.SetTop(nodeControl, y); + + ConfigureContextMenu(nodeControl); // 配置节点右键菜单 + ConfigureNodeEvents(nodeControl); // 配置节点事件 + }); + } + /// /// 配置节点右键菜单 /// @@ -587,6 +608,8 @@ namespace Serein.WorkBench { var contextMenu = new ContextMenu(); + // var nodeModel = nodeControl.ViewModel.Node; + if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void)) { contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) => @@ -594,13 +617,16 @@ namespace Serein.WorkBench DisplayReturnTypeTreeViewer(returnType); })); } - contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => SetIsStartBlock(nodeControl))); - contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => DeleteBlock(nodeControl))); + var nodeGuid = nodeControl?.ViewModel?.Node?.Guid; + contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid))); + contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid))); + contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed))); contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail))); contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsError))); contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream))); + nodeControl.ContextMenu = contextMenu; } @@ -617,18 +643,6 @@ namespace Serein.WorkBench return menuItem; } - /// - /// 配置节点/区域连接的右键菜单 - /// - /// - private void ConfigureLineContextMenu(Connection connection) - { - var contextMenu = new ContextMenu(); - contextMenu.Items.Add(CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection))); - connection.ArrowPath.ContextMenu = contextMenu; - connection.BezierPath.ContextMenu = contextMenu; - - } /// /// 配置节点事件 @@ -641,7 +655,91 @@ namespace Serein.WorkBench nodeControl.MouseLeftButtonUp += Block_MouseLeftButtonUp; } - #endregion + + + #endregion + + #region 右键菜单事件 + /// + /// 开始创建连接 True线 操作,设置起始块和绘制连接线。 + /// + private void StartConnection(NodeControlBase startNodeControl, ConnectionType connectionType) + { + var tf = Connections.FirstOrDefault(it => it.Start == startNodeControl)?.Type; + IsConnecting = true; + currentConnectionType = connectionType; + startConnectNodeControl = startNodeControl; + + // 确保起点和终点位置的正确顺序 + currentLine = new Line + { + Stroke = connectionType == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) + : connectionType == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) + : connectionType == ConnectionType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B")) + : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")), + StrokeDashArray = new DoubleCollection([2]), + StrokeThickness = 2, + X1 = Canvas.GetLeft(startConnectNodeControl) + startConnectNodeControl.ActualWidth / 2, + Y1 = Canvas.GetTop(startConnectNodeControl) + startConnectNodeControl.ActualHeight / 2, + X2 = Canvas.GetLeft(startConnectNodeControl) + startConnectNodeControl.ActualWidth / 2, // 初始时终点与起点重合 + Y2 = Canvas.GetTop(startConnectNodeControl) + startConnectNodeControl.ActualHeight / 2, + }; + + FlowChartCanvas.Children.Add(currentLine); + this.KeyDown += MainWindow_KeyDown; + } + + /// + /// 配置连接曲线的右键菜单 + /// + /// + private void ConfigureLineContextMenu(Connection connection) + { + var contextMenu = new ContextMenu(); + + contextMenu.Items.Add(CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection))); + connection.ArrowPath.ContextMenu = contextMenu; + connection.BezierPath.ContextMenu = contextMenu; + } + /// + /// 删除该连线 + /// + /// + private void DeleteConnection(Connection connection) + { + var connectionToRemove = connection; + if (connectionToRemove == null) + { + return; + } + // 获取起始节点与终止节点,消除映射关系 + var fromNodeGuid = connectionToRemove.Start.ViewModel.Node.Guid; + var toNodeGuid = connectionToRemove.End.ViewModel.Node.Guid; + FlowEnvironment.RemoteConnect(fromNodeGuid, toNodeGuid, connection.Type); + } + + + /// + /// 查看返回类型(树形结构展开类型的成员) + /// + /// + private void DisplayReturnTypeTreeViewer(Type type) + { + try + { + var typeViewerWindow = new TypeViewerWindow + { + Type = type, + }; + typeViewerWindow.LoadTypeInformation(); + typeViewerWindow.Show(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + #endregion #region 拖拽DLL文件到左侧功能区,加载相关节点清单 /// @@ -658,7 +756,7 @@ namespace Serein.WorkBench { if (file.EndsWith(".dll")) { - LoadAssembly(file); + FlowEnvironment.LoadDll(file); } } } @@ -675,172 +773,10 @@ namespace Serein.WorkBench e.Handled = true; } - /// - /// 本地换成的方法 - /// - // private static ConcurrentDictionary globalDicDelegates = new ConcurrentDictionary(); - - private static ConcurrentDictionary DllMethodDetails { get; } = []; - /// - /// 加载指定路径的DLL文件 - /// - /// - private void LoadAssembly(string dllPath) - { - try - { - Assembly assembly = Assembly.LoadFrom(dllPath); // 加载DLL文件 - Type[] types = assembly.GetTypes(); // 获取程序集中的所有类型 - - List scanTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute()?.Scan == true ).ToList(); - //List<(bool, Type)> scanTypes = scanTypesTemp.Select(t => (t.GetCustomAttribute()?.Scan == true, t)).ToList(); - - //bool isNetFramework = false; - //if(scanTypes.Count == 0) - //{ - // scanTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute()?.Scan == true).ToList(); - // if(scanTypes.Count == 0) - // { - // return; - // } - // else - // { - // isNetFramework = true; - // } - //} - - loadedAssemblies.Add(assembly); // 将加载的程序集添加到列表中 - loadedAssemblyPaths.Add(dllPath); // 记录加载的DLL路径 - List conditionMethods = []; - List actionMethods = []; - List flipflopMethods = []; - - - // foreach ((bool isNetFramework,Type type) item in scanTypes) - foreach (var item in scanTypes) - { - //加载DLL - var dict = DelegateGenerator.GenerateMethodDetails(ServiceContainer, item, false); - - foreach (var detail in dict) - { - WriteLog($"Method: {detail.Key}, Type: {detail.Value.MethodDynamicType}\r\n"); - DllMethodDetails.TryAdd(detail.Key, detail.Value); - - // 根据 DynamicType 分类 - switch (detail.Value.MethodDynamicType.ToString()) - { - case nameof(Serein.Library.Enums.NodeType.Condition): - conditionMethods.Add(detail.Value); - break; - case nameof(Serein.Library.Enums.NodeType.Action): - actionMethods.Add(detail.Value); - break; - case nameof(Serein.Library.Enums.NodeType.Flipflop): - flipflopMethods.Add(detail.Value); - break; - } - - DictMethodDetail.TryAdd(detail.Key, detail.Value); - // 将委托缓存到全局字典 - DelegateCache.GlobalDicDelegates.TryAdd(detail.Key, detail.Value.MethodDelegate); - } - } - - // 遍历类型,根据接口分类 - /*foreach (Type type in types) - { - // 确保类型是一个类并且实现了ICondition、IAction、IState中的一个或多个接口 - if (type.IsClass && (typeof(ICondition).IsAssignableFrom(type) || typeof(IAction).IsAssignableFrom(type) || typeof(IState).IsAssignableFrom(type))) - { - // 使用反射创建实例 - var instance = Activator.CreateInstance(type); - // 获取 InitDynamicDelegate 方法 - - var method = type.GetMethod("InitDynamicDelegate"); - if (method != null) - { - // 调用 InitDynamicDelegate 方法 - var result = method.Invoke(instance, null); - if (result is ConcurrentDictionary dic) - { - foreach (var kvp in dic) - { - // 根据 DynamicType 分类 - if (kvp.Value.MethodDynamicType == DynamicType.Condition) - { - conditionMethods.Add(kvp.Value); - } - else if (kvp.Value.MethodDynamicType == DynamicType.Action) - { - actionMethods.Add(kvp.Value); - } - //else if (kvp.Value == DynamicType.State) - //{ - // stateTypes.Add(type); - //} - - // 将委托缓存到全局字典 - globalDicDelegates.TryAdd(kvp.Key, kvp.Value.MethodDelegate); - } - } - } - } - }*/ - - // 显示加载的DLL信息 - DisplayControlDll(assembly, conditionMethods, actionMethods, flipflopMethods); - } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - MessageBox.Show($"加载程序集失败: {ex}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); - } - } - - /// - /// 显示DLL信息 - /// - /// dll对象 - /// 条件接口 - /// 动作接口 - /// 状态接口 - private void DisplayControlDll(Assembly assembly, - List conditionTypes, - List actionTypes, - List flipflopMethods) - { - - var dllControl = new DllControl - { - Header = "DLL name : " + assembly.GetName().Name // 设置控件标题为程序集名称 - }; - - - foreach (var item in actionTypes) - { - dllControl.AddAction(item.Clone()); // 添加动作类型到控件 - } - foreach (var item in flipflopMethods) - { - dllControl.AddFlipflop(item.Clone()); // 添加触发器方法到控件 - } - - /*foreach (var item in stateTypes) - { - dllControl.AddState(item); - }*/ - - DllStackPanel.Children.Add(dllControl); // 将控件添加到界面上显示 - } #endregion - #region 左侧功能区拖拽到区域 - private void ConditionNodeControl_Drop(object sender, DragEventArgs e) - { - - } + #region 与流程图相关的拖拽操作 /// /// 基础节点的拖拽放置创建 @@ -852,65 +788,11 @@ namespace Serein.WorkBench if (sender is UserControl control) { // 创建一个 DataObject 用于拖拽操作,并设置拖拽效果 - var dragData = new DataObject(MouseNodeType.BaseNodeType, control.GetType()); + var dragData = new DataObject(MouseNodeType.CreateBaseNodeInCanvas, control.GetType()); DragDrop.DoDragDrop(control, dragData, DragDropEffects.Move); } } - private void ConditionRegionControl_Drop(object sender, DragEventArgs e) - { - //if (e.Data.GetDataPresent(MouseNodeType.DllNodeType)) - //{ - // MethodDetails methodDetails = e.Data.GetData(MouseNodeType.DllNodeType) as MethodDetails; - // if (methodDetails == null) return; - // var droppedType = methodDetails.MInstance.GetType(); - // ICondition condition = (ICondition)Activator.CreateInstance(droppedType); - // var baseNode = new SingleConditionNode(condition);// 放置新的节点 - // var node = new ConditionNodeControl(baseNode) - // { - // DataContext = droppedType, - // Header = methodDetails.MethodName, - // }; - // baseNode.MethodDetails = methodDetails; - - // ConditionRegionControl.AddCondition(baseNode); - //} - } - - private void ActionRegionControl_Drop(object sender, DragEventArgs e) - { - //if (e.Data.GetDataPresent(MouseNodeType.DllNodeType)) - //{ - // MethodDetails methodDetails = e.Data.GetData(MouseNodeType.DllNodeType) as MethodDetails; - // if (methodDetails == null) return; - // var droppedType = methodDetails.MInstance.GetType(); - // IAction action = (IAction)Activator.CreateInstance(droppedType); - // var baseNode = new SingleActionNode(action);// 放置新的节点 - // baseNode.MethodDetails = methodDetails; - - // ActionRegionControl.AddAction(baseNode, false); - - //} - } - - private void StateRegionControl_Drop(object sender, DragEventArgs e) - { - // 处理区域的拖拽 - } - - private void RegionControl_PreviewMouseMove(object sender, MouseEventArgs e) - { - if (sender is UserControl control) - { - // 创建一个 DataObject 用于拖拽操作,并设置拖拽效果 - var dragData = new DataObject(MouseNodeType.RegionType, control.GetType()); - DragDrop.DoDragDrop(control, dragData, DragDropEffects.Move); - } - } - #endregion - - #region 与流程图相关的拖拽操作 - /// /// 放置操作,根据拖放数据创建相应的控件,并处理相关操作 /// @@ -918,114 +800,36 @@ namespace Serein.WorkBench /// private void FlowChartCanvas_Drop(object sender, DragEventArgs e) { - NodeControlBase? nodeControl = null; - Point dropPosition = e.GetPosition(FlowChartCanvas); - - if (e.Data.GetDataPresent(MouseNodeType.RegionType)) // 拖动左侧功能区的区域控件 + canvasDropPosition = e.GetPosition(FlowChartCanvas); // 更新画布落点 + if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas)) { - if (e.Data.GetData(MouseNodeType.RegionType) is Type type) + if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData) { - nodeControl = CreateNodeForRegionType(type); + // 创建DLL文件的节点对象 + FlowEnvironment.CreateNode(nodeData.NodeControlType, nodeData.MethodDetails); } } - else if (e.Data.GetDataPresent(MouseNodeType.BaseNodeType)) // 基础控件 + else if (e.Data.GetDataPresent(MouseNodeType.CreateBaseNodeInCanvas)) { - if (e.Data.GetData(MouseNodeType.BaseNodeType) is Type type) + if (e.Data.GetData(MouseNodeType.CreateBaseNodeInCanvas) is Type droppedType) { - nodeControl = CreateNodeForBase(type); + NodeControlType nodeControlType = droppedType switch + { + Type when typeof(ConditionRegionControl).IsAssignableFrom(droppedType) => NodeControlType.ConditionRegion, // 条件区域 + Type when typeof(ConditionNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpCondition, + Type when typeof(ExpOpNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpOp, + _ => NodeControlType.None, + }; + if(nodeControlType != NodeControlType.None) + { + // 创建基础节点对象 + FlowEnvironment.CreateNode(nodeControlType); + } } } - else if (e.Data.GetDataPresent(MouseNodeType.DllNodeType)) // 拖动dll的控件 - { - if(e.Data.GetData(MouseNodeType.DllNodeType) is MethodDetails methodDetails) - { - nodeControl = CreateNodeForMethodDetails(methodDetails); // 创建新节点 - } - } - - if (nodeControl != null) - { - // 首先判断是否为区域,如果是,则尝试将节点放置在区域中 - if (TryPlaceNodeInRegion(nodeControl, dropPosition, e)) - { - return; - } - else - { - // 放置在画布上 - PlaceNodeOnCanvas(nodeControl, dropPosition); - } - } - e.Handled = true; } - /// - /// 拖拽创建区域 - /// - /// - /// - private NodeControlBase? CreateNodeForRegionType(Type droppedType) - { - return droppedType switch - { - Type when typeof(ConditionRegionControl).IsAssignableFrom(droppedType) - => CreateNodeControl(), // 条件区域 - - //Type when typeof(CompositeActionNode).IsAssignableFrom(droppedType) - // => CreateNodeControl(), // 动作区域 - - _ => throw new NotImplementedException("非预期的区域类型"), - }; - - } - /// - /// 拖拽创建来自基础节点 - /// - /// - /// - private NodeControlBase? CreateNodeForBase(Type droppedType) - { - return droppedType switch - { - Type when typeof(ConditionNodeControl).IsAssignableFrom(droppedType) - => CreateNodeControl(), // 条件控件 - Type when typeof(ExpOpNodeControl).IsAssignableFrom(droppedType) - => CreateNodeControl(), // 操作表达式控件 - _ => throw new NotImplementedException("非预期的基础节点类型"), - }; - } - - /// - /// 拖拽创建来自DLL的节点 - /// - /// - /// - private NodeControlBase? CreateNodeForMethodDetails(MethodDetails methodDetails) - { - - NodeControlBase control = methodDetails.MethodDynamicType switch - { - //DynamicNodeType.Condition => CreateNodeControl(typeof(SingleConditionNode), methodDetails), // 单个条件控件 - Serein.Library.Enums.NodeType.Action => CreateNodeControl(methodDetails),// 单个动作控件 - Serein.Library.Enums.NodeType.Flipflop => CreateNodeControl(methodDetails), // 单个动作控件 - _ => throw new NotImplementedException("非预期的Dll节点类型"), - }; - - // 如果是触发器,则需要添加到集合中 - if (control is FlipflopNodeControl flipflopNodeControl && flipflopNodeControl.ViewModel.Node is SingleFlipflopNode flipflopNode) - { - var guid = flipflopNode.Guid; - if (!flipflopNodes.Exists(it => it.Guid.Equals(guid))) - { - flipflopNodes.Add(flipflopNode); - } - } - return control; - } - - - /// /// 尝试将节点放置在区域中 /// @@ -1033,21 +837,18 @@ namespace Serein.WorkBench /// /// /// - private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, Point dropPosition, DragEventArgs e) + private bool TryPlaceNodeInRegion(NodeControlBase nodeControl) { - HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, dropPosition); + HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, canvasDropPosition); if (hitTestResult != null && hitTestResult.VisualHit is UIElement hitElement) { - var data = e.Data.GetData(MouseNodeType.BaseNodeType); - if(data is null) - { - return false; - } - if (data == typeof(ConditionNodeControl)) + // 准备放置条件表达式控件 + if (nodeControl.ViewModel.Node.ControlType == NodeControlType.ExpCondition) { ConditionRegionControl conditionRegion = GetParentOfType(hitElement); if (conditionRegion != null) { + // 如果存在条件区域容器 conditionRegion.AddCondition(nodeControl); return true; } @@ -1055,31 +856,9 @@ namespace Serein.WorkBench } return false; } - /// - /// 在画布上放置节点 - /// - /// - /// - private void PlaceNodeOnCanvas(NodeControlBase nodeControl, Point dropPosition) - { - if (flowStartBlock == null) - { - SetIsStartBlock(nodeControl); - } - - Canvas.SetLeft(nodeControl, dropPosition.X); - Canvas.SetTop(nodeControl, dropPosition.Y); - FlowChartCanvas.Children.Add(nodeControl); - nodeControls.Add(nodeControl); - - ConfigureContextMenu(nodeControl); // 配置右键菜单 - ConfigureNodeEvents(nodeControl);// 配置节点UI相关的事件 - AdjustCanvasSize(nodeControl); // 更新画布 - } - /// - /// 判断当前元素是否是泛型类型 + /// 穿透元素获取区域容器 /// /// /// @@ -1090,19 +869,15 @@ namespace Serein.WorkBench { if (element is T) { - return element as T; - } element = VisualTreeHelper.GetParent(element); } - return null; - } - + /// /// 拖动效果,根据拖放数据是否为指定类型设置拖放效果 /// @@ -1110,9 +885,8 @@ namespace Serein.WorkBench /// private void FlowChartCanvas_DragOver(object sender, DragEventArgs e) { - if (e.Data.GetDataPresent(MouseNodeType.RegionType) - || e.Data.GetDataPresent(MouseNodeType.DllNodeType) - || e.Data.GetDataPresent(MouseNodeType.BaseNodeType)) + if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas) + || e.Data.GetDataPresent(MouseNodeType.CreateBaseNodeInCanvas)) { e.Effects = DragDropEffects.Move; } @@ -1141,7 +915,7 @@ namespace Serein.WorkBench /// private void Block_MouseMove(object sender, MouseEventArgs e) { - if (IsControlDragging) + if (IsControlDragging) // 如果正在拖动控件 { Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置 // 获取引发事件的控件 @@ -1173,217 +947,7 @@ namespace Serein.WorkBench } } - /// - /// 调整FlowChartCanvas的尺寸,确保显示所有控件。 - /// - private void AdjustCanvasSize(UIElement element) - { - double right = Canvas.GetLeft(element) + ((FrameworkElement)element).ActualWidth; - double bottom = Canvas.GetTop(element) + ((FrameworkElement)element).ActualHeight; - - bool adjusted = false; - - // 如果控件超出了FlowChartCanvas的宽度或高度,调整FlowChartCanvas的尺寸 - if (right > FlowChartCanvas.Width) - { - FlowChartCanvas.Width = right + 20; // 添加一些额外的缓冲空间 - adjusted = true; - } - - if (bottom > FlowChartCanvas.Height) - { - FlowChartCanvas.Height = bottom + 20; // 添加一些额外的缓冲空间 - adjusted = true; - } - - // 如果没有调整,则确保FlowChartCanvas的尺寸不小于ScrollViewer的可见区域 - if (!adjusted) - { - // 确保 FlowChartCanvas 的最小尺寸 - var scrollViewerViewportWidth = FlowChartStackPanel.ViewportWidth; - var scrollViewerViewportHeight = FlowChartStackPanel.ViewportHeight; - - if (FlowChartCanvas.Width < scrollViewerViewportWidth) - { - FlowChartCanvas.Width = scrollViewerViewportWidth; - } - - if (FlowChartCanvas.Height < scrollViewerViewportHeight) - { - FlowChartCanvas.Height = scrollViewerViewportHeight; - } - } - } - - /// - /// 开始创建连接 True线 操作,设置起始块和绘制连接线。 - /// - private void StartConnection(NodeControlBase startBlock, ConnectionType connectionType) - { - var tf = connections.FirstOrDefault(it => it.Start == startBlock)?.Type; - - /*if (!TorF && startBlock.Node.MethodDetails.MethodDynamicType == DynamicNodeType.Action) - { - MessageBox.Show($"动作节点不允许存在假分支"); - return; - }*/ - - IsConnecting = true; - currentConnectionType = connectionType; - startConnectBlock = startBlock; - - // 确保起点和终点位置的正确顺序 - currentLine = new Line - { - Stroke = connectionType == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) - : connectionType == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) - : connectionType == ConnectionType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B")) - : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")), - StrokeDashArray = new DoubleCollection([2]), - StrokeThickness = 2, - X1 = Canvas.GetLeft(startConnectBlock) + startConnectBlock.ActualWidth / 2, - Y1 = Canvas.GetTop(startConnectBlock) + startConnectBlock.ActualHeight / 2, - X2 = Canvas.GetLeft(startConnectBlock) + startConnectBlock.ActualWidth / 2, // 初始时终点与起点重合 - Y2 = Canvas.GetTop(startConnectBlock) + startConnectBlock.ActualHeight / 2, - }; - - FlowChartCanvas.Children.Add(currentLine); - //FlowChartCanvas.MouseMove += FlowChartCanvas_MouseMove; - this.KeyDown += MainWindow_KeyDown; - } - - - /// - /// 在画布中按下鼠标左键 - /// - /// - /// - private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) - { - if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) - { - IsSelectControl = true; - - // 开始选取时,记录鼠标起始点 - startPoint = e.GetPosition(FlowChartCanvas); - - // 初始化选取矩形的位置和大小 - Canvas.SetLeft(SelectionRectangle, startPoint.X); - Canvas.SetTop(SelectionRectangle, startPoint.Y); - SelectionRectangle.Width = 0; - SelectionRectangle.Height = 0; - - // 显示选取矩形 - SelectionRectangle.Visibility = Visibility.Visible; - - // 捕获鼠标,以便在鼠标移动到Canvas外部时仍能处理事件 - FlowChartCanvas.CaptureMouse(); - } - } - /// - /// 在画布中释放鼠标按下,结束选取状态 - /// - /// - /// - private void FlowChartCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) - { - if (IsSelectControl) - { - - IsSelectControl = false; - // 释放鼠标捕获 - FlowChartCanvas.ReleaseMouseCapture(); - - // 隐藏选取矩形(如果需要保持选取状态,可以删除此行) - SelectionRectangle.Visibility = Visibility.Collapsed; - - // 处理选取区域内的元素(例如,获取选取范围内的控件) - Rect selectionArea = new Rect(Canvas.GetLeft(SelectionRectangle), - Canvas.GetTop(SelectionRectangle), - SelectionRectangle.Width, - SelectionRectangle.Height); - - - selectControls.Clear(); - // 在此处处理选取的逻辑 - foreach (UIElement element in FlowChartCanvas.Children) - { - Rect elementBounds = new Rect(Canvas.GetLeft(element), Canvas.GetTop(element), - element.RenderSize.Width, element.RenderSize.Height); - - if (selectionArea.Contains(elementBounds)) - { - // 选中元素,执行相应操作 - if (element is NodeControlBase control) - { - selectControls.Add(control); - } - } - } - Console.WriteLine($"一共选取了{selectControls.Count}个控件"); - } - - } - /// - /// 鼠标在画布移动。 - /// 选择控件状态下,调整选择框大小 - /// 连接状态下,实时更新连接线的终点位置。 - /// 移动画布状态下,移动画布。 - /// - private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e) - { - if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) - { - // 获取当前鼠标位置 - Point currentPoint = e.GetPosition(FlowChartCanvas); - - // 更新选取矩形的位置和大小 - double x = Math.Min(currentPoint.X, startPoint.X); - double y = Math.Min(currentPoint.Y, startPoint.Y); - double width = Math.Abs(currentPoint.X - startPoint.X); - double height = Math.Abs(currentPoint.Y - startPoint.Y); - - Canvas.SetLeft(SelectionRectangle, x); - Canvas.SetTop(SelectionRectangle, y); - SelectionRectangle.Width = width; - SelectionRectangle.Height = height; - } - - if (IsConnecting) - { - Point position = e.GetPosition(FlowChartCanvas); - if(currentLine == null || startConnectBlock == null) - { - return; - } - currentLine.X1 = Canvas.GetLeft(startConnectBlock) + startConnectBlock.ActualWidth / 2; - currentLine.Y1 = Canvas.GetTop(startConnectBlock) + startConnectBlock.ActualHeight / 2; - currentLine.X2 = position.X; - currentLine.Y2 = position.Y; - } - if (IsCanvasDragging) - { - Point currentMousePosition = e.GetPosition(this); - double deltaX = currentMousePosition.X - startPoint.X; - double deltaY = currentMousePosition.Y - startPoint.Y; - - translateTransform.X += deltaX; - translateTransform.Y += deltaY; - - startPoint = currentMousePosition; - - // AdjustCanvasSizeAndContent(deltaX, deltaY); - - - foreach (var line in connections) - { - line.Refresh(); - } - - e.Handled = true; // 防止事件传播影响其他控件 - } - - } + #region UI连接控件操作 /// /// 控件的鼠标左键松开事件,结束拖动操作,创建连线 @@ -1396,6 +960,16 @@ namespace Serein.WorkBench ((UIElement)sender).ReleaseMouseCapture(); // 释放鼠标捕获 } else if (IsConnecting) + { + var formNodeGuid = startConnectNodeControl?.ViewModel.Node.Guid; + var toNodeGuid = (sender as NodeControlBase)?.ViewModel.Node.Guid; + if (string.IsNullOrEmpty(formNodeGuid) || string.IsNullOrEmpty(toNodeGuid)) + { + return; + } + FlowEnvironment.ConnectNode(formNodeGuid, toNodeGuid, currentConnectionType); + } + /*else if (IsConnecting) { bool isRegion = false; NodeControlBase? targetBlock; @@ -1439,7 +1013,7 @@ namespace Serein.WorkBench { var connection = new Connection { Start = startConnectBlock, End = targetBlock, Type = currentConnectionType }; - + if (currentConnectionType == ConnectionType.IsSucceed) { startConnectBlock.ViewModel.Node.SucceedBranch.Add(targetBlock.ViewModel.Node); @@ -1460,12 +1034,12 @@ namespace Serein.WorkBench // 保存连接关系 BsControl.Draw(FlowChartCanvas, connection); ConfigureLineContextMenu(connection); - + targetBlock.ViewModel.Node.PreviousNodes.Add(startConnectBlock.ViewModel.Node); // 将当前发起连接的节点,添加到被连接的节点的上一节点队列。(用于回溯) connections.Add(connection); } EndConnection(); - } + }*/ } /// @@ -1486,9 +1060,7 @@ namespace Serein.WorkBench private void EndConnection() { IsConnecting = false; - startConnectBlock = null; - //FlowChartCanvas.MouseMove -= FlowChartCanvas_MouseMove; - + startConnectNodeControl = null; // 移除虚线 if (currentLine != null) { @@ -1502,7 +1074,7 @@ namespace Serein.WorkBench /// private void UpdateConnections(UserControl block) { - foreach (var connection in connections) + foreach (var connection in Connections) { if (connection.Start == block || connection.End == block) { @@ -1510,199 +1082,142 @@ namespace Serein.WorkBench } } } - - + #endregion + #region 画布中框选节点控件动作 /// - /// 删除该控件,以及与该控件相关的所有连线 + /// 在画布中尝试选取控件 /// - /// - private void DeleteBlock(NodeControlBase nodeControl) + /// + /// + private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { - if (nodeControl.ViewModel.Node.IsStart) + if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) { - if (nodeControls.Count > 1) + IsSelectControl = true; + + // 开始选取时,记录鼠标起始点 + startPoint = e.GetPosition(FlowChartCanvas); + + // 初始化选取矩形的位置和大小 + Canvas.SetLeft(SelectionRectangle, startPoint.X); + Canvas.SetTop(SelectionRectangle, startPoint.Y); + SelectionRectangle.Width = 0; + SelectionRectangle.Height = 0; + + // 显示选取矩形 + SelectionRectangle.Visibility = Visibility.Visible; + + // 捕获鼠标,以便在鼠标移动到Canvas外部时仍能处理事件 + FlowChartCanvas.CaptureMouse(); + } + } + + /// + /// 在画布中释放鼠标按下,结束选取状态 + /// + /// + /// + private void FlowChartCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (IsSelectControl) + { + + IsSelectControl = false; + // 释放鼠标捕获 + FlowChartCanvas.ReleaseMouseCapture(); + + // 隐藏选取矩形(如果需要保持选取状态,可以删除此行) + SelectionRectangle.Visibility = Visibility.Collapsed; + + // 处理选取区域内的元素(例如,获取选取范围内的控件) + Rect selectionArea = new Rect(Canvas.GetLeft(SelectionRectangle), + Canvas.GetTop(SelectionRectangle), + SelectionRectangle.Width, + SelectionRectangle.Height); + + + selectControls.Clear(); + // 在此处处理选取的逻辑 + foreach (UIElement element in FlowChartCanvas.Children) + { + Rect elementBounds = new Rect(Canvas.GetLeft(element), Canvas.GetTop(element), + element.RenderSize.Width, element.RenderSize.Height); + + if (selectionArea.Contains(elementBounds)) + { + // 选中元素,执行相应操作 + if (element is NodeControlBase control) + { + selectControls.Add(control); + } + } + } + Console.WriteLine($"一共选取了{selectControls.Count}个控件"); + } + } + + /// + /// 鼠标在画布移动。 + /// 选择控件状态下,调整选择框大小 + /// 连接状态下,实时更新连接线的终点位置。 + /// 移动画布状态下,移动画布。 + /// + private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e) + { + if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) + { + // 获取当前鼠标位置 + Point currentPoint = e.GetPosition(FlowChartCanvas); + + // 更新选取矩形的位置和大小 + double x = Math.Min(currentPoint.X, startPoint.X); + double y = Math.Min(currentPoint.Y, startPoint.Y); + double width = Math.Abs(currentPoint.X - startPoint.X); + double height = Math.Abs(currentPoint.Y - startPoint.Y); + + Canvas.SetLeft(SelectionRectangle, x); + Canvas.SetTop(SelectionRectangle, y); + SelectionRectangle.Width = width; + SelectionRectangle.Height = height; + } + + if (IsConnecting) + { + Point position = e.GetPosition(FlowChartCanvas); + if (currentLine == null || startConnectNodeControl == null) { - MessageBox.Show("若存在其它控件时,起点控件不能删除"); return; } - flowStartBlock = null; + currentLine.X1 = Canvas.GetLeft(startConnectNodeControl) + startConnectNodeControl.ActualWidth / 2; + currentLine.Y1 = Canvas.GetTop(startConnectNodeControl) + startConnectNodeControl.ActualHeight / 2; + currentLine.X2 = position.X; + currentLine.Y2 = position.Y; } - var RemoveEonnections = connections.Where(c => c.Start.ViewModel.Node.Guid.Equals(nodeControl.ViewModel.Node.Guid) - || c.End.ViewModel.Node.Guid.Equals(nodeControl.ViewModel.Node.Guid)).ToList(); - - Remove(RemoveEonnections, nodeControl.ViewModel.Node); - // 删除控件 - FlowChartCanvas.Children.Remove(nodeControl); - nodeControls.Remove(nodeControl); - - - } - /// - /// 移除控件连接关系 - /// - /// - /// - private void Remove(List connections, NodeBase targetNode) - { - if (connections.Count == 0) + if (IsCanvasDragging) { - return; - } - var tempArr = connections.ToArray(); - foreach (var connection in tempArr) - { - var startNode = connection.Start.ViewModel.Node; - var endNode = connection.End.ViewModel.Node; - bool IsStartInThisConnection = false; - // 要删除的节点(targetNode),在连接关系中是否为起点 - // 如果是,则需要从 targetNode 中删除子节点。 - // 如果不是,则需要从连接关系中的起始节点删除 targetNode 。 - if (startNode.Guid.Equals(targetNode.Guid)) + Point currentMousePosition = e.GetPosition(this); + double deltaX = currentMousePosition.X - startPoint.X; + double deltaY = currentMousePosition.Y - startPoint.Y; + + translateTransform.X += deltaX; + translateTransform.Y += deltaY; + + startPoint = currentMousePosition; + + // AdjustCanvasSizeAndContent(deltaX, deltaY); + + + foreach (var line in Connections) { - IsStartInThisConnection = true; + line.Refresh(); } - if (connection.Type == ConnectionType.IsSucceed) - { - if (IsStartInThisConnection) - { - targetNode.SucceedBranch.Remove(endNode); - } - else - { - startNode.SucceedBranch.Remove(targetNode); - } - } - else if (connection.Type == ConnectionType.IsFail) - { - if (IsStartInThisConnection) - { - targetNode.FailBranch.Remove(endNode); - } - else - { - startNode.FailBranch.Remove(targetNode); - } - } - else if (connection.Type == ConnectionType.IsError) - { - if (IsStartInThisConnection) - { - targetNode.ErrorBranch.Remove(endNode); - } - else - { - startNode.ErrorBranch.Remove(targetNode); - } - } - else if (connection.Type == ConnectionType.Upstream) - { - if (IsStartInThisConnection) - { - targetNode.UpstreamBranch.Remove(endNode); - } - else - { - endNode.UpstreamBranch.Remove(targetNode); - } - } - - connection.RemoveFromCanvas(FlowChartCanvas); - connections.Remove(connection); - - if (startNode is SingleFlipflopNode singleFlipflopNode) - { - flipflopNodes.Remove(singleFlipflopNode); - } - } - } - - /// - /// 设为起点 - /// - /// - /// - private void SetIsStartBlock(NodeControlBase nodeControl) - { - if (nodeControl == null) { return; } - if (flowStartBlock != null) - { - flowStartBlock.ViewModel.Node.IsStart = false; - flowStartBlock.BorderBrush = Brushes.Black; - flowStartBlock.BorderThickness = new Thickness(0); + e.Handled = true; // 防止事件传播影响其他控件 } - nodeControl.ViewModel.Node.IsStart = true; - nodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")); - nodeControl.BorderThickness = new Thickness(2); - - flowStartBlock = nodeControl; - } - - - /// - /// 查看返回类型(树形结构展开类型的成员) - /// - /// - private void DisplayReturnTypeTreeViewer(Type type) - { - try - { - var typeViewerWindow = new TypeViewerWindow - { - Type = type, - }; - typeViewerWindow.LoadTypeInformation(); - typeViewerWindow.Show(); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - } - - /// - /// 删除该连线 - /// - /// - private void DeleteConnection(Connection connection) - { - var connectionToRemove = connection; - if (connectionToRemove == null) - { - return; - } - // 获取起始节点与终止节点,消除映射关系 - var StartNode = connectionToRemove.Start.ViewModel.Node; - var EndNode = connectionToRemove.End.ViewModel.Node; - - if (connectionToRemove.Type == ConnectionType.IsSucceed) - { - StartNode.SucceedBranch.Remove(EndNode); - } - else if (connectionToRemove.Type == ConnectionType.IsFail) - { - StartNode.FailBranch.Remove(EndNode); - } - else if (connectionToRemove.Type == ConnectionType.IsError) - { - StartNode.ErrorBranch.Remove(EndNode); - } - - - EndNode.PreviousNodes.Remove(StartNode); - - - - if (connectionToRemove != null) - { - connectionToRemove.RemoveFromCanvas(FlowChartCanvas); - connections.Remove(connectionToRemove); - } - } - - + } + #endregion #region 拖动画布实现缩放平移效果 private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e) { @@ -1852,7 +1367,7 @@ namespace Serein.WorkBench /// private void UnloadAllButton_Click(object sender, RoutedEventArgs e) { - UnloadAllAssemblies(); + FlowEnvironment.ClearAll(); } /// /// 卸载DLL文件,清空当前项目 @@ -1861,21 +1376,15 @@ namespace Serein.WorkBench /// private void UnloadAllAssemblies() { - loadedAssemblies.Clear(); - loadedAssemblyPaths.Clear(); DllStackPanel.Children.Clear(); FlowChartCanvas.Children.Clear(); - - connections.Clear(); + Connections.Clear(); + NodeControls.Clear(); currentLine = null; - flowStartBlock = null; - startConnectBlock = null; + startConnectNodeControl = null; MessageBox.Show("所有DLL已卸载。", "信息", MessageBoxButton.OK, MessageBoxImage.Information); } - - - /// /// 运行测试 /// @@ -1884,11 +1393,7 @@ namespace Serein.WorkBench private async void ButtonDebugRun_Click(object sender, RoutedEventArgs e) { logWindow?.Show(); - var nodes = nodeControls.Select(control => control.ViewModel.Node).ToList(); - var methodDetails = DictMethodDetail.Values.ToList(); - nodeFlowStarter ??= new NodeFlowStarter(ServiceContainer, methodDetails); - await nodeFlowStarter.RunAsync(nodes); - WriteLog("----------------\r\n"); + await FlowEnvironment.StartAsync(); } /// @@ -1898,7 +1403,7 @@ namespace Serein.WorkBench /// private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e) { - nodeFlowStarter?.Exit(); + FlowEnvironment.Exit(); } /// @@ -1910,8 +1415,63 @@ namespace Serein.WorkBench /// private void ButtonSaveFile_Click(object sender, RoutedEventArgs e) { + var projectData = FlowEnvironment.SaveProject(); + projectData.Basic = new Basic + { + canvas = new FlowCanvas + { + lenght = (float)FlowChartCanvas.Width, + width = (float)FlowChartCanvas.Height, + }, + versions = "1", + }; + + foreach(var node in projectData.Nodes) + { + var control = new ActionNodeControl(null);// GetControl(node.Guid); + Point positionRelativeToParent = control.TranslatePoint(new Point(0, 0), FlowChartCanvas); + + node.Position = new Position + { + X = (float)positionRelativeToParent.X, + Y = (float)positionRelativeToParent.Y, + }; + } + var projectJsonData = JArray.FromObject(projectData); + var savePath = SaveContentToFile(projectJsonData.ToString()); + savePath = System.IO.Path.GetDirectoryName(savePath); + + // 复制dll文件 + //if (string.IsNullOrEmpty(savePath)) + //{ + // return; + //} + //foreach (var dll in loadedAssemblies) + //{ + // try + // { + + // string targetPath = System.IO.Path.Combine(savePath, System.IO.Path.GetFileName(dll.CodeBase)); + + + // // 确保目标目录存在 + // Directory.CreateDirectory(savePath); + + // var sourceFile = new Uri(dll.CodeBase).LocalPath; + + // // 复制文件到目标目录 + // File.Copy(sourceFile, targetPath, true); + // } + // catch (Exception ex) + // { + // WriteLog($"DLL复制失败:{dll.CodeBase} \r\n错误:{ex}\r\n"); + // } + //} + +/* try { + // 生成节点信息 var nodeInfos = nodeControls.Select(control => { @@ -1967,19 +1527,19 @@ namespace Serein.WorkBench return new NodeInfo { - guid = node.Guid, - name = node.MethodDetails?.MethodName, - label = node.DisplayName ?? "", - type = node.GetType().ToString(), - position = new Position + Guid = node.Guid, + Name = node.MethodDetails?.MethodName, + Label = node.DisplayName ?? "", + Type = node.GetType().ToString(), + Position = new Position { x = (float)positionRelativeToParent.X, y = (float)positionRelativeToParent.Y, }, - trueNodes = trueNodes.ToArray(), - falseNodes = falseNodes.ToArray(), - upstreamNodes = upstreamNodes.ToArray(), - parameterData = parameterData.ToArray(), + TrueNodes = trueNodes.ToArray(), + FalseNodes = falseNodes.ToArray(), + UpstreamNodes = upstreamNodes.ToArray(), + ParameterData = parameterData.ToArray(), }; }).ToList(); @@ -2023,8 +1583,6 @@ namespace Serein.WorkBench } else if (region is ActionRegionControl && region.ViewModel.Node is CompositeActionNode actionRegion) // 动作区域控件 { - //WriteLog(region.Node.GetType().ToString() + "\r\n"); - List childNodes = []; var tmpChildNodes = actionRegion.ActionNodes; foreach (var node in tmpChildNodes) @@ -2078,7 +1636,6 @@ namespace Serein.WorkBench relativePath = GetRelativePath(App.FileDataPath, filePath); } - var result = new { name = temp.Name, @@ -2092,23 +1649,24 @@ namespace Serein.WorkBench { ["basic"] = new JObject { - ["canvas"] = new JObject + ["Canvas"] = new JObject { - ["width"] = FlowChartCanvas.Width, - ["lenght"] = FlowChartCanvas.Height, + ["Width"] = FlowChartCanvas.Width, + ["Lenght"] = FlowChartCanvas.Height, }, - ["versions"] = "1", + ["Versions"] = "1", }, - ["library"] = JArray.FromObject(dlls), - ["startNode"] = flowStartBlock == null ? "" : flowStartBlock.ViewModel.Node.Guid, - ["nodes"] = JArray.FromObject(nodeInfos), - ["regions"] = JArray.FromObject(regionObjs), + ["Librarys"] = JArray.FromObject(dlls), + ["StartNode"] = flowStartBlock?.ViewModel.Node.Guid, + ["Nodes"] = JArray.FromObject(nodeInfos), + ["Regions"] = JArray.FromObject(regionObjs), }; // WriteLog(keyValuePairs.ToString()); var savePath = SaveContentToFile(keyValuePairs.ToString()); savePath = System.IO.Path.GetDirectoryName(savePath); + // 复制dll文件 if (string.IsNullOrEmpty(savePath)) { return; @@ -2134,40 +1692,11 @@ namespace Serein.WorkBench WriteLog($"DLL复制失败:{dll.CodeBase} \r\n错误:{ex}\r\n"); } } - /*string filePath = System.IO.Path.Combine(Environment.CurrentDirectory, "project.nf"); - - try - { - // 将文本内容写入文件 - File.WriteAllText(filePath, keyValuePairs.ToString()); - - Console.WriteLine($"文本已成功保存到文件: {filePath}"); - } - catch (Exception ex) - { - Console.WriteLine($"保存文件时出现错误: {ex.Message}"); - }*/ - - /*if (item is SingleActionNode) - { - } - else if (item is SingleConditionNode) - { - - } - else if (item is CompositeActionNode) - { - - } - else if (item is CompositeConditionNode) - { - - }*/ } catch (Exception ex) { Debug.Write(ex.Message); - } + }*/ } public static string? SaveContentToFile(string content) @@ -2212,10 +1741,7 @@ namespace Serein.WorkBench return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar)); } - private void Window_SizeChanged(object sender, SizeChangedEventArgs e) - { - // AdjustCanvasSize(); - } + } diff --git a/WorkBench/MainWindowViewModel.cs b/WorkBench/MainWindowViewModel.cs new file mode 100644 index 0000000..c22f689 --- /dev/null +++ b/WorkBench/MainWindowViewModel.cs @@ -0,0 +1,81 @@ +using Serein.Library.Attributes; +using Serein.Library.Entity; +using Serein.Library.Utils; +using Serein.NodeFlow; +using Serein.NodeFlow.Tool; +using Serein.WorkBench.Node.View; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace Serein.WorkBench +{ + public class MainWindowViewModel + { + private readonly MainWindow window ; + public MainWindowViewModel(MainWindow window) + { + FlowEnvironment = new FlowEnvironment(); + this.window = window; + } + + public FlowEnvironment FlowEnvironment { get; set; } + + + #region 加载项目文件 + public void LoadProjectFile(SereinOutputFileData projectFile) + { + var dllPaths = projectFile.Librarys.Select(it => it.Path).ToList(); + foreach (var dll in dllPaths) + { + var filePath = System.IO.Path.GetFullPath(System.IO.Path.Combine(App.FileDataPath, dll)); + //LoadAssembly(filePath); + } + } + + + + + + private void DisplayControlDll(Assembly assembly, + List conditionTypes, + List actionTypes, + List flipflopMethods) + { + + var dllControl = new DllControl + { + Header = "DLL name : " + assembly.GetName().Name // 设置控件标题为程序集名称 + }; + + + foreach (var item in actionTypes) + { + dllControl.AddAction(item.Clone()); // 添加动作类型到控件 + } + foreach (var item in flipflopMethods) + { + dllControl.AddFlipflop(item.Clone()); // 添加触发器方法到控件 + } + + /*foreach (var item in stateTypes) + { + dllControl.AddState(item); + }*/ + + window.DllStackPanel.Children.Add(dllControl); // 将控件添加到界面上显示 + } + + + + #endregion + + + + } +} diff --git a/WorkBench/Node/View/ActionRegionControl.xaml.cs b/WorkBench/Node/View/ActionRegionControl.xaml.cs index 7b79165..60889d0 100644 --- a/WorkBench/Node/View/ActionRegionControl.xaml.cs +++ b/WorkBench/Node/View/ActionRegionControl.xaml.cs @@ -1,4 +1,5 @@ -using Serein.NodeFlow.Model; +using Serein.NodeFlow; +using Serein.NodeFlow.Model; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; @@ -107,8 +108,19 @@ namespace Serein.WorkBench.Node.View { if (sender is TextBlock typeText) { - var dragData = new DataObject(MouseNodeType.RegionType, typeText.Tag); + MoveNodeData moveNodeData = new MoveNodeData + { + NodeControlType = Library.Enums.NodeControlType.ConditionRegion + }; + + // 创建一个 DataObject 用于拖拽操作,并设置拖拽效果 + DataObject dragData = new DataObject(MouseNodeType.CreateDllNodeInCanvas, moveNodeData); + DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move); + + + //var dragData = new DataObject(MouseNodeType.CreateNodeInCanvas, typeText.Tag); + //DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move); } } } diff --git a/WorkBench/Node/View/DllControlControl.xaml.cs b/WorkBench/Node/View/DllControlControl.xaml.cs index 6007da7..353b2e0 100644 --- a/WorkBench/Node/View/DllControlControl.xaml.cs +++ b/WorkBench/Node/View/DllControlControl.xaml.cs @@ -1,5 +1,8 @@ -using Serein.NodeFlow; +using Serein.Library.Entity; +using Serein.Library.Enums; +using Serein.NodeFlow; using System.Windows; +using System.Windows.Automation; using System.Windows.Controls; using System.Windows.Input; @@ -115,12 +118,26 @@ namespace Serein.WorkBench.Node.View { // 获取触发事件的 TextBlock - TextBlock typeText = sender as TextBlock; - if (typeText != null) + if (sender is TextBlock typeText && typeText.Tag is MethodDetails md) { + MoveNodeData moveNodeData = new MoveNodeData + { + NodeControlType = md.MethodDynamicType switch + { + NodeType.Action => NodeControlType.Action, + NodeType.Flipflop => NodeControlType.Flipflop, + _ => NodeControlType.None, + }, + MethodDetails = md, + }; + if(moveNodeData.NodeControlType == NodeControlType.None) + { + return; + } + // 创建一个 DataObject 用于拖拽操作,并设置拖拽效果 - DataObject dragData = new DataObject(MouseNodeType.DllNodeType, typeText.Tag); + DataObject dragData = new DataObject(MouseNodeType.CreateDllNodeInCanvas, moveNodeData); DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move); } } diff --git a/WorkBench/Node/View/NodeControlBase.cs b/WorkBench/Node/View/NodeControlBase.cs index 54539b4..5a30a67 100644 --- a/WorkBench/Node/View/NodeControlBase.cs +++ b/WorkBench/Node/View/NodeControlBase.cs @@ -1,5 +1,6 @@ -using Serein.NodeFlow; -using Serein.NodeFlow.Model; +using Serein.Library.Api; +using Serein.Library.Entity; +using Serein.NodeFlow.Base; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; @@ -34,7 +35,7 @@ namespace Serein.WorkBench.Node.View public abstract class NodeControlViewModelBase : INotifyPropertyChanged { - public NodeControlViewModelBase(NodeBase node) + public NodeControlViewModelBase(NodeModelBase node) { this.Node = node; MethodDetails = this.Node.MethodDetails; @@ -43,7 +44,7 @@ namespace Serein.WorkBench.Node.View /// /// 对应的节点实体类 /// - public NodeBase Node { get; set; } + public NodeModelBase Node { get; } /// /// 表示节点控件是否被选中 @@ -63,13 +64,7 @@ namespace Serein.WorkBench.Node.View } } - - public event PropertyChangedEventHandler PropertyChanged; - - - - protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { diff --git a/WorkBench/Themes/MethodDetailsControl.xaml.cs b/WorkBench/Themes/MethodDetailsControl.xaml.cs index 4187c45..a971475 100644 --- a/WorkBench/Themes/MethodDetailsControl.xaml.cs +++ b/WorkBench/Themes/MethodDetailsControl.xaml.cs @@ -1,4 +1,5 @@ -using Serein.NodeFlow; +using Serein.Library.Entity; +using Serein.NodeFlow; using System.Collections; using System.Globalization; using System.Windows;