LocalConsole: the in-process marshal that ends missions
Domain correction from playtest: hand-fed eggs are a developer shortcut - a mission only ends on a console command, so the clock hits 00:00 and counts up forever. Even single-player games need a console marshal. RPL4CONSOLE is that console. Like the real one it lives on its own thread: it owns the mission clock and raises the stop request at the selected length; the app-manager per-frame hook (new gPerFrameHook seam in APPMGR, called while the application global is live - the loop condition NULLs it on exit, which ate the first attempt) executes the engine-safe part, dispatching the same StopMissionMessage TeslaConsole sent. Final scores flow in through a new RP-layer sink (gConsoleScoreSink in RPCNSL): RPPlayer feeds it the same score it sends a real console at mission end. It also inherits the launcher role: the application tears down after a stop (arcade pods were relaunched per mission by TeslaLauncher), so WinMain respawns the process when the console ended the mission, landing back on the race-setup screen. L4NetworkManager grows FeedLocalEgg (the single-user egg-inject path, callable mid-session) for the future in-process loop. Verified end to end: menu -> 3:00 race -> stop dispatched exactly on time -> final score collected (host 1 = 4113) -> process respawned with the front end up. -egg runs stay unmarshaled (the dev shortcut). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
HWND ghWnd = 0;
|
HWND ghWnd = 0;
|
||||||
|
|
||||||
|
// per-frame observer slot (see RunMissions); the game layer registers
|
||||||
|
// the single-player local-console marshal here
|
||||||
|
void (*gPerFrameHook)() = NULL;
|
||||||
|
|
||||||
ApplicationManager* ApplicationManager::CurrentAppManager = NULL;
|
ApplicationManager* ApplicationManager::CurrentAppManager = NULL;
|
||||||
|
|
||||||
ApplicationManager::ApplicationManager(HINSTANCE hInstance, HWND hWnd, Scalar frame_rate) : Node(ApplicationManagerClassID), runningApplications(this)
|
ApplicationManager::ApplicationManager(HINSTANCE hInstance, HWND hWnd, Scalar frame_rate) : Node(ApplicationManagerClassID), runningApplications(this)
|
||||||
@@ -74,6 +78,17 @@ Start_Of_Frame:
|
|||||||
{
|
{
|
||||||
Check(application);
|
Check(application);
|
||||||
foregroundTasksRun++;
|
foregroundTasksRun++;
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
// Give the per-frame observer a slice while 'application' is live
|
||||||
|
// (the loop condition NULLs the global on exit). The single-player
|
||||||
|
// local-console marshal hangs off this; NULL when unregistered.
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
if (gPerFrameHook != NULL)
|
||||||
|
{
|
||||||
|
(*gPerFrameHook)();
|
||||||
|
}
|
||||||
|
|
||||||
if (!application->ExecuteForeground(end_of_frame, frameDuration))
|
if (!application->ExecuteForeground(end_of_frame, frameDuration))
|
||||||
{
|
{
|
||||||
if (!application->Shutdown(current_application.GetSize()))
|
if (!application->Shutdown(current_application.GetSize()))
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
extern HWND ghWnd;
|
extern HWND ghWnd;
|
||||||
|
|
||||||
|
// per-frame observer called once per RunMissions frame, after the
|
||||||
|
// foreground pass (the single-player local-console marshal uses this)
|
||||||
|
extern void (*gPerFrameHook)();
|
||||||
|
|
||||||
class ApplicationManager : public Node
|
class ApplicationManager : public Node
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -384,6 +384,32 @@ void L4NetworkManager::CreateConsoleHost()
|
|||||||
networkEggNotationFile = NULL;
|
networkEggNotationFile = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// L4NetworkManager::FeedLocalEgg Load a local egg file and post the same
|
||||||
|
// 'local egg' message the single-user startup posts, kicking the next
|
||||||
|
// mission cycle. Used by the in-game front end / local console.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
L4NetworkManager::FeedLocalEgg(const char *egg_path)
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
Check_Pointer(egg_path);
|
||||||
|
|
||||||
|
if (networkEggNotationFile != NULL)
|
||||||
|
{
|
||||||
|
Unregister_Object(networkEggNotationFile);
|
||||||
|
delete networkEggNotationFile;
|
||||||
|
networkEggNotationFile = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkEggNotationFile = new NotationFile(egg_path);
|
||||||
|
Register_Object(networkEggNotationFile);
|
||||||
|
networkEggNotationFile->WriteFile("last.egg");
|
||||||
|
|
||||||
|
ReceiveEggFileMessage egg_message(-1, 10, "local egg", 10);
|
||||||
|
application->Post(DefaultEventPriority, this, &egg_message);
|
||||||
|
}
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// L4NetworkManager::LoadMission This routine is called when a mission starts to
|
// L4NetworkManager::LoadMission This routine is called when a mission starts to
|
||||||
// allow the network management system to do stuff (potentially establishing
|
// allow the network management system to do stuff (potentially establishing
|
||||||
|
|||||||
@@ -296,6 +296,11 @@ public:
|
|||||||
//static MessageHandlerSet MessageHandlers;
|
//static MessageHandlerSet MessageHandlers;
|
||||||
static MessageHandlerSet& GetMessageHandlers();
|
static MessageHandlerSet& GetMessageHandlers();
|
||||||
|
|
||||||
|
// Load a local egg file and kick the mission cycle - the same path
|
||||||
|
// the single-user startup takes; used by the in-game front end /
|
||||||
|
// local console for the race-after-race loop.
|
||||||
|
void FeedLocalEgg(const char *egg_path);
|
||||||
|
|
||||||
void ReceiveEggFileMessageHandler(
|
void ReceiveEggFileMessageHandler(
|
||||||
ReceiveEggFileMessage *EggMessage);
|
ReceiveEggFileMessage *EggMessage);
|
||||||
void AcknowledgeEggFileMessageHandler(
|
void AcknowledgeEggFileMessageHandler(
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
#include "rpcnsl.h"
|
#include "rpcnsl.h"
|
||||||
|
|
||||||
|
// see rpcnsl.h - the in-process console registers its intake here
|
||||||
|
void (*gConsoleScoreSink)(int player_host_ID, int final_score) = NULL;
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~ ConsolePlayerVTVScoredMessage ~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~ ConsolePlayerVTVScoredMessage ~~~~~~~~~~~~~~~~~~~~~~
|
||||||
ConsolePlayerVTVScoredMessage::
|
ConsolePlayerVTVScoredMessage::
|
||||||
ConsolePlayerVTVScoredMessage(HostID player_host_ID):
|
ConsolePlayerVTVScoredMessage(HostID player_host_ID):
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ enum
|
|||||||
ConsolePlayerVTVDamagedMessageID = 6
|
ConsolePlayerVTVDamagedMessageID = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~ Local console sink ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// When an in-process console marshals the game (RP412 single player),
|
||||||
|
// this points at its final-score intake; the player's mission-ending
|
||||||
|
// path feeds it the same score it sends a real console. NULL when no
|
||||||
|
// local console is present.
|
||||||
|
extern void (*gConsoleScoreSink)(int player_host_ID, int final_score);
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~ ConsolePlayerVTVScoredMessage ~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~ ConsolePlayerVTVScoredMessage ~~~~~~~~~~~~~~~~~~~
|
||||||
#ifdef MAC
|
#ifdef MAC
|
||||||
#pragma options align=mac68k4byte
|
#pragma options align=mac68k4byte
|
||||||
|
|||||||
@@ -122,6 +122,15 @@ void
|
|||||||
Player::MissionEndingMessageHandler(message);
|
Player::MissionEndingMessageHandler(message);
|
||||||
|
|
||||||
Check(application);
|
Check(application);
|
||||||
|
|
||||||
|
// feed the in-process console the same final score a real console
|
||||||
|
// gets (no-op when none is registered)
|
||||||
|
if (gConsoleScoreSink != NULL &&
|
||||||
|
application->GetApplicationState() == Application::EndingMission)
|
||||||
|
{
|
||||||
|
(*gConsoleScoreSink)((int) ownerID, (int) currentScore);
|
||||||
|
}
|
||||||
|
|
||||||
HostManager *host_manager = application->GetHostManager();
|
HostManager *host_manager = application->GetHostManager();
|
||||||
Check(host_manager);
|
Check(host_manager);
|
||||||
Host *console_host = host_manager->GetConsoleHost();
|
Host *console_host = host_manager->GetConsoleHost();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "rpl4pb.h"
|
#include "rpl4pb.h"
|
||||||
#include "rpl4fe.h"
|
#include "rpl4fe.h"
|
||||||
|
#include "rpl4console.h"
|
||||||
#include "..\munga_l4\l4splr.h"
|
#include "..\munga_l4\l4splr.h"
|
||||||
#include "rpl4ver.h"
|
#include "rpl4ver.h"
|
||||||
#include "..\munga\resver.h"
|
#include "..\munga\resver.h"
|
||||||
@@ -208,6 +209,12 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
return 0; // player closed the menu without launching
|
return 0; // player closed the menu without launching
|
||||||
}
|
}
|
||||||
L4Application::SetEggNotationFileName(frontend_egg);
|
L4Application::SetEggNotationFileName(frontend_egg);
|
||||||
|
|
||||||
|
// 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.)
|
||||||
|
RPL4LocalConsole_Install(RPL4FrontEnd_LastMissionSeconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -311,5 +318,27 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
Stop_Registering();
|
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;
|
return Exit_Code;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
#include "..\munga_l4\mungal4.h"
|
||||||
|
#pragma hdrstop
|
||||||
|
|
||||||
|
#include "rpl4console.h"
|
||||||
|
#include "rpl4fe.h"
|
||||||
|
#include "..\munga\appmgr.h"
|
||||||
|
#include "..\munga\appmsg.h"
|
||||||
|
#include "..\rp\rpcnsl.h"
|
||||||
|
#include "..\munga_l4\l4app.h"
|
||||||
|
#include "..\munga_l4\l4net.h"
|
||||||
|
|
||||||
|
//########################################################################
|
||||||
|
// The local console runs on ITS OWN THREAD, like the real console: it
|
||||||
|
// stays alive across the whole session, owns the mission clock, and
|
||||||
|
// raises the stop request when the selected length expires. The game
|
||||||
|
// thread's per-frame tick is the only place engine calls happen - it
|
||||||
|
// reports state transitions to the console thread and executes the
|
||||||
|
// requested StopMissionMessage dispatch (the engine is single
|
||||||
|
// threaded; cross-thread dispatch is not safe).
|
||||||
|
//
|
||||||
|
// Results flow in through gConsoleScoreSink (RP layer): the same final
|
||||||
|
// scores RPPlayer sent the arcade console at mission end.
|
||||||
|
//########################################################################
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
enum ConsolePhase
|
||||||
|
{
|
||||||
|
PhaseWaiting = 0, // waiting for the mission to start running
|
||||||
|
PhaseRunning, // mission running, console thread watching the clock
|
||||||
|
PhaseStopped // stop dispatched, waiting for teardown
|
||||||
|
};
|
||||||
|
|
||||||
|
ConsolePhase gPhase = PhaseWaiting;
|
||||||
|
int gMissionSeconds = 0;
|
||||||
|
Application *gWatchedApp = NULL;
|
||||||
|
|
||||||
|
HANDLE gConsoleThread = NULL;
|
||||||
|
|
||||||
|
// shared with the console thread
|
||||||
|
volatile LONG gMissionRunning = 0;
|
||||||
|
volatile LONG gStopRequested = 0;
|
||||||
|
volatile LONG gShuttingDown = 0;
|
||||||
|
volatile LONG gRunStartTick = 0;
|
||||||
|
volatile LONG gLengthMs = 0;
|
||||||
|
|
||||||
|
// collected mission results (this session's last race)
|
||||||
|
enum { maxResults = 16 };
|
||||||
|
struct FinalScore
|
||||||
|
{
|
||||||
|
int hostID;
|
||||||
|
int score;
|
||||||
|
};
|
||||||
|
FinalScore gResults[maxResults];
|
||||||
|
int gResultCount = 0;
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// The console thread: the mission clock lives here
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
DWORD WINAPI ConsoleThreadProc(LPVOID)
|
||||||
|
{
|
||||||
|
while (!gShuttingDown)
|
||||||
|
{
|
||||||
|
Sleep(250);
|
||||||
|
|
||||||
|
if (gMissionRunning && !gStopRequested)
|
||||||
|
{
|
||||||
|
LONG length_ms = gLengthMs;
|
||||||
|
if (length_ms > 0 &&
|
||||||
|
(LONG)(GetTickCount() - (DWORD) gRunStartTick) >= length_ms)
|
||||||
|
{
|
||||||
|
InterlockedExchange(&gStopRequested, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Final-score intake (called on the game thread from RPPlayer's
|
||||||
|
// mission-ending path, via the RP-layer sink)
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
void CollectFinalScore(int host_ID, int score)
|
||||||
|
{
|
||||||
|
if (gResultCount < maxResults)
|
||||||
|
{
|
||||||
|
gResults[gResultCount].hostID = host_ID;
|
||||||
|
gResults[gResultCount].score = score;
|
||||||
|
++gResultCount;
|
||||||
|
}
|
||||||
|
DEBUG_STREAM << "LocalConsole: final score, host " << host_ID
|
||||||
|
<< " = " << score << "\n" << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// The game-thread tick: state reporting + engine-safe execution
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
void ConsoleTick()
|
||||||
|
{
|
||||||
|
if (application == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gPhase != PhaseWaiting && application != gWatchedApp)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int state = application->GetApplicationState();
|
||||||
|
|
||||||
|
switch (gPhase)
|
||||||
|
{
|
||||||
|
case PhaseWaiting:
|
||||||
|
if (state == Application::RunningMission)
|
||||||
|
{
|
||||||
|
gPhase = PhaseRunning;
|
||||||
|
gWatchedApp = application;
|
||||||
|
gResultCount = 0;
|
||||||
|
InterlockedExchange(&gRunStartTick, (LONG) GetTickCount());
|
||||||
|
InterlockedExchange(&gStopRequested, 0);
|
||||||
|
InterlockedExchange(&gMissionRunning, 1);
|
||||||
|
DEBUG_STREAM << "LocalConsole: mission running, length "
|
||||||
|
<< gMissionSeconds << "s\n" << std::flush;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PhaseRunning:
|
||||||
|
if (state != Application::RunningMission)
|
||||||
|
{
|
||||||
|
// mission ended some other way (pilot exit etc.)
|
||||||
|
InterlockedExchange(&gMissionRunning, 0);
|
||||||
|
gPhase = PhaseStopped;
|
||||||
|
}
|
||||||
|
else if (gStopRequested)
|
||||||
|
{
|
||||||
|
//-----------------------------------------------------
|
||||||
|
// The console clock expired: end the race exactly the
|
||||||
|
// way the arcade console did, otherwise the mission
|
||||||
|
// clock rolls past 00:00 and counts up forever.
|
||||||
|
//-----------------------------------------------------
|
||||||
|
DEBUG_STREAM << "LocalConsole: time expired - stopping mission\n" << std::flush;
|
||||||
|
InterlockedExchange(&gMissionRunning, 0);
|
||||||
|
Application::StopMissionMessage message(0);
|
||||||
|
application->Dispatch(&message);
|
||||||
|
gPhase = PhaseStopped;
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
RPL4LocalConsole_Install(int mission_seconds)
|
||||||
|
{
|
||||||
|
gMissionSeconds = mission_seconds;
|
||||||
|
InterlockedExchange(&gLengthMs, (LONG) mission_seconds * 1000);
|
||||||
|
gPhase = PhaseWaiting;
|
||||||
|
gWatchedApp = NULL;
|
||||||
|
|
||||||
|
// game-thread execution point
|
||||||
|
gPerFrameHook = &ConsoleTick;
|
||||||
|
|
||||||
|
// results intake from the RP layer
|
||||||
|
gConsoleScoreSink = &CollectFinalScore;
|
||||||
|
|
||||||
|
// the console itself lives on its own thread, like the real one
|
||||||
|
if (gConsoleThread == NULL)
|
||||||
|
{
|
||||||
|
gConsoleThread = CreateThread(
|
||||||
|
NULL, 0, ConsoleThreadProc, NULL, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_STREAM << "LocalConsole: installed (length "
|
||||||
|
<< mission_seconds << "s, console thread "
|
||||||
|
<< (gConsoleThread != NULL ? "up" : "FAILED") << ")\n" << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logical
|
||||||
|
RPL4LocalConsole_ShouldRelaunch()
|
||||||
|
{
|
||||||
|
return gPhase == PhaseStopped;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "..\munga\style.h"
|
||||||
|
|
||||||
|
//########################################################################
|
||||||
|
//########################## RPL4 Local Console ##########################
|
||||||
|
//########################################################################
|
||||||
|
//
|
||||||
|
// The single-player marshal. Hand-fed eggs are a developer shortcut: a
|
||||||
|
// mission only ends on a console command, so without one the timer hits
|
||||||
|
// 00:00 and just counts up forever. This is the in-process console for
|
||||||
|
// front-end-launched games - it watches the mission, dispatches the
|
||||||
|
// same StopMissionMessage TeslaConsole sent when the selected length
|
||||||
|
// expires, and when the pod returns to WaitingForEgg it brings the race
|
||||||
|
// setup back up and feeds the next egg (or quits if the player closes
|
||||||
|
// the menu).
|
||||||
|
//
|
||||||
|
// Installs itself as the application manager's per-frame hook.
|
||||||
|
//
|
||||||
|
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.
|
||||||
|
Logical
|
||||||
|
RPL4LocalConsole_ShouldRelaunch();
|
||||||
@@ -152,6 +152,7 @@ namespace
|
|||||||
};
|
};
|
||||||
|
|
||||||
FEState *gFE = NULL;
|
FEState *gFE = NULL;
|
||||||
|
int gLastMissionSeconds = 0;
|
||||||
|
|
||||||
const COLORREF kGreenBright = RGB(64, 255, 64);
|
const COLORREF kGreenBright = RGB(64, 255, 64);
|
||||||
const COLORREF kGreenDim = RGB(24, 140, 24);
|
const COLORREF kGreenDim = RGB(24, 140, 24);
|
||||||
@@ -680,6 +681,8 @@ Logical
|
|||||||
Logical launched = fe.launched;
|
Logical launched = fe.launched;
|
||||||
if (launched)
|
if (launched)
|
||||||
{
|
{
|
||||||
|
gLastMissionSeconds = kLengths[fe.selection[GroupLength]].seconds;
|
||||||
|
|
||||||
GetWindowTextA(fe.nameEdit, fe.pilotName, sizeof(fe.pilotName) - 1);
|
GetWindowTextA(fe.nameEdit, fe.pilotName, sizeof(fe.pilotName) - 1);
|
||||||
if (fe.pilotName[0] == '\0')
|
if (fe.pilotName[0] == '\0')
|
||||||
{
|
{
|
||||||
@@ -718,6 +721,12 @@ Logical
|
|||||||
return launched;
|
return launched;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
RPL4FrontEnd_LastMissionSeconds()
|
||||||
|
{
|
||||||
|
return gLastMissionSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
//########################################################################
|
//########################################################################
|
||||||
// Ordinal plasma graphics (verbatim from the console / TEST.EGG)
|
// Ordinal plasma graphics (verbatim from the console / TEST.EGG)
|
||||||
//########################################################################
|
//########################################################################
|
||||||
|
|||||||
@@ -23,3 +23,8 @@ Logical
|
|||||||
char *egg_path_out,
|
char *egg_path_out,
|
||||||
int egg_path_size
|
int egg_path_size
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The race length (seconds; 0 = endless) selected at the last
|
||||||
|
// successful launch - the local console marshals the mission with it.
|
||||||
|
int
|
||||||
|
RPL4FrontEnd_LastMissionSeconds();
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include=".\RPL4.CPP" />
|
<ClCompile Include=".\RPL4.CPP" />
|
||||||
<ClCompile Include=".\RPL4APP.cpp" />
|
<ClCompile Include=".\RPL4APP.cpp" />
|
||||||
|
<ClCompile Include=".\RPL4CONSOLE.cpp" />
|
||||||
<ClCompile Include=".\RPL4FE.cpp" />
|
<ClCompile Include=".\RPL4FE.cpp" />
|
||||||
<ClCompile Include=".\RPL4ARND.cpp" />
|
<ClCompile Include=".\RPL4ARND.cpp" />
|
||||||
<ClCompile Include=".\RPL4GAUG.cpp" />
|
<ClCompile Include=".\RPL4GAUG.cpp" />
|
||||||
@@ -142,6 +143,7 @@
|
|||||||
<ClInclude Include=".\RPL4.h" />
|
<ClInclude Include=".\RPL4.h" />
|
||||||
<ClInclude Include=".\RPL4.HPP" />
|
<ClInclude Include=".\RPL4.HPP" />
|
||||||
<ClInclude Include=".\RPL4APP.h" />
|
<ClInclude Include=".\RPL4APP.h" />
|
||||||
|
<ClInclude Include=".\RPL4CONSOLE.h" />
|
||||||
<ClInclude Include=".\RPL4FE.h" />
|
<ClInclude Include=".\RPL4FE.h" />
|
||||||
<ClInclude Include=".\RPL4ARND.h" />
|
<ClInclude Include=".\RPL4ARND.h" />
|
||||||
<ClInclude Include=".\RPL4GAUG.h" />
|
<ClInclude Include=".\RPL4GAUG.h" />
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
<ClCompile Include=".\RPL4FE.cpp">
|
<ClCompile Include=".\RPL4FE.cpp">
|
||||||
<Filter>Source Files\RP_L4</Filter>
|
<Filter>Source Files\RP_L4</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include=".\RPL4CONSOLE.cpp">
|
||||||
|
<Filter>Source Files\RP_L4</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include=".\RPL4ARND.cpp">
|
<ClCompile Include=".\RPL4ARND.cpp">
|
||||||
<Filter>Source Files\RP_L4</Filter>
|
<Filter>Source Files\RP_L4</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@@ -140,6 +143,9 @@
|
|||||||
<ClInclude Include=".\RPL4FE.h">
|
<ClInclude Include=".\RPL4FE.h">
|
||||||
<Filter>Header Files\RP_L4</Filter>
|
<Filter>Header Files\RP_L4</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include=".\RPL4CONSOLE.h">
|
||||||
|
<Filter>Header Files\RP_L4</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include=".\RPL4ARND.h">
|
<ClInclude Include=".\RPL4ARND.h">
|
||||||
<Filter>Header Files\RP_L4</Filter>
|
<Filter>Header Files\RP_L4</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|||||||
@@ -84,6 +84,19 @@ so press-feedback works like the real button field.
|
|||||||
`RPMission.ToEggString` port incl. GDI-rendered plasma name bitmaps)
|
`RPMission.ToEggString` port incl. GDI-rendered plasma name bitmaps)
|
||||||
and feeds the standard egg-load path. Verified end to end: menu →
|
and feeds the standard egg-load path. Verified end to end: menu →
|
||||||
LAUNCH → generated egg → racing in the cockpit.
|
LAUNCH → generated egg → racing in the cockpit.
|
||||||
|
- ✅ **LocalConsole marshal** (`RP_L4/RPL4CONSOLE.cpp`): hand-fed eggs are
|
||||||
|
a dev shortcut — a mission only ends on a console command, otherwise
|
||||||
|
the clock rolls past 00:00 and counts up forever. Front-end games are
|
||||||
|
now marshaled by an in-process console **on its own thread** (like the
|
||||||
|
real one): it owns the mission clock, dispatches the arcade's
|
||||||
|
`StopMissionMessage` at expiry, and collects the final scores through
|
||||||
|
the same intake `RPPlayer` fed the real console (`gConsoleScoreSink`).
|
||||||
|
It also plays the **launcher role**: after a marshaled mission end the
|
||||||
|
process respawns (the arcade launcher restarted pods per mission),
|
||||||
|
landing back on the setup screen. Verified: 3:00 race ends at 3:00,
|
||||||
|
score collected, fresh instance boots to the menu. `-egg` runs stay
|
||||||
|
unmarshaled (the dev shortcut, timer counts up as before).
|
||||||
|
Results SCREEN (showing the collected scores) is the next brick.
|
||||||
- See [RP412-FRONTEND-DESIGN.md](RP412-FRONTEND-DESIGN.md) for the
|
- See [RP412-FRONTEND-DESIGN.md](RP412-FRONTEND-DESIGN.md) for the
|
||||||
TeslaConsole control-code analysis and the Steam lobby mapping this
|
TeslaConsole control-code analysis and the Steam lobby mapping this
|
||||||
builds toward (lobby owner = console, SteamID list = pilot list,
|
builds toward (lobby owner = console, SteamID list = pilot list,
|
||||||
|
|||||||
Reference in New Issue
Block a user