Platform
Every Engine package needs a window before it needs anything else. Wyrd.Ecs.Platform opens one and owns SDL’s video subsystem for as long as PlatformSystem lives.
Opening a window
Section titled “Opening a window”using Wyrd.Ecs.Platform;
var world = new WorldBuilder() .AddWindow("My Game", 1280, 720) .Build();AddWindow registers a PlatformSystem that calls SDL_Init(Video) and creates the window immediately. Every other Engine package (AddRenderer, AddInput) resolves this system by type, but construction order doesn’t matter: call AddWindow before or after them in the same chain, Build() sorts it out. It still has to be called somewhere in the chain, though - Build() throws if it isn’t.
Reading the window and events
Section titled “Reading the window and events”var platform = world.GetSystem<PlatformSystem>();platform.Window; // the native SDL window handleplatform.Events; // every SDL event pumped this tickWindow is a raw IntPtr, for consumers reaching past this package into SDL3-CS directly. Events refills once per tick, before anything else runs, PlatformSystem schedules itself first.
Devices connecting and disconnecting
Section titled “Devices connecting and disconnecting”public sealed partial class DeviceLogSystem(World world) : QuerySystem{ private readonly EventReader<DeviceChange> _deviceChanges = world.CreateEventReader<DeviceChange>();
protected override IQuery DefineQuery(Query query) => query;
public void Update(Time time) { foreach (var change in _deviceChanges.Read()) { // change.DeviceId, change.DeviceKind (Keyboard or Mouse), change.Change } }}PlatformSystem is the single source of DeviceChange, one event per real hot-plug, covering both directions so a reader can’t handle connects while forgetting disconnects.
Running the game loop
Section titled “Running the game loop”world.Run();Run blocks the calling thread, calling world.Update(...) once per iteration with the real elapsed time since the last call, until an Exit event stops it. Call Update directly to embed your own loop, a host application or a test harness.
world.Run(targetFrameTime: TimeSpan.FromSeconds(1.0 / 60));A windowed app leaves targetFrameTime at its default of null, the renderer’s swapchain vsync paces the loop. A headless world passes a target to sleep between iterations instead of spinning a core at 100%.
Stopping the loop
Section titled “Stopping the loop”world.RequestExit(); // clean shutdown, code 0world.RequestExit(1); // a non-zero exit codeRequestExit emits an Exit event; Run returns on the next iteration that observes one. Call it from any system, or from outside the tick loop entirely, a watchdog, a host process’s own shutdown signal.
Closing the window emits one too: PlatformSystem calls RequestExit on SDL.EventType.Quit, read off the same event pump above.
Renderer and Input both build on the window AddWindow opens.