From c3e156f9b53b3233453a32822c5f500b7e62a838 Mon Sep 17 00:00:00 2001 From: thrust26 Date: Thu, 23 Jul 2020 12:39:22 +0200 Subject: [PATCH] started adding playback mode (see #678) --- src/common/PKeyboardHandler.cxx | 2 + src/common/RewindManager.cxx | 6 +- src/emucore/Event.hxx | 1 + src/emucore/EventHandler.cxx | 43 +++++++++++-- src/emucore/EventHandler.hxx | 3 +- src/emucore/EventHandlerConstants.hxx | 1 + src/emucore/FrameBuffer.cxx | 40 ++++++++++++ src/emucore/OSystem.cxx | 1 + src/gui/TimeMachineDialog.cxx | 87 ++++++++++++++++++++------- src/gui/TimeMachineDialog.hxx | 7 ++- 10 files changed, 160 insertions(+), 31 deletions(-) diff --git a/src/common/PKeyboardHandler.cxx b/src/common/PKeyboardHandler.cxx index 884ee594a..f5d39b20a 100644 --- a/src/common/PKeyboardHandler.cxx +++ b/src/common/PKeyboardHandler.cxx @@ -416,6 +416,7 @@ void PhysicalKeyboardHandler::handleEvent(StellaKey key, StellaMod mod, bool pre { case EventHandlerState::EMULATION: case EventHandlerState::PAUSE: + case EventHandlerState::PLAYBACK: myHandler.handleEvent(myKeyMap.get(EventMode::kEmulationMode, key, mod), pressed, repeated); break; @@ -563,6 +564,7 @@ PhysicalKeyboardHandler::EventMappingArray PhysicalKeyboardHandler::DefaultCommo {Event::Unwind1Menu, KBDK_RIGHT, MOD3}, {Event::Unwind10Menu, KBDK_RIGHT, KBDM_SHIFT | MOD3}, {Event::UnwindAllMenu, KBDK_UP, MOD3}, + {Event::TogglePlayBackMode, KBDK_SPACE, KBDM_SHIFT}, #if defined(RETRON77) {Event::ConsoleColorToggle, KBDK_F4}, // back ("COLOR","B/W") diff --git a/src/common/RewindManager.cxx b/src/common/RewindManager.cxx index 6bafbeeaa..70fa481d7 100644 --- a/src/common/RewindManager.cxx +++ b/src/common/RewindManager.cxx @@ -181,7 +181,8 @@ uInt32 RewindManager::rewindStates(uInt32 numStates) else message = "Rewind not possible"; - if(myOSystem.eventHandler().state() != EventHandlerState::TIMEMACHINE) + if(myOSystem.eventHandler().state() != EventHandlerState::TIMEMACHINE + && myOSystem.eventHandler().state() != EventHandlerState::PLAYBACK) myOSystem.frameBuffer().showMessage(message); return i; } @@ -215,7 +216,8 @@ uInt32 RewindManager::unwindStates(uInt32 numStates) else message = "Unwind not possible"; - if(myOSystem.eventHandler().state() != EventHandlerState::TIMEMACHINE) + if(myOSystem.eventHandler().state() != EventHandlerState::TIMEMACHINE + && myOSystem.eventHandler().state() != EventHandlerState::PLAYBACK) myOSystem.frameBuffer().showMessage(message); return i; } diff --git a/src/emucore/Event.hxx b/src/emucore/Event.hxx index f666e19bc..010007c72 100644 --- a/src/emucore/Event.hxx +++ b/src/emucore/Event.hxx @@ -125,6 +125,7 @@ class Event ToggleAdaptRefresh, PreviousMultiCartRom, // add new events from here to avoid that user remapped events get overwritten PreviousSettingGroup, NextSettingGroup, + TogglePlayBackMode, LastType }; diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx index b979a18c6..5dbd2ca2f 100644 --- a/src/emucore/EventHandler.cxx +++ b/src/emucore/EventHandler.cxx @@ -1283,6 +1283,10 @@ void EventHandler::handleEvent(Event::Type event, Int32 value, bool repeated) if (pressed && !repeated) changeStateByEvent(Event::TimeMachineMode); return; + case EventHandlerState::PLAYBACK: + if (pressed && !repeated) changeStateByEvent(Event::TogglePlayBackMode); + return; + // this event is called when exiting a ROM from the debugger, so it acts like pressing ESC in emulation case EventHandlerState::EMULATION: case EventHandlerState::DEBUGGER: @@ -1556,7 +1560,7 @@ bool EventHandler::changeStateByEvent(Event::Type type) switch(type) { case Event::TogglePauseMode: - if(myState == EventHandlerState::EMULATION) + if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PLAYBACK) setState(EventHandlerState::PAUSE); else if(myState == EventHandlerState::PAUSE) setState(EventHandlerState::EMULATION); @@ -1565,14 +1569,16 @@ bool EventHandler::changeStateByEvent(Event::Type type) break; case Event::OptionsMenuMode: - if (myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE) + if (myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE + || myState == EventHandlerState::PLAYBACK) enterMenuMode(EventHandlerState::OPTIONSMENU); else handled = false; break; case Event::CmdMenuMode: - if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE) + if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE + || myState == EventHandlerState::PLAYBACK) enterMenuMode(EventHandlerState::CMDMENU); else if(myState == EventHandlerState::CMDMENU && !myOSystem.settings().getBool("minimal_ui")) // The extra check for "minimal_ui" allows mapping e.g. right joystick fire @@ -1583,7 +1589,8 @@ bool EventHandler::changeStateByEvent(Event::Type type) break; case Event::TimeMachineMode: - if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE) + if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE + || myState == EventHandlerState::PLAYBACK) enterTimeMachineMenuMode(0, false); else if(myState == EventHandlerState::TIMEMACHINE) leaveMenuMode(); @@ -1591,10 +1598,24 @@ bool EventHandler::changeStateByEvent(Event::Type type) handled = false; break; + case Event::TogglePlayBackMode: + if (myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE + || myState == EventHandlerState::TIMEMACHINE) + enterPlayBackMode(); + else if (myState == EventHandlerState::PLAYBACK) + #ifdef GUI_SUPPORT + enterMenuMode(EventHandlerState::TIMEMACHINE); + #else + setState(EventHandlerState::PAUSE); + #endif + else + handled = false; + break; + case Event::DebuggerMode: #ifdef DEBUGGER_SUPPORT if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE - || myState == EventHandlerState::TIMEMACHINE) + || myState == EventHandlerState::TIMEMACHINE || myState == EventHandlerState::PLAYBACK) enterDebugMode(); else if(myState == EventHandlerState::DEBUGGER && myOSystem.debugger().canExit()) leaveDebugMode(); @@ -2248,6 +2269,15 @@ void EventHandler::enterTimeMachineMenuMode(uInt32 numWinds, bool unwind) #endif } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EventHandler::enterPlayBackMode() +{ +#ifdef GUI_SUPPORT + setState(EventHandlerState::PLAYBACK); + myOSystem.sound().mute(true); // sound does not work in playback mode +#endif +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void EventHandler::setState(EventHandlerState state) { @@ -2263,6 +2293,7 @@ void EventHandler::setState(EventHandlerState state) switch(myState) { case EventHandlerState::EMULATION: + case EventHandlerState::PLAYBACK: myOSystem.sound().mute(false); enableTextEvents(false); break; @@ -2541,6 +2572,7 @@ EventHandler::EmulActionList EventHandler::ourEmulActionList = { { { Event::Unwind1Menu, "Unwind one state & enter TM UI", "" }, { Event::Unwind10Menu, "Unwind 10 states & enter TM UI", "" }, { Event::UnwindAllMenu, "Unwind all states & enter TM UI", "" }, + { Event::TogglePlayBackMode, "Toggle 'Time Machine' playback mode", "" }, { Event::Combo1, "Combo 1", "" }, { Event::Combo2, "Combo 2", "" }, @@ -2628,6 +2660,7 @@ const Event::EventSet EventHandler::StateEvents = { Event::TimeMachineMode, Event::RewindPause, Event::UnwindPause, Event::ToggleTimeMachine, Event::Rewind1Menu, Event::Rewind10Menu, Event::RewindAllMenu, Event::Unwind1Menu, Event::Unwind10Menu, Event::UnwindAllMenu, + Event::TogglePlayBackMode, Event::SaveAllStates, Event::LoadAllStates, Event::ToggleAutoSlot, }; diff --git a/src/emucore/EventHandler.hxx b/src/emucore/EventHandler.hxx index 0ff428998..3f780b150 100644 --- a/src/emucore/EventHandler.hxx +++ b/src/emucore/EventHandler.hxx @@ -136,6 +136,7 @@ class EventHandler bool enterDebugMode(); void leaveDebugMode(); void enterTimeMachineMenuMode(uInt32 numWinds, bool unwind); + void enterPlayBackMode(); /** Send an event directly to the event handler. @@ -557,7 +558,7 @@ class EventHandler #else REFRESH_SIZE = 0, #endif - EMUL_ACTIONLIST_SIZE = 159 + PNG_SIZE + COMBO_SIZE + REFRESH_SIZE, + EMUL_ACTIONLIST_SIZE = 160 + PNG_SIZE + COMBO_SIZE + REFRESH_SIZE, MENU_ACTIONLIST_SIZE = 18 ; diff --git a/src/emucore/EventHandlerConstants.hxx b/src/emucore/EventHandlerConstants.hxx index 8f11ecab6..0009af3d3 100644 --- a/src/emucore/EventHandlerConstants.hxx +++ b/src/emucore/EventHandlerConstants.hxx @@ -22,6 +22,7 @@ enum class EventHandlerState { EMULATION, TIMEMACHINE, + PLAYBACK, PAUSE, LAUNCHER, OPTIONSMENU, diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 2f4626968..d35ebd36f 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -29,6 +29,8 @@ #include "FBSurface.hxx" #include "TIASurface.hxx" #include "FrameBuffer.hxx" +#include "StateManager.hxx" +#include "RewindManager.hxx" #ifdef DEBUGGER_SUPPORT #include "Debugger.hxx" @@ -422,6 +424,44 @@ void FrameBuffer::update(bool force) break; // EventHandlerState::TIMEMACHINE } + case EventHandlerState::PLAYBACK: + { + static Int32 frames = 0; + RewindManager& r = myOSystem.state().rewindManager(); + bool success = true; + Int64 frameCycles = 76 * std::max(myOSystem.console().tia().scanlinesLastFrame(), 240); + + if(--frames <= 0) + { + r.unwindStates(1); + // get time between current and next state + uInt64 startCycles = r.getCurrentCycles(); + success = r.unwindStates(1); + // display larger state gaps faster + frames = std::sqrt((myOSystem.console().tia().cycles() - startCycles) / frameCycles); + + if(success) + r.rewindStates(1); + } + + force = force || success; + if (force) + myTIASurface->render(); + + // Stop playback mode at the end of the state buffer + // and switch to Time Machine or Pause mode + if (!success) + { + frames = 0; + #ifdef GUI_SUPPORT + myOSystem.eventHandler().enterMenuMode(EventHandlerState::TIMEMACHINE); + #else + myOSystem.eventHandler().changeStateByEvent(Event::TogglePauseMode); + #endif + } + break; // EventHandlerState::PLAYBACK + } + case EventHandlerState::LAUNCHER: { force = force || myOSystem.launcher().needsRedraw(); diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index f6f13f05e..47a7a6c95 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -343,6 +343,7 @@ FBInitStatus OSystem::createFrameBuffer() case EventHandlerState::OPTIONSMENU: case EventHandlerState::CMDMENU: case EventHandlerState::TIMEMACHINE: + case EventHandlerState::PLAYBACK: #endif if((fbstatus = myConsole->initializeVideo()) != FBInitStatus::Success) return fbstatus; diff --git a/src/gui/TimeMachineDialog.cxx b/src/gui/TimeMachineDialog.cxx index f157c67a8..2ed5295f5 100644 --- a/src/gui/TimeMachineDialog.cxx +++ b/src/gui/TimeMachineDialog.cxx @@ -69,22 +69,23 @@ static constexpr std::array STOP = { 0b11111111111111, 0b11111111111111 }; -static constexpr std::array PLAY = { - 0b11000000000000, - 0b11110000000000, - 0b11111100000000, - 0b11111111000000, - 0b11111111110000, - 0b11111111111100, - 0b11111111111111, - 0b11111111111111, - 0b11111111111100, - 0b11111111110000, - 0b11111111000000, - 0b11111100000000, - 0b11110000000000, - 0b11000000000000 +static constexpr std::array EXIT = { + 0b01100000000110, + 0b11110000001111, + 0b11111000011111, + 0b01111100111110, + 0b00111111111100, + 0b00011111111000, + 0b00001111110000, + 0b00001111110000, + 0b00011111111000, + 0b00111111111100, + 0b01111100111110, + 0b11111000011111, + 0b11110000001111, + 0b01100000000110 }; + static constexpr std::array REWIND_ALL = { 0, 0b11000011000011, @@ -117,6 +118,22 @@ static constexpr std::array REWIND_1 = { 0b00000110001110, 0 }; +static constexpr std::array PLAYBACK = { + 0b11000000000000, + 0b11110000000000, + 0b11111100000000, + 0b11111111000000, + 0b11111111110000, + 0b11111111111100, + 0b11111111111111, + 0b11111111111111, + 0b11111111111100, + 0b11111111110000, + 0b11111111000000, + 0b11111100000000, + 0b11110000000000, + 0b11000000000000 +}; static constexpr std::array UNWIND_1 = { 0, 0b01110001100000, @@ -235,8 +252,8 @@ TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent, STOP.data(), BUTTON_W, BUTTON_H, kToggle); xpos += buttonWidth + BUTTON_GAP; - myPlayWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, - PLAY.data(), BUTTON_W, BUTTON_H, kPlay); + myExitWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, + EXIT.data(), BUTTON_W, BUTTON_H, kExit); xpos += buttonWidth + BUTTON_GAP * 4; myRewindAllWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, @@ -247,6 +264,10 @@ TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent, REWIND_1.data(), BUTTON_W, BUTTON_H, kRewind1, true); xpos += buttonWidth + BUTTON_GAP; + myPlayBackWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, + PLAYBACK.data(), BUTTON_W, BUTTON_H, kPlayBack); + xpos += buttonWidth + BUTTON_GAP; + myUnwind1Widget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, UNWIND_1.data(), BUTTON_W, BUTTON_H, kUnwind1, true); xpos += buttonWidth + BUTTON_GAP; @@ -331,14 +352,33 @@ void TimeMachineDialog::handleKeyDown(StellaKey key, StellaMod mod, bool repeate break; case Event::ExitMode: - handleCommand(nullptr, kPlay, 0, 0); + handleCommand(nullptr, kExit, 0, 0); + break; + + default: + Dialog::handleKeyDown(key, mod); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeMachineDialog::handleKeyUp(StellaKey key, StellaMod mod) +{ + // The following shortcuts duplicate the shortcuts in EventHandler + // Note: mode switches must happen in key UP + + Event::Type event = instance().eventHandler().eventForKey(EventMode::kEmulationMode, key, mod); + + switch (event) + { + case Event::TogglePlayBackMode: + handleCommand(nullptr, kPlayBack, 0, 0); break; default: if (key == KBDK_SPACE) - handleCommand(nullptr, kPlay, 0, 0); + handleCommand(nullptr, kPlayBack, 0, 0); else - Dialog::handleKeyDown(key, mod); + Dialog::handleKeyUp(key, mod); } } @@ -361,7 +401,7 @@ void TimeMachineDialog::handleCommand(CommandSender* sender, int cmd, handleToggle(); break; - case kPlay: + case kExit: instance().eventHandler().leaveMenuMode(); break; @@ -377,6 +417,10 @@ void TimeMachineDialog::handleCommand(CommandSender* sender, int cmd, handleWinds(-1000); break; + case kPlayBack: + instance().eventHandler().enterPlayBackMode(); + break; + case kUnwind1: handleWinds(1); break; @@ -479,6 +523,7 @@ void TimeMachineDialog::handleWinds(Int32 numWinds) // Enable/disable buttons myRewindAllWidget->setEnabled(!r.atFirst()); myRewind1Widget->setEnabled(!r.atFirst()); + myPlayBackWidget->setEnabled(!r.atLast()); myUnwindAllWidget->setEnabled(!r.atLast()); myUnwind1Widget->setEnabled(!r.atLast()); mySaveAllWidget->setEnabled(r.getLastIdx() != 0); diff --git a/src/gui/TimeMachineDialog.hxx b/src/gui/TimeMachineDialog.hxx index 45c9dfa06..283249514 100644 --- a/src/gui/TimeMachineDialog.hxx +++ b/src/gui/TimeMachineDialog.hxx @@ -38,6 +38,7 @@ class TimeMachineDialog : public Dialog private: void loadConfig() override; void handleKeyDown(StellaKey key, StellaMod mod, bool repeated) override; + void handleKeyUp(StellaKey key, StellaMod mod) override; void handleCommand(CommandSender* sender, int cmd, int data, int id) override; /** initialize timeline bar */ @@ -58,7 +59,8 @@ class TimeMachineDialog : public Dialog { kTimeline = 'TMtl', kToggle = 'TMtg', - kPlay = 'TMpl', + kExit = 'TMex', + kPlayBack = 'TMpb', kRewindAll = 'TMra', kRewind10 = 'TMr1', kRewind1 = 'TMre', @@ -73,7 +75,8 @@ class TimeMachineDialog : public Dialog TimeLineWidget* myTimeline{nullptr}; ButtonWidget* myToggleWidget{nullptr}; - ButtonWidget* myPlayWidget{nullptr}; + ButtonWidget* myExitWidget{ nullptr }; + ButtonWidget* myPlayBackWidget{nullptr}; ButtonWidget* myRewindAllWidget{nullptr}; ButtonWidget* myRewind1Widget{nullptr}; ButtonWidget* myUnwind1Widget{nullptr};