mirror of
https://github.com/Siccity/xNode.git
synced 2026-03-26 22:49:02 +08:00
Fixed missing format arguments for exception * Added missing string format arguments used for exception * Moved duplicate exception message to XNodeRuntimeConstants class * Modified NodeDataCache deserialization exception thrown to be the same format as Node Simplified logic for discovering node types * Simplified the reflection logic used to discover node types by creating a constant array of assembly prefixes to ignore and then checking to see if discovered assembly names begin with those prefixes, and if they do skip checking them for derived node types. Added additional common assembly prefixes that Unity loads into memory that would not contain Node types.
212 lines
11 KiB
C#
212 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using UnityEngine;
|
|
|
|
namespace XNode {
|
|
/// <summary> Precaches reflection data in editor so we won't have to do it runtime </summary>
|
|
public static class NodeDataCache {
|
|
private static PortDataCache portDataCache;
|
|
private static bool Initialized { get { return portDataCache != null; } }
|
|
|
|
/// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. </summary>
|
|
public static void UpdatePorts(Node node, Dictionary<string, NodePort> ports) {
|
|
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> dynamicListPorts = new List<NodePort>();
|
|
|
|
List<NodePort> typePortCache;
|
|
if (portDataCache.TryGetValue(nodeType, out typePortCache)) {
|
|
for (int i = 0; i < typePortCache.Count; i++) {
|
|
staticPorts.Add(typePortCache[i].fieldName, portDataCache[nodeType][i]);
|
|
}
|
|
}
|
|
|
|
// Cleanup port dict - Remove nonexisting static ports - update static port types
|
|
// AND update dynamic ports (albeit only those in lists) too, in order to enforce proper serialisation.
|
|
// Loop through current node ports
|
|
foreach (NodePort port in ports.Values.ToList()) {
|
|
// If port still exists, check it it has been changed
|
|
NodePort staticPort;
|
|
if (staticPorts.TryGetValue(port.fieldName, out staticPort)) {
|
|
// If port exists but with wrong settings, remove it. Re-add it later.
|
|
if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) {
|
|
// If port is not dynamic and direction hasn't changed, add it to the list so we can try reconnecting the ports connections.
|
|
if (!port.IsDynamic && port.direction == staticPort.direction) removedPorts.Add(port.fieldName, port.GetConnections());
|
|
port.ClearConnections();
|
|
ports.Remove(port.fieldName);
|
|
} else port.ValueType = staticPort.ValueType;
|
|
}
|
|
// If port doesn't exist anymore, remove it
|
|
else if (port.IsStatic) {
|
|
port.ClearConnections();
|
|
ports.Remove(port.fieldName);
|
|
}
|
|
// If the port is dynamic and is managed by a dynamic port list, flag it for reference updates
|
|
else if (IsDynamicListPort(port)) {
|
|
dynamicListPorts.Add(port);
|
|
}
|
|
}
|
|
// Add missing ports
|
|
foreach (NodePort staticPort in staticPorts.Values) {
|
|
if (!ports.ContainsKey(staticPort.fieldName)) {
|
|
NodePort port = new NodePort(staticPort, node);
|
|
//If we just removed the port, try re-adding the connections
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Finally, make sure dynamic list port settings correspond to the settings of their "backing port"
|
|
foreach (NodePort listPort in dynamicListPorts) {
|
|
// At this point we know that ports here are dynamic list ports
|
|
// which have passed name/"backing port" checks, ergo we can proceed more safely.
|
|
string backingPortName = listPort.fieldName.Split(' ')[0];
|
|
NodePort backingPort = staticPorts[backingPortName];
|
|
|
|
// Update port constraints. Creating a new port instead will break the editor, mandating the need for setters.
|
|
listPort.ValueType = GetBackingValueType(backingPort.ValueType);
|
|
listPort.direction = backingPort.direction;
|
|
listPort.connectionType = backingPort.connectionType;
|
|
listPort.typeConstraint = backingPort.typeConstraint;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts the underlying types from arrays and lists, the only collections for dynamic port lists
|
|
/// currently supported. If the given type is not applicable (i.e. if the dynamic list port was not
|
|
/// defined as an array or a list), returns the given type itself.
|
|
/// </summary>
|
|
private static System.Type GetBackingValueType(System.Type portValType) {
|
|
if (portValType.HasElementType) {
|
|
return portValType.GetElementType();
|
|
}
|
|
if (portValType.IsGenericType && portValType.GetGenericTypeDefinition() == typeof(List<>)) {
|
|
return portValType.GetGenericArguments()[0];
|
|
}
|
|
return portValType;
|
|
}
|
|
|
|
/// <summary>Returns true if the given port is in a dynamic port list.</summary>
|
|
private static bool IsDynamicListPort(NodePort port) {
|
|
// Ports flagged as "dynamicPortList = true" end up having a "backing port" and a name with an index, but we have
|
|
// no guarantee that a dynamic port called "output 0" is an element in a list backed by a static "output" port.
|
|
// Thus, we need to check for attributes... (but at least we don't need to look at all fields this time)
|
|
string[] fieldNameParts = port.fieldName.Split(' ');
|
|
if (fieldNameParts.Length != 2) return false;
|
|
|
|
FieldInfo backingPortInfo = port.node.GetType().GetField(fieldNameParts[0]);
|
|
if (backingPortInfo == null) return false;
|
|
|
|
object[] attribs = backingPortInfo.GetCustomAttributes(true);
|
|
return attribs.Any(x => {
|
|
Node.InputAttribute inputAttribute = x as Node.InputAttribute;
|
|
Node.OutputAttribute outputAttribute = x as Node.OutputAttribute;
|
|
return inputAttribute != null && inputAttribute.dynamicPortList ||
|
|
outputAttribute != null && outputAttribute.dynamicPortList;
|
|
});
|
|
}
|
|
|
|
/// <summary> Cache node types </summary>
|
|
private static void BuildCache() {
|
|
portDataCache = new PortDataCache();
|
|
Type baseType = typeof(Node);
|
|
List<Type> nodeTypes = new List<Type>();
|
|
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
|
|
|
// Loop through assemblies and add node types to list
|
|
foreach (Assembly assembly in assemblies)
|
|
{
|
|
// Skip certain dlls to improve performance
|
|
string assemblyName = assembly.GetName().Name;
|
|
if (!XNodeRuntimeConstants.IGNORE_ASSEMBLY_PREFIXES.Any(x => assemblyName.StartsWith(x)))
|
|
{
|
|
IEnumerable<Type> foundNodeTypes = assembly.GetTypes()
|
|
.Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t));
|
|
nodeTypes.AddRange(foundNodeTypes);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < nodeTypes.Count; i++) {
|
|
CachePorts(nodeTypes[i]);
|
|
}
|
|
}
|
|
|
|
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 doesnt return inherited private fields, so walk through base types and pick those up
|
|
System.Type tempType = nodeType;
|
|
while ((tempType = tempType.BaseType) != typeof(XNode.Node)) {
|
|
fieldInfo.AddRange(tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
|
|
}
|
|
return fieldInfo;
|
|
}
|
|
|
|
private static void CachePorts(System.Type nodeType) {
|
|
List<System.Reflection.FieldInfo> fieldInfo = GetNodeFields(nodeType);
|
|
|
|
for (int i = 0; i < fieldInfo.Count; i++) {
|
|
|
|
//Get InputAttribute and 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 {
|
|
if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List<NodePort>());
|
|
portDataCache[nodeType].Add(new NodePort(fieldInfo[i]));
|
|
}
|
|
}
|
|
}
|
|
|
|
[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)
|
|
{
|
|
var msg = string.Format(
|
|
XNodeRuntimeConstants.MISMATCHED_KEYS_TO_VALUES_EXCEPTION_MESSAGE,
|
|
keys.Count,
|
|
values.Count);
|
|
throw new Exception(msg);
|
|
}
|
|
|
|
for (int i = 0; i < keys.Count; i++)
|
|
this.Add(keys[i], values[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|