自定义节点编辑器xNode——NodeDataCache(三)(番外)

本文中的项目来源于 https://github.com/Siccity/xNode
本章节内容和之后关系不太大,可不看

1.6 NodeDataCache

我们能可以在编辑器中预处理反射数据,这样我们就不必在运行时进行处理

1.6.1 PortDataCache

这个类继承了Dictionary和ISerializationCallbackReceiver接口,通过Type来保存NodePort

[System.Serializable]
private class PortDataCache : Dictionary<System.Type, List<NodePort>>, ISerializationCallbackReceiver {
    [SerializeField] private List<System.Type> keys = new List<System.Type>();
    [SerializeField] private List<List<NodePort>> values = new List<List<NodePort>>();

    // save the dictionary to lists
    public void OnBeforeSerialize() {
        keys.Clear();
        values.Clear();
        foreach (var pair in this) {
            keys.Add(pair.Key);
            values.Add(pair.Value);
        }
    }

    // load dictionary from lists
    public void OnAfterDeserialize() {
        this.Clear();

        if (keys.Count != values.Count)
            throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable."));

        for (int i = 0; i < keys.Count; i++)
            this.Add(keys[i], values[i]);
    }
}
1.6.2 基本属性

在NodeDataCache中有一个portDataCache字典保存数据,还有一个Initialized布尔值来确认是否初始化成功

public static class NodeDataCache {
	private static PortDataCache portDataCache;
	private static bool Initialized { get { return portDataCache != null; } }
1.6.3 函数介绍
1.6.3.1 GetNodeFields
///通过反射根据Type获取字段信息(包括私有和公有的)
public static List<FieldInfo> GetNodeFields(System.Type nodeType) 
{
    List<System.Reflection.FieldInfo> fieldInfo = new List<System.Reflection.FieldInfo>(nodeType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));

    //GetFields不返回继承的私有字段,所以需要再遍历获取那些私有的字段
    System.Type tempType = nodeType;
    while ((tempType = tempType.BaseType) != typeof(XNode.Node)) {
        fieldInfo.AddRange(tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
    }
    return fieldInfo;
}
1.6.3.2 CachePorts
private static void CachePorts(System.Type nodeType) 
{
	//通过1.6.3.1的函数获取Node的所有字段信息
    List<System.Reflection.FieldInfo> fieldInfo = GetNodeFields(nodeType);

    for (int i = 0; i < fieldInfo.Count; i++) {

        //通过Linq获取InputAttribute和OutputAttribute
        object[] attribs = fieldInfo[i].GetCustomAttributes(true);
        Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute;
        Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute;
		
		//如果都没有就跳过
        if (inputAttrib == null && outputAttrib == null) continue;
		//如果都有就提示不能都有
        if (inputAttrib != null && outputAttrib != null) 
        	Debug.LogError("Field " + fieldInfo[i].Name + " of type " + nodeType.FullName + " cannot be both input and output.");
        else {
        	//如果portDataCache没有这个Key就添加一个Key
            if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List<NodePort>());
            //为portDataCache添加NodePort
            portDataCache[nodeType].Add(new NodePort(fieldInfo[i]));
        }
    }
}
1.6.3.3 BuildCache
///缓存类型
private static void BuildCache() 
{
    portDataCache = new PortDataCache();
    System.Type baseType = typeof(Node);
    List<System.Type> nodeTypes = new List<System.Type>();
    System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();

    //遍历程序集并将节点类型添加到列表中
    foreach (Assembly assembly in assemblies) 
    {
        //跳过某些dll库以提高遍历速度
        string assemblyName = assembly.GetName().Name;
        int index = assemblyName.IndexOf('.');
        if (index != -1) assemblyName = assemblyName.Substring(0, index);
        switch (assemblyName) {
            case "UnityEditor":
            case "UnityEngine":
            case "System":
            case "mscorlib":
                continue;
            default:
                nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
                break;
        }
    }
	
	//然后根据已经加载的Node类型,使用1.6.3.2的函数缓存节点的字段信息
    for (int i = 0; i < nodeTypes.Count; i++) {
        CachePorts(nodeTypes[i]);
    }
}
1.6.3.4 UpdatePorts
///更新静态端口以反射类的字段
public static void UpdatePorts(Node node, Dictionary<string, NodePort> ports) 
{
	//根据前面的介绍,这个函数把程序集中所有和Node有关的字段都加载到了目前的一个字典中
	//如果没初始化就先初始化一下
    if (!Initialized) BuildCache();
	
    Dictionary<string, NodePort> staticPorts = new Dictionary<string, NodePort>();
    Dictionary<string, List<NodePort>> removedPorts = new Dictionary<string, List<NodePort>>();
    //当前节点的类型
    System.Type nodeType = node.GetType();

    List<NodePort> typePortCache;
    //如果能从字典里根据Key获取到值,那么就把信息复制到typePortCache中
    if (portDataCache.TryGetValue(nodeType, out typePortCache)) {
        for (int i = 0; i < typePortCache.Count; i++) {
            staticPorts.Add(typePortCache[i].fieldName, portDataCache[nodeType][i]);
        }
    }

    //清除接口-删除不存在的静态接口-更新静态接口类型
    //循环当前节点接口
    foreach (NodePort port in ports.Values.ToList()) {
        // 如果接口仍然存在,检查它是否已经更改
        NodePort staticPort;
        if (staticPorts.TryGetValue(port.fieldName, out staticPort)) {
            //如果接口存在但设置错误,就删除它,稍后重新添加
            if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) {
                //如果接口不是动态的且方向没有改变,就将其添加到列表中,以便我们恢复接口原本的连接
                if (!port.IsDynamic && port.direction == staticPort.direction) removedPorts.Add(port.fieldName, port.GetConnections());
                port.ClearConnections();
                ports.Remove(port.fieldName);
            } else port.ValueType = staticPort.ValueType;
        }
        //如果接口不存在了就删除它
        else if (port.IsStatic) {
            port.ClearConnections();
            ports.Remove(port.fieldName);
        }
    }
    //添加丢失的接口
    foreach (NodePort staticPort in staticPorts.Values) {
        if (!ports.ContainsKey(staticPort.fieldName)) {
            NodePort port = new NodePort(staticPort, node);
            //如果我们刚刚删除了接口,尝试恢复接口原本的连接
            List<NodePort> reconnectConnections;
            if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) {
                for (int i = 0; i < reconnectConnections.Count; i++) {
                    NodePort connection = reconnectConnections[i];
                    if (connection == null) continue;
                    if (port.CanConnectTo(connection)) port.Connect(connection);
                }
            }
            ports.Add(staticPort.fieldName, port);
        }
    }
}

这一部分就是让我们能够预先处理需要反射的数据,这样在运行时就不需要处理这些数据

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
XMLConfigBuilder是MyBatis框架中用于解析mybatis-config.xml配置文件的类,它采用工厂模式进行设计。下面是XMLConfigBuilder类对应的源码,并对其进行解释: ```java public class XMLConfigBuilder extends BaseBuilder { private boolean parsed; private final XPathParser parser; private String environment; private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory(); private final MapperBuilderAssistant builderAssistant; private final Map<String, XNode> sqlFragments = new HashMap<String, XNode>(); public XMLConfigBuilder(Reader reader, String environment, Properties props) { this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props); } public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); this.builderAssistant = new MapperBuilderAssistant(configuration, null); this.parsed = false; this.environment = environment; this.parser = parser; this.configuration.setVariables(props); } public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { //解析properties节点,将其中的属性值设置到Configuration对象中 propertiesElement(root.evalNode("properties")); //解析settings节点,将其中的配置设置到Configuration对象中 Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); //解析typeAliases节点,将其中的别名注册到Configuration对象中 typeAliasesElement(root.evalNode("typeAliases")); //解析plugins节点,将其中的插件添加到InterceptorChain对象中 pluginElement(root.evalNode("plugins")); //解析objectFactory节点,将其中的实现类设置到Configuration对象中 objectFactoryElement(root.evalNode("objectFactory")); //解析objectWrapperFactory节点,将其中的实现类设置到Configuration对象中 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //解析reflectorFactory节点,将其中的实现类设置到Configuration对象中 reflectorFactoryElement(root.evalNode("reflectorFactory")); //将settings节点中的配置设置到Configuration对象中 settingsElement(settings); //解析environments节点,将其中的环境注册到Configuration对象中 environmentsElement(root.evalNode("environments")); //解析databaseIdProvider节点,将其中的实现类设置到Configuration对象中 databaseIdProviderElement(root.evalNode("databaseIdProvider")); //解析typeHandlers节点,将其中的类型处理器注册到Configuration对象中 typeHandlerElement(root.evalNode("typeHandlers")); //解析mappers节点,将其中的Mapper接口注册到Configuration对象中 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } //省略其他方法... } ``` XMLConfigBuilder类的构造方法中,接收一个XPathParser对象、一个环境名和一个Properties对象。其中,XPathParser对象用于解析XML文件,环境名用于指定当前使用的是哪个环境的配置,Properties对象用于保存一些配置属性。 XMLConfigBuilder类的parse()方法用于解析mybatis-config.xml配置文件,它首先检查是否已经解析过该文件,如果解析过则抛出异常。然后它调用parseConfiguration()方法进行具体的解析,解析mybatis-config.xml中的各个节点,并将解析结果设置到Configuration对象中。其中,parseConfiguration()方法调用了多个私有方法,用于解析mybatis-config.xml中的各个节点。 这种工厂模式的设计,使得MyBatis框架能够轻松地扩展和维护,只需要添加相应的节点解析方法即可。同时,将解析过程封装到XMLConfigBuilder类中,使得MyBatis框架的其他组件可以直接拿到解析好的Configuration对象,方便使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值