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