diff --git a/src/DebugInterface/CDebugInterface.cpp b/src/DebugInterface/CDebugInterface.cpp index f698f09..604aefc 100644 --- a/src/DebugInterface/CDebugInterface.cpp +++ b/src/DebugInterface/CDebugInterface.cpp @@ -619,6 +619,14 @@ void CDebugInterface::StepOverSubroutine() bool CDebugInterface::RunEmulationForOneFrame() { + return RunEmulationForFrames(1); +} + +bool CDebugInterface::RunEmulationForFrames(uint32 numFrames) +{ + if (numFrames < 1) + return false; + if (!isRunning) return false; @@ -642,7 +650,7 @@ bool CDebugInterface::RunEmulationForOneFrame() frameStepRequestGeneration++; requestGeneration = frameStepRequestGeneration; - frameStepTargetFrameNumber = GetEmulationFrameNumber() + 1; + frameStepTargetFrameNumber = GetEmulationFrameNumber() + numFrames; frameStepPending = true; frameStepTargetVSyncReached = false; } @@ -650,7 +658,9 @@ bool CDebugInterface::RunEmulationForOneFrame() SetDebugMode(DEBUGGER_MODE_RUNNING); bool frameCompleted = false; - for (int tries = 0; tries < 100; tries++) + // wait budget scales with the requested frame count (50 ms per try; a realtime PAL frame is 20 ms) + const int maxTries = 100 + (int)numFrames * 2; + for (int tries = 0; tries < maxTries; tries++) { std::unique_lock lock(frameStepMutex); frameCompleted = frameStepCV.wait_for(lock, std::chrono::milliseconds(50), [this, requestGeneration]() { diff --git a/src/DebugInterface/CDebugInterface.h b/src/DebugInterface/CDebugInterface.h index a297814..2928245 100644 --- a/src/DebugInterface/CDebugInterface.h +++ b/src/DebugInterface/CDebugInterface.h @@ -221,6 +221,7 @@ class CDebugInterface virtual void StepOneCycle(); virtual void StepOverSubroutine(); virtual bool RunEmulationForOneFrame(); + virtual bool RunEmulationForFrames(uint32 numFrames); virtual void RunContinueEmulation(); diff --git a/src/Emulators/vice/ViceInterface/CDebugInterfaceVice.cpp b/src/Emulators/vice/ViceInterface/CDebugInterfaceVice.cpp index b915d82..5b914ee 100644 --- a/src/Emulators/vice/ViceInterface/CDebugInterfaceVice.cpp +++ b/src/Emulators/vice/ViceInterface/CDebugInterfaceVice.cpp @@ -1317,11 +1317,20 @@ bool CDebugInterfaceVice::KeyboardDown(uint32 mtKeyCode) return false; } + C64KeyMap *keyMap = C64KeyMapGetDefault(); + if (keyMap == NULL) + return false; + + C64KeyCode *key = keyMap->FindKeyCode(mtKeyCode); + if (key == NULL) + return false; + CDebugInterfaceViceTaskKeyboardEvent *task = - new CDebugInterfaceViceTaskKeyboardEvent(this, DEBUGGER_EVENT_BUTTON_DOWN, mtKeyCode); + new CDebugInterfaceViceTaskKeyboardEvent(this, DEBUGGER_EVENT_BUTTON_DOWN, mtKeyCode, + key->matrixRow, key->matrixCol, key->shift); AddCpuDebugInterruptTask(task); c64d_vice_input_tasks_flag = 1; - return false; + return true; } bool CDebugInterfaceVice::KeyboardUp(uint32 mtKeyCode) @@ -1335,11 +1344,20 @@ bool CDebugInterfaceVice::KeyboardUp(uint32 mtKeyCode) return false; } + C64KeyMap *keyMap = C64KeyMapGetDefault(); + if (keyMap == NULL) + return false; + + C64KeyCode *key = keyMap->FindKeyCode(mtKeyCode); + if (key == NULL) + return false; + CDebugInterfaceViceTaskKeyboardEvent *task = - new CDebugInterfaceViceTaskKeyboardEvent(this, DEBUGGER_EVENT_BUTTON_UP, mtKeyCode); + new CDebugInterfaceViceTaskKeyboardEvent(this, DEBUGGER_EVENT_BUTTON_UP, mtKeyCode, + key->matrixRow, key->matrixCol, key->shift); AddCpuDebugInterruptTask(task); c64d_vice_input_tasks_flag = 1; - return false; + return true; } void CDebugInterfaceVice::JoystickDown(int port, uint32 axis) diff --git a/src/Emulators/vice/ViceInterface/CDebugInterfaceViceTasks.cpp b/src/Emulators/vice/ViceInterface/CDebugInterfaceViceTasks.cpp index 3592cf7..9390117 100644 --- a/src/Emulators/vice/ViceInterface/CDebugInterfaceViceTasks.cpp +++ b/src/Emulators/vice/ViceInterface/CDebugInterfaceViceTasks.cpp @@ -10,6 +10,12 @@ extern "C" { void c64d_joystick_key_up(int key, unsigned int joyport); int keyboard_key_pressed(signed long key); int keyboard_key_released(signed long key); + int keyboard_key_pressed_matrix(int row, int column, int shift); + int keyboard_key_released_matrix(int row, int column, int shift); + int keyboard_set_latch_keyarr(int row, int col, int value); + void keyboard_set_keyarr_any(int row, int col, int value); + void c64d_keyboard_key_down_latch(); + void c64d_keyboard_key_up_latch(); } CDebugInterfaceViceTaskJoystickEvent::CDebugInterfaceViceTaskJoystickEvent( @@ -83,11 +89,15 @@ void CDebugInterfaceViceTaskJoystickEvent::ExecuteTask() } CDebugInterfaceViceTaskKeyboardEvent::CDebugInterfaceViceTaskKeyboardEvent( - CDebugInterfaceVice *debugInterface, u8 buttonState, u32 mtKeyCode) + CDebugInterfaceVice *debugInterface, u8 buttonState, u32 mtKeyCode, + int matrixRow, int matrixCol, int shift) { this->debugInterface = debugInterface; this->buttonState = buttonState; this->mtKeyCode = mtKeyCode; + this->matrixRow = matrixRow; + this->matrixCol = matrixCol; + this->shift = shift; } void CDebugInterfaceViceTaskKeyboardEvent::ExecuteTask() @@ -100,13 +110,34 @@ void CDebugInterfaceViceTaskKeyboardEvent::ExecuteTask() inputEventsBuffer->PutU8(buttonState); } + // press/release via the resolved matrix position — same sequence the virtual keyboard + // uses (CViewC64KeyMap::SelectKey); the c64d_*_latch calls latch immediately, without + // the randomized KEYBOARD_RAND alarm, so measurements stay cycle-deterministic if (buttonState == DEBUGGER_EVENT_BUTTON_DOWN) { - keyboard_key_pressed((signed long)mtKeyCode); + if (matrixRow < 0) + { + keyboard_set_keyarr_any(matrixRow, matrixCol, 1); + } + else + { + keyboard_key_pressed_matrix(matrixRow, matrixCol, shift); + keyboard_set_latch_keyarr(matrixRow, matrixCol, 1); + c64d_keyboard_key_down_latch(); + } } else { - keyboard_key_released((signed long)mtKeyCode); + if (matrixRow < 0) + { + keyboard_set_keyarr_any(matrixRow, matrixCol, 0); + } + else + { + keyboard_key_released_matrix(matrixRow, matrixCol, shift); + keyboard_set_latch_keyarr(matrixRow, matrixCol, 0); + c64d_keyboard_key_up_latch(); + } } } diff --git a/src/Emulators/vice/ViceInterface/CDebugInterfaceViceTasks.h b/src/Emulators/vice/ViceInterface/CDebugInterfaceViceTasks.h index 2c115b4..4be5e5b 100644 --- a/src/Emulators/vice/ViceInterface/CDebugInterfaceViceTasks.h +++ b/src/Emulators/vice/ViceInterface/CDebugInterfaceViceTasks.h @@ -21,12 +21,17 @@ class CDebugInterfaceViceTaskJoystickEvent : public CDebugInterfaceTask class CDebugInterfaceViceTaskKeyboardEvent : public CDebugInterfaceTask { public: - CDebugInterfaceViceTaskKeyboardEvent(CDebugInterfaceVice *debugInterface, u8 buttonState, u32 mtKeyCode); + CDebugInterfaceViceTaskKeyboardEvent(CDebugInterfaceVice *debugInterface, u8 buttonState, u32 mtKeyCode, + int matrixRow, int matrixCol, int shift); virtual void ExecuteTask(); CDebugInterfaceVice *debugInterface; u8 buttonState; u32 mtKeyCode; + // C64 keyboard matrix position resolved from C64KeyMap (matrixRow < 0 = special key, e.g. RESTORE) + int matrixRow; + int matrixCol; + int shift; }; class CDebugInterfaceViceTaskReset : public CDebugInterfaceTask diff --git a/src/Remote/CDebuggerServerApi.cpp b/src/Remote/CDebuggerServerApi.cpp index cbc7c42..8974220 100644 --- a/src/Remote/CDebuggerServerApi.cpp +++ b/src/Remote/CDebuggerServerApi.cpp @@ -144,6 +144,37 @@ void CDebuggerServerApi::RegisterEndpoints(CDebuggerServer *server) return server->PrepareResult(HTTP_OK, token, json(), NULL, 0); }); + // Deterministic replay: run exactly N frames then pause. Blocks until finished, + // so the reply arriving means the frames have already run (frame = final counter). + sprintf(buf, "%s/run/frames", plat); + RegisterEndpoint(server, buf, plat, "control", "Run exactly N frames then pause (blocks until finished; count 1..100000)", + [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* + { + int count = 1; + if (params.is_object() && params.contains("count")) + { + count = params.at("count").get(); + } + if (count < 1 || count > 100000) + { + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } + if (debugInterface->GetDebugMode() != DEBUGGER_MODE_PAUSED) + { + json errorJson; + errorJson["error"] = "machine must be paused; current+N would race the emulation thread"; + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, errorJson, NULL, 0); + } + unsigned int frameStart = debuggerApi->GetEmulationFrameNumber(); + bool completed = debugInterface->RunEmulationForFrames((uint32)count); + json result; + result["completed"] = completed; + result["count"] = count; + result["frameStart"] = frameStart; + result["frame"] = debuggerApi->GetEmulationFrameNumber(); + return server->PrepareResult(completed ? HTTP_OK : HTTP_NOT_ACCEPTABLE, token, result, NULL, 0); + }); + sprintf(buf, "%s/step/cycle", plat); RegisterEndpoint(server, buf, plat, "cpu", "Step one CPU cycle", [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* @@ -319,18 +350,34 @@ void CDebuggerServerApi::RegisterEndpoints(CDebuggerServer *server) RegisterEndpoint(server, buf, plat, "input", "Send key-down event", [this, server](const string token, json params, unsigned char* binaryData, int binaryDataSize) -> vector* { + if (!params.is_object() || !params.contains("keyCode") || !params.at("keyCode").is_number_integer()) + { + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } int mtKeyCode = params.at("keyCode").get(); + debugInterface->LockIoMutex(); bool res = debuggerApi->KeyboardDown(mtKeyCode); - return server->PrepareResult(res ? HTTP_OK : HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + debugInterface->UnlockIoMutex(); + json result; + result["queued"] = res; + return server->PrepareResult(res ? HTTP_ACCEPTED : HTTP_NOT_ACCEPTABLE, token, result, NULL, 0); }); sprintf(buf, "%s/input/key/up", plat); RegisterEndpoint(server, buf, plat, "input", "Send key-up event", [this, server](const string token, json params, unsigned char* binaryData, int binaryDataSize) -> vector* { + if (!params.is_object() || !params.contains("keyCode") || !params.at("keyCode").is_number_integer()) + { + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } int mtKeyCode = params.at("keyCode").get(); + debugInterface->LockIoMutex(); bool res = debuggerApi->KeyboardUp(mtKeyCode); - return server->PrepareResult(res ? HTTP_OK : HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + debugInterface->UnlockIoMutex(); + json result; + result["queued"] = res; + return server->PrepareResult(res ? HTTP_ACCEPTED : HTTP_NOT_ACCEPTABLE, token, result, NULL, 0); }); sprintf(buf, "%s/input/joystick/down", plat); @@ -941,11 +988,18 @@ void CDebuggerServerApi::RegisterEndpoints(CDebuggerServer *server) RegisterEndpoint(server, buf, plat, "input", "Press a keyboard key (keyCode: MTKEY/SDL keycode; ASCII for printable chars)", [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { + if (!params.is_object() || !params.contains("keyCode") || !params.at("keyCode").is_number_integer()) + { + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } uint32_t keyCode = params.at("keyCode").get(); - debugInterface->KeyboardDown(keyCode); + debugInterface->LockIoMutex(); + bool queued = debugInterface->KeyboardDown(keyCode); + debugInterface->UnlockIoMutex(); json result; result["keyCode"] = keyCode; - return server->PrepareResult(HTTP_OK, token, result, NULL, 0); + result["queued"] = queued; + return server->PrepareResult(queued ? HTTP_ACCEPTED : HTTP_NOT_ACCEPTABLE, token, result, NULL, 0); }); // Keyboard key release @@ -953,11 +1007,18 @@ void CDebuggerServerApi::RegisterEndpoints(CDebuggerServer *server) RegisterEndpoint(server, buf, plat, "input", "Release a keyboard key (keyCode: MTKEY/SDL keycode; ASCII for printable chars)", [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { + if (!params.is_object() || !params.contains("keyCode") || !params.at("keyCode").is_number_integer()) + { + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } uint32_t keyCode = params.at("keyCode").get(); - debugInterface->KeyboardUp(keyCode); + debugInterface->LockIoMutex(); + bool queued = debugInterface->KeyboardUp(keyCode); + debugInterface->UnlockIoMutex(); json result; result["keyCode"] = keyCode; - return server->PrepareResult(HTTP_OK, token, result, NULL, 0); + result["queued"] = queued; + return server->PrepareResult(queued ? HTTP_ACCEPTED : HTTP_NOT_ACCEPTABLE, token, result, NULL, 0); }); // Emulator start — starts the emulation thread for this platform