本文中的项目来源于 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);
}
}
}
这一部分就是让我们能够预先处理需要反射的数据,这样在运行时就不需要处理这些数据