Commit 890bff30 authored by shrabbit's avatar shrabbit
Browse files

添加节点注册表和优化测试,更新图和包加载器以支持节点注册和加载(测试)

parent 79eb67e0
Loading
Loading
Loading
Loading
+98 −0
Original line number Diff line number Diff line
using System;
using System.Collections.Generic;

namespace Nocube.Common.Nodes;

/// <summary>
/// 节点注册表,用于管理节点类型和实例创建函数
/// </summary>
public class NodeRegistry
{
    private readonly Dictionary<string, Func<INode>> _nodeFactories = new();
    private readonly Dictionary<string, Type> _nodeTypes = new();

    /// <summary>
    /// 注册节点类型
    /// </summary>
    /// <param name="typeName">节点类型名称</param>
    /// <param name="factory">节点实例创建函数</param>
    public void RegisterNode(string typeName, Func<INode> factory)
    {
        if (!_nodeFactories.ContainsKey(typeName))
        {
            _nodeFactories[typeName] = factory;
        }
    }

    /// <summary>
    /// 注册节点类型
    /// </summary>
    /// <param name="type">节点类型</param>
    public void RegisterNode(Type type)
    {
        if (type != null && typeof(INode).IsAssignableFrom(type))
        {
            var typeName = type.FullName ?? type.Name;
            _nodeTypes[typeName] = type;
            
            if (!_nodeFactories.ContainsKey(typeName))
            {
                _nodeFactories[typeName] = () => Activator.CreateInstance(type) as INode;
            }
        }
    }

    /// <summary>
    /// 根据类型名称获取节点实例
    /// </summary>
    /// <param name="typeName">节点类型名称</param>
    /// <returns>节点实例或null</returns>
    public INode? CreateNode(string typeName)
    {
        if (_nodeFactories.TryGetValue(typeName, out var factory))
        {
            try
            {
                return factory();
            }
            catch
            {
                // 创建失败,返回null
            }
        }

        // 如果工厂创建失败,尝试直接使用类型创建
        if (_nodeTypes.TryGetValue(typeName, out var type))
        {
            try
            {
                return Activator.CreateInstance(type) as INode;
            }
            catch
            {
                // 创建失败,返回null
            }
        }

        return null;
    }

    /// <summary>
    /// 检查是否包含指定类型的节点
    /// </summary>
    /// <param name="typeName">节点类型名称</param>
    /// <returns>如果包含则返回true,否则返回false</returns>
    public bool ContainsNode(string typeName)
    {
        return _nodeFactories.ContainsKey(typeName) || _nodeTypes.ContainsKey(typeName);
    }

    /// <summary>
    /// 获取所有已注册的节点类型名称
    /// </summary>
    /// <returns>节点类型名称列表</returns>
    public IEnumerable<string> GetRegisteredNodeTypes()
    {
        return _nodeFactories.Keys;
    }
}
+63 −17
Original line number Diff line number Diff line
using System;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Generic;
using Nocube.Common;
using Nocube.Common.Nodes;
using Nocube.Common.Packages;
using Nocube.Common.Properties;
using Nocube.Engine.Packages;

namespace Nocube.Engine.Graphs;

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

    public NodeRegistry NodeRegistry { get; } = new();
    public PackageLoader PackageLoader { get; } = new();
    public IEngineContext? EngineContext { get; set; }

    /// <summary>
    /// 构造函数
    /// </summary>
    public Graph()
    {
    }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="engineContext">引擎上下文</param>
    public Graph(IEngineContext engineContext)
    {
        EngineContext = engineContext;
    }

    /// <summary>
    /// 加载所有包
    /// </summary>
    public async Task LoadPackagesAsync()
    {
        if (EngineContext != null)
        {
            await PackageLoader.LoadAllPackagesAsync(NodeRegistry, EngineContext);
        }
    }

    /// <summary>
    /// 读取图
    /// </summary>
    /// <param name="graphJson">图 JSON</param>
    /// <returns></returns>
    public Task ReadAsync(string graphJson)
    public async Task ReadAsync(string graphJson)
    {
        return Task.Run(() =>
        await Task.Run(() =>
        {
            Flows.Clear();
            Nodes.Clear();
@@ -49,13 +86,6 @@ public class Graph : IName, IDescription
            var serializableNodes = nodes.Deserialize<List<SerializableGraphNodeTree>>()!;

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

            // 重建属性引用并填充 Flows 列表
@@ -117,7 +147,14 @@ public class Graph : IName, IDescription
    /// <returns>节点实例或null</returns>
    private INode? CreateNodeInstance(string typeName, SerializableNodeInfo serializableNode)
    {
        // 首先尝试通过反射创建节点实例
        // 首先尝试通过注册表创建节点实例
        var node = NodeRegistry.CreateNode(typeName);
        if (node != null)
        {
            return node;
        }

        // 如果注册表创建失败,尝试通过反射创建节点实例
        try
        {
            var type = Type.GetType(typeName);
@@ -126,7 +163,8 @@ public class Graph : IName, IDescription
                var instance = Activator.CreateInstance(type) as INode;
                if (instance != null)
                {
                    // 这里我们可以尝试设置属性值,但通常节点属性是在构造函数中初始化的
                    // 注册到注册表,以便下次使用
                    NodeRegistry.RegisterNode(type);
                    return instance;
                }
            }
@@ -326,7 +364,11 @@ public class Graph : IName, IDescription
                ["meta"] = meta
            };

            return obj.ToJsonString();
            return obj.ToJsonString(new JsonSerializerOptions
            {
                WriteIndented = true,
                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
            });
        });
    }

@@ -337,17 +379,21 @@ public class Graph : IName, IDescription
    /// <returns>异步任务</returns>
    public async Task ExecuteAsync(CancellationToken cancellationToken = default)
    {
        // 执行图中的所有节点
        // 收集所有可执行的函数节点
        var functions = new List<IFunction>();
        foreach (var tree in Nodes)
        {
            foreach (var nodeInfo in tree.Nodes)
            {
                if (nodeInfo.Node is IFunction function)
                {
                    await function.Execute(cancellationToken);
                    functions.Add(function);
                }
            }
        }

        // 并行执行所有函数节点
        await Task.WhenAll(functions.Select(f => f.Execute(cancellationToken)));
    }

    /// <summary>
@@ -355,8 +401,8 @@ public class Graph : IName, IDescription
    /// </summary>
    public void PropagateData()
    {
        // 遍历所有的流(连接),将数据从源属性传递到目标属性
        foreach (var flow in Flows)
        // 并行处理数据传播,提高效率
        Parallel.ForEach(Flows, flow =>
        {
            // 检查源属性是否有数据
            if (flow.From.Data != null)
@@ -368,7 +414,7 @@ public class Graph : IName, IDescription
                    flow.To.Data = flow.From.Data;
                }
            }
        }
        });
    }

    /// <summary>
+46 −2
Original line number Diff line number Diff line
using System.IO;
using Nocube.Common.Nodes;
using Nocube.Common.Packages;
using Weikio.PluginFramework.Catalogs;
@@ -43,8 +44,51 @@ public class PackageLoader
    /// 注入包,调用加载逻辑,传递上下文。
    /// </summary>
    /// <param name="loader">加载器</param>
    public void InjectPackage(IPackageLoader loader)
    /// <param name="nodeRegistry">节点注册表</param>
    /// <param name="context">引擎上下文</param>
    public void InjectPackage(IPackageLoader loader, NodeRegistry nodeRegistry, IEngineContext context)
    {
        loader.LoadPackage(new List<Func<INode>>(), null!);
        var nodeFactories = new List<Func<INode>>();
        
        // 注入包,包会将节点工厂添加到nodeFactories列表
        loader.LoadPackage(nodeFactories, context);
        
        // 将节点工厂注册到注册表
        foreach (var factory in nodeFactories)
        {
            try
            {
                var node = factory();
                if (node != null)
                {
                    var typeName = node.GetType().FullName ?? node.GetType().Name;
                    nodeRegistry.RegisterNode(typeName, factory);
                }
            }
            catch
            {
                // 注册失败,跳过
            }
        }
    }

    /// <summary>
    /// 加载所有包到节点注册表
    /// </summary>
    /// <param name="nodeRegistry">节点注册表</param>
    /// <param name="context">引擎上下文</param>
    public async Task LoadAllPackagesAsync(NodeRegistry nodeRegistry, IEngineContext context)
    {
        // 确保包路径存在
        if (!Directory.Exists(PackagePath))
        {
            Directory.CreateDirectory(PackagePath);
        }
        
        // 加载所有包
        await foreach (var loader in ListInfos(PackagePath))
        {
            InjectPackage(loader, nodeRegistry, context);
        }
    }
}
 No newline at end of file
+173 −0
Original line number Diff line number Diff line
using Nocube.Common.Nodes;
using Nocube.Common.Packages;
using Nocube.Engine.Graphs;
using Nocube.Engine.Packages;

namespace Nocube.Engine.Shared;

#if DEBUG
public static class OptimizationTest
{
    public static async Task TestNodeRegistry()
    {
        Console.WriteLine("=== Testing Node Registry ===");
        
        var registry = new NodeRegistry();
        
        // 测试注册和创建节点
        var testNode = new TestNode();
        var typeName = testNode.GetType().FullName ?? testNode.GetType().Name;
        
        // 注册节点类型
        registry.RegisterNode(typeof(TestNode));
        
        // 测试创建节点
        var createdNode = registry.CreateNode(typeName);
        Console.WriteLine($"Created node: {createdNode?.Name}");
        
        // 测试通过工厂方法注册节点
        registry.RegisterNode("CustomTestNode", () => new TestNode());
        var customNode = registry.CreateNode("CustomTestNode");
        Console.WriteLine($"Created custom node: {customNode?.Name}");
        
        // 测试不存在的节点类型
        var nonExistentNode = registry.CreateNode("NonExistentNode");
        Console.WriteLine($"Created non-existent node: {nonExistentNode?.Name ?? "null (expected)"}");
        
        Console.WriteLine("Node registry test completed.");
    }

    public static async Task TestPackageLoader()
    {
        Console.WriteLine("\n=== Testing Package Loader ===");
        
        var registry = new NodeRegistry();
        var loader = new PackageLoader();
        
        // 测试加载包
        // 注意:这里假设包路径存在,实际测试时可能需要调整
        try
        {
            // 创建一个简单的引擎上下文
            var context = new WorkspaceContext();
            
            // 加载所有包
            await loader.LoadAllPackagesAsync(registry, context);
            
            // 检查注册的节点类型
            var registeredTypes = registry.GetRegisteredNodeTypes();
            Console.WriteLine($"Registered node types: {registeredTypes.Count()}");
            foreach (var type in registeredTypes)
            {
                Console.WriteLine($"- {type}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Package loader test error: {ex.Message}");
        }
        
        Console.WriteLine("Package loader test completed.");
    }

    public static async Task TestGraphWithRegistry()
    {
        Console.WriteLine("\n=== Testing Graph with Node Registry ===");
        
        var graph = new Graph();
        var context = new WorkspaceContext();
        graph.EngineContext = context;
        
        // 加载包
        await graph.LoadPackagesAsync();
        
        // 创建测试节点
        var testNode = new TestNode();
        var nodeInfo = new NodeInfo(testNode, 100, 100);
        var nodeTree = new GraphNodeTree([], [nodeInfo]);
        graph.Nodes.Add(nodeTree);
        
        // 设置输入值
        testNode.GetAProperty().Data = 123;
        testNode.GetBProperty().Data = "Test";
        testNode.GetCProperty().Data = true;
        
        // 执行节点
        Console.WriteLine("Executing node...");
        await graph.ExecuteAsync();
        
        // 检查输出
        var output = testNode.GetReturnProperty().Data;
        Console.WriteLine($"Node output: {output}");
        
        // 测试序列化和反序列化
        Console.WriteLine("Testing serialization and deserialization...");
        var json = await graph.SaveAsync();
        
        var newGraph = new Graph();
        newGraph.EngineContext = context;
        await newGraph.LoadPackagesAsync();
        await newGraph.ReadAsync(json);
        
        Console.WriteLine($"Loaded graph has {newGraph.Nodes.Count} node trees");
        
        Console.WriteLine("Graph with registry test completed.");
    }

    public static async Task TestExecutionPerformance()
    {
        Console.WriteLine("\n=== Testing Execution Performance ===");
        
        var graph = new Graph();
        
        // 创建多个测试节点
        int nodeCount = 10;
        var nodeInfos = new List<NodeInfo>();
        
        for (int i = 0; i < nodeCount; i++)
        {
            var testNode = new TestNode();
            testNode.GetAProperty().Data = i;
            testNode.GetBProperty().Data = $"Node {i}";
            testNode.GetCProperty().Data = i % 2 == 0;
            
            var nodeInfo = new NodeInfo(testNode, 100 + i * 100, 100);
            nodeInfos.Add(nodeInfo);
        }
        
        var nodeTree = new GraphNodeTree([], nodeInfos.ToArray());
        graph.Nodes.Add(nodeTree);
        
        // 测试执行时间
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
        await graph.ExecuteAsync();
        stopwatch.Stop();
        
        Console.WriteLine($"Executed {nodeCount} nodes in {stopwatch.ElapsedMilliseconds} ms");
        
        // 检查输出
        foreach (var nodeInfo in nodeInfos)
        {
            if (nodeInfo.Node is TestNode testNode)
            {
                var output = testNode.GetReturnProperty().Data;
                Console.WriteLine($"Node output: {output}");
            }
        }
        
        Console.WriteLine("Execution performance test completed.");
    }

    public static async Task RunAllTests()
    {
        Console.WriteLine("Running all optimization tests...");
        
        await TestNodeRegistry();
        await TestPackageLoader();
        await TestGraphWithRegistry();
        await TestExecutionPerformance();
        
        Console.WriteLine("All optimization tests completed.");
    }
}
#endif