diff --git a/Scripts/Attributes/NodeEnum.cs b/Scripts/Attributes/NodeEnum.cs index 9cdaef4..92d5cfb 100644 --- a/Scripts/Attributes/NodeEnum.cs +++ b/Scripts/Attributes/NodeEnum.cs @@ -1,5 +1,8 @@ using UnityEngine; -/// Draw enums correctly within nodes. Without it, enums show up at the wrong positions. -/// Enums with this attribute are not detected by EditorGui.ChangeCheck due to waiting before executing -public class NodeEnumAttribute : PropertyAttribute { } \ No newline at end of file +namespace Attributes +{ + /// Draw enums correctly within nodes. Without it, enums show up at the wrong positions. + /// Enums with this attribute are not detected by EditorGui.ChangeCheck due to waiting before executing + public class NodeEnumAttribute : PropertyAttribute { } +} \ No newline at end of file diff --git a/Scripts/Attributes/PortTypeOverrideAttribute.cs b/Scripts/Attributes/PortTypeOverrideAttribute.cs index 316ca2d..9fd5be7 100644 --- a/Scripts/Attributes/PortTypeOverrideAttribute.cs +++ b/Scripts/Attributes/PortTypeOverrideAttribute.cs @@ -1,12 +1,16 @@ using System; -/// Overrides the ValueType of the Port, to have a ValueType different from the type of its serializable field -/// Especially useful in Dynamic Port Lists to create Value-Port Pairs with different type. -[AttributeUsage(AttributeTargets.Field)] -public class PortTypeOverrideAttribute : Attribute { - public Type type; - /// Overrides the ValueType of the Port - /// ValueType of the Port - public PortTypeOverrideAttribute(Type type) { - this.type = type; + +namespace Attributes +{ + /// Overrides the ValueType of the Port, to have a ValueType different from the type of its serializable field + /// Especially useful in Dynamic Port Lists to create Value-Port Pairs with different type. + [AttributeUsage(AttributeTargets.Field)] + public class PortTypeOverrideAttribute : Attribute { + public Type type; + /// Overrides the ValueType of the Port + /// ValueType of the Port + public PortTypeOverrideAttribute(Type type) { + this.type = type; + } } } diff --git a/Scripts/Editor/Drawers/NodeEnumDrawer.cs b/Scripts/Editor/Drawers/NodeEnumDrawer.cs index 8aa748c..0319820 100644 --- a/Scripts/Editor/Drawers/NodeEnumDrawer.cs +++ b/Scripts/Editor/Drawers/NodeEnumDrawer.cs @@ -1,9 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using Attributes; using UnityEditor; using UnityEngine; -using XNode; using XNodeEditor; namespace XNodeEditor { diff --git a/Scripts/Editor/GraphAndNodeEditor.cs b/Scripts/Editor/GraphAndNodeEditor.cs index 6859855..6bbb865 100644 --- a/Scripts/Editor/GraphAndNodeEditor.cs +++ b/Scripts/Editor/GraphAndNodeEditor.cs @@ -8,7 +8,7 @@ using Sirenix.Utilities.Editor; namespace XNodeEditor { /// Override graph inspector to show an 'Open Graph' button at the top - [CustomEditor(typeof(XNode.NodeGraph), true)] + [CustomEditor(typeof(NodeGraph), true)] #if ODIN_INSPECTOR public class GlobalGraphEditor : OdinEditor { public override void OnInspectorGUI() { @@ -25,7 +25,7 @@ namespace XNodeEditor { serializedObject.Update(); if (GUILayout.Button("Edit graph", GUILayout.Height(40))) { - NodeEditorWindow.Open(serializedObject.targetObject as XNode.NodeGraph); + NodeEditorWindow.Open(serializedObject.targetObject as NodeGraph); } GUILayout.Space(EditorGUIUtility.singleLineHeight); @@ -38,7 +38,7 @@ namespace XNodeEditor { } #endif - [CustomEditor(typeof(XNode.Node), true)] + [CustomEditor(typeof(Node), true)] #if ODIN_INSPECTOR public class GlobalNodeEditor : OdinEditor { public override void OnInspectorGUI() { @@ -58,7 +58,7 @@ namespace XNodeEditor { if (GUILayout.Button("Edit graph", GUILayout.Height(40))) { SerializedProperty graphProp = serializedObject.FindProperty("graph"); - NodeEditorWindow w = NodeEditorWindow.Open(graphProp.objectReferenceValue as XNode.NodeGraph); + NodeEditorWindow w = NodeEditorWindow.Open(graphProp.objectReferenceValue as NodeGraph); w.Home(); // Focus selected node } diff --git a/Scripts/Editor/GraphRenameFixAssetProcessor.cs b/Scripts/Editor/GraphRenameFixAssetProcessor.cs index 264e8b1..cf5ec93 100644 --- a/Scripts/Editor/GraphRenameFixAssetProcessor.cs +++ b/Scripts/Editor/GraphRenameFixAssetProcessor.cs @@ -1,13 +1,12 @@ using UnityEditor; -using XNode; namespace XNodeEditor { /// /// This asset processor resolves an issue with the new v2 AssetDatabase system present on 2019.3 and later. When - /// renaming a asset, it appears that sometimes the v2 AssetDatabase will swap which asset - /// is the main asset (present at top level) between the and one of its + /// renaming a asset, it appears that sometimes the v2 AssetDatabase will swap which asset + /// is the main asset (present at top level) between the and one of its /// sub-assets. As a workaround until Unity fixes this, this asset processor checks all renamed assets and if it - /// finds a case where a has been made the main asset it will swap it back to being a sub-asset + /// finds a case where a has been made the main asset it will swap it back to being a sub-asset /// and rename the node to the default name for that node type. /// internal sealed class GraphRenameFixAssetProcessor : AssetPostprocessor { diff --git a/Scripts/Editor/Internal/RerouteReference.cs b/Scripts/Editor/Internal/RerouteReference.cs index 4e21130..995b1dd 100644 --- a/Scripts/Editor/Internal/RerouteReference.cs +++ b/Scripts/Editor/Internal/RerouteReference.cs @@ -2,11 +2,11 @@ namespace XNodeEditor.Internal { public struct RerouteReference { - public XNode.NodePort port; + public NodePort port; public int connectionIndex; public int pointIndex; - public RerouteReference(XNode.NodePort port, int connectionIndex, int pointIndex) { + public RerouteReference(NodePort port, int connectionIndex, int pointIndex) { this.port = port; this.connectionIndex = connectionIndex; this.pointIndex = pointIndex; diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 8522fc0..4c57611 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -14,12 +14,12 @@ using GenericMenu = XNodeEditor.AdvancedGenericMenu; namespace XNodeEditor { /// Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. - [CustomNodeEditor(typeof(XNode.Node))] - public class NodeEditor : XNodeEditor.Internal.NodeEditorBase { + [CustomNodeEditor(typeof(Node))] + public class NodeEditor : XNodeEditor.Internal.NodeEditorBase { /// Fires every whenever a node was modified through the editor - public static Action onUpdateNode; - public readonly static Dictionary portPositions = new Dictionary(); + public static Action onUpdateNode; + public readonly static Dictionary portPositions = new Dictionary(); #if ODIN_INSPECTOR protected internal static bool inNodeEditor = false; @@ -82,7 +82,7 @@ namespace XNodeEditor { #endif // Iterate through dynamic ports and draw them in the order in which they are serialized - foreach (XNode.NodePort dynamicPort in target.DynamicPorts) { + foreach (NodePort dynamicPort in target.DynamicPorts) { if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort)) continue; NodeEditorGUILayout.PortField(dynamicPort); } @@ -136,8 +136,8 @@ namespace XNodeEditor { public virtual void AddContextMenuItems(GenericMenu menu) { bool canRemove = true; // Actions if only one node is selected - if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { - XNode.Node node = Selection.activeObject as XNode.Node; + if (Selection.objects.Length == 1 && Selection.activeObject is Node) { + Node node = Selection.activeObject as Node; menu.AddItem(new GUIContent("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node)); menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode); @@ -152,8 +152,8 @@ namespace XNodeEditor { else menu.AddItem(new GUIContent("Remove"), false, null); // Custom sctions if only one node is selected - if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { - XNode.Node node = Selection.activeObject as XNode.Node; + if (Selection.objects.Length == 1 && Selection.activeObject is Node) { + Node node = Selection.activeObject as Node; menu.AddCustomContextMenuItems(node); } } @@ -171,7 +171,7 @@ namespace XNodeEditor { [AttributeUsage(AttributeTargets.Class)] public class CustomNodeEditorAttribute : Attribute, - XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { + XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { private Type inspectedType; /// Tells a NodeEditor which Node type it is an editor for /// Type that this editor can edit diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index a9147f2..fb81194 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -15,7 +15,7 @@ namespace XNodeEditor { public static bool isPanning { get; private set; } public static Vector2[] dragOffset; - public static XNode.Node[] copyBuffer = null; + public static Node[] copyBuffer = null; public bool IsDraggingPort { get { return draggedOutput != null; } } public bool IsHoveringPort { get { return hoveredPort != null; } } @@ -23,17 +23,17 @@ namespace XNodeEditor { public bool IsHoveringReroute { get { return hoveredReroute.port != null; } } /// Return the dragged port or null if not exist - public XNode.NodePort DraggedOutputPort { get { XNode.NodePort result = draggedOutput; return result; } } + public NodePort DraggedOutputPort { get { NodePort result = draggedOutput; return result; } } /// Return the Hovered port or null if not exist - public XNode.NodePort HoveredPort { get { XNode.NodePort result = hoveredPort; return result; } } + public NodePort HoveredPort { get { NodePort result = hoveredPort; return result; } } /// Return the Hovered node or null if not exist - public XNode.Node HoveredNode { get { XNode.Node result = hoveredNode; return result; } } + public Node HoveredNode { get { Node result = hoveredNode; return result; } } - private XNode.Node hoveredNode = null; - [NonSerialized] public XNode.NodePort hoveredPort = null; - [NonSerialized] private XNode.NodePort draggedOutput = null; - [NonSerialized] private XNode.NodePort draggedOutputTarget = null; - [NonSerialized] private XNode.NodePort autoConnectOutput = null; + private Node hoveredNode = null; + [NonSerialized] public NodePort hoveredPort = null; + [NonSerialized] private NodePort draggedOutput = null; + [NonSerialized] private NodePort draggedOutputTarget = null; + [NonSerialized] private NodePort autoConnectOutput = null; [NonSerialized] private List draggedOutputReroutes = new List(); private RerouteReference hoveredReroute = new RerouteReference(); @@ -91,8 +91,8 @@ namespace XNodeEditor { Vector2 mousePos = WindowToGridPosition(e.mousePosition); // Move selected nodes with offset for (int i = 0; i < Selection.objects.Length; i++) { - if (Selection.objects[i] is XNode.Node) { - XNode.Node node = Selection.objects[i] as XNode.Node; + if (Selection.objects[i] is Node) { + Node node = Selection.objects[i] as Node; Undo.RecordObject(node, "Moved Node"); Vector2 initial = node.position; node.position = mousePos + dragOffset[i]; @@ -104,7 +104,7 @@ namespace XNodeEditor { // Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame. Vector2 offset = node.position - initial; if (offset.sqrMagnitude > 0) { - foreach (XNode.NodePort output in node.Outputs) { + foreach (NodePort output in node.Outputs) { Rect rect; if (portConnectionPoints.TryGetValue(output, out rect)) { rect.position += offset; @@ -112,7 +112,7 @@ namespace XNodeEditor { } } - foreach (XNode.NodePort input in node.Inputs) { + foreach (NodePort input in node.Inputs) { Rect rect; if (portConnectionPoints.TryGetValue(input, out rect)) { rect.position += offset; @@ -167,8 +167,8 @@ namespace XNodeEditor { hoveredPort.VerifyConnections(); autoConnectOutput = null; if (hoveredPort.IsConnected) { - XNode.Node node = hoveredPort.node; - XNode.NodePort output = hoveredPort.Connection; + Node node = hoveredPort.node; + NodePort output = hoveredPort.Connection; int outputConnectionIndex = output.GetConnectionIndex(hoveredPort); draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex); hoveredPort.Disconnect(output); @@ -222,7 +222,7 @@ namespace XNodeEditor { if (IsDraggingPort) { // If connection is valid, save it if (draggedOutputTarget != null && graphEditor.CanConnect(draggedOutput, draggedOutputTarget)) { - XNode.Node node = draggedOutputTarget.node; + Node node = draggedOutputTarget.node; if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget); // ConnectionIndex can be -1 if the connection is removed instantly after creation @@ -245,8 +245,8 @@ namespace XNodeEditor { EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } else if (currentActivity == NodeActivity.DragNode) { - IEnumerable nodes = Selection.objects.Where(x => x is XNode.Node).Select(x => x as XNode.Node); - foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node); + IEnumerable nodes = Selection.objects.Where(x => x is Node).Select(x => x as Node); + foreach (Node node in nodes) EditorUtility.SetDirty(node); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } else if (!IsHoveringNode) { // If click outside node, release field focus @@ -316,12 +316,12 @@ namespace XNodeEditor { if (e.keyCode == KeyCode.F2) RenameSelectedNode(); } if (e.keyCode == KeyCode.A) { - if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node))) { - foreach (XNode.Node node in graph.nodes) { + if (Selection.objects.Any(x => graph.nodes.Contains(x as Node))) { + foreach (Node node in graph.nodes) { DeselectNode(node); } } else { - foreach (XNode.Node node in graph.nodes) { + foreach (Node node in graph.nodes) { SelectNode(node, true); } } @@ -366,8 +366,8 @@ namespace XNodeEditor { dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count]; // Selected nodes for (int i = 0; i < Selection.objects.Length; i++) { - if (Selection.objects[i] is XNode.Node) { - XNode.Node node = Selection.objects[i] as XNode.Node; + if (Selection.objects[i] is Node) { + Node node = Selection.objects[i] as Node; dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition); } } @@ -380,7 +380,7 @@ namespace XNodeEditor { /// Puts all selected nodes in focus. If no nodes are present, resets view and zoom to to origin public void Home() { - var nodes = Selection.objects.Where(o => o is XNode.Node).Cast().ToList(); + var nodes = Selection.objects.Where(o => o is Node).Cast().ToList(); if (nodes.Count > 0) { Vector2 minPos = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); Vector2 maxPos = nodes.Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero)).Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y))); @@ -400,8 +400,8 @@ namespace XNodeEditor { } selectedReroutes.Clear(); foreach (UnityEngine.Object item in Selection.objects) { - if (item is XNode.Node) { - XNode.Node node = item as XNode.Node; + if (item is Node) { + Node node = item as Node; graphEditor.RemoveNode(node); } } @@ -409,8 +409,8 @@ namespace XNodeEditor { /// Initiate a rename on the currently selected node public void RenameSelectedNode() { - if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { - XNode.Node node = Selection.activeObject as XNode.Node; + if (Selection.objects.Length == 1 && Selection.activeObject is Node) { + Node node = Selection.activeObject as Node; Vector2 size; if (nodeSizes.TryGetValue(node, out size)) { RenamePopup.Show(Selection.activeObject, size.x); @@ -421,7 +421,7 @@ namespace XNodeEditor { } /// Draw this node on top of other nodes by placing it last in the graph.nodes list - public void MoveNodeToTop(XNode.Node node) { + public void MoveNodeToTop(Node node) { int index; while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) { graph.nodes[index] = graph.nodes[index + 1]; @@ -432,7 +432,7 @@ namespace XNodeEditor { /// Duplicate selected nodes and select the duplicates public void DuplicateSelectedNodes() { // Get selected nodes which are part of this graph - XNode.Node[] selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); + Node[] selectedNodes = Selection.objects.Select(x => x as Node).Where(x => x != null && x.graph == graph).ToArray(); if (selectedNodes == null || selectedNodes.Length == 0) return; // Get top left node position Vector2 topLeftNode = selectedNodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); @@ -440,14 +440,14 @@ namespace XNodeEditor { } public void CopySelectedNodes() { - copyBuffer = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); + copyBuffer = Selection.objects.Select(x => x as Node).Where(x => x != null && x.graph == graph).ToArray(); } public void PasteNodes(Vector2 pos) { InsertDuplicateNodes(copyBuffer, pos); } - private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft) { + private void InsertDuplicateNodes(Node[] nodes, Vector2 topLeft) { if (nodes == null || nodes.Length == 0) return; // Get top-left node @@ -455,20 +455,20 @@ namespace XNodeEditor { Vector2 offset = topLeft - topLeftNode; UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.Length]; - Dictionary substitutes = new Dictionary(); + Dictionary substitutes = new Dictionary(); for (int i = 0; i < nodes.Length; i++) { - XNode.Node srcNode = nodes[i]; + Node srcNode = nodes[i]; if (srcNode == null) continue; // Check if user is allowed to add more of given node type - XNode.Node.DisallowMultipleNodesAttribute disallowAttrib; + Node.DisallowMultipleNodesAttribute disallowAttrib; Type nodeType = srcNode.GetType(); if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib)) { int typeCount = graph.nodes.Count(x => x.GetType() == nodeType); if (typeCount >= disallowAttrib.max) continue; } - XNode.Node newNode = graphEditor.CopyNode(srcNode); + Node newNode = graphEditor.CopyNode(srcNode); substitutes.Add(srcNode, newNode); newNode.position = srcNode.position + offset; newNodes[i] = newNode; @@ -476,14 +476,14 @@ namespace XNodeEditor { // Walk through the selected nodes again, recreate connections, using the new nodes for (int i = 0; i < nodes.Length; i++) { - XNode.Node srcNode = nodes[i]; + Node srcNode = nodes[i]; if (srcNode == null) continue; - foreach (XNode.NodePort port in srcNode.Ports) { + foreach (NodePort port in srcNode.Ports) { for (int c = 0; c < port.ConnectionCount; c++) { - XNode.NodePort inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c); - XNode.NodePort outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c); + NodePort inputPort = port.direction == NodePort.IO.Input ? port : port.GetConnection(c); + NodePort outputPort = port.direction == NodePort.IO.Output ? port : port.GetConnection(c); - XNode.Node newNodeIn, newNodeOut; + Node newNodeIn, newNodeOut; if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) { newNodeIn.UpdatePorts(); newNodeOut.UpdatePorts(); @@ -537,7 +537,7 @@ namespace XNodeEditor { } } - bool IsHoveringTitle(XNode.Node node) { + bool IsHoveringTitle(Node node) { Vector2 mousePos = Event.current.mousePosition; //Get node position Vector2 nodePos = GridToWindowPosition(node.position); @@ -550,11 +550,11 @@ namespace XNodeEditor { } /// Attempt to connect dragged output to target node - public void AutoConnect(XNode.Node node) { + public void AutoConnect(Node node) { if (autoConnectOutput == null) return; // Find compatible input port - XNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && graphEditor.CanConnect(autoConnectOutput, x)); + NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && graphEditor.CanConnect(autoConnectOutput, x)); if (inputPort != null) autoConnectOutput.Connect(inputPort); // Save changes diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs b/Scripts/Editor/NodeEditorAssetModProcessor.cs index f4b14a2..82472eb 100644 --- a/Scripts/Editor/NodeEditorAssetModProcessor.cs +++ b/Scripts/Editor/NodeEditorAssetModProcessor.cs @@ -22,7 +22,7 @@ namespace XNodeEditor { // Check script type. Return if deleting a non-node script UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; System.Type scriptType = script.GetClass (); - if (scriptType == null || (scriptType != typeof (XNode.Node) && !scriptType.IsSubclassOf (typeof (XNode.Node)))) return AssetDeleteResult.DidNotDelete; + if (scriptType == null || (scriptType != typeof (Node) && !scriptType.IsSubclassOf (typeof (Node)))) return AssetDeleteResult.DidNotDelete; // Find all ScriptableObjects using this script string[] guids = AssetDatabase.FindAssets ("t:" + scriptType); @@ -30,7 +30,7 @@ namespace XNodeEditor { string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]); Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath); for (int k = 0; k < objs.Length; k++) { - XNode.Node node = objs[k] as XNode.Node; + Node node = objs[k] as Node; if (node.GetType () == scriptType) { if (node != null && node.graph != null) { // Delete the node and notify the user @@ -48,17 +48,17 @@ namespace XNodeEditor { [InitializeOnLoadMethod] private static void OnReloadEditor () { // Find all NodeGraph assets - string[] guids = AssetDatabase.FindAssets ("t:" + typeof (XNode.NodeGraph)); + string[] guids = AssetDatabase.FindAssets ("t:" + typeof (NodeGraph)); for (int i = 0; i < guids.Length; i++) { string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]); - XNode.NodeGraph graph = AssetDatabase.LoadAssetAtPath (assetpath, typeof (XNode.NodeGraph)) as XNode.NodeGraph; + NodeGraph graph = AssetDatabase.LoadAssetAtPath (assetpath, typeof (NodeGraph)) as NodeGraph; graph.nodes.RemoveAll(x => x == null); //Remove null items Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath); // Ensure that all sub node assets are present in the graph node list for (int u = 0; u < objs.Length; u++) { // Ignore null sub assets if (objs[u] == null) continue; - if (!graph.nodes.Contains (objs[u] as XNode.Node)) graph.nodes.Add(objs[u] as XNode.Node); + if (!graph.nodes.Contains (objs[u] as Node)) graph.nodes.Add(objs[u] as Node); } } } diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 35b2e2a..c37a624 100755 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -13,7 +13,7 @@ namespace XNodeEditor { public partial class NodeEditorWindow { public NodeGraphEditor graphEditor; private List selectionCache; - private List culledNodes; + private List culledNodes; /// 19 if docked, 22 if not private int topPadding { get { return isDocked() ? 19 : 22; } } /// Executed after all other window GUI. Useful if Zoom is ruining your day. Automatically resets after being run. @@ -113,7 +113,7 @@ namespace XNodeEditor { } /// Show right-click context menu for hovered port - void ShowPortContextMenu(XNode.NodePort hoveredPort) { + void ShowPortContextMenu(NodePort hoveredPort) { GenericMenu contextMenu = new GenericMenu(); foreach (var port in hoveredPort.GetConnections()) { var name = port.node.name; @@ -125,10 +125,10 @@ namespace XNodeEditor { if (NodeEditorPreferences.GetSettings().createFilter) { contextMenu.AddSeparator(""); - if (hoveredPort.direction == XNode.NodePort.IO.Input) - graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Output); + if (hoveredPort.direction == NodePort.IO.Input) + graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, NodePort.IO.Output); else - graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Input); + graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, NodePort.IO.Input); } contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); @@ -335,12 +335,12 @@ namespace XNodeEditor { List gridPoints = new List(2); Color col = GUI.color; - foreach (XNode.Node node in graph.nodes) { + foreach (Node node in graph.nodes) { //If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset. if (node == null) continue; // Draw full connections and output > reroute - foreach (XNode.NodePort output in node.Outputs) { + foreach (NodePort output in node.Outputs) { //Needs cleanup. Null checks are ugly Rect fromRect; if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue; @@ -349,7 +349,7 @@ namespace XNodeEditor { GUIStyle portStyle = graphEditor.GetPortStyle(output); for (int k = 0; k < output.ConnectionCount; k++) { - XNode.NodePort input = output.GetConnection(k); + NodePort input = output.GetConnection(k); Gradient noodleGradient = graphEditor.GetNoodleGradient(output, input); float noodleThickness = graphEditor.GetNoodleThickness(output, input); @@ -404,7 +404,7 @@ namespace XNodeEditor { } System.Reflection.MethodInfo onValidate = null; - if (Selection.activeObject != null && Selection.activeObject is XNode.Node) { + if (Selection.activeObject != null && Selection.activeObject is Node) { onValidate = Selection.activeObject.GetType().GetMethod("OnValidate"); if (onValidate != null) EditorGUI.BeginChangeCheck(); } @@ -430,14 +430,14 @@ namespace XNodeEditor { //Save guiColor so we can revert it Color guiColor = GUI.color; - List removeEntries = new List(); + List removeEntries = new List(); - if (e.type == EventType.Layout) culledNodes = new List(); + if (e.type == EventType.Layout) culledNodes = new List(); for (int n = 0; n < graph.nodes.Count; n++) { // Skip null nodes. The user could be in the process of renaming scripts, so removing them at this point is not advisable. if (graph.nodes[n] == null) continue; if (n >= graph.nodes.Count) return; - XNode.Node node = graph.nodes[n]; + Node node = graph.nodes[n]; // Culling if (e.type == EventType.Layout) { @@ -529,14 +529,14 @@ namespace XNodeEditor { //Check if we are hovering any of this nodes ports //Check input ports - foreach (XNode.NodePort input in node.Inputs) { + foreach (NodePort input in node.Inputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(input)) continue; Rect r = GridToWindowRectNoClipped(portConnectionPoints[input]); if (r.Contains(mousePos)) hoveredPort = input; } //Check all output ports - foreach (XNode.NodePort output in node.Outputs) { + foreach (NodePort output in node.Outputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(output)) continue; Rect r = GridToWindowRectNoClipped(portConnectionPoints[output]); @@ -556,7 +556,7 @@ namespace XNodeEditor { if (onValidate != null && EditorGUI.EndChangeCheck()) onValidate.Invoke(Selection.activeObject, null); } - private bool ShouldBeCulled(XNode.Node node) { + private bool ShouldBeCulled(Node node) { Vector2 nodePos = GridToWindowPositionNoClipped(node.position); if (nodePos.x / _zoom > position.width) return true; // Right diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index a0e3358..8bb8155 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -22,18 +22,18 @@ namespace XNodeEditor { /// Make a field for a serialized property. Automatically displays relevant node port. public static void PropertyField(SerializedProperty property, GUIContent label, bool includeChildren = true, params GUILayoutOption[] options) { if (property == null) throw new NullReferenceException(); - XNode.Node node = property.serializedObject.targetObject as XNode.Node; - XNode.NodePort port = node.GetPort(property.name); + Node node = property.serializedObject.targetObject as Node; + NodePort port = node.GetPort(property.name); PropertyField(property, label, port, includeChildren); } /// Make a field for a serialized property. Manual node port override. - public static void PropertyField(SerializedProperty property, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { + public static void PropertyField(SerializedProperty property, NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { PropertyField(property, null, port, includeChildren, options); } /// Make a field for a serialized property. Manual node port override. - public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { + public static void PropertyField(SerializedProperty property, GUIContent label, NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { if (property == null) throw new NullReferenceException(); // If property is not a port, display a regular property field @@ -44,10 +44,10 @@ namespace XNodeEditor { List propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name); // If property is an input, display a regular property field and put a port handle on the left side - if (port.direction == XNode.NodePort.IO.Input) { + if (port.direction == NodePort.IO.Input) { // Get data from [Input] attribute - XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; - XNode.Node.InputAttribute inputAttribute; + Node.ShowBackingValue showBacking = Node.ShowBackingValue.Unconnected; + Node.InputAttribute inputAttribute; bool dynamicPortList = false; if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute)) { dynamicPortList = inputAttribute.dynamicPortList; @@ -55,8 +55,8 @@ namespace XNodeEditor { } bool usePropertyAttributes = dynamicPortList || - showBacking == XNode.Node.ShowBackingValue.Never || - (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); + showBacking == Node.ShowBackingValue.Never || + (showBacking == Node.ShowBackingValue.Unconnected && port.IsConnected); float spacePadding = 0; string tooltip = null; @@ -79,22 +79,22 @@ namespace XNodeEditor { if (dynamicPortList) { Type type = GetType(property); - XNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; + Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : Node.ConnectionType.Multiple; DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); return; } switch (showBacking) { - case XNode.Node.ShowBackingValue.Unconnected: + case Node.ShowBackingValue.Unconnected: // Display a label if port is connected if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip)); // Display an editable property field if port is not connected else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); break; - case XNode.Node.ShowBackingValue.Never: + case Node.ShowBackingValue.Never: // Display a label EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip)); break; - case XNode.Node.ShowBackingValue.Always: + case Node.ShowBackingValue.Always: // Display an editable property field EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); break; @@ -104,10 +104,10 @@ namespace XNodeEditor { float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left; rect.position = rect.position - new Vector2(16 + paddingLeft, -spacePadding); // If property is an output, display a text label and put a port handle on the right side - } else if (port.direction == XNode.NodePort.IO.Output) { + } else if (port.direction == NodePort.IO.Output) { // Get data from [Output] attribute - XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; - XNode.Node.OutputAttribute outputAttribute; + Node.ShowBackingValue showBacking = Node.ShowBackingValue.Unconnected; + Node.OutputAttribute outputAttribute; bool dynamicPortList = false; if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute)) { dynamicPortList = outputAttribute.dynamicPortList; @@ -115,8 +115,8 @@ namespace XNodeEditor { } bool usePropertyAttributes = dynamicPortList || - showBacking == XNode.Node.ShowBackingValue.Never || - (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); + showBacking == Node.ShowBackingValue.Never || + (showBacking == Node.ShowBackingValue.Unconnected && port.IsConnected); float spacePadding = 0; string tooltip = null; @@ -139,22 +139,22 @@ namespace XNodeEditor { if (dynamicPortList) { Type type = GetType(property); - XNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; + Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : Node.ConnectionType.Multiple; DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); return; } switch (showBacking) { - case XNode.Node.ShowBackingValue.Unconnected: + case Node.ShowBackingValue.Unconnected: // Display a label if port is connected if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); // Display an editable property field if port is not connected else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); break; - case XNode.Node.ShowBackingValue.Never: + case Node.ShowBackingValue.Never: // Display a label EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); break; - case XNode.Node.ShowBackingValue.Always: + case Node.ShowBackingValue.Always: // Display an editable property field EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); break; @@ -185,19 +185,19 @@ namespace XNodeEditor { } /// Make a simple port field. - public static void PortField(XNode.NodePort port, params GUILayoutOption[] options) { + public static void PortField(NodePort port, params GUILayoutOption[] options) { PortField(null, port, options); } /// Make a simple port field. - public static void PortField(GUIContent label, XNode.NodePort port, params GUILayoutOption[] options) { + public static void PortField(GUIContent label, NodePort port, params GUILayoutOption[] options) { if (port == null) return; if (options == null) options = new GUILayoutOption[] { GUILayout.MinWidth(30) }; Vector2 position = Vector3.zero; GUIContent content = label != null ? label : new GUIContent(ObjectNames.NicifyVariableName(port.fieldName)); // If property is an input, display a regular property field and put a port handle on the left side - if (port.direction == XNode.NodePort.IO.Input) { + if (port.direction == NodePort.IO.Input) { // Display a label EditorGUILayout.LabelField(content, options); @@ -206,7 +206,7 @@ namespace XNodeEditor { position = rect.position - new Vector2(16 + paddingLeft, 0); } // If property is an output, display a text label and put a port handle on the right side - else if (port.direction == XNode.NodePort.IO.Output) { + else if (port.direction == NodePort.IO.Output) { // Display a label EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options); @@ -218,7 +218,7 @@ namespace XNodeEditor { } /// Make a simple port field. - public static void PortField(Vector2 position, XNode.NodePort port) { + public static void PortField(Vector2 position, NodePort port) { if (port == null) return; Rect rect = new Rect(position, new Vector2(16, 16)); @@ -235,17 +235,17 @@ namespace XNodeEditor { } /// Add a port field to previous layout element. - public static void AddPortField(XNode.NodePort port) { + public static void AddPortField(NodePort port) { if (port == null) return; Rect rect = new Rect(); // If property is an input, display a regular property field and put a port handle on the left side - if (port.direction == XNode.NodePort.IO.Input) { + if (port.direction == NodePort.IO.Input) { rect = GUILayoutUtility.GetLastRect(); float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left; rect.position = rect.position - new Vector2(16 + paddingLeft, 0); // If property is an output, display a text label and put a port handle on the right side - } else if (port.direction == XNode.NodePort.IO.Output) { + } else if (port.direction == NodePort.IO.Output) { rect = GUILayoutUtility.GetLastRect(); rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right; rect.position = rect.position + new Vector2(rect.width, 0); @@ -265,7 +265,7 @@ namespace XNodeEditor { } /// Draws an input and an output port on the same line - public static void PortPair(XNode.NodePort input, XNode.NodePort output) { + public static void PortPair(NodePort input, NodePort output) { GUILayout.BeginHorizontal(); NodeEditorGUILayout.PortField(input, GUILayout.MinWidth(0)); NodeEditorGUILayout.PortField(output, GUILayout.MinWidth(0)); @@ -292,18 +292,18 @@ namespace XNodeEditor { #region Obsolete [Obsolete("Use IsDynamicPortListPort instead")] - public static bool IsInstancePortListPort(XNode.NodePort port) { + public static bool IsInstancePortListPort(NodePort port) { return IsDynamicPortListPort(port); } [Obsolete("Use DynamicPortList instead")] - public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action onCreation = null) { + public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, NodePort.IO io, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = Node.TypeConstraint.None, Action onCreation = null) { DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation); } #endregion /// Is this port part of a DynamicPortList? - public static bool IsDynamicPortListPort(XNode.NodePort port) { + public static bool IsDynamicPortListPort(NodePort port) { string[] parts = port.fieldName.Split(' '); if (parts.Length != 2) return false; Dictionary cache; @@ -320,8 +320,8 @@ namespace XNodeEditor { /// The serializedObject of the node /// Connection type of added dynamic ports /// Called on the list on creation. Use this if you want to customize the created ReorderableList - public static void DynamicPortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action onCreation = null) { - XNode.Node node = serializedObject.targetObject as XNode.Node; + public static void DynamicPortList(string fieldName, Type type, SerializedObject serializedObject, NodePort.IO io, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = Node.TypeConstraint.None, Action onCreation = null) { + Node node = serializedObject.targetObject as Node; var indexedPorts = node.DynamicPorts.Select(x => { string[] split = x.fieldName.Split(' '); @@ -331,9 +331,9 @@ namespace XNodeEditor { return new { index = i, port = x }; } } - return new { index = -1, port = (XNode.NodePort)null }; + return new { index = -1, port = (NodePort)null }; }).Where(x => x.port != null); - List dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); + List dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); node.UpdatePorts(); @@ -354,15 +354,15 @@ namespace XNodeEditor { } - private static ReorderableList CreateReorderableList(string fieldName, List dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action onCreation) { + private static ReorderableList CreateReorderableList(string fieldName, List dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, NodePort.IO io, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Action onCreation) { bool hasArrayData = arrayData != null && arrayData.isArray; - XNode.Node node = serializedObject.targetObject as XNode.Node; + Node node = serializedObject.targetObject as Node; ReorderableList list = new ReorderableList(dynamicPorts, null, true, true, true, true); string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName); list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { - XNode.NodePort port = node.GetPort(fieldName + " " + index); + NodePort port = node.GetPort(fieldName + " " + index); if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) { if (arrayData.arraySize <= index) { EditorGUI.LabelField(rect, "Array[" + index + "] data out of range"); @@ -402,8 +402,8 @@ namespace XNodeEditor { // Move up if (rl.index > reorderableListIndex) { for (int i = reorderableListIndex; i < rl.index; ++i) { - XNode.NodePort port = node.GetPort(fieldName + " " + i); - XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i + 1)); + NodePort port = node.GetPort(fieldName + " " + i); + NodePort nextPort = node.GetPort(fieldName + " " + (i + 1)); port.SwapConnections(nextPort); // Swap cached positions to mitigate twitching @@ -416,8 +416,8 @@ namespace XNodeEditor { // Move down else { for (int i = reorderableListIndex; i > rl.index; --i) { - XNode.NodePort port = node.GetPort(fieldName + " " + i); - XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i - 1)); + NodePort port = node.GetPort(fieldName + " " + i); + NodePort nextPort = node.GetPort(fieldName + " " + (i - 1)); port.SwapConnections(nextPort); // Swap cached positions to mitigate twitching @@ -449,7 +449,7 @@ namespace XNodeEditor { int i = 0; while (node.HasPort(newName)) newName = fieldName + " " + (++i); - if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName); + if (io == NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, Node.TypeConstraint.None, newName); else node.AddDynamicInput(type, connectionType, typeConstraint, newName); serializedObject.Update(); EditorUtility.SetDirty(node); @@ -469,7 +469,7 @@ namespace XNodeEditor { return new { index = i, port = x }; } } - return new { index = -1, port = (XNode.NodePort)null }; + return new { index = -1, port = (NodePort)null }; }).Where(x => x.port != null); dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); @@ -486,7 +486,7 @@ namespace XNodeEditor { // Move following connections one step up to replace the missing connection for (int k = index + 1; k < dynamicPorts.Count(); k++) { for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++) { - XNode.NodePort other = dynamicPorts[k].GetConnection(j); + NodePort other = dynamicPorts[k].GetConnection(j); dynamicPorts[k].Disconnect(other); dynamicPorts[k - 1].Connect(other); } @@ -523,7 +523,7 @@ namespace XNodeEditor { string newName = arrayData.name + " 0"; int i = 0; while (node.HasPort(newName)) newName = arrayData.name + " " + (++i); - if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, typeConstraint, newName); + if (io == NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, typeConstraint, newName); else node.AddDynamicInput(type, connectionType, typeConstraint, newName); EditorUtility.SetDirty(node); dynamicPortCount++; diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index d401139..6f9b6c6 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -28,13 +28,13 @@ namespace XNodeEditor { public static Type[] GetNodeTypes() { //Get all classes deriving from Node via reflection - return GetDerivedTypes(typeof(XNode.Node)); + return GetDerivedTypes(typeof(Node)); } /// Custom node tint colors defined with [NodeColor(r, g, b)] public static bool TryGetAttributeTint(this Type nodeType, out Color tint) { if (nodeTint == null) { - CacheAttributes(ref nodeTint, x => x.color); + CacheAttributes(ref nodeTint, x => x.color); } return nodeTint.TryGetValue(nodeType, out tint); } @@ -42,7 +42,7 @@ namespace XNodeEditor { /// Get custom node widths defined with [NodeWidth(width)] public static bool TryGetAttributeWidth(this Type nodeType, out int width) { if (nodeWidth == null) { - CacheAttributes(ref nodeWidth, x => x.width); + CacheAttributes(ref nodeWidth, x => x.width); } return nodeWidth.TryGetValue(nodeType, out width); } @@ -62,7 +62,7 @@ namespace XNodeEditor { // If we can't find field in the first run, it's probably a private field in a base class. FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); // Search base classes for private fields only. Public fields are found above - while (field == null && (type = type.BaseType) != typeof(XNode.Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + while (field == null && (type = type.BaseType) != typeof(Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); return field; } diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index 753973b..5102936 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -134,14 +134,14 @@ namespace XNodeEditor { /// Type to find compatiblities /// /// True if NodeType has some port with value type compatible - public static bool HasCompatiblePortType(Type nodeType, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { - Type findType = typeof(XNode.Node.InputAttribute); - if (direction == XNode.NodePort.IO.Output) - findType = typeof(XNode.Node.OutputAttribute); + public static bool HasCompatiblePortType(Type nodeType, Type compatibleType, NodePort.IO direction = NodePort.IO.Input) { + Type findType = typeof(Node.InputAttribute); + if (direction == NodePort.IO.Output) + findType = typeof(Node.OutputAttribute); //Get All fields from node type and we go filter only field with portAttribute. //This way is possible to know the values of the all ports and if have some with compatible value tue - foreach (FieldInfo f in XNode.NodeDataCache.GetNodeFields(nodeType)) { + foreach (FieldInfo f in NodeDataCache.GetNodeFields(nodeType)) { var portAttribute = f.GetCustomAttributes(findType, false).FirstOrDefault(); if (portAttribute != null) { if (IsCastableTo(f.FieldType, compatibleType)) { @@ -159,7 +159,7 @@ namespace XNodeEditor { /// List with all nodes type to filter /// Compatible Type to Filter /// Return Only Node Types with ports compatible, or an empty list - public static List GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { + public static List GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, NodePort.IO direction = NodePort.IO.Input) { //Result List List filteredTypes = new List(); diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index a7ec96b..aeee324 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -11,8 +11,8 @@ namespace XNodeEditor { public static NodeEditorWindow current; /// Stores node positions for all nodePorts. - public Dictionary portConnectionPoints { get { return _portConnectionPoints; } } - private Dictionary _portConnectionPoints = new Dictionary(); + public Dictionary portConnectionPoints { get { return _portConnectionPoints; } } + private Dictionary _portConnectionPoints = new Dictionary(); [SerializeField] private NodePortReference[] _references = new NodePortReference[0]; [SerializeField] private Rect[] _rects = new Rect[0]; @@ -25,15 +25,15 @@ namespace XNodeEditor { private Func _isDocked; [System.Serializable] private class NodePortReference { - [SerializeField] private XNode.Node _node; + [SerializeField] private Node _node; [SerializeField] private string _name; - public NodePortReference(XNode.NodePort nodePort) { + public NodePortReference(NodePort nodePort) { _node = nodePort.node; _name = nodePort.fieldName; } - public XNode.NodePort GetNodePort() { + public NodePort GetNodePort() { if (_node == null) { return null; } @@ -59,16 +59,16 @@ namespace XNodeEditor { int length = _references.Length; if (length == _rects.Length) { for (int i = 0; i < length; i++) { - XNode.NodePort nodePort = _references[i].GetNodePort(); + NodePort nodePort = _references[i].GetNodePort(); if (nodePort != null) _portConnectionPoints.Add(nodePort, _rects[i]); } } } - public Dictionary nodeSizes { get { return _nodeSizes; } } - private Dictionary _nodeSizes = new Dictionary(); - public XNode.NodeGraph graph; + public Dictionary nodeSizes { get { return _nodeSizes; } } + private Dictionary _nodeSizes = new Dictionary(); + public NodeGraph graph; public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } } private Vector2 _panOffset; public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, NodeEditorPreferences.GetSettings().minZoom, NodeEditorPreferences.GetSettings().maxZoom); Repaint(); } } @@ -97,7 +97,7 @@ namespace XNodeEditor { /// Handle Selection Change events private static void OnSelectionChanged() { - XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph; + NodeGraph nodeGraph = Selection.activeObject as NodeGraph; if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) { if (NodeEditorPreferences.GetSettings().openOnCreate) Open(nodeGraph); } @@ -132,7 +132,7 @@ namespace XNodeEditor { string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", ""); if (string.IsNullOrEmpty(path)) return; else { - XNode.NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath(path); + NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath(path); if (existingGraph != null) AssetDatabase.DeleteAsset(path); AssetDatabase.CreateAsset(graph, path); EditorUtility.SetDirty(graph); @@ -171,7 +171,7 @@ namespace XNodeEditor { return new Vector2(xOffset, yOffset); } - public void SelectNode(XNode.Node node, bool add) { + public void SelectNode(Node node, bool add) { if (add) { List selection = new List(Selection.objects); selection.Add(node); @@ -179,7 +179,7 @@ namespace XNodeEditor { } else Selection.objects = new Object[] { node }; } - public void DeselectNode(XNode.Node node) { + public void DeselectNode(Node node) { List selection = new List(Selection.objects); selection.Remove(node); Selection.objects = selection.ToArray(); @@ -187,7 +187,7 @@ namespace XNodeEditor { [OnOpenAsset(0)] public static bool OnOpen(int instanceID, int line) { - XNode.NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.NodeGraph; + NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as NodeGraph; if (nodeGraph != null) { Open(nodeGraph); return true; @@ -196,7 +196,7 @@ namespace XNodeEditor { } /// Open the provided graph in the NodeEditor - public static NodeEditorWindow Open(XNode.NodeGraph graph) { + public static NodeEditorWindow Open(NodeGraph graph) { if (!graph) return null; NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow; diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 60b0cdb..dcae580 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -8,8 +8,8 @@ using GenericMenu = XNodeEditor.AdvancedGenericMenu; namespace XNodeEditor { /// Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. - [CustomNodeGraphEditor(typeof(XNode.NodeGraph))] - public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase { + [CustomNodeGraphEditor(typeof(NodeGraph))] + public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase { [Obsolete("Use window.position instead")] public Rect position { get { return window.position; } set { window.position = value; } } /// Are we currently renaming a node? @@ -42,7 +42,7 @@ namespace XNodeEditor { /// Returns context node menu path. Null or empty strings for hidden nodes. public virtual string GetNodeMenuName(Type type) { //Check if type has the CreateNodeMenuAttribute - XNode.Node.CreateNodeMenuAttribute attrib; + Node.CreateNodeMenuAttribute attrib; if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path return attrib.menuName; else // Return generated path @@ -52,7 +52,7 @@ namespace XNodeEditor { /// The order by which the menu items are displayed. public virtual int GetNodeMenuOrder(Type type) { //Check if type has the CreateNodeMenuAttribute - XNode.Node.CreateNodeMenuAttribute attrib; + Node.CreateNodeMenuAttribute attrib; if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path return attrib.order; else @@ -62,7 +62,7 @@ namespace XNodeEditor { /// /// Called before connecting two ports in the graph view to see if the output port is compatible with the input port /// - public virtual bool CanConnect(XNode.NodePort output, XNode.NodePort input) { + public virtual bool CanConnect(NodePort output, NodePort input) { return output.CanConnectTo(input); } @@ -73,7 +73,7 @@ namespace XNodeEditor { /// /// Use it to filter only nodes with ports value type, compatible with this type /// Direction of the compatiblity - public virtual void AddContextMenuItems(GenericMenu menu, Type compatibleType = null, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { + public virtual void AddContextMenuItems(GenericMenu menu, Type compatibleType = null, NodePort.IO direction = NodePort.IO.Input) { Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition); Type[] nodeTypes; @@ -92,7 +92,7 @@ namespace XNodeEditor { if (string.IsNullOrEmpty(path)) continue; // Check if user is allowed to add more of given node type - XNode.Node.DisallowMultipleNodesAttribute disallowAttrib; + Node.DisallowMultipleNodesAttribute disallowAttrib; bool disallowed = false; if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib)) { int typeCount = target.nodes.Count(x => x.GetType() == type); @@ -102,7 +102,7 @@ namespace XNodeEditor { // Add node entry to context menu if (disallowed) menu.AddItem(new GUIContent(path), false, null); else menu.AddItem(new GUIContent(path), false, () => { - XNode.Node node = CreateNode(type, pos); + Node node = CreateNode(type, pos); if (node != null) NodeEditorWindow.current.AutoConnect(node); // handle null nodes to avoid nullref exceptions }); } @@ -116,7 +116,7 @@ namespace XNodeEditor { /// Returned gradient is used to color noodles /// The output this noodle comes from. Never null. /// The output this noodle comes from. Can be null if we are dragging the noodle. - public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input) { + public virtual Gradient GetNoodleGradient(NodePort output, NodePort input) { Gradient grad = new Gradient(); // If dragging the noodle, draw solid, slightly transparent @@ -147,20 +147,20 @@ namespace XNodeEditor { /// Returned float is used for noodle thickness /// The output this noodle comes from. Never null. /// The output this noodle comes from. Can be null if we are dragging the noodle. - public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input) { + public virtual float GetNoodleThickness(NodePort output, NodePort input) { return NodeEditorPreferences.GetSettings().noodleThickness; } - public virtual NoodlePath GetNoodlePath(XNode.NodePort output, XNode.NodePort input) { + public virtual NoodlePath GetNoodlePath(NodePort output, NodePort input) { return NodeEditorPreferences.GetSettings().noodlePath; } - public virtual NoodleStroke GetNoodleStroke(XNode.NodePort output, XNode.NodePort input) { + public virtual NoodleStroke GetNoodleStroke(NodePort output, NodePort input) { return NodeEditorPreferences.GetSettings().noodleStroke; } /// Returned color is used to color ports - public virtual Color GetPortColor(XNode.NodePort port) { + public virtual Color GetPortColor(NodePort port) { return GetTypeColor(port.ValueType); } @@ -174,8 +174,8 @@ namespace XNodeEditor { /// /// the owner of the style /// - public virtual GUIStyle GetPortStyle(XNode.NodePort port) { - if (port.direction == XNode.NodePort.IO.Input) + public virtual GUIStyle GetPortStyle(NodePort port) { + if (port.direction == NodePort.IO.Input) return NodeEditorResources.styles.inputPort; return NodeEditorResources.styles.outputPort; @@ -183,7 +183,7 @@ namespace XNodeEditor { /// The returned color is used to color the background of the door. /// Usually used for outer edge effect - public virtual Color GetPortBackgroundColor(XNode.NodePort port) { + public virtual Color GetPortBackgroundColor(NodePort port) { return Color.gray; } @@ -193,7 +193,7 @@ namespace XNodeEditor { } /// Override to display custom tooltips - public virtual string GetPortTooltip(XNode.NodePort port) { + public virtual string GetPortTooltip(NodePort port) { Type portType = port.ValueType; string tooltip = ""; tooltip = portType.PrettyName(); @@ -210,9 +210,9 @@ namespace XNodeEditor { } /// Create a node and save it in the graph asset - public virtual XNode.Node CreateNode(Type type, Vector2 position) { + public virtual Node CreateNode(Type type, Vector2 position) { Undo.RecordObject(target, "Create Node"); - XNode.Node node = target.AddNode(type); + Node node = target.AddNode(type); if (node == null) return null; // handle null nodes to avoid nullref exceptions Undo.RegisterCreatedObjectUndo(node, "Create Node"); node.position = position; @@ -224,9 +224,9 @@ namespace XNodeEditor { } /// Creates a copy of the original node in the graph - public virtual XNode.Node CopyNode(XNode.Node original) { + public virtual Node CopyNode(Node original) { Undo.RecordObject(target, "Duplicate Node"); - XNode.Node node = target.CopyNode(original); + Node node = target.CopyNode(original); Undo.RegisterCreatedObjectUndo(node, "Duplicate Node"); node.name = original.name; if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) AssetDatabase.AddObjectToAsset(node, target); @@ -235,11 +235,11 @@ namespace XNodeEditor { } /// Return false for nodes that can't be removed - public virtual bool CanRemove(XNode.Node node) { + public virtual bool CanRemove(Node node) { // Check graph attributes to see if this node is required Type graphType = target.GetType(); - XNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll( - graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute); + NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll( + graphType.GetCustomAttributes(typeof(NodeGraph.RequireNodeAttribute), true), x => x as NodeGraph.RequireNodeAttribute); if (attribs.Any(x => x.Requires(node.GetType()))) { if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) { return false; @@ -249,7 +249,7 @@ namespace XNodeEditor { } /// Safely remove a node and all its connections. - public virtual void RemoveNode(XNode.Node node) { + public virtual void RemoveNode(Node node) { if (!CanRemove(node)) return; // Remove the node @@ -265,7 +265,7 @@ namespace XNodeEditor { [AttributeUsage(AttributeTargets.Class)] public class CustomNodeGraphEditorAttribute : Attribute, - XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { + XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { private Type inspectedType; public string editorPrefsKey; /// Tells a NodeGraphEditor which Graph type it is an editor for diff --git a/Scripts/Editor/NodeGraphImporter.cs b/Scripts/Editor/NodeGraphImporter.cs index 3faf54f..70dcb3a 100644 --- a/Scripts/Editor/NodeGraphImporter.cs +++ b/Scripts/Editor/NodeGraphImporter.cs @@ -4,7 +4,6 @@ using System.Linq; using UnityEditor; using UnityEditor.Experimental.AssetImporters; using UnityEngine; -using XNode; namespace XNodeEditor { /// Deals with modified assets @@ -34,7 +33,7 @@ namespace XNodeEditor { private static void AddRequired(NodeGraph graph, Type type, ref Vector2 position) { if (!graph.nodes.Any(x => x.GetType() == type)) { - XNode.Node node = graph.AddNode(type); + Node node = graph.AddNode(type); node.position = position; position.x += 200; if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type); diff --git a/Scripts/Editor/NodeGroupEditor.cs b/Scripts/Editor/NodeGroupEditor.cs new file mode 100644 index 0000000..40e093d --- /dev/null +++ b/Scripts/Editor/NodeGroupEditor.cs @@ -0,0 +1,109 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace XNodeEditor.NodeGroups { + [CustomNodeEditor(typeof(NodeGroup))] + public class NodeGroupEditor : NodeEditor { + private NodeGroup group { get { return _group != null ? _group : _group = target as NodeGroup; } } + private NodeGroup _group; + public static Texture2D corner { get { return _corner != null ? _corner : _corner = Resources.Load("xnode_corner"); } } + private static Texture2D _corner; + private bool isDragging; + private Vector2 size; + + public override void OnBodyGUI() { + Event e = Event.current; + switch (e.type) { + case EventType.MouseDrag: + if (isDragging) { + group.width = Mathf.Max(200, (int) e.mousePosition.x + 16); + group.height = Mathf.Max(100, (int) e.mousePosition.y - 34); + NodeEditorWindow.current.Repaint(); + } + break; + case EventType.MouseDown: + // Ignore everything except left clicks + if (e.button != 0) return; + if (NodeEditorWindow.current.nodeSizes.TryGetValue(target, out size)) { + // Mouse position checking is in node local space + Rect lowerRight = new Rect(size.x - 34, size.y - 34, 30, 30); + if (lowerRight.Contains(e.mousePosition)) { + isDragging = true; + } + } + break; + case EventType.MouseUp: + isDragging = false; + // Select nodes inside the group + if (Selection.Contains(target)) { + List selection = Selection.objects.ToList(); + // Select Nodes + selection.AddRange(group.GetNodes()); + // Select Reroutes + foreach (Node node in target.graph.nodes) { + if (node != null) + { + foreach (NodePort port in node.Ports) { + for (int i = 0; i < port.ConnectionCount; i++) { + List reroutes = port.GetReroutePoints(i); + for (int k = 0; k < reroutes.Count; k++) { + Vector2 p = reroutes[k]; + if (p.x < group.position.x) continue; + if (p.y < group.position.y) continue; + if (p.x > group.position.x + group.width) continue; + if (p.y > group.position.y + group.height + 30) continue; + if (NodeEditorWindow.current.selectedReroutes.Any(x => x.port == port && x.connectionIndex == i && x.pointIndex == k)) continue; + NodeEditorWindow.current.selectedReroutes.Add( + new Internal.RerouteReference(port, i, k) + ); + } + } + } + } + else + { + continue; + } + } + Selection.objects = selection.Distinct().ToArray(); + } + break; + case EventType.Repaint: + // Move to bottom + if (target.graph.nodes.IndexOf(target) != 0) { + target.graph.nodes.Remove(target); + target.graph.nodes.Insert(0, target); + } + // Add scale cursors + if (NodeEditorWindow.current.nodeSizes.TryGetValue(target, out size)) { + Rect lowerRight = new Rect(target.position, new Vector2(30, 30)); + lowerRight.y += size.y - 34; + lowerRight.x += size.x - 34; + lowerRight = NodeEditorWindow.current.GridToWindowRect(lowerRight); + NodeEditorWindow.current.onLateGUI += () => AddMouseRect(lowerRight); + } + break; + } + + // Control height of node + GUILayout.Space(group.height); + + GUI.DrawTexture(new Rect(group.width - 34, group.height + 16, 24, 24), corner); + } + + public override int GetWidth() { + return group.width; + } + + public override Color GetTint() { + return group.color; + } + + public static void AddMouseRect(Rect rect) { + EditorGUIUtility.AddCursorRect(rect, MouseCursor.ResizeUpLeft); + } + } +} diff --git a/Scripts/Editor/RenamePopup.cs b/Scripts/Editor/RenamePopup.cs index ca1ee15..f992a03 100644 --- a/Scripts/Editor/RenamePopup.cs +++ b/Scripts/Editor/RenamePopup.cs @@ -52,9 +52,9 @@ namespace XNodeEditor { if (input == null || input.Trim() == "") { if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) { target.name = NodeEditorUtilities.NodeDefaultName(target.GetType()); - NodeEditor.GetEditor((XNode.Node)target, NodeEditorWindow.current).OnRename(); + NodeEditor.GetEditor((Node)target, NodeEditorWindow.current).OnRename(); if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) { - AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target)); + AssetDatabase.SetMainObject((target as Node).graph, AssetDatabase.GetAssetPath(target)); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); } Close(); @@ -65,9 +65,9 @@ namespace XNodeEditor { else { if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) { target.name = input; - NodeEditor.GetEditor((XNode.Node)target, NodeEditorWindow.current).OnRename(); + NodeEditor.GetEditor((Node)target, NodeEditorWindow.current).OnRename(); if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) { - AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target)); + AssetDatabase.SetMainObject((target as Node).graph, AssetDatabase.GetAssetPath(target)); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); } Close(); diff --git a/Scripts/Editor/SceneGraphEditor.cs b/Scripts/Editor/SceneGraphEditor.cs index dd290a8..52e1c50 100644 --- a/Scripts/Editor/SceneGraphEditor.cs +++ b/Scripts/Editor/SceneGraphEditor.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; -using XNode; namespace XNodeEditor { [CustomEditor(typeof(SceneGraph), true)] diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 62a2f4a..3e38db4 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -2,420 +2,418 @@ using System.Collections.Generic; using UnityEngine; -namespace XNode { - /// - /// Base class for all nodes - /// - /// - /// Classes extending this class will be considered as valid nodes by xNode. - /// - /// [System.Serializable] - /// public class Adder : Node { - /// [Input] public float a; - /// [Input] public float b; - /// [Output] public float result; - /// - /// // GetValue should be overridden to return a value for any specified output port - /// public override object GetValue(NodePort port) { - /// return a + b; - /// } - /// } - /// - /// - [Serializable] - public abstract class Node : ScriptableObject { - /// Used by and to determine when to display the field value associated with a - public enum ShowBackingValue { - /// Never show the backing value - Never, - /// Show the backing value only when the port does not have any active connections - Unconnected, - /// Always show the backing value - Always +/// +/// Base class for all nodes +/// +/// +/// Classes extending this class will be considered as valid nodes by xNode. +/// +/// [System.Serializable] +/// public class Adder : Node { +/// [Input] public float a; +/// [Input] public float b; +/// [Output] public float result; +/// +/// // GetValue should be overridden to return a value for any specified output port +/// public override object GetValue(NodePort port) { +/// return a + b; +/// } +/// } +/// +/// +[Serializable] +public abstract class Node : ScriptableObject { + /// Used by and to determine when to display the field value associated with a + public enum ShowBackingValue { + /// Never show the backing value + Never, + /// Show the backing value only when the port does not have any active connections + Unconnected, + /// Always show the backing value + Always + } + + public enum ConnectionType { + /// Allow multiple connections + Multiple, + /// always override the current connection + Override, + } + + /// Tells which types of input to allow + public enum TypeConstraint { + /// Allow all types of input + None, + /// Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object) + Inherited, + /// Allow only similar types + Strict, + /// Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject) + InheritedInverse, + /// Allow connections where output value type is assignable from input value or input value type is assignable from output value type + InheritedAny + } + + #region Obsolete + [Obsolete("Use DynamicPorts instead")] + public IEnumerable InstancePorts { get { return DynamicPorts; } } + + [Obsolete("Use DynamicOutputs instead")] + public IEnumerable InstanceOutputs { get { return DynamicOutputs; } } + + [Obsolete("Use DynamicInputs instead")] + public IEnumerable InstanceInputs { get { return DynamicInputs; } } + + [Obsolete("Use AddDynamicInput instead")] + public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddDynamicInput(type, connectionType, typeConstraint, fieldName); + } + + [Obsolete("Use AddDynamicOutput instead")] + public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddDynamicOutput(type, connectionType, typeConstraint, fieldName); + } + + [Obsolete("Use AddDynamicPort instead")] + private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName); + } + + [Obsolete("Use RemoveDynamicPort instead")] + public void RemoveInstancePort(string fieldName) { + RemoveDynamicPort(fieldName); + } + + [Obsolete("Use RemoveDynamicPort instead")] + public void RemoveInstancePort(NodePort port) { + RemoveDynamicPort(port); + } + + [Obsolete("Use ClearDynamicPorts instead")] + public void ClearInstancePorts() { + ClearDynamicPorts(); + } + #endregion + + /// Iterate over all ports on this node. + public IEnumerable Ports { get { foreach (NodePort port in ports.Values) yield return port; } } + /// Iterate over all outputs on this node. + public IEnumerable Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } } + /// Iterate over all inputs on this node. + public IEnumerable Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } } + /// Iterate over all dynamic ports on this node. + public IEnumerable DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } } + /// Iterate over all dynamic outputs on this node. + public IEnumerable DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } } + /// Iterate over all dynamic inputs on this node. + public IEnumerable DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } } + /// Parent + [SerializeField] public NodeGraph graph; + /// Position on the + [SerializeField] public Vector2 position; + /// It is recommended not to modify these at hand. Instead, see and + [SerializeField] private NodePortDictionary ports = new NodePortDictionary(); + + /// Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable + public static NodeGraph graphHotfix; + + protected void OnEnable() { + if (graphHotfix != null) graph = graphHotfix; + graphHotfix = null; + UpdatePorts(); + Init(); + } + + /// Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. This happens automatically on enable or on redrawing a dynamic port list. + public void UpdatePorts() { + NodeDataCache.UpdatePorts(this, ports); + } + + /// Initialize node. Called on enable. + protected virtual void Init() { } + + /// Checks all connections for invalid references, and removes them. + public void VerifyConnections() { + foreach (NodePort port in Ports) port.VerifyConnections(); + } + + #region Dynamic Ports + /// Convenience function. + /// + /// + public NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName); + } + + /// Convenience function. + /// + /// + public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName); + } + + /// Add a dynamic, serialized port to this node. + /// + /// + private NodePort AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + if (fieldName == null) { + fieldName = "dynamicInput_0"; + int i = 0; + while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i); + } else if (HasPort(fieldName)) { + Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this); + return ports[fieldName]; } + NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this); + ports.Add(fieldName, port); + return port; + } - public enum ConnectionType { - /// Allow multiple connections - Multiple, - /// always override the current connection - Override, - } + /// Remove an dynamic port from the node + public void RemoveDynamicPort(string fieldName) { + NodePort dynamicPort = GetPort(fieldName); + if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist"); + RemoveDynamicPort(GetPort(fieldName)); + } - /// Tells which types of input to allow - public enum TypeConstraint { - /// Allow all types of input - None, - /// Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object) - Inherited, - /// Allow only similar types - Strict, - /// Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject) - InheritedInverse, - /// Allow connections where output value type is assignable from input value or input value type is assignable from output value type - InheritedAny - } + /// Remove an dynamic port from the node + public void RemoveDynamicPort(NodePort port) { + if (port == null) throw new ArgumentNullException("port"); + else if (port.IsStatic) throw new ArgumentException("cannot remove static port"); + port.ClearConnections(); + ports.Remove(port.fieldName); + } -#region Obsolete - [Obsolete("Use DynamicPorts instead")] - public IEnumerable InstancePorts { get { return DynamicPorts; } } - - [Obsolete("Use DynamicOutputs instead")] - public IEnumerable InstanceOutputs { get { return DynamicOutputs; } } - - [Obsolete("Use DynamicInputs instead")] - public IEnumerable InstanceInputs { get { return DynamicInputs; } } - - [Obsolete("Use AddDynamicInput instead")] - public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { - return AddDynamicInput(type, connectionType, typeConstraint, fieldName); - } - - [Obsolete("Use AddDynamicOutput instead")] - public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { - return AddDynamicOutput(type, connectionType, typeConstraint, fieldName); - } - - [Obsolete("Use AddDynamicPort instead")] - private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { - return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName); - } - - [Obsolete("Use RemoveDynamicPort instead")] - public void RemoveInstancePort(string fieldName) { - RemoveDynamicPort(fieldName); - } - - [Obsolete("Use RemoveDynamicPort instead")] - public void RemoveInstancePort(NodePort port) { + /// Removes all dynamic ports from the node + [ContextMenu("Clear Dynamic Ports")] + public void ClearDynamicPorts() { + List dynamicPorts = new List(DynamicPorts); + foreach (NodePort port in dynamicPorts) { RemoveDynamicPort(port); } + } + #endregion - [Obsolete("Use ClearDynamicPorts instead")] - public void ClearInstancePorts() { - ClearDynamicPorts(); - } -#endregion + #region Ports + /// Returns output port which matches fieldName + public NodePort GetOutputPort(string fieldName) { + NodePort port = GetPort(fieldName); + if (port == null || port.direction != NodePort.IO.Output) return null; + else return port; + } - /// Iterate over all ports on this node. - public IEnumerable Ports { get { foreach (NodePort port in ports.Values) yield return port; } } - /// Iterate over all outputs on this node. - public IEnumerable Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } } - /// Iterate over all inputs on this node. - public IEnumerable Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } } - /// Iterate over all dynamic ports on this node. - public IEnumerable DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } } - /// Iterate over all dynamic outputs on this node. - public IEnumerable DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } } - /// Iterate over all dynamic inputs on this node. - public IEnumerable DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } } - /// Parent - [SerializeField] public NodeGraph graph; - /// Position on the - [SerializeField] public Vector2 position; - /// It is recommended not to modify these at hand. Instead, see and - [SerializeField] private NodePortDictionary ports = new NodePortDictionary(); + /// Returns input port which matches fieldName + public NodePort GetInputPort(string fieldName) { + NodePort port = GetPort(fieldName); + if (port == null || port.direction != NodePort.IO.Input) return null; + else return port; + } - /// Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable - public static NodeGraph graphHotfix; + /// Returns port which matches fieldName + public NodePort GetPort(string fieldName) { + NodePort port; + if (ports.TryGetValue(fieldName, out port)) return port; + else return null; + } - protected void OnEnable() { - if (graphHotfix != null) graph = graphHotfix; - graphHotfix = null; - UpdatePorts(); - Init(); - } + public bool HasPort(string fieldName) { + return ports.ContainsKey(fieldName); + } + #endregion - /// Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. This happens automatically on enable or on redrawing a dynamic port list. - public void UpdatePorts() { - NodeDataCache.UpdatePorts(this, ports); - } + #region Inputs/Outputs + /// Return input value for a specified port. Returns fallback value if no ports are connected + /// Field name of requested input port + /// If no ports are connected, this value will be returned + public T GetInputValue(string fieldName, T fallback = default(T)) { + NodePort port = GetPort(fieldName); + if (port != null && port.IsConnected) return port.GetInputValue(); + else return fallback; + } - /// Initialize node. Called on enable. - protected virtual void Init() { } + /// Return all input values for a specified port. Returns fallback value if no ports are connected + /// Field name of requested input port + /// If no ports are connected, this value will be returned + public T[] GetInputValues(string fieldName, params T[] fallback) { + NodePort port = GetPort(fieldName); + if (port != null && port.IsConnected) return port.GetInputValues(); + else return fallback; + } - /// Checks all connections for invalid references, and removes them. - public void VerifyConnections() { - foreach (NodePort port in Ports) port.VerifyConnections(); - } + /// Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. + /// The requested port. + public virtual object GetValue(NodePort port) { + Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType()); + return null; + } + #endregion -#region Dynamic Ports - /// Convenience function. - /// - /// - public NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { - return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName); - } + /// Called after a connection between two s is created + /// Output Input + public virtual void OnCreateConnection(NodePort from, NodePort to) { } - /// Convenience function. - /// - /// - public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { - return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName); - } + /// Called after a connection is removed from this port + /// Output or Input + public virtual void OnRemoveConnection(NodePort port) { } - /// Add a dynamic, serialized port to this node. - /// - /// - private NodePort AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { - if (fieldName == null) { - fieldName = "dynamicInput_0"; - int i = 0; - while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i); - } else if (HasPort(fieldName)) { - Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this); - return ports[fieldName]; - } - NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this); - ports.Add(fieldName, port); - return port; - } + /// Disconnect everything from this node + public void ClearConnections() { + foreach (NodePort port in Ports) port.ClearConnections(); + } - /// Remove an dynamic port from the node - public void RemoveDynamicPort(string fieldName) { - NodePort dynamicPort = GetPort(fieldName); - if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist"); - RemoveDynamicPort(GetPort(fieldName)); - } + #region Attributes + /// Mark a serializable field as an input port. You can access this through + [AttributeUsage(AttributeTargets.Field)] + public class InputAttribute : Attribute { + public ShowBackingValue backingValue; + public ConnectionType connectionType; + [Obsolete("Use dynamicPortList instead")] + public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } } + public bool dynamicPortList; + public TypeConstraint typeConstraint; - /// Remove an dynamic port from the node - public void RemoveDynamicPort(NodePort port) { - if (port == null) throw new ArgumentNullException("port"); - else if (port.IsStatic) throw new ArgumentException("cannot remove static port"); - port.ClearConnections(); - ports.Remove(port.fieldName); - } - - /// Removes all dynamic ports from the node - [ContextMenu("Clear Dynamic Ports")] - public void ClearDynamicPorts() { - List dynamicPorts = new List(DynamicPorts); - foreach (NodePort port in dynamicPorts) { - RemoveDynamicPort(port); - } - } -#endregion - -#region Ports - /// Returns output port which matches fieldName - public NodePort GetOutputPort(string fieldName) { - NodePort port = GetPort(fieldName); - if (port == null || port.direction != NodePort.IO.Output) return null; - else return port; - } - - /// Returns input port which matches fieldName - public NodePort GetInputPort(string fieldName) { - NodePort port = GetPort(fieldName); - if (port == null || port.direction != NodePort.IO.Input) return null; - else return port; - } - - /// Returns port which matches fieldName - public NodePort GetPort(string fieldName) { - NodePort port; - if (ports.TryGetValue(fieldName, out port)) return port; - else return null; - } - - public bool HasPort(string fieldName) { - return ports.ContainsKey(fieldName); - } -#endregion - -#region Inputs/Outputs - /// Return input value for a specified port. Returns fallback value if no ports are connected - /// Field name of requested input port - /// If no ports are connected, this value will be returned - public T GetInputValue(string fieldName, T fallback = default(T)) { - NodePort port = GetPort(fieldName); - if (port != null && port.IsConnected) return port.GetInputValue(); - else return fallback; - } - - /// Return all input values for a specified port. Returns fallback value if no ports are connected - /// Field name of requested input port - /// If no ports are connected, this value will be returned - public T[] GetInputValues(string fieldName, params T[] fallback) { - NodePort port = GetPort(fieldName); - if (port != null && port.IsConnected) return port.GetInputValues(); - else return fallback; - } - - /// Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. - /// The requested port. - public virtual object GetValue(NodePort port) { - Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType()); - return null; - } -#endregion - - /// Called after a connection between two s is created - /// Output Input - public virtual void OnCreateConnection(NodePort from, NodePort to) { } - - /// Called after a connection is removed from this port - /// Output or Input - public virtual void OnRemoveConnection(NodePort port) { } - - /// Disconnect everything from this node - public void ClearConnections() { - foreach (NodePort port in Ports) port.ClearConnections(); - } - -#region Attributes /// Mark a serializable field as an input port. You can access this through - [AttributeUsage(AttributeTargets.Field)] - public class InputAttribute : Attribute { - public ShowBackingValue backingValue; - public ConnectionType connectionType; - [Obsolete("Use dynamicPortList instead")] - public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } } - public bool dynamicPortList; - public TypeConstraint typeConstraint; + /// Should we display the backing value for this port as an editor field? + /// Should we allow multiple connections? + /// Constrains which input connections can be made to this port + /// If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays + public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) { + this.backingValue = backingValue; + this.connectionType = connectionType; + this.dynamicPortList = dynamicPortList; + this.typeConstraint = typeConstraint; + } + } - /// Mark a serializable field as an input port. You can access this through - /// Should we display the backing value for this port as an editor field? - /// Should we allow multiple connections? - /// Constrains which input connections can be made to this port - /// If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays - public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) { - this.backingValue = backingValue; - this.connectionType = connectionType; - this.dynamicPortList = dynamicPortList; - this.typeConstraint = typeConstraint; - } + /// Mark a serializable field as an output port. You can access this through + [AttributeUsage(AttributeTargets.Field)] + public class OutputAttribute : Attribute { + public ShowBackingValue backingValue; + public ConnectionType connectionType; + [Obsolete("Use dynamicPortList instead")] + public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } } + public bool dynamicPortList; + public TypeConstraint typeConstraint; + + /// Mark a serializable field as an output port. You can access this through + /// Should we display the backing value for this port as an editor field? + /// Should we allow multiple connections? + /// Constrains which input connections can be made from this port + /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays + public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) { + this.backingValue = backingValue; + this.connectionType = connectionType; + this.dynamicPortList = dynamicPortList; + this.typeConstraint = typeConstraint; } /// Mark a serializable field as an output port. You can access this through - [AttributeUsage(AttributeTargets.Field)] - public class OutputAttribute : Attribute { - public ShowBackingValue backingValue; - public ConnectionType connectionType; - [Obsolete("Use dynamicPortList instead")] - public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } } - public bool dynamicPortList; - public TypeConstraint typeConstraint; + /// Should we display the backing value for this port as an editor field? + /// Should we allow multiple connections? + /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays + [Obsolete("Use constructor with TypeConstraint")] + public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList) { } + } - /// Mark a serializable field as an output port. You can access this through - /// Should we display the backing value for this port as an editor field? - /// Should we allow multiple connections? - /// Constrains which input connections can be made from this port - /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays - public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) { - this.backingValue = backingValue; - this.connectionType = connectionType; - this.dynamicPortList = dynamicPortList; - this.typeConstraint = typeConstraint; - } - - /// Mark a serializable field as an output port. You can access this through - /// Should we display the backing value for this port as an editor field? - /// Should we allow multiple connections? - /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays - [Obsolete("Use constructor with TypeConstraint")] - public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList) { } + /// Manually supply node class with a context menu path + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class CreateNodeMenuAttribute : Attribute { + public string menuName; + public int order; + /// Manually supply node class with a context menu path + /// Path to this node in the context menu. Null or empty hides it. + public CreateNodeMenuAttribute(string menuName) { + this.menuName = menuName; + this.order = 0; } /// Manually supply node class with a context menu path - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class CreateNodeMenuAttribute : Attribute { - public string menuName; - public int order; - /// Manually supply node class with a context menu path - /// Path to this node in the context menu. Null or empty hides it. - public CreateNodeMenuAttribute(string menuName) { - this.menuName = menuName; - this.order = 0; - } - - /// Manually supply node class with a context menu path - /// Path to this node in the context menu. Null or empty hides it. - /// The order by which the menu items are displayed. - public CreateNodeMenuAttribute(string menuName, int order) { - this.menuName = menuName; - this.order = order; - } + /// Path to this node in the context menu. Null or empty hides it. + /// The order by which the menu items are displayed. + public CreateNodeMenuAttribute(string menuName, int order) { + this.menuName = menuName; + this.order = order; } + } + /// Prevents Node of the same type to be added more than once (configurable) to a NodeGraph + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class DisallowMultipleNodesAttribute : Attribute { + // TODO: Make inheritance work in such a way that applying [DisallowMultipleNodes(1)] to type NodeBar : Node + // while type NodeFoo : NodeBar exists, will let you add *either one* of these nodes, but not both. + public int max; /// Prevents Node of the same type to be added more than once (configurable) to a NodeGraph - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class DisallowMultipleNodesAttribute : Attribute { - // TODO: Make inheritance work in such a way that applying [DisallowMultipleNodes(1)] to type NodeBar : Node - // while type NodeFoo : NodeBar exists, will let you add *either one* of these nodes, but not both. - public int max; - /// Prevents Node of the same type to be added more than once (configurable) to a NodeGraph - /// How many nodes to allow. Defaults to 1. - public DisallowMultipleNodesAttribute(int max = 1) { - this.max = max; - } + /// How many nodes to allow. Defaults to 1. + public DisallowMultipleNodesAttribute(int max = 1) { + this.max = max; + } + } + + /// Specify a color for this node type + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class NodeTintAttribute : Attribute { + public Color color; + /// Specify a color for this node type + /// Red [0.0f .. 1.0f] + /// Green [0.0f .. 1.0f] + /// Blue [0.0f .. 1.0f] + public NodeTintAttribute(float r, float g, float b) { + color = new Color(r, g, b); } /// Specify a color for this node type - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class NodeTintAttribute : Attribute { - public Color color; - /// Specify a color for this node type - /// Red [0.0f .. 1.0f] - /// Green [0.0f .. 1.0f] - /// Blue [0.0f .. 1.0f] - public NodeTintAttribute(float r, float g, float b) { - color = new Color(r, g, b); - } - - /// Specify a color for this node type - /// HEX color value - public NodeTintAttribute(string hex) { - ColorUtility.TryParseHtmlString(hex, out color); - } - - /// Specify a color for this node type - /// Red [0 .. 255] - /// Green [0 .. 255] - /// Blue [0 .. 255] - public NodeTintAttribute(byte r, byte g, byte b) { - color = new Color32(r, g, b, byte.MaxValue); - } + /// HEX color value + public NodeTintAttribute(string hex) { + ColorUtility.TryParseHtmlString(hex, out color); } - /// Specify a width for this node type - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class NodeWidthAttribute : Attribute { - public int width; - /// Specify a width for this node type - /// Width - public NodeWidthAttribute(int width) { - this.width = width; - } - } -#endregion - - [Serializable] private class NodePortDictionary : Dictionary, ISerializationCallbackReceiver { - [SerializeField] private List keys = new List(); - [SerializeField] private List values = new List(); - - public void OnBeforeSerialize() { - keys.Clear(); - values.Clear(); - keys.Capacity = this.Count; - values.Capacity = this.Count; - foreach (KeyValuePair pair in this) { - keys.Add(pair.Key); - values.Add(pair.Value); - } - } - - public void OnAfterDeserialize() { - this.Clear(); -#if UNITY_2021_3_OR_NEWER - this.EnsureCapacity(keys.Count); -#endif - - if (keys.Count != values.Count) - throw new System.Exception("there are " + keys.Count + " keys and " + values.Count + " 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]); - } + /// Specify a color for this node type + /// Red [0 .. 255] + /// Green [0 .. 255] + /// Blue [0 .. 255] + public NodeTintAttribute(byte r, byte g, byte b) { + color = new Color32(r, g, b, byte.MaxValue); } } -} + + /// Specify a width for this node type + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class NodeWidthAttribute : Attribute { + public int width; + /// Specify a width for this node type + /// Width + public NodeWidthAttribute(int width) { + this.width = width; + } + } + #endregion + + [Serializable] private class NodePortDictionary : Dictionary, ISerializationCallbackReceiver { + [SerializeField] private List keys = new List(); + [SerializeField] private List values = new List(); + + public void OnBeforeSerialize() { + keys.Clear(); + values.Clear(); + keys.Capacity = this.Count; + values.Capacity = this.Count; + foreach (KeyValuePair pair in this) { + keys.Add(pair.Key); + values.Add(pair.Value); + } + } + + public void OnAfterDeserialize() { + this.Clear(); +#if UNITY_2021_3_OR_NEWER + this.EnsureCapacity(keys.Count); +#endif + + if (keys.Count != values.Count) + throw new System.Exception("there are " + keys.Count + " keys and " + values.Count + " 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]); + } + } +} \ No newline at end of file diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index 4f4937d..fdce868 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -3,224 +3,222 @@ 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 Dictionary> formerlySerializedAsCache; - private static Dictionary typeQualifiedNameCache; - private static bool Initialized { get { return portDataCache != null; } } +/// Precaches reflection data in editor so we won't have to do it runtime +public static class NodeDataCache { + private static PortDataCache portDataCache; + private static Dictionary> formerlySerializedAsCache; + private static Dictionary typeQualifiedNameCache; + private static bool Initialized { get { return portDataCache != null; } } - public static string GetTypeQualifiedName(System.Type type) { - if(typeQualifiedNameCache == null) typeQualifiedNameCache = new Dictionary(); + public static string GetTypeQualifiedName(System.Type type) { + if(typeQualifiedNameCache == null) typeQualifiedNameCache = new Dictionary(); - string name; - if (!typeQualifiedNameCache.TryGetValue(type, out name)) { - name = type.AssemblyQualifiedName; - typeQualifiedNameCache.Add(type, name); - } - return name; + string name; + if (!typeQualifiedNameCache.TryGetValue(type, out name)) { + name = type.AssemblyQualifiedName; + typeQualifiedNameCache.Add(type, name); } + return name; + } - /// Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. - public static void UpdatePorts(Node node, Dictionary ports) { - if (!Initialized) BuildCache(); + /// Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. + public static void UpdatePorts(Node node, Dictionary ports) { + if (!Initialized) BuildCache(); - Dictionary> removedPorts = new Dictionary>(); - System.Type nodeType = node.GetType(); + Dictionary> removedPorts = new Dictionary>(); + System.Type nodeType = node.GetType(); - Dictionary formerlySerializedAs = null; - if (formerlySerializedAsCache != null) formerlySerializedAsCache.TryGetValue(nodeType, out formerlySerializedAs); + Dictionary formerlySerializedAs = null; + if (formerlySerializedAsCache != null) formerlySerializedAsCache.TryGetValue(nodeType, out formerlySerializedAs); - List dynamicListPorts = new List(); + List dynamicListPorts = new List(); - Dictionary staticPorts; - if (!portDataCache.TryGetValue(nodeType, out staticPorts)) { - staticPorts = new Dictionary(); - } - - // 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.ToArray()) { - // 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) { - //See if the field is tagged with FormerlySerializedAs, if so add the port with its new field name to removedPorts - // so it can be reconnected in missing ports stage. - string newName = null; - if (formerlySerializedAs != null && formerlySerializedAs.TryGetValue(port.fieldName, out newName)) removedPorts.Add(newName, port.GetConnections()); + Dictionary staticPorts; + if (!portDataCache.TryGetValue(nodeType, out staticPorts)) { + staticPorts = new Dictionary(); + } + // 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.ToArray()) { + // 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); - } - // 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); - } + } else port.ValueType = staticPort.ValueType; } - // 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 reconnectConnections; - if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) { - for (int i = 0; i < reconnectConnections.Count; i++) { - NodePort connection = reconnectConnections[i]; - if (connection == null) continue; - // CAVEAT: Ports connected under special conditions defined in graphEditor.CanConnect overrides will not auto-connect. - // To fix this, this code would need to be moved to an editor script and call graphEditor.CanConnect instead of port.CanConnectTo. - // This is only a problem in the rare edge case where user is using non-standard CanConnect overrides and changes port type of an already connected port - if (port.CanConnectTo(connection)) port.Connect(connection); - } - } - ports.Add(staticPort.fieldName, port); - } + // If port doesn't exist anymore, remove it + else if (port.IsStatic) { + //See if the field is tagged with FormerlySerializedAs, if so add the port with its new field name to removedPorts + // so it can be reconnected in missing ports stage. + string newName = null; + if (formerlySerializedAs != null && formerlySerializedAs.TryGetValue(port.fieldName, out newName)) removedPorts.Add(newName, port.GetConnections()); + + port.ClearConnections(); + ports.Remove(port.fieldName); } - - // 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.Substring(0, listPort.fieldName.IndexOf(' ')); - 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; + // 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); } } - - /// - /// 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. - /// - 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; - } - - /// Returns true if the given port is in a dynamic port list. - 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; - }); - } - - /// Cache node types - private static void BuildCache() { - portDataCache = new PortDataCache(); - System.Type baseType = typeof(Node); - List nodeTypes = new List(); - System.Reflection.Assembly[] assemblies = System.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; - int index = assemblyName.IndexOf('.'); - if (index != -1) assemblyName = assemblyName.Substring(0, index); - switch (assemblyName) { - // The following assemblies, and sub-assemblies (eg. UnityEngine.UI) are skipped - case "UnityEditor": - case "UnityEngine": - case "Unity": - case "System": - case "mscorlib": - case "Microsoft": - continue; - default: - nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); - break; - } - } - - for (int i = 0; i < nodeTypes.Count; i++) { - CachePorts(nodeTypes[i]); - } - } - - public static List GetNodeFields(System.Type nodeType) { - List fieldInfo = new List(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[] parentFields = tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); - for (int i = 0; i < parentFields.Length; i++) { - // Ensure that we do not already have a member with this type and name - FieldInfo parentField = parentFields[i]; - if (fieldInfo.TrueForAll(x => x.Name != parentField.Name)) { - fieldInfo.Add(parentField); + // 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 reconnectConnections; + if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) { + for (int i = 0; i < reconnectConnections.Count; i++) { + NodePort connection = reconnectConnections[i]; + if (connection == null) continue; + // CAVEAT: Ports connected under special conditions defined in graphEditor.CanConnect overrides will not auto-connect. + // To fix this, this code would need to be moved to an editor script and call graphEditor.CanConnect instead of port.CanConnectTo. + // This is only a problem in the rare edge case where user is using non-standard CanConnect overrides and changes port type of an already connected port + if (port.CanConnectTo(connection)) port.Connect(connection); } } - } - return fieldInfo; - } - - private static void CachePorts(System.Type nodeType) { - List 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; - UnityEngine.Serialization.FormerlySerializedAsAttribute formerlySerializedAsAttribute = attribs.FirstOrDefault(x => x is UnityEngine.Serialization.FormerlySerializedAsAttribute) as UnityEngine.Serialization.FormerlySerializedAsAttribute; - - 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 Dictionary()); - NodePort port = new NodePort(fieldInfo[i]); - portDataCache[nodeType].Add(port.fieldName, port); - } - - if (formerlySerializedAsAttribute != null) { - if (formerlySerializedAsCache == null) formerlySerializedAsCache = new Dictionary>(); - if (!formerlySerializedAsCache.ContainsKey(nodeType)) formerlySerializedAsCache.Add(nodeType, new Dictionary()); - - if (formerlySerializedAsCache[nodeType].ContainsKey(formerlySerializedAsAttribute.oldName)) Debug.LogError("Another FormerlySerializedAs with value '" + formerlySerializedAsAttribute.oldName + "' already exist on this node."); - else formerlySerializedAsCache[nodeType].Add(formerlySerializedAsAttribute.oldName, fieldInfo[i].Name); - } + ports.Add(staticPort.fieldName, port); } } - [System.Serializable] - private class PortDataCache : Dictionary> { } + // 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.Substring(0, listPort.fieldName.IndexOf(' ')); + 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; + } } -} + + /// + /// 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. + /// + 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; + } + + /// Returns true if the given port is in a dynamic port list. + 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; + }); + } + + /// Cache node types + private static void BuildCache() { + portDataCache = new PortDataCache(); + System.Type baseType = typeof(Node); + List nodeTypes = new List(); + System.Reflection.Assembly[] assemblies = System.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; + int index = assemblyName.IndexOf('.'); + if (index != -1) assemblyName = assemblyName.Substring(0, index); + switch (assemblyName) { + // The following assemblies, and sub-assemblies (eg. UnityEngine.UI) are skipped + case "UnityEditor": + case "UnityEngine": + case "Unity": + case "System": + case "mscorlib": + case "Microsoft": + continue; + default: + nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); + break; + } + } + + for (int i = 0; i < nodeTypes.Count; i++) { + CachePorts(nodeTypes[i]); + } + } + + public static List GetNodeFields(System.Type nodeType) { + List fieldInfo = new List(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(Node)) { + FieldInfo[] parentFields = tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); + for (int i = 0; i < parentFields.Length; i++) { + // Ensure that we do not already have a member with this type and name + FieldInfo parentField = parentFields[i]; + if (fieldInfo.TrueForAll(x => x.Name != parentField.Name)) { + fieldInfo.Add(parentField); + } + } + } + return fieldInfo; + } + + private static void CachePorts(System.Type nodeType) { + List 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; + UnityEngine.Serialization.FormerlySerializedAsAttribute formerlySerializedAsAttribute = attribs.FirstOrDefault(x => x is UnityEngine.Serialization.FormerlySerializedAsAttribute) as UnityEngine.Serialization.FormerlySerializedAsAttribute; + + 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 Dictionary()); + NodePort port = new NodePort(fieldInfo[i]); + portDataCache[nodeType].Add(port.fieldName, port); + } + + if (formerlySerializedAsAttribute != null) { + if (formerlySerializedAsCache == null) formerlySerializedAsCache = new Dictionary>(); + if (!formerlySerializedAsCache.ContainsKey(nodeType)) formerlySerializedAsCache.Add(nodeType, new Dictionary()); + + if (formerlySerializedAsCache[nodeType].ContainsKey(formerlySerializedAsAttribute.oldName)) Debug.LogError("Another FormerlySerializedAs with value '" + formerlySerializedAsAttribute.oldName + "' already exist on this node."); + else formerlySerializedAsCache[nodeType].Add(formerlySerializedAsAttribute.oldName, fieldInfo[i].Name); + } + } + } + + [System.Serializable] + private class PortDataCache : Dictionary> { } +} \ No newline at end of file diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index 347bc8e..3083d34 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -2,123 +2,121 @@ using System.Collections.Generic; using UnityEngine; -namespace XNode { - /// Base class for all node graphs - [Serializable] - public abstract class NodeGraph : ScriptableObject { +/// Base class for all node graphs +[Serializable] +public abstract class NodeGraph : ScriptableObject { - /// All nodes in the graph. - /// See: - [SerializeField] public List nodes = new List(); + /// All nodes in the graph. + /// See: + [SerializeField] public List nodes = new List(); - /// Add a node to the graph by type (convenience method - will call the System.Type version) - public T AddNode() where T : Node { - return AddNode(typeof(T)) as T; - } - - /// Add a node to the graph by type - public virtual Node AddNode(Type type) { - Node.graphHotfix = this; - Node node = ScriptableObject.CreateInstance(type) as Node; - node.graph = this; - nodes.Add(node); - return node; - } - - /// Creates a copy of the original node in the graph - public virtual Node CopyNode(Node original) { - Node.graphHotfix = this; - Node node = ScriptableObject.Instantiate(original); - node.graph = this; - node.ClearConnections(); - nodes.Add(node); - return node; - } - - /// Safely remove a node and all its connections - /// The node to remove - public virtual void RemoveNode(Node node) { - node.ClearConnections(); - nodes.Remove(node); - if (Application.isPlaying) Destroy(node); - } - - /// Remove all nodes and connections from the graph - public virtual void Clear() { - if (Application.isPlaying) { - for (int i = 0; i < nodes.Count; i++) { - if (nodes[i] != null) Destroy(nodes[i]); - } - } - nodes.Clear(); - } - - /// Create a new deep copy of this graph - public virtual XNode.NodeGraph Copy() { - // Instantiate a new nodegraph instance - NodeGraph graph = Instantiate(this); - // Instantiate all nodes inside the graph - for (int i = 0; i < nodes.Count; i++) { - if (nodes[i] == null) continue; - Node.graphHotfix = graph; - Node node = Instantiate(nodes[i]) as Node; - node.graph = graph; - graph.nodes[i] = node; - } - - // Redirect all connections - for (int i = 0; i < graph.nodes.Count; i++) { - if (graph.nodes[i] == null) continue; - foreach (NodePort port in graph.nodes[i].Ports) { - port.Redirect(nodes, graph.nodes); - } - } - - return graph; - } - - protected virtual void OnDestroy() { - // Remove all nodes prior to graph destruction - Clear(); - } - -#region Attributes - /// Automatically ensures the existance of a certain node type, and prevents it from being deleted. - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class RequireNodeAttribute : Attribute { - public Type type0; - public Type type1; - public Type type2; - - /// Automatically ensures the existance of a certain node type, and prevents it from being deleted - public RequireNodeAttribute(Type type) { - this.type0 = type; - this.type1 = null; - this.type2 = null; - } - - /// Automatically ensures the existance of a certain node type, and prevents it from being deleted - public RequireNodeAttribute(Type type, Type type2) { - this.type0 = type; - this.type1 = type2; - this.type2 = null; - } - - /// Automatically ensures the existance of a certain node type, and prevents it from being deleted - public RequireNodeAttribute(Type type, Type type2, Type type3) { - this.type0 = type; - this.type1 = type2; - this.type2 = type3; - } - - public bool Requires(Type type) { - if (type == null) return false; - if (type == type0) return true; - else if (type == type1) return true; - else if (type == type2) return true; - return false; - } - } -#endregion + /// Add a node to the graph by type (convenience method - will call the System.Type version) + public T AddNode() where T : Node { + return AddNode(typeof(T)) as T; } + + /// Add a node to the graph by type + public virtual Node AddNode(Type type) { + Node.graphHotfix = this; + Node node = ScriptableObject.CreateInstance(type) as Node; + node.graph = this; + nodes.Add(node); + return node; + } + + /// Creates a copy of the original node in the graph + public virtual Node CopyNode(Node original) { + Node.graphHotfix = this; + Node node = ScriptableObject.Instantiate(original); + node.graph = this; + node.ClearConnections(); + nodes.Add(node); + return node; + } + + /// Safely remove a node and all its connections + /// The node to remove + public virtual void RemoveNode(Node node) { + node.ClearConnections(); + nodes.Remove(node); + if (Application.isPlaying) Destroy(node); + } + + /// Remove all nodes and connections from the graph + public virtual void Clear() { + if (Application.isPlaying) { + for (int i = 0; i < nodes.Count; i++) { + if (nodes[i] != null) Destroy(nodes[i]); + } + } + nodes.Clear(); + } + + /// Create a new deep copy of this graph + public virtual NodeGraph Copy() { + // Instantiate a new nodegraph instance + NodeGraph graph = Instantiate(this); + // Instantiate all nodes inside the graph + for (int i = 0; i < nodes.Count; i++) { + if (nodes[i] == null) continue; + Node.graphHotfix = graph; + Node node = Instantiate(nodes[i]) as Node; + node.graph = graph; + graph.nodes[i] = node; + } + + // Redirect all connections + for (int i = 0; i < graph.nodes.Count; i++) { + if (graph.nodes[i] == null) continue; + foreach (NodePort port in graph.nodes[i].Ports) { + port.Redirect(nodes, graph.nodes); + } + } + + return graph; + } + + protected virtual void OnDestroy() { + // Remove all nodes prior to graph destruction + Clear(); + } + + #region Attributes + /// Automatically ensures the existance of a certain node type, and prevents it from being deleted. + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class RequireNodeAttribute : Attribute { + public Type type0; + public Type type1; + public Type type2; + + /// Automatically ensures the existance of a certain node type, and prevents it from being deleted + public RequireNodeAttribute(Type type) { + this.type0 = type; + this.type1 = null; + this.type2 = null; + } + + /// Automatically ensures the existance of a certain node type, and prevents it from being deleted + public RequireNodeAttribute(Type type, Type type2) { + this.type0 = type; + this.type1 = type2; + this.type2 = null; + } + + /// Automatically ensures the existance of a certain node type, and prevents it from being deleted + public RequireNodeAttribute(Type type, Type type2, Type type3) { + this.type0 = type; + this.type1 = type2; + this.type2 = type3; + } + + public bool Requires(Type type) { + if (type == null) return false; + if (type == type0) return true; + else if (type == type1) return true; + else if (type == type2) return true; + return false; + } + } + #endregion } \ No newline at end of file diff --git a/Scripts/NodeGroup.cs b/Scripts/NodeGroup.cs new file mode 100644 index 0000000..09b8e68 --- /dev/null +++ b/Scripts/NodeGroup.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using UnityEngine; + +[CreateNodeMenu("Group")] +public class NodeGroup : Node { + public int width = 400; + public int height = 400; + public Color color = new Color(1f, 1f, 1f, 0.1f); + + public override object GetValue(NodePort port) { + return null; + } + + /// Gets nodes in this group + public List GetNodes() { + List result = new List(); + foreach (Node node in graph.nodes) { + if (node == this) continue; + if (node == null) continue; + if (node.position.x < this.position.x) continue; + if (node.position.y < this.position.y) continue; + if (node.position.x > this.position.x + width) continue; + if (node.position.y > this.position.y + height + 30) continue; + result.Add(node); + } + return result; + } +} \ No newline at end of file diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index 1fbc62f..7660381 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -1,422 +1,421 @@ using System; using System.Collections.Generic; using System.Reflection; +using Attributes; using UnityEngine; -namespace XNode { - [Serializable] - public class NodePort { - public enum IO { Input, Output } +[Serializable] +public class NodePort { + public enum IO { Input, Output } - public int ConnectionCount { get { return connections.Count; } } - /// Return the first non-null connection - public NodePort Connection { - get { - for (int i = 0; i < connections.Count; i++) { - if (connections[i] != null) return connections[i].Port; - } - return null; - } - } - - public IO direction { - get { return _direction; } - internal set { _direction = value; } - } - public Node.ConnectionType connectionType { - get { return _connectionType; } - internal set { _connectionType = value; } - } - public Node.TypeConstraint typeConstraint { - get { return _typeConstraint; } - internal set { _typeConstraint = value; } - } - - /// Is this port connected to anytihng? - public bool IsConnected { get { return connections.Count != 0; } } - public bool IsInput { get { return direction == IO.Input; } } - public bool IsOutput { get { return direction == IO.Output; } } - - public string fieldName { get { return _fieldName; } } - public Node node { get { return _node; } } - public bool IsDynamic { get { return _dynamic; } } - public bool IsStatic { get { return !_dynamic; } } - public Type ValueType { - get { - if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false); - return valueType; - } - set { - if (valueType == value) return; - valueType = value; - if (value != null) _typeQualifiedName = NodeDataCache.GetTypeQualifiedName(value); - } - } - private Type valueType; - - [SerializeField] private string _fieldName; - [SerializeField] private Node _node; - [SerializeField] private string _typeQualifiedName; - [SerializeField] private List connections = new List(); - [SerializeField] private IO _direction; - [SerializeField] private Node.ConnectionType _connectionType; - [SerializeField] private Node.TypeConstraint _typeConstraint; - [SerializeField] private bool _dynamic; - - /// Construct a static targetless nodeport. Used as a template. - public NodePort(FieldInfo fieldInfo) { - _fieldName = fieldInfo.Name; - ValueType = fieldInfo.FieldType; - _dynamic = false; - var attribs = fieldInfo.GetCustomAttributes(false); - for (int i = 0; i < attribs.Length; i++) { - if (attribs[i] is Node.InputAttribute) { - _direction = IO.Input; - _connectionType = (attribs[i] as Node.InputAttribute).connectionType; - _typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint; - } else if (attribs[i] is Node.OutputAttribute) { - _direction = IO.Output; - _connectionType = (attribs[i] as Node.OutputAttribute).connectionType; - _typeConstraint = (attribs[i] as Node.OutputAttribute).typeConstraint; - } - // Override ValueType of the Port - if(attribs[i] is PortTypeOverrideAttribute) { - ValueType = (attribs[i] as PortTypeOverrideAttribute).type; - } - } - } - - /// Copy a nodePort but assign it to another node. - public NodePort(NodePort nodePort, Node node) { - _fieldName = nodePort._fieldName; - ValueType = nodePort.valueType; - _direction = nodePort.direction; - _dynamic = nodePort._dynamic; - _connectionType = nodePort._connectionType; - _typeConstraint = nodePort._typeConstraint; - _node = node; - } - - /// Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. - public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Node node) { - _fieldName = fieldName; - this.ValueType = type; - _direction = direction; - _node = node; - _dynamic = true; - _connectionType = connectionType; - _typeConstraint = typeConstraint; - } - - /// Checks all connections for invalid references, and removes them. - public void VerifyConnections() { - for (int i = connections.Count - 1; i >= 0; i--) { - if (connections[i].node != null && - !string.IsNullOrEmpty(connections[i].fieldName) && - connections[i].node.GetPort(connections[i].fieldName) != null) - continue; - connections.RemoveAt(i); - } - } - - /// Return the output value of this node through its parent nodes GetValue override method. - /// - public object GetOutputValue() { - if (direction == IO.Input) return null; - return node.GetValue(this); - } - - /// Return the output value of the first connected port. Returns null if none found or invalid. - /// - public object GetInputValue() { - NodePort connectedPort = Connection; - if (connectedPort == null) return null; - return connectedPort.GetOutputValue(); - } - - /// Return the output values of all connected ports. - /// - public object[] GetInputValues() { - object[] objs = new object[ConnectionCount]; - for (int i = 0; i < ConnectionCount; i++) { - NodePort connectedPort = connections[i].Port; - if (connectedPort == null) { // if we happen to find a null port, remove it and look again - connections.RemoveAt(i); - i--; - continue; - } - objs[i] = connectedPort.GetOutputValue(); - } - return objs; - } - - /// Return the output value of the first connected port. Returns null if none found or invalid. - /// - public T GetInputValue() { - object obj = GetInputValue(); - return obj is T ? (T) obj : default(T); - } - - /// Return the output values of all connected ports. - /// - public T[] GetInputValues() { - object[] objs = GetInputValues(); - T[] ts = new T[objs.Length]; - for (int i = 0; i < objs.Length; i++) { - if (objs[i] is T) ts[i] = (T) objs[i]; - } - return ts; - } - - /// Return true if port is connected and has a valid input. - /// - public bool TryGetInputValue(out T value) { - object obj = GetInputValue(); - if (obj is T) { - value = (T) obj; - return true; - } else { - value = default(T); - return false; - } - } - - /// Return the sum of all inputs. - /// - public float GetInputSum(float fallback) { - object[] objs = GetInputValues(); - if (objs.Length == 0) return fallback; - float result = 0; - for (int i = 0; i < objs.Length; i++) { - if (objs[i] is float) result += (float) objs[i]; - } - return result; - } - - /// Return the sum of all inputs. - /// - public int GetInputSum(int fallback) { - object[] objs = GetInputValues(); - if (objs.Length == 0) return fallback; - int result = 0; - for (int i = 0; i < objs.Length; i++) { - if (objs[i] is int) result += (int) objs[i]; - } - return result; - } - - /// Connect this to another - /// The to connect to - public void Connect(NodePort port) { - if (connections == null) connections = new List(); - if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; } - if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; } - if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; } - if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; } -#if UNITY_EDITOR - UnityEditor.Undo.RecordObject(node, "Connect Port"); - UnityEditor.Undo.RecordObject(port.node, "Connect Port"); -#endif - if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); } - if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); } - connections.Add(new PortConnection(port)); - if (port.connections == null) port.connections = new List(); - if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this)); - node.OnCreateConnection(this, port); - port.node.OnCreateConnection(this, port); - } - - public List GetConnections() { - List result = new List(); + public int ConnectionCount { get { return connections.Count; } } + /// Return the first non-null connection + public NodePort Connection { + get { for (int i = 0; i < connections.Count; i++) { - NodePort port = GetConnection(i); - if (port != null) result.Add(port); + if (connections[i] != null) return connections[i].Port; } - return result; + return null; } + } - public NodePort GetConnection(int i) { - //If the connection is broken for some reason, remove it. - if (connections[i].node == null || string.IsNullOrEmpty(connections[i].fieldName)) { - connections.RemoveAt(i); - return null; + public IO direction { + get { return _direction; } + internal set { _direction = value; } + } + public Node.ConnectionType connectionType { + get { return _connectionType; } + internal set { _connectionType = value; } + } + public Node.TypeConstraint typeConstraint { + get { return _typeConstraint; } + internal set { _typeConstraint = value; } + } + + /// Is this port connected to anytihng? + public bool IsConnected { get { return connections.Count != 0; } } + public bool IsInput { get { return direction == IO.Input; } } + public bool IsOutput { get { return direction == IO.Output; } } + + public string fieldName { get { return _fieldName; } } + public Node node { get { return _node; } } + public bool IsDynamic { get { return _dynamic; } } + public bool IsStatic { get { return !_dynamic; } } + public Type ValueType { + get { + if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false); + return valueType; + } + set { + if (valueType == value) return; + valueType = value; + if (value != null) _typeQualifiedName = NodeDataCache.GetTypeQualifiedName(value); + } + } + private Type valueType; + + [SerializeField] private string _fieldName; + [SerializeField] private Node _node; + [SerializeField] private string _typeQualifiedName; + [SerializeField] private List connections = new List(); + [SerializeField] private IO _direction; + [SerializeField] private Node.ConnectionType _connectionType; + [SerializeField] private Node.TypeConstraint _typeConstraint; + [SerializeField] private bool _dynamic; + + /// Construct a static targetless nodeport. Used as a template. + public NodePort(FieldInfo fieldInfo) { + _fieldName = fieldInfo.Name; + ValueType = fieldInfo.FieldType; + _dynamic = false; + var attribs = fieldInfo.GetCustomAttributes(false); + for (int i = 0; i < attribs.Length; i++) { + if (attribs[i] is Node.InputAttribute) { + _direction = IO.Input; + _connectionType = (attribs[i] as Node.InputAttribute).connectionType; + _typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint; + } else if (attribs[i] is Node.OutputAttribute) { + _direction = IO.Output; + _connectionType = (attribs[i] as Node.OutputAttribute).connectionType; + _typeConstraint = (attribs[i] as Node.OutputAttribute).typeConstraint; } - NodePort port = connections[i].node.GetPort(connections[i].fieldName); - if (port == null) { - connections.RemoveAt(i); - return null; - } - return port; - } - - /// Get index of the connection connecting this and specified ports - public int GetConnectionIndex(NodePort port) { - for (int i = 0; i < ConnectionCount; i++) { - if (connections[i].Port == port) return i; - } - return -1; - } - - public bool IsConnectedTo(NodePort port) { - for (int i = 0; i < connections.Count; i++) { - if (connections[i].Port == port) return true; - } - return false; - } - - /// Returns true if this port can connect to specified port - public bool CanConnectTo(NodePort port) { - // Figure out which is input and which is output - NodePort input = null, output = null; - if (IsInput) input = this; - else output = this; - if (port.IsInput) input = port; - else output = port; - // If there isn't one of each, they can't connect - if (input == null || output == null) return false; - // Check input type constraints - if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; - if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; - if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; - if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; - // Check output type constraints - if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; - if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; - if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; - if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; - // Success - return true; - } - - /// Disconnect this port from another port - public void Disconnect(NodePort port) { - // Remove this ports connection to the other - for (int i = connections.Count - 1; i >= 0; i--) { - if (connections[i].Port == port) { - connections.RemoveAt(i); - } - } - if (port != null) { - // Remove the other ports connection to this port - for (int i = 0; i < port.connections.Count; i++) { - if (port.connections[i].Port == this) { - port.connections.RemoveAt(i); - // Trigger OnRemoveConnection from this side port - port.node.OnRemoveConnection(port); - } - } - } - // Trigger OnRemoveConnection - node.OnRemoveConnection(this); - } - - /// Disconnect this port from another port - public void Disconnect(int i) { - // Remove the other ports connection to this port - NodePort otherPort = connections[i].Port; - if (otherPort != null) { - otherPort.connections.RemoveAll(it => { return it.Port == this; }); - } - // Remove this ports connection to the other - connections.RemoveAt(i); - - // Trigger OnRemoveConnection - node.OnRemoveConnection(this); - if (otherPort != null) otherPort.node.OnRemoveConnection(otherPort); - } - - public void ClearConnections() { - while (connections.Count > 0) { - Disconnect(connections[0].Port); - } - } - - /// Get reroute points for a given connection. This is used for organization - public List GetReroutePoints(int index) { - return connections[index].reroutePoints; - } - - /// Swap connections with another node - public void SwapConnections(NodePort targetPort) { - int aConnectionCount = connections.Count; - int bConnectionCount = targetPort.connections.Count; - - List portConnections = new List(); - List targetPortConnections = new List(); - - // Cache port connections - for (int i = 0; i < aConnectionCount; i++) - portConnections.Add(connections[i].Port); - - // Cache target port connections - for (int i = 0; i < bConnectionCount; i++) - targetPortConnections.Add(targetPort.connections[i].Port); - - ClearConnections(); - targetPort.ClearConnections(); - - // Add port connections to targetPort - for (int i = 0; i < portConnections.Count; i++) - targetPort.Connect(portConnections[i]); - - // Add target port connections to this one - for (int i = 0; i < targetPortConnections.Count; i++) - Connect(targetPortConnections[i]); - - } - - /// Copy all connections pointing to a node and add them to this one - public void AddConnections(NodePort targetPort) { - int connectionCount = targetPort.ConnectionCount; - for (int i = 0; i < connectionCount; i++) { - PortConnection connection = targetPort.connections[i]; - NodePort otherPort = connection.Port; - Connect(otherPort); - } - } - - /// Move all connections pointing to this node, to another node - public void MoveConnections(NodePort targetPort) { - int connectionCount = connections.Count; - - // Add connections to target port - for (int i = 0; i < connectionCount; i++) { - PortConnection connection = targetPort.connections[i]; - NodePort otherPort = connection.Port; - Connect(otherPort); - } - ClearConnections(); - } - - /// Swap connected nodes from the old list with nodes from the new list - public void Redirect(List oldNodes, List newNodes) { - foreach (PortConnection connection in connections) { - int index = oldNodes.IndexOf(connection.node); - if (index >= 0) connection.node = newNodes[index]; - } - } - - [Serializable] - private class PortConnection { - [SerializeField] public string fieldName; - [SerializeField] public Node node; - public NodePort Port { get { return port != null ? port : port = GetPort(); } } - - [NonSerialized] private NodePort port; - /// Extra connection path points for organization - [SerializeField] public List reroutePoints = new List(); - - public PortConnection(NodePort port) { - this.port = port; - node = port.node; - fieldName = port.fieldName; - } - - /// Returns the port that this points to - private NodePort GetPort() { - if (node == null || string.IsNullOrEmpty(fieldName)) return null; - return node.GetPort(fieldName); + // Override ValueType of the Port + if(attribs[i] is PortTypeOverrideAttribute) { + ValueType = (attribs[i] as PortTypeOverrideAttribute).type; } } } -} + + /// Copy a nodePort but assign it to another node. + public NodePort(NodePort nodePort, Node node) { + _fieldName = nodePort._fieldName; + ValueType = nodePort.valueType; + _direction = nodePort.direction; + _dynamic = nodePort._dynamic; + _connectionType = nodePort._connectionType; + _typeConstraint = nodePort._typeConstraint; + _node = node; + } + + /// Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. + public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Node node) { + _fieldName = fieldName; + this.ValueType = type; + _direction = direction; + _node = node; + _dynamic = true; + _connectionType = connectionType; + _typeConstraint = typeConstraint; + } + + /// Checks all connections for invalid references, and removes them. + public void VerifyConnections() { + for (int i = connections.Count - 1; i >= 0; i--) { + if (connections[i].node != null && + !string.IsNullOrEmpty(connections[i].fieldName) && + connections[i].node.GetPort(connections[i].fieldName) != null) + continue; + connections.RemoveAt(i); + } + } + + /// Return the output value of this node through its parent nodes GetValue override method. + /// + public object GetOutputValue() { + if (direction == IO.Input) return null; + return node.GetValue(this); + } + + /// Return the output value of the first connected port. Returns null if none found or invalid. + /// + public object GetInputValue() { + NodePort connectedPort = Connection; + if (connectedPort == null) return null; + return connectedPort.GetOutputValue(); + } + + /// Return the output values of all connected ports. + /// + public object[] GetInputValues() { + object[] objs = new object[ConnectionCount]; + for (int i = 0; i < ConnectionCount; i++) { + NodePort connectedPort = connections[i].Port; + if (connectedPort == null) { // if we happen to find a null port, remove it and look again + connections.RemoveAt(i); + i--; + continue; + } + objs[i] = connectedPort.GetOutputValue(); + } + return objs; + } + + /// Return the output value of the first connected port. Returns null if none found or invalid. + /// + public T GetInputValue() { + object obj = GetInputValue(); + return obj is T ? (T) obj : default(T); + } + + /// Return the output values of all connected ports. + /// + public T[] GetInputValues() { + object[] objs = GetInputValues(); + T[] ts = new T[objs.Length]; + for (int i = 0; i < objs.Length; i++) { + if (objs[i] is T) ts[i] = (T) objs[i]; + } + return ts; + } + + /// Return true if port is connected and has a valid input. + /// + public bool TryGetInputValue(out T value) { + object obj = GetInputValue(); + if (obj is T) { + value = (T) obj; + return true; + } else { + value = default(T); + return false; + } + } + + /// Return the sum of all inputs. + /// + public float GetInputSum(float fallback) { + object[] objs = GetInputValues(); + if (objs.Length == 0) return fallback; + float result = 0; + for (int i = 0; i < objs.Length; i++) { + if (objs[i] is float) result += (float) objs[i]; + } + return result; + } + + /// Return the sum of all inputs. + /// + public int GetInputSum(int fallback) { + object[] objs = GetInputValues(); + if (objs.Length == 0) return fallback; + int result = 0; + for (int i = 0; i < objs.Length; i++) { + if (objs[i] is int) result += (int) objs[i]; + } + return result; + } + + /// Connect this to another + /// The to connect to + public void Connect(NodePort port) { + if (connections == null) connections = new List(); + if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; } + if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; } + if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; } + if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; } +#if UNITY_EDITOR + UnityEditor.Undo.RecordObject(node, "Connect Port"); + UnityEditor.Undo.RecordObject(port.node, "Connect Port"); +#endif + if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); } + if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); } + connections.Add(new PortConnection(port)); + if (port.connections == null) port.connections = new List(); + if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this)); + node.OnCreateConnection(this, port); + port.node.OnCreateConnection(this, port); + } + + public List GetConnections() { + List result = new List(); + for (int i = 0; i < connections.Count; i++) { + NodePort port = GetConnection(i); + if (port != null) result.Add(port); + } + return result; + } + + public NodePort GetConnection(int i) { + //If the connection is broken for some reason, remove it. + if (connections[i].node == null || string.IsNullOrEmpty(connections[i].fieldName)) { + connections.RemoveAt(i); + return null; + } + NodePort port = connections[i].node.GetPort(connections[i].fieldName); + if (port == null) { + connections.RemoveAt(i); + return null; + } + return port; + } + + /// Get index of the connection connecting this and specified ports + public int GetConnectionIndex(NodePort port) { + for (int i = 0; i < ConnectionCount; i++) { + if (connections[i].Port == port) return i; + } + return -1; + } + + public bool IsConnectedTo(NodePort port) { + for (int i = 0; i < connections.Count; i++) { + if (connections[i].Port == port) return true; + } + return false; + } + + /// Returns true if this port can connect to specified port + public bool CanConnectTo(NodePort port) { + // Figure out which is input and which is output + NodePort input = null, output = null; + if (IsInput) input = this; + else output = this; + if (port.IsInput) input = port; + else output = port; + // If there isn't one of each, they can't connect + if (input == null || output == null) return false; + // Check input type constraints + if (input.typeConstraint == Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; + if (input.typeConstraint == Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; + if (input.typeConstraint == Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; + if (input.typeConstraint == Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; + // Check output type constraints + if (output.typeConstraint == Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; + if (output.typeConstraint == Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; + if (output.typeConstraint == Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; + if (output.typeConstraint == Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; + // Success + return true; + } + + /// Disconnect this port from another port + public void Disconnect(NodePort port) { + // Remove this ports connection to the other + for (int i = connections.Count - 1; i >= 0; i--) { + if (connections[i].Port == port) { + connections.RemoveAt(i); + } + } + if (port != null) { + // Remove the other ports connection to this port + for (int i = 0; i < port.connections.Count; i++) { + if (port.connections[i].Port == this) { + port.connections.RemoveAt(i); + // Trigger OnRemoveConnection from this side port + port.node.OnRemoveConnection(port); + } + } + } + // Trigger OnRemoveConnection + node.OnRemoveConnection(this); + } + + /// Disconnect this port from another port + public void Disconnect(int i) { + // Remove the other ports connection to this port + NodePort otherPort = connections[i].Port; + if (otherPort != null) { + otherPort.connections.RemoveAll(it => { return it.Port == this; }); + } + // Remove this ports connection to the other + connections.RemoveAt(i); + + // Trigger OnRemoveConnection + node.OnRemoveConnection(this); + if (otherPort != null) otherPort.node.OnRemoveConnection(otherPort); + } + + public void ClearConnections() { + while (connections.Count > 0) { + Disconnect(connections[0].Port); + } + } + + /// Get reroute points for a given connection. This is used for organization + public List GetReroutePoints(int index) { + return connections[index].reroutePoints; + } + + /// Swap connections with another node + public void SwapConnections(NodePort targetPort) { + int aConnectionCount = connections.Count; + int bConnectionCount = targetPort.connections.Count; + + List portConnections = new List(); + List targetPortConnections = new List(); + + // Cache port connections + for (int i = 0; i < aConnectionCount; i++) + portConnections.Add(connections[i].Port); + + // Cache target port connections + for (int i = 0; i < bConnectionCount; i++) + targetPortConnections.Add(targetPort.connections[i].Port); + + ClearConnections(); + targetPort.ClearConnections(); + + // Add port connections to targetPort + for (int i = 0; i < portConnections.Count; i++) + targetPort.Connect(portConnections[i]); + + // Add target port connections to this one + for (int i = 0; i < targetPortConnections.Count; i++) + Connect(targetPortConnections[i]); + + } + + /// Copy all connections pointing to a node and add them to this one + public void AddConnections(NodePort targetPort) { + int connectionCount = targetPort.ConnectionCount; + for (int i = 0; i < connectionCount; i++) { + PortConnection connection = targetPort.connections[i]; + NodePort otherPort = connection.Port; + Connect(otherPort); + } + } + + /// Move all connections pointing to this node, to another node + public void MoveConnections(NodePort targetPort) { + int connectionCount = connections.Count; + + // Add connections to target port + for (int i = 0; i < connectionCount; i++) { + PortConnection connection = targetPort.connections[i]; + NodePort otherPort = connection.Port; + Connect(otherPort); + } + ClearConnections(); + } + + /// Swap connected nodes from the old list with nodes from the new list + public void Redirect(List oldNodes, List newNodes) { + foreach (PortConnection connection in connections) { + int index = oldNodes.IndexOf(connection.node); + if (index >= 0) connection.node = newNodes[index]; + } + } + + [Serializable] + private class PortConnection { + [SerializeField] public string fieldName; + [SerializeField] public Node node; + public NodePort Port { get { return port != null ? port : port = GetPort(); } } + + [NonSerialized] private NodePort port; + /// Extra connection path points for organization + [SerializeField] public List reroutePoints = new List(); + + public PortConnection(NodePort port) { + this.port = port; + node = port.node; + fieldName = port.fieldName; + } + + /// Returns the port that this points to + private NodePort GetPort() { + if (node == null || string.IsNullOrEmpty(fieldName)) return null; + return node.GetPort(fieldName); + } + } +} \ No newline at end of file diff --git a/Scripts/SceneGraph.cs b/Scripts/SceneGraph.cs index bb2774f..26daf5a 100644 --- a/Scripts/SceneGraph.cs +++ b/Scripts/SceneGraph.cs @@ -1,23 +1,18 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using XNode; +using UnityEngine; -namespace XNode { - /// Lets you instantiate a node graph in the scene. This allows you to reference in-scene objects. - public class SceneGraph : MonoBehaviour { - public NodeGraph graph; - } +/// Lets you instantiate a node graph in the scene. This allows you to reference in-scene objects. +public class SceneGraph : MonoBehaviour { + public NodeGraph graph; +} - /// Derive from this class to create a SceneGraph with a specific graph type. - /// - /// - /// public class MySceneGraph : SceneGraph { - /// - /// } - /// - /// - public class SceneGraph : SceneGraph where T : NodeGraph { - public new T graph { get { return base.graph as T; } set { base.graph = value; } } - } +/// Derive from this class to create a SceneGraph with a specific graph type. +/// +/// +/// public class MySceneGraph : SceneGraph { +/// +/// } +/// +/// +public class SceneGraph : SceneGraph where T : NodeGraph { + public new T graph { get { return base.graph as T; } set { base.graph = value; } } } \ No newline at end of file