A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Reflection;
5using UnityEngine;
6using UnityObject = UnityEngine.Object;
7
8namespace Unity.VisualScripting
9{
10 [Descriptor(typeof(IUnit))]
11 public class UnitDescriptor<TUnit> : Descriptor<TUnit, UnitDescription>, IUnitDescriptor
12 where TUnit : class, IUnit
13 {
14 public UnitDescriptor(TUnit target) : base(target)
15 {
16 unitType = unit.GetType();
17 }
18
19 protected Type unitType { get; }
20
21 public TUnit unit => target;
22
23 IUnit IUnitDescriptor.unit => unit;
24
25 private enum State
26 {
27 Defined,
28
29 NotDefined,
30
31 FailedToDefine
32 }
33
34 private State state
35 {
36 get
37 {
38 if (unit.isDefined)
39 {
40 return State.Defined;
41 }
42 else if (unit.failedToDefine)
43 {
44 return State.FailedToDefine;
45 }
46 else
47 {
48 return State.NotDefined;
49 }
50 }
51 }
52
53
54 #region Reflected Description
55
56 static UnitDescriptor()
57 {
58 XmlDocumentation.loadComplete += FreeReflectedDescriptions;
59 }
60
61 public static void FreeReflectedDescriptions()
62 {
63 reflectedDescriptions.Clear();
64 reflectedInputDescriptions.Clear();
65 reflectedOutputDescriptions.Clear();
66 }
67
68 protected UnitDescription reflectedDescription
69 {
70 get
71 {
72 if (!reflectedDescriptions.TryGetValue(unitType, out var reflectedDescription))
73 {
74 reflectedDescription = FetchReflectedDescription(unitType);
75 reflectedDescriptions.Add(unitType, reflectedDescription);
76 }
77
78 return reflectedDescription;
79 }
80 }
81
82 protected UnitPortDescription ReflectedPortDescription(IUnitPort port)
83 {
84 if (port is IUnitInvalidPort)
85 {
86 return null;
87 }
88
89 if (port is IUnitInputPort)
90 {
91 if (!reflectedInputDescriptions.TryGetValue(unitType, out var _reflectedInputDescriptions))
92 {
93 _reflectedInputDescriptions = FetchReflectedPortDescriptions<IUnitInputPort>(unitType);
94 reflectedInputDescriptions.Add(unitType, _reflectedInputDescriptions);
95 }
96
97 if (_reflectedInputDescriptions.TryGetValue(port.key, out var portDescription))
98 {
99 return portDescription;
100 }
101 }
102 else if (port is IUnitOutputPort)
103 {
104 if (!reflectedOutputDescriptions.TryGetValue(unitType, out var _reflectedOutputDescriptions))
105 {
106 _reflectedOutputDescriptions = FetchReflectedPortDescriptions<IUnitOutputPort>(unitType);
107 reflectedOutputDescriptions.Add(unitType, _reflectedOutputDescriptions);
108 }
109
110 if (_reflectedOutputDescriptions.TryGetValue(port.key, out var portDescription))
111 {
112 return portDescription;
113 }
114 }
115
116 return null;
117 }
118
119 private static readonly Dictionary<Type, UnitDescription> reflectedDescriptions = new Dictionary<Type, UnitDescription>();
120
121 private static readonly Dictionary<Type, Dictionary<string, UnitPortDescription>> reflectedInputDescriptions = new Dictionary<Type, Dictionary<string, UnitPortDescription>>();
122
123 private static readonly Dictionary<Type, Dictionary<string, UnitPortDescription>> reflectedOutputDescriptions = new Dictionary<Type, Dictionary<string, UnitPortDescription>>();
124
125 private static UnitDescription FetchReflectedDescription(Type unitType)
126 {
127 var oldName = BoltFlowNameUtility.UnitPreviousTitle(unitType);
128 var prefix = string.IsNullOrEmpty(oldName) ? string.Empty : $"(Previously named {oldName}) ";
129
130 return new UnitDescription()
131 {
132 title = BoltFlowNameUtility.UnitTitle(unitType, false, true),
133 shortTitle = BoltFlowNameUtility.UnitTitle(unitType, true, true),
134 surtitle = unitType.GetAttribute<UnitSurtitleAttribute>()?.surtitle,
135 subtitle = unitType.GetAttribute<UnitSubtitleAttribute>()?.subtitle,
136 summary = prefix + unitType.Summary()
137 };
138 }
139
140 private static Dictionary<string, UnitPortDescription> FetchReflectedPortDescriptions<T>(Type unitType) where T : IUnitPort
141 {
142 var descriptions = new Dictionary<string, UnitPortDescription>();
143
144 foreach (var portMember in unitType.GetMembers().Where(member => typeof(T).IsAssignableFrom(member.GetAccessorType())))
145 {
146 var key = portMember.GetAttribute<PortKeyAttribute>()?.key ?? portMember.Name;
147
148 if (descriptions.ContainsKey(key))
149 {
150 Debug.LogWarning("Duplicate reflected port description for: " + key);
151
152 continue;
153 }
154
155 descriptions.Add(key, FetchReflectedPortDescription(portMember));
156 }
157
158 return descriptions;
159 }
160
161 private static UnitPortDescription FetchReflectedPortDescription(MemberInfo portMember)
162 {
163 return new UnitPortDescription()
164 {
165 label = portMember.GetAttribute<PortLabelAttribute>()?.label ?? portMember.HumanName(),
166 showLabel = !(portMember.HasAttribute<PortLabelHiddenAttribute>() || (portMember.GetAttribute<PortLabelAttribute>()?.hidden ?? false)),
167 summary = portMember.Summary(),
168 getMetadata = (unitMetadata) => unitMetadata[portMember.Name]
169 };
170 }
171
172 #endregion
173
174
175 #region Description
176
177 [Assigns]
178 public sealed override string Title()
179 {
180 switch (state)
181 {
182 case State.Defined: return DefinedTitle();
183 case State.NotDefined: return DefaultTitle();
184 case State.FailedToDefine: return ErrorTitle(unit.definitionException);
185 default: throw new UnexpectedEnumValueException<State>(state);
186 }
187 }
188
189 [Assigns]
190 public string ShortTitle()
191 {
192 switch (state)
193 {
194 case State.Defined: return DefinedShortTitle();
195 case State.NotDefined: return DefaultShortTitle();
196 case State.FailedToDefine: return ErrorShortTitle(unit.definitionException);
197 default: throw new UnexpectedEnumValueException<State>(state);
198 }
199 }
200
201 [Assigns]
202 public string Surtitle()
203 {
204 switch (state)
205 {
206 case State.Defined: return DefinedSurtitle();
207 case State.NotDefined: return DefaultSurtitle();
208 case State.FailedToDefine: return ErrorSurtitle(unit.definitionException);
209 default: throw new UnexpectedEnumValueException<State>(state);
210 }
211 }
212
213 [Assigns]
214 public string Subtitle()
215 {
216 switch (state)
217 {
218 case State.Defined: return DefinedSubtitle();
219 case State.NotDefined: return DefaultSubtitle();
220 case State.FailedToDefine: return ErrorSubtitle(unit.definitionException);
221 default: throw new UnexpectedEnumValueException<State>(state);
222 }
223 }
224
225 [Assigns]
226 public sealed override string Summary()
227 {
228 switch (state)
229 {
230 case State.Defined: return DefinedSummary();
231 case State.NotDefined: return DefaultSummary();
232 case State.FailedToDefine: return ErrorSummary(unit.definitionException);
233 default: throw new UnexpectedEnumValueException<State>(state);
234 }
235 }
236
237 [Assigns]
238 [RequiresUnityAPI]
239 public sealed override EditorTexture Icon()
240 {
241 switch (state)
242 {
243 case State.Defined: return DefinedIcon();
244 case State.NotDefined: return DefaultIcon();
245 case State.FailedToDefine: return ErrorIcon(unit.definitionException);
246 default: throw new UnexpectedEnumValueException<State>(state);
247 }
248 }
249
250 [Assigns]
251 [RequiresUnityAPI]
252 public IEnumerable<EditorTexture> Icons()
253 {
254 switch (state)
255 {
256 case State.Defined: return DefinedIcons();
257 case State.NotDefined: return DefaultIcons();
258 case State.FailedToDefine: return ErrorIcons(unit.definitionException);
259 default: throw new UnexpectedEnumValueException<State>(state);
260 }
261 }
262
263 protected virtual string DefinedTitle()
264 {
265 return reflectedDescription.title;
266 }
267
268 protected virtual string DefaultTitle()
269 {
270 return reflectedDescription.title;
271 }
272
273 protected virtual string ErrorTitle(Exception exception)
274 {
275 return reflectedDescription.title;
276 }
277
278 protected virtual string DefinedShortTitle()
279 {
280 return reflectedDescription.shortTitle;
281 }
282
283 protected virtual string DefaultShortTitle()
284 {
285 return reflectedDescription.shortTitle;
286 }
287
288 protected virtual string ErrorShortTitle(Exception exception)
289 {
290 return ErrorTitle(exception);
291 }
292
293 protected virtual string DefinedSurtitle()
294 {
295 return reflectedDescription.surtitle;
296 }
297
298 protected virtual string DefaultSurtitle()
299 {
300 return reflectedDescription.surtitle;
301 }
302
303 protected virtual string ErrorSurtitle(Exception exception)
304 {
305 return null;
306 }
307
308 protected virtual string DefinedSubtitle()
309 {
310 return reflectedDescription.subtitle;
311 }
312
313 protected virtual string DefaultSubtitle()
314 {
315 return reflectedDescription.subtitle;
316 }
317
318 protected virtual string ErrorSubtitle(Exception exception)
319 {
320 return null;
321 }
322
323 protected virtual string DefinedSummary()
324 {
325 return reflectedDescription.summary;
326 }
327
328 protected virtual string DefaultSummary()
329 {
330 return reflectedDescription.summary;
331 }
332
333 protected virtual string ErrorSummary(Exception exception)
334 {
335 return $"This node failed to define.\n\n{exception.DisplayName()}: {exception.Message}";
336 }
337
338 protected virtual EditorTexture DefinedIcon()
339 {
340 return unit.GetType().Icon();
341 }
342
343 protected virtual EditorTexture DefaultIcon()
344 {
345 return unit.GetType().Icon();
346 }
347
348 protected virtual EditorTexture ErrorIcon(Exception exception)
349 {
350 return BoltCore.Icons.errorState;
351 }
352
353 protected virtual IEnumerable<EditorTexture> DefinedIcons()
354 {
355 return Enumerable.Empty<EditorTexture>();
356 }
357
358 protected virtual IEnumerable<EditorTexture> DefaultIcons()
359 {
360 return Enumerable.Empty<EditorTexture>();
361 }
362
363 protected virtual IEnumerable<EditorTexture> ErrorIcons(Exception exception)
364 {
365 return Enumerable.Empty<EditorTexture>();
366 }
367
368 public void DescribePort(IUnitPort port, UnitPortDescription description)
369 {
370 description.getMetadata = (unitMetadata) => unitMetadata.StaticObject(port);
371
372 // Only defined nodes can have specific ports
373 if (state == State.Defined)
374 {
375 DefinedPort(port, description);
376 }
377 }
378
379 protected virtual void DefinedPort(IUnitPort port, UnitPortDescription description)
380 {
381 var reflectedPortDescription = ReflectedPortDescription(port);
382
383 if (reflectedPortDescription != null)
384 {
385 description.CopyFrom(reflectedPortDescription);
386 }
387 }
388
389 #endregion
390 }
391}