Commit 79eb67e0 authored by shrabbit's avatar shrabbit
Browse files

添加节点序列化和反序列化功能,更新图的读写逻辑,增加测试方法和按钮

parent bc672709
Loading
Loading
Loading
Loading
+482 −6
Original line number Diff line number Diff line
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Nocube.Common;
using Nocube.Common.Nodes;
using Nocube.Common.Properties;

namespace Nocube.Engine.Graphs;

@@ -14,7 +17,6 @@ public class Graph : IName, IDescription
    public Version EngineVersion { get; set; } = null!;
    public Dictionary<string, JsonNode> Metadata { get; private set; } = [];


    /// <summary>
    /// 读取图
    /// </summary>
@@ -40,8 +42,34 @@ public class Graph : IName, IDescription
            Name = meta!["name"]!.GetValue<string>();
            EngineVersion = new Version(meta!["engine"]!.GetValue<string>());

            Flows = flows.Deserialize<List<GraphFlow>>()!;
            Nodes = nodes.Deserialize<List<GraphNodeTree>>()!;
            // 加载流引用(使用标识符)
            var flowRefs = flows.Deserialize<List<GraphFlowRef>>()!;

            // 加载可序列化的节点信息
            var serializableNodes = nodes.Deserialize<List<SerializableGraphNodeTree>>()!;

            // 将可序列化的节点信息转换回实际的 GraphNodeTree
            // 由于我们无法在不知道具体节点类型的情况下创建节点,
            // 我们需要采用一种不同的方法:使用节点索引和属性名称的组合
            // 实际上,我们仍然需要保留原始节点结构,但要能正确序列化
            // 让我们采用一种更简单的方式,将节点的类型信息也序列化
            
            // 为了简化,这里假设我们有一个节点注册表或工厂
            // 在实际实现中,你需要有一个全局的节点类型注册表
            Nodes = ConvertFromSerializable(serializableNodes);

            // 重建属性引用并填充 Flows 列表
            Flows.Clear();
            foreach (var flowRef in flowRefs)
            {
                var fromProp = GetPropertyByReference(flowRef.From);
                var toProp = GetPropertyByReference(flowRef.To);
                
                if (fromProp != null && toProp != null)
                {
                    Flows.Add(new GraphFlow(fromProp, toProp));
                }
            }

            foreach (var data in meta!["data"]!.AsArray())
            {
@@ -51,6 +79,198 @@ public class Graph : IName, IDescription
        });
    }

    /// <summary>
    /// 将可序列化的节点树转换为实际的节点树
    /// </summary>
    /// <param name="serializableTrees">可序列化的节点树列表</param>
    /// <returns>实际的节点树列表</returns>
    private List<GraphNodeTree> ConvertFromSerializable(List<SerializableGraphNodeTree> serializableTrees)
    {
        var result = new List<GraphNodeTree>();
        
        foreach (var serializableTree in serializableTrees)
        {
            var children = ConvertFromSerializable(serializableTree.Children.ToList());
            var nodes = new List<NodeInfo>();
            
            foreach (var serializableNode in serializableTree.Nodes)
            {
                // 创建虚拟节点,实际应用中需要根据类型创建真实节点
                var node = CreateNodeInstance(serializableNode.NodeType, serializableNode);
                if (node != null)
                {
                    nodes.Add(new NodeInfo(node, serializableNode.X, serializableNode.Y));
                }
            }
            
            result.Add(new GraphNodeTree(children.ToArray(), nodes.ToArray()));
        }
        
        return result;
    }

    /// <summary>
    /// 根据类型名称创建节点实例
    /// </summary>
    /// <param name="typeName">节点类型全名</param>
    /// <param name="serializableNode">可序列化的节点信息</param>
    /// <returns>节点实例或null</returns>
    private INode? CreateNodeInstance(string typeName, SerializableNodeInfo serializableNode)
    {
        // 首先尝试通过反射创建节点实例
        try
        {
            var type = Type.GetType(typeName);
            if (type != null && typeof(INode).IsAssignableFrom(type))
            {
                var instance = Activator.CreateInstance(type) as INode;
                if (instance != null)
                {
                    // 这里我们可以尝试设置属性值,但通常节点属性是在构造函数中初始化的
                    return instance;
                }
            }
        }
        catch
        {
            // 如果反射创建失败,则尝试其他方式
        }
        
        // 如果反射失败,尝试根据常见类型手动创建
        // 这里可以扩展以支持更多特定类型的节点创建
        return null;
    }

    /// <summary>
    /// 将实际的节点树转换为可序列化的形式
    /// </summary>
    /// <param name="trees">实际的节点树列表</param>
    /// <returns>可序列化的节点树列表</returns>
    private List<SerializableGraphNodeTree> ConvertToSerializable(List<GraphNodeTree> trees)
    {
        var result = new List<SerializableGraphNodeTree>();
        
        foreach (var tree in trees)
        {
            var children = ConvertToSerializable(tree.Children.ToList()).ToArray();
            var nodes = new List<SerializableNodeInfo>();
            
            foreach (var nodeInfo in tree.Nodes)
            {
                var properties = new List<SerializableProperty>();
                foreach (var property in nodeInfo.Node.Properties)
                {
                    properties.Add(new SerializableProperty(
                        property.Name,
                        property.Description,
                        property.ArrowType.Name,
                        property.ArrowType.FullName ?? property.ArrowType.Name,
                        property.Direct.ToString()
                    ));
                }
                
                nodes.Add(new SerializableNodeInfo(
                    nodeInfo.Node.GetType().FullName ?? nodeInfo.Node.GetType().Name,
                    nodeInfo.Node.Name,
                    nodeInfo.Node.Description,
                    "", // Path 暂时留空
                    nodeInfo.X,
                    nodeInfo.Y,
                    properties
                ));
            }
            
            result.Add(new SerializableGraphNodeTree(children, nodes.ToArray()));
        }
        
        return result;
    }

    /// <summary>
    /// 根据属性引用获取实际的属性对象
    /// </summary>
    /// <param name="reference">属性引用</param>
    /// <returns>属性对象或null</returns>
    private IProperty? GetPropertyByReference(PropertyReference reference)
    {
        // 遍历所有节点树来找到指定索引的节点
        int currentIndex = 0;
        
        foreach (var tree in Nodes)
        {
            foreach (var nodeInfo in tree.Nodes)
            {
                if (currentIndex == reference.NodeIndex)
                {
                    // 找到目标节点,查找指定名称的属性
                    return nodeInfo.Node.Properties.FirstOrDefault(p => p.Name == reference.PropertyName);
                }
                currentIndex++;
            }
        }
        
        return null;
    }

    /// <summary>
    /// 根据属性对象获取其引用
    /// </summary>
    /// <param name="property">属性对象</param>
    /// <param name="node">包含该属性的节点</param>
    /// <returns>属性引用或null</returns>
    private PropertyReference? GetReferenceByProperty(IProperty property, INode node)
    {
        // 找到节点在整个图中的索引
        int nodeIndex = 0;
        bool found = false;
        
        foreach (var tree in Nodes)
        {
            foreach (var nodeInfo in tree.Nodes)
            {
                if (ReferenceEquals(nodeInfo.Node, node))
                {
                    found = true;
                    break;
                }
                nodeIndex++;
            }
            
            if (found)
                break;
        }
        
        if (!found)
            return null;
            
        return new PropertyReference(nodeIndex, property.Name);
    }

    /// <summary>
    /// 根据属性对象找到其所在的节点
    /// </summary>
    /// <param name="property">要查找的属性</param>
    /// <returns>包含该属性的节点或null</returns>
    private INode? FindNodeContainingProperty(IProperty property)
    {
        foreach (var tree in Nodes)
        {
            foreach (var nodeInfo in tree.Nodes)
            {
                // 通过名称等信息匹配属性
                var matchingProp = nodeInfo.Node.Properties.FirstOrDefault(
                    p => p.Name == property.Name 
                         && p.Direct == property.Direct
                         && p.Description == property.Description);
                
                if (matchingProp != null && matchingProp.ArrowType.FullName == property.ArrowType.FullName)
                    return nodeInfo.Node;
            }
        }
        
        return null;
    }

    /// <summary>
    /// 保存图
    /// </summary>
@@ -59,8 +279,30 @@ public class Graph : IName, IDescription
    {
        return await Task.Run(() =>
        {
            var flow = JsonSerializer.SerializeToNode(Flows);
            var node = JsonSerializer.SerializeToNode(Nodes);
            // 将内部的 GraphFlow 转换为 GraphFlowRef 以便序列化
            var flowRefs = new List<GraphFlowRef>();
            foreach (var flow in Flows)
            {
                // 查找包含这两个属性的节点
                var fromNode = FindNodeContainingProperty(flow.From);
                var toNode = FindNodeContainingProperty(flow.To);
                
                if (fromNode != null && toNode != null)
                {
                    var fromRef = GetReferenceByProperty(flow.From, fromNode);
                    var toRef = GetReferenceByProperty(flow.To, toNode);
                    
                    if (fromRef != null && toRef != null)
                    {
                        flowRefs.Add(new GraphFlowRef(fromRef, toRef));
                    }
                }
            }
            
            // 将节点转换为可序列化的形式
            var serializableNodes = ConvertToSerializable(Nodes);
            var flowJ = JsonSerializer.SerializeToNode(flowRefs);
            var node = JsonSerializer.SerializeToNode(serializableNodes);

            // 存储元数据
            var data = new JsonArray();
@@ -79,7 +321,7 @@ public class Graph : IName, IDescription

            var obj = new JsonObject
            {
                ["flows"] = flow,
                ["flows"] = flowJ,
                ["nodes"] = node,
                ["meta"] = meta
            };
@@ -87,4 +329,238 @@ public class Graph : IName, IDescription
            return obj.ToJsonString();
        });
    }

    /// <summary>
    /// 执行图中的节点
    /// </summary>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>异步任务</returns>
    public async Task ExecuteAsync(CancellationToken cancellationToken = default)
    {
        // 执行图中的所有节点
        foreach (var tree in Nodes)
        {
            foreach (var nodeInfo in tree.Nodes)
            {
                if (nodeInfo.Node is IFunction function)
                {
                    await function.Execute(cancellationToken);
                }
            }
        }
    }

    /// <summary>
    /// 传播数据通过图中的连接,只传播类型兼容的数据
    /// </summary>
    public void PropagateData()
    {
        // 遍历所有的流(连接),将数据从源属性传递到目标属性
        foreach (var flow in Flows)
        {
            // 检查源属性是否有数据
            if (flow.From.Data != null)
            {
                // 检查类型兼容性
                if (IsTypeCompatible(flow.From.Data.GetType(), flow.To.ArrowType))
                {
                    // 将源属性的数据传递给目标属性
                    flow.To.Data = flow.From.Data;
                }
            }
        }
    }

    /// <summary>
    /// 检查源类型是否可以安全转换为目标类型
    /// </summary>
    /// <param name="sourceType">源类型</param>
    /// <param name="targetType">目标类型</param>
    /// <returns>如果类型兼容则返回true,否则返回false</returns>
    private bool IsTypeCompatible(Type sourceType, Type targetType)
    {
        // 如果类型完全相同
        if (sourceType == targetType || targetType.IsAssignableFrom(sourceType))
            return true;

        // 处理基本数值类型转换
        if (IsNumericType(sourceType) && IsNumericType(targetType))
            return true;

        // 特殊处理字符串到其他类型的转换
        if (sourceType == typeof(string))
        {
            // 检查目标类型是否可以由字符串解析
            if (targetType == typeof(int) || targetType == typeof(double) || 
                targetType == typeof(float) || targetType == typeof(decimal) ||
                targetType == typeof(bool) || targetType == typeof(long))
                return true;
        }

        // 检查目标类型是否可以由源类型转换
        var convertMethod = targetType.GetMethod("op_Implicit", [sourceType]) ?? 
                           targetType.GetMethod("op_Explicit", [sourceType]);
        return convertMethod != null;
    }

    /// <summary>
    /// 检查类型是否为数值类型
    /// </summary>
    /// <param name="type">要检查的类型</param>
    /// <returns>如果是数值类型返回true,否则返回false</returns>
    private static bool IsNumericType(Type type)
    {
        return type == typeof(int) || type == typeof(uint) || type == typeof(long) || 
               type == typeof(ulong) || type == typeof(short) || type == typeof(ushort) ||
               type == typeof(byte) || type == typeof(sbyte) || type == typeof(float) ||
               type == typeof(double) || type == typeof(decimal);
    }

    /// <summary>
    /// 设置节点属性的值
    /// </summary>
    /// <param name="nodeIndex">节点索引</param>
    /// <param name="propertyName">属性名称</param>
    /// <param name="value">要设置的值</param>
    /// <returns>如果找到属性则返回true,否则返回false</returns>
    public bool SetPropertyData(int nodeIndex, string propertyName, object? value)
    {
        int currentIndex = 0;
        
        foreach (var tree in Nodes)
        {
            foreach (var nodeInfo in tree.Nodes)
            {
                if (currentIndex == nodeIndex)
                {
                    var property = nodeInfo.Node.Properties.FirstOrDefault(p => p.Name == propertyName);
                    if (property != null)
                    {
                        // 检查类型兼容性后再设置
                        if (value == null || IsTypeCompatible(value.GetType(), property.ArrowType))
                        {
                            property.Data = value;
                            return true;
                        }
                    }
                }
                currentIndex++;
            }
        }
        
        return false;
    }

    /// <summary>
    /// 获取节点属性的值
    /// </summary>
    /// <param name="nodeIndex">节点索引</param>
    /// <param name="propertyName">属性名称</param>
    /// <returns>属性值,如果未找到则返回null</returns>
    public object? GetPropertyData(int nodeIndex, string propertyName)
    {
        int currentIndex = 0;
        
        foreach (var tree in Nodes)
        {
            foreach (var nodeInfo in tree.Nodes)
            {
                if (currentIndex == nodeIndex)
                {
                    var property = nodeInfo.Node.Properties.FirstOrDefault(p => p.Name == propertyName);
                    if (property != null)
                    {
                        return property.Data;
                    }
                }
                currentIndex++;
            }
        }
        
        return null;
    }

    /// <summary>
    /// 从指定的入口节点开始执行图
    /// </summary>
    /// <param name="entryNodeIndex">入口节点的索引</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>异步任务</returns>
    public async Task ExecuteFromAsync(int entryNodeIndex, CancellationToken cancellationToken = default)
    {
        // 找到入口节点并执行
        IFunction? entryFunction = null;
        int currentIndex = 0;

        foreach (var tree in Nodes)
        {
            foreach (var nodeInfo in tree.Nodes)
            {
                if (currentIndex == entryNodeIndex)
                {
                    if (nodeInfo.Node is IFunction function)
                    {
                        entryFunction = function;
                        break;
                    }
                }
                currentIndex++;
            }
            
            if (entryFunction != null)
                break;
        }

        if (entryFunction != null)
        {
            await entryFunction.Execute(cancellationToken);
            
            // 传播数据到连接的节点
            PropagateData();
            
            // 可以选择继续执行下游节点
            await ExecuteDownstreamNodes(entryFunction, cancellationToken);
        }
    }

    /// <summary>
    /// 执行下游节点
    /// </summary>
    /// <param name="executedNode">已执行的节点</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>异步任务</returns>
    private async Task ExecuteDownstreamNodes(INode executedNode, CancellationToken cancellationToken)
    {
        // 查找所有从当前节点输出的连接
        var outputFlows = Flows.Where(f => 
            executedNode.Properties.Contains(f.From)).ToList();

        // 对每个连接的目标节点执行操作
        foreach (var flow in outputFlows)
        {
            // 找到目标节点
            foreach (var tree in Nodes)
            {
                foreach (var nodeInfo in tree.Nodes)
                {
                    if (nodeInfo.Node.Properties.Contains(flow.To))
                    {
                        // 传播数据
                        flow.To.Data = flow.From.Data;
                        
                        // 如果目标节点是可执行的函数节点,执行它
                        if (nodeInfo.Node is IFunction downstreamFunction)
                        {
                            await downstreamFunction.Execute(cancellationToken);
                            
                            // 继续传播数据并执行后续节点
                            PropagateData();
                            await ExecuteDownstreamNodes(nodeInfo.Node, cancellationToken);
                        }
                        break;
                    }
                }
            }
        }
    }
}
 No newline at end of file
+17 −0
Original line number Diff line number Diff line
@@ -2,5 +2,22 @@ using Nocube.Common.Properties;

namespace Nocube.Engine.Graphs;

// 可序列化的节点信息
public record SerializableNodeInfo(string NodeType, string NodeName, string NodeDescription, string NodePath, float X, float Y, List<SerializableProperty> Properties);

// 可序列化的属性信息
public record SerializableProperty(string Name, string Description, string TypeName, string TypeFullName, string FlowDirection);

// 可序列化的节点树
public record SerializableGraphNodeTree(SerializableGraphNodeTree[] Children, SerializableNodeInfo[] Nodes);

public record GraphNodeTree(GraphNodeTree[] Children, NodeInfo[] Nodes);

// 定义属性引用标识符
public record PropertyReference(int NodeIndex, string PropertyName);

// 新的流程记录,使用属性引用标识符而非直接引用
public record GraphFlowRef(PropertyReference From, PropertyReference To);

// 保留原来的 GraphFlow 用于内部使用,但不用于序列化
public record GraphFlow(IProperty From, IProperty To);
 No newline at end of file
+178 −3

File changed.

Preview size limit exceeded, changes collapsed.

+4 −1
Original line number Diff line number Diff line
@@ -7,7 +7,10 @@
             x:Class="Nocube.UI.Views.Studio.StudioMainView">
    
    <Grid RowDefinitions="48, *">
        <StackPanel Orientation="Horizontal">
            <Button Click="Button_OnClick">Create Node</Button>
            <Button Click="Button_OnClick2">Test Graph</Button>
        </StackPanel>
        <node:NodifyEditor Grid.Row="1" x:Name="Editor">

        </node:NodifyEditor>
+5 −0
Original line number Diff line number Diff line
@@ -22,4 +22,9 @@ public partial class StudioMainView : UserControl
        list.Add(NodeIfToNode.ToNode(new TestNode()));
        Editor.Items.Add(NodeIfToNode.ToNode(new TestNode()));
    }

    private async void Button_OnClick2(object? sender, RoutedEventArgs e)
    {
        await GraphSerializationTest.TestGraphExecution();
    }
}
 No newline at end of file