From f85e5f5bcd4e261c7b572c96f5005f686f88a9db Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 3 Aug 2026 15:30:45 -0500 Subject: [PATCH] BT410 5.3.105: L4SPLR restored -- the mission-review spool layer, back-dated from the surviving RP411 descendant No 1995 L4SPLR.CPP survives, but its header does, and the direct descendant source survives in the RP411 tree. This is that 741-line file back-dated onto the 1995 engine APIs the header declares -- the BACKFILLS method -- and the ENGINE COUNT MOVES: 181 -> 182 TUs, link clean, zero unresolved. What the brick is: the whole mission-review spool layer -- SpoolingInterestManager every interest packet -> the spool L4SpoolingNetworkManager normal connect + the spool PREAMBLE (app ID, data version, per-host remote/local + HostID) L4PlaybackNetworkManager "connect" FROM the spool: verify the preamble, rebuild the host table with no wire, post Load L4SpoolingApplication the recorder app -- renderless makers, deferred stop (spool the ending exactly once), hand the finished spool to the application manager SpoolerTask the playback pump: egg packets while waiting, interest packets while loading, then everything doled out on recorded timestamps + GetTimeBias() Back-dating deltas, each named in the header block: the HINSTANCE/HWND ctor reverts to (ResourceFile*, ApplicationID); SOCKADDR_IN + two-arg ResolveAddress revert to the 1995 unsigned-long address; L4Host construction uses NullNetworkAddress/0 (L4NET.CPP's own idiom); PostQuitMessage becomes Exit_Code = AbortExitCodeID (the loop gate ExecuteForeground already tests); accessor-style statics revert to the header's static members; and the later-era MakeGaugeRenderer/Shutdown parameters drop. Two BC4.52 notes in source: the StopMissionMessage re-post carries an explicit (Receiver::Message *) cast for the compiler's nested-scope upcast quirk (NetworkClient derives Receiver, so the type IS right), and the foreground tick casts to LBE4ControlsManager::Execute exactly as L4APP does (the 1995 base ControlsManager has no Execute). KNOWN DEBT, deliberately left: L4PlaybackNetworkManager::DefaultData is referenced by its ctor but defined nowhere yet -- the linker does not pull the member because nothing references the playback manager until BTL4PB.CPP exists. That definition belongs with the playback app increment, which is next. Regression run clean (same y-track terrain walk). Co-Authored-By: Claude Fable 5 --- restoration/source410/MUNGA_L4/L4SPLR.CPP | 741 ++++++++++++++++++++++ 1 file changed, 741 insertions(+) create mode 100644 restoration/source410/MUNGA_L4/L4SPLR.CPP diff --git a/restoration/source410/MUNGA_L4/L4SPLR.CPP b/restoration/source410/MUNGA_L4/L4SPLR.CPP new file mode 100644 index 00000000..35a79d42 --- /dev/null +++ b/restoration/source410/MUNGA_L4/L4SPLR.CPP @@ -0,0 +1,741 @@ +//===========================================================================// +// File: l4splr.cpp // +// Project: MUNGA Brick: L4 Spooling / Playback // +// Contents: The mission-review spool layer -- the recorder-side network // +// managers, the spooling application, and the playback pump // +//---------------------------------------------------------------------------// +// Date Who Modification // +// -------- --- ---------------------------------------------------------- // +// // +//---------------------------------------------------------------------------// +// Copyright (C) 1995, Virtual World Entertainment, Inc. // +// All Rights reserved worldwide // +// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // +//===========================================================================// +// +// RECONSTRUCTION: no 1995 L4SPLR.CPP survives, but its HEADER does +// (CODE/RP/MUNGA_L4/L4SPLR.HPP), and the direct descendant source survives +// in the RP411 tree (MUNGA_L4/L4SPLR.cpp) -- this file is that source +// BACK-DATED onto the 1995 engine APIs the header declares, the same method +// as the MUNGA backfills (BACKFILLS.NOTES.md). Deltas from the RP411 body: +// +// * the windowing-era ctor (HINSTANCE/HWND) reverts to the header's +// (ResourceFile *, ApplicationID); +// * SOCKADDR_IN + two-arg ResolveAddress revert to the 1995 +// unsigned-long address (L4NET.HPP:265) and FindHost(NetworkAddress); +// * L4Host construction takes NullNetworkAddress / socket 0, the same +// idiom as L4NET.CPP's own console-host build; +// * PostQuitMessage(AbortExitCodeID) becomes Exit_Code = AbortExitCodeID +// (the 1995 loop gate ExecuteForeground already tests); +// * accessor-style statics revert to the header's static members; +// * MakeGaugeRenderer()/Shutdown() lose their later-era parameters. +// + +#include +#pragma hdrstop + +#if !defined(L4SPLR_HPP) +# include +#endif + +#if !defined(L4NET_HPP) +# include +#endif + +#if !defined(MISSION_HPP) +# include +#endif + +#if !defined(APP_HPP) +# include +#endif + +#if !defined(L4CTRL_HPP) +# include +#endif + +//############################################################################# +//###################### SpoolingInterestManager ########################## +//############################################################################# + +SpoolingInterestManager::SpoolingInterestManager() +{ +} + +SpoolingInterestManager::~SpoolingInterestManager() +{ +} + +// +//############################################################################# +//############################################################################# +// +void + SpoolingInterestManager::LoadInterestArenas(Mission *) +{ + Check(this); +} + +void + SpoolingInterestManager::LoadMission(Mission *) +{ + Check(this); +} + +// +//############################################################################# +// Every network message reaching the interest manager is spooled. +//############################################################################# +// +void + SpoolingInterestManager::ReceiveNetworkPacket( + NetworkPacket *packet, + Receiver::Message * + ) +{ + packet->timeStamp = Now(); + + Check(l4_spooling_application); + SpoolFile + *spool = l4_spooling_application->GetSpoolFile(); + Check(spool); + spool->SpoolPacket(packet); +} + +void + SpoolingInterestManager::Shutdown() +{ + Check(this); +} + +//############################################################################# +//###################### L4SpoolingNetworkManager ######################### +//############################################################################# + +L4SpoolingNetworkManager::L4SpoolingNetworkManager() +{ +} + +L4SpoolingNetworkManager::~L4SpoolingNetworkManager() +{ +} + +// +//############################################################################# +// Start the connection normally, then write the spool PREAMBLE: the +// application ID, the resource major version, and per mission host a +// remote/local flag plus its host ID -- everything playback needs to +// reconstruct the host table without a live network. +//############################################################################# +// +void + L4SpoolingNetworkManager::StartConnecting(Mission *mission) +{ + L4NetworkManager::StartConnecting(mission); + + Check(l4_spooling_application); + SpoolFile + *spool = l4_spooling_application->GetSpoolFile(); + Check(spool); + + *(ApplicationID *)spool->GetPointer() = application->GetApplicationID(); + spool->AdvancePointer(sizeof(ApplicationID)); + + ResourceFile + *res_file = application->GetResourceFile(); + Check(res_file); + int + major_version = res_file->versionArray[1]; + *(int *)spool->GetPointer() = major_version; + spool->AdvancePointer(sizeof(major_version)); + + HostManager + *host_mgr = application->GetHostManager(); + Check(host_mgr); + + Mission::HostIterator + mission_host_iterator(mission); + MissionHostData + *mission_host_data; + + while ((mission_host_data = mission_host_iterator.ReadAndNext()) != NULL) + { + CString + host_name(mission_host_data->GetAddressString()); + unsigned long + net_address = ResolveAddress(host_name); + Host + *host = host_mgr->FindHost(net_address); + + *(Logical *)spool->GetPointer() = (host != host_mgr->GetLocalHost()); + spool->AdvancePointer(sizeof(Logical)); + + *(HostID *)spool->GetPointer() = host->GetHostID(); + spool->AdvancePointer(sizeof(HostID)); + } +} + +// +//############################################################################# +// Egg traffic is spooled on its way through. +//############################################################################# +// +void + L4SpoolingNetworkManager::ReceiveNetworkPacket( + NetworkPacket *packet, + Receiver::Message *message + ) +{ + if (message->messageID == ReceiveEggFileMessageID) + { + packet->timeStamp = Now(); + Check(l4_spooling_application); + SpoolFile + *spool = l4_spooling_application->GetSpoolFile(); + Check(spool); + spool->SpoolPacket(packet); + } + L4NetworkManager::ReceiveNetworkPacket(packet, message); +} + +//############################################################################# +//###################### L4PlaybackNetworkManager ######################### +//############################################################################# + +L4PlaybackNetworkManager::L4PlaybackNetworkManager(): + NetworkManager(L4PlaybackNetworkManager::DefaultData) +{ +} + +L4PlaybackNetworkManager::~L4PlaybackNetworkManager() +{ +} + +// +//############################################################################# +// "Connect" from the spool instead of the wire: read back the preamble the +// spooler wrote, verify it belongs to this application and data version, +// rebuild the host table from the recorded flags, and post LoadMission. +//############################################################################# +// +void + L4PlaybackNetworkManager::StartConnecting(Mission *mission) +{ + Mission::HostIterator + mission_host_iterator(mission); + MissionHostData + *mission_host_data; + + Check(application); + SpoolFile + *spool = application->GetSpoolFile(); + Check(spool); + + ApplicationID + id = *(ApplicationID *)spool->GetPointer(); + spool->AdvancePointer(sizeof(id)); + int + major_rev = *(int *)spool->GetPointer(); + spool->AdvancePointer(sizeof(major_rev)); + + if (id != application->GetApplicationID()) + { + DEBUG_STREAM + << "\n\nError - Not a spool file for this application!\n" << flush; + Exit_Code = AbortExitCodeID; + return; + } + + ResourceFile + *resource_file = application->GetResourceFile(); + Check(resource_file); + + if (major_rev != resource_file->versionArray[1]) + { + DEBUG_STREAM + << "\n\nError - Spool file major data version should be " + << (int)resource_file->versionArray[1] << ", not " << major_rev + << "!\n" << flush; + Exit_Code = AbortExitCodeID; + return; + } + + HostManager + *host_mgr = application->GetHostManager(); + Check(host_mgr); + + while ((mission_host_data = mission_host_iterator.ReadAndNext()) != NULL) + { + Check(mission_host_data); + CString + host_name(mission_host_data->GetAddressString()); + + Logical + remote = *(Logical *)spool->GetPointer(); + spool->AdvancePointer(sizeof(Logical)); + HostID + host_id = *(HostID *)spool->GetPointer(); + spool->AdvancePointer(sizeof(HostID)); + + Host + *my_host = + new L4Host( + host_id, + mission_host_data->GetHostType(), + NullNetworkAddress, + 0, + host_name + ); + Register_Object(my_host); + if (remote) + { + host_mgr->AdoptRemoteHost(my_host); + } + else + { + host_mgr->AdoptLocalHost(my_host); + } + } + + Check(application); + Application::Message + load_message( + Application::LoadMissionMessageID, + sizeof(Application::Message) + ); + application->Post(DefaultEventPriority, application, &load_message); +} + +// +//############################################################################# +//############################################################################# +// +Logical + L4PlaybackNetworkManager::Shutdown() +{ + Check(this); + NetworkManager::Shutdown(); + + Check(application); + HostManager + *host_mgr = application->GetHostManager(); + Check(host_mgr); + HostManager::RemoteHostIterator + remote_hosts(host_mgr); + Host + *my_host; + while ((my_host = remote_hosts.ReadAndNext()) != NULL) + { + Check(my_host); + host_mgr->OrphanRemoteHost(my_host); + Unregister_Object(my_host); + delete my_host; + } + return True; +} + +// +// Playback has no live wire: the buffers are always empty. +// +Logical + L4PlaybackNetworkManager::CheckBuffers(NetworkPacket *) +{ + return False; +} + +//############################################################################# +//###################### L4SpoolingApplication ########################## +//############################################################################# + +L4SpoolingApplication + *&l4_spooling_application = (L4SpoolingApplication *&)application; + +const Receiver::HandlerEntry + L4SpoolingApplication::MessageHandlerEntries[] = +{ + MESSAGE_ENTRY(L4SpoolingApplication, RunMission), + MESSAGE_ENTRY(L4SpoolingApplication, StopMission), + MESSAGE_ENTRY(L4SpoolingApplication, AbortMission), + MESSAGE_ENTRY(L4SpoolingApplication, LoadMission) +}; + +L4SpoolingApplication::MessageHandlerSet + L4SpoolingApplication::MessageHandlers( + ELEMENTS(L4SpoolingApplication::MessageHandlerEntries), + L4SpoolingApplication::MessageHandlerEntries, + L4Application::MessageHandlers + ); + +Derivation + L4SpoolingApplication::ClassDerivations( + L4Application::ClassDerivations, + "L4SpoolingApplication" + ); + +L4SpoolingApplication::SharedData + L4SpoolingApplication::DefaultData( + L4SpoolingApplication::ClassDerivations, + L4SpoolingApplication::MessageHandlers + ); + +// +//############################################################################# +//############################################################################# +// +L4SpoolingApplication::L4SpoolingApplication( + ResourceFile *resource_file, + ApplicationID application_ID +): + L4Application( + resource_file, + application_ID, + L4ApplicationClassID, + DefaultData + ) +{ + missionSpooled = False; +} + +L4SpoolingApplication::~L4SpoolingApplication() +{ +} + +Logical + L4SpoolingApplication::TestInstance() const +{ + return IsDerivedFrom(ClassDerivations); +} + +// +//############################################################################# +// Message handlers. +//############################################################################# +// +void + L4SpoolingApplication::LoadMissionMessageHandler(Message *) +{ + applicationState.SetState(WaitingForLaunch); +} + +void + L4SpoolingApplication::RunMissionMessageHandler(RunMissionMessage *) +{ + Check(this); + applicationState.SetState(RunningMission); +} + +// +// The stop is deferred until the mission has fully spooled, then the spool +// is handed to the application manager for storage. +// +void + L4SpoolingApplication::StopMissionMessageHandler(StopMissionMessage *message) +{ + if (!missionSpooled) + { + missionSpooled = True; + + Time + post_time = Now(); + post_time += 3.0f; + + // + // The StopMissionMessage IS a Receiver::Message (NetworkClient + // derives Receiver, so NetworkClient::Message resolves there); the + // explicit cast is for BC4.52's nested-scope upcast quirk only. + // + Post( + DefaultEventPriority, this, + (Receiver::Message *)message, post_time); + } + else + { + MissionReviewApplicationManager + *app_mgr = GetApplicationManager(); + Check(app_mgr); + SpoolFile + *spool = GetSpoolFile(); + Check(spool); + app_mgr->StoreSpoolFile(spool); + spoolFile = NULL; + + L4Application::StopMissionMessageHandler(message); + } +} + +void + L4SpoolingApplication::AbortMissionMessageHandler( + AbortMissionMessage *message + ) +{ + Check(this); + Check(spoolFile); + spoolFile->Rewind(); + + L4Application::AbortMissionMessageHandler(message); +} + +// +//############################################################################# +// Run/stop traffic is spooled on its way through -- the stop only once the +// mission has been marked spooled, so the recorded file ends exactly once. +//############################################################################# +// +void + L4SpoolingApplication::ReceiveNetworkPacket( + NetworkPacket *packet, + Receiver::Message *message + ) +{ + Check(this); + + SpoolFile + *spool; + switch (message->messageID) + { + case StopMissionMessageID: + if (missionSpooled) + { + packet->timeStamp = Now(); + spool = GetSpoolFile(); + Check(spool); + spool->SpoolPacket(packet); + break; + } + // fall through -- an un-spooled stop records like a run + + case RunMissionMessageID: + packet->timeStamp = Now(); + spool = GetSpoolFile(); + Check(spool); + spool->SpoolPacket(packet); + break; + } + L4Application::ReceiveNetworkPacket(packet, message); +} + +// +//############################################################################# +// The maker overrides: a spooler records; it does not render. +//############################################################################# +// +Registry * + L4SpoolingApplication::MakeRegistry() +{ + return NULL; +} + +InterestManager * + L4SpoolingApplication::MakeInterestManager() +{ + return new SpoolingInterestManager; +} + +NetworkManager * + L4SpoolingApplication::MakeNetworkManager() +{ + return new L4SpoolingNetworkManager; +} + +VideoRenderer * + L4SpoolingApplication::MakeVideoRenderer() +{ + return NULL; +} + +AudioRenderer * + L4SpoolingApplication::MakeAudioRenderer() +{ + return NULL; +} + +GaugeRenderer * + L4SpoolingApplication::MakeGaugeRenderer() +{ + return NULL; +} + +Mission * + L4SpoolingApplication::MakeMission( + NotationFile *notation_file, + ResourceFile *resources + ) +{ + Check(this); + Check(notation_file); + Check(resources); + + return new Mission(notation_file, resources); +} + +Entity * + L4SpoolingApplication::MakeViewpointEntity(Entity__MakeMessage *) +{ + return NULL; +} + +// +//############################################################################# +//############################################################################# +// +void + L4SpoolingApplication::Initialize() +{ + Check(this); + Application::Initialize(); + + L4NetworkManager + *net_mgr = GetNetworkManager(); + Check(net_mgr); + net_mgr->CreateConsoleHost(); + + MissionReviewApplicationManager + *app_mgr = GetApplicationManager(); + Check(app_mgr); + spoolFile = app_mgr->GetEmptySpoolFile(); +} + +void + L4SpoolingApplication::LoadBackgroundTasks() +{ +} + +// +//############################################################################# +//############################################################################# +// +Logical + L4SpoolingApplication::ExecuteForeground( + Time /* start_of_frame */, + Scalar /* frame_duration */ + ) +{ + SET_FOREGROUND_PROCESSING(); + Check(this); + + // + // The 1995 base ControlsManager has no Execute; the LBE4 subclass owns + // the per-frame poll (L4CTRL.HPP:225), same cast L4APP itself uses. + // + Check(controlsManager); + ((LBE4ControlsManager *)controlsManager)->Execute(); + + CLEAR_FOREGROUND_PROCESSING(); + return executeFrames && !Exit_Code; +} + +void + L4SpoolingApplication::ExecuteBackgroundTask() +{ + Check(this); + + Check(networkManager); + if (!networkManager->RoutePacket()) + { + ProcessOneEvent(DefaultEventPriority); + } +} + +Logical + L4SpoolingApplication::Shutdown() +{ + Check(this); + + L4Application::Shutdown(); + return !Exit_Code && !missionSpooled; +} + +//############################################################################# +//########################### SpoolerTask ############################### +//############################################################################# + +SpoolerTask::SpoolerTask() +{ +} + +SpoolerTask::~SpoolerTask() +{ +} + +void + SpoolerTask::DispatchPacket(NetworkPacket *) +{ + Fail("SpoolerTask::DispatchPacket - shouldn't be here!"); +} + +long + SpoolerTask::GetTimeBias() +{ + Fail("SpoolerTask::GetTimeBias - shouldn't be here!"); + return 0; +} + +// +//############################################################################# +// The playback pump: interpret the spool against the application state -- +// egg packets while waiting for the egg, interest packets while loading, +// then everything else doled out on the recorded timestamps biased by when +// the run began. +//############################################################################# +// +void + SpoolerTask::Execute() +{ + Check(this); + Check(application); + + SpoolFile + *spool = application->GetSpoolFile(); + if (!spool) + { + return; + } + + Check(spool); + NetworkPacket + *packet; + switch (application->GetApplicationState()) + { + case Application::WaitingForEgg: + packet = (NetworkPacket *)spool->GetPointer(); + while (packet->clientID == NetworkClient::NetworkManagerClientID) + { + spool->NextPacket(); + DispatchPacket(packet); + packet = (NetworkPacket *)spool->GetPointer(); + } + break; + + case Application::CreatingMission: + case Application::LoadingMission: + packet = (NetworkPacket *)spool->GetPointer(); + while ( + packet->clientID != NetworkClient::ApplicationClientID && + spool->GetBytesRemaining() > 0 + ) + { + spool->NextPacket(); + DispatchPacket(packet); + packet = (NetworkPacket *)spool->GetPointer(); + } + break; + + case Application::LaunchingMission: + case Application::RunningMission: + while (spool && spool->GetBytesRemaining() > 0) + { + packet = (NetworkPacket *)spool->GetPointer(); + Time + when = packet->timeStamp; + when.ticks += GetTimeBias(); + if (when > Now()) + { + break; + } + spool->NextPacket(); + DispatchPacket(packet); + spool = application->GetSpoolFile(); + } + break; + } +}