test
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Descriptor(typeof(FlowGraph))]
|
||||
public sealed class FlowGraphDescriptor : GraphDescriptor<FlowGraph, GraphDescription>
|
||||
{
|
||||
public FlowGraphDescriptor(FlowGraph target) : base(target) { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2945371b70d5248c29fba485e0f535f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Descriptor(typeof(ScriptMachine))]
|
||||
public sealed class FlowMachineDescriptor : MachineDescriptor<ScriptMachine, MachineDescription>
|
||||
{
|
||||
public FlowMachineDescriptor(ScriptMachine target) : base(target) { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf11352faae8e4195bed44acfb96c522
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Descriptor(typeof(ScriptGraphAsset))]
|
||||
public sealed class FlowMacroDescriptor : MacroDescriptor<ScriptGraphAsset, MacroDescription>
|
||||
{
|
||||
public FlowMacroDescriptor(ScriptGraphAsset target) : base(target) { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23070ced5c01f495eb6b3dcc75ab6fc1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56af1e1695c6a47bb826414e1888797f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,324 @@
|
||||
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("Node is not properly configured.");
|
||||
}
|
||||
}
|
||||
else if (unit is MissingType)
|
||||
{
|
||||
var formerType = $"{(unit as MissingType)?.formerType}";
|
||||
formerType = string.IsNullOrEmpty(formerType) ? string.Empty : $"'{formerType}'";
|
||||
yield return new ActionButtonWarning(
|
||||
WarningLevel.Error,
|
||||
$"The source script for this node type can't be found. Did you remove its script?\n" +
|
||||
$"Replace the node or add the {formerType} script file back to your project files.",
|
||||
"Replace Node",
|
||||
() =>
|
||||
{ UnitWidgetHelper.ReplaceUnit(unit, reference, context, context.selection, new EventWrapper(unit)); }
|
||||
);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!isEntered)
|
||||
{
|
||||
yield return Warning.Info("Node 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}\" node is deprecated: {obsoleteAttribute.Message}");
|
||||
yield return Warning.Caution($"Deprecated: {obsoleteAttribute.Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"\"{unitName}\" node is deprecated.");
|
||||
yield return Warning.Caution("This node 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.");
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_IOS || UNITY_ANDROID || UNITY_TVOS
|
||||
if (unit is IMouseEventUnit)
|
||||
{
|
||||
var graphName = string.IsNullOrEmpty(unit.graph.title) ? "A ScriptGraph" : $"The ScriptGraph {unit.graph.title}";
|
||||
var unitName = BoltFlowNameUtility.UnitTitle(unit.GetType(), true, false);
|
||||
Debug.LogWarning($"{graphName} contains a {unitName} node. Presence of MouseEvent nodes might impact performance on handheld devices.");
|
||||
yield return Warning.Caution("Presence of MouseEvent nodes might impact performance on handheld devices.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59cd7f950ffc142d997761f2547b6bfb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,7 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class UnitAnalysis : GraphElementAnalysis
|
||||
{
|
||||
public bool isEntered { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c1bdc9069f004a5f96a18d68a450d49
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 046d84976aadf4fa58001515d8167521
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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 node 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 nodes 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
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66a77230ee8e44f3f917beab188d7ba2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class UnitPortDescription : IDescription
|
||||
{
|
||||
private string _label;
|
||||
|
||||
private bool _isLabelVisible = true;
|
||||
|
||||
internal IUnitPort portType;
|
||||
|
||||
public EditorTexture icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_icon == null || !_icon.IsValid())
|
||||
{
|
||||
_icon = GetIcon(portType);
|
||||
}
|
||||
|
||||
return _icon;
|
||||
}
|
||||
set => _icon = value;
|
||||
}
|
||||
|
||||
private EditorTexture _icon;
|
||||
|
||||
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 Func<Metadata, Metadata> getMetadata { get; set; }
|
||||
|
||||
public void CopyFrom(UnitPortDescription other)
|
||||
{
|
||||
_label = other._label;
|
||||
_isLabelVisible = other._isLabelVisible;
|
||||
summary = other.summary;
|
||||
portType = other.portType ?? portType;
|
||||
getMetadata = other.getMetadata ?? getMetadata;
|
||||
}
|
||||
|
||||
private static EditorTexture GetIcon(IUnitPort portType)
|
||||
{
|
||||
if (portType is IUnitControlPort)
|
||||
{
|
||||
return typeof(Flow).Icon();
|
||||
}
|
||||
else if (portType is IUnitValuePort)
|
||||
{
|
||||
return Icons.Type(((IUnitValuePort)portType).type);
|
||||
}
|
||||
else if (portType is IUnitInvalidPort)
|
||||
{
|
||||
return BoltCore.Resources.icons.errorState;
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new NotSupportedException();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51835fa842102474c83eeea00b0602aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,44 @@
|
||||
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;
|
||||
|
||||
description.portType = 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();
|
||||
|
||||
description.portType = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a272fe91f6be43dfb94b0c752db3a9c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user