Commit 56a0ba79 authored by shrabbit's avatar shrabbit
Browse files

添加节点扩展方法和标记接口,优化图执行逻辑和测试断言。正在构建标识系统

parent bf1cecdf
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
namespace Nocube.Common;

public static class StringExtensions
{
    extension(string s)
    {
        public IMarker AsMarker()
        {
            return new Marker(s.GetHashCode());
        }
    }
}

public static class GuidExtensions
{
    extension(Guid g)
    {
        public IMarker AsMarker()
        {
            return new Marker(BitConverter.ToInt32(((Guid)(object)g).ToByteArray(), 0));
        }
    }
}

public static class CommonExtensions
{
    extension<T>(T any) 
        where T : struct
    {
        public IMarker<T> AsMarker()
        {
            return new MarkerHash<T>(any);
        }
    }
}

public static class ConvExtensions
{
    extension<T>(T any)
        where T : struct, IConvertible
    {
        public IMarker<T> AsMarker()
        {
            return new Marker<T>(any);
        }
    }
}
 No newline at end of file
+25 −0
Original line number Diff line number Diff line
namespace Nocube.Common;

/// <summary>
/// 泛型标记接口
/// </summary>
/// <typeparam name="TMark">必须可被转换为int。</typeparam>
public interface IMarker<out TMark> : IMarker
    where TMark : struct
{
    /// <summary>
    /// 标记
    /// </summary>
    new TMark MarkId { get; }
}

/// <summary>
/// 普通标记接口,用int表示。
/// </summary>
public interface IMarker
{
    /// <summary>
    /// 标记
    /// </summary>
    int MarkId { get; }
}
 No newline at end of file
+34 −0
Original line number Diff line number Diff line
namespace Nocube.Common;

/// <summary>
/// 标记器类,用于创建具有特定标识符的标记。此类实现IMarker接口,并且可以用于任何满足结构体和可转换约束的类型。
/// </summary>
/// <typeparam name="TMark">标记ID的类型,必须是结构体并实现IConvertible接口。</typeparam>
public class Marker<TMark>(TMark markId) : IMarker<TMark>
    where TMark : struct, IConvertible
{
    int IMarker.MarkId => markId.ToInt32(null);

    TMark IMarker<TMark>.MarkId => markId;
}

/// <summary>
/// 基于哈希的标记器类,用于创建具有特定标识符的标记。此类实现IMarker接口,并且适用于任何结构体类型作为标记ID。
/// </summary>
/// <typeparam name="TMark">标记ID的类型,必须是结构体。</typeparam>
public class MarkerHash<TMark>(TMark markId) : IMarker<TMark>
    where TMark : struct
{
    int IMarker.MarkId => markId.GetHashCode();

    TMark IMarker<TMark>.MarkId => markId;
}

/// <summary>
/// 无泛型Marker
/// </summary>
/// <param name="markId">标识整数</param>
public class Marker(int markId) : IMarker
{
    int IMarker.MarkId => markId;
}
 No newline at end of file
+11 −2
Original line number Diff line number Diff line
@@ -239,6 +239,7 @@ public class GraphTest
        
        // 再次进行存储
        var json = await graph.SaveAsync();
        Console.WriteLine(json);

        // 从JSON读取图
        var newGraph = new Graph(engine);
@@ -247,9 +248,17 @@ public class GraphTest
        // 为第一个节点输入数据
        var newFirst = newGraph.Nodes[0].Nodes[0].Node;
        newFirst.Properties[0].Data = result;
        Assert.Multiple(() =>
        {
            // 断言节点数量
            Assert.That(newGraph.Nodes, Has.Count.EqualTo(10), "节点数量不为10.");

            // 断言连接数量
            Assert.That(newGraph.Flows, Has.Count.EqualTo(9), "连接数量不为9.");
        });

        // 执行节点
        await graph.ExecuteAsync();
        await newGraph.ExecuteAsync();
        
        // 检查结果
        var newIndex = 0;
+77 −19
Original line number Diff line number Diff line
@@ -174,6 +174,34 @@ public class Graph : IName, IDescription
            // 如果反射创建失败,则尝试其他方式
        }
        
        // 如果反射失败,尝试通过引擎上下文获取节点实例
        if (EngineContext != null)
        {
            try
            {
                var nodes = EngineContext.GetNodes();
                foreach (var existingNode in nodes)
                {
                    var existingTypeName = existingNode.GetType().FullName ?? existingNode.GetType().Name;
                    if (existingTypeName.Equals(typeName, StringComparison.OrdinalIgnoreCase))
                    {
                        // 创建一个新的节点实例,使用现有节点的类型
                        var instance = Activator.CreateInstance(existingNode.GetType()) as INode;
                        if (instance != null)
                        {
                            // 注册到注册表,以便下次使用
                            NodeRegistry.RegisterNode(existingNode.GetType());
                            return instance;
                        }
                    }
                }
            }
            catch
            {
                // 如果通过引擎上下文创建失败,则尝试其他方式
            }
        }
        
        // 如果反射失败,尝试根据常见类型手动创建
        // 这里可以扩展以支持更多特定类型的节点创建
        return null;
@@ -295,13 +323,11 @@ public class Graph : IName, IDescription
        {
            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);
                    p => ReferenceEquals(p, property));
                
                if (matchingProp != null && matchingProp.ArrowType.FullName == property.ArrowType.FullName)
                if (matchingProp != null)
                    return nodeInfo.Node;
            }
        }
@@ -387,37 +413,69 @@ public class Graph : IName, IDescription
    /// <returns>异步任务</returns>
    public async Task ExecuteAsync(CancellationToken cancellationToken = default)
    {
        // 对于链式结构,我们需要按照依赖顺序执行节点
        // 这里采用一种简单的方法:执行-传播-再执行,重复多次,直到所有节点的数据都被正确传播
        // 使用拓扑排序或依赖驱动的方法来执行节点
        // 记录每个节点的最后执行时间戳或版本号,避免重复执行
        var lastExecuted = new Dictionary<INode, int>();
        
        // 最大执行次数,防止无限循环
        const int maxIterations = 10;
        // 先传播一次初始数据,确保所有预设的输入数据都正确传递
        PropagateData();
        
        const int maxIterations = 100; // 增加最大迭代次数
        int iterationCount = 0;
        
        for (int i = 0; i < maxIterations; i++)
        while (iterationCount < maxIterations)
        {
            // 1. 传播数据,确保所有节点都能接收到输入数据
            PropagateData();
            bool executedAny = false;
            iterationCount++;
            
            // 2. 收集所有可执行的函数节点
            var functions = new List<IFunction>();
            // 遍历所有节点,执行那些满足执行条件的节点
            foreach (var tree in Nodes)
            {
                foreach (var nodeInfo in tree.Nodes)
                {
                    if (nodeInfo.Node is IFunction function)
                    {
                        functions.Add(function);
                        // 检查节点是否可以执行,且尚未在此轮迭代中执行过
                        if (CanNodeExecute(nodeInfo.Node) && 
                            (!lastExecuted.ContainsKey(nodeInfo.Node) || lastExecuted[nodeInfo.Node] < iterationCount))
                        {
                            await function.Execute(cancellationToken);
                            lastExecuted[nodeInfo.Node] = iterationCount; // 记录执行版本
                            executedAny = true;
                        }
                    }
                }
            }
            
            // 3. 并行执行所有函数节点
            await Task.WhenAll(functions.Select(f => f.Execute(cancellationToken)));
            
            // 4. 再次传播数据,确保执行结果被正确传递
            // 每轮执行后传播数据,使下游节点可能获得执行所需的数据
            PropagateData();
            
            // 如果本轮没有执行任何节点,说明已达到稳定状态
            if (!executedAny)
            {
                break;
            }
        }
    }

    /// <summary>
    /// 检查节点是否可以执行(基于其输入属性的数据状态)
    /// </summary>
    /// <param name="node">要检查的节点</param>
    /// <returns>如果节点可以执行则返回true,否则返回false</returns>
    private bool CanNodeExecute(INode node)
    {
        // 检查节点的所有输入属性
        var inputProperties = node.Properties.Where(p => p.Direct == FlowDirect.In || p.Direct == FlowDirect.Any).ToList();
        
        // 如果节点没有输入属性,可以直接执行
        if (!inputProperties.Any())
            return true;
        
        // 对于LinkedNode这样的简单传递节点,只要有输入数据就可以执行
        // 检查是否有至少一个输入属性有数据
        return inputProperties.Any(p => p.Data != null);
    }

    /// <summary>
    /// 传播数据通过图中的连接,只传播类型兼容的数据