Engine Initialization
The path from WinMain through SDL window creation, subsystem construction, and into the main loop.
This article traces startup from WinMain to the moment the first frame ticks. The path is short on paper but strict in ordering: each subsystem depends on the ones that came before it, and the sequence in GameEngine::init is load-bearing. If you swap two lines you usually find out during a crash, not during a compile.
From WinMain to GameMain
WinMain does the Windows-specific work — registers the exception handlers, parses the command line, opens the SDL window — then hands control to GameMain. Window creation is one of the few places where platform code touches global engine state directly. initializeAppWindowsSDL in WinMain.cpp:921 creates the SDL3 window via Platform::SDLPlatform::Instance().Init(), extracts the native HWND for the D3D11 backend, and pushes the actual client size back into TheWritableGlobalData before anything else reads it:
ApplicationHWnd = (HWND)platform.GetNativeWindowHandle(); // Push the actual size into TheGlobalData *before* the renderer is created // so the swap chain matches the real window and isn't stretched. if (TheWritableGlobalData) { Int actualW = platform.GetWidth(); Int actualH = platform.GetHeight(); if (actualW > 0 && actualH > 0) { TheWritableGlobalData->m_xResolution = actualW; TheWritableGlobalData->m_yResolution = actualH; } }
Then GameMain takes over. It is deliberately tiny. GameMain.cpp:39 reads, in full:
TheFramePacer = new FramePacer(); TheFramePacer->enableFramesPerSecondLimit(TRUE); TheGameEngine = CreateGameEngine(); TheGameEngine->init(); // ... TheGameEngine->execute();
FramePacer is built before the engine on purpose — GameEngine::init queries it when reading frame-rate and scheduling options. CreateGameEngine in WinMain.cpp:1511 is the factory. On this build it returns an SDLGameEngine; originally it returned Win32GameEngine. The abstract factory is how the SDL port slotted in without touching gameplay code.
The subsystem init order
GameEngine::init in GameEngine.cpp:376 is one long ordered sequence of calls to initSubsystem, each of which registers the subsystem and optionally parses its default and override INI trees. The order matters in three escalating ways.
Filesystem first. TheLocalFileSystem at line 462 is created before anything else because every later INI load, every logo texture, every audio bank reaches through it. TheArchiveFileSystem at line 473 must come after — the archive system mounts .big files via the local FS. A subsystem that tries to log or read data before this point will silently find no files.
Data before consumers. TheWritableGlobalData (line 498), TheGameText (line 548), and the INI stores (TheScienceStore, TheMultiplayerSettings, TheTerrainTypes, TheTerrainRoads, TheGlobalLanguageData) all come before audio at line 571, because audio reads global volume flags and language-specific speech paths during its own init.
Factories before factory-consumers. TheModuleFactory at line 586 must exist before TheThingFactory at line 621, because every Object template instantiates the module classes the factory knows about. TheParticleSystemManager (line 592) precedes TheFXListStore (line 602) for the same reason. The standard shape of each line looks like:
initSubsystem(TheThingFactory, "TheThingFactory", createThingFactory(), &xferCRC, "Data\\INI\\Default\\Object", "Data\\INI\\Object");
The two path arguments are the default and override INI trees. The XferCRC argument accumulates a data-CRC across every INI the subsystem parses — that CRC is what multiplayer hosts compare to catch desync before a match starts.
The main loop
Once init returns, execute() runs until something sets m_quitting. GameEngine.cpp:1143 is where the game spends ~100% of its wall-clock time:
while( !m_quitting ) { try { update(); } catch (INIException e) { /* RELEASE_CRASH */ } catch (...) { /* RELEASE_CRASH */ } TheFramePacer->update(); }
update() walks the subsystem list and calls UPDATE() on each one in registration order — the same order init picked them up. m_quitting is a plain Bool; anything can flip it via setQuitting(TRUE) (the benchmark timer does, so does the shell menu's Exit button, so does the Windows WM_CLOSE path). There is no graceful-shutdown state — the loop just exits and GameMain deletes TheGameEngine and TheFramePacer.
Quirks
-xres/-yresdisables the startup maximize. If either was on the command line,TheGlobalData->m_explicitDisplayResolutionisTRUEandinitializeAppWindowsSDLpassesstartMaximized=falseso SDL opens the window at the exact requested size. Otherwise SDL'sSDL_WINDOW_MAXIMIZEDsilently substitutes the desktop work area and your-xres 1280turns into whatever fits the screen.- Resize is deferred.
gPendingResizeis set in the window proc (seeWinMain.cpp:491) and acted on by the main loop, because D3D device reset from inside a window-proc callback reenters the renderer mid-frame. The SDL event pump follows the same pattern. CreateGameEngineis an abstract factory. This build returnsSDLGameEngine. The original returnedWin32GameEngine. Nothing inGameEngine::initknows which one it is.TheFramePaceris created beforeTheGameEngine.GameEngine::initreads pacer options, so the order inGameMainis not cosmetic.- Logs emitted before
TheLocalFileSysteminit go nowhere.DEBUG_LOGupstream of line 462 resolves to a no-op file handle. The opening "Generals version ..." banner at line 385 is the only thing that logs before the FS is up, and it has its own in-memory buffer. TheNameKeyGeneratorandTheCommandListare outside the subsystem list on purpose. Line 434 and line 447 construct them directly. They survive engine resets because the name-key hashing is relied on byreset()itself.