Files
RP412/MUNGA/APP.h
T
CydandClaude Opus 5 f5ad3036cc Keeping the race, chosen under YOUR ROLE
The recording tee, which is what the Live Cam was for.

L4SpoolingApplication turned out not to be the obstacle it looked like.
The review build already records by teeing - it spools each packet and
then hands it on - and what tied that to a review build was never the
recording but where the buffer came from.
MissionReviewApplicationManager is only a pool allocator, and SpoolFile
takes whatever buffer it is handed, so SpoolRecorder owns one buffer and
needs none of it.

One hook covers what the review build taps in two places. Both
InterestManager and NetworkManager derive from NetworkClient, so
NetworkClient::ReceiveNetworkPacket sees entity updates and mission
control alike, and it sits before Dispatch so a packet is kept whether or
not anything downstream wants it. The recorder arms on the first packet
rather than at the green light, because playback rebuilds the world from
the LoadMission and RunMission packets and a spool that starts at the flag
cannot be replayed.

Two things a live recorder must do that the review one did not.

It must not touch the packet. The spooler restamps in place with local
arrival time, which is right in itself - playback paces off those stamps
and packets from different senders carry different clock origins - but the
sender's timestamp is what Simulation::ReadUpdateRecord hands to
RP412NETCLOCK and from there to the projection. Overwriting it live would
feed arrival jitter into where remote pods are drawn, which is the tick
just fixed. So the write position is taken first and the COPY is stamped,
in the spool, afterwards.

And it must not take the race down. SpoolFile::SpoolPacket answers a full
buffer with PostQuitMessage - a fair end to a replay, and killing the race
being recorded on a live host. The recorder checks the room first and
stops, and says so.

RP412RECORDSIZE defaults to 100MB rather than the review build's 6, on
Cyd's call: a full grid sends around 17KB a second, so six megabytes is
six minutes and a hundred is an hour and a half, which costs nothing on
any machine that can run this.

Saved at the buzzer - the first of the two StopMissions, the end of the
race rather than the fade timer - into SPOOLS\<timestamp>.spl and copied
to last.spl, matching where the review build looks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:54:53 -05:00

816 lines
17 KiB
C++

#pragma once
#include "network.h"
#include "event.h"
#include "state.h"
#include "cstr.h"
#if defined(TRACE_FOREGROUND_PROCESSING)
extern BitTrace Foreground_Processing;
#define SET_FOREGROUND_PROCESSING() Foreground_Processing.Set()
#define CLEAR_FOREGROUND_PROCESSING() Foreground_Processing.Clear()
#else
#define SET_FOREGROUND_PROCESSING()
#define CLEAR_FOREGROUND_PROCESSING()
#endif
#if defined(TRACE_UPDATE_MANAGER)
extern BitTrace Update_Manager;
#define SET_UPDATE_MANAGER() Update_Manager.Set()
#define CLEAR_UPDATE_MANAGER() Update_Manager.Clear()
#else
#define SET_UPDATE_MANAGER()
#define CLEAR_UPDATE_MANAGER()
#endif
#if defined(TRACE_RENDERER_MANAGER)
extern BitTrace Renderer_Manager;
#define SET_RENDERER_MANAGER() Renderer_Manager.Set()
#define CLEAR_RENDERER_MANAGER() Renderer_Manager.Clear()
#else
#define SET_RENDERER_MANAGER()
#define CLEAR_RENDERER_MANAGER()
#endif
class Mission;
class Registry;
class InterestManager;
class HostManager;
class UpdateManager;
class RendererManager;
class ControlsManager;
class Entity;
class EntityManager;
class Renderer;
class BackgroundTasks;
class ApplicationManager;
class CameraShip;
class SpoolFile;
class AudioRenderer;
class VideoRenderer;
class GaugeRenderer;
class IcomManager;
class ModeManager;
class Player;
class GeneralEventQueue;
class Entity__MakeMessage;
class ResourceFile;
//##########################################################################
//######################### Live Cam tracing #############################
//##########################################################################
//
// RP412CAMLOG=1 traces the camera-station bring-up: the registry choosing
// a director, the director creating its camera ship, and the launch
// handshake it waits on. The Live Cam path is selected purely by egg data
// (hostType=1 plus vehicle=camera), so when it does not come up there is
// nothing in the log to say how far it got - which is exactly the hole
// this fills. Off by default; it prints once per second while waiting.
//
Logical
RPCameraLog();
//##########################################################################
//######################### Application ##############################
//##########################################################################
class Application__StateQueryMessage;
class Application__CheckLoadMessage;
class Application__RunMissionMessage;
class Application__StopMissionMessage;
class Application__SuspendMissionMessage;
class Application__ResumeMissionMessage;
class Application__AbortMissionMessage;
enum EventPriorities
{
MinEventPriority = 0,
LowEventPriority,
DefaultEventPriority,
HighEventPriority,
MaxEventPriority
};
const EventPriorities ControlsEventPriority = HighEventPriority;
const EventPriorities CreationEventPriority = HighEventPriority;
const EventPriorities DestructionEventPriority = HighEventPriority;
const EventPriorities UpdateEventPriority = MaxEventPriority;
const EventPriorities HighInterestEventPriority = DefaultEventPriority;
const EventPriorities LowInterestEventPriority = LowEventPriority;
const EventPriorities EntityManagerEventPriority = DefaultEventPriority;
const EventPriorities EntityInvalidEventPriority = LowEventPriority;
enum ApplicationID
{
RPL4,
BTL4,
NDL4
};
#define EVENT_PRIORITIES_COUNT (5)
class Application:
public NetworkClient
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction, Destruction, and Testing
//
public:
Application(
ResourceFile *resource_file,
ApplicationID application_id,
ClassID class_ID=ApplicationClassID,
SharedData &shared_data=DefaultData
);
~Application();
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Execution control
//
public:
virtual void Initialize();
virtual void
LoadBackgroundTasks();
virtual Logical
ExecuteForeground(
Time start_of_frame,
Scalar frame_duration
);
virtual void
ExecuteBackgroundTask();
void
Stop();
virtual Logical
Shutdown(int remainingApps);
virtual void
Terminate();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Creation Callbacks
//
public:
void
CreateMission(NotationFile *egg_notation_file);
virtual Entity*
MakeAndLinkViewpointEntity(Entity__MakeMessage *message);
protected:
virtual Mission*
MakeMission(
NotationFile *notation_file,
ResourceFile *resources
);
virtual Entity*
MakeViewpointEntity(Entity__MakeMessage *message);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Current mission
//
public:
Mission*
GetCurrentMission();
Player*
GetMissionPlayer()
{return missionPlayer;}
SpoolFile*
GetSpoolFile()
{return spoolFile;}
protected:
Player
*missionPlayer;
SpoolFile*
spoolFile;
Time
lastCreationMessage;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Application State
//
public:
enum {
InitializingState = 0,
WaitingForEgg,
LoadingMission,
WaitingForLaunch,
LaunchingMission,
RunningMission,
EndingMission,
StoppingMission,
SuspendingMission,
ResumingMission,
AbortingMission,
CreatingMission,
ApplicationStateCount
};
Enumeration
GetApplicationState();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// System level event processing
//
public:
void
Post(
int priority,
Receiver *target,
Receiver::Message *message,
const Time &when=Time::Null
);
void
SendEvent(
int priority,
HostID host_ID,
NetworkManager::ClientID client_ID,
Receiver::Message *message,
Time when=Time::Null
);
void
BroadcastEvent(
int priority,
NetworkManager::ClientID client_ID,
Receiver::Message *message,
Time when=Time::Null
);
void
ExclusiveBroadcastEvent(
int priority,
NetworkManager::ClientID client_ID,
Receiver::Message *message,
Time when=Time::Null
);
#if defined(TRACE_EVENT_COUNT)
size_t
GetEventCount();
#endif
void
DumpEventQueue();
#if 0
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Creation and Interest Event Posting
//
public:
void
PostCreationEvent(
Receiver::Message *message,
const Time &when=Time::Null
);
void
PostInterestEvent(
Entity *entity,
Renderer *renderer
);
void
PostUpdateEvent(
Entity *entity,
Receiver::Message *message
);
void
PostDestructionEvent(
Entity *entity,
Receiver::Message *message
);
#endif
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// System level message processing
//
public:
void
SendMessage(
HostID host_ID,
NetworkManager::ClientID client_ID,
Receiver::Message *message
);
void
BroadcastMessage(
NetworkManager::ClientID client_ID,
Receiver::Message *message
);
void
ExclusiveBroadcastMessage(
NetworkManager::ClientID client_ID,
Receiver::Message *message
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Event level event processing
//
public:
Logical
ProcessOneEvent(int min_priority=0);
void
ProcessAllEvents(int min_priority=0);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Accessors
//
public:
Scalar
GetApplicationLoopFrameRate();
Scalar
GetSecondsRemainingInGame()
{return secondsRemainingInGame;}
//
// Seconds since the console's RunMission started the race, counting up.
// Every machine anchors this on the same message, so anything derived
// from it agrees across the mesh without being replicated - see the
// clockwork doors in DOOR.cpp. Reads 0 outside a running mission
// (gameStarted holds garbage until RunMission stamps it).
//
Scalar
GetMissionElapsed();
ApplicationID
GetApplicationID()
{return applicationID;}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Module accessors
//
public:
//
// Managers
//
NetworkManager*
GetNetworkManager();
EntityManager*
GetEntityManager();
Registry*
GetRegistry();
HostManager*
GetHostManager();
InterestManager*
GetInterestManager();
UpdateManager*
GetUpdateManager();
RendererManager*
GetRendererManager();
ControlsManager*
GetControlsManager();
IcomManager*
GetIntercomManager();
AudioRenderer*
GetAudioRenderer();
VideoRenderer*
GetVideoRenderer();
GaugeRenderer*
GetGaugeRenderer();
ModeManager*
GetModeManager();
//
// StreamableResourceFile
//
ResourceFile*
GetResourceFile();
void
SetResourceFile(ResourceFile *resources);
ApplicationManager*
GetApplicationManager();
//
// Viewpoint entity
//
Entity*
GetViewpointEntity();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Message Support
//
public:
enum {
StateQueryMessageID = NetworkClient::NextMessageID,
CheckLoadMessageID,
RunMissionMessageID,
StopMissionMessageID,
KeyCommandMessageID,
SuspendMissionMessageID,
ResumeMissionMessageID,
LoadMissionMessageID,
AbortMissionMessageID,
NextMessageID
};
typedef Application__StateQueryMessage StateQueryMessage;
typedef Application__CheckLoadMessage CheckLoadMessage;
typedef Application__RunMissionMessage RunMissionMessage;
typedef Application__StopMissionMessage StopMissionMessage;
typedef Application__SuspendMissionMessage SuspendMissionMessage;
typedef Application__ResumeMissionMessage ResumeMissionMessage;
typedef Application__AbortMissionMessage AbortMissionMessage;
static const HandlerEntry
MessageHandlerEntries[];
//static MessageHandlerSet MessageHandlers;
static MessageHandlerSet& GetMessageHandlers();
void
StateQueryMessageHandler(StateQueryMessage *message);
void
CheckLoadMessageHandler(CheckLoadMessage *message);
void
RunMissionMessageHandler(RunMissionMessage *message);
void
StopMissionMessageHandler(StopMissionMessage *message);
void
SuspendMissionMessageHandler(SuspendMissionMessage *message);
void
ResumeMissionMessageHandler(ResumeMissionMessage *message);
void
KeyCommandMessageHandler(ReceiverDataMessageOf<int> *message);
void
LoadMissionMessageHandler(Message *message);
void
AbortMissionMessageHandler(AbortMissionMessage *message);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation *GetClassDerivations();
static SharedData DefaultData;
static Logical DoSuppressGauges() { return suppressGauges; }
//
// A Live Cam station. It has no pod, so none of the five instrument
// MFDs have anything to put on them - the panes would just composite
// as black holes over the view. It DOES keep the map, but landscape:
// the pod's is portrait because that is how the glass was physically
// mounted in the cabinet, and a camera has no cabinet.
//
// Settable because the role is picked in the lobby rather than passed
// as -lc on the command line, so the front end has to hand it over.
// That happens before the renderers are built - the single-binary race
// loop makes a fresh application per race, after the menu.
//
static Logical IsCameraStation() { return cameraStation; }
static void SetCameraStation(Logical state) { cameraStation = state; }
//
// Whether to keep a spool of this race. Picked on the setup screen
// under the role, and set the same way and for the same reason: it
// has to be known before the network manager is built, which happens
// after the menu, and it must be set either way because the same
// process races again and a stale True would quietly record a session
// nobody asked to keep.
//
static Logical IsRecording() { return recordMission; }
static void SetRecording(Logical state) { recordMission = state; }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Modules
//
protected:
//
// Managers
//
NetworkManager
*networkManager;
EntityManager
*entityManager;
Registry
*registry;
HostManager
*hostManager;
InterestManager
*interestManager;
UpdateManager
*updateManager;
RendererManager
*rendererManager;
ControlsManager
*controlsManager;
IcomManager
*intercomManager;
AudioRenderer
*audioRenderer;
VideoRenderer
*videoRenderer;
GaugeRenderer
*gaugeRenderer;
ModeManager
*modeManager;
static Logical suppressGauges;
static Logical cameraStation;
static Logical recordMission;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Module Creation
//
protected:
//
// Managers
//
virtual NetworkManager*
MakeNetworkManager();
virtual Registry*
MakeRegistry();
virtual ControlsManager*
MakeControlsManager();
virtual IcomManager*
MakeIntercomManager();
virtual InterestManager*
MakeInterestManager();
virtual AudioRenderer*
MakeAudioRenderer();
virtual VideoRenderer*
MakeVideoRenderer();
virtual GaugeRenderer*
MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index);
virtual ModeManager*
MakeModeManager();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private data
//
protected:
ApplicationID
applicationID;
Scalar
secondsRemainingInGame;
Time
gameStarted;
GeneralEventQueue
*eventQueue;
BackgroundTasks
*backgroundTasks;
ResourceFile
*resourceFile;
Entity
*viewpointEntity;
Logical
executeFrames;
StateIndicator
applicationState;
Mission
*currentMission;
Logical
routePacketFinished;
};
extern Application *application;
extern int Exit_Code;
//~~~~~~~~~~~~~~~~~~~~ Application__CheckLoadMessage ~~~~~~~~~~~~~~~~~~~~~~~
class Application__CheckLoadMessage:
public NetworkClient::Message
{
public:
Application__CheckLoadMessage();
};
//~~~~~~~~~~~~~~~~~~~~~~~~~ Application inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
inline Mission*
Application::GetCurrentMission()
{
Check(this);
return currentMission;
}
inline Enumeration
Application::GetApplicationState()
{
Check(this);
return applicationState.GetState();
}
inline void
Application::Post(
int priority,
Receiver *target,
Receiver::Message *message,
const Time &when
)
{
Check(this);
eventQueue->Post(priority,target,message,when);
}
inline void
Application::SendEvent(
int priority,
HostID host_ID,
NetworkClient::ClientID client_ID,
Receiver::Message *message,
Time when
)
{
Check(this);
eventQueue->SendEvent(priority,host_ID,client_ID,message,when);
}
inline void
Application::BroadcastEvent(
int priority,
NetworkClient::ClientID client_ID,
Receiver::Message *message,
Time when
)
{
Check(this);
eventQueue->BroadcastEvent(priority,client_ID,message,when);
}
inline void
Application::ExclusiveBroadcastEvent(
int priority,
NetworkClient::ClientID client_ID,
Receiver::Message *message,
Time when
)
{
Check(this);
eventQueue->ExclusiveBroadcastEvent(priority,client_ID,message,when);
}
#if defined(GET_EVENT_COUNT)
inline size_t
Application::GetEventCount()
{
return eventQueue->GetEventCount();
}
#endif
inline void
Application::DumpEventQueue()
{
eventQueue->DumpEventQueue();
}
inline void
Application::SendMessage(
HostID host_ID,
NetworkClient::ClientID client_ID,
Receiver::Message *message
)
{
Check(this);
networkManager->Send(message,client_ID,host_ID);
}
inline void
Application::BroadcastMessage(
NetworkManager::ClientID client_ID,
Receiver::Message *message
)
{
Check(this);
networkManager->Broadcast(message,client_ID);
}
inline void
Application::ExclusiveBroadcastMessage(
NetworkManager::ClientID client_ID,
Receiver::Message *message
)
{
Check(this);
networkManager->ExclusiveBroadcast(message,client_ID);
}
inline Logical
Application::ProcessOneEvent(int min_priority)
{
Check(this);
return eventQueue->ProcessOneEvent(min_priority);
}
inline void
Application::ProcessAllEvents(int min_priority)
{
Check(this);
eventQueue->ProcessAllEvents(min_priority);
}
inline NetworkManager*
Application::GetNetworkManager()
{
Check(networkManager);
return networkManager;
}
inline EntityManager*
Application::GetEntityManager()
{
return entityManager;
}
inline Registry*
Application::GetRegistry()
{
return registry;
}
inline HostManager*
Application::GetHostManager()
{
return hostManager;
}
inline InterestManager*
Application::GetInterestManager()
{
return interestManager;
}
inline UpdateManager*
Application::GetUpdateManager()
{
return updateManager;
}
inline RendererManager*
Application::GetRendererManager()
{
return rendererManager;
}
inline ControlsManager*
Application::GetControlsManager()
{
return controlsManager;
}
inline IcomManager*
Application::GetIntercomManager()
{
return intercomManager;
}
inline ResourceFile*
Application::GetResourceFile()
{
return resourceFile;
}
inline void
Application::SetResourceFile(ResourceFile *resources)
{
resourceFile = resources;
}
inline Entity*
Application::GetViewpointEntity()
{
return viewpointEntity;
}
inline AudioRenderer*
Application::GetAudioRenderer()
{
return audioRenderer;
}
inline VideoRenderer*
Application::GetVideoRenderer()
{
return videoRenderer;
}
inline GaugeRenderer*
Application::GetGaugeRenderer()
{
return gaugeRenderer;
}
inline ModeManager*
Application::GetModeManager()
{
return modeManager;
}