// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved. // This code can only be used under the standard Unity Asset Store End User License Agreement // A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using UnityEngine; using UnityEngine.Events; // ReSharper disable MemberCanBePrivate.Global namespace Doozy.Runtime.Nody { /// Base class for all ports that facilitate connections between nodes in the Nody system [Serializable] public class FlowPort { [SerializeField] private string NodeId; /// Node Id public string nodeId { get => NodeId; internal set => NodeId = value; } [SerializeField] private string PortId; /// Port Id public string portId => PortId; [SerializeField] private PortDirection Direction; /// Port direction (Input/Output) - the port's connections direction public PortDirection direction { get => Direction; internal set => Direction = value; } [SerializeField] private PortCapacity Capacity; /// Port capacity (Single/Multi) - the number of connections this port can have public PortCapacity capacity { get => Capacity; internal set => Capacity = value; } [SerializeField] private List Connections; /// /// All the connections this port has. /// These are the ids of ports that this port is connected to /// public List connections => Connections; /// /// Get the first connection of this port. /// This is the first port id that this port is connected to. /// If one does not exit, this returns null /// public string firstConnection => connections.FirstOrDefault(); /// True if this port has at least one connections (checks connections count) public bool isConnected => Connections.Count > 0; /// True if this port's direction is Input public bool isInput => Direction == PortDirection.Input; /// True if this port's direction is Output public bool isOutput => Direction == PortDirection.Output; /// True if this port accents only one connection (overrides the previous one) public bool acceptsOnlyOneConnection => Capacity == PortCapacity.Single; /// True if this port accepts multiple connections public bool acceptsMultipleConnections => Capacity == PortCapacity.Multi; [SerializeField] private string Value; /// Returns the port's value as a string public string value { get => Value; set => Value = value; } [SerializeField] private Type m_ValueType; /// Returns the value type this port has and automatically updates the m_valueType if needed public Type valueType { get { if (m_ValueType != null) return m_ValueType; if (string.IsNullOrEmpty(ValueTypeQualifiedName)) return null; m_ValueType = Type.GetType(ValueTypeQualifiedName, false); return m_ValueType; } private set { m_ValueType = value; if (value == null) return; ValueTypeQualifiedName = value.AssemblyQualifiedName; } } [SerializeField] private string ValueTypeQualifiedName; /// Returns the value TypeQualifiedName and automatically updates the m_valueType if needed private string valueTypeQualifiedName { get => ValueTypeQualifiedName; set { ValueTypeQualifiedName = value; m_ValueType = Type.GetType(value, false); } } [SerializeField] private bool CanBeDeleted; /// /// [Editor] True if this port can be deleted. /// Used to prevent special ports from being deleted in the editor /// public bool canBeDeleted { get => CanBeDeleted; internal set => CanBeDeleted = value; } [SerializeField] private bool CanBeReordered; /// /// [Editor] True if this port can be reordered. /// Used to prevent special ports from being reordered in the editor /// public bool canBeReordered { get => CanBeReordered; internal set => CanBeReordered = value; } /// [Editor] Ping this port public UnityAction ping { get; set; } /// [Editor] Refresh this port's editor public UnityAction refreshPortEditor { get; set; } /// [Editor] Refresh this port's view public UnityAction refreshPortView { get; set; } /// [Editor] Called on port connected (references the other port) public UnityAction onConnected { get; set; } /// [Editor] Called on port disconnected (references the other port) public UnityAction onDisconnected { get; set; } /// Parent node reference public FlowNode node { get; set; } /// Construct a port that needs node id, direction and capacity to be set public FlowPort() { PortId = Guid.NewGuid().ToString(); Connections = new List(); CanBeDeleted = true; CanBeReordered = true; } /// Construct a port /// Node that this port belongs to /// Port direction (Input/Output) - the port's connections direction /// Port capacity (Single/Multi) - the number of connections this port can have public FlowPort(FlowNode node, PortDirection direction, PortCapacity capacity) : this() { NodeId = node.nodeId; Direction = direction; Capacity = capacity; valueType = valueType; valueTypeQualifiedName = valueType.AssemblyQualifiedName; value = JsonUtility.ToJson(Activator.CreateInstance(valueType)); } /// Construct a deep copy from another FlowPort /// Source port public FlowPort(FlowPort other) { PortId = other.portId; NodeId = other.nodeId; Direction = other.direction; Capacity = other.capacity; Connections = new List(other.connections); CanBeDeleted = other.CanBeDeleted; CanBeReordered = other.CanBeReordered; valueType = other.valueType; valueTypeQualifiedName = other.valueTypeQualifiedName; value = other.value; } /// Get the port value /// Data Type public T GetValue() => (T)JsonUtility.FromJson(value, typeof(T)); // public T GetValue() => (T)JsonUtility.FromJson(value, valueType); /// Set port value /// Port data /// Data type public FlowPort SetValue(T data) { valueType = typeof(T); value = JsonUtility.ToJson(data); return this; } /// Get the port value as a scriptable object public ScriptableObject GetScriptableObjectValue(ScriptableObject targetScriptableObject) { JsonUtility.FromJsonOverwrite(value, targetScriptableObject); return targetScriptableObject; } } [SuppressMessage("ReSharper", "UnusedMember.Global")] public static class FlowPortExtensions { /// Remove connection by deleting the given port id from the connections list /// Target port /// Port id to search for public static T RemoveConnection(this T target, string portId) where T : FlowPort { if (target.connections.Contains(portId)) target.connections.Remove(portId); return target; } /// True if this port is connected to another port with the given port id /// Target port /// Port id to search for to determine if this port is connected to it or not public static bool IsConnectedToPort(this T target, string otherPortId) where T : FlowPort => target.direction switch { PortDirection.Input => target.connections.Any(c => c.Equals(otherPortId)), //Input -> look at the outputPortId PortDirection.Output => target.connections.Any(c => c.Equals(otherPortId)), //Output -> look at the inputPortId _ => throw new ArgumentOutOfRangeException() //wtf }; /// [Editor] Set the port's node id /// Target port /// Node id of the node that contains this port public static T SetNodeId(this T target, string nodeId) where T : FlowPort { target.nodeId = nodeId; return target; } /// [Editor] Set the port direction (Input/Output) - the port's connections direction /// Target port /// Port direction public static T SetDirection(this T target, PortDirection direction) where T : FlowPort { target.direction = direction; return target; } /// [Editor] Set the port capacity (Single/Multi) - the number of connections this port can have /// Target port /// Port capacity public static T SetCapacity(this T target, PortCapacity capacity) where T : FlowPort { target.capacity = capacity; return target; } /// [Editor] Set True if this port can be deleted. Used to prevent special ports from being deleted in the editor /// Target port /// True if this port can be deleted public static T SetCanBeDeleted(this T target, bool canBeDeleted) where T : FlowPort { target.canBeDeleted = canBeDeleted; return target; } /// /// [Editor] Set True if this port can be reordered. /// Used to prevent special ports from being reordered in the editor /// /// Target port /// True if this port can be reordered public static T SetCanBeReordered(this T target, bool canBeReordered) where T : FlowPort { target.canBeReordered = canBeReordered; return target; } /// [Editor] Ping port /// Target port /// Flow direction (back flow is when returning to the previous node) ( public static T Ping(this T target, FlowDirection flowDirection) where T : FlowPort { target.ping?.Invoke(flowDirection); return target; } /// [Editor] Refresh port's editor /// Target port public static T RefreshPortEditor(this T target) where T : FlowPort { target.refreshPortEditor?.Invoke(); return target; } /// [Editor] Refresh port's view /// Target port public static T RefreshPortView(this T target) where T : FlowPort { target.refreshPortView?.Invoke(); return target; } } }