From 9150f485626f451b8c778d42c2231e49d768c536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 1 Jan 2024 18:17:05 +0100 Subject: [PATCH] Refactor demo recorder usage To simplify the usage of the demo recorder, parameters are added to the `IDemoRecorder::Stop` function to control whether the demo file is removed or renamed when stopping the recording. Ensure demo files of the replay recorder are always removed (except on crashes). The temporary replay demos were previously not removed when being disconnected from a server. Fix auto demos being stopped and restarted when enabling replays in the settings menu. Now only the replay recorder is stopped/started when necessary. Fix replay recorder being restarted when setting `cl_replay` variable with console without changing the value. Remove unnecessary `CClient::m_ButtonRender` variable. Demo rendering is already stopped correctly when reaching the end of the demo without this additional variable. Remove unused `CDemoRecorder::m_pMapData` variable. --- src/engine/client.h | 2 +- src/engine/client/client.cpp | 143 ++++++++---------- src/engine/client/client.h | 4 +- src/engine/demo.h | 10 +- src/engine/server/server.cpp | 45 ++---- src/engine/shared/demo.cpp | 82 +++++++--- src/engine/shared/demo.h | 11 +- src/game/client/components/menus_ingame.cpp | 4 +- src/game/client/components/menus_settings.cpp | 11 +- 9 files changed, 161 insertions(+), 151 deletions(-) diff --git a/src/engine/client.h b/src/engine/client.h index d4c9f3fd4..41477c1f3 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -165,7 +165,7 @@ public: #endif virtual void DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder, bool Verbose = false) = 0; virtual void DemoRecorder_HandleAutoStart() = 0; - virtual void DemoRecorder_Stop(int Recorder, bool RemoveFile = false) = 0; + virtual void DemoRecorder_UpdateReplayRecorder() = 0; virtual class IDemoRecorder *DemoRecorder(int Recorder) = 0; virtual void AutoScreenshot_Start() = 0; virtual void AutoStatScreenshot_Start() = 0; diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 311e478d5..c759b6717 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -532,11 +532,13 @@ void CClient::DisconnectWithReason(const char *pReason) m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, gs_ClientNetworkPrintColor); // stop demo playback and recorder + // make sure to remove replay tmp demo m_DemoPlayer.Stop(); - for(int i = 0; i < RECORDER_MAX; i++) - DemoRecorder_Stop(i); + for(int Recorder = 0; Recorder < RECORDER_MAX; Recorder++) + { + DemoRecorder(Recorder)->Stop(Recorder == RECORDER_REPLAYS ? IDemoRecorder::EStopMode::REMOVE_FILE : IDemoRecorder::EStopMode::KEEP_FILE); + } - // m_aRconAuthed[0] = 0; mem_zero(m_aRconUsername, sizeof(m_aRconUsername)); mem_zero(m_aRconPassword, sizeof(m_aRconPassword)); @@ -581,14 +583,9 @@ void CClient::DisconnectWithReason(const char *pReason) void CClient::Disconnect() { - m_ButtonRender = false; if(m_State != IClient::STATE_OFFLINE) - DisconnectWithReason(0); - - // make sure to remove replay tmp demo - if(g_Config.m_ClReplays) { - DemoRecorder_Stop(RECORDER_REPLAYS, true); + DisconnectWithReason(nullptr); } } @@ -946,8 +943,10 @@ const char *CClient::LoadMap(const char *pName, const char *pFilename, SHA256_DI } // stop demo recording if we loaded a new map - for(int i = 0; i < RECORDER_MAX; i++) - DemoRecorder_Stop(i, i == RECORDER_REPLAYS); + for(int Recorder = 0; Recorder < RECORDER_MAX; Recorder++) + { + DemoRecorder(Recorder)->Stop(Recorder == RECORDER_REPLAYS ? IDemoRecorder::EStopMode::REMOVE_FILE : IDemoRecorder::EStopMode::KEEP_FILE); + } char aBuf[256]; str_format(aBuf, sizeof(aBuf), "loaded map '%s'", pFilename); @@ -2352,22 +2351,20 @@ void CClient::Update() { if(State() == IClient::STATE_DEMOPLAYBACK) { -#if defined(CONF_VIDEORECORDER) - if(m_DemoPlayer.IsPlaying() && IVideo::Current()) - { - IVideo::Current()->NextVideoFrame(); - IVideo::Current()->NextAudioFrameTimeline([this](short *pFinalOut, unsigned Frames) { - Sound()->Mix(pFinalOut, Frames); - }); - } - else if(m_ButtonRender) - Disconnect(); -#endif - - m_DemoPlayer.Update(); - if(m_DemoPlayer.IsPlaying()) { +#if defined(CONF_VIDEORECORDER) + if(IVideo::Current()) + { + IVideo::Current()->NextVideoFrame(); + IVideo::Current()->NextAudioFrameTimeline([this](short *pFinalOut, unsigned Frames) { + Sound()->Mix(pFinalOut, Frames); + }); + } +#endif + + m_DemoPlayer.Update(); + // update timers const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info(); m_aCurGameTick[g_Config.m_ClDummy] = pInfo->m_Info.m_CurrentTick; @@ -2377,7 +2374,8 @@ void CClient::Update() } else { - // disconnect on error + // Disconnect when demo playback stopped, either due to playback error + // or because the end of the demo was reached when rendering it. DisconnectWithReason(m_DemoPlayer.ErrorMessage()); if(m_DemoPlayer.ErrorMessage()[0] != '\0') { @@ -3472,26 +3470,32 @@ void CClient::SaveReplay(const int Length, const char *pFilename) } if(!DemoRecorder(RECORDER_REPLAYS)->IsRecording()) + { m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "replay", "ERROR: demorecorder isn't recording. Try to rejoin to fix that."); + } else if(DemoRecorder(RECORDER_REPLAYS)->Length() < 1) + { m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "replay", "ERROR: demorecorder isn't recording for at least 1 second."); + } else { // First we stop the recorder to slice correctly the demo after - DemoRecorder_Stop(RECORDER_REPLAYS); - - char aDate[64]; - str_timestamp(aDate, sizeof(aDate)); + DemoRecorder(RECORDER_REPLAYS)->Stop(IDemoRecorder::EStopMode::KEEP_FILE); char aFilename[IO_MAX_PATH_LENGTH]; - if(str_comp(pFilename, "") == 0) - str_format(aFilename, sizeof(aFilename), "demos/replays/%s_%s (replay).demo", m_aCurrentMap, aDate); + if(pFilename[0] == '\0') + { + char aTimestamp[20]; + str_timestamp(aTimestamp, sizeof(aTimestamp)); + str_format(aFilename, sizeof(aFilename), "demos/replays/%s_%s_(replay).demo", m_aCurrentMap, aTimestamp); + } else + { str_format(aFilename, sizeof(aFilename), "demos/replays/%s.demo", pFilename); - - char *pSrc = m_aDemoRecorder[RECORDER_REPLAYS].GetCurrentFilename(); + } // Slice the demo to get only the last cl_replay_length seconds + const char *pSrc = m_aDemoRecorder[RECORDER_REPLAYS].CurrentFilename(); const int EndTick = GameTick(g_Config.m_ClDummy); const int StartTick = EndTick - Length * GameTickSpeed(); @@ -3503,7 +3507,7 @@ void CClient::SaveReplay(const int Length, const char *pFilename) m_EditJobs.push_back(pDemoEditTask); // And we restart the recorder - DemoRecorder_StartReplayRecorder(); + DemoRecorder_UpdateReplayRecorder(); } } @@ -3586,7 +3590,6 @@ const char *CClient::DemoPlayer_Render(const char *pFilename, int StorageType, c const char *pError = DemoPlayer_Play(pFilename, StorageType); if(pError) return pError; - m_ButtonRender = true; this->CClient::StartVideo(NULL, this, pVideoName); m_DemoPlayer.Play(); @@ -3632,19 +3635,23 @@ void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int if(State() != IClient::STATE_ONLINE) { if(Verbose) + { m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online"); + } } else { char aFilename[IO_MAX_PATH_LENGTH]; if(WithTimestamp) { - char aDate[20]; - str_timestamp(aDate, sizeof(aDate)); - str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", pFilename, aDate); + char aTimestamp[20]; + str_timestamp(aTimestamp, sizeof(aTimestamp)); + str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", pFilename, aTimestamp); } else + { str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pFilename); + } m_aDemoRecorder[Recorder].Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Sha256(), m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File()); } @@ -3654,10 +3661,12 @@ void CClient::DemoRecorder_HandleAutoStart() { if(g_Config.m_ClAutoDemoRecord) { - DemoRecorder_Stop(RECORDER_AUTO); - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "auto/%s", m_aCurrentMap); - DemoRecorder_Start(aBuf, true, RECORDER_AUTO); + DemoRecorder(RECORDER_AUTO)->Stop(IDemoRecorder::EStopMode::KEEP_FILE); + + char aFilename[IO_MAX_PATH_LENGTH]; + str_format(aFilename, sizeof(aFilename), "auto/%s", m_aCurrentMap); + DemoRecorder_Start(aFilename, true, RECORDER_AUTO); + if(g_Config.m_ClAutoDemoMax) { // clean up auto recorded demos @@ -3665,34 +3674,22 @@ void CClient::DemoRecorder_HandleAutoStart() AutoDemos.Init(Storage(), "demos/auto", "" /* empty for wild card */, ".demo", g_Config.m_ClAutoDemoMax); } } - if(!DemoRecorder(RECORDER_REPLAYS)->IsRecording()) - { - DemoRecorder_StartReplayRecorder(); - } + + DemoRecorder_UpdateReplayRecorder(); } -void CClient::DemoRecorder_StartReplayRecorder() +void CClient::DemoRecorder_UpdateReplayRecorder() { - if(g_Config.m_ClReplays) + if(!g_Config.m_ClReplays && DemoRecorder(RECORDER_REPLAYS)->IsRecording()) { - DemoRecorder_Stop(RECORDER_REPLAYS); - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "replays/replay_tmp-%s", m_aCurrentMap); - DemoRecorder_Start(aBuf, true, RECORDER_REPLAYS); + DemoRecorder(RECORDER_REPLAYS)->Stop(IDemoRecorder::EStopMode::REMOVE_FILE); } -} -void CClient::DemoRecorder_Stop(int Recorder, bool RemoveFile) -{ - m_aDemoRecorder[Recorder].Stop(); - if(RemoveFile) + if(g_Config.m_ClReplays && !DemoRecorder(RECORDER_REPLAYS)->IsRecording()) { - const char *pFilename = m_aDemoRecorder[Recorder].GetCurrentFilename(); - if(pFilename[0] != '\0') - { - Storage()->RemoveFile(pFilename, IStorage::TYPE_SAVE); - m_aDemoRecorder[Recorder].ClearCurrentFilename(); - } + char aFilename[IO_MAX_PATH_LENGTH]; + str_format(aFilename, sizeof(aFilename), "replays/replay_tmp_%s", m_aCurrentMap); + DemoRecorder_Start(aFilename, true, RECORDER_REPLAYS); } } @@ -3725,7 +3722,7 @@ void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData) void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData) { CClient *pSelf = (CClient *)pUserData; - pSelf->DemoRecorder_Stop(RECORDER_MANUAL); + pSelf->DemoRecorder(RECORDER_MANUAL)->Stop(IDemoRecorder::EStopMode::KEEP_FILE); } void CClient::Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData) @@ -4019,17 +4016,7 @@ void CClient::ConchainReplays(IConsole::IResult *pResult, void *pUserData, ICons pfnCallback(pResult, pCallbackUserData); if(pResult->NumArguments()) { - int Status = pResult->GetInteger(0); - if(Status == 0) - { - // stop recording and remove the tmp demo file - pSelf->DemoRecorder_Stop(RECORDER_REPLAYS, true); - } - else - { - // start recording - pSelf->DemoRecorder_HandleAutoStart(); - } + pSelf->DemoRecorder_UpdateReplayRecorder(); } } @@ -4562,7 +4549,9 @@ void CClient::RaceRecord_Start(const char *pFilename) void CClient::RaceRecord_Stop() { if(m_aDemoRecorder[RECORDER_RACE].IsRecording()) - m_aDemoRecorder[RECORDER_RACE].Stop(); + { + m_aDemoRecorder[RECORDER_RACE].Stop(IDemoRecorder::EStopMode::KEEP_FILE); + } } bool CClient::RaceRecord_IsRecording() diff --git a/src/engine/client/client.h b/src/engine/client/client.h index e4f9c3a20..30d7e6eaf 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -119,7 +119,6 @@ class CClient : public IClient, public CDemoPlayer::IListener int m_UseTempRconCommands = 0; char m_aPassword[sizeof(g_Config.m_Password)] = ""; bool m_SendPassword = false; - bool m_ButtonRender = false; // version-checking char m_aVersionStr[10] = "0"; @@ -437,8 +436,7 @@ public: const char *DemoPlayer_Play(const char *pFilename, int StorageType) override; void DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder, bool Verbose = false) override; void DemoRecorder_HandleAutoStart() override; - void DemoRecorder_StartReplayRecorder(); - void DemoRecorder_Stop(int Recorder, bool RemoveFile = false) override; + void DemoRecorder_UpdateReplayRecorder() override; void DemoRecorder_AddDemoMarker(int Recorder); IDemoRecorder *DemoRecorder(int Recorder) override; diff --git a/src/engine/demo.h b/src/engine/demo.h index f42193e9b..37e9dceeb 100644 --- a/src/engine/demo.h +++ b/src/engine/demo.h @@ -101,11 +101,17 @@ class IDemoRecorder : public IInterface { MACRO_INTERFACE("demorecorder") public: + enum class EStopMode + { + KEEP_FILE, + REMOVE_FILE, + }; + virtual ~IDemoRecorder() {} virtual bool IsRecording() const = 0; - virtual int Stop() = 0; + virtual int Stop(IDemoRecorder::EStopMode Mode, const char *pTargetFilename = "") = 0; virtual int Length() const = 0; - virtual char *GetCurrentFilename() = 0; + virtual const char *CurrentFilename() const = 0; }; class IDemoEditor : public IInterface diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 8a3d3f1c0..19bed8ad5 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -3412,12 +3412,14 @@ void CServer::DemoRecorder_HandleAutoStart() { if(Config()->m_SvAutoDemoRecord) { - m_aDemoRecorder[RECORDER_AUTO].Stop(); + m_aDemoRecorder[RECORDER_AUTO].Stop(IDemoRecorder::EStopMode::KEEP_FILE); + + char aTimestamp[20]; + str_timestamp(aTimestamp, sizeof(aTimestamp)); char aFilename[IO_MAX_PATH_LENGTH]; - char aDate[20]; - str_timestamp(aDate, sizeof(aDate)); - str_format(aFilename, sizeof(aFilename), "demos/auto/server/%s_%s.demo", m_aCurrentMap, aDate); + str_format(aFilename, sizeof(aFilename), "demos/auto/server/%s_%s.demo", m_aCurrentMap, aTimestamp); m_aDemoRecorder[RECORDER_AUTO].Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_aCurrentMapSha256[MAP_TYPE_SIX], m_aCurrentMapCrc[MAP_TYPE_SIX], "server", m_aCurrentMapSize[MAP_TYPE_SIX], m_apCurrentMapData[MAP_TYPE_SIX]); + if(Config()->m_SvAutoDemoMax) { // clean up auto recorded demos @@ -3431,14 +3433,9 @@ void CServer::SaveDemo(int ClientID, float Time) { if(IsRecording(ClientID)) { - m_aDemoRecorder[ClientID].Stop(); - - // rename the demo - char aOldFilename[IO_MAX_PATH_LENGTH]; char aNewFilename[IO_MAX_PATH_LENGTH]; - str_format(aOldFilename, sizeof(aOldFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, ClientID); str_format(aNewFilename, sizeof(aNewFilename), "demos/%s_%s_%05.2f.demo", m_aCurrentMap, m_aClients[ClientID].m_aName, Time); - Storage()->RenameFile(aOldFilename, aNewFilename, IStorage::TYPE_SAVE); + m_aDemoRecorder[ClientID].Stop(IDemoRecorder::EStopMode::KEEP_FILE, aNewFilename); } } @@ -3456,11 +3453,7 @@ void CServer::StopRecord(int ClientID) { if(IsRecording(ClientID)) { - m_aDemoRecorder[ClientID].Stop(); - - char aFilename[IO_MAX_PATH_LENGTH]; - str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, ClientID); - Storage()->RemoveFile(aFilename, IStorage::TYPE_SAVE); + m_aDemoRecorder[ClientID].Stop(IDemoRecorder::EStopMode::REMOVE_FILE); } } @@ -3476,22 +3469,13 @@ void CServer::StopDemos() if(!m_aDemoRecorder[i].IsRecording()) continue; - m_aDemoRecorder[i].Stop(); - - // remove tmp demos - if(i < MAX_CLIENTS) - { - char aPath[256]; - str_format(aPath, sizeof(aPath), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, i); - Storage()->RemoveFile(aPath, IStorage::TYPE_SAVE); - } + m_aDemoRecorder[i].Stop(i < MAX_CLIENTS ? IDemoRecorder::EStopMode::REMOVE_FILE : IDemoRecorder::EStopMode::KEEP_FILE); } } void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) { CServer *pServer = (CServer *)pUser; - char aFilename[IO_MAX_PATH_LENGTH]; if(pServer->IsRecording(RECORDER_MANUAL)) { @@ -3499,20 +3483,23 @@ void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) return; } + char aFilename[IO_MAX_PATH_LENGTH]; if(pResult->NumArguments()) + { str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pResult->GetString(0)); + } else { - char aDate[20]; - str_timestamp(aDate, sizeof(aDate)); - str_format(aFilename, sizeof(aFilename), "demos/demo_%s.demo", aDate); + char aTimestamp[20]; + str_timestamp(aTimestamp, sizeof(aTimestamp)); + str_format(aFilename, sizeof(aFilename), "demos/demo_%s.demo", aTimestamp); } pServer->m_aDemoRecorder[RECORDER_MANUAL].Start(pServer->Storage(), pServer->Console(), aFilename, pServer->GameServer()->NetVersion(), pServer->m_aCurrentMap, pServer->m_aCurrentMapSha256[MAP_TYPE_SIX], pServer->m_aCurrentMapCrc[MAP_TYPE_SIX], "server", pServer->m_aCurrentMapSize[MAP_TYPE_SIX], pServer->m_apCurrentMapData[MAP_TYPE_SIX]); } void CServer::ConStopRecord(IConsole::IResult *pResult, void *pUser) { - ((CServer *)pUser)->m_aDemoRecorder[RECORDER_MANUAL].Stop(); + ((CServer *)pUser)->m_aDemoRecorder[RECORDER_MANUAL].Stop(IDemoRecorder::EStopMode::KEEP_FILE); } void CServer::ConMapReload(IConsole::IResult *pResult, void *pUser) diff --git a/src/engine/shared/demo.cpp b/src/engine/shared/demo.cpp index 2eefe2b82..9196ae0f7 100644 --- a/src/engine/shared/demo.cpp +++ b/src/engine/shared/demo.cpp @@ -62,18 +62,15 @@ int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, con { dbg_assert(m_File == 0, "Demo recorder already recording"); - m_pfnFilter = pfnFilter; - m_pUser = pUser; - - m_pMapData = pMapData; m_pConsole = pConsole; + m_pStorage = pStorage; IOHANDLE DemoFile = pStorage->OpenFile(pFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); if(!DemoFile) { if(m_pConsole) { - char aBuf[256]; + char aBuf[64 + IO_MAX_PATH_LENGTH]; str_format(aBuf, sizeof(aBuf), "Unable to open '%s' for recording", pFilename); m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor); } @@ -186,6 +183,10 @@ int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, con str_format(aBuf, sizeof(aBuf), "Recording to '%s'", pFilename); m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor); } + + m_pfnFilter = pfnFilter; + m_pUser = pUser; + m_File = DemoFile; str_copy(m_aCurrentFilename, pFilename); @@ -337,33 +338,68 @@ void CDemoRecorder::RecordMessage(const void *pData, int Size) Write(CHUNKTYPE_MESSAGE, pData, Size); } -int CDemoRecorder::Stop() +int CDemoRecorder::Stop(IDemoRecorder::EStopMode Mode, const char *pTargetFilename) { if(!m_File) return -1; - // add the demo length to the header - io_seek(m_File, gs_LengthOffset, IOSEEK_START); - unsigned char aLength[sizeof(int32_t)]; - uint_to_bytes_be(aLength, Length()); - io_write(m_File, aLength, sizeof(aLength)); - - // add the timeline markers to the header - io_seek(m_File, gs_NumMarkersOffset, IOSEEK_START); - unsigned char aNumMarkers[sizeof(int32_t)]; - uint_to_bytes_be(aNumMarkers, m_NumTimelineMarkers); - io_write(m_File, aNumMarkers, sizeof(aNumMarkers)); - for(int i = 0; i < m_NumTimelineMarkers; i++) + if(Mode == IDemoRecorder::EStopMode::KEEP_FILE) { - unsigned char aMarker[sizeof(int32_t)]; - uint_to_bytes_be(aMarker, m_aTimelineMarkers[i]); - io_write(m_File, aMarker, sizeof(aMarker)); + // add the demo length to the header + io_seek(m_File, gs_LengthOffset, IOSEEK_START); + unsigned char aLength[sizeof(int32_t)]; + uint_to_bytes_be(aLength, Length()); + io_write(m_File, aLength, sizeof(aLength)); + + // add the timeline markers to the header + io_seek(m_File, gs_NumMarkersOffset, IOSEEK_START); + unsigned char aNumMarkers[sizeof(int32_t)]; + uint_to_bytes_be(aNumMarkers, m_NumTimelineMarkers); + io_write(m_File, aNumMarkers, sizeof(aNumMarkers)); + for(int i = 0; i < m_NumTimelineMarkers; i++) + { + unsigned char aMarker[sizeof(int32_t)]; + uint_to_bytes_be(aMarker, m_aTimelineMarkers[i]); + io_write(m_File, aMarker, sizeof(aMarker)); + } } io_close(m_File); m_File = 0; + + if(Mode == IDemoRecorder::EStopMode::REMOVE_FILE) + { + if(!m_pStorage->RemoveFile(m_aCurrentFilename, IStorage::TYPE_SAVE)) + { + if(m_pConsole) + { + char aBuf[64 + IO_MAX_PATH_LENGTH]; + str_format(aBuf, sizeof(aBuf), "Could not remove demo file '%s'.", m_aCurrentFilename); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor); + } + return -1; + } + } + else if(pTargetFilename[0] != '\0') + { + if(!m_pStorage->RenameFile(m_aCurrentFilename, pTargetFilename, IStorage::TYPE_SAVE)) + { + if(m_pConsole) + { + char aBuf[64 + 2 * IO_MAX_PATH_LENGTH]; + str_format(aBuf, sizeof(aBuf), "Could not move demo file '%s' to '%s'.", m_aCurrentFilename, pTargetFilename); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor); + } + return -1; + } + } + if(m_pConsole) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Stopped recording", gs_DemoPrintColor); + { + char aBuf[64 + IO_MAX_PATH_LENGTH]; + str_format(aBuf, sizeof(aBuf), "Stopped recording to '%s'", m_aCurrentFilename); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor); + } return 0; } @@ -1229,5 +1265,5 @@ void CDemoEditor::Slice(const char *pDemo, const char *pDst, int StartTick, int } DemoPlayer.Stop(); - DemoRecorder.Stop(); + DemoRecorder.Stop(IDemoRecorder::EStopMode::KEEP_FILE); } diff --git a/src/engine/shared/demo.h b/src/engine/shared/demo.h index 0544f4178..e0c62d848 100644 --- a/src/engine/shared/demo.h +++ b/src/engine/shared/demo.h @@ -18,17 +18,21 @@ typedef std::function TUpdateIntraTimesFunc; class CDemoRecorder : public IDemoRecorder { class IConsole *m_pConsole; + class IStorage *m_pStorage; + IOHANDLE m_File; char m_aCurrentFilename[IO_MAX_PATH_LENGTH]; int m_LastTickMarker; int m_LastKeyFrame; int m_FirstTick; + unsigned char m_aLastSnapshotData[CSnapshot::MAX_SIZE]; class CSnapshotDelta *m_pSnapshotDelta; + int m_NumTimelineMarkers; int m_aTimelineMarkers[MAX_TIMELINE_MARKERS]; + bool m_NoMapData; - unsigned char *m_pMapData; DEMOFUNC_FILTER m_pfnFilter; void *m_pUser; @@ -42,7 +46,7 @@ public: ~CDemoRecorder() override; int Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetversion, const char *pMap, const SHA256_DIGEST &Sha256, unsigned MapCrc, const char *pType, unsigned MapSize, unsigned char *pMapData, IOHANDLE MapFile = nullptr, DEMOFUNC_FILTER pfnFilter = nullptr, void *pUser = nullptr); - int Stop() override; + int Stop(IDemoRecorder::EStopMode Mode, const char *pTargetFilename = "") override; void AddDemoMarker(); void AddDemoMarker(int Tick); @@ -51,8 +55,7 @@ public: void RecordMessage(const void *pData, int Size); bool IsRecording() const override { return m_File != nullptr; } - char *GetCurrentFilename() override { return m_aCurrentFilename; } - void ClearCurrentFilename() { m_aCurrentFilename[0] = '\0'; } + const char *CurrentFilename() const override { return m_aCurrentFilename; } int Length() const override { return (m_LastTickMarker - m_FirstTick) / SERVER_TICK_SPEED; } }; diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 7b720213c..323668a65 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -105,13 +105,13 @@ void CMenus::RenderGame(CUIRect MainView) ButtonBar.VSplitRight(140.0f, &ButtonBar, &Button); static CButtonContainer s_DemoButton; - bool Recording = DemoRecorder(RECORDER_MANUAL)->IsRecording(); + const bool Recording = DemoRecorder(RECORDER_MANUAL)->IsRecording(); if(DoButton_Menu(&s_DemoButton, Recording ? Localize("Stop record") : Localize("Record demo"), 0, &Button)) { if(!Recording) Client()->DemoRecorder_Start(Client()->GetCurrentMap(), true, RECORDER_MANUAL); else - Client()->DemoRecorder_Stop(RECORDER_MANUAL); + Client()->DemoRecorder(RECORDER_MANUAL)->Stop(IDemoRecorder::EStopMode::KEEP_FILE); } static CButtonContainer s_SpectateButton; diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 92b0e6d57..ae153d4cc 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -3194,16 +3194,7 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) if(DoButton_CheckBox(&g_Config.m_ClReplays, Localize("Enable replays"), g_Config.m_ClReplays, &Button)) { g_Config.m_ClReplays ^= 1; - if(!g_Config.m_ClReplays) - { - // stop recording and remove the tmp demo file - Client()->DemoRecorder_Stop(RECORDER_REPLAYS, true); - } - else - { - // start recording - Client()->DemoRecorder_HandleAutoStart(); - } + Client()->DemoRecorder_UpdateReplayRecorder(); } Left.HSplitTop(20.0f, &Button, &Left);