Single-binary race loop: menu -> race -> menu in one process
WinMain now wraps the engine block in a loop: when a front-end-launched mission ends under the local console, the setup screen comes back in the same process instead of exiting (the arcade relaunch-per-mission model). Replaces the CreateProcess self-respawn - required for Steam, where the lobby and sockets must survive across races. Second-cycle re-init crash fixed: d3d_OBJECT kept a static texture cache keyed by filename, so race 2 got IDirect3DTexture9 pointers created on race 1 destroyed device and died at first draw (DrawMesh AV). The cache is now flushed in ~DPLRenderer before the device is released, and ParticleEngine::Initialize drops particles left over from the previous mission. Verified: three consecutive 30s races in one PID, each stopped on time by the console with final scores collected. Also: L4CONSOLELEN env override for test-length races, and the console exposes MissionCompleted() for the loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -277,6 +277,17 @@ L4TEXOP d3d_OBJECT::LoadTexture(LPDIRECT3DDEVICE9 device, const char *fileName)
|
||||
return texOp;
|
||||
}
|
||||
|
||||
void d3d_OBJECT::FlushTextureCache()
|
||||
{
|
||||
stdext::hash_map<std::string, L4TEXOP>::iterator iter;
|
||||
for (iter = mTextureCache.begin(); iter != mTextureCache.end(); ++iter)
|
||||
{
|
||||
if ((*iter).second.texture != NULL)
|
||||
(*iter).second.texture->Release();
|
||||
}
|
||||
mTextureCache.clear();
|
||||
}
|
||||
|
||||
d3d_OBJECT::d3d_OBJECT(LPDIRECT3DDEVICE9 device, int vertCount)
|
||||
: mDevice(device),
|
||||
mVertCount(vertCount),
|
||||
|
||||
@@ -106,6 +106,10 @@ public:
|
||||
static void ResetState(LPDIRECT3DDEVICE9 device);
|
||||
static d3d_OBJECT* LoadObject(LPDIRECT3DDEVICE9 device, char *fileName);
|
||||
static L4TEXOP LoadTexture(LPDIRECT3DDEVICE9 device, const char *fileName);
|
||||
// Cached textures are bound to the device that created them - the
|
||||
// cache MUST be flushed when that device goes away (the single-binary
|
||||
// race loop tears the renderer down between missions).
|
||||
static void FlushTextureCache();
|
||||
|
||||
private:
|
||||
static d3d_OBJECT* LoadSpheres(LPDIRECT3DDEVICE9 device, char *fileName);
|
||||
|
||||
@@ -260,6 +260,17 @@ void ParticleEngine::Initialize(LPDIRECT3DDEVICE9 device)
|
||||
mDevice = device;
|
||||
memset(mInstalledEffects, 0, sizeof(mInstalledEffects));
|
||||
|
||||
// particles left over from the previous mission (single-binary race
|
||||
// loop) reference the old device's resources - drop them
|
||||
while (mParticlesHead)
|
||||
{
|
||||
Particle *next = mParticlesHead->mNextParticle;
|
||||
delete mParticlesHead;
|
||||
mParticlesHead = next;
|
||||
}
|
||||
mParticlesTail = NULL;
|
||||
mTotalParticleCount = 0;
|
||||
|
||||
ParticleEngine::mMaxParticleCount = atoi(getenv("MAXPARTICLES"));
|
||||
|
||||
// create the vertex buffer that will store the four vertices we need for the billboards
|
||||
|
||||
@@ -3495,6 +3495,10 @@ DPLRenderer::~DPLRenderer()
|
||||
delete mAux2Index;
|
||||
}
|
||||
|
||||
// device-bound resources living in static caches would dangle into
|
||||
// the next race of the single-binary loop - drop them with the device
|
||||
d3d_OBJECT::FlushTextureCache();
|
||||
|
||||
SAFE_RELEASE(mDevice);
|
||||
SAFE_RELEASE(gD3D);
|
||||
//STUBBED: DPL RB 1/14/07
|
||||
|
||||
+41
-29
@@ -195,25 +195,45 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
|
||||
//
|
||||
//-------------------------------------------------------------------------
|
||||
// Front end: started without an egg, a network, or mission review ->
|
||||
// run the in-game race setup and build the mission egg locally.
|
||||
// Front-end mode: started without an egg, a network, or mission review.
|
||||
// The race loop below keeps everything in ONE process - setup screen,
|
||||
// mission, teardown, and back to the setup screen - with the local
|
||||
// console marshaling each race. (-egg / -net / -mr run a single pass,
|
||||
// exactly as before.)
|
||||
//-------------------------------------------------------------------------
|
||||
//
|
||||
if (L4Application::GetMissionReviewMode() == 0 &&
|
||||
Logical front_end_mode =
|
||||
L4Application::GetMissionReviewMode() == 0 &&
|
||||
L4Application::GetNetworkCommonFlatAddress() == 0 &&
|
||||
!L4Application::GetEggNotationFileName()) // no egg on the command line
|
||||
!L4Application::GetEggNotationFileName(); // no egg on the command line
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (front_end_mode)
|
||||
{
|
||||
if (!IsWindow(hWnd))
|
||||
{
|
||||
break; // the cockpit window is gone (closed mid-race)
|
||||
}
|
||||
|
||||
// drain any WM_QUIT left over from the previous mission's
|
||||
// teardown so it cannot instantly close the setup screen
|
||||
MSG leftover;
|
||||
while (PeekMessage(&leftover, NULL, WM_QUIT, WM_QUIT, PM_REMOVE))
|
||||
{
|
||||
}
|
||||
|
||||
static char frontend_egg[MAX_PATH];
|
||||
if (!RPL4FrontEnd_Run(hInstance, hWnd, frontend_egg, sizeof(frontend_egg)))
|
||||
{
|
||||
return 0; // player closed the menu without launching
|
||||
break; // player closed the menu without launching
|
||||
}
|
||||
L4Application::SetEggNotationFileName(frontend_egg);
|
||||
Exit_Code = 0;
|
||||
|
||||
// Front-end games are marshaled by the in-process console: it
|
||||
// ends the race when the selected time expires and loops back
|
||||
// to the setup screen. (Hand-fed -egg runs stay unmarshaled -
|
||||
// the developer shortcut.)
|
||||
// ends the race when the selected time expires. (Hand-fed -egg
|
||||
// runs stay unmarshaled - the developer shortcut.)
|
||||
RPL4LocalConsole_Install(RPL4FrontEnd_LastMissionSeconds());
|
||||
}
|
||||
|
||||
@@ -309,6 +329,19 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
delete app_manager;
|
||||
}
|
||||
|
||||
//
|
||||
//-------------------------------------------------------------------------
|
||||
// Single-binary race loop: if the local console ended this mission,
|
||||
// go back to the setup screen for the next one; anything else (user
|
||||
// quit, -egg / -net / -mr single pass) falls out.
|
||||
//-------------------------------------------------------------------------
|
||||
//
|
||||
if (!front_end_mode || !RPL4LocalConsole_MissionCompleted())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if !_DEBUG
|
||||
// symmetric with the fullscreen-only hide at startup
|
||||
if (L4Application::GetFullscreen())
|
||||
@@ -319,26 +352,5 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
|
||||
Stop_Registering();
|
||||
|
||||
//
|
||||
//-------------------------------------------------------------------------
|
||||
// Launcher role: when the local console ended the mission, respawn the
|
||||
// process for the next race (the arcade launcher restarted the pod after
|
||||
// every mission) - the fresh instance boots back into the setup screen.
|
||||
//-------------------------------------------------------------------------
|
||||
//
|
||||
if (RPL4LocalConsole_ShouldRelaunch())
|
||||
{
|
||||
STARTUPINFOW startup_info;
|
||||
PROCESS_INFORMATION process_info;
|
||||
memset(&startup_info, 0, sizeof(startup_info));
|
||||
startup_info.cb = sizeof(startup_info);
|
||||
if (CreateProcessW(NULL, GetCommandLineW(), NULL, NULL, FALSE,
|
||||
0, NULL, NULL, &startup_info, &process_info))
|
||||
{
|
||||
CloseHandle(process_info.hThread);
|
||||
CloseHandle(process_info.hProcess);
|
||||
}
|
||||
}
|
||||
|
||||
return Exit_Code;
|
||||
}
|
||||
|
||||
+14
-6
@@ -147,11 +147,10 @@ namespace
|
||||
break;
|
||||
|
||||
case PhaseStopped:
|
||||
// The application tears itself down after a stop (in the
|
||||
// arcade the launcher restarted the pod for the next
|
||||
// mission). WinMain asks ShouldRelaunch() on the way out
|
||||
// and respawns the process, landing back on the setup
|
||||
// screen with the collected results in the log.
|
||||
// The application tears itself down after a stop (arcade
|
||||
// pods were relaunched per mission). WinMain's race loop
|
||||
// asks MissionCompleted() and cycles back to the setup
|
||||
// screen in the same process.
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -160,6 +159,15 @@ namespace
|
||||
void
|
||||
RPL4LocalConsole_Install(int mission_seconds)
|
||||
{
|
||||
// debug: L4CONSOLELEN overrides the mission length (test races)
|
||||
const char *override_string = getenv("L4CONSOLELEN");
|
||||
if (override_string != NULL && atoi(override_string) > 0)
|
||||
{
|
||||
mission_seconds = atoi(override_string);
|
||||
DEBUG_STREAM << "LocalConsole: L4CONSOLELEN override, "
|
||||
<< mission_seconds << "s\n" << std::flush;
|
||||
}
|
||||
|
||||
gMissionSeconds = mission_seconds;
|
||||
InterlockedExchange(&gLengthMs, (LONG) mission_seconds * 1000);
|
||||
gPhase = PhaseWaiting;
|
||||
@@ -184,7 +192,7 @@ void
|
||||
}
|
||||
|
||||
Logical
|
||||
RPL4LocalConsole_ShouldRelaunch()
|
||||
RPL4LocalConsole_MissionCompleted()
|
||||
{
|
||||
return gPhase == PhaseStopped;
|
||||
}
|
||||
|
||||
+3
-4
@@ -21,8 +21,7 @@ void
|
||||
RPL4LocalConsole_Install(int mission_seconds);
|
||||
|
||||
// True when the last mission ended under the console's control (timer
|
||||
// stop or pilot exit). WinMain then plays the launcher role - the
|
||||
// arcade launcher restarted the pod after every mission - and respawns
|
||||
// the process, landing back on the race setup screen.
|
||||
// stop or pilot exit). WinMain's single-binary race loop then cycles
|
||||
// back to the setup screen for the next race in the same process.
|
||||
Logical
|
||||
RPL4LocalConsole_ShouldRelaunch();
|
||||
RPL4LocalConsole_MissionCompleted();
|
||||
|
||||
Reference in New Issue
Block a user