//===========================================================================// // 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 ######################### //############################################################################# // // The 1995 header declares no statics of its own (the DefaultData reference // in the RP411 descendant was later-era drift); chain the base's. // L4PlaybackNetworkManager::L4PlaybackNetworkManager(): NetworkManager(NetworkManager::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; } }