using System; using System.Collections; using System.Collections.Generic; //using System.Diagnostics; using UnityEngine; //for debug messages using SimpleJSON; //to parse the JSON namespace LiteGraph { public enum DataType { NONE, ENUM, NUMBER, STRING, BOOL, VEC2, VEC3 }; public struct vec2 { float x; float y; }; public struct vec3 { float x; float y; float z; }; //not used yet... public class MutableType { public DataType type; public bool data_bool; public float data_number; public string data_string; public vec2 data_vec2; public vec3 data_vec3; public bool AsBool { get { return data_bool; } } public int AsEnum { get { return (int)data_number; } } public float AsFloat { get { return data_number; } } public string AsString { get { return data_string; } } public vec2 AsVec2 { get { return data_vec2; } } public vec3 AsVec3 { get { return data_vec3; } } public void Set(bool v) { type = DataType.BOOL; data_bool = v; } public void Set(int v) { type = DataType.ENUM; data_number = v; } public void Set(float v) { type = DataType.NUMBER; data_number = v; } public void Set(string v) { type = DataType.STRING; data_string = v; } public void Set(vec2 v) { type = DataType.VEC2; data_vec2 = v; } public void Set(vec3 v) { type = DataType.VEC3; data_vec3 = v; } public override string ToString() { switch (type) { case DataType.NONE: return ""; case DataType.ENUM: return data_number.ToString(); case DataType.BOOL: return data_bool.ToString(); case DataType.NUMBER: return data_number.ToString(); case DataType.STRING: return data_string; case DataType.VEC2: return data_vec2.ToString(); case DataType.VEC3: return data_vec3.ToString(); } return ""; } }; //to store connection info public class LLink { public int id; public int origin_id; public int origin_slot; public int target_id; public int target_slot; public DataType data_type; public bool data_bool; public float data_float; public string data_string; public vec2 data_vec2; public vec3 data_vec3; public LLink(int id, DataType type, int origin_id, int origin_slot, int target_id, int target_slot) { this.id = id; this.data_type = type; this.origin_id = origin_id; this.origin_slot = origin_slot; this.target_id = target_id; this.target_slot = target_slot; } public void setData(bool data) { data_type = DataType.BOOL; data_bool = data; } public void setData(float data) { data_type = DataType.NUMBER; data_float = data; } public void setData(string data) { data_type = DataType.STRING; data_string = data; } public void setData(vec2 data) { data_type = DataType.VEC2; data_vec2 = data; } public void setData(vec3 data) { data_type = DataType.VEC3; data_vec3 = data; } } //to store slot info public class LSlot { public int num; public string name; public DataType type; public LLink link = null; //for input slots public List links = new List(); //for output slots public LSlot(string name, DataType type) { this.name = name; this.type = type; } } //the node base class public class LGraphNode { public int id = -1; public LGraph graph = null; public int order = -1; public List inputs = new List(); public List outputs = new List(); public LGraphNode() { } public virtual LSlot addInput(string name, DataType type = DataType.NONE) { LSlot slot = new LSlot(name, type); slot.num = inputs.Count; inputs.Add(slot); return slot; } public virtual LSlot addOutput(string name, DataType type = DataType.NONE) { LSlot slot = new LSlot(name, type); slot.num = outputs.Count; outputs.Add(slot); return slot; } public virtual bool getInputData(int slot_num, bool default_value) { LLink link = inputs[slot_num].link; if (link != null) return link.data_bool; return default_value; } public virtual float getInputData(int slot_num, float default_value ) { LLink link = inputs[slot_num].link; if (link != null) return link.data_float; return default_value; } public virtual string getInputData(int slot_num, string default_value) { LLink link = inputs[slot_num].link; if (link != null) return link.data_string; return default_value; } public virtual vec2 getInputData(int slot_num, vec2 default_value) { LLink link = inputs[slot_num].link; if (link != null) return link.data_vec2; return default_value; } public virtual vec3 getInputData(int slot_num, vec3 default_value) { LLink link = inputs[slot_num].link; if (link != null) return link.data_vec3; return default_value; } public virtual void setOutputData(int slot_num, bool v) { if (inputs.Count <= slot_num) return; LSlot slot = outputs[slot_num]; if (slot == null) return; for (int i = 0; i < slot.links.Count; ++i) { LLink link = slot.links[i]; if (link == null) return; link.setData(v); } } public virtual void setOutputData(int slot_num, float v) { if (outputs.Count <= slot_num) return; LSlot slot = outputs[slot_num]; if (slot == null) return; for (int i = 0; i < slot.links.Count; ++i) { LLink link = slot.links[i]; if (link == null) return; link.setData(v); } } public virtual void setOutputData(int slot_num, string v) { if (outputs.Count <= slot_num) return; LSlot slot = outputs[slot_num]; if (slot == null) return; for (int i = 0; i < slot.links.Count; ++i) { LLink link = slot.links[i]; if (link == null) return; link.setData(v); } } public virtual void setOutputData(int slot_num, vec2 v) { if (outputs.Count <= slot_num) return; LSlot slot = outputs[slot_num]; if (slot == null) return; for (int i = 0; i < slot.links.Count; ++i) { LLink link = slot.links[i]; if (link == null) return; link.setData(v); } } public virtual void setOutputData(int slot_num, vec3 v) { if (outputs.Count <= slot_num) return; LSlot slot = outputs[slot_num]; if (slot == null) return; for (int i = 0; i < slot.links.Count; ++i) { LLink link = slot.links[i]; if (link == null) return; link.setData(v); } } public virtual void transferData(int input_slot_num, int output_slot_num) { LLink input_link = inputs[input_slot_num].link; if (input_link == null) return; if (outputs.Count <= output_slot_num) return; LSlot slot = outputs[output_slot_num]; if (slot == null) return; for (int i = 0; i < slot.links.Count; ++i) { LLink link = slot.links[i]; if (link == null) return; switch(input_link.data_type) { case DataType.BOOL: link.setData(input_link.data_bool); break; case DataType.NUMBER: link.setData(input_link.data_float); break; case DataType.STRING: link.setData(input_link.data_string); break; case DataType.VEC2: link.setData(input_link.data_vec2); break; case DataType.VEC3: link.setData(input_link.data_vec3); break; } } } public virtual bool connect(int origin_slot, LGraphNode target, int target_slot) { if(graph == null) throw (new Exception("node does not belong to a graph")); if (graph != target.graph) throw (new Exception("nodes do not belong to same graph") ); LSlot origin_slot_info = this.outputs[origin_slot]; LSlot target_slot_info = target.inputs[target_slot]; if (origin_slot_info == null || target_slot_info == null) return false; if(origin_slot_info.type != target_slot_info.type && (origin_slot_info.type != DataType.NONE && target_slot_info.type != DataType.NONE) ) throw (new Exception("connecting incompatible types")); int id = graph.last_link_id++; LLink link = new LLink(id, origin_slot_info.type, this.id, origin_slot, target.id, target_slot); graph.links.Add(link); origin_slot_info.links.Add(link); target_slot_info.link = link; graph.sortByExecutionOrder(); return true; } public virtual void onExecute() { } public virtual void configure(JSONNode json_node) { this.id = json_node["id"].AsInt; this.order = json_node["order"].AsInt; //inputs var json_inputs = json_node["inputs"]; if (json_inputs != null) { JSONNode.Enumerator it = json_inputs.GetEnumerator(); int i = 0; while(it.MoveNext()) { JSONNode json_slot = it.Current; string str_type = json_slot["type"]; DataType type = DataType.NONE; if (str_type != null && Globals.stringToDataType.ContainsKey(str_type)) type = Globals.stringToDataType[str_type]; LSlot slot = null; if (inputs.Count > i) slot = inputs[i]; if(slot == null) slot = this.addInput( json_slot["name"], type ); JSONNode json_link = json_slot["link"]; if (json_link != null) slot.link = graph.links_by_id[json_link.AsInt]; ++i; } } //outputs var json_outputs = json_node["outputs"]; if (json_outputs != null) { JSONNode.Enumerator it = json_outputs.GetEnumerator(); int i = 0; while (it.MoveNext()) { JSONNode json_slot = it.Current; string str_type = json_slot["type"]; DataType type = DataType.NONE; if (str_type != null && Globals.stringToDataType.ContainsKey(str_type)) type = Globals.stringToDataType[str_type]; LSlot slot = null; if (outputs.Count > i) slot = outputs[i]; if (slot == null) slot = this.addOutput(json_slot["name"], type); JSONNode json_links = json_slot["links"]; if(json_links != null) { JSONNode.Enumerator it2 = json_links.GetEnumerator(); while (it2.MoveNext()) { JSONNode json_link_id = it2.Current; LLink link = graph.links_by_id[json_link_id.AsInt]; if (link != null) slot.links.Add(link); else Debug.LogError("Link ID not found!: " + json_link_id); } } ++i; } } //custom data (properties) this.onConfigure(json_node); } public virtual void onConfigure(JSONNode json_node) { //overwrite this one } } //namespace to store global litegraph data public class Globals { static public Dictionary stringToDataType = new Dictionary { { "NONE", DataType.NONE }, { "", DataType.NONE }, { "ENUM", DataType.ENUM }, {"NUMBER",DataType.NUMBER }, { "number", DataType.NUMBER }, { "BOOLEAN", DataType.BOOL }, { "boolean", DataType.BOOL }, { "STRING", DataType.STRING }, { "string", DataType.STRING }, { "VEC2", DataType.VEC2 } }; static public Dictionary> node_types = new Dictionary>(); static public void registerType(string name, Func ctor ) { node_types.Add(name, ctor); } static public LGraphNode createNodeType(string name) { if (!node_types.ContainsKey(name)) { Debug.Log("Node type not found: " + name); return null; } Func ctor = node_types[name]; return ctor(); } }; //one graph public class LGraph { public List nodes = new List(); public Dictionary nodes_by_id = new Dictionary(); public List nodes_in_execution_order = new List(); public List links = new List(); public Dictionary links_by_id = new Dictionary(); public bool has_errors = false; public int last_node_id = 0; public int last_link_id = 0; public double time = 0; //time in seconds public Dictionary outputs = new Dictionary(); // Start is called before the first frame update public LGraph() { } public void add(LGraphNode node) { if (node.graph != null) throw ( new Exception("already has graph") ); node.graph = this; node.id = last_node_id++; node.order = node.id; nodes.Add(node); nodes_by_id.Add(node.id, node); } public void clear() { has_errors = false; nodes.Clear(); nodes_by_id.Clear(); links.Clear(); links_by_id.Clear(); nodes_in_execution_order.Clear(); last_link_id = 0; last_node_id = 0; outputs.Clear(); } // Update is called once per frame public void runStep(float dt = 0) { for (int i = 0; i < nodes_in_execution_order.Count; ++i) { LGraphNode node = nodes_in_execution_order[i]; node.onExecute(); } time += dt; } public void configure(string data) { sortByExecutionOrder(); } public void sortByExecutionOrder() { nodes_in_execution_order = nodes.GetRange(0, nodes.Count); nodes_in_execution_order.Sort(delegate (LGraphNode a, LGraphNode b) { return a.order - b.order; }); } public void fromJSONText(string text) { clear(); var root = JSON.Parse(text); last_node_id = root["last_node_id"].AsInt; last_link_id = root["last_link_id"].AsInt; var json_links = root["links"]; for (int i = 0; i < json_links.Count; ++i) { var json_node = json_links[i]; int id = json_node[0].AsInt; int origin_id = json_node[1].AsInt; int origin_slot = json_node[2].AsInt; int target_id = json_node[3].AsInt; int target_slot = json_node[4].AsInt; JSONNode json_type = json_node[5]; DataType type = DataType.NONE; if(json_type != null && json_type.Value != "0" && Globals.stringToDataType.ContainsKey(json_type) ) type = Globals.stringToDataType[ json_type ]; LLink link = new LLink(id, type, origin_id, origin_slot, target_id, target_slot); links.Add(link); links_by_id[link.id] = link; } var json_nodes = root["nodes"]; for (int i = 0; i < json_nodes.Count; ++i) { var json_node = json_nodes[i]; string node_type = json_node["type"]; Debug.Log(node_type); LGraphNode node = LiteGraph.Globals.createNodeType(node_type); if (node == null) { Debug.Log("Error: node type not found: " + node_type); has_errors = true; continue; } node.graph = this; nodes.Add(node); node.configure(json_node); } sortByExecutionOrder(); } public float getOutput(string name, float def_value) { if (!outputs.ContainsKey(name)) return def_value; return outputs[name]; } } public class Main { public static void Init() { Main.loadNodes(); } public static void loadNodes() { Globals.registerType(WatchNode.type, () => new WatchNode() ); Globals.registerType(RandomNumberNode.type, () => new RandomNumberNode()); Globals.registerType(ConstNumberNode.type, () => new ConstNumberNode()); Globals.registerType(TimeNode.type, () => new TimeNode()); Globals.registerType(ConditionNode.type, () => new ConditionNode()); Globals.registerType(GraphOutputNode.type, () => new GraphOutputNode()); Globals.registerType(GateNode.type, () => new GateNode()); } public static void test() { Debug.Log("Testing Graph..."); LGraph graph = new LGraph(); LGraphNode node1 = LiteGraph.Globals.createNodeType("math/rand"); graph.add(node1); LGraphNode node2 = LiteGraph.Globals.createNodeType("basic/watch"); graph.add(node2); node1.connect(0,node2,0); for(int i = 0; i < 100; ++i) graph.runStep(); } } }