From 413639e9feef74eb56f23487898ef5cc1483e2eb Mon Sep 17 00:00:00 2001 From: GPayne Date: Wed, 19 May 2021 19:47:51 -0600 Subject: [PATCH] Fixed problems with pausing time and time management --- data/assets/util/default_keybindings.asset | 2 +- .../openspace/interaction/sessionrecording.h | 75 +++++++--- src/interaction/sessionrecording.cpp | 130 +++++++++++++----- src/interaction/sessionrecording_lua.inl | 59 ++++++++ src/util/time.cpp | 9 ++ src/util/time_lua.inl | 39 ++++++ 6 files changed, 264 insertions(+), 50 deletions(-) diff --git a/data/assets/util/default_keybindings.asset b/data/assets/util/default_keybindings.asset index 57d90ddaa2..158e5f623d 100644 --- a/data/assets/util/default_keybindings.asset +++ b/data/assets/util/default_keybindings.asset @@ -36,7 +36,7 @@ local Keybindings = { { Key = "SPACE", Name = "Toggle Pause (Interpolated)", - Command = "openspace.time.interpolateTogglePause()", + Command = "openspace.time.pauseToggleViaKeyboard()", Documentation = "Smoothly starts and stops the simulation time.", GuiPath = "/Simulation Speed", Local = true diff --git a/include/openspace/interaction/sessionrecording.h b/include/openspace/interaction/sessionrecording.h index 10e7d6b601..7cce41be01 100644 --- a/include/openspace/interaction/sessionrecording.h +++ b/include/openspace/interaction/sessionrecording.h @@ -70,6 +70,18 @@ public: double timeSim; }; + /* + * Struct for storing a script substring that, if found in a saved script, + * will be replaced by its substringReplacement counterpart. + */ + struct ScriptSubstringReplace { + std::string substringFound; + std::string substringReplacement; + ScriptSubstringReplace(std::string found, std::string replace) + : substringFound(found) + , substringReplacement(replace) {}; + }; + static const size_t FileHeaderVersionLength = 5; char FileHeaderVersion[FileHeaderVersionLength+1] = "01.00"; char TargetConvertVersion[FileHeaderVersionLength+1] = "01.00"; @@ -186,6 +198,22 @@ public: */ void stopPlayback(); + /** + * Returns playback pause status. + * + * \return \c true if playback is paused + */ + bool isPlaybackPaused(); + + /** + * Pauses a playback session. This does both the normal pause functionality of + * setting simulation delta time to zero, and pausing the progression through the + * timeline. + * + * \param pause if true, then will set playback timeline progression to zero + */ + void setPlaybackPause(bool pause); + /** * Enables that rendered frames should be saved during playback * \param fps Number of frames per second. @@ -618,6 +646,9 @@ protected: unsigned int findIndexOfLastCameraKeyframeInTimeline(); bool doesTimelineEntryContainCamera(unsigned int index) const; std::vector> _stateChangeCallbacks; + bool doesStartWithSubstring(const std::string& s, const std::string& matchSubstr); + void trimCommandsFromScriptIfFound(std::string& script); + void replaceCommandsFromScriptIfFound(std::string& script); RecordedType getNextKeyframeType(); RecordedType getPrevKeyframeType(); @@ -666,7 +697,8 @@ protected: bool _playbackActive_time = false; bool _playbackActive_script = false; bool _hasHitEndOfCameraKeyframes = false; - bool _playbackStartedPaused = false; + bool _playbackPaused = false; + bool _playbackPausedWithinDeltaTimePause = false; double _playbackPauseOffset = 0.0; double _previousTime = 0.0; @@ -682,11 +714,6 @@ protected: bool _cleanupNeeded = false; const std::string scriptReturnPrefix = "return "; - std::vector scriptsUsingScenegraphNodesNavAccess = { - "RetargetAnchor", - "Anchor", - "Aim" - }; std::vector _keyframesCamera; std::vector _keyframesTime; @@ -702,19 +729,35 @@ protected: "NavigationHandler.OrbitalNavigator.RetargetAnchor", "NavigationHandler.OrbitalNavigator.RetargetAim" }; - //A script that begins with an exact match of any of the strings contained in _scriptRejects will not be recorded + //A script that begins with an exact match of any of the strings contained in + // _scriptRejects will not be recorded const std::vector _scriptRejects = { - "openspace.sessionRecording", - "openspace.scriptScheduler.clear", - "openspace.time.interpolatePause", - "openspace.time.interpolateTogglePause", - "openspace.time.setPause", - "openspace.time.togglePause" + "openspace.sessionRecording.enableTakeScreenShotDuringPlayback", + "openspace.sessionRecording.startPlayback", + "openspace.sessionRecording.stopPlayback", + "openspace.sessionRecording.startRecording", + "openspace.sessionRecording.stopRecording", + "openspace.scriptScheduler.clear" }; const std::vector _navScriptsUsingNodes = { - "RetargetAnchor", - "Anchor", - "Aim" + "RetargetAnchor", + "Anchor", + "Aim" + }; + //Any script snippet included in this vector will be trimmed from any script + // from the script manager, before it is recorded in the session recording file. + // The remainder of the script will be retained. + const std::vector _scriptsToBeTrimmed = { + "openspace.sessionRecording.togglePlaybackPause" + }; + //Any script snippet included in this vector will be trimmed from any script + // from the script manager, before it is recorded in the session recording file. + // The remainder of the script will be retained. + const std::vector _scriptsToBeReplaced = { + { + "openspace.time.pauseToggleViaKeyboard", + "openspace.time.interpolateTogglePause" + } }; std::vector _loadedNodes; diff --git a/src/interaction/sessionrecording.cpp b/src/interaction/sessionrecording.cpp index cb7d8bcf83..bb4675b160 100644 --- a/src/interaction/sessionrecording.cpp +++ b/src/interaction/sessionrecording.cpp @@ -236,6 +236,7 @@ bool SessionRecording::startRecording(const std::string& filename) { global::timeManager->time().j2000Seconds() }; + recordCurrentTimePauseState(); recordCurrentTimeRate(); LINFO("Session recording started"); } @@ -252,7 +253,7 @@ void SessionRecording::recordCurrentTimePauseState() { void SessionRecording::recordCurrentTimeRate() { std::string initialTimeRateCommand = fmt::format( - "openspace.time.setDeltaTime({})", global::timeManager->deltaTime() + "openspace.time.setDeltaTime({})", global::timeManager->targetDeltaTime() ); saveScriptKeyframeToPropertiesBaseline(initialTimeRateCommand); } @@ -437,7 +438,7 @@ bool SessionRecording::startPlayback(std::string& filename, system_clock::duration::period::den / system_clock::duration::period::num; _saveRendering_isFirstFrame = true; _playbackPauseOffset = 0.0; - _playbackStartedPaused = global::timeManager->isPaused(); + _playbackPaused = false; //Set playback flags to true for all modes _playbackActive_camera = true; @@ -482,6 +483,27 @@ bool SessionRecording::startPlayback(std::string& filename, return true; } +bool SessionRecording::isPlaybackPaused() { + return (_state == SessionState::Playback && _playbackPaused); +} + +void SessionRecording::setPlaybackPause(bool pause) { + if (_state == SessionState::Playback) { + if (pause && !_playbackPaused) { + _playbackPausedWithinDeltaTimePause = global::timeManager->isPaused(); + if (!_playbackPausedWithinDeltaTimePause) { + global::timeManager->setPause(true); + } + } + else if (!pause && _playbackPaused) { + if (!_playbackPausedWithinDeltaTimePause) { + global::timeManager->setPause(false); + } + } + _playbackPaused = pause; + } +} + bool SessionRecording::findFirstCameraKeyframeInTimeline() { bool foundCameraKeyframe = false; for (unsigned int i = 0; i < _timeline.size(); i++) { @@ -588,7 +610,7 @@ void SessionRecording::cleanUpPlayback() { _saveRenderingDuringPlayback = false; _saveRendering_isFirstFrame = true; _playbackPauseOffset = 0.0; - _playbackStartedPaused = false; + _playbackPaused = false; _cleanupNeeded = false; } @@ -768,15 +790,16 @@ void SessionRecording::saveTimeKeyframeAscii(Timestamps& times, void SessionRecording::saveScriptKeyframeToTimeline(std::string script) { - size_t substringStartIdx = 0; - if (script.substr(0, scriptReturnPrefix.length()).compare(scriptReturnPrefix) == 0) { - substringStartIdx += scriptReturnPrefix.length(); + if (doesStartWithSubstring(script, scriptReturnPrefix)) { + script = script.substr(scriptReturnPrefix.length()); } for (std::string reject : _scriptRejects) { - if (script.substr(substringStartIdx, reject.length()).compare(reject) == 0) { + if (doesStartWithSubstring(script, reject)) { return; } } + trimCommandsFromScriptIfFound(script); + replaceCommandsFromScriptIfFound(script); datamessagestructures::ScriptMessage sm = _externInteract.generateScriptMessage(script); @@ -784,8 +807,13 @@ void SessionRecording::saveScriptKeyframeToTimeline(std::string script) addKeyframe(times, sm._script, _playbackLineNum); } -void SessionRecording::saveScriptKeyframeToPropertiesBaseline(std::string script) +bool SessionRecording::doesStartWithSubstring(const std::string& s, + const std::string& matchSubstr) { + return (s.substr(0, matchSubstr.length()).compare(matchSubstr) == 0); +} + +void SessionRecording::saveScriptKeyframeToPropertiesBaseline(std::string script) { Timestamps times = generateCurrentTimestamp3(global::windowDelegate->applicationTime()); size_t indexIntoScriptKeyframesFromMainTimeline @@ -800,6 +828,28 @@ void SessionRecording::saveScriptKeyframeToPropertiesBaseline(std::string script ); } +void SessionRecording::trimCommandsFromScriptIfFound(std::string& script) +{ + for (std::string trimSnippet : _scriptsToBeTrimmed) { + auto findIdx = script.find(trimSnippet); + if (findIdx != std::string::npos) { + auto findClosingParens = script.find_first_of(')', findIdx); + script.erase(findIdx, findClosingParens + 1); + } + } +} + +void SessionRecording::replaceCommandsFromScriptIfFound(std::string& script) +{ + for (ScriptSubstringReplace replacementSnippet : _scriptsToBeReplaced) { + auto findIdx = script.find(replacementSnippet.substringFound); + if (findIdx != std::string::npos) { + script.erase(findIdx, replacementSnippet.substringFound.length()); + script.insert(findIdx, replacementSnippet.substringReplacement); + } + } +} + void SessionRecording::saveScriptKeyframeBinary(Timestamps& times, datamessagestructures::ScriptMessage& sm, unsigned char* smBuffer, @@ -849,8 +899,7 @@ void SessionRecording::savePropertyBaseline(properties::Property& prop) { bool SessionRecording::isPropertyAllowedForBaseline(const std::string& propString) { for (std::string reject : _propertyBaselineRejects) { - if (propString.substr(0, reject.length()).compare(reject) == 0) - { + if (doesStartWithSubstring(propString, reject)) { return false; } } @@ -1076,7 +1125,9 @@ double SessionRecording::fixedDeltaTimeDuringFrameOutput() const { } } -std::chrono::steady_clock::time_point SessionRecording::currentPlaybackInterpolationTime() const { +std::chrono::steady_clock::time_point +SessionRecording::currentPlaybackInterpolationTime() const +{ return _saveRenderingCurrentRecordedTime_interpolation; } @@ -1182,7 +1233,6 @@ bool SessionRecording::readCameraKeyframeBinary(Timestamps& times, )); return false; } - //times.timeOs = kf._timestamp; if (!file) { LINFO(fmt::format( @@ -1408,8 +1458,7 @@ bool SessionRecording::checkIfScriptUsesScenegraphNode(std::string s) { checkForScenegraphNodeAccess_Nav(subj, found); if (found.length() > 0) { auto it = std::find(_loadedNodes.begin(), _loadedNodes.end(), found); - if (it == _loadedNodes.end()) - { + if (it == _loadedNodes.end()) { LERROR(fmt::format( "Playback file requires scenegraph node '{}', which is " "not currently loaded", found @@ -1441,12 +1490,14 @@ void SessionRecording::checkForScenegraphNodeAccess_Nav(std::string& s, { const std::string nextTerm = "NavigationHandler.OrbitalNavigator."; auto posNav = s.find(nextTerm); - for (std::string accessName : _navScriptsUsingNodes) { - if (s.substr(posNav + nextTerm.length()).rfind(accessName) == 0) { - std::string postName = s.substr(posNav + nextTerm.length() - + accessName.length() + 2); - eraseSpacesFromString(postName); - result = getNameFromSurroundingQuotes(postName); + if (posNav != std::string::npos) { + for (std::string accessName : _navScriptsUsingNodes) { + if (s.substr(posNav + nextTerm.length()).rfind(accessName) == 0) { + std::string postName = s.substr(posNav + nextTerm.length() + + accessName.length() + 2); + eraseSpacesFromString(postName); + result = getNameFromSurroundingQuotes(postName); + } } } } @@ -1474,8 +1525,7 @@ bool SessionRecording::checkIfInitialFocusNodeIsLoaded(unsigned int camIdx1) { std::string startFocusNode = _keyframesCamera[_timeline[camIdx1].idxIntoKeyframeTypeArray].focusNode; auto it = std::find(_loadedNodes.begin(), _loadedNodes.end(), startFocusNode); - if (it == _loadedNodes.end()) - { + if (it == _loadedNodes.end()) { LERROR(fmt::format( "Playback file requires scenegraph node '{}', which is " "not currently loaded", startFocusNode @@ -1671,17 +1721,9 @@ void SessionRecording::moveAheadInTime() { using namespace std::chrono; bool paused = global::timeManager->isPaused(); - if (paused) { - if (_playbackStartedPaused) { - return; - } - else { - _playbackPauseOffset - += global::windowDelegate->applicationTime() - _previousTime; - } - } - else { - _playbackStartedPaused = false; + if (_playbackPaused) { + _playbackPauseOffset + += global::windowDelegate->applicationTime() - _previousTime; } _previousTime = global::windowDelegate->applicationTime(); @@ -2529,6 +2571,28 @@ scripting::LuaLibrary SessionRecording::luaLibrary() { "Performs a conversion of the specified file to the most most recent " "file format, creating a copy of the recording file." }, + { + "setPlaybackPause", + &luascriptfunctions::setPlaybackPause, + {}, + "bool", + "Pauses or resumes the playback progression through keyframes" + }, + { + "togglePlaybackPause", + &luascriptfunctions::togglePlaybackPause, + {}, + "", + "Toggles the pause function, i.e. temporarily setting the delta time to 0" + " and restoring it afterwards" + }, + { + "isPlayingBack", + & luascriptfunctions::isPlayingBack, + {}, + "", + "Returns true if session recording is currently playing back a recording" + } } }; } diff --git a/src/interaction/sessionrecording_lua.inl b/src/interaction/sessionrecording_lua.inl index fe457ce3df..89f30ad370 100644 --- a/src/interaction/sessionrecording_lua.inl +++ b/src/interaction/sessionrecording_lua.inl @@ -185,4 +185,63 @@ int fileFormatConversion(lua_State* L) { return 0; } +int setPlaybackPause(lua_State* L) { + const int nArguments = lua_gettop(L); + + if (nArguments == 1) { + const bool pause = lua_toboolean(L, 1) == 1; + global::sessionRecording->setPlaybackPause(pause); + } + else { + lua_settop(L, 0); + return luaL_error( + L, + "bad number of arguments, expected 1, got %i", + nArguments + ); + } + + lua_settop(L, 0); + ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); + return 0; +} + +int togglePlaybackPause(lua_State* L) { + const int nArguments = lua_gettop(L); + + if (nArguments == 0) { + bool isPlaybackPaused = global::sessionRecording->isPlaybackPaused(); + global::sessionRecording->setPlaybackPause(!isPlaybackPaused); + } + else { + lua_settop(L, 0); + return luaL_error( + L, + "bad number of arguments, expected 0, got %i", + nArguments + ); + } + + lua_settop(L, 0); + ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); + return 0; +} + +int isPlayingBack(lua_State* L) { + const int nArguments = lua_gettop(L); + + if (nArguments != 0) { + lua_settop(L, 0); + return luaL_error( + L, + "bad number of arguments, expected 0, got %i", + nArguments + ); + } + + ghoul::lua::push(L, global::sessionRecording->isPlayingBack()); + ghoul_assert(lua_gettop(L) == 1, "Incorrect number of items left on stack"); + return 1; +} + } // namespace openspace::luascriptfunctions diff --git a/src/util/time.cpp b/src/util/time.cpp index bd886aa9aa..189238e8b3 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -264,6 +264,15 @@ scripting::LuaLibrary Time::luaLibrary() { " and restoring it afterwards. If an input value is given, the " "interpolation is done over the specified number of seconds." }, + { + "pauseToggleViaKeyboard", + &luascriptfunctions::time_pauseToggleViaKeyboard, + {}, + "", + "Toggles the pause function from a keypress. This function behaves like" + " interpolateTogglePause during normal mode, and behaves like" + " sessionRecording.pausePlayback when playing-back a recording." + }, { "currentTime", &luascriptfunctions::time_currentTime, diff --git a/src/util/time_lua.inl b/src/util/time_lua.inl index 95ebcd4d11..185567f97b 100644 --- a/src/util/time_lua.inl +++ b/src/util/time_lua.inl @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -379,6 +380,44 @@ int time_interpolateTogglePause(lua_State* L) { return 0; } +/** +* \ingroup LuaScripts +* time_pauseToggleViaKeyboard(): +* This allows for a keypress (via keybinding) to have dual functionality. In normal +* operational mode it will behave just like time_interpolateTogglePause, but +* during playback of a session recording it will pause the playback without manipulating +* the delta time. +*/ +int time_pauseToggleViaKeyboard(lua_State* L) { + const int nArguments = lua_gettop(L); + + if (nArguments == 0) { + if (global::sessionRecording->isPlayingBack()) { + bool isPlaybackPaused = global::sessionRecording->isPlaybackPaused(); + global::sessionRecording->setPlaybackPause(!isPlaybackPaused); + } + else { + const bool pause = !global::timeManager->isPaused(); + global::timeManager->interpolatePause(pause, + pause ? + global::timeManager->defaultPauseInterpolationDuration() : + global::timeManager->defaultUnpauseInterpolationDuration() + ); + } + } + else { + lua_settop(L, 0); + return luaL_error( + L, + "bad number of arguments, expected 0 or 1, got %i", + nArguments + ); + } + + lua_settop(L, 0); + ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); + return 0; +} /** * \ingroup LuaScripts