This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
namespace Unity.VisualScripting
{
[Plugin(BoltProduct.ID)]
internal class Acknowledgement_NCalc : PluginAcknowledgement
{
public Acknowledgement_NCalc(Plugin plugin) : base(plugin) { }
public override string title => "NCalc";
public override string author => "Sébastien Ros";
public override string url => "https://ncalc.codeplex.com/";
public override string licenseName => "MIT";
public override string licenseText => CommonLicenses.MIT;
}
}

View File

@@ -0,0 +1,21 @@
using UnityEditor;
namespace Unity.VisualScripting.Analytics
{
class FlowMacroSavedEvent : UnityEditor.AssetModificationProcessor
{
static string[] OnWillSaveAssets(string[] paths)
{
foreach (string path in paths)
{
var assetType = AssetDatabase.GetMainAssetTypeAtPath(path);
if (assetType == typeof(ScriptGraphAsset))
{
UsageAnalytics.CollectAndSend();
break;
}
}
return paths;
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Linq;
namespace Unity.VisualScripting
{
public static class BoltFlowNameUtility
{
[Obsolete("This method is obsolete. Please use the new UnitTitle(unitType, short, includeStatus) instead.")]
public static string UnitTitle(Type unitType, bool @short)
{
if (@short)
{
var shortTitle = unitType.GetAttribute<UnitShortTitleAttribute>()?.title;
if (shortTitle != null)
{
return shortTitle;
}
}
var title = unitType.GetAttribute<UnitTitleAttribute>()?.title;
if (title != null)
{
return title;
}
return unitType.HumanName();
}
public static string UnitTitle(Type unitType, bool @short, bool includeStatus)
{
var suffix = string.Empty;
if (includeStatus && Attribute.IsDefined(unitType, typeof(ObsoleteAttribute)))
suffix = " (Deprecated)";
if (@short)
{
var shortTitle = unitType.GetAttribute<UnitShortTitleAttribute>()?.title;
if (shortTitle != null)
{
return $"{shortTitle} {suffix}";
}
}
var title = unitType.GetAttribute<UnitTitleAttribute>()?.title;
return title != null ? $"{title} {suffix}" : $"{unitType.HumanName()} {suffix}";
}
public static string UnitPreviousTitle(Type unitType)
{
var title = unitType.GetAttribute<RenamedFromAttribute>()?.previousName.Split('.').Last();
return title ?? string.Empty;
}
}
}

View File

@@ -0,0 +1,39 @@
using UnityEngine;
namespace Unity.VisualScripting
{
[Widget(typeof(ControlConnection))]
public sealed class ControlConnectionWidget : UnitConnectionWidget<ControlConnection>
{
public ControlConnectionWidget(FlowCanvas canvas, ControlConnection connection) : base(canvas, connection) { }
#region Drawing
public override Color color => Color.white;
protected override bool colorIfActive => !BoltFlow.Configuration.animateControlConnections || !BoltFlow.Configuration.animateValueConnections;
#endregion
#region Droplets
protected override bool showDroplets => BoltFlow.Configuration.animateControlConnections;
protected override Vector2 GetDropletSize()
{
return BoltFlow.Icons.valuePortConnected?[12].Size() ?? 12 * Vector2.one;
}
protected override void DrawDroplet(Rect position)
{
if (BoltFlow.Icons.valuePortConnected != null)
{
GUI.DrawTexture(position, BoltFlow.Icons.valuePortConnected[12]);
}
}
#endregion
}
}

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace Unity.VisualScripting
{
public interface IUnitConnectionWidget : IGraphElementWidget
{
Color color { get; }
}
}

View File

@@ -0,0 +1,28 @@
using UnityEngine;
namespace Unity.VisualScripting
{
[Widget(typeof(InvalidConnection))]
public sealed class InvalidConnectionWidget : UnitConnectionWidget<InvalidConnection>
{
public InvalidConnectionWidget(FlowCanvas canvas, InvalidConnection connection) : base(canvas, connection) { }
#region Drawing
public override Color color => UnitConnectionStyles.invalidColor;
#endregion
#region Droplets
protected override bool showDroplets => false;
protected override Vector2 GetDropletSize() => Vector2.zero;
protected override void DrawDroplet(Rect position) { }
#endregion
}
}

View File

@@ -0,0 +1,19 @@
using UnityEngine;
namespace Unity.VisualScripting
{
public static class UnitConnectionStyles
{
public static readonly Color activeColor = new Color(0.37f, 0.66f, 0.95f);
public static readonly Color highlightColor = new Color(1, 0.95f, 0f);
public static readonly Color invalidColor = new Color(1, 0, 0);
public static readonly Color disconnectColor = new Color(0.95f, 0.1f, 0.1f);
public static readonly float minBend = 15;
public static readonly float relativeBend = 1 / 4f;
}
}

View File

@@ -0,0 +1,247 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Unity.VisualScripting
{
public abstract class UnitConnectionWidget<TConnection> : GraphElementWidget<FlowCanvas, TConnection>, IUnitConnectionWidget
where TConnection : class, IUnitConnection
{
protected UnitConnectionWidget(FlowCanvas canvas, TConnection connection) : base(canvas, connection) { }
#region Model
protected TConnection connection => element;
protected IUnitConnectionDebugData ConnectionDebugData => GetDebugData<IUnitConnectionDebugData>();
#endregion
#region Lifecycle
public override void BeforeFrame()
{
base.BeforeFrame();
if (showDroplets)
{
GraphGUI.UpdateDroplets(canvas, droplets, ConnectionDebugData.lastInvokeFrame, ref lastInvokeTime, ref dropTime);
}
}
#endregion
#region Positioning
public override IEnumerable<IWidget> positionDependencies
{
get
{
yield return canvas.Widget(connection.source);
yield return canvas.Widget(connection.destination);
}
}
protected override bool snapToGrid => false;
public Rect sourceHandlePosition { get; private set; }
public Rect destinationHandlePosition { get; private set; }
public Vector2 sourceHandleEdgeCenter { get; private set; }
public Vector2 destinationHandleEdgeCenter { get; private set; }
public Vector2 middlePosition;
private Rect _position;
private Rect _clippingPosition;
public override Rect position
{
get { return _position; }
set { }
}
public override Rect clippingPosition => _clippingPosition;
public override void CachePosition()
{
base.CachePosition();
sourceHandlePosition = canvas.Widget<IUnitPortWidget>(connection.source).handlePosition;
destinationHandlePosition = canvas.Widget<IUnitPortWidget>(connection.destination).handlePosition;
sourceHandleEdgeCenter = sourceHandlePosition.GetEdgeCenter(Edge.Right);
destinationHandleEdgeCenter = destinationHandlePosition.GetEdgeCenter(Edge.Left);
middlePosition = (sourceHandlePosition.center + destinationHandlePosition.center) / 2;
_position = new Rect
(
middlePosition.x,
middlePosition.y,
0,
0
);
_clippingPosition = _position.Encompass(sourceHandleEdgeCenter).Encompass(destinationHandleEdgeCenter);
}
#endregion
#region Drawing
protected virtual bool colorIfActive => true;
public abstract Color color { get; }
protected override bool dim
{
get
{
var dim = BoltCore.Configuration.dimInactiveNodes && !connection.destination.unit.Analysis<UnitAnalysis>(context).isEntered;
if (BoltCore.Configuration.dimIncompatibleNodes && canvas.isCreatingConnection)
{
dim = true;
}
return dim;
}
}
public override void DrawBackground()
{
base.DrawBackground();
BeginDim();
DrawConnection();
if (showDroplets)
{
DrawDroplets();
}
EndDim();
}
protected virtual void DrawConnection()
{
var color = this.color;
var sourceWidget = canvas.Widget<IUnitPortWidget>(connection.source);
var destinationWidget = canvas.Widget<IUnitPortWidget>(connection.destination);
var highlight = !canvas.isCreatingConnection && (sourceWidget.isMouseOver || destinationWidget.isMouseOver);
var willDisconnect = sourceWidget.willDisconnect || destinationWidget.willDisconnect;
if (willDisconnect)
{
color = UnitConnectionStyles.disconnectColor;
}
else if (highlight)
{
color = UnitConnectionStyles.highlightColor;
}
else if (colorIfActive)
{
if (EditorApplication.isPaused)
{
if (EditorTimeBinding.frame == ConnectionDebugData.lastInvokeFrame)
{
color = UnitConnectionStyles.activeColor;
}
}
else
{
color = Color.Lerp(UnitConnectionStyles.activeColor, color, (EditorTimeBinding.time - ConnectionDebugData.lastInvokeTime) / UnitWidget<IUnit>.Styles.invokeFadeDuration);
}
}
var thickness = 3;
GraphGUI.DrawConnection(color, sourceHandleEdgeCenter, destinationHandleEdgeCenter, Edge.Right, Edge.Left, null, Vector2.zero, UnitConnectionStyles.relativeBend, UnitConnectionStyles.minBend, thickness);
}
#endregion
#region Selecting
public override bool canSelect => false;
#endregion
#region Dragging
public override bool canDrag => false;
#endregion
#region Deleting
public override bool canDelete => true;
#endregion
#region Droplets
private readonly List<float> droplets = new List<float>();
private float dropTime;
private float lastInvokeTime;
private const float handleAlignmentMargin = 0.1f;
protected virtual bool showDroplets => true;
protected abstract Vector2 GetDropletSize();
protected abstract void DrawDroplet(Rect position);
protected virtual void DrawDroplets()
{
foreach (var droplet in droplets)
{
Vector2 position;
if (droplet < handleAlignmentMargin)
{
var t = droplet / handleAlignmentMargin;
position = Vector2.Lerp(sourceHandlePosition.center, sourceHandleEdgeCenter, t);
}
else if (droplet > 1 - handleAlignmentMargin)
{
var t = (droplet - (1 - handleAlignmentMargin)) / handleAlignmentMargin;
position = Vector2.Lerp(destinationHandleEdgeCenter, destinationHandlePosition.center, t);
}
else
{
var t = (droplet - handleAlignmentMargin) / (1 - 2 * handleAlignmentMargin);
position = GraphGUI.GetPointOnConnection(t, sourceHandleEdgeCenter, destinationHandleEdgeCenter, Edge.Right, Edge.Left, UnitConnectionStyles.relativeBend, UnitConnectionStyles.minBend);
}
var size = GetDropletSize();
using (LudiqGUI.color.Override(GUI.color * color))
{
DrawDroplet(new Rect(position.x - size.x / 2, position.y - size.y / 2, size.x, size.y));
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,150 @@
using System;
using UnityEditor;
using UnityEngine;
namespace Unity.VisualScripting
{
[Widget(typeof(ValueConnection))]
public sealed class ValueConnectionWidget : UnitConnectionWidget<ValueConnection>
{
public ValueConnectionWidget(FlowCanvas canvas, ValueConnection connection) : base(canvas, connection) { }
private new ValueConnection.DebugData ConnectionDebugData => GetDebugData<ValueConnection.DebugData>();
#region Drawing
public override Color color => DetermineColor(connection.source.type, connection.destination.type);
protected override bool colorIfActive => !BoltFlow.Configuration.animateControlConnections || !BoltFlow.Configuration.animateValueConnections;
public override void DrawForeground()
{
base.DrawForeground();
if (BoltFlow.Configuration.showConnectionValues)
{
var showLastValue = EditorApplication.isPlaying && ConnectionDebugData.assignedLastValue;
var showPredictedvalue = BoltFlow.Configuration.predictConnectionValues && !EditorApplication.isPlaying && Flow.CanPredict(connection.source, reference);
if (showLastValue || showPredictedvalue)
{
var previousIconSize = EditorGUIUtility.GetIconSize();
EditorGUIUtility.SetIconSize(new Vector2(IconSize.Small, IconSize.Small));
object value;
if (showLastValue)
{
value = ConnectionDebugData.lastValue;
}
else // if (showPredictedvalue)
{
value = Flow.Predict(connection.source, reference);
}
var label = new GUIContent(value.ToShortString(), Icons.Type(value?.GetType())?[IconSize.Small]);
var labelSize = Styles.prediction.CalcSize(label);
var labelPosition = new Rect(position.position - labelSize / 2, labelSize);
BeginDim();
GUI.Label(labelPosition, label, Styles.prediction);
EndDim();
EditorGUIUtility.SetIconSize(previousIconSize);
}
}
}
public static Color DetermineColor(Type source, Type destination)
{
if (destination == typeof(object))
{
return DetermineColor(source);
}
return DetermineColor(destination);
}
public static Color DetermineColor(Type type)
{
if (type == null)
{
return new Color(0.8f, 0.8f, 0.8f);
}
if (type == typeof(string))
{
return new Color(1.0f, 0.62f, 0.35f);
}
if (type == typeof(bool))
{
return new Color(0.86f, 0.55f, 0.92f);
}
if (type == typeof(char))
{
return new Color(1.0f, 0.90f, 0.40f);
}
if (type.IsEnum)
{
return new Color(1.0f, 0.63f, 0.66f);
}
if (type.IsNumeric())
{
return new Color(0.45f, 0.78f, 1f);
}
if (type.IsNumericConstruct())
{
return new Color(0.45f, 1.00f, 0.82f);
}
return new Color(0.60f, 0.88f, 0.00f);
}
#endregion
#region Droplets
protected override bool showDroplets => BoltFlow.Configuration.animateValueConnections;
protected override Vector2 GetDropletSize()
{
return BoltFlow.Icons.valuePortConnected?[12].Size() ?? 12 * Vector3.one;
}
protected override void DrawDroplet(Rect position)
{
if (BoltFlow.Icons.valuePortConnected != null)
{
GUI.DrawTexture(position, BoltFlow.Icons.valuePortConnected?[12]);
}
}
#endregion
private static class Styles
{
static Styles()
{
prediction = new GUIStyle(EditorStyles.label);
prediction.normal.textColor = Color.white;
prediction.fontSize = 9;
prediction.normal.background = new Color(0, 0, 0, 0.25f).GetPixel();
prediction.padding = new RectOffset(4, 6, 3, 3);
prediction.margin = new RectOffset(0, 0, 0, 0);
prediction.alignment = TextAnchor.MiddleCenter;
}
public static readonly GUIStyle prediction;
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(FlowGraph))]
public sealed class FlowGraphDescriptor : GraphDescriptor<FlowGraph, GraphDescription>
{
public FlowGraphDescriptor(FlowGraph target) : base(target) { }
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(FlowMachine))]
public sealed class FlowMachineDescriptor : MachineDescriptor<FlowMachine, MachineDescription>
{
public FlowMachineDescriptor(FlowMachine target) : base(target) { }
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(ScriptGraphAsset))]
public sealed class FlowMacroDescriptor : MacroDescriptor<ScriptGraphAsset, MacroDescription>
{
public FlowMacroDescriptor(ScriptGraphAsset target) : base(target) { }
}
}

View File

@@ -0,0 +1,23 @@
namespace Unity.VisualScripting
{
public interface IUnitDescriptor : IDescriptor
{
IUnit unit { get; }
new UnitDescription description { get; }
string Title();
string ShortTitle();
string Surtitle();
string Subtitle();
string Summary();
EditorTexture Icon();
void DescribePort(IUnitPort port, UnitPortDescription description);
}
}

View File

@@ -0,0 +1,300 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Unity.VisualScripting
{
[Analyser(typeof(IUnit))]
public class UnitAnalyser<TUnit> : Analyser<TUnit, UnitAnalysis>
where TUnit : class, IUnit
{
public UnitAnalyser(GraphReference reference, TUnit target) : base(reference, target) { }
public TUnit unit => target;
[Assigns]
protected bool IsEntered()
{
using (var recursion = Recursion.New(1))
{
return IsEntered(unit, recursion);
}
}
private static bool IsEntered(IUnit unit, Recursion recursion)
{
if (unit.isControlRoot)
{
return true;
}
foreach (var controlInput in unit.controlInputs)
{
if (!controlInput.isPredictable || controlInput.couldBeEntered)
{
return true;
}
}
foreach (var valueOutput in unit.valueOutputs)
{
if (!recursion?.TryEnter(valueOutput) ?? false)
{
continue;
}
var valueOutputEntered = valueOutput.validConnections.Any(c => IsEntered(c.destination.unit, recursion));
recursion?.Exit(valueOutput);
if (valueOutputEntered)
{
return true;
}
}
return false;
}
private string PortLabel(IUnitPort port)
{
return port.Description<UnitPortDescription>().label;
}
[Assigns]
protected virtual IEnumerable<Warning> Warnings()
{
var isEntered = IsEntered();
if (!unit.isDefined)
{
if (unit.definitionException != null)
{
yield return Warning.Exception(unit.definitionException);
}
else if (!unit.canDefine)
{
yield return Warning.Caution("Unit is not properly configured.");
}
}
if (!isEntered)
{
yield return Warning.Info("Unit is never entered.");
}
// Obsolete attribute is not inherited, so traverse the chain manually
var obsoleteAttribute = unit.GetType().AndHierarchy().FirstOrDefault(t => t.HasAttribute<ObsoleteAttribute>())?.GetAttribute<ObsoleteAttribute>();
if (obsoleteAttribute != null)
{
var unitName = BoltFlowNameUtility.UnitTitle(unit.GetType(), true, false);
if (obsoleteAttribute.Message != null)
{
Debug.LogWarning($"\"{unitName}\" unit is deprecated: {obsoleteAttribute.Message}");
yield return Warning.Caution($"Deprecated: {obsoleteAttribute.Message}");
}
else
{
Debug.LogWarning($"\"{unitName}\" unit is deprecated.");
yield return Warning.Caution("This unit is deprecated.");
}
}
if (unit.isDefined)
{
foreach (var invalidInput in unit.invalidInputs)
{
yield return Warning.Caution($"{PortLabel(invalidInput)} is not used by this unit.");
}
foreach (var invalidOutput in unit.invalidOutputs)
{
yield return Warning.Caution($"{PortLabel(invalidOutput)} is not provided by this unit.");
}
foreach (var validPort in unit.validPorts)
{
if (validPort.hasInvalidConnection)
{
yield return Warning.Caution($"{PortLabel(validPort)} has an invalid connection.");
}
}
}
foreach (var controlInput in unit.controlInputs)
{
if (!controlInput.hasValidConnection)
{
continue;
}
foreach (var relation in controlInput.relations)
{
if (relation.source is ValueInput)
{
var valueInput = (ValueInput)relation.source;
foreach (var warning in ValueInputWarnings(valueInput))
{
yield return warning;
}
}
}
}
foreach (var controlOutput in unit.controlOutputs)
{
if (!controlOutput.hasValidConnection)
{
continue;
}
var controlInputs = controlOutput.relations.Select(r => r.source).OfType<ControlInput>();
var isTriggered = !controlInputs.Any() || controlInputs.Any(ci => !ci.isPredictable || ci.couldBeEntered);
foreach (var relation in controlOutput.relations)
{
if (relation.source is ValueInput)
{
var valueInput = (ValueInput)relation.source;
foreach (var warning in ValueInputWarnings(valueInput))
{
yield return warning;
}
}
}
if (isEntered && !isTriggered)
{
yield return Warning.Caution($"{PortLabel(controlOutput)} is connected, but it is never triggered.");
}
}
foreach (var valueOutput in unit.valueOutputs)
{
if (!valueOutput.hasValidConnection)
{
continue;
}
foreach (var relation in valueOutput.relations)
{
if (relation.source is ControlInput)
{
var controlInput = (ControlInput)relation.source;
if (isEntered && controlInput.isPredictable && !controlInput.couldBeEntered)
{
yield return Warning.Severe($"{PortLabel(controlInput)} is required, but it is never entered.");
}
}
else if (relation.source is ValueInput)
{
var valueInput = (ValueInput)relation.source;
foreach (var warning in ValueInputWarnings(valueInput))
{
yield return warning;
}
}
}
}
}
private IEnumerable<Warning> ValueInputWarnings(ValueInput valueInput)
{
// We can disable null reference check if no self is available
// and the port requires an owner, for example in macros.
var trustFutureOwner = valueInput.nullMeansSelf && reference.self == null;
var checkForNullReference = BoltFlow.Configuration.predictPotentialNullReferences && !valueInput.allowsNull && !trustFutureOwner;
var checkForMissingComponent = BoltFlow.Configuration.predictPotentialMissingComponents && typeof(Component).IsAssignableFrom(valueInput.type);
// Note that we cannot directly check the input's predicted value, because it
// will return false for safeguard specifically because it might be missing requirements.
// Therefore, we first check the connected value, then the default value.
// If the port is connected to a predictable output, use the connected value to perform checks.
if (valueInput.hasValidConnection)
{
var valueOutput = valueInput.validConnectedPorts.Single();
if (Flow.CanPredict(valueOutput, reference))
{
if (checkForNullReference)
{
if (Flow.Predict(valueOutput, reference) == null)
{
yield return Warning.Severe($"{PortLabel(valueInput)} cannot be null.");
}
}
if (checkForMissingComponent)
{
var connectedPredictedValue = Flow.Predict(valueOutput, reference);
// This check is necessary, because the predicted value could be
// incompatible as connections with non-guaranteed conversions are allowed.
if (ConversionUtility.CanConvert(connectedPredictedValue, typeof(GameObject), true))
{
var gameObject = ConversionUtility.Convert<GameObject>(connectedPredictedValue);
if (gameObject != null)
{
var component = (Component)ConversionUtility.Convert(gameObject, valueInput.type);
if (component == null)
{
yield return Warning.Caution($"{PortLabel(valueInput)} is missing a {valueInput.type.DisplayName()} component.");
}
}
}
}
}
}
// If the port isn't connected but has a default value, use the default value to perform checks.
else if (valueInput.hasDefaultValue)
{
if (checkForNullReference)
{
if (Flow.Predict(valueInput, reference) == null)
{
yield return Warning.Severe($"{PortLabel(valueInput)} cannot be null.");
}
}
if (checkForMissingComponent)
{
var unconnectedPredictedValue = Flow.Predict(valueInput, reference);
if (ConversionUtility.CanConvert(unconnectedPredictedValue, typeof(GameObject), true))
{
var gameObject = ConversionUtility.Convert<GameObject>(unconnectedPredictedValue);
if (gameObject != null)
{
var component = (Component)ConversionUtility.Convert(gameObject, valueInput.type);
if (component == null)
{
yield return Warning.Caution($"{PortLabel(valueInput)} is missing a {valueInput.type.DisplayName()} component.");
}
}
}
}
}
// The value isn't connected and has no default value,
// therefore it is certain to be missing at runtime.
else
{
yield return Warning.Severe($"{PortLabel(valueInput)} is missing.");
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Unity.VisualScripting
{
public sealed class UnitAnalysis : GraphElementAnalysis
{
public bool isEntered { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Unity.VisualScripting
{
public sealed class UnitDescription : GraphElementDescription
{
public string shortTitle { get; set; }
public string surtitle { get; set; }
public string subtitle { get; set; }
public EditorTexture[] icons { get; set; }
}
}

View File

@@ -0,0 +1,391 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace Unity.VisualScripting
{
[Descriptor(typeof(IUnit))]
public class UnitDescriptor<TUnit> : Descriptor<TUnit, UnitDescription>, IUnitDescriptor
where TUnit : class, IUnit
{
public UnitDescriptor(TUnit target) : base(target)
{
unitType = unit.GetType();
}
protected Type unitType { get; }
public TUnit unit => target;
IUnit IUnitDescriptor.unit => unit;
private enum State
{
Defined,
NotDefined,
FailedToDefine
}
private State state
{
get
{
if (unit.isDefined)
{
return State.Defined;
}
else if (unit.failedToDefine)
{
return State.FailedToDefine;
}
else
{
return State.NotDefined;
}
}
}
#region Reflected Description
static UnitDescriptor()
{
XmlDocumentation.loadComplete += FreeReflectedDescriptions;
}
public static void FreeReflectedDescriptions()
{
reflectedDescriptions.Clear();
reflectedInputDescriptions.Clear();
reflectedOutputDescriptions.Clear();
}
protected UnitDescription reflectedDescription
{
get
{
if (!reflectedDescriptions.TryGetValue(unitType, out var reflectedDescription))
{
reflectedDescription = FetchReflectedDescription(unitType);
reflectedDescriptions.Add(unitType, reflectedDescription);
}
return reflectedDescription;
}
}
protected UnitPortDescription ReflectedPortDescription(IUnitPort port)
{
if (port is IUnitInvalidPort)
{
return null;
}
if (port is IUnitInputPort)
{
if (!reflectedInputDescriptions.TryGetValue(unitType, out var _reflectedInputDescriptions))
{
_reflectedInputDescriptions = FetchReflectedPortDescriptions<IUnitInputPort>(unitType);
reflectedInputDescriptions.Add(unitType, _reflectedInputDescriptions);
}
if (_reflectedInputDescriptions.TryGetValue(port.key, out var portDescription))
{
return portDescription;
}
}
else if (port is IUnitOutputPort)
{
if (!reflectedOutputDescriptions.TryGetValue(unitType, out var _reflectedOutputDescriptions))
{
_reflectedOutputDescriptions = FetchReflectedPortDescriptions<IUnitOutputPort>(unitType);
reflectedOutputDescriptions.Add(unitType, _reflectedOutputDescriptions);
}
if (_reflectedOutputDescriptions.TryGetValue(port.key, out var portDescription))
{
return portDescription;
}
}
return null;
}
private static readonly Dictionary<Type, UnitDescription> reflectedDescriptions = new Dictionary<Type, UnitDescription>();
private static readonly Dictionary<Type, Dictionary<string, UnitPortDescription>> reflectedInputDescriptions = new Dictionary<Type, Dictionary<string, UnitPortDescription>>();
private static readonly Dictionary<Type, Dictionary<string, UnitPortDescription>> reflectedOutputDescriptions = new Dictionary<Type, Dictionary<string, UnitPortDescription>>();
private static UnitDescription FetchReflectedDescription(Type unitType)
{
var oldName = BoltFlowNameUtility.UnitPreviousTitle(unitType);
var prefix = string.IsNullOrEmpty(oldName) ? string.Empty : $"(Previously named {oldName}) ";
return new UnitDescription()
{
title = BoltFlowNameUtility.UnitTitle(unitType, false, true),
shortTitle = BoltFlowNameUtility.UnitTitle(unitType, true, true),
surtitle = unitType.GetAttribute<UnitSurtitleAttribute>()?.surtitle,
subtitle = unitType.GetAttribute<UnitSubtitleAttribute>()?.subtitle,
summary = prefix + unitType.Summary()
};
}
private static Dictionary<string, UnitPortDescription> FetchReflectedPortDescriptions<T>(Type unitType) where T : IUnitPort
{
var descriptions = new Dictionary<string, UnitPortDescription>();
foreach (var portMember in unitType.GetMembers().Where(member => typeof(T).IsAssignableFrom(member.GetAccessorType())))
{
var key = portMember.GetAttribute<PortKeyAttribute>()?.key ?? portMember.Name;
if (descriptions.ContainsKey(key))
{
Debug.LogWarning("Duplicate reflected port description for: " + key);
continue;
}
descriptions.Add(key, FetchReflectedPortDescription(portMember));
}
return descriptions;
}
private static UnitPortDescription FetchReflectedPortDescription(MemberInfo portMember)
{
return new UnitPortDescription()
{
label = portMember.GetAttribute<PortLabelAttribute>()?.label ?? portMember.HumanName(),
showLabel = !(portMember.HasAttribute<PortLabelHiddenAttribute>() || (portMember.GetAttribute<PortLabelAttribute>()?.hidden ?? false)),
summary = portMember.Summary(),
getMetadata = (unitMetadata) => unitMetadata[portMember.Name]
};
}
#endregion
#region Description
[Assigns]
public sealed override string Title()
{
switch (state)
{
case State.Defined: return DefinedTitle();
case State.NotDefined: return DefaultTitle();
case State.FailedToDefine: return ErrorTitle(unit.definitionException);
default: throw new UnexpectedEnumValueException<State>(state);
}
}
[Assigns]
public string ShortTitle()
{
switch (state)
{
case State.Defined: return DefinedShortTitle();
case State.NotDefined: return DefaultShortTitle();
case State.FailedToDefine: return ErrorShortTitle(unit.definitionException);
default: throw new UnexpectedEnumValueException<State>(state);
}
}
[Assigns]
public string Surtitle()
{
switch (state)
{
case State.Defined: return DefinedSurtitle();
case State.NotDefined: return DefaultSurtitle();
case State.FailedToDefine: return ErrorSurtitle(unit.definitionException);
default: throw new UnexpectedEnumValueException<State>(state);
}
}
[Assigns]
public string Subtitle()
{
switch (state)
{
case State.Defined: return DefinedSubtitle();
case State.NotDefined: return DefaultSubtitle();
case State.FailedToDefine: return ErrorSubtitle(unit.definitionException);
default: throw new UnexpectedEnumValueException<State>(state);
}
}
[Assigns]
public sealed override string Summary()
{
switch (state)
{
case State.Defined: return DefinedSummary();
case State.NotDefined: return DefaultSummary();
case State.FailedToDefine: return ErrorSummary(unit.definitionException);
default: throw new UnexpectedEnumValueException<State>(state);
}
}
[Assigns]
[RequiresUnityAPI]
public sealed override EditorTexture Icon()
{
switch (state)
{
case State.Defined: return DefinedIcon();
case State.NotDefined: return DefaultIcon();
case State.FailedToDefine: return ErrorIcon(unit.definitionException);
default: throw new UnexpectedEnumValueException<State>(state);
}
}
[Assigns]
[RequiresUnityAPI]
public IEnumerable<EditorTexture> Icons()
{
switch (state)
{
case State.Defined: return DefinedIcons();
case State.NotDefined: return DefaultIcons();
case State.FailedToDefine: return ErrorIcons(unit.definitionException);
default: throw new UnexpectedEnumValueException<State>(state);
}
}
protected virtual string DefinedTitle()
{
return reflectedDescription.title;
}
protected virtual string DefaultTitle()
{
return reflectedDescription.title;
}
protected virtual string ErrorTitle(Exception exception)
{
return reflectedDescription.title;
}
protected virtual string DefinedShortTitle()
{
return reflectedDescription.shortTitle;
}
protected virtual string DefaultShortTitle()
{
return reflectedDescription.shortTitle;
}
protected virtual string ErrorShortTitle(Exception exception)
{
return ErrorTitle(exception);
}
protected virtual string DefinedSurtitle()
{
return reflectedDescription.surtitle;
}
protected virtual string DefaultSurtitle()
{
return reflectedDescription.surtitle;
}
protected virtual string ErrorSurtitle(Exception exception)
{
return null;
}
protected virtual string DefinedSubtitle()
{
return reflectedDescription.subtitle;
}
protected virtual string DefaultSubtitle()
{
return reflectedDescription.subtitle;
}
protected virtual string ErrorSubtitle(Exception exception)
{
return null;
}
protected virtual string DefinedSummary()
{
return reflectedDescription.summary;
}
protected virtual string DefaultSummary()
{
return reflectedDescription.summary;
}
protected virtual string ErrorSummary(Exception exception)
{
return $"This unit failed to define.\n\n{exception.DisplayName()}: {exception.Message}";
}
protected virtual EditorTexture DefinedIcon()
{
return unit.GetType().Icon();
}
protected virtual EditorTexture DefaultIcon()
{
return unit.GetType().Icon();
}
protected virtual EditorTexture ErrorIcon(Exception exception)
{
return BoltCore.Icons.errorState;
}
protected virtual IEnumerable<EditorTexture> DefinedIcons()
{
return Enumerable.Empty<EditorTexture>();
}
protected virtual IEnumerable<EditorTexture> DefaultIcons()
{
return Enumerable.Empty<EditorTexture>();
}
protected virtual IEnumerable<EditorTexture> ErrorIcons(Exception exception)
{
return Enumerable.Empty<EditorTexture>();
}
public void DescribePort(IUnitPort port, UnitPortDescription description)
{
description.getMetadata = (unitMetadata) => unitMetadata.StaticObject(port);
// Only defined units can have specific ports
if (state == State.Defined)
{
DefinedPort(port, description);
}
}
protected virtual void DefinedPort(IUnitPort port, UnitPortDescription description)
{
var reflectedPortDescription = ReflectedPortDescription(port);
if (reflectedPortDescription != null)
{
description.CopyFrom(reflectedPortDescription);
}
}
#endregion
}
}

View File

@@ -0,0 +1,42 @@
using System;
namespace Unity.VisualScripting
{
public sealed class UnitPortDescription : IDescription
{
private string _label;
private bool _isLabelVisible = true;
public string fallbackLabel { get; set; }
public string label
{
get => _label ?? fallbackLabel;
set => _label = value;
}
public bool showLabel
{
get => !BoltFlow.Configuration.hidePortLabels || _isLabelVisible;
set => _isLabelVisible = value;
}
string IDescription.title => label;
public string summary { get; set; }
public EditorTexture icon { get; set; }
public Func<Metadata, Metadata> getMetadata { get; set; }
public void CopyFrom(UnitPortDescription other)
{
_label = other._label;
_isLabelVisible = other._isLabelVisible;
summary = other.summary;
icon = other.icon ?? icon;
getMetadata = other.getMetadata ?? getMetadata;
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
namespace Unity.VisualScripting
{
[Descriptor(typeof(IUnitPort))]
public sealed class UnitPortDescriptor : IDescriptor
{
public UnitPortDescriptor(IUnitPort target)
{
Ensure.That(nameof(target)).IsNotNull(target);
this.target = target;
}
public IUnitPort target { get; }
object IDescriptor.target => target;
public UnitPortDescription description { get; private set; } = new UnitPortDescription();
IDescription IDescriptor.description => description;
public bool isDirty { get; set; } = true;
public void Validate()
{
if (isDirty)
{
isDirty = false;
description.fallbackLabel = target.key.Filter(symbols: false, punctuation: false).Prettify();
UnityAPI.Async(() => description.icon = GetIcon(target));
target.unit?.Descriptor<IUnitDescriptor>().DescribePort(target, description);
// No DescriptionAssignment is run, so we'll just always assume that the description changes.
DescriptorProvider.instance.TriggerDescriptionChange(target);
}
}
private static EditorTexture GetIcon(IUnitPort port)
{
if (port is IUnitControlPort)
{
return typeof(Flow).Icon();
}
else if (port is IUnitValuePort)
{
return Icons.Type(((IUnitValuePort)port).type);
}
else if (port is IUnitInvalidPort)
{
return BoltCore.Resources.icons.errorState;
}
else
{
throw new NotSupportedException();
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More