/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "LegacyTimeDemoRecorder_precompiled.h" #include "TimeDemoRecorder.h" #include <CryFile.h> #include <CryTypeInfo.h> #include <IActorSystem.h> #include <IAgent.h> #include <IAnimatedCharacter.h> #include <ILevelSystem.h> #include <IGameFramework.h> #include <ITestSystem.h> #include <IMovieSystem.h> #include <IMovementController.h> #include <IStatoscope.h> #include <ITimeOfDay.h> #include <ITimeDemoRecorder.h> #include <AzFramework/IO/FileOperations.h> #include <HMDBus.h> ////////////////////////////////////////////////////////////////////////// // Brush Export structures. ////////////////////////////////////////////////////////////////////////// #define TIMEDEMO_FILE_SIGNATURE "CRY " #define TIMEDEMO_FILE_TYPE 150 #define TIMEDEMO_FILE_VERSION_1 1 #define TIMEDEMO_FILE_VERSION_2 2 #define TIMEDEMO_FILE_VERSION_3 4 // ? #define TIMEDEMO_FILE_VERSION_4 6 #define TIMEDEMO_FILE_VERSION_7 7 #define TIMEDEMO_FILE_VERSION TIMEDEMO_FILE_VERSION_7 #define TIMEDEMO_MAX_INPUT_EVENTS 16 #define TIMEDEMO_MAX_GAME_EVENTS 1 // For now... #define TIMEDEMO_MAX_DESCRIPTION_LENGTH 64 #define TIMEDEMO_RESULTS_DIR "@LOG@/TestResults" #define FIXED_TIME_STEP (30) // Assume runing at 30fps. enum ETimeDemoFileFlags { eTimeDemoCompressed = 0x0001, }; #pragma pack(push,1) struct STimeDemoHeader { char signature[4]; // File signature. int filetype; // File type. int version; // File version. int nDataOffset; // Offset where frame data starts. ////////////////////////////////////////////////////////////////////////// int numFrames; // Number of frames. int nFrameSize; // Size of the per frame data in bytes. float totalTime; char levelname[128]; // @see ETimeDemoFileFlags uint32 nDemoFlags; uint32 nCompressedDataSize; uint32 nUncompressedDataSze; char reserved[116]; ////////////////////////////////////////////////////////////////////////// void SwapEndianThis() { SwapEndian(filetype); SwapEndian(version); SwapEndian(nDataOffset); SwapEndian(numFrames); SwapEndian(nFrameSize); SwapEndian(totalTime); SwapEndian(nDemoFlags); SwapEndian(nCompressedDataSize); SwapEndian(nUncompressedDataSze); } }; ////////////////////////////////////////////////////////////////////////// struct STimeDemoHeader_4 : public STimeDemoHeader { uint16 fixedTimeStep; void SwapEndianThis() { STimeDemoHeader::SwapEndianThis(); SwapEndian(fixedTimeStep); } }; ////////////////////////////////////////////////////////////////////////// struct STimeDemoFrame_1 { Vec3 curPlayerPosition; Ang3 angles; float frametime; unsigned int nActionFlags[2]; float fLeaning; int nPolygonsPerFrame; char reserved[28]; }; ////////////////////////////////////////////////////////////////////////// struct STimeDemoFrameEvent_2 { #ifndef NEED_ENDIAN_SWAP struct { uint16 deviceType : 4; uint16 state : 4; uint16 modifiers : 4; uint16 reserved : 4; }; #else struct { uint16 state : 4; uint16 deviceType : 4; uint16 reserved : 4; uint16 modifiers : 4; }; #endif uint16 keyId; float value; void SwapEndianThis() { SwapEndian(keyId); // this points to the first uint32 in structure SwapEndian(value); } }; struct STimeDemoFrame_2 { Vec3 curPlayerPosition; Ang3 curCameraAngles; Quat curViewRotation; float frametime; unsigned int nActionFlags[2]; float fLeaning; int nPolygonsPerFrame; uint8 numInputEvents; STimeDemoFrameEvent_2 inputEvents[TIMEDEMO_MAX_INPUT_EVENTS]; char reserved[32]; ////////////////////////////////////////////////////////////////////////// void SwapEndianThis() { SwapEndian(curPlayerPosition); SwapEndian(curCameraAngles); SwapEndian(curViewRotation); SwapEndian(frametime); SwapEndian(nActionFlags[0]); SwapEndian(nActionFlags[1]); SwapEndian(fLeaning); SwapEndian(nPolygonsPerFrame); SwapEndian(numInputEvents); for (int i = 0; i < TIMEDEMO_MAX_INPUT_EVENTS; i++) { inputEvents[i].SwapEndianThis(); } } }; struct SRecordedGameEvent { uint32 gameEventType; char entityName[TIMEDEMO_MAX_DESCRIPTION_LENGTH]; char description[TIMEDEMO_MAX_DESCRIPTION_LENGTH]; char description2[TIMEDEMO_MAX_DESCRIPTION_LENGTH]; float value; int32 extra; void operator =(const STimeDemoGameEvent& event) { //id = event.id; gameEventType = event.gameEventType; value = event.value; extra = event.extra; cry_strcpy(entityName, event.entityName.c_str()); cry_strcpy(description, event.description.c_str()); cry_strcpy(description2, event.description2.c_str()); } void SwapEndianThis() { SwapEndian(extra); SwapEndian(value); } }; STimeDemoGameEvent::STimeDemoGameEvent(IEntity* pEntity, const GameplayEvent& event) { if (pEntity) { entityName = pEntity->GetName(); } gameEventType = event.event; value = event.value; description = event.description; description2 = (const char*)(event.extra); }; STimeDemoGameEvent::STimeDemoGameEvent(const SRecordedGameEvent& event) { entityName = event.entityName; gameEventType = event.gameEventType; value = event.value; extra = event.extra; description = event.description; description2 = event.description2; } struct STimeDemoFrame_3 { int nFrameDataSize; // Size of this frame in bytes. Vec3 curPlayerPosition; Ang3 curCameraAngles; Quat curViewRotation; float frametime; unsigned int nActionFlags[2]; float fLeaning; int nPolygonsPerFrame; int numInputEvents; STimeDemoFrameEvent_2 inputEvents[TIMEDEMO_MAX_INPUT_EVENTS]; char reserved[32]; ////////////////////////////////////////////////////////////////////////// void SwapEndianThis() { SwapEndian(curPlayerPosition); SwapEndian(curCameraAngles); SwapEndian(curViewRotation); SwapEndian(frametime); SwapEndian(nActionFlags[0]); SwapEndian(nActionFlags[1]); SwapEndian(fLeaning); SwapEndian(nPolygonsPerFrame); SwapEndian(numInputEvents); for (int i = 0; i < TIMEDEMO_MAX_INPUT_EVENTS; i++) { inputEvents[i].SwapEndianThis(); } } }; struct STimeDemoFrame_4 { int nFrameDataSize; // Size of this frame in bytes. Vec3 curPlayerPosition; Ang3 curCameraAngles; Quat curViewRotation; float frametime; unsigned int nActionFlags[2]; float fLeaning; int nPolygonsPerFrame; int numInputEvents; STimeDemoFrameEvent_2 inputEvents[TIMEDEMO_MAX_INPUT_EVENTS]; int numGameEvents; SRecordedGameEvent gameEvents[TIMEDEMO_MAX_GAME_EVENTS]; uint32 bFollow; // if true, data from the next timedemo frame will be collected in this frame char reserved[32]; //char data[]; // Special frame data. ////////////////////////////////////////////////////////////////////////// void SwapEndianThis() { SwapEndian(curPlayerPosition); SwapEndian(curCameraAngles); SwapEndian(curViewRotation); SwapEndian(frametime); SwapEndian(nActionFlags[0]); SwapEndian(nActionFlags[1]); SwapEndian(fLeaning); SwapEndian(nPolygonsPerFrame); SwapEndian(numInputEvents); SwapEndian(numGameEvents); for (int i = 0; i < TIMEDEMO_MAX_INPUT_EVENTS; i++) { inputEvents[i].SwapEndianThis(); } for (int i = 0; i < TIMEDEMO_MAX_GAME_EVENTS; i++) { gameEvents[i].SwapEndianThis(); } } }; struct STimeDemoFrame_7 { int nFrameDataSize; Vec3 curPlayerPosition; Quat curPlayerRotation; Quat curViewRotation; Vec3 curHmdPositionOffset; Quat curHmdViewRotation; float frametime; unsigned int nActionFlags[2]; float fLeaning; int nPolygonsPerFrame; int numInputEvents; STimeDemoFrameEvent_2 inputEvents[TIMEDEMO_MAX_INPUT_EVENTS]; int numGameEvents; SRecordedGameEvent gameEvents[TIMEDEMO_MAX_GAME_EVENTS]; uint32 bFollow; char reserved[32]; ////////////////////////////////////////////////////////////////////////// void SwapEndianThis() { SwapEndian(curPlayerPosition); SwapEndian(curPlayerRotation); SwapEndian(curViewRotation); SwapEndian(curHmdPositionOffset); SwapEndian(curHmdViewRotation); SwapEndian(frametime); SwapEndian(nActionFlags[0]); SwapEndian(nActionFlags[1]); SwapEndian(fLeaning); SwapEndian(nPolygonsPerFrame); SwapEndian(numInputEvents); SwapEndian(numGameEvents); for (int i = 0; i < TIMEDEMO_MAX_INPUT_EVENTS; ++i) { inputEvents[i].SwapEndianThis(); } for (int i = 0; i < TIMEDEMO_MAX_GAME_EVENTS; ++i) { gameEvents[i].SwapEndianThis(); } } }; #pragma pack(pop) ////////////////////////////////////////////////////////////////////////// CTimeDemoRecorder* CTimeDemoRecorder::s_pTimeDemoRecorder = 0; ICVar* CTimeDemoRecorder::s_timedemo_file = 0; ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::cmd_StartRecordingTimeDemo(IConsoleCmdArgs* pArgs) { if (s_pTimeDemoRecorder) { if (s_pTimeDemoRecorder->IsRecording()) { return; } if (pArgs->GetArgCount() > 1) { s_timedemo_file->Set(pArgs->GetArg(1)); } s_pTimeDemoRecorder->Record(true); } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::cmd_Play(IConsoleCmdArgs* pArgs) { if (s_pTimeDemoRecorder) { if (pArgs->GetArgCount() > 1) { s_timedemo_file->Set(pArgs->GetArg(1)); } s_pTimeDemoRecorder->StartDemoDelayed(2); } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::cmd_Stop(IConsoleCmdArgs* pArgs) { if (s_pTimeDemoRecorder) { s_pTimeDemoRecorder->Record(false); } if (s_pTimeDemoRecorder) { s_pTimeDemoRecorder->Play(false); } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::cmd_StartDemoChain(IConsoleCmdArgs* pArgs) { if (pArgs->GetArgCount() > 1) { const char* sLevelsFile = pArgs->GetArg(1); s_pTimeDemoRecorder->StartChainDemo(sLevelsFile, false); } else { s_pTimeDemoRecorder->StartChainDemo("test_chainlevels.txt", true); } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::cmd_StartDemoLevel(IConsoleCmdArgs* pArgs) { const int argCount = pArgs->GetArgCount(); if (argCount > 1) { const int levelCount = argCount - 1; std::vector<const char*> levels(levelCount); for (int i = 0; i < levelCount; ++i) { levels[i] = pArgs->GetArg(i + 1); } s_pTimeDemoRecorder->StartDemoLevel(&(levels.front()), levelCount); } else { CryLogAlways("Expect level name(s)"); } } static void OnChange_demo_num_orientations(ICVar* pCVar) { if (pCVar->GetIVal() < 1) { pCVar->Set(1); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// CTimeDemoRecorder::CTimeDemoRecorder() : m_listeners(1) { s_pTimeDemoRecorder = this; CRY_ASSERT(GetISystem()); m_numLoops = 0; m_maxLoops = 0; m_numOrientations = 1; m_bRecording = false; m_bPlaying = false; m_bDemoFinished = false; m_bPaused = false; m_bChainloadingDemo = false; m_lastFrameTime.SetMilliSeconds(0); m_lastAveFrameRate = 0; m_lastPlayedTotalTime = 0; m_pTimeDemoInfo = 0; ////////////////////////////////////////////////////////////////////////// m_nTotalPolysRecorded = 0; m_nTotalPolysPlayed = 0; m_lastPlayedTotalTime = 0; m_lastAveFrameRate = 0; m_sumFPS = 0; m_minFPS = 0; m_maxFPS = 0; m_currFPS = 0; ; m_minFPSCounter = 0; m_minFPS_Frame = 0; m_maxFPS_Frame = 0; m_nCurrPolys = 0; m_nMaxPolys = 0; m_nMinPolys = 0; m_nPolysPerSec = 0; m_nPolysCounter = 0; ; ////////////////////////////////////////////////////////////////////////// m_bAIEnabled = false; m_fixedTimeStep = 0; m_maxLoops = 2; m_demo_scroll_pause = 1; m_demo_max_frames = 100000; m_demo_savestats = 0; m_demo_profile = 0; m_demo_noinfo = 0; m_demo_save_every_frame = 0; m_demo_use_hmd_rotation = 0; m_nCurrentDemoLevel = 0; m_countDownPlay = 0; m_lastChainDemoTime = 0; m_demoEnded = false; m_fileVersion = TIMEDEMO_FILE_VERSION; // Register demo variables. s_timedemo_file = REGISTER_STRING("demo_file", "timedemo", 0, "Time Demo Filename"); REGISTER_CVAR2("demo_game_state", &m_demo_gameState, 0, 0, "enable/disable the game state recording"); REGISTER_CVAR2("demo_profile", &m_demo_profile, 1, 0, "Enable demo profiling"); REGISTER_CVAR2("demo_noinfo", &m_demo_noinfo, 0, 0, "Disable info display during demo playback"); REGISTER_COMMAND("record", &CTimeDemoRecorder::cmd_StartRecordingTimeDemo, 0, "Starts recording of a time demo.\n" "Usage: record demoname\n" "File 'demoname.tmd' will be created."); REGISTER_COMMAND("stoprecording", &CTimeDemoRecorder::cmd_Stop, 0, "Stops recording of a time demo.\n" "Usage: stoprecording\n" "File 'demoname.?' will be saved."); REGISTER_COMMAND("demo", &CTimeDemoRecorder::cmd_Play, 0, "Plays a time demo from file.\n" "Usage: demo demoname\n"); REGISTER_COMMAND("stopdemo", &CTimeDemoRecorder::cmd_Stop, 0, "Stop playing a time demo.\n"); REGISTER_COMMAND("demo_StartDemoChain", &CTimeDemoRecorder::cmd_StartDemoChain, 0, "Load's a file at 1st argument with the list of levels and play time demo on each\n"); REGISTER_COMMAND("demo_StartDemoLevel", &CTimeDemoRecorder::cmd_StartDemoLevel, 0, "Prepares and starts time demos for the specified set of level names\n"); REGISTER_CVAR2("demo_num_runs", &m_maxLoops, 1, 0, "Number of times to loop timedemo"); REGISTER_CVAR2("demo_scroll_pause", &m_demo_scroll_pause, 1, 0, "ScrollLock pauses demo play/record"); REGISTER_CVAR2("demo_quit", &m_demo_quit, 0, 0, "Quit game after demo runs finished"); REGISTER_CVAR2("demo_screenshot_frame", &m_demo_screenshot_frame, 0, 0, "Make screenshot on specified frame during demo playback, If Negative then do screen shoot every N frame"); REGISTER_CVAR2("demo_max_frames", &m_demo_max_frames, 100000, 0, "Max number of frames to save"); REGISTER_CVAR2("demo_savestats", &m_demo_savestats, 0, 0, "Save level stats at the end of the loop"); REGISTER_CVAR2("demo_ai", &m_demo_ai, 1, 0, "Enable/Disable AI during the demo"); // Note: Do not restart the level for Hunt, because the timedemo logic uses the regular map command and not mission which is doing other stuff. REGISTER_CVAR2("demo_restart_level", &m_demo_restart_level, 0, 0, "Restart level after each loop: 0 = Off; 1 = use quicksave on first playback; 2 = load level start"); REGISTER_CVAR2("demo_panoramic", &m_demo_panoramic, 0, 0, "Panoramic view when playing back demo"); REGISTER_CVAR2("demo_fixed_timestep", &m_demo_fixed_timestep, FIXED_TIME_STEP, 0, "number of updates per second"); REGISTER_CVAR2("demo_vtune", &m_demo_vtune, 0, 0, "Enables VTune profiling when running time demo"); REGISTER_CVAR2("demo_time_of_day", &m_demo_time_of_day, -1, 0, "Sets the time of day to override in game settings if not negative"); REGISTER_CVAR2("demo_save_every_frame", &m_demo_save_every_frame, 0, 0, "Save timedemo every frame during recording, in case game crashes timedemo will be reliable"); REGISTER_CVAR2("demo_use_hmd_rotation", &m_demo_use_hmd_rotation, 0, 0, "Uses alternative entity and view rotation for HMD Devices"); REGISTER_STRING("demo_finish_cmd", "", 0, "Console command to run when demo is finished"); REGISTER_CVAR2_CB("demo_num_orientations", &m_numOrientations, 1, 0, "Number of horizontal orientations to play the demo using\n" "e.g. 3 will play: looking ahead, 120deg left, 120deg right\n" "default/min: 1", OnChange_demo_num_orientations); } ////////////////////////////////////////////////////////////////////////// CTimeDemoRecorder::~CTimeDemoRecorder() { s_pTimeDemoRecorder = 0; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::Reset() { m_bRecording = false; m_bPlaying = false; m_bDemoFinished = false; m_bPaused = false; m_bChainloadingDemo = false; m_demoEnded = false; } ////////////////////////////////////////////////////////////////////////// const char* CTimeDemoRecorder::GetCurrentLevelPath() { static char buf[_MAX_PATH]; const char* sLevelName = gEnv->pGame->GetIGameFramework()->GetAbsLevelPath(buf, _MAX_PATH); return sLevelName; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::Record(bool bEnable) { if (bEnable == m_bRecording) { return; } if (bEnable == m_bRecording) { return; } if (gEnv->pMovieSystem) { gEnv->pMovieSystem->StopAllSequences(); } m_bRecording = bEnable; m_bPlaying = false; if (m_bRecording) { SaveAllEntitiesState(); uint64 onEventSubscriptions = 0; onEventSubscriptions |= ENTITY_EVENT_BIT(ENTITY_EVENT_XFORM); onEventSubscriptions |= ENTITY_EVENT_BIT(ENTITY_EVENT_HIDE); onEventSubscriptions |= ENTITY_EVENT_BIT(ENTITY_EVENT_UNHIDE); onEventSubscriptions |= ENTITY_EVENT_BIT(ENTITY_EVENT_ATTACH); onEventSubscriptions |= ENTITY_EVENT_BIT(ENTITY_EVENT_DETACH); onEventSubscriptions |= ENTITY_EVENT_BIT(ENTITY_EVENT_DETACH_THIS); onEventSubscriptions |= ENTITY_EVENT_BIT(ENTITY_EVENT_ENABLE_PHYSICS); onEventSubscriptions |= ENTITY_EVENT_BIT(ENTITY_EVENT_ENTER_SCRIPT_STATE); gEnv->pEntitySystem->AddSink(this, IEntitySystem::OnEvent, onEventSubscriptions); // Start recording. { m_records.clear(); m_records.reserve(1000); } m_currentFrameInputEvents.clear(); m_currentFrameEntityEvents.clear(); // Start listening input events. if (gEnv->pInput) { gEnv->pInput->AddEventListener(this); } StartSession(); m_recordStartTime = GetTime(); m_lastFrameTime = m_recordStartTime; } else { // Stop recording. m_recordedDemoTime = m_totalDemoTime; m_lastFrameTime = GetTime(); gEnv->pEntitySystem->RemoveSink(this); m_currentFrameInputEvents.clear(); m_currentFrameEntityEvents.clear(); // Stop listening tho the input events. if (gEnv->pInput) { gEnv->pInput->RemoveEventListener(this); } StopSession(); // Save after stopping. string filename = PathUtil::Make(GetCurrentLevelPath(), s_timedemo_file->GetString(), "tmd"); s_pTimeDemoRecorder->Save(filename.c_str()); } m_currentFrame = 0; m_totalDemoTime.SetMilliSeconds(0); SignalRecording(bEnable); } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::Play(bool bEnable) { if (bEnable == m_bPlaying) { return; } if (bEnable) { CRY_ASSERT(*GetCurrentLevelPath() != 0); // Try to load demo file. string filename = PathUtil::Make(GetCurrentLevelPath(), s_timedemo_file->GetString(), "tmd"); // Put it back later! Load(filename); if (m_records.empty()) { m_bDemoFinished = true; return; } if (gEnv->pStatoscope) { gEnv->pStatoscope->AddUserMarker("TimeDemo", "Start playing"); } } else { if (m_bPlaying) { // Turning off playback. if (m_demo_savestats != 0) { if (m_nCurrentDemoLevel >= m_demoLevels.size()) { // Save stats after last run only. gEnv->pConsole->ExecuteString("SaveLevelStats"); } } if (gEnv->pStatoscope) { gEnv->pStatoscope->AddUserMarker("TimeDemo", "Stop playing"); } } } m_bPlaying = bEnable; IActor* pClActor = gEnv->pGame->GetIGameFramework()->GetClientActor(); if (pClActor) { IAnimatedCharacter* pAnimChar = pClActor->GetAnimatedCharacter(); if (pAnimChar) { pAnimChar->SetNoMovementOverride(m_bPlaying); } } if (m_bPlaying) { string levelName = GetCurrentLevelName(); LogInfo("=============================================================="); LogInfo("TimeDemo Play Started ,Level=%s (Total Frames: %d, Recorded Time: %.2fs)", levelName.c_str(), (int)m_records.size(), m_recordedDemoTime.GetSeconds()); m_bDemoFinished = false; RestoreAllEntitiesState(); // Start demo playback. m_lastPlayedTotalTime = 0; StartSession(); } else { LogInfo("AutoTest Play Ended, (%d Runs Performed)", m_numLoops); LogInfo("=============================================================="); // End demo playback. m_lastPlayedTotalTime = m_totalDemoTime.GetSeconds(); StopSession(); } m_bRecording = false; m_currentFrame = 0; m_totalDemoTime.SetMilliSeconds(0); m_lastFpsTimeRecorded = GetTime(); m_numLoops = 0; m_fpsCounter = 0; m_lastFpsTimeRecorded = GetTime(); m_currFPS = 0; m_sumFPS = 0; m_minFPS = 10000; m_maxFPS = -10000; m_nMaxPolys = INT_MIN; m_nMinPolys = INT_MAX; SignalPlayback(bEnable); } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::Save(const char* filename) { // Not save empty file. if (m_records.empty()) { return; } m_file = filename; // Save Time demo file. // #if TIMEDEMO_FILE_VERSION== TIMEDEMO_FILE_VERSION_4 // STimeDemoHeader_4 hdr; // #else STimeDemoHeader hdr; //#endif memset(&hdr, 0, sizeof(hdr)); cry_strcpy(hdr.levelname, GetCurrentLevelPath()); hdr.nDataOffset = sizeof(hdr); //hdr.nFrameSize = sizeof(STimeDemoFrame_3); memcpy(hdr.signature, TIMEDEMO_FILE_SIGNATURE, 4); hdr.filetype = TIMEDEMO_FILE_TYPE; hdr.version = TIMEDEMO_FILE_VERSION; #if TIMEDEMO_FILE_VERSION == TIMEDEMO_FILE_VERSION_3 hdr.nFrameSize = sizeof(STimeDemoFrame_3); hdr.nDemoFlags = 0; hdr.numFrames = m_records.size(); hdr.totalTime = m_recordedDemoTime.GetSeconds(); hdr.nUncompressedDataSze = hdr.nFrameSize * hdr.numFrames; std::vector<STimeDemoFrame_3> file_records; file_records.resize(hdr.numFrames); for (int i = 0; i < hdr.numFrames; i++) { FrameRecord& rec = m_records[i]; STimeDemoFrame_3& frame = file_records[i]; ZeroStruct(frame); ZeroStruct(frame.inputEvents); frame.nFrameDataSize = sizeof(frame); frame.curViewRotation = rec.playerViewRotation; frame.curCameraAngles = rec.cameraAngles; frame.curPlayerPosition = rec.playerPosition; frame.frametime = rec.frameTime; *frame.nActionFlags = *rec.nActionFlags; frame.fLeaning = rec.fLeaning; frame.nPolygonsPerFrame = rec.nPolygons; frame.numInputEvents = 0; for (InputEventsList::const_iterator it = rec.inputEventsList.begin(); it != rec.inputEventsList.end() && frame.numInputEvents < TIMEDEMO_MAX_INPUT_EVENTS; ++it) { const SInputEvent& inputEvent = *it; frame.inputEvents[frame.numInputEvents].deviceType = inputEvent.deviceType; frame.inputEvents[frame.numInputEvents].modifiers = inputEvent.modifiers; frame.inputEvents[frame.numInputEvents].state = inputEvent.state; frame.inputEvents[frame.numInputEvents].keyId = inputEvent.keyId; frame.inputEvents[frame.numInputEvents].value = inputEvent.value; frame.numInputEvents++; } } #endif #if TIMEDEMO_FILE_VERSION == TIMEDEMO_FILE_VERSION_7 hdr.nFrameSize = sizeof(STimeDemoFrame_7); hdr.nDemoFlags = 0; hdr.numFrames = m_records.size(); float* pHdrFixedTimeStep = (float*)hdr.reserved; *pHdrFixedTimeStep = (float)m_demo_fixed_timestep; // check possible extra frames for carrying extra game events int n = hdr.numFrames; for (int i = 0; i < n; i++) { FrameRecord& rec = m_records[i]; int gesize = rec.gameEvents.size(); if (gesize > TIMEDEMO_MAX_GAME_EVENTS) { hdr.numFrames += (gesize - 1) / TIMEDEMO_MAX_GAME_EVENTS; } } hdr.totalTime = m_recordedDemoTime.GetSeconds(); hdr.nUncompressedDataSze = hdr.nFrameSize * hdr.numFrames; std::vector<STimeDemoFrame_7> file_records; file_records.resize(hdr.numFrames); for (int i = 0, fr = 0; fr < hdr.numFrames; ++i, ++fr) { FrameRecord& rec = m_records[i]; STimeDemoFrame_7& frame = file_records[fr]; frame.curPlayerPosition = rec.playerPosition; frame.curPlayerRotation = rec.playerRotation; frame.curViewRotation = rec.playerViewRotation; frame.curHmdPositionOffset = rec.hmdPositionOffset; frame.curHmdViewRotation = rec.hmdViewRotation; frame.frametime = rec.frameTime; *frame.nActionFlags = *rec.nActionFlags; frame.fLeaning = rec.fLeaning; frame.nPolygonsPerFrame = rec.nPolygons; //input events frame.numInputEvents = 0; frame.bFollow = 0; ZeroStruct(frame.inputEvents); ZeroStruct(frame.gameEvents); for (InputEventsList::const_iterator it = rec.inputEventsList.begin(); it != rec.inputEventsList.end() && frame.numInputEvents < TIMEDEMO_MAX_INPUT_EVENTS; ++it) { const SInputEvent& inputEvent = *it; frame.inputEvents[frame.numInputEvents].deviceType = inputEvent.deviceType; frame.inputEvents[frame.numInputEvents].modifiers = inputEvent.modifiers; frame.inputEvents[frame.numInputEvents].state = inputEvent.state; frame.inputEvents[frame.numInputEvents].keyId = inputEvent.keyId; frame.inputEvents[frame.numInputEvents].value = inputEvent.value; frame.numInputEvents++; } // game events // LEAVE THE GAME EVENTS FOR LAST (extended frames) frame.numGameEvents = rec.gameEvents.size(); int remainingEvents = frame.numGameEvents - TIMEDEMO_MAX_GAME_EVENTS; if (frame.numGameEvents > TIMEDEMO_MAX_GAME_EVENTS) { frame.numGameEvents = TIMEDEMO_MAX_GAME_EVENTS; } int fc = 0; for (int j = 0; j < frame.numGameEvents; j++) { frame.gameEvents[j] = rec.gameEvents[fc++]; } bool bExtended = false; STimeDemoFrame_7* pAddedFrame = &frame; if (remainingEvents > 0) { bExtended = true; } while (remainingEvents > 0) { // GameWarning("Timedemo: Exceeding number of game events in frame %i. Those game events will not be recorded.",i); pAddedFrame->bFollow = 1; fr++; pAddedFrame = &file_records[fr]; pAddedFrame->numGameEvents = min(remainingEvents, TIMEDEMO_MAX_GAME_EVENTS); remainingEvents -= TIMEDEMO_MAX_GAME_EVENTS; CRY_ASSERT(pAddedFrame->numGameEvents < TIMEDEMO_MAX_GAME_EVENTS); for (int j = 0; j < pAddedFrame->numGameEvents; j++) { pAddedFrame->gameEvents[j] = rec.gameEvents[fc++]; } } if (bExtended) { pAddedFrame->bFollow = 0; } } #endif ////////////////////////////////////////////////////////////////////////// // Save to file. ////////////////////////////////////////////////////////////////////////// CCryFile file; if (!file.Open(filename, "wb")) { GetISystem()->Warning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, 0, filename, "Cannot open time demo file %s", filename); return; } size_t nCompressedSize = hdr.nUncompressedDataSze * 2 + 1024; char* pCompressedData = new char[nCompressedSize]; if (GetISystem()->CompressDataBlock(&file_records[0], hdr.nUncompressedDataSze, pCompressedData, nCompressedSize)) { // Save compressed. hdr.nCompressedDataSize = nCompressedSize; hdr.nDemoFlags |= eTimeDemoCompressed; file.Write(&hdr, sizeof(hdr)); file.Write(pCompressedData, hdr.nCompressedDataSize); } else { // Save uncompressed. file.Write(&hdr, sizeof(hdr)); file.Write(&file_records[0], hdr.nUncompressedDataSze); } delete []pCompressedData; /* XmlNodeRef root = GetISystem()->CreateXmlNode( "TimeDemo" ); root->setAttr( "TotalTime",m_recordedDemoTime ); for (FrameRecords::iterator it = m_records.begin(); it != m_records.end(); ++it) { FrameRecord &rec = *it; XmlNodeRef xmlRecord = root->newChild( "Frame" ); xmlRecord->setAttr( "Pos",rec.playerPos ); xmlRecord->setAttr( "Ang",rec.playerRotation ); xmlRecord->setAttr( "Time",rec.frameTime ); } root->saveToFile( filename ); */ } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::AddFrameRecord(const FrameRecord& rec) { m_records.push_back(rec); } ////////////////////////////////////////////////////////////////////////// bool CTimeDemoRecorder::Load(const char* filename) { // ignore invalid file access fro time demo playback CDebugAllowFileAccess ignoreInvalidFileAccess; stl::free_container(m_records); m_recordedDemoTime.SetMilliSeconds(0); m_totalDemoTime.SetMilliSeconds(0); IInput* pIInput = GetISystem()->GetIInput(); // Cache IInput pointer. CCryFile file; if (!file.Open(filename, "rb", ICryPak::FOPEN_ONDISK)) { char str[256]; CryGetCurrentDirectory(sizeof(str), str); GetISystem()->Warning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, 0, filename, "Cannot open time demo file %s (%s)", filename, str); return false; } // Load Time demo file. // #if TIMEDEMO_FILE_VERSION== TIMEDEMO_FILE_VERSION_4 // STimeDemoHeader_4 hdr; // #else STimeDemoHeader hdr; //#endif file.ReadRaw(&hdr, sizeof(hdr)); if (hdr.signature[0] == 'C' && hdr.signature[1] == 'R' && hdr.signature[2] == 'Y' && (uint8)hdr.signature[3] == 0x96) { // This is old header. file.Seek(0, SEEK_SET); file.ReadRaw(((char*)&hdr) + 1, sizeof(hdr) - 1); // Old header was 1 byte shorter at the signature. } // Swap endian if needed. hdr.SwapEndianThis(); m_recordedDemoTime = hdr.totalTime; m_totalDemoTime = m_recordedDemoTime; { m_file = filename; m_records.reserve(hdr.numFrames); } m_fileVersion = hdr.version; const float fixedTimeStepMin = 0.0f; const float fixedTimeStepMax = 1000.f; switch (m_fileVersion) { case TIMEDEMO_FILE_VERSION_1: { for (int i = 0; i < hdr.numFrames && !file.IsEof(); i++) { STimeDemoFrame_1 frame; FrameRecord rec; file.ReadRaw(&frame, sizeof(frame)); Quat rot; rot.SetRotationXYZ(Ang3(DEG2RAD(frame.angles))); rot = rot * Quat::CreateRotationZ(gf_PI); // to fix some game to camera rotation issues. rec.playerViewRotation = rot; rec.cameraAngles = frame.angles; rec.playerPosition = frame.curPlayerPosition; rec.frameTime = frame.frametime; *rec.nActionFlags = *frame.nActionFlags; rec.fLeaning = frame.fLeaning; rec.nPolygons = frame.nPolygonsPerFrame; AddFrameRecord(rec); } } break; case TIMEDEMO_FILE_VERSION_2: { char* pFrameData = new char [hdr.nUncompressedDataSze]; if (hdr.nDemoFlags & eTimeDemoCompressed) { char* pCompressedData = new char [hdr.nCompressedDataSize]; // Read Compressed. file.ReadRaw(pCompressedData, hdr.nCompressedDataSize); // Uncompress data. size_t uncompressedSize = hdr.nUncompressedDataSze; GetISystem()->DecompressDataBlock(pCompressedData, hdr.nCompressedDataSize, pFrameData, uncompressedSize); CRY_ASSERT(uncompressedSize == hdr.nUncompressedDataSze); if (uncompressedSize != hdr.nUncompressedDataSze) { GameWarning("Corrupted compressed time demo file %s", filename); delete []pCompressedData; return false; } delete []pCompressedData; } else { // Read Uncompressed. if (file.ReadRaw(pFrameData, hdr.nUncompressedDataSze) != hdr.nUncompressedDataSze) { GameWarning("Corrupted time demo file %s", filename); return false; } } CRY_ASSERT(sizeof(STimeDemoFrame_2) * hdr.numFrames == hdr.nUncompressedDataSze); if (sizeof(STimeDemoFrame_2) * hdr.numFrames != hdr.nUncompressedDataSze) { GameWarning("Corrupted time demo file %s", filename); return false; } STimeDemoFrame_2* pFileFrame = (STimeDemoFrame_2*)pFrameData; for (int i = 0; i < hdr.numFrames; i++) { STimeDemoFrame_2& frame = *pFileFrame++; frame.SwapEndianThis(); // Swap endian if needed FrameRecord rec; rec.playerViewRotation = frame.curViewRotation; rec.cameraAngles = frame.curCameraAngles; rec.playerPosition = frame.curPlayerPosition; rec.frameTime = frame.frametime; *rec.nActionFlags = *frame.nActionFlags; rec.fLeaning = frame.fLeaning; rec.nPolygons = frame.nPolygonsPerFrame; if (frame.numInputEvents > 0) { for (int j = 0; j < frame.numInputEvents; j++) { SInputEvent inputEvent; inputEvent.deviceType = (EInputDeviceType)frame.inputEvents[j].deviceType; inputEvent.modifiers = frame.inputEvents[j].modifiers; inputEvent.state = (EInputState)frame.inputEvents[j].state; inputEvent.keyId = (EKeyId)frame.inputEvents[j].keyId; inputEvent.value = frame.inputEvents[j].value; SInputSymbol* pInputSymbol = pIInput ? pIInput->LookupSymbol(inputEvent.deviceType, 0, inputEvent.keyId) : 0; if (pInputSymbol) { inputEvent.keyName = pInputSymbol->name; } rec.inputEventsList.push_back(inputEvent); } } AddFrameRecord(rec); } delete []pFrameData; } break; case TIMEDEMO_FILE_VERSION_3: { char* pFrameData = new char [hdr.nUncompressedDataSze]; if (hdr.nDemoFlags & eTimeDemoCompressed) { char* pCompressedData = new char [hdr.nCompressedDataSize]; // Read Compressed. file.ReadRaw(pCompressedData, hdr.nCompressedDataSize); // Uncompress data. size_t uncompressedSize = hdr.nUncompressedDataSze; GetISystem()->DecompressDataBlock(pCompressedData, hdr.nCompressedDataSize, pFrameData, uncompressedSize); CRY_ASSERT(uncompressedSize == hdr.nUncompressedDataSze); if (uncompressedSize != hdr.nUncompressedDataSze) { GameWarning("Corrupted compressed time demo file %s", filename); delete []pCompressedData; return false; } delete []pCompressedData; } else { // Read Uncompressed. if (file.ReadRaw(pFrameData, hdr.nUncompressedDataSze) != hdr.nUncompressedDataSze) { GameWarning("Corrupted time demo file %s", filename); return false; } } CRY_ASSERT(sizeof(STimeDemoFrame_3) * hdr.numFrames == hdr.nUncompressedDataSze); if (sizeof(STimeDemoFrame_3) * hdr.numFrames != hdr.nUncompressedDataSze) { GameWarning("Corrupted time demo file %s", filename); return false; } STimeDemoFrame_3* pFileFrame = (STimeDemoFrame_3*)pFrameData; for (int i = 0; i < hdr.numFrames; i++) { STimeDemoFrame_3& frame = *pFileFrame++; frame.SwapEndianThis(); // Swap endian if needed FrameRecord rec; rec.playerViewRotation = frame.curViewRotation; rec.cameraAngles = frame.curCameraAngles; rec.playerPosition = frame.curPlayerPosition; rec.frameTime = frame.frametime; *rec.nActionFlags = *frame.nActionFlags; rec.fLeaning = frame.fLeaning; rec.nPolygons = frame.nPolygonsPerFrame; if (frame.numInputEvents > 0) { for (int j = 0; j < frame.numInputEvents; j++) { SInputEvent inputEvent; inputEvent.deviceType = (EInputDeviceType)frame.inputEvents[j].deviceType; inputEvent.modifiers = frame.inputEvents[j].modifiers; inputEvent.state = (EInputState)frame.inputEvents[j].state; inputEvent.keyId = (EKeyId)frame.inputEvents[j].keyId; inputEvent.value = frame.inputEvents[j].value; SInputSymbol* pInputSymbol = pIInput ? pIInput->LookupSymbol(inputEvent.deviceType, 0, inputEvent.keyId) : 0; if (pInputSymbol) { inputEvent.keyName = pInputSymbol->name; } rec.inputEventsList.push_back(inputEvent); } } AddFrameRecord(rec); } delete []pFrameData; } break; case TIMEDEMO_FILE_VERSION_4: { float recFixedTimeStep = float(*hdr.reserved); if (recFixedTimeStep > 0 && recFixedTimeStep < 1000) { m_demo_fixed_timestep = (uint16)recFixedTimeStep; } char* pFrameData = new char [hdr.nUncompressedDataSze]; if (hdr.nDemoFlags & eTimeDemoCompressed) { char* pCompressedData = new char [hdr.nCompressedDataSize]; // Read Compressed. file.ReadRaw(pCompressedData, hdr.nCompressedDataSize); // Uncompress data. size_t uncompressedSize = hdr.nUncompressedDataSze; GetISystem()->DecompressDataBlock(pCompressedData, hdr.nCompressedDataSize, pFrameData, uncompressedSize); CRY_ASSERT(uncompressedSize == hdr.nUncompressedDataSze); if (uncompressedSize != hdr.nUncompressedDataSze) { GameWarning("Corrupted compressed time demo file %s", filename); delete []pCompressedData; return false; } delete []pCompressedData; } else { // Read Uncompressed. if (file.ReadRaw(pFrameData, hdr.nUncompressedDataSze) != hdr.nUncompressedDataSze) { GameWarning("Corrupted time demo file %s", filename); return false; } } CRY_ASSERT(sizeof(STimeDemoFrame_4) * hdr.numFrames == hdr.nUncompressedDataSze); if (sizeof(STimeDemoFrame_4) * hdr.numFrames != hdr.nUncompressedDataSze) { GameWarning("Corrupted time demo file %s", filename); return false; } STimeDemoFrame_4* pFileFrame = (STimeDemoFrame_4*)pFrameData; for (int i = 0; i < hdr.numFrames; i++) { STimeDemoFrame_4& frame = *pFileFrame++; frame.SwapEndianThis(); // Swap endian if needed FrameRecord rec; rec.playerViewRotation = frame.curViewRotation; rec.cameraAngles = frame.curCameraAngles; rec.playerPosition = frame.curPlayerPosition; rec.frameTime = frame.frametime; *rec.nActionFlags = *frame.nActionFlags; rec.fLeaning = frame.fLeaning; rec.nPolygons = frame.nPolygonsPerFrame; if (frame.numInputEvents > 0) { for (int j = 0; j < frame.numInputEvents; j++) { SInputEvent inputEvent; inputEvent.deviceType = (EInputDeviceType)frame.inputEvents[j].deviceType; inputEvent.modifiers = frame.inputEvents[j].modifiers; inputEvent.state = (EInputState)frame.inputEvents[j].state; inputEvent.keyId = (EKeyId)frame.inputEvents[j].keyId; inputEvent.value = frame.inputEvents[j].value; SInputSymbol* pInputSymbol = pIInput ? pIInput->LookupSymbol(inputEvent.deviceType, 0, inputEvent.keyId) : 0; if (pInputSymbol) { inputEvent.keyName = pInputSymbol->name; } rec.inputEventsList.push_back(inputEvent); } } if (frame.numGameEvents > 0) { for (int j = 0; j < frame.numGameEvents; j++) { rec.gameEvents.push_back(frame.gameEvents[j]); } } STimeDemoFrame_4* pAddFrame = &frame; while (pAddFrame->bFollow && i < hdr.numFrames - 1) { ++i; pAddFrame = pFileFrame++; for (int j = 0; j < pAddFrame->numGameEvents; j++) { rec.gameEvents.push_back(pAddFrame->gameEvents[j]); } } AddFrameRecord(rec); } delete []pFrameData; } break; case TIMEDEMO_FILE_VERSION_7: { float recFixedTimeStep = float(*hdr.reserved); if (recFixedTimeStep > fixedTimeStepMin && recFixedTimeStep < fixedTimeStepMax) { m_demo_fixed_timestep = (uint16)recFixedTimeStep; } std::vector<char> frameData; frameData.resize(hdr.nUncompressedDataSze); if (hdr.nDemoFlags & eTimeDemoCompressed) { std::vector<char> compressedData; compressedData.resize(hdr.nCompressedDataSize); // Read Compressed. file.ReadRaw(compressedData.data(), compressedData.size()); // Uncompress data. size_t uncompressedSize = frameData.size(); GetISystem()->DecompressDataBlock(compressedData.data(), compressedData.size(), frameData.data(), uncompressedSize); CRY_ASSERT(uncompressedSize == frameData.size()); if (uncompressedSize != frameData.size()) { GameWarning("Corrupted compressed time demo file %s", filename); return false; } } else { // Read Uncompressed. if (file.ReadRaw(frameData.data(), frameData.size()) != frameData.size()) { GameWarning("Corrupted time demo file %s", filename); return false; } } CRY_ASSERT(sizeof(STimeDemoFrame_7) * hdr.numFrames == hdr.nUncompressedDataSze); if (sizeof(STimeDemoFrame_7) * hdr.numFrames != hdr.nUncompressedDataSze) { GameWarning("Corrupted time demo file %s", filename); return false; } STimeDemoFrame_7* pFileFrame = (STimeDemoFrame_7*)(frameData.data()); for (int i = 0; i < hdr.numFrames; ++i) { STimeDemoFrame_7& frame = *pFileFrame++; frame.SwapEndianThis(); // Swap endian if needed FrameRecord rec; rec.playerPosition = frame.curPlayerPosition; rec.playerRotation = frame.curPlayerRotation; rec.playerViewRotation = frame.curViewRotation; rec.hmdPositionOffset = frame.curHmdPositionOffset; rec.hmdViewRotation = frame.curHmdViewRotation; rec.frameTime = frame.frametime; *rec.nActionFlags = *frame.nActionFlags; rec.fLeaning = frame.fLeaning; rec.nPolygons = frame.nPolygonsPerFrame; if (frame.numInputEvents > 0) { rec.inputEventsList.reserve(frame.numInputEvents); for (int j = 0; j < frame.numInputEvents; ++j) { SInputEvent inputEvent; inputEvent.deviceType = (EInputDeviceType)frame.inputEvents[j].deviceType; inputEvent.modifiers = frame.inputEvents[j].modifiers; inputEvent.state = (EInputState)frame.inputEvents[j].state; inputEvent.keyId = (EKeyId)frame.inputEvents[j].keyId; inputEvent.value = frame.inputEvents[j].value; SInputSymbol* pInputSymbol = pIInput ? pIInput->LookupSymbol(inputEvent.deviceType, 0, inputEvent.keyId) : 0; if (pInputSymbol) { inputEvent.keyName = pInputSymbol->name; } rec.inputEventsList.push_back(inputEvent); } } if (frame.numGameEvents > 0) { rec.gameEvents.reserve(frame.numGameEvents); for (int j = 0; j < frame.numGameEvents; ++j) { rec.gameEvents.push_back(frame.gameEvents[j]); } } STimeDemoFrame_7* pAddFrame = &frame; while (pAddFrame->bFollow && i < hdr.numFrames - 1) { ++i; pAddFrame = pFileFrame++; rec.gameEvents.reserve(pAddFrame->numGameEvents); for (int j = 0; j < pAddFrame->numGameEvents; ++j) { rec.gameEvents.push_back(pAddFrame->gameEvents[j]); } } AddFrameRecord(rec); } } break; default: { GameWarning("Timedemo: Unknown file version"); } break; } return true; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::PreUpdate() { if (m_bPlaying && !m_bPaused && !gEnv->pConsole->GetStatus()) // Only when console closed. { bool bPlaying = PlayFrame(); if (!bPlaying) // No more frames { // Stop playing if max runs reached. LogEndOfLoop(); m_numLoops++; if (m_numLoops >= m_maxLoops * m_numOrientations) { Play(false); m_bDemoFinished = true; } else { ResetSessionLoop(); } } } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::PostUpdate() { if (gEnv->pSystem->IsQuitting()) { return; } if (!m_countDownPlay && !m_bPlaying && m_bDemoFinished) { if (!m_demoLevels.empty()) { StartNextChainedLevel(); return; } ICVar* pFinishCmd = gEnv->pConsole->GetCVar("demo_finish_cmd"); if (pFinishCmd) { const char* const szFinishCmd = pFinishCmd->GetString(); if (szFinishCmd && szFinishCmd[0] != '\0') { gEnv->pConsole->ExecuteString(szFinishCmd); } } if (m_demo_quit) { QuitGame(); } else if (!m_demoEnded) { EndDemo(); } } if (m_countDownPlay) { // to avoid playing demo before game is initialized (when running autotest) m_countDownPlay--; if (m_countDownPlay == 0) { Play(true); } } ProcessKeysInput(); if (!m_bPaused && m_bRecording && !gEnv->pConsole->GetStatus()) // Only when console closed. { // Reset random number generators seed. if (!RecordFrame()) { Record(false); } } if ((m_bPlaying || m_bRecording) && m_demo_noinfo <= 0) { RenderInfo(1); } } ////////////////////////////////////////////////////////////////////////// bool CTimeDemoRecorder::RecordFrame() { CTimeValue time = GetTime(); if (m_bPaused) { m_lastFrameTime = time; return true; } GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_RANDOM_SEED, 0, 0); FrameRecord rec; rec.frameTime = (time - m_lastFrameTime).GetSeconds(); IEntity* pPlayerEntity = NULL; IActor* pClientActor = gEnv->pGame->GetIGameFramework()->GetClientActor(); if (pClientActor) { pPlayerEntity = pClientActor->GetEntity(); } if (pPlayerEntity) { rec.playerPosition = pPlayerEntity->GetPos(); rec.playerRotation = pPlayerEntity->GetRotation(); rec.playerViewRotation = pClientActor == NULL ? pPlayerEntity->GetRotation() : pClientActor->GetViewRotation(); } // Legacy Ang3 cameraAngles = Ang3(GetISystem()->GetViewCamera().GetMatrix()); rec.cameraAngles = RAD2DEG(cameraAngles); { const AZ::VR::TrackingState* trackingState = nullptr; EBUS_EVENT_RESULT(trackingState, AZ::VR::HMDDeviceRequestBus, GetTrackingState); if (trackingState && trackingState->CheckStatusFlags(AZ::VR::HMDStatus_IsUsable)) { rec.hmdViewRotation = AZQuaternionToLYQuaternion(trackingState->pose.orientation); rec.hmdPositionOffset = AZVec3ToLYVec3(trackingState->pose.position); } } ////////////////////////////////////////////////////////////////////////// // Record input events. ////////////////////////////////////////////////////////////////////////// rec.inputEventsList = m_currentFrameInputEvents; rec.entityEvents = m_currentFrameEntityEvents; rec.gameEvents = m_currentFrameGameEvents; m_currentFrameInputEvents.clear(); m_currentFrameEntityEvents.clear(); m_currentFrameGameEvents.clear(); ////////////////////////////////////////////////////////////////////////// m_totalDemoTime += rec.frameTime; int nPolygons, nShadowVolPolys; gEnv->pRenderer->GetPolyCount(nPolygons, nShadowVolPolys); rec.nPolygons = nPolygons; m_nTotalPolysRecorded += nPolygons; AddFrameRecord(rec); FrameRecords::iterator it = m_records.begin(); FrameRecords::iterator it1 = it; ++it1; m_lastFrameTime = GetTime(); if (m_demo_save_every_frame) { // Save after stopping. string filename = PathUtil::Make(GetCurrentLevelPath(), s_timedemo_file->GetString(), "tmd"); s_pTimeDemoRecorder->Save(filename.c_str()); } m_currentFrame++; if (m_currentFrame >= m_demo_max_frames) { Record(false); return false; } return true; } ////////////////////////////////////////////////////////////////////////// bool CTimeDemoRecorder::PlayFrame() { if (m_records.empty()) // can't playback empty records. { return false; } CTimeValue time = GetTime(); CTimeValue deltaFrameTime = (time - m_lastFrameTime); float frameTime = deltaFrameTime.GetSeconds(); if (m_bPaused) { m_lastFrameTime = time; return true; } FrameRecord& rec = m_records[m_currentFrame]; m_nTotalPolysRecorded += rec.nPolygons; ////////////////////////////////////////////////////////////////////////// // Play input events. ////////////////////////////////////////////////////////////////////////// if (!rec.inputEventsList.empty()) { //string str; for (InputEventsList::const_iterator it = rec.inputEventsList.begin(), end = rec.inputEventsList.end(); it != end; ++it) { const SInputEvent& inputEvent = *it; if (gEnv->pInput) { gEnv->pInput->PostInputEvent(inputEvent); } } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // Play back entity events. ////////////////////////////////////////////////////////////////////////// { for (int i = 0; i < rec.entityEvents.size(); i++) { PlayBackEntityEvent(rec.entityEvents[i]); } } ////////////////////////////////////////////////////////////////////////// IEntity* pPlayer = NULL; IActor* pClActor = gEnv->pGame->GetIGameFramework()->GetClientActor(); if (pClActor) { pPlayer = pClActor->GetEntity(); } if (pPlayer) { if (pPlayer->GetParent() == 0) { // Only if player is not linked to anything. pPlayer->SetPos(rec.playerPosition, ENTITY_XFORM_TIMEDEMO); int orientationIndex = m_numLoops % m_numOrientations; float zAngle = ((float)orientationIndex / m_numOrientations) * gf_PI2; const Quat& rotation = m_demo_use_hmd_rotation == 0 ? rec.playerViewRotation : rec.playerRotation; Quat adjustedPlayerRotation = Quat::CreateRotationZ(zAngle) * rotation; CRY_ASSERT(adjustedPlayerRotation.IsValid()); pPlayer->SetRotation(adjustedPlayerRotation, ENTITY_XFORM_TIMEDEMO); } } m_totalDemoTime += deltaFrameTime; int nPolygons = ComputePolyCount(); m_nTotalPolysPlayed += nPolygons; ////////////////////////////////////////////////////////////////////////// // Calculate Frame Rates. ////////////////////////////////////////////////////////////////////////// // Skip some frames before calculating frame rates. float timeElapsed = (time - m_lastFpsTimeRecorded).GetSeconds(); if (timeElapsed > 1 && m_fpsCounter > 0) { // Skip some frames before recording frame rates. //if (m_currentFrame > 60) m_nPolysPerSec = (int)(float(m_nPolysCounter) / timeElapsed); m_nPolysCounter = 0; m_fpsCounter = 0; m_lastFpsTimeRecorded = time; } else { m_fpsCounter++; } if (m_currentFrame > m_minFPSCounter) { //m_currFPS = (float)m_fpsCounter / timeElapsed; m_currFPS = (float)(1.0 / deltaFrameTime.GetSeconds()); m_sumFPS += m_currFPS; if (m_currFPS > m_maxFPS) { m_maxFPS_Frame = m_currentFrame; m_maxFPS = m_currFPS; } if (m_currFPS < m_minFPS) { m_minFPS_Frame = m_currentFrame; m_minFPS = m_currFPS; } } ////////////////////////////////////////////////////////////////////////// // Fill Time Demo Info structure. ////////////////////////////////////////////////////////////////////////// if (m_pTimeDemoInfo) { m_pTimeDemoInfo->pFrames[m_currentFrame].fFrameRate = (float)(1.0 / deltaFrameTime.GetSeconds()); m_pTimeDemoInfo->pFrames[m_currentFrame].nPolysRendered = nPolygons; m_pTimeDemoInfo->pFrames[m_currentFrame].nDrawCalls = gEnv->pRenderer->GetCurrentNumberOfDrawCalls(); } ////////////////////////////////////////////////////////////////////////// m_lastFrameTime = GetTime(); ////////////////////////////////////////////////////////////////////////// //override game time of day ////////////////////////////////////////////////////////////////////////// if (0 <= m_demo_time_of_day) { //force update if time is significantly different from current time float fTime = gEnv->p3DEngine->GetTimeOfDay()->GetTime(); const float cfSmallDeltaTime = 0.1f; if ((fTime > m_demo_time_of_day + cfSmallDeltaTime) || (fTime < m_demo_time_of_day - cfSmallDeltaTime)) { gEnv->p3DEngine->GetTimeOfDay()->SetTime((float)m_demo_time_of_day, true); SetConsoleVar("e_TimeOfDaySpeed", 0); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// if ((m_numLoops == 0) && ((m_demo_screenshot_frame && m_currentFrame == m_demo_screenshot_frame) || (m_demo_screenshot_frame < 0) && (m_currentFrame % abs(m_demo_screenshot_frame) == 0))) { gEnv->pRenderer->ScreenShot(); } ////////////////////////////////////////////////////////////////////////// ReplayGameState(rec); m_currentFrame++; // Play looped. if (m_currentFrame >= GetNumberOfFrames() || m_currentFrame > m_demo_max_frames) { return false; } SignalPlayFrame(); return true; } ////////////////////////////////////////////////////////////////////////// CTimeValue CTimeDemoRecorder::GetTime() { // Must be asynchronius time, used for profiling. return gEnv->pTimer->GetAsyncTime(); } ////////////////////////////////////////////////////////////////////////// int CTimeDemoRecorder::GetNumFrames() const { return m_records.size(); } ////////////////////////////////////////////////////////////////////////// float CTimeDemoRecorder::GetAverageFrameRate() const { if (m_currentFrame) { float aveFrameTime = m_totalDemoTime.GetSeconds() / m_currentFrame; float aveFrameRate = 1.0f / aveFrameTime; return aveFrameRate; } return 0.0f; } ////////////////////////////////////////////////////////////////////////// float CTimeDemoRecorder::RenderInfo(float y) { float retY = 0; if (m_demo_noinfo != 0) { return retY; } const char* sInfo = m_bPaused ? " (Paused)" : ""; IRenderer* pRenderer = gEnv->pRenderer; if (m_bRecording) { // TO DO float fColor[4] = {0.7f, 0, 0, 1}; pRenderer->Draw2dLabel(1, y, 1.3f, fColor, false, "Recording AutoTest%s", sInfo); } else if (m_bPlaying) { float fColor[4] = {0, 0.7f, 0, 1}; pRenderer->Draw2dLabel(1, y, 1.3f, fColor, false, "Playing AutoTest%s - Loop %d of %d, Orientation %d of %d", sInfo, (m_numLoops / m_numOrientations) + 1, m_maxLoops, (m_numLoops % m_numOrientations) + 1, m_numOrientations); } y += 15; if (m_bRecording) { float fColor[4] = {1, 0, 0, 1}; pRenderer->Draw2dLabel(1, y + retY, 1.3f, fColor, false, "TimeDemo%s, Frames: %d", sInfo, m_currentFrame); retY += 15; } else if (m_bPlaying) { int numFrames = GetNumFrames(); float fColor[4] = {0, 1, 0, 1}; pRenderer->Draw2dLabel(1, y + retY, 1.3f, fColor, false, "TimeDemo%s, Frame %d of %d", sInfo, m_currentFrame, numFrames); retY += 15; float aveFrameRate = GetAverageFrameRate(); //float aveFrameRate = m_currentFrame > m_minFPSCounter ? m_sumFPS/(m_currentFrame - m_minFPSCounter) : 0; //float aveFrameRate = m_sumFPS/(m_currentFrame +1); float polyRatio = m_nTotalPolysPlayed ? (float)m_nTotalPolysRecorded / m_nTotalPolysPlayed : 0.0f; //int numFrames = GetNumFrames(); pRenderer->Draw2dLabel(1, y + retY, 1.3f, fColor, false, "TestProfiler%s, Frame %d", sInfo, m_currentFrame); retY += 15; pRenderer->Draw2dLabel(1, y + retY, 1.3f, fColor, false, " Last Played Length: %.2fs, FPS: %.2f", m_lastPlayedTotalTime, m_lastAveFrameRate); retY += 15; pRenderer->Draw2dLabel(1, y + retY, 1.3f, fColor, false, " Average FPS: %.2f, FPS: %.2f, Polys/Frame: %d", aveFrameRate, m_currFPS, m_nCurrPolys); retY += 15; pRenderer->Draw2dLabel(1, y + retY, 1.3f, fColor, false, " Polys Rec/Play Ratio: %.2f", polyRatio); retY += 15; } if (!m_bPaused && m_demo_gameState && m_fileVersion >= TIMEDEMO_FILE_VERSION_4) { IGameStateRecorder* pGameStateRecorder = gEnv->pGame->GetIGameFramework()->GetIGameplayRecorder()->GetIGameStateRecorder(); if (pGameStateRecorder) { retY += pGameStateRecorder->RenderInfo(y + retY, m_bRecording); } } return retY; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::SetConsoleVar(const char* sVarName, float value) { ICVar* pVar = gEnv->pConsole->GetCVar(sVarName); if (pVar) { pVar->Set(value); } } ////////////////////////////////////////////////////////////////////////// float CTimeDemoRecorder::GetConsoleVar(const char* sVarName) { ICVar* pVar = gEnv->pConsole->GetCVar(sVarName); if (pVar) { return pVar->GetFVal(); } return 0; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::StartSession() { Pause(false); bool bCurrentlyRecording = m_bRecording; m_bRecording = false; if (m_demo_restart_level == 1 && bCurrentlyRecording) { //QS does not work when playing/recording a timedemo, so pretend we're not... // Quick save at the beginning of the recording, so we restore initial state as good as possible. IGameFramework* pGameFramework = gEnv->pGame->GetIGameFramework(); if (pGameFramework) { pGameFramework->SaveGame(GetInitSaveFileName(), true, true, eSGR_QuickSave, true); } } ResetSessionLoop(); m_bRecording = bCurrentlyRecording; m_nTotalPolysPlayed = 0; m_nTotalPolysRecorded = 0; ////////////////////////////////////////////////////////////////////////// if (m_bRecording && m_bAIEnabled) { SaveAllEntitiesState(); } ////////////////////////////////////////////////////////////////////////// if (gEnv->pMovieSystem) { gEnv->pMovieSystem->StopAllCutScenes(); } if (m_demo_ai == 0) { SetConsoleVar("ai_SystemUpdate", 0); SetConsoleVar("ai_IgnorePlayer", 1); SetConsoleVar("ai_SoundPerception", 0); } else { SetConsoleVar("ai_UseCalculationStopperCounter", 1); // To make AI async time independent. } // No wait for key-press on level load. SetConsoleVar("hud_startPaused", 0); // Not cut-scenes SetConsoleVar("mov_NoCutscenes", 1); // cache console vars - changing them runtime has no effect m_bAIEnabled = m_demo_ai > 0; m_prevGodMode = (int)GetConsoleVar("g_godMode"); if (m_demo_gameState && m_fileVersion >= TIMEDEMO_FILE_VERSION_4) { gEnv->pGame->GetIGameFramework()->GetIGameplayRecorder()->EnableGameStateRecorder(true, this, m_bRecording); } if (m_bPlaying) { IActor* pClientActor = gEnv->pGame->GetIGameFramework()->GetClientActor(); if (pClientActor) { pClientActor->EnableTimeDemo(true); } } { if (!m_pTimeDemoInfo) { m_pTimeDemoInfo = new STimeDemoInfo(); m_pTimeDemoInfo->pFrames = 0; } int size = GetNumberOfFrames(); if (m_pTimeDemoInfo && m_pTimeDemoInfo->nFrameCount != size) { delete []m_pTimeDemoInfo->pFrames; STimeDemoInfo* pTD = m_pTimeDemoInfo; pTD->nFrameCount = size; pTD->pFrames = new STimeDemoFrameInfo[pTD->nFrameCount]; } } ////////////////////////////////////////////////////////////////////////// // Force player out of the vehicle on start. ////////////////////////////////////////////////////////////////////////// // Luc TO DO: remove this gEnv->pConsole->ExecuteString("v_exit_player"); m_lastAveFrameRate = 0; m_lastPlayedTotalTime = 0; m_totalDemoTime.SetMilliSeconds(0); // Register to frame profiler. // remember old profiling settings m_bEnabledProfiling = gEnv->pFrameProfileSystem->IsEnabled(); m_bVisibleProfiling = gEnv->pFrameProfileSystem->IsVisible(); if (m_demo_profile) { gEnv->pFrameProfileSystem->Enable(true, gEnv->pFrameProfileSystem->IsVisible()); } gEnv->pFrameProfileSystem->AddPeaksListener(this); // Profile m_oldPeakTolerance = GetConsoleVar("profile_peak"); SetConsoleVar("profile_peak", 50); m_fixedTimeStep = GetConsoleVar("t_FixedStep"); if (m_demo_fixed_timestep > 0) { SetConsoleVar("t_FixedStep", 1.0f / (float)m_demo_fixed_timestep); } if (m_demo_vtune) { GetISystem()->GetIProfilingSystem()->VTuneResume(); } m_lastFrameTime = GetTime(); } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::StopSession() { if (m_demo_vtune) { GetISystem()->GetIProfilingSystem()->VTunePause(); } // Set old time step. SetConsoleVar("t_FixedStep", m_fixedTimeStep); if (m_demo_ai == 0) { SetConsoleVar("ai_SystemUpdate", 1); SetConsoleVar("ai_IgnorePlayer", 0); SetConsoleVar("ai_SoundPerception", 1); } else { SetConsoleVar("ai_UseCalculationStopperCounter", 0); // To make AI async time independent. } SetConsoleVar("mov_NoCutscenes", 0); IActor* pClientActor = gEnv->pGame->GetIGameFramework()->GetClientActor(); if (pClientActor) { pClientActor->EnableTimeDemo(false); } gEnv->pGame->GetIGameFramework()->GetIGameplayRecorder()->EnableGameStateRecorder(false, this, false); // Profile. SetConsoleVar("profile_peak", m_oldPeakTolerance); gEnv->pFrameProfileSystem->RemovePeaksListener(this); if (m_demo_profile) { gEnv->pFrameProfileSystem->Enable(m_bEnabledProfiling, m_bVisibleProfiling); } m_lastPlayedTotalTime = m_totalDemoTime.GetSeconds(); } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::ResetSessionLoop() { if (m_demo_restart_level != 0 && m_bPlaying) { IGameFramework* pGameFramework = gEnv->pGame->GetIGameFramework(); switch (m_demo_restart_level) { case 1: // Quick load at the beginning of the playback, so we restore initial state as good as possible. if (pGameFramework) { pGameFramework->LoadGame(GetInitSaveFileName()); } break; case 2: default: //load save made at start of level if (pGameFramework) { pGameFramework->LoadGame(pGameFramework->GetStartLevelSaveGameName()); } break; } } // Reset random number generators seed. GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_RANDOM_SEED, 0, 0); m_totalDemoTime.SetMilliSeconds(0); m_currentFrame = 0; if (gEnv->pStatoscope) { CryStackStringT<char, 64> loopStr; loopStr.Format("Loop %d Orientation %d", m_numLoops / m_numOrientations, m_numLoops % m_numOrientations); gEnv->pStatoscope->AddUserMarker("TimeDemo", loopStr.c_str()); } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::EraseLogFile() { string filename = PathUtil::Make(TIMEDEMO_RESULTS_DIR, PathUtil::ReplaceExtension(CTimeDemoRecorder::s_timedemo_file->GetString(), "log")); gEnv->pCryPak->RemoveFile(filename); gEnv->pCryPak->RemoveFile("@LOG@/AutoTestFinished.log"); } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::LogInfo(const char* format, ...) { CDebugAllowFileAccess ignoreInvalidFileAccess; va_list ArgList; char szBuffer[1024]; va_start(ArgList, format); vsprintf_s(szBuffer, format, ArgList); va_start(ArgList, format); gEnv->pLog->LogV(IMiniLog::eMessage, format, ArgList); va_end(ArgList); gEnv->pLog->Log(szBuffer); string filename = PathUtil::Make(TIMEDEMO_RESULTS_DIR, PathUtil::ReplaceExtension(CTimeDemoRecorder::s_timedemo_file->GetString(), "log")); AZ::IO::HandleType fileHandle = fxopen(filename.c_str(), "at"); if (fileHandle != AZ::IO::InvalidHandle) { // Write the string to the file and close it AZ::IO::Print(fileHandle, "%s\n", szBuffer); gEnv->pFileIO->Close(fileHandle); } } ////////////////////////////////////////////////////////////////////////// bool CTimeDemoRecorder::OnInputEvent(const SInputEvent& event) { if ((event.deviceType == eIDT_Keyboard) && event.keyId == eKI_Tilde) { return false; } if (event.deviceType != eIDT_Unknown) { m_currentFrameInputEvents.push_back(event); } return false; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::LogEndOfLoop() { m_lastPlayedTotalTime = m_totalDemoTime.GetSeconds(); m_lastAveFrameRate = GetAverageFrameRate(); if (m_pTimeDemoInfo) { STimeDemoInfo* pTD = m_pTimeDemoInfo; pTD->lastPlayedTotalTime = m_lastPlayedTotalTime; pTD->lastAveFrameRate = m_lastAveFrameRate; pTD->minFPS = m_minFPS; pTD->maxFPS = m_maxFPS; pTD->minFPS_Frame = m_minFPS_Frame; pTD->maxFPS_Frame = m_maxFPS_Frame; pTD->nTotalPolysRecorded = m_nTotalPolysRecorded; pTD->nTotalPolysPlayed = m_nTotalPolysPlayed; GetISystem()->GetITestSystem()->SetTimeDemoInfo(m_pTimeDemoInfo); } int numFrames = GetNumberOfFrames();//m_records.size(); LogInfo(" Run Finished."); LogInfo(" Play Time: %.2fs, Average FPS: %.2f", m_lastPlayedTotalTime, m_lastAveFrameRate); LogInfo(" Min FPS: %.2f at frame %d, Max FPS: %.2f at frame %d", m_minFPS, m_minFPS_Frame, m_maxFPS, m_maxFPS_Frame); if (m_lastPlayedTotalTime * (float)numFrames > 0.f) { LogInfo(" Average Tri/Sec: %d, Tri/Frame: %d", (int)(m_nTotalPolysPlayed / m_lastPlayedTotalTime), m_nTotalPolysPlayed / numFrames); } if (m_nTotalPolysPlayed) { LogInfo(" Recorded/Played Tris ratio: %.2f", (float)m_nTotalPolysRecorded / m_nTotalPolysPlayed); } IMemoryManager::SProcessMemInfo meminfo; if (GetISystem()->GetIMemoryManager()->GetProcessMemInfo(meminfo)) { int MB = 1024 * 1024; LogInfo(" Memory Usage: WorkingSet=%" PRIu64 "Mb, PageFile=%" PRIu64 "Mb, PageFaults=%" PRIu64 "", meminfo.WorkingSetSize / MB, meminfo.PagefileUsage / MB, meminfo.PageFaultCount); } } void CTimeDemoRecorder::GetMemoryStatistics(ICrySizer* s) const { SIZER_SUBCOMPONENT_NAME(s, "TimeDemoRecorder"); s->Add(*this); s->AddObject(m_records); s->AddObject(m_currentFrameInputEvents); s->AddObject(m_currentFrameEntityEvents); s->AddObject(m_currentFrameGameEvents); } bool CTimeDemoRecorder::OnBeforeSpawn(SEntitySpawnParams& params) { return true; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::OnSpawn(IEntity* pEntity, SEntitySpawnParams& params) { } ////////////////////////////////////////////////////////////////////////// bool CTimeDemoRecorder::OnRemove(IEntity* pEntity) { return true; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::OnReused(IEntity* pEntity, SEntitySpawnParams& params) { } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::OnEvent(IEntity* pEntity, SEntityEvent& event) { if (m_bRecording) { // Record entity event for this frame. EntityGUID guid = pEntity->GetGuid(); if (!guid) { return; } // Record entity event for this frame. switch (event.event) { // Events to save. case ENTITY_EVENT_XFORM: case ENTITY_EVENT_HIDE: case ENTITY_EVENT_UNHIDE: case ENTITY_EVENT_ATTACH: case ENTITY_EVENT_DETACH: case ENTITY_EVENT_DETACH_THIS: case ENTITY_EVENT_ENABLE_PHYSICS: case ENTITY_EVENT_ENTER_SCRIPT_STATE: { EntityEventRecord rec; memset(&rec, 0, sizeof(rec)); rec.entityId = pEntity->GetId(); rec.guid = guid; rec.eventType = event.event; rec.nParam[0] = event.nParam[0]; rec.nParam[1] = event.nParam[1]; rec.nParam[2] = event.nParam[2]; rec.nParam[3] = event.nParam[3]; rec.pos = pEntity->GetPos(); rec.q = pEntity->GetRotation(); m_currentFrameEntityEvents.push_back(rec); } break; // Skip all other events. default: break; } } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::PlayBackEntityEvent(const EntityEventRecord& rec) { EntityId entityId = gEnv->pEntitySystem->FindEntityByGuid(rec.guid); IEntity* pEntity = gEnv->pEntitySystem->GetEntity(entityId); if (!pEntity) { return; } switch (rec.eventType) { // Events to save. case ENTITY_EVENT_XFORM: pEntity->SetPosRotScale(rec.pos, rec.q, pEntity->GetScale(), ENTITY_XFORM_TIMEDEMO); break; case ENTITY_EVENT_HIDE: pEntity->Hide(true); break; case ENTITY_EVENT_UNHIDE: pEntity->Hide(false); break; case ENTITY_EVENT_ATTACH: { IEntity* pChild = (IEntity*)gEnv->pEntitySystem->GetEntity((EntityId)rec.nParam[0]); // Get Child entity. if (pChild) { pEntity->AttachChild(pChild); } } break; case ENTITY_EVENT_DETACH: break; case ENTITY_EVENT_DETACH_THIS: pEntity->DetachThis(0, ENTITY_XFORM_TIMEDEMO); break; case ENTITY_EVENT_ENABLE_PHYSICS: if (rec.nParam[0] == 0) { pEntity->EnablePhysics(false); } else { pEntity->EnablePhysics(true); } break; case ENTITY_EVENT_ENTER_SCRIPT_STATE: { IComponentScriptPtr scriptComponent = pEntity->GetComponent<IComponentScript>(); if (scriptComponent) { scriptComponent->GotoStateId((int)rec.nParam[0]); } } break; } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::SaveAllEntitiesState() { m_firstFrameEntityState.clear(); // Record all objects positions. IEntity* pEntity; IEntityItPtr pEntityIter = gEnv->pEntitySystem->GetEntityIterator(); while (pEntity = pEntityIter->Next()) { EntityGUID guid = pEntity->GetGuid(); if (guid) { EntityEventRecord rec; memset(&rec, 0, sizeof(rec)); rec.entityId = pEntity->GetId(); rec.guid = guid; rec.eventType = ENTITY_EVENT_RESET; rec.pos = pEntity->GetPos(); rec.q = pEntity->GetRotation(); rec.flags |= (pEntity->IsHidden()) ? EntityEventRecord::HIDDEN : 0; m_firstFrameEntityState.push_back(rec); } } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::RestoreAllEntitiesState() { for (int i = 0; i < m_firstFrameEntityState.size(); i++) { EntityEventRecord& rec = m_firstFrameEntityState[i]; if (rec.eventType == ENTITY_EVENT_RESET) { EntityId entityId = gEnv->pEntitySystem->FindEntityByGuid(rec.guid); IEntity* pEntity = gEnv->pEntitySystem->GetEntity(entityId); if (!pEntity) { continue; } pEntity->Hide((rec.flags & EntityEventRecord::HIDDEN) != 0); pEntity->SetPosRotScale(rec.pos, rec.q, pEntity->GetScale(), ENTITY_XFORM_TIMEDEMO); } } } /////////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::ParseParams(XmlNodeRef baseNode) { } /////////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::SetVariable(const char* name, const char* szValue) { } /////////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::SetVariable(const char* name, float value) { } /////////////////////////////////////////////////////////////////////////////// int CTimeDemoRecorder::GetNumberOfFrames() { return m_records.size(); } /////////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::OnGameplayEvent(IEntity* pEntity, const GameplayEvent& event) { if (IsRecording()) { STimeDemoGameEvent ge(pEntity, event); m_currentFrameGameEvents.push_back(ge); } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::OnFrameProfilerPeak(CFrameProfiler* pProfiler, float fPeakTime) { if (m_bPlaying && !m_bPaused) { LogInfo(" -Peak at Frame %d, %.2fms : %s (count: %d)", m_currentFrame, fPeakTime, pProfiler->m_name, pProfiler->m_count); } } ////////////////////////////////////////////////////////////////////////// int CTimeDemoRecorder::ComputePolyCount() { int nPolygons, nShadowVolPolys; gEnv->pRenderer->GetPolyCount(nPolygons, nShadowVolPolys); m_nPolysCounter += nPolygons; m_nCurrPolys = nPolygons; if (nPolygons > m_nMaxPolys) { m_nMaxPolys = nPolygons; } if (nPolygons < m_nMinPolys) { m_nMinPolys = nPolygons; } return nPolygons; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::StartChainDemo(const char* levelsListFilename, bool bAutoLoadChainConfig) { if (bAutoLoadChainConfig) { gEnv->pConsole->ExecuteString("exec AutoTestChain.cfg"); } m_bChainloadingDemo = true; EraseLogFile(); m_demoLevels.clear(); if (levelsListFilename && *levelsListFilename) { // Open file with list of levels for autotest. // This is used only for development so doesn`t need to use CryPak! AZ::IO::HandleType fileHandle = fxopen(levelsListFilename, "rt"); if (fileHandle != AZ::IO::InvalidHandle) { while (!gEnv->pFileIO->Eof(fileHandle)) { char str[512]; if (AZ::IO::FGetS(str, sizeof(str), fileHandle)) { string level = str; level.Trim(); if (level.size()) { SChainDemoLevel lvl; lvl.level = level; lvl.time = 0; lvl.bSuccess = false; lvl.bRun = false; m_demoLevels.push_back(lvl); } } } gEnv->pFileIO->Close(fileHandle); } else { CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "Failed to find chainload file: %s", levelsListFilename); } } if (!m_demoLevels.empty()) { if (IsRecording()) { Record(false); } else if (IsPlaying()) { Play(false); } m_nCurrentDemoLevel = 0; StartNextChainedLevel(); } else { CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "No levels found for chainload in: %s", levelsListFilename); } } void CTimeDemoRecorder::StartDemoLevel(const char** levelNames, int levelCount) { m_bChainloadingDemo = true; EraseLogFile(); m_demoLevels.clear(); if (levelNames && levelCount > 0) { for (int i = 0; i < levelCount; ++i) { SChainDemoLevel lvl; lvl.level = levelNames[i]; lvl.time = 0; lvl.bSuccess = false; lvl.bRun = false; m_demoLevels.push_back(lvl); } } if (!m_demoLevels.empty()) { if (IsRecording()) { Record(false); } else if (IsPlaying()) { Play(false); } m_nCurrentDemoLevel = 0; StartNextChainedLevel(); } else { CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "demo_StartDemoLevel: No level(s) specified."); } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::StartNextChainedLevel() { if (!m_demoLevels.empty()) { float tcurr = GetISystem()->GetITimer()->GetAsyncTime().GetSeconds(); if (m_nCurrentDemoLevel - 1 >= 0 && m_nCurrentDemoLevel - 1 < m_demoLevels.size()) { m_demoLevels[m_nCurrentDemoLevel - 1].bSuccess = true; m_demoLevels[m_nCurrentDemoLevel - 1].bRun = true; m_demoLevels[m_nCurrentDemoLevel - 1].time = tcurr - m_lastChainDemoTime; } m_lastChainDemoTime = tcurr; SaveChainloadingJUnitResults(); // This support loading level/playing time demo, then loading next level, etc... if (m_nCurrentDemoLevel < m_demoLevels.size()) { CryStackStringT<char, 256> mapCmd("map "); mapCmd += m_demoLevels[m_nCurrentDemoLevel].level; gEnv->pConsole->ExecuteString(mapCmd); StartDemoDelayed(50); m_nCurrentDemoLevel++; return; } } ICVar* pFinishCmd = gEnv->pConsole->GetCVar("demo_finish_cmd"); if (pFinishCmd) { const char* const szFinishCmd = pFinishCmd->GetString(); if (szFinishCmd && szFinishCmd[0] != '\0') { gEnv->pConsole->ExecuteString(szFinishCmd); } } if (m_demo_quit) { // If No more chained levels. quit. QuitGame(); } else if (!m_demoEnded) { EndDemo(); } } ////////////////////////////////////////////////////////////////////////// string CTimeDemoRecorder::GetCurrentLevelName() { return gEnv->pGame->GetIGameFramework()->GetLevelName(); } ////////////////////////////////////////////////////////////////////////// string CTimeDemoRecorder::GetInitSaveFileName() { string level = GetCurrentLevelName(); string str = "autotest_init_"; str += level; str += LY_SAVEGAME_FILE_EXT; return str; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::SaveChainloadingJUnitResults() { XmlNodeRef testsuit = GetISystem()->CreateXmlNode("testsuite"); testsuit->setAttr("name", "ChainLoading"); for (int i = 0; i < (int)m_demoLevels.size(); i++) { int nSeconds = static_cast<int>(m_demoLevels[i].time); XmlNodeRef testcase = testsuit->newChild("testcase"); testcase->setAttr("name", m_demoLevels[i].level.c_str()); testcase->setAttr("time", nSeconds); if (!m_demoLevels[i].bSuccess) { XmlNodeRef failure = testcase->newChild("failure"); if (!m_demoLevels[i].bRun) { failure->setAttr("type", "Not Run"); } else { failure->setAttr("type", "Failed"); } } } gEnv->pCryPak->MakeDir(TIMEDEMO_RESULTS_DIR); testsuit->saveToFile(TIMEDEMO_RESULTS_DIR "/ChainLoadingJUnit.xml"); } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::EndDemo() { m_bDemoFinished = true; m_demoEnded = true; IGameFramework* pGameFramework = gEnv->pGame->GetIGameFramework(); if (!gEnv->IsEditor() && pGameFramework) { pGameFramework->EndGameContext(false); } CryLogAlways("Testing Successfully Finished, Quiting..."); } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::QuitGame() { CryLogAlways("Testing Successfully Finished, Quiting..."); m_bDemoFinished = true; GetISystem()->Quit(); } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::ProcessKeysInput() { if (!gEnv->IsDedicated() && gEnv->pSystem->IsDevMode()) { // Check if special development keys where pressed. // We're not referencing the VK_ constants directly here so the code will compile on non-Windows platforms. // If it wasn't legacy functinoality that will be deleted then we would deal with it in a better way. // See AzFramework\Input\Devices\Keyboard\InputDeviceKeyboardWindowsScanCodes.h for VK_ constant values. bool bAlt = ((CryGetAsyncKeyState(/*VK_LMENU*/0xA4) & (1 << 15)) != 0) || (CryGetAsyncKeyState(/*VK_RMENU*/0xA5) & (1 << 15)) != 0; bool bCtrl = (CryGetAsyncKeyState(/*VK_CONTROL*/0x11) & (1 << 15)) != 0; bool bShift = (CryGetAsyncKeyState(/*VK_SHIFT*/0x10) & (1 << 15)) != 0; bool bCancel = CryGetAsyncKeyState(/*VK_CANCEL*/0x03) & 1; bool bTimeDemoKey = CryGetAsyncKeyState(/*VK_SNAPSHOT*/0x2C) & 1; if (bCancel) { if (IsRecording()) { // stop all test modules Record(false); return; } // Toggle start/stop of demo recording. if (IsPlaying()) { // Stop playing. Play(false); return; } } ////////////////////////////////////////////////////////////////////////// // Time demo on/off ////////////////////////////////////////////////////////////////////////// if ((bCtrl) && bTimeDemoKey) { if (!IsRecording()) { // Start record. Record(true); } } if (bShift && bTimeDemoKey) { if (!IsPlaying()) { // Load and start playing. Play(true); } } } bool bPaused = false; if (m_bRecording || m_bPlaying) { bPaused = gEnv->pConsole->IsOpened(); if (!bPaused && m_demo_scroll_pause != 0 && gEnv->pInput) { static TKeyName scrollKey("scrolllock"); bPaused = gEnv->pInput->InputState(scrollKey, eIS_Down); } if (bPaused != m_bPaused) { Pause(bPaused); } } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::ReplayGameState(FrameRecord& rec) { ////////////////////////////////////////////////////////////////////////// // Game state IGameStateRecorder* pGameStateRecorder = gEnv->pGame->GetIGameFramework()->GetIGameplayRecorder()->GetIGameStateRecorder(); if (pGameStateRecorder && m_fileVersion >= TIMEDEMO_FILE_VERSION_4 && m_demo_gameState) { int n = rec.gameEvents.size(); for (int i = 0; i < n; i++) { STimeDemoGameEvent& gameEvent = rec.gameEvents[i]; //IEntity * pEntity = gEnv->pEntitySystem->GetEntity(gameEvent.id); IEntity* pEntity = gEnv->pEntitySystem->FindEntityByName(gameEvent.entityName); if (pEntity) { GameplayEvent event; event.event = gameEvent.gameEventType; event.value = gameEvent.value; event.extra = (void*)(EXPAND_PTR)(gameEvent.extra); if (gameEvent.description.size()) { event.description = gameEvent.description.c_str(); } if (gameEvent.description2.size()) { event.extra = (void*)gameEvent.description2.c_str(); } pGameStateRecorder->OnRecordedGameplayEvent(pEntity, event, m_currentFrame); } } } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::StartDemoDelayed(int nFrames) { EraseLogFile(); m_countDownPlay = nFrames; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::RegisterListener(ITimeDemoListener* pListener) { m_listeners.Add(pListener); } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::UnregisterListener(ITimeDemoListener* pListener) { m_listeners.Remove(pListener); } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::GetCurrentFrameRecord(STimeDemoFrameRecord& externalRecord) const { const FrameRecord& record = m_records[m_currentFrame]; externalRecord.playerPosition = record.playerPosition; externalRecord.playerRotation = record.playerRotation; externalRecord.playerViewRotation = record.playerViewRotation; externalRecord.hmdPositionOffset = record.hmdPositionOffset; externalRecord.hmdViewRotation = record.hmdViewRotation; } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::SignalPlayback(bool bEnable) { for (TTimeDemoListeners::Notifier notifier(m_listeners); notifier.IsValid(); notifier.Next()) { notifier->OnPlayback(bEnable); } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::SignalRecording(bool bEnable) { for (TTimeDemoListeners::Notifier notifier(m_listeners); notifier.IsValid(); notifier.Next()) { notifier->OnRecord(bEnable); } } ////////////////////////////////////////////////////////////////////////// void CTimeDemoRecorder::SignalPlayFrame() { for (TTimeDemoListeners::Notifier notifier(m_listeners); notifier.IsValid(); notifier.Next()) { notifier->OnPlayFrame(); } }