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 : Descriptor, 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(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(unitType); reflectedOutputDescriptions.Add(unitType, _reflectedOutputDescriptions); } if (_reflectedOutputDescriptions.TryGetValue(port.key, out var portDescription)) { return portDescription; } } return null; } private static readonly Dictionary reflectedDescriptions = new Dictionary(); private static readonly Dictionary> reflectedInputDescriptions = new Dictionary>(); private static readonly Dictionary> reflectedOutputDescriptions = new Dictionary>(); 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()?.surtitle, subtitle = unitType.GetAttribute()?.subtitle, summary = prefix + unitType.Summary() }; } private static Dictionary FetchReflectedPortDescriptions(Type unitType) where T : IUnitPort { var descriptions = new Dictionary(); foreach (var portMember in unitType.GetMembers().Where(member => typeof(T).IsAssignableFrom(member.GetAccessorType()))) { var key = portMember.GetAttribute()?.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()?.label ?? portMember.HumanName(), showLabel = !(portMember.HasAttribute() || (portMember.GetAttribute()?.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); } } [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); } } [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); } } [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); } } [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); } } [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); } } [Assigns] [RequiresUnityAPI] public IEnumerable 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); } } 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 DefinedIcons() { return Enumerable.Empty(); } protected virtual IEnumerable DefaultIcons() { return Enumerable.Empty(); } protected virtual IEnumerable ErrorIcons(Exception exception) { return Enumerable.Empty(); } 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 } }