Game/ControlSystem/ControlSystem.cs

System that runs control logic for occupied chairs each fixed update. It sorts occupied BaseChair instances by when they became occupied, builds connected object groups, and for each group pushes the occupant's input scope and calls IPlayerControllable.OnControl on eligible components.

Native Interop
public class ControlSystem : GameObjectSystem<ControlSystem>
{
	// When each chair first became occupied. Used to sort seats so the earliest occupant is in charge
	private readonly Dictionary<BaseChair, RealTimeSince> _occupiedSince = new();

	public ControlSystem( Scene scene ) : base( scene )
	{
		Listen( Stage.StartFixedUpdate, 10, OnTick, "ControlSystem" );
	}

	void OnTick()
	{
		// TODO this should be more generic, some kind of interface?
		var driven = new HashSet<GameObject>();

		foreach ( var chair in GetSortedSeats() )
		{
			var builder = new LinkedGameObjectBuilder();
			builder.AddConnected( chair.GameObject );

			// Skip if a seat occupied earlier already claimed this
			if ( builder.Objects.Any( driven.Contains ) ) continue;
			driven.UnionWith( builder.Objects );

			RunControl( chair, builder );
		}
	}

	IEnumerable<BaseChair> GetSortedSeats()
	{
		var chairs = Scene.GetAll<BaseChair>();

		foreach ( var chair in chairs )
		{
			if ( !chair.IsValid() || !chair.IsOccupied )
				_occupiedSince.Remove( chair );
			else
				_occupiedSince.TryAdd( chair, 0 );
		}

		return chairs
			.Where( c => c.IsValid() && c.IsOccupied )
			.OrderBy( c => (float)_occupiedSince.GetValueOrDefault( c, default ) );
	}

	void RunControl( BaseChair chair, LinkedGameObjectBuilder builder )
	{
		var player = chair.GetOccupant();
		if ( !player.IsValid() ) return;

		using var scope = ClientInput.PushScope( player );

		foreach ( var o in builder.Objects )
		{
			foreach ( var controllable in o.GetComponentsInChildren<IPlayerControllable>() )
			{
				if ( controllable is null ) continue;
				if ( !controllable.CanControl( player ) ) continue;

				controllable.OnControl();
			}
		}
	}
}