started adding playback mode (see #678)

This commit is contained in:
thrust26 2020-07-23 12:39:22 +02:00
parent 031df6aeaf
commit c3e156f9b5
10 changed files with 160 additions and 31 deletions

View File

@ -416,6 +416,7 @@ void PhysicalKeyboardHandler::handleEvent(StellaKey key, StellaMod mod, bool pre
{ {
case EventHandlerState::EMULATION: case EventHandlerState::EMULATION:
case EventHandlerState::PAUSE: case EventHandlerState::PAUSE:
case EventHandlerState::PLAYBACK:
myHandler.handleEvent(myKeyMap.get(EventMode::kEmulationMode, key, mod), pressed, repeated); myHandler.handleEvent(myKeyMap.get(EventMode::kEmulationMode, key, mod), pressed, repeated);
break; break;
@ -563,6 +564,7 @@ PhysicalKeyboardHandler::EventMappingArray PhysicalKeyboardHandler::DefaultCommo
{Event::Unwind1Menu, KBDK_RIGHT, MOD3}, {Event::Unwind1Menu, KBDK_RIGHT, MOD3},
{Event::Unwind10Menu, KBDK_RIGHT, KBDM_SHIFT | MOD3}, {Event::Unwind10Menu, KBDK_RIGHT, KBDM_SHIFT | MOD3},
{Event::UnwindAllMenu, KBDK_UP, MOD3}, {Event::UnwindAllMenu, KBDK_UP, MOD3},
{Event::TogglePlayBackMode, KBDK_SPACE, KBDM_SHIFT},
#if defined(RETRON77) #if defined(RETRON77)
{Event::ConsoleColorToggle, KBDK_F4}, // back ("COLOR","B/W") {Event::ConsoleColorToggle, KBDK_F4}, // back ("COLOR","B/W")

View File

@ -181,7 +181,8 @@ uInt32 RewindManager::rewindStates(uInt32 numStates)
else else
message = "Rewind not possible"; 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); myOSystem.frameBuffer().showMessage(message);
return i; return i;
} }
@ -215,7 +216,8 @@ uInt32 RewindManager::unwindStates(uInt32 numStates)
else else
message = "Unwind not possible"; 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); myOSystem.frameBuffer().showMessage(message);
return i; return i;
} }

View File

@ -125,6 +125,7 @@ class Event
ToggleAdaptRefresh, PreviousMultiCartRom, ToggleAdaptRefresh, PreviousMultiCartRom,
// add new events from here to avoid that user remapped events get overwritten // add new events from here to avoid that user remapped events get overwritten
PreviousSettingGroup, NextSettingGroup, PreviousSettingGroup, NextSettingGroup,
TogglePlayBackMode,
LastType LastType
}; };

View File

@ -1283,6 +1283,10 @@ void EventHandler::handleEvent(Event::Type event, Int32 value, bool repeated)
if (pressed && !repeated) changeStateByEvent(Event::TimeMachineMode); if (pressed && !repeated) changeStateByEvent(Event::TimeMachineMode);
return; 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 // this event is called when exiting a ROM from the debugger, so it acts like pressing ESC in emulation
case EventHandlerState::EMULATION: case EventHandlerState::EMULATION:
case EventHandlerState::DEBUGGER: case EventHandlerState::DEBUGGER:
@ -1556,7 +1560,7 @@ bool EventHandler::changeStateByEvent(Event::Type type)
switch(type) switch(type)
{ {
case Event::TogglePauseMode: case Event::TogglePauseMode:
if(myState == EventHandlerState::EMULATION) if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PLAYBACK)
setState(EventHandlerState::PAUSE); setState(EventHandlerState::PAUSE);
else if(myState == EventHandlerState::PAUSE) else if(myState == EventHandlerState::PAUSE)
setState(EventHandlerState::EMULATION); setState(EventHandlerState::EMULATION);
@ -1565,14 +1569,16 @@ bool EventHandler::changeStateByEvent(Event::Type type)
break; break;
case Event::OptionsMenuMode: case Event::OptionsMenuMode:
if (myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE) if (myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE
|| myState == EventHandlerState::PLAYBACK)
enterMenuMode(EventHandlerState::OPTIONSMENU); enterMenuMode(EventHandlerState::OPTIONSMENU);
else else
handled = false; handled = false;
break; break;
case Event::CmdMenuMode: case Event::CmdMenuMode:
if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE) if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE
|| myState == EventHandlerState::PLAYBACK)
enterMenuMode(EventHandlerState::CMDMENU); enterMenuMode(EventHandlerState::CMDMENU);
else if(myState == EventHandlerState::CMDMENU && !myOSystem.settings().getBool("minimal_ui")) else if(myState == EventHandlerState::CMDMENU && !myOSystem.settings().getBool("minimal_ui"))
// The extra check for "minimal_ui" allows mapping e.g. right joystick fire // The extra check for "minimal_ui" allows mapping e.g. right joystick fire
@ -1583,7 +1589,8 @@ bool EventHandler::changeStateByEvent(Event::Type type)
break; break;
case Event::TimeMachineMode: case Event::TimeMachineMode:
if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE) if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE
|| myState == EventHandlerState::PLAYBACK)
enterTimeMachineMenuMode(0, false); enterTimeMachineMenuMode(0, false);
else if(myState == EventHandlerState::TIMEMACHINE) else if(myState == EventHandlerState::TIMEMACHINE)
leaveMenuMode(); leaveMenuMode();
@ -1591,10 +1598,24 @@ bool EventHandler::changeStateByEvent(Event::Type type)
handled = false; handled = false;
break; 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: case Event::DebuggerMode:
#ifdef DEBUGGER_SUPPORT #ifdef DEBUGGER_SUPPORT
if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE
|| myState == EventHandlerState::TIMEMACHINE) || myState == EventHandlerState::TIMEMACHINE || myState == EventHandlerState::PLAYBACK)
enterDebugMode(); enterDebugMode();
else if(myState == EventHandlerState::DEBUGGER && myOSystem.debugger().canExit()) else if(myState == EventHandlerState::DEBUGGER && myOSystem.debugger().canExit())
leaveDebugMode(); leaveDebugMode();
@ -2248,6 +2269,15 @@ void EventHandler::enterTimeMachineMenuMode(uInt32 numWinds, bool unwind)
#endif #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) void EventHandler::setState(EventHandlerState state)
{ {
@ -2263,6 +2293,7 @@ void EventHandler::setState(EventHandlerState state)
switch(myState) switch(myState)
{ {
case EventHandlerState::EMULATION: case EventHandlerState::EMULATION:
case EventHandlerState::PLAYBACK:
myOSystem.sound().mute(false); myOSystem.sound().mute(false);
enableTextEvents(false); enableTextEvents(false);
break; break;
@ -2541,6 +2572,7 @@ EventHandler::EmulActionList EventHandler::ourEmulActionList = { {
{ Event::Unwind1Menu, "Unwind one state & enter TM UI", "" }, { Event::Unwind1Menu, "Unwind one state & enter TM UI", "" },
{ Event::Unwind10Menu, "Unwind 10 states & enter TM UI", "" }, { Event::Unwind10Menu, "Unwind 10 states & enter TM UI", "" },
{ Event::UnwindAllMenu, "Unwind all states & enter TM UI", "" }, { Event::UnwindAllMenu, "Unwind all states & enter TM UI", "" },
{ Event::TogglePlayBackMode, "Toggle 'Time Machine' playback mode", "" },
{ Event::Combo1, "Combo 1", "" }, { Event::Combo1, "Combo 1", "" },
{ Event::Combo2, "Combo 2", "" }, { Event::Combo2, "Combo 2", "" },
@ -2628,6 +2660,7 @@ const Event::EventSet EventHandler::StateEvents = {
Event::TimeMachineMode, Event::RewindPause, Event::UnwindPause, Event::ToggleTimeMachine, Event::TimeMachineMode, Event::RewindPause, Event::UnwindPause, Event::ToggleTimeMachine,
Event::Rewind1Menu, Event::Rewind10Menu, Event::RewindAllMenu, Event::Rewind1Menu, Event::Rewind10Menu, Event::RewindAllMenu,
Event::Unwind1Menu, Event::Unwind10Menu, Event::UnwindAllMenu, Event::Unwind1Menu, Event::Unwind10Menu, Event::UnwindAllMenu,
Event::TogglePlayBackMode,
Event::SaveAllStates, Event::LoadAllStates, Event::ToggleAutoSlot, Event::SaveAllStates, Event::LoadAllStates, Event::ToggleAutoSlot,
}; };

View File

@ -136,6 +136,7 @@ class EventHandler
bool enterDebugMode(); bool enterDebugMode();
void leaveDebugMode(); void leaveDebugMode();
void enterTimeMachineMenuMode(uInt32 numWinds, bool unwind); void enterTimeMachineMenuMode(uInt32 numWinds, bool unwind);
void enterPlayBackMode();
/** /**
Send an event directly to the event handler. Send an event directly to the event handler.
@ -557,7 +558,7 @@ class EventHandler
#else #else
REFRESH_SIZE = 0, REFRESH_SIZE = 0,
#endif #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 MENU_ACTIONLIST_SIZE = 18
; ;

View File

@ -22,6 +22,7 @@
enum class EventHandlerState { enum class EventHandlerState {
EMULATION, EMULATION,
TIMEMACHINE, TIMEMACHINE,
PLAYBACK,
PAUSE, PAUSE,
LAUNCHER, LAUNCHER,
OPTIONSMENU, OPTIONSMENU,

View File

@ -29,6 +29,8 @@
#include "FBSurface.hxx" #include "FBSurface.hxx"
#include "TIASurface.hxx" #include "TIASurface.hxx"
#include "FrameBuffer.hxx" #include "FrameBuffer.hxx"
#include "StateManager.hxx"
#include "RewindManager.hxx"
#ifdef DEBUGGER_SUPPORT #ifdef DEBUGGER_SUPPORT
#include "Debugger.hxx" #include "Debugger.hxx"
@ -422,6 +424,44 @@ void FrameBuffer::update(bool force)
break; // EventHandlerState::TIMEMACHINE break; // EventHandlerState::TIMEMACHINE
} }
case EventHandlerState::PLAYBACK:
{
static Int32 frames = 0;
RewindManager& r = myOSystem.state().rewindManager();
bool success = true;
Int64 frameCycles = 76 * std::max<Int32>(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: case EventHandlerState::LAUNCHER:
{ {
force = force || myOSystem.launcher().needsRedraw(); force = force || myOSystem.launcher().needsRedraw();

View File

@ -343,6 +343,7 @@ FBInitStatus OSystem::createFrameBuffer()
case EventHandlerState::OPTIONSMENU: case EventHandlerState::OPTIONSMENU:
case EventHandlerState::CMDMENU: case EventHandlerState::CMDMENU:
case EventHandlerState::TIMEMACHINE: case EventHandlerState::TIMEMACHINE:
case EventHandlerState::PLAYBACK:
#endif #endif
if((fbstatus = myConsole->initializeVideo()) != FBInitStatus::Success) if((fbstatus = myConsole->initializeVideo()) != FBInitStatus::Success)
return fbstatus; return fbstatus;

View File

@ -69,22 +69,23 @@ static constexpr std::array<uInt32, BUTTON_H> STOP = {
0b11111111111111, 0b11111111111111,
0b11111111111111 0b11111111111111
}; };
static constexpr std::array<uInt32, BUTTON_H> PLAY = { static constexpr std::array<uInt32, BUTTON_H> EXIT = {
0b11000000000000, 0b01100000000110,
0b11110000000000, 0b11110000001111,
0b11111100000000, 0b11111000011111,
0b11111111000000, 0b01111100111110,
0b11111111110000, 0b00111111111100,
0b11111111111100, 0b00011111111000,
0b11111111111111, 0b00001111110000,
0b11111111111111, 0b00001111110000,
0b11111111111100, 0b00011111111000,
0b11111111110000, 0b00111111111100,
0b11111111000000, 0b01111100111110,
0b11111100000000, 0b11111000011111,
0b11110000000000, 0b11110000001111,
0b11000000000000 0b01100000000110
}; };
static constexpr std::array<uInt32, BUTTON_H> REWIND_ALL = { static constexpr std::array<uInt32, BUTTON_H> REWIND_ALL = {
0, 0,
0b11000011000011, 0b11000011000011,
@ -117,6 +118,22 @@ static constexpr std::array<uInt32, BUTTON_H> REWIND_1 = {
0b00000110001110, 0b00000110001110,
0 0
}; };
static constexpr std::array<uInt32, BUTTON_H> PLAYBACK = {
0b11000000000000,
0b11110000000000,
0b11111100000000,
0b11111111000000,
0b11111111110000,
0b11111111111100,
0b11111111111111,
0b11111111111111,
0b11111111111100,
0b11111111110000,
0b11111111000000,
0b11111100000000,
0b11110000000000,
0b11000000000000
};
static constexpr std::array<uInt32, BUTTON_H> UNWIND_1 = { static constexpr std::array<uInt32, BUTTON_H> UNWIND_1 = {
0, 0,
0b01110001100000, 0b01110001100000,
@ -235,8 +252,8 @@ TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent,
STOP.data(), BUTTON_W, BUTTON_H, kToggle); STOP.data(), BUTTON_W, BUTTON_H, kToggle);
xpos += buttonWidth + BUTTON_GAP; xpos += buttonWidth + BUTTON_GAP;
myPlayWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, myExitWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight,
PLAY.data(), BUTTON_W, BUTTON_H, kPlay); EXIT.data(), BUTTON_W, BUTTON_H, kExit);
xpos += buttonWidth + BUTTON_GAP * 4; xpos += buttonWidth + BUTTON_GAP * 4;
myRewindAllWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, 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); REWIND_1.data(), BUTTON_W, BUTTON_H, kRewind1, true);
xpos += buttonWidth + BUTTON_GAP; 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, myUnwind1Widget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight,
UNWIND_1.data(), BUTTON_W, BUTTON_H, kUnwind1, true); UNWIND_1.data(), BUTTON_W, BUTTON_H, kUnwind1, true);
xpos += buttonWidth + BUTTON_GAP; xpos += buttonWidth + BUTTON_GAP;
@ -331,14 +352,33 @@ void TimeMachineDialog::handleKeyDown(StellaKey key, StellaMod mod, bool repeate
break; break;
case Event::ExitMode: 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; break;
default: default:
if (key == KBDK_SPACE) if (key == KBDK_SPACE)
handleCommand(nullptr, kPlay, 0, 0); handleCommand(nullptr, kPlayBack, 0, 0);
else else
Dialog::handleKeyDown(key, mod); Dialog::handleKeyUp(key, mod);
} }
} }
@ -361,7 +401,7 @@ void TimeMachineDialog::handleCommand(CommandSender* sender, int cmd,
handleToggle(); handleToggle();
break; break;
case kPlay: case kExit:
instance().eventHandler().leaveMenuMode(); instance().eventHandler().leaveMenuMode();
break; break;
@ -377,6 +417,10 @@ void TimeMachineDialog::handleCommand(CommandSender* sender, int cmd,
handleWinds(-1000); handleWinds(-1000);
break; break;
case kPlayBack:
instance().eventHandler().enterPlayBackMode();
break;
case kUnwind1: case kUnwind1:
handleWinds(1); handleWinds(1);
break; break;
@ -479,6 +523,7 @@ void TimeMachineDialog::handleWinds(Int32 numWinds)
// Enable/disable buttons // Enable/disable buttons
myRewindAllWidget->setEnabled(!r.atFirst()); myRewindAllWidget->setEnabled(!r.atFirst());
myRewind1Widget->setEnabled(!r.atFirst()); myRewind1Widget->setEnabled(!r.atFirst());
myPlayBackWidget->setEnabled(!r.atLast());
myUnwindAllWidget->setEnabled(!r.atLast()); myUnwindAllWidget->setEnabled(!r.atLast());
myUnwind1Widget->setEnabled(!r.atLast()); myUnwind1Widget->setEnabled(!r.atLast());
mySaveAllWidget->setEnabled(r.getLastIdx() != 0); mySaveAllWidget->setEnabled(r.getLastIdx() != 0);

View File

@ -38,6 +38,7 @@ class TimeMachineDialog : public Dialog
private: private:
void loadConfig() override; void loadConfig() override;
void handleKeyDown(StellaKey key, StellaMod mod, bool repeated) 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; void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
/** initialize timeline bar */ /** initialize timeline bar */
@ -58,7 +59,8 @@ class TimeMachineDialog : public Dialog
{ {
kTimeline = 'TMtl', kTimeline = 'TMtl',
kToggle = 'TMtg', kToggle = 'TMtg',
kPlay = 'TMpl', kExit = 'TMex',
kPlayBack = 'TMpb',
kRewindAll = 'TMra', kRewindAll = 'TMra',
kRewind10 = 'TMr1', kRewind10 = 'TMr1',
kRewind1 = 'TMre', kRewind1 = 'TMre',
@ -73,7 +75,8 @@ class TimeMachineDialog : public Dialog
TimeLineWidget* myTimeline{nullptr}; TimeLineWidget* myTimeline{nullptr};
ButtonWidget* myToggleWidget{nullptr}; ButtonWidget* myToggleWidget{nullptr};
ButtonWidget* myPlayWidget{nullptr}; ButtonWidget* myExitWidget{ nullptr };
ButtonWidget* myPlayBackWidget{nullptr};
ButtonWidget* myRewindAllWidget{nullptr}; ButtonWidget* myRewindAllWidget{nullptr};
ButtonWidget* myRewind1Widget{nullptr}; ButtonWidget* myRewind1Widget{nullptr};
ButtonWidget* myUnwind1Widget{nullptr}; ButtonWidget* myUnwind1Widget{nullptr};