MIT License NuGet

Home / Docs / Platform entry points

Platform entry points

Your App class is shared. Each platform adds one small file that constructs it and hands it to a runner. That is the entire platform surface of a typical application — six files, none longer than a screen.

What a runner does

WindowsApp.Run and its siblings create the window or surface, bring up the graphics backend, call DeviceServices.Initialize with that platform's service implementations, and finish by invoking BaseApp.Init — which is what eventually calls your Create. Your code never constructs a device service and never touches a swap chain.

NamespaceRunnerBackend
Season.Platforms.WindowsWindowsApp.Run(BaseApp)Direct3D 12
Season.Platforms.LinuxLinuxApp.Run(BaseApp)Vulkan, on SDL
Season.Platforms.AndroidAndroidApp.Run(BaseApp)Vulkan
Season.Platforms.iOSiOSApp.Run(BaseApp)Metal
Season.Platforms.MacCatalystMacCatalystApp.Run(BaseApp)Metal
Season.Platforms.WebWebApp.Run(...), awaitableWebGPU

The six files

WindowsPlatforms/Windows/App.xaml.cs

using Season.Platforms.Windows;

public partial class App : Microsoft.UI.Xaml.Application
{
    protected override void OnLaunched(LaunchActivatedEventArgs args)
    {
        WindowsApp.Run(new Hello.App());
    }
}

LinuxPlatforms/Linux/Linux.cs

using Season.Platforms.Linux;

internal class Program
{
    static void Main(string[] args) => LinuxApp.Run(new App());
}

AndroidPlatforms/Android/MainActivity.cs

using Season.Platforms.Android;

[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true,
    ScreenOrientation = ScreenOrientation.FullSensor)]
internal class MainActivity : BaseActivity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        if (!AndroidApp.IsInitialized)
            AndroidApp.Run(new App());

        base.OnCreate(savedInstanceState);
    }
}

Deriving from BaseActivity is what wires the Android lifecycle and touch events into the engine. The AndroidApp.IsInitialized guard matters because Android recreates activities: a configuration change must not start a second engine.

iOS and Mac CatalystPlatforms/iOS/Program.cs

using Season.Platforms.iOS;

internal class Program
{
    static void Main(string[] args) => iOSApp.Run(new App());
}

Web — from a Blazor WebAssembly page, after the canvas exists

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (!firstRender || _started) return;
    _started = true;

    await WebApp.Run(new App(), JSRuntime, HttpClient, CanvasId, "");
}
The web runner is the one with parameters

WebApp.Run(BaseApp app, IJSRuntime jsRuntime, HttpClient httpClient, string canvasId = "season-canvas", string? assetBasePath = null). The IJSRuntime is how the engine reaches the WebGPU device, which lives in JavaScript; the HttpClient is how asset loading works, because a browser has no synchronous file system. Pass an assetBasePath if your assets are not served from the application root.

Asking which platform you are on

Occasionally you need to. DeviceServices.Core.Platform returns a Platform value — Windows, Linux, MacCatalyst, Android, iOS or Web — and the reference application uses it to keep features off the platforms that cannot host them.

if (DeviceServices.Core.Platform is Platform.Web)
{
    // No window capture, no local model inference.
}

// Services that are not implemented everywhere are null, not stubs.
if (DeviceServices.Recorder != null)
    await DeviceServices.Recorder.Start();

The null check is the convention, not a defensive habit: a service with no implementation on a platform is left null deliberately so that the gap is visible at the call site rather than failing silently at runtime. The full list is in cross-platform support.