using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace XNode {
/// Precaches reflection data in editor so we won't have to do it runtime
public static class NodeDataCache {
private static PortDataCache portDataCache;
private static bool Initialized { get { return portDataCache != null; } }
/// Update static ports to reflect class fields.
public static void UpdatePorts(Node node, Dictionary ports) {
if (!Initialized) BuildCache();
Dictionary staticPorts = new Dictionary();
System.Type nodeType = node.GetType();
if (portDataCache.ContainsKey(nodeType)) {
for (int i = 0; i < portDataCache[nodeType].Count; i++) {
staticPorts.Add(portDataCache[nodeType][i].fieldName, portDataCache[nodeType][i]);
}
}
// Cleanup port dict - Remove nonexisting static ports - update static port types
// Loop through current node ports
foreach (NodePort port in ports.Values.ToList()) {
// If port still exists, check it it has been changed
if (staticPorts.ContainsKey(port.fieldName)) {
NodePort staticPort = staticPorts[port.fieldName];
// If port exists but with wrong settings, remove it. Re-add it later.
if (port.connectionType != staticPort.connectionType || port.IsDynamic || port.direction != staticPort.direction) ports.Remove(port.fieldName);
else port.ValueType = staticPort.ValueType;
}
// If port doesn't exist anymore, remove it
else if (port.IsStatic) ports.Remove(port.fieldName);
}
// Add missing ports
foreach (NodePort staticPort in staticPorts.Values) {
if (!ports.ContainsKey(staticPort.fieldName)) {
ports.Add(staticPort.fieldName, new NodePort(staticPort, node));
}
}
}
private static void BuildCache() {
portDataCache = new PortDataCache();
System.Type baseType = typeof(Node);
Assembly assembly = Assembly.GetAssembly(baseType);
System.Type[] nodeTypes = assembly.GetTypes().Where(t =>
!t.IsAbstract &&
baseType.IsAssignableFrom(t)
).ToArray();
for (int i = 0; i < nodeTypes.Length; i++) {
CachePorts(nodeTypes[i]);
}
}
private static void CachePorts(System.Type nodeType) {
System.Reflection.FieldInfo[] fieldInfo = nodeType.GetFields();
for (int i = 0; i < fieldInfo.Length; i++) {
//Get InputAttribute and OutputAttribute
object[] attribs = fieldInfo[i].GetCustomAttributes(false);
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 {
if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List());
portDataCache[nodeType].Add(new NodePort(fieldInfo[i]));
}
}
}
[System.Serializable]
private class PortDataCache : Dictionary>, ISerializationCallbackReceiver {
[SerializeField] private List keys = new List();
[SerializeField] private List> values = new List>();
// 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]);
}
}
}
}