Skip to content

Threads

Bartholomew Ho edited this page Nov 9, 2026 · 1 revision

The game runs several threads at once. Most FH2 classes must be used from one particular thread, because the game is in the middle of changing things on the others. Get it wrong and you get an exception that says where to call from, rather than a crash.

The threads

Thread Where your code runs on it What to use there
The game's thread Game.Tick, Game.Run, Game.PhysicsStep, ANNA entries Game, Player, Vehicle, World, UI, Audio and most others
The UI thread Menus.Frame, settings' Changed, pages, title-screen entries, a hotkey's Rebound Menus and UiElement, SettingsTile
The platform's thread OnInit, a hotkey's Pressed setup only: add settings, hotkeys, ANNA entries, handlers
Your own threads new Thread(...), Task.Run, timers waiting, files, the network; nothing in the game

Each class page says which thread it's for.

Getting onto the game's thread

From anywhere else, wrap the work in Game.Run:

// A hotkey runs on a platform thread, so hand the work to the game.
Pressed = () => Game.Run(() => Player.Car?.Repair()),
Pressed = Sub() Game.Run(Sub() Player.Car?.Repair())

It runs at the next game frame. Inside Game.Tick it runs straight away.

To get a value back, use Game.RunAsync and await it from your own thread:

float speed = await Game.RunAsync(() => Player.Car?.SpeedKmh ?? 0);

Never wait for RunAsync on the game's thread itself. It would wait for itself forever and freeze the game.

Game.IsGameThread tells you where you are.

Keep it short

Game.Tick and Menus.Frame run every frame, and the game waits for them. Do the slow work (files, the network, Thread.Sleep) on a thread of your own, and only hand the result to the game.

OnInit is the same: the game is waiting to start. Start a thread for anything slow:

protected override void OnInit()
{
    new Thread(() =>
    {
        ModPlatform.WaitForGame();
        // ...slow work...
    }) { IsBackground = true }.Start();
}

Exceptions

An exception thrown in Game.Tick, a setting's Changed, a command or any other callback is caught and written to platform.log. It never reaches the game, and your handler stays on.

Clone this wiki locally