#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); // Where the egg for this race lives, so it can be kept beside the // recording. A spool records what MOVED and never the track it moved // through, so the two are one artifact: without its egg a spool cannot // be replayed, and frontend.egg is overwritten by the next race set up // on this machine. Saving them together is what stops a recording // going stale the moment somebody picks a different track. void SetEggPath(const char *path); // Write SPOOLS\.spl, the matching .egg beside it, and copy // the spool 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; char eggPath[260]; }; // 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; };