diff --git a/Editor/CanvasView.cs b/Editor/CanvasView.cs index f51f74b..240858f 100644 --- a/Editor/CanvasView.cs +++ b/Editor/CanvasView.cs @@ -21,7 +21,7 @@ public class CanvasView : GraphView public Graph Graph { get; private set; } - private readonly Label title; + private Label title; private readonly List commentViews = new List(); private readonly SearchWindow searchWindow; private readonly EdgeConnectorListener edgeConnectorListener; @@ -69,7 +69,7 @@ public CanvasView(GraphEditorWindow window) RegisterCallback(OnFirstResize); - title = new Label("BLUEGRAPH"); + title = new Label("BlueGraph"); title.AddToClassList("canvasViewTitle"); Add(title); @@ -224,6 +224,9 @@ public void AddSearchProvider(ISearchProvider provider) public void Load(Graph graph) { Graph = graph; + + graph.ReconstructPortConnections(); + serializedGraph = new SerializedObject(Graph); title.text = graph.Title; SetupZoom(graph.ZoomMinScale, graph.ZoomMaxScale); @@ -267,8 +270,6 @@ public void Load(Graph graph) node.Name = required.nodeName; node.Position = required.position; AddNodeFromSearch(node, node.Position, null, false); - - } } @@ -278,7 +279,7 @@ public void Load(Graph graph) /// /// Create a new node from reflection data and insert into the Graph. /// - internal void AddNodeFromSearch(Node node, Vector2 screenPosition, PortView connectedPort = null, bool registerUndo = true) + public void AddNodeFromSearch(Node node, Vector2 screenPosition, PortView connectedPort = null, bool registerUndo = true) { // Calculate where to place this node on the graph var windowRoot = EditorWindow.rootVisualElement; diff --git a/Editor/GraphAssetHandler.cs b/Editor/GraphAssetHandler.cs index ae66e99..92d9162 100644 --- a/Editor/GraphAssetHandler.cs +++ b/Editor/GraphAssetHandler.cs @@ -24,7 +24,7 @@ public static bool OnOpenAsset(int instanceID, int line) /// /// Open the appropriate GraphEditor for the Graph asset /// - public static void OnOpenGraph(Graph graph) + public static GraphEditor OnOpenGraph(Graph graph) { var editor = UnityEditor.Editor.CreateEditor(graph) as GraphEditor; if (!editor) @@ -34,7 +34,9 @@ public static void OnOpenGraph(Graph graph) else { editor.CreateOrFocusEditorWindow(); + return editor; } + return null; } } } diff --git a/Editor/GraphEditorWindow.cs b/Editor/GraphEditorWindow.cs index 3a05023..83de9c1 100644 --- a/Editor/GraphEditorWindow.cs +++ b/Editor/GraphEditorWindow.cs @@ -1,4 +1,5 @@ -using UnityEditor; +using System.Threading.Tasks; +using UnityEditor; using UnityEngine; using UnityEngine.UIElements; @@ -13,12 +14,17 @@ public class GraphEditorWindow : EditorWindow public Graph Graph { get; protected set; } + private int cacheDelay = 10; + private int cacheTick = 0; + /// /// Load a graph asset in this window for editing /// public virtual void Load(Graph graph) { Graph = graph; + Graph.IsBeingEditted = true; + Graph.ReconstructPortConnections(); Canvas = new CanvasView(this); Canvas.Load(graph); @@ -29,6 +35,29 @@ public virtual void Load(Graph graph) Repaint(); } + private void OnDestroy() + { + OnClose(); + } + + protected virtual void OnFocus() + { + if (Graph != null) + Graph.IsBeingEditted = true; + } + + protected virtual void OnLostFocus() + { + if (Graph != null) + Graph.IsBeingEditted = false; + } + + /// + /// Override to add additional functional to the closing functionality of the Graph Editor. + /// + protected virtual void OnClose() + { } + protected virtual void Update() { // Canvas can be invalidated when the Unity Editor @@ -39,6 +68,20 @@ protected virtual void Update() return; } + cacheTick++; + + if(cacheTick%cacheDelay == 0) + { + if (Application.isPlaying) + { + Graph?.ReconstructPortConnections(); + } + else + { + Graph?.CachePortConnections(); + } + } + Canvas.Update(); } @@ -52,5 +95,12 @@ protected virtual void OnEnable() Load(Graph); } } + + private Task NextEditorFrame() + { + var tcs = new TaskCompletionSource(); + EditorApplication.delayCall += () => tcs.SetResult(true); + return tcs.Task; + } } } diff --git a/Editor/NodeReflection.cs b/Editor/NodeReflection.cs index a913260..22bd8ba 100644 --- a/Editor/NodeReflection.cs +++ b/Editor/NodeReflection.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -187,7 +188,7 @@ public NodeReflectionData(Type type, NodeAttribute nodeAttr) contextMethods = new Dictionary(); var attrs = type.GetCustomAttributes(true); - foreach (var attr in attrs) + foreach (var attr in attrs.Reverse()) { if (attr is TagsAttribute tagAttr) { @@ -206,6 +207,17 @@ public NodeReflectionData(Type type, NodeAttribute nodeAttr) HasControlElement = false }); } + else if (attr is InputAttribute input) + { + Ports.Add(new PortReflectionData() + { + Name = input.Name, + Type = input.Type, + Direction = PortDirection.Input, + Capacity = input.Multiple ? PortCapacity.Multiple : PortCapacity.Single, + HasControlElement = false + }); + } } // Load additional data from class fields @@ -215,6 +227,7 @@ public NodeReflectionData(Type type, NodeAttribute nodeAttr) LoadContextMethods(); } + public bool HasInputOfType(Type type) { foreach (var port in Ports) diff --git a/Editor/NodeView.cs b/Editor/NodeView.cs index bf4add6..9653af8 100644 --- a/Editor/NodeView.cs +++ b/Editor/NodeView.cs @@ -49,6 +49,12 @@ internal void Initialize(Node node, CanvasView canvas, EdgeConnectorListener con errorMessage = new Label { name = "error-label" }; errorContainer.Add(errorMessage); + if (errorContainer != null) + { + errorContainer.RegisterCallback( (MouseEnterEvent evt) => errorMessage.name = "error-label-hover"); + errorContainer.RegisterCallback( (MouseLeaveEvent evt) => errorMessage.name = "error-label"); + } + Insert(0, errorContainer); SetPosition(new Rect(node.Position, Vector2.one)); @@ -77,6 +83,7 @@ internal void Initialize(Node node, CanvasView canvas, EdgeConnectorListener con OnInitialize(); } + /// /// Executed after receiving a node target and initial configuration /// but before being added to the graph. diff --git a/Editor/Resources/BlueGraphEditor/NodeView Hovered.uss b/Editor/Resources/BlueGraphEditor/NodeView Hovered.uss new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/Editor/Resources/BlueGraphEditor/NodeView Hovered.uss @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Editor/Resources/BlueGraphEditor/NodeView Hovered.uss.meta b/Editor/Resources/BlueGraphEditor/NodeView Hovered.uss.meta new file mode 100644 index 0000000..8928c5e --- /dev/null +++ b/Editor/Resources/BlueGraphEditor/NodeView Hovered.uss.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: c728599441f2baa4eb1b629a0e69f29f +ScriptedImporter: + fileIDToRecycleName: + 11400000: stylesheet + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} diff --git a/Editor/Resources/BlueGraphEditor/NodeView.uss b/Editor/Resources/BlueGraphEditor/NodeView.uss index ccf5994..92de762 100644 --- a/Editor/Resources/BlueGraphEditor/NodeView.uss +++ b/Editor/Resources/BlueGraphEditor/NodeView.uss @@ -117,15 +117,43 @@ .nodeView #error-label { color: var(--bluegraph-node-error-color); -unity-text-align: upper-center; - font-size: 110%; + font-size: 16px; position: relative; - bottom: -50px; + bottom: 40%; + overflow: hidden; - min-width: 200px; - align-self: center; + min-width: 500px; + height: 16px; + + align-self: left; + white-space: normal; + -unity-text-align: upper-left + + transition: all 0.5s; +} +.nodeView #error-label-hover { + color: var(--bluegraph-node-error-color); + -unity-text-align: upper-center; + font-size: 16px; + + position: relative; + bottom: 40%; + + overflow: hidden; + + min-width: 500px; + + align-self: left; white-space: normal; + + height: 0px; + -unity-text-align: lower-left; + + overflow: visible; + + transition: height 0.5s; } .nodeView #error-icon { @@ -133,5 +161,5 @@ width: 20px; height: 20px; left: -16px; - top: -16px; + top: -24px; } diff --git a/Editor/Resources/BlueGraphEditorHover.meta b/Editor/Resources/BlueGraphEditorHover.meta new file mode 100644 index 0000000..c039537 --- /dev/null +++ b/Editor/Resources/BlueGraphEditorHover.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f60312d3c88e70d4394eb3d06b8322bd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Attributes.cs b/Runtime/Attributes.cs index 2f4bd70..e68cee5 100644 --- a/Runtime/Attributes.cs +++ b/Runtime/Attributes.cs @@ -58,7 +58,7 @@ public TagsAttribute(params string[] tags) /// /// An input port exposed on a Node /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, AllowMultiple = true)] public class InputAttribute : Attribute { /// @@ -68,6 +68,8 @@ public class InputAttribute : Attribute /// public string Name { get; set; } + public Type Type; + /// /// Can this input accept multiple outputs at once. /// @@ -78,9 +80,10 @@ public class InputAttribute : Attribute /// public bool Editable { get; set; } = true; - public InputAttribute(string name = null) + public InputAttribute(string name = null, Type type = null) { Name = name; + this.Type = type; } } @@ -229,4 +232,20 @@ public CustomNodeViewAttribute(Type nodeType) NodeType = nodeType; } } + + /// + /// Tells the Node Caching system to cache the Node to a specific type. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class CacheToAttribute : Attribute + { + public Type Type; + public bool CacheAsBoth = true; + + public CacheToAttribute(Type type, bool cacheAsBoth = true) + { + Type = type; + CacheAsBoth = cacheAsBoth; + } + } } diff --git a/Runtime/Graph.cs b/Runtime/Graph.cs index 56a04ba..cf05f77 100644 --- a/Runtime/Graph.cs +++ b/Runtime/Graph.cs @@ -1,6 +1,12 @@ -using System; + +using BlueGraph.Utils; +using System; using System.Collections; using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor.Experimental.GraphView; +using UnityEditor.MemoryProfiler; using UnityEngine; namespace BlueGraph @@ -33,6 +39,8 @@ public virtual string Title get { return "BLUEGRAPH"; } } + public bool IsBeingEditted = false; + /// /// Retrieve the min zoom value scale used by CanvasView /// @@ -65,6 +73,25 @@ public virtual float ZoomMaxScale } } + [SerializeField] + private SerializableDictionary nodeByTypeCache; + /// + /// The nodes Cached in the Graph, by their type so they can be easily queried. + /// + protected SerializableDictionary NodesByTypeCache => nodeByTypeCache ??= new(); + /// + /// The cached connections between nodes from the Schematics Editor in a Dictionary that allows them to be reconstructed if they're lost. + /// + [SerializeField] + [HideInInspector] + internal SerializableDictionary portConnectionCache = new(); + /// + /// A Cache pairing Port IDs to their Ports that is concstructed at Runtime to improve Port Lookup time + /// + [SerializeField] + [HideInInspector] + private SerializableDictionary runtimePortCache = new(); + /// /// Retrieve all nodes on this graph /// @@ -98,6 +125,7 @@ public int AssetVersion [SerializeField, HideInInspector] private int assetVersion = 1; + /// /// Propagate OnDisable to all nodes. /// @@ -127,8 +155,11 @@ private void OnEnable() /// /// Propagate OnValidate to all nodes. /// - private void OnValidate() + public virtual void OnValidate() { + if (!Application.isPlaying) + CacheNodesByType(); + OnGraphValidate(); foreach (var node in Nodes) @@ -191,7 +222,15 @@ public IEnumerable GetNodes() where T : Node } } } - + + /// + /// Find all nodes on the Graph of, or inherited from, the given type, from the . + /// + public Node[] GetCachedNodes() where TNode : Node + { + return NodesByTypeCache[typeof(TNode)]; + } + /// /// Add a new node to the Graph. /// @@ -255,6 +294,119 @@ public void RemoveNode(Node node) node.Graph = null; } + /// + /// Caches the Connections between ports so they can be recreated if they are lost at Runtime. + /// + public void CachePortConnections() + { + portConnectionCache.Clear(); + + foreach (var node in Nodes) + { + foreach(var kvp in node.Ports) + { + int connectionCount = kvp.Value.ConnectedPorts.Count(); + + portConnectionCache[kvp.Value.ID] = new string[connectionCount]; + + if (connectionCount == 0) continue; + + for (int i = 0; i < connectionCount; i++) + { + portConnectionCache[kvp.Value.ID][i] = kvp.Value.ConnectedPorts.ElementAt(i).ID; + } + } + } + } + + /// + /// Caches all the Nodes in the Graph By Type in the Dictionary + /// + public void CacheNodesByType() + { + NodesByTypeCache.Clear(); + ResetExtendedNodeCaches(); + + foreach (var node in Nodes) + { + var attr = node.GetType().GetCustomAttribute(false); + CacheNodeByType(node, attr); + } + } + + /// + /// Override this to handle the clearing of extended Node Caches + /// + protected virtual void ResetExtendedNodeCaches() + { } + + /// + /// Override this for cache handling. + /// + /// + protected virtual void CacheNodeByType(Node node, CacheToAttribute attr) + { + if (attr != null) + { + if (!NodesByTypeCache.ContainsKey(attr.Type)) + NodesByTypeCache.Add(attr.Type, new Node[0]); + NodesByTypeCache[attr.Type] = NodesByTypeCache[attr.Type].Append((Node)node).ToArray(); + } + if (attr == null || attr.CacheAsBoth) + { + var type = node.GetType(); + + if (!NodesByTypeCache.ContainsKey(type) || NodesByTypeCache[type] == null) + NodesByTypeCache[type] = new Node[0]; + NodesByTypeCache[type] = NodesByTypeCache[type].Append((Node)node).ToArray(); + } + } + + /// + /// Uses the previously Cached Node Port information to Reconstruct the Graph during runtime. + /// + public void ReconstructPortConnections() + { + foreach(var node in Nodes) + { + foreach(var kvp in node.Ports) + { + if (!portConnectionCache.ContainsKey(kvp.Value.ID)) continue; + + foreach (var connectedPortID in portConnectionCache[kvp.Value.ID]) + { + var portToConnect = FindPortByID(connectedPortID); + + if(portToConnect != null && !kvp.Value.ConnectedPorts.Any(port => port.ID == connectedPortID)) + kvp.Value.Connect(portToConnect, true); + } + } + } + } + + /// + /// Searches through all the ports of all the nodes in the graph to find a port with a matching ID. + /// + /// + /// + private Port FindPortByID(string portID) + { + if (runtimePortCache.ContainsKey(portID)) + return runtimePortCache[portID]; + else + { + foreach(var node in Nodes) + { + foreach(var kvp in node.Ports) + { + if (kvp.Value.ID == portID) + return kvp.Value; + } + } + } + return null; + } + /// /// Add a new edge between two Ports. /// diff --git a/Runtime/Node.cs b/Runtime/Node.cs index 19fbc8f..4366a52 100644 --- a/Runtime/Node.cs +++ b/Runtime/Node.cs @@ -11,6 +11,23 @@ public abstract class Node public event Action OnValidateEvent; public event Action OnErrorEvent; + [SerializeField] private string _graphID; + + /// + /// An ID for the node that is only ever set once. + /// + public string GraphID + { + get + { + if (id == null) + { + id = Guid.NewGuid().ToString(); + } + return id; + } + } + [SerializeField] private string id; public string ID diff --git a/Runtime/Port.cs b/Runtime/Port.cs index 5ccc314..3602203 100644 --- a/Runtime/Port.cs +++ b/Runtime/Port.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace BlueGraph @@ -114,13 +115,31 @@ public int ConnectionCount get { return connections.Count; } } - internal List Connections + public List Connections { get { return connections; } + set { connections = value; } } [SerializeField] private List connections = new List(); + [SerializeField] + private string id; + /// + /// The Port ID is stored in the Graph's ConnectionCache to rebuild broken connections. + /// + public string ID + { + get + { + if (string.IsNullOrEmpty(id)) + { + id = Guid.NewGuid().ToString(); + } + return id; + } + } + /// /// Enumerate all ports connected by edges to this port /// @@ -235,10 +254,10 @@ internal void DisconnectAll() /// /// Use Graph.AddEdge() over this. /// - internal void Connect(Port port) + internal void Connect(Port port, bool force = false) { // Skip if we're already connected - if (GetConnection(port) != null) + if (!force && GetConnection(port) != null) { return; } @@ -292,7 +311,7 @@ internal void Disconnect(Port port) /// internal void UpdateConnections() { - if (hasLoadedConnections) + if (hasLoadedConnections || Node == null) { return; } diff --git a/Runtime/Utils.meta b/Runtime/Utils.meta new file mode 100644 index 0000000..cefaf33 --- /dev/null +++ b/Runtime/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 76b2b26d0f4c2b846bf5a9d8b1db5d38 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Utils/SerializableDictionary.cs b/Runtime/Utils/SerializableDictionary.cs new file mode 100644 index 0000000..0c3bd27 --- /dev/null +++ b/Runtime/Utils/SerializableDictionary.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace BlueGraph.Utils +{ + [Serializable] + public class SerializableDictionary : IDictionary, ISerializationCallbackReceiver + { + private Dictionary dictionary = new Dictionary(); + + [SerializeField] + private List items = new List(); + + private bool invalidFlag; + + public TValue this[TKey key] + { + get + { + if (dictionary.ContainsKey(key)) + return dictionary[key]; + else + { + //Debug.LogWarning("Key " + key + " doesn't exist!"); + return default(TValue); + } + } + + set + { + if (!dictionary.ContainsKey(key)) + dictionary.Add(key, value); + else + dictionary[key] = value; + } + } + + public ICollection Keys + { + get { return dictionary.Keys; } + } + + public ICollection Values + { + get { return dictionary.Values; } + } + + public void Add(TKey key, TValue value) + { + dictionary.Add(key, value); + } + + public bool ContainsKey(TKey key) + { + return dictionary.ContainsKey(key); + } + + public bool Remove(TKey key) + { + return dictionary.Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return dictionary.TryGetValue(key, out value); + } + + public void Clear() + { + dictionary.Clear(); + } + + public int Count + { + get { return dictionary.Count; } + } + + bool ICollection>.IsReadOnly + { + get { return (dictionary as ICollection>).IsReadOnly; } + } + + void ICollection>.Add(KeyValuePair item) + { + (dictionary as ICollection>).Add(item); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return (dictionary as ICollection>).Contains(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + (dictionary as ICollection>).CopyTo(array, arrayIndex); + } + + bool ICollection>.Remove(KeyValuePair item) + { + return (dictionary as ICollection>).Remove(item); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return (dictionary as IEnumerable>).GetEnumerator(); + } + + public IEnumerator> GetEnumerator() + => dictionary.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public void OnBeforeSerialize() + { + if (invalidFlag) + { + return; + } + else + { + items.Clear(); + } + + foreach (var pair in dictionary) + { + items.Add(new DictionaryItem(pair.Key, pair.Value)); + } + } + + public void OnAfterDeserialize() + { + dictionary.Clear(); + + invalidFlag = false; + + for (var i = 0; i < items.Count; ++i) + { + if (items[i] != null) + { + if (items[i].key != null || !(dictionary.ContainsKey(items[i].key))) + { + dictionary.Add(items[i].key, items[i].value); + } + else + { + invalidFlag = true; + continue; + } + } + } + + if (!invalidFlag) + { + items.Clear(); + } + } + + public SerializableDictionary() + { + } + + /// + /// Clones the other Dictionary into this one. + /// + /// From. + public SerializableDictionary(SerializableDictionary from) + { + foreach (TKey key in from.Keys) + { + Add(key, from[key]); + } + } + + public TKey KeyAt(int index) + { + return items[index].key; + } + + public TValue ValueAt(int index) + { + return items[index].value; + } + + public override string ToString() + { + var returnValue = ""; + + var keyList = Keys.ToList(); + for (int i = 0; i < keyList.Count; i++) + { + var key = keyList[i]; + + var keyString = key is float ? (Math.Truncate(((float)(object)key) * 100) / 100).ToString() : key.ToString(); + var valueString = this[key] is float ? (Math.Truncate(((float)(object)this[key]) * 100) / 100).ToString() : this[key].ToString(); + var itemString = keyString + ":" + valueString; + + returnValue += itemString; + + if (i < keyList.Count - 1) + returnValue += ","; + } + + return returnValue; + } + + /// + /// Creates a new serializable dictionary from a string representation. + /// + /// The string representation of the dictionary. + /// A new serializable dictionary with the same key-value pairs as the string. + public static SerializableDictionary NewFromString(string dictionaryString) + { + var dictionary = new SerializableDictionary(); + + var items = dictionaryString.Split(','); + + foreach (var item in items) + { + var parts = item.Split(':'); + var keyString = parts[0]; + var valueString = parts[1]; + + var key = (TKey)Convert.ChangeType(keyString, typeof(TKey)); + var value = (TValue)Convert.ChangeType(valueString, typeof(TValue)); + + dictionary.Add(key, value); + } + + // Return the dictionary + return dictionary; + } + + /// + /// Sets the dictionary of this instance from a string representation. + /// + public void FromString(string dictionaryString) + { + dictionary = NewFromString(dictionaryString).dictionary; + } + + public static implicit operator Dictionary(SerializableDictionary serializableDictionary) + { + if (serializableDictionary == null) + return null; + + return new Dictionary(serializableDictionary.dictionary); + } + + public static implicit operator SerializableDictionary(Dictionary normalDictionary) + { + if (normalDictionary == null) + return null; + + var sDict = new SerializableDictionary(); + foreach (var kvp in normalDictionary) + { + sDict.Add(kvp.Key, kvp.Value); + } + return sDict; + } + + + + [Serializable] + public class DictionaryItem + { + [SerializeField] + public TKey key; + [SerializeField] + public TValue value; + + public DictionaryItem(TKey key, TValue value) + { + this.key = key; + this.value = value; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Utils/SerializableDictionary.cs.meta b/Runtime/Utils/SerializableDictionary.cs.meta new file mode 100644 index 0000000..3640187 --- /dev/null +++ b/Runtime/Utils/SerializableDictionary.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ddf4afbc4df01974d94253dbf1dd3142 \ No newline at end of file diff --git a/Runtime/Utils/SerializedType.cs b/Runtime/Utils/SerializedType.cs new file mode 100644 index 0000000..ba27233 --- /dev/null +++ b/Runtime/Utils/SerializedType.cs @@ -0,0 +1,59 @@ +using System; +using UnityEngine; + +namespace BlueGraph.Utils +{ + [Serializable] + public class SerializableType + { + [SerializeField] private string typeName; + + public Type Type + { + get => string.IsNullOrEmpty(typeName) ? null : Type.GetType(typeName); + set => typeName = value?.AssemblyQualifiedName; + } + + public static implicit operator Type(SerializableType serializableType) + { + return serializableType?.Type; + } + + public static implicit operator SerializableType(Type type) + { + return new SerializableType { Type = type }; + } + + public override string ToString() + { + return Type?.FullName ?? "null"; + } + + public override bool Equals(object obj) + { + if (obj is SerializableType other) + { + return Type == other.Type; // Compare the actual Type objects + } + if (obj is Type type) + { + return Type == type; + } + return false; + } + + public override int GetHashCode() + { + return Type?.GetHashCode() ?? 0; + } + + public static bool operator ==(SerializableType a, SerializableType b) + { + if (ReferenceEquals(a, b)) return true; + if (a is null || b is null) return false; + return a.Type == b.Type; + } + + public static bool operator !=(SerializableType a, SerializableType b) => !(a == b); + } +} \ No newline at end of file diff --git a/Runtime/Utils/SerializedType.cs.meta b/Runtime/Utils/SerializedType.cs.meta new file mode 100644 index 0000000..94c4ce7 --- /dev/null +++ b/Runtime/Utils/SerializedType.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 615a145a689a36b43a2304e2f751818e \ No newline at end of file