Files
RP412/MUNGA/SPOOLER.h
T
CydandClaude Opus 5 6f63770f22 A spool needs a header, and a world to play into
Two more blockers down, found by watching it rather than reasoning about
it - Cyd reported a black screen with a full pod cockpit over it, and both
halves of that turned out to be real and separate.

The black screen: playback had no mission. L4NetworkManager reads the -egg
file and posts a ReceiveEggFileMessage in single user mode, which is what
ends in CreateMission and builds the world - but that is the POD's network
manager. L4PlaybackNetworkManager descends from NetworkManager and
inherited none of it, so the playback application came up with a cockpit,
nothing behind it, and not a word in the log. A spool records what MOVED,
never the track it moved through, so the egg is not optional.

The cockpit: Application::SetCameraStation is the front end's answer to a
question asked on the setup screen, and playback never sees the setup
screen, so a Live Cam recording replayed as a pod - five instrument panes
over the view, map back in the middle. The egg knows what the station was;
it is the same egg the race ran on. Read it from the host type instead.

Then playback got far enough to reject the spool outright:

    Error - Not a spool file for this application!
    Error - Spool file major data version should be 3, not 0!

Correct of it. A spool opens with the application ID, the resource major
version, and one (remote, hostID) pair per egg host, and SpoolRecorder was
writing packets and nothing else - so playback read a zero where the
application ID belonged. The header is written now, in
L4NetworkManager::StartConnecting, because every field in it is
network-layer knowledge and that is the first moment all of it exists.

NOTE the recordings made before this cannot be played back. They have no
header, and there is nothing in the file to reconstruct one from - the
host table describes machines that were on the wire at the time. A race
recorded from here on will have one.

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

154 lines
4.2 KiB
C++

#pragma once
#include "appmgr.h"
//##########################################################################
//########################### SpoolFile ################################
//##########################################################################
class SpoolFile:
public MemoryStream
{
public:
SpoolFile(
void *stream_start,
size_t stream_size,
size_t initial_offset=0
);
SpoolFile(SpoolFile& spool);
~SpoolFile();
enum SpoolState {
Empty,
Spooling,
Stored,
Playing
};
SpoolState
spoolState;
void
SaveAs(const char *name);
void
Read(const char *name);
void
SpoolPacket(NetworkPacket *packet);
NetworkPacket*
NextPacket();
};
//##########################################################################
//########################## SpoolRecorder #############################
//##########################################################################
//
// Keeping a race from a station that is also PLAYING one.
//
// The review build already records by teeing: L4SpoolingNetworkManager
// spools each packet and then hands it to the ordinary receive path. What
// tied that to a review build was never the recording - it was where the
// buffer came from. MissionReviewApplicationManager is only a pool
// allocator, and SpoolFile takes whatever buffer it is handed, so a
// recorder that owns one buffer needs none of it.
//
// Two things a live recorder must do that the review one did not:
//
// It must not touch the packet. The spooler restamps each packet with
// local arrival time, which is right - playback paces off those stamps and
// packets from different senders carry different clock origins, so they
// have to be put on one clock. But doing it in place would overwrite the
// sender's timestamp that Simulation::ReadUpdateRecord feeds to
// RP412NETCLOCK and the dead reckoner, which is exactly the input behind
// the projection tick. So it stamps the COPY, in the spool, after writing.
//
// And it must not take the race down with it. SpoolFile::SpoolPacket
// answers a full buffer with PostQuitMessage, which for a review is a fair
// end to a replay and for a live host is killing the race being recorded.
// The recorder checks the room first and simply stops.
//
class SpoolRecorder
{
public:
SpoolRecorder();
~SpoolRecorder();
// Allocate and begin. Call before the mission loads: playback rebuilds
// the world from the LoadMission and RunMission packets, so a spool
// armed at the green light cannot be replayed.
Logical
Arm();
// Tee one received packet. Safe to call unarmed or after the buffer
// has filled - both do nothing.
void
Record(const NetworkPacket *packet);
// Write SPOOLS\<timestamp>.spl and copy it to last.spl.
void
Save();
void
Disarm();
Logical
IsArmed() const
{ return armed; }
//
// A spool opens with a header - the application ID, the resource
// major version, and one (remote, hostID) pair per host named in the
// egg - and playback refuses a spool without it. All of that is
// network-layer knowledge, so it is written by the network manager
// once the hosts exist, and these three are what let it.
//
SpoolFile*
GetSpool()
{ return spool; }
Logical
HeaderWritten() const
{ return headerWritten; }
void
MarkHeaderWritten()
{ headerWritten = True; }
protected:
char *buffer;
SpoolFile *spool;
size_t bufferSize;
Logical armed;
Logical full;
Logical headerWritten;
int packetsRecorded;
};
// The process-wide recorder, made on first use.
SpoolRecorder *
SpoolRecorder_Get();
//##########################################################################
//############## MissionReviewApplicationManager #####################
//##########################################################################
class MissionReviewApplicationManager:
public ApplicationManager
{
public:
MissionReviewApplicationManager(HINSTANCE hInstance, HWND hWnd, Scalar frame_rate, int spool_count, size_t spool_size);
~MissionReviewApplicationManager();
SpoolFile*
GetEmptySpoolFile();
SpoolFile*
GetStoredSpoolFile(CString spoolFileName);
void
StoreSpoolFile(SpoolFile *spool_file);
void
ReleaseSpoolFile(SpoolFile *spool_file);
protected:
int spoolCount;
SpoolFile **spoolFiles;
char **spoolBuffers;
size_t spoolSize;
};