A game about forced loneliness, made by TACStudios
1using System;
2using UnityEngine;
3
4namespace Unity.VisualScripting
5{
6 /* Implementation note:
7 * Using an abstract base class works as a type unification workaround.
8 * https://stackoverflow.com/questions/22721763
9 * https://stackoverflow.com/a/7664919
10 *
11 * However, this forces us to use concrete classes for connections
12 * instead of interfaces. In other words, no IControlConnection / IValueConnection.
13 * If we did use interfaces, there would be ambiguity that needs to be resolved
14 * at every reference to the source or destination.
15 *
16 * However, using a disambiguator hack seems to confuse even recent Mono runtime versions of Unity
17 * and breaks its vtable. Sometimes, method pointers are just plain wrong.
18 * I'm guessing this is specifically due to InvalidConnection, which actually
19 * does unify the types; what the C# warning warned about.
20 * https://stackoverflow.com/q/50051657/154502
21 *
22 * THEREFORE, IUnitConnection has to be implemented at the concrete class level,
23 * because at that point the type unification warning is moot, because the type arguments are
24 * provided.
25 */
26
27 public abstract class UnitConnection<TSourcePort, TDestinationPort> : GraphElement<FlowGraph>, IConnection<TSourcePort, TDestinationPort>
28 where TSourcePort : class, IUnitOutputPort
29 where TDestinationPort : class, IUnitInputPort
30 {
31 [Obsolete(Serialization.ConstructorWarning)]
32 protected UnitConnection() { }
33
34 protected UnitConnection(TSourcePort source, TDestinationPort destination)
35 {
36 Ensure.That(nameof(source)).IsNotNull(source);
37 Ensure.That(nameof(destination)).IsNotNull(destination);
38
39 if (source.unit.graph != destination.unit.graph)
40 {
41 throw new NotSupportedException("Cannot create connections across graphs.");
42 }
43
44 if (source.unit == destination.unit)
45 {
46 throw new InvalidConnectionException("Cannot create connections on the same unit.");
47 }
48
49 sourceUnit = source.unit;
50 sourceKey = source.key;
51 destinationUnit = destination.unit;
52 destinationKey = destination.key;
53 }
54
55 public virtual IGraphElementDebugData CreateDebugData()
56 {
57 return new UnitConnectionDebugData();
58 }
59
60 #region Ports
61
62 [Serialize]
63 protected IUnit sourceUnit { get; private set; }
64
65 [Serialize]
66 protected string sourceKey { get; private set; }
67
68 [Serialize]
69 protected IUnit destinationUnit { get; private set; }
70
71 [Serialize]
72 protected string destinationKey { get; private set; }
73
74 [DoNotSerialize]
75 public abstract TSourcePort source { get; }
76
77 [DoNotSerialize]
78 public abstract TDestinationPort destination { get; }
79
80 #endregion
81
82 #region Dependencies
83
84 public override int dependencyOrder => 1;
85
86 public abstract bool sourceExists { get; }
87
88 public abstract bool destinationExists { get; }
89
90 protected void CopyFrom(UnitConnection<TSourcePort, TDestinationPort> source)
91 {
92 base.CopyFrom(source);
93 }
94
95 public override bool HandleDependencies()
96 {
97 // Replace the connection with an invalid connection if the ports are either missing or incompatible.
98 // If the ports are missing, create invalid ports if required.
99
100 var valid = true;
101 IUnitOutputPort source;
102 IUnitInputPort destination;
103
104 if (!sourceExists)
105 {
106 if (!sourceUnit.invalidOutputs.Contains(sourceKey))
107 {
108 sourceUnit.invalidOutputs.Add(new InvalidOutput(sourceKey));
109 }
110
111 source = sourceUnit.invalidOutputs[sourceKey];
112 valid = false;
113 }
114 else
115 {
116 source = this.source;
117 }
118
119 if (!destinationExists)
120 {
121 if (!destinationUnit.invalidInputs.Contains(destinationKey))
122 {
123 destinationUnit.invalidInputs.Add(new InvalidInput(destinationKey));
124 }
125
126 destination = destinationUnit.invalidInputs[destinationKey];
127 valid = false;
128 }
129 else
130 {
131 destination = this.destination;
132 }
133
134 if (!source.CanValidlyConnectTo(destination))
135 {
136 valid = false;
137 }
138
139 if (!valid && source.CanInvalidlyConnectTo(destination))
140 {
141 source.InvalidlyConnectTo(destination);
142
143 // Silence this warning if a unit with a missing type is involved (as it will not have any defined ports).
144 // This is to avoid drowning users in warning and error messages if a unit's script goes missing.
145 if (source.unit.GetType() != typeof(MissingType) && destination.unit.GetType() != typeof(MissingType))
146 {
147 Debug.LogWarning($"Could not load connection between '{source.key}' of '{sourceUnit}' and '{destination.key}' of '{destinationUnit}'.");
148 }
149 }
150
151 return valid;
152 }
153
154 #endregion
155
156 #region Analytics
157
158 public override AnalyticsIdentifier GetAnalyticsIdentifier()
159 {
160 return null;
161 }
162
163 #endregion
164 }
165}