Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

Commit

Permalink
#70 Modular Canvas and Traversal behaviours (#109)
Browse files Browse the repository at this point in the history
#70 Modular Canvas and Traversal behaviours

This update streamlines the process of adding new types of canvases with new properties, behaviours, requirements and traversal algorithms to the framework in a modular way.
Canvas behaviour can be scripted by controlling many aspects of the canvas and it's modifications.
It's also possible to add new ways of traversing these canvases, either similar to the default calculation, a statemachine, a dialogue system, or just a data visualizer, this is now possible without workarounds.

This is made possible by having a canvas type specify the basic properties and behaviours and a seperate traversal class manage the traversal of the canvas. Many canvas types could use the same traversal algorithm.

WARNING:
I seperated the default calculation behaviour from canvas base class, making it a seperate subclass, and NodeCanvas now serves only as a base class. 
Due to compability with previous save files (which would be broken if NodeCanvas was now abstract) it's now weakly limited (canvases with default type are possible but prohibited). 
In order to use previous saves, you first have to specify a valid type (any Canvas subclass) after loading. Also note later on NodeCanvas WILL be made abstract and thus older saves will be broken!

Included is a Graph Canvas type with seperate (yet empty) traversal behaviour to show how easily canvases can be scripted.

Changelog:
- NodeCanvas is now the base class for every other canvas type
- Added warning and side panel entry to convert canvas if the loaded canvas is of type NodeCanvas (base type). Rest of the framework is also mostly disabled if current canvas is of base type
- Added NodeCanvasTraversal base class to add unique traversal algorithms to a specific canvas type
- Added various callbacks to NodeCanvas like OnCreate, OnValidate, OnBeforeSavingCanvas, CanAddNode
- Added NodeCanvas.CreateCanvas methods to create a canvas
- NodeEditor is now completely lifted off the calculation code, API to calculate changed to `nodeCanvas.TraverseAll ()` and `nodeCanvas.OnNodeChange (Node)`
- Added Graph canvas example
- Fixed warnings in RootGraphNode
  • Loading branch information
Seneral authored Jan 9, 2017
1 parent d39cf5b commit 22ba05d
Show file tree
Hide file tree
Showing 32 changed files with 458 additions and 153 deletions.
22 changes: 19 additions & 3 deletions Editor/Node_Editor/NodeEditorWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class NodeEditorWindow : EditorWindow
private string sceneCanvasName = "";
private Rect loadSceneUIPos;
private Rect createCanvasUIPos;
private Rect convertCanvasUIPos;
private int sideWindowWidth = 400;

public Rect sideWindowRect { get { return new Rect (position.width - sideWindowWidth, 0, sideWindowWidth, position.height); } }
Expand Down Expand Up @@ -80,6 +81,8 @@ private void OnEnable()
// Setup Cache
canvasCache = new NodeEditorUserCache(Path.GetDirectoryName(AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject (this))));
canvasCache.SetupCacheEvents();
if (canvasCache.nodeCanvas.GetType () == typeof(NodeCanvas))
ShowNotification(new GUIContent("The Canvas has no specific type. Please use the convert button to assign a type and re-save the canvas!"));
}

private void NormalReInit()
Expand Down Expand Up @@ -160,8 +163,8 @@ private void OnGUI()

private void DrawSideWindow()
{
GUILayout.Label (new GUIContent ("Node Editor (" + canvasCache.nodeCanvas.saveName + ")", "Opened Canvas path: " + canvasCache.nodeCanvas.savePath), NodeEditorGUI.nodeLabelBold);
GUILayout.Label ((canvasCache.nodeCanvas.livesInScene? "Scene Save" : "Asset Save") + ", Type: " + canvasCache.typeData.DisplayString + "");
GUILayout.Label (new GUIContent ("" + canvasCache.nodeCanvas.saveName + " (" + (canvasCache.nodeCanvas.livesInScene? "Scene Save" : "Asset Save") + ")", "Opened Canvas path: " + canvasCache.nodeCanvas.savePath), NodeEditorGUI.nodeLabelBold);
GUILayout.Label ("Type: " + canvasCache.typeData.DisplayString + "/" + canvasCache.nodeCanvas.GetType ().Name + "");

// EditorGUILayout.ObjectField ("Loaded Canvas", canvasCache.nodeCanvas, typeof(NodeCanvas), false);
// EditorGUILayout.ObjectField ("Loaded State", canvasCache.editorState, typeof(NodeEditorState), false);
Expand All @@ -177,6 +180,17 @@ private void DrawSideWindow()
Rect popupPos = GUILayoutUtility.GetLastRect();
createCanvasUIPos = new Rect(popupPos.x + 2, popupPos.yMax + 2, popupPos.width - 4, 0);
}
if (canvasCache.nodeCanvas.GetType () == typeof(NodeCanvas) && GUILayout.Button(new GUIContent("Convert Canvas", "Converts the current canvas to a new type.")))
{
NodeEditorFramework.Utilities.GenericMenu menu = new NodeEditorFramework.Utilities.GenericMenu();
NodeCanvasManager.FillCanvasTypeMenu(ref menu, canvasCache.ConvertCanvasType);
menu.Show(convertCanvasUIPos.position, convertCanvasUIPos.width);
}
if (Event.current.type == EventType.Repaint)
{
Rect popupPos = GUILayoutUtility.GetLastRect();
convertCanvasUIPos = new Rect(popupPos.x + 2, popupPos.yMax + 2, popupPos.width - 4, 0);
}

if (GUILayout.Button(new GUIContent("Save Canvas", "Save the Canvas to the load location")))
{
Expand Down Expand Up @@ -218,6 +232,8 @@ private void DrawSideWindow()
}
else
canvasCache.LoadNodeCanvas (path);
if (canvasCache.nodeCanvas.GetType () == typeof(NodeCanvas))
ShowNotification(new GUIContent("The Canvas has no specific type. Please use the convert button to assign a type and re-save the canvas!"));
}

//GUILayout.Space(6);
Expand Down Expand Up @@ -246,7 +262,7 @@ private void DrawSideWindow()
GUILayout.Label ("Utility/Debug", NodeEditorGUI.nodeLabel);

if (GUILayout.Button (new GUIContent ("Recalculate All", "Initiates complete recalculate. Usually does not need to be triggered manually.")))
NodeEditor.Calculator.RecalculateAll (canvasCache.nodeCanvas);
canvasCache.nodeCanvas.TraverseAll ();

if (GUILayout.Button ("Force Re-Init"))
NodeEditor.ReInit (true);
Expand Down
43 changes: 43 additions & 0 deletions Node_Editor/Example/GraphCanvasType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using UnityEngine;
using System.Collections;
using NodeEditorFramework;

namespace NodeEditorFramework.Standard
{
[NodeCanvasType("Graph Canvas")]
public class GraphCanvasType : NodeCanvas
{
public override string canvasName { get { return "Graph"; } }

private string rootNodeID { get { return "rootGraphNode"; } }
public RootGraphNode rootNode;

protected override void OnCreate ()
{
Traversal = new GraphTraversal (this);
rootNode = Node.Create (rootNodeID, Vector2.zero) as RootGraphNode;
}

private void OnEnable ()
{
// Register to other callbacks
//NodeEditorCallbacks.OnDeleteNode += CheckDeleteNode;
}

protected override void OnValidate ()
{
if (Traversal == null)
Traversal = new GraphTraversal (this);
if (rootNode == null && (rootNode = nodes.Find ((Node n) => n.GetID == rootNodeID) as RootGraphNode) == null)
rootNode = Node.Create (rootNodeID, Vector2.zero) as RootGraphNode;
}

public override bool CanAddNode (string nodeID)
{
//Debug.Log ("Check can add node " + nodeID);
if (nodeID == rootNodeID)
return !nodes.Exists ((Node n) => n.GetID == rootNodeID);
return true;
}
}
}
12 changes: 12 additions & 0 deletions Node_Editor/Example/GraphCanvasType.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions Node_Editor/Example/GraphTraversal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using NodeEditorFramework;
using UnityEngine;
using System.Collections.Generic;

namespace NodeEditorFramework.Standard
{
public class GraphTraversal : NodeCanvasTraversal
{
GraphCanvasType Canvas;

public GraphTraversal (GraphCanvasType canvas) : base(canvas)
{
Canvas = canvas;
}

/// <summary>
/// Traverse the canvas and evaluate it
/// </summary>
public override void TraverseAll ()
{
RootGraphNode rootNode = Canvas.rootNode;
rootNode.Calculate ();
//Debug.Log ("RootNode is " + rootNode);
}
}
}

12 changes: 12 additions & 0 deletions Node_Editor/Example/GraphTraversal.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Node_Editor/Example/RTCanvasCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void AssureCanvas ()
public void CalculateCanvas ()
{
AssureCanvas ();
NodeEditor.Calculator.RecalculateAll (canvas);
canvas.TraverseAll ();
DebugOutputResults ();
}

Expand Down
2 changes: 1 addition & 1 deletion Node_Editor/Example/RTNodeEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public void SideGUI ()
GUILayout.Space (6);

if (GUILayout.Button (new GUIContent ("Recalculate All", "Initiates complete recalculate. Usually does not need to be triggered manually.")))
NodeEditor.Calculator.RecalculateAll (cache.nodeCanvas);
cache.nodeCanvas.TraverseAll ();

if (GUILayout.Button ("Force Re-Init"))
NodeEditor.ReInit (true);
Expand Down
29 changes: 29 additions & 0 deletions Node_Editor/Framework/CalculationCanvasType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using UnityEngine;
using System.Collections;
using NodeEditorFramework;

namespace NodeEditorFramework.Standard
{
[NodeCanvasType("Calculation")]
public class CalculationCanvasType : NodeCanvas
{
public override string canvasName { get { return "Calculation Canvas"; } }

protected override void OnCreate ()
{
Traversal = new DefaultCanvasCalculator (this);
}

public void OnEnable ()
{
// Register to other callbacks, f.E.:
//NodeEditorCallbacks.OnDeleteNode += OnDeleteNode;
}

protected override void OnValidate ()
{
if (Traversal == null)
Traversal = new DefaultCanvasCalculator (this);
}
}
}
12 changes: 12 additions & 0 deletions Node_Editor/Framework/CalculationCanvasType.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Node_Editor/Framework/ConnectionTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public enum ConnectionDrawMethod { Bezier, StraightLine }
/// </summary>
public static class ConnectionTypes
{
private static Type NullType { get { return typeof(ConnectionTypes); } }
private static Type NullType { get { return typeof(void); } }

// Static consistent information about types
private static Dictionary<string, TypeData> types;
Expand Down
12 changes: 8 additions & 4 deletions Node_Editor/Framework/DefaultNodeCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
using UnityEngine;
using System.Collections.Generic;

namespace NodeEditorFramework
namespace NodeEditorFramework.Standard
{
public partial class DefaultNodeCalculator : INodeCalculator
public class DefaultCanvasCalculator : NodeCanvasTraversal
{
// A list of Nodes from which calculation originates -> Call StartCalculation
public List<Node> workList;
private int calculationCount;

public DefaultCanvasCalculator (NodeCanvas canvas) : base(canvas)
{
}

/// <summary>
/// Recalculate from every Input Node.
/// Usually does not need to be called at all, the smart calculation system is doing the job just fine
/// </summary>
public void RecalculateAll (NodeCanvas nodeCanvas)
public override void TraverseAll ()
{
workList = new List<Node> ();
foreach (Node node in nodeCanvas.nodes)
Expand All @@ -33,7 +37,7 @@ public void RecalculateAll (NodeCanvas nodeCanvas)
/// Recalculate from this node.
/// Usually does not need to be called manually
/// </summary>
public void RecalculateFrom (Node node)
public override void OnChange (Node node)
{
node.ClearCalculation ();
workList = new List<Node> { node };
Expand Down
12 changes: 12 additions & 0 deletions Node_Editor/Framework/DefaultNodeCalculator.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 0 additions & 12 deletions Node_Editor/Framework/Interfaces/INodeCalculator.cs

This file was deleted.

10 changes: 8 additions & 2 deletions Node_Editor/Framework/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ public abstract partial class Node : ScriptableObject
/// </summary>
protected internal void InitBase ()
{
NodeEditor.Calculator.RecalculateFrom (this);
if (!NodeEditor.curNodeCanvas.nodes.Contains (this))
NodeEditor.curNodeCanvas.nodes.Add (this);
NodeEditor.curNodeCanvas.OnNodeChange (this);
#if UNITY_EDITOR
if (String.IsNullOrEmpty (name))
name = UnityEditor.ObjectNames.NicifyVariableName (GetID);
Expand Down Expand Up @@ -73,6 +73,7 @@ public void Delete ()
DestroyImmediate (nodeKnobs[knobCnt], true);
}
DestroyImmediate (this, true);
NodeEditor.curNodeCanvas.Validate ();
}

/// <summary>
Expand All @@ -89,9 +90,13 @@ public static Node Create (string nodeID, Vector2 position)
/// </summary>
public static Node Create (string nodeID, Vector2 position, NodeOutput connectingOutput)
{
if (!NodeCanvasManager.CheckCanvasCompability (nodeID, NodeEditor.curNodeCanvas))
throw new UnityException ("Cannot create Node with ID '" + nodeID + "' as it is not compatible with the current canavs type (" + NodeEditor.curNodeCanvas.GetType ().ToString () + ")!");
if (!NodeEditor.curNodeCanvas.CanAddNode (nodeID))
throw new UnityException ("Cannot create another Node with ID '" + nodeID + "' on the current canvas of type (" + NodeEditor.curNodeCanvas.GetType ().ToString () + ")!");
Node node = NodeTypes.getDefaultNode (nodeID);
if (node == null)
throw new UnityException ("Cannot create Node with id " + nodeID + " as no such Node type is registered!");
throw new UnityException ("Cannot create Node as ID '" + nodeID + "' is not registered!");

node = node.Create (position);

Expand All @@ -110,6 +115,7 @@ public static Node Create (string nodeID, Vector2 position, NodeOutput connectin
}

NodeEditorCallbacks.IssueOnAddNode (node);
NodeEditor.curNodeCanvas.Validate ();

return node;
}
Expand Down
Loading

0 comments on commit 22ba05d

Please sign in to comment.