Components/ManualLink.cs
/// <summary>
/// A non-physics logical link between two GameObjects.
/// Used by the Wire tool to group unconnected objects so the Duplicator
/// treats them as part of the same contraption.
/// </summary>
public sealed class ManualLink : Component
{
	[Property, Sync]
	public GameObject Body { get; set; }

	// The signal wire this link carries, if it was created by wiring two ports together.
	// Destroying the link (undo, the wire tool's unlink) disconnects the wire too.
	[Property, Sync, Hide]
	public Component SignalSource { get; set; }

	[Property, Sync, Hide]
	public string SignalOutputId { get; set; }

	[Property, Sync, Hide]
	public Component SignalTarget { get; set; }

	[Property, Sync, Hide]
	public string SignalInputId { get; set; }

	internal bool HasWire => SignalSource is not null && SignalTarget is not null;

	/// <summary>
	/// Wires whose output is the source component itself — reference and presence links.
	/// </summary>
	internal bool IsComponentWire => string.Equals( SignalOutputId, SignalSystem.ComponentOutputId, StringComparison.OrdinalIgnoreCase );

	/// <summary>
	/// Stamp the wire on both ends of the pair, so either side can resolve it from
	/// its own link children.
	/// </summary>
	internal void SetWire( Component source, string outputId, Component target, string inputId )
	{
		SetWireEnd( source, outputId, target, inputId );
		Body?.GetComponent<ManualLink>()?.SetWireEnd( source, outputId, target, inputId );
	}

	private void SetWireEnd( Component source, string outputId, Component target, string inputId )
	{
		SignalSource = source;
		SignalOutputId = outputId;
		SignalTarget = target;
		SignalInputId = inputId;
	}

	internal static GameObject[] CreatePair( GameObject first, GameObject second )
	{
		var firstLink = new GameObject( first, false, "link" );
		var secondLink = new GameObject( second, false, "link" );

		firstLink.AddComponent<ManualLink>().Body = secondLink;
		secondLink.AddComponent<ManualLink>().Body = firstLink;

		secondLink.NetworkSpawn();
		firstLink.NetworkSpawn();
		return [firstLink, secondLink];
	}

	protected override void OnDestroy()
	{
		SignalSystem.DisconnectWire( this );

		if ( Body.IsValid() )
			Body.Destroy();

		base.OnDestroy();
	}

	protected override void OnUpdate()
	{
		if ( !Body.IsValid() )
			DestroyGameObject();
	}
}