// 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;
}
}
}