Emulator/Sio/LinkCableHost.cs
using System.Threading;

namespace sGBA;

public sealed class WakeLatch
{
	private readonly SemaphoreSlim _sem = new( 0, int.MaxValue );

	public void Set()
	{
		if ( _sem.CurrentCount == 0 )
		{
			try { _sem.Release(); }
			catch ( SemaphoreFullException ) { }
		}
	}

	public void Wait( int timeoutMs )
	{
		_sem.Wait( timeoutMs );
	}
}

public sealed class LinkCablePlayerUser : ILockstepUser
{
	private readonly LinkCableInstance _instance;

	public LinkCablePlayerUser( LinkCableInstance instance )
	{
		_instance = instance;
	}

	public void Sleep() { }

	public void Wake()
	{
		_instance.WakeSignal.Set();
	}

	public int RequestedId()
	{
		return _instance.RequestedId;
	}

	public void PlayerIdChanged( int id )
	{
		_instance.PlayerId = id;
	}
}

public sealed class LinkCableInstance
{
	public Gba Core { get; }
	public GbaSioLockstepDriver Driver { get; }
	public LinkCablePlayerUser User { get; }

	public int RequestedId { get; }
	public int Slot { get; }
	public int PlayerId { get; set; } = -1;

	public ushort Keys { get; set; } = 0x03FF;
	public long LastHarvestedFrame { get; set; }
	public long LastUploadedFrame { get; set; } = -1;
	public long PendingVideoFrame { get; set; } = -1;
	public RealTimeSince TimeSinceStateSent { get; set; }

	public object CoreLock { get; } = new();
	public WakeLatch WakeSignal { get; } = new();
	public bool PaceMaster { get; set; }

	public LinkCableInstance( Gba core, int requestedId )
	{
		Core = core;
		RequestedId = requestedId;
		Slot = requestedId;
		User = new LinkCablePlayerUser( this );
		Driver = new GbaSioLockstepDriver( User );
	}
}

public sealed class LinkCableHost
{
	public GbaSioLockstepCoordinator Coordinator { get; } = new();

	private readonly List<LinkCableInstance> _instances = [];
	private readonly object _listLock = new();

	public IReadOnlyList<LinkCableInstance> Instances => _instances;

	public LinkCableInstance Attach( Gba core, int requestedId )
	{
		var inst = new LinkCableInstance( core, requestedId );
		Coordinator.Attach( inst.Driver );
		core.Io.SetSioDriver( inst.Driver );
		lock ( _listLock )
			_instances.Add( inst );
		return inst;
	}

	public void Detach( LinkCableInstance inst )
	{
		if ( inst == null )
			return;
		lock ( _listLock )
		{
			if ( !_instances.Remove( inst ) )
				return;
		}
		if ( ReferenceEquals( inst.Core.Io.SioDriver, inst.Driver ) )
			inst.Core.Io.SetSioDriver( null );
		Coordinator.Detach( inst.Driver );
	}

	public LinkCableInstance FindBySlot( int slot )
	{
		lock ( _listLock )
		{
			for ( int i = 0; i < _instances.Count; i++ )
			{
				if ( _instances[i].Slot == slot )
					return _instances[i];
			}
		}
		return null;
	}
}