Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ set(BRIDGE_SOURCES
src/input/DemoInputProvider.cpp
src/input/BrakeInput.cpp
src/input/KeyHoldBridge.cpp
src/input/KeyboardInputProvider.cpp
src/input/EngineInputTarget.cpp
src/input/DemoControlsTarget.cpp
)

# Platform-specific audio hardware and factory
Expand Down Expand Up @@ -575,6 +578,24 @@ if(BUILD_BRIDGE_TESTS)
add_test(NAME demo_input_tests COMMAND demo_input_tests --gtest_color=yes)
endif()

# Keyboard consolidation tests - consolidated KeyboardInputProvider + EngineInputTarget
add_executable(keyboard_consolidation_tests
test/input/KeyboardInputProviderTest.cpp
)
target_include_directories(keyboard_consolidation_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/test
)
target_link_libraries(keyboard_consolidation_tests
PRIVATE
GTest::gtest_main
engine-sim-bridge
)
target_compile_options(keyboard_consolidation_tests PRIVATE -Werror -Wall -Wextra -Wno-unused-parameter -Wno-mismatched-tags)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
add_test(NAME keyboard_consolidation_tests COMMAND keyboard_consolidation_tests --gtest_color=yes)
endif()

# Twin scenario tests - Phase 1 acceptance criteria tests
add_executable(twin_scenario_tests
test/scenarios/AccelerationScenarioTest.cpp
Expand Down
41 changes: 41 additions & 0 deletions include/input/DemoControlsTarget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// DemoControlsTarget.h - Adapts IDemoControls to IKeyActionTarget
// Used in demo mode: consolidated KeyboardInputProvider dispatches to this target,
// which translates actions into IDemoControls calls.

#ifndef DEMO_CONTROLS_TARGET_H
#define DEMO_CONTROLS_TARGET_H

#include "input/IKeyActionTarget.h"

namespace input {

class IDemoControls;
class IInputProvider;

class DemoControlsTarget : public IKeyActionTarget {
public:
explicit DemoControlsTarget(IDemoControls* controls);

void setDemoProvider(IInputProvider* provider);

void quit() override;
void setThrottle(double level) override;
void adjustThrottle(double delta) override;
void shiftUp() override;
void shiftDown() override;
void toggleIgnition() override;
void setStarter() override;
void setBrake(double level) override;

EngineInput buildEngineInput(double dt) override;

private:
IDemoControls* controls_;
IInputProvider* demoProvider_ = nullptr;
double currentThrottle_ = 0.0;
bool starterPressed_ = false;
};

} // namespace input

#endif // DEMO_CONTROLS_TARGET_H
52 changes: 52 additions & 0 deletions include/input/EngineInputTarget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// EngineInputTarget.h - Standard-mode key action target
// Owns throttle, gear, ignition, starter, brake, dyno state.
// Translates IKeyActionTarget calls into EngineInput state.

#ifndef ENGINE_INPUT_TARGET_H
#define ENGINE_INPUT_TARGET_H

#include "input/IKeyActionTarget.h"
#include "io/IInputProvider.h"

namespace input {

class EngineInputTarget : public IKeyActionTarget {
public:
EngineInputTarget();

void quit() override;
void setThrottle(double level) override;
void adjustThrottle(double delta) override;
void shiftUp() override;
void shiftDown() override;
void toggleIgnition() override;
void setStarter() override;
void cyclePreset() override;
void adjustDynoTorque(double delta) override;
void releaseDynoTorque() override;
void setBrake(double level) override;

// Build the EngineInput struct from current state.
// Resets one-shot flags (gearDelta, starter, presetCycle).
EngineInput buildInput();

EngineInput buildEngineInput(double dt) override { (void)dt; return buildInput(); }

bool quitRequested() const { return quitRequested_; }

private:
double throttle_;
bool ignition_;
bool starterButton_;
int gearDelta_;
int gearSelector_;
double dynoTorqueScale_;
double brakeLevel_;
bool presetCycle_;
bool quitRequested_;
bool throttleTouched_; // true if set/adjusted this frame
};

} // namespace input

#endif // ENGINE_INPUT_TARGET_H
33 changes: 33 additions & 0 deletions include/input/IKeyActionTarget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// IKeyActionTarget.h - Strategy interface for key action routing
// KeyboardInputProvider dispatches decoded key actions to this interface.
// Concrete targets: EngineInputTarget (standard), DemoControlsTarget (demo mode).

#ifndef I_KEY_ACTION_TARGET_H
#define I_KEY_ACTION_TARGET_H

#include "io/IInputProvider.h"

namespace input {

class IKeyActionTarget {
public:
virtual ~IKeyActionTarget() = default;

virtual void quit() {}
virtual void setThrottle(double) {}
virtual void adjustThrottle(double) {}
virtual void shiftUp() {}
virtual void shiftDown() {}
virtual void toggleIgnition() {}
virtual void setStarter() {}
virtual void cyclePreset() {}
virtual void adjustDynoTorque(double) {}
virtual void releaseDynoTorque() {}
virtual void setBrake(double) {}

virtual EngineInput buildEngineInput(double dt) { (void)dt; return {}; }
};

} // namespace input

#endif // I_KEY_ACTION_TARGET_H
15 changes: 15 additions & 0 deletions include/input/IKeyboardInput.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// IKeyboardInput.h - Abstract keyboard input reader
// Decouples key reading from terminal specifics for testability.

#ifndef I_KEYBOARD_INPUT_H
#define I_KEYBOARD_INPUT_H

class IKeyboardInput {
public:
virtual ~IKeyboardInput() = default;

// Read one key. Returns key code, or -1 if no key available.
virtual int getKey() = 0;
};

#endif // I_KEYBOARD_INPUT_H
7 changes: 7 additions & 0 deletions include/input/KeyHoldBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <unordered_map>
#include <functional>
#include <initializer_list>

namespace input {

Expand Down Expand Up @@ -39,6 +40,12 @@ class KeyHoldBridge {
// True for one frame when key transitions from down → up (timeout)
bool isKeyReleased(int key) const;

// True on press OR OS repeat — the "hold to ramp" pattern
bool isKeyActive(int key) const;

// True if any of the given keys is active
bool isKeyActiveAny(std::initializer_list<int> keys) const;

private:
struct KeyState {
double timeSinceEvent = 0.0;
Expand Down
48 changes: 48 additions & 0 deletions include/input/KeyboardInputProvider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// KeyboardInputProvider.h - Consolidated keyboard input provider
// Uses IKeyActionTarget (Strategy) to route key actions to standard or demo mode.
// Uses KeyHoldBridge for key state tracking (pressed, repeating, down, released).

#ifndef KEYBOARD_INPUT_PROVIDER_CONSOLIDATED_H
#define KEYBOARD_INPUT_PROVIDER_CONSOLIDATED_H

#include "input/IKeyActionTarget.h"
#include "input/KeyHoldBridge.h"
#include "io/IInputProvider.h"

#include <memory>

class IKeyboardInput;
class ISimulatorSession;

namespace input {

class KeyboardInputProvider : public IInputProvider {
public:
KeyboardInputProvider(
std::unique_ptr<IKeyboardInput> keyboard,
IKeyActionTarget* target);

~KeyboardInputProvider() override;

bool Initialize() override;
void Shutdown() override;
bool IsConnected() const override;
EngineInput OnUpdateSimulation(double dt) override;
std::string GetProviderName() const override;
std::string GetLastError() const override;

void setSession(ISimulatorSession* session);

private:
void processKeys(double dt);

std::unique_ptr<IKeyboardInput> keyboard_;
IKeyActionTarget* target_;
KeyHoldBridge keyHold_;
ISimulatorSession* session_ = nullptr;
std::string lastError_;
};

} // namespace input

#endif // KEYBOARD_INPUT_PROVIDER_CONSOLIDATED_H
58 changes: 50 additions & 8 deletions include/simulator/ScriptCompileHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,53 @@ struct ScriptCompileTarget {
std::filesystem::path wrapperPath;
std::string relativeImport;
bool insideAssets = false;

~ScriptCompileTarget() {
cleanup();
}

// Non-copyable to prevent double-cleanup
ScriptCompileTarget() = default;
ScriptCompileTarget(const ScriptCompileTarget&) = delete;
ScriptCompileTarget& operator=(const ScriptCompileTarget&) = delete;
ScriptCompileTarget(ScriptCompileTarget&& o) noexcept
: simDir(std::move(o.simDir))
, assetsDir(std::move(o.assetsDir))
, compileTarget(std::move(o.compileTarget))
, tempScriptPath(std::move(o.tempScriptPath))
, wrapperPath(std::move(o.wrapperPath))
, relativeImport(std::move(o.relativeImport))
, insideAssets(o.insideAssets) {
o.tempScriptPath.clear();
o.wrapperPath.clear();
}
ScriptCompileTarget& operator=(ScriptCompileTarget&& o) noexcept {
if (this != &o) {
cleanup();
simDir = std::move(o.simDir);
assetsDir = std::move(o.assetsDir);
compileTarget = std::move(o.compileTarget);
tempScriptPath = std::move(o.tempScriptPath);
wrapperPath = std::move(o.wrapperPath);
relativeImport = std::move(o.relativeImport);
insideAssets = o.insideAssets;
o.tempScriptPath.clear();
o.wrapperPath.clear();
}
return *this;
}

private:
void cleanup() {
if (!wrapperPath.empty()) {
std::filesystem::remove(wrapperPath);
wrapperPath.clear();
}
if (!tempScriptPath.empty()) {
std::filesystem::remove(tempScriptPath);
tempScriptPath.clear();
}
}
};

inline std::filesystem::path findEngineSimRoot(
Expand Down Expand Up @@ -136,13 +183,8 @@ inline ScriptCompileTarget prepareScriptCompileTarget(
return target;
}

inline void cleanupScriptCompileTarget(const ScriptCompileTarget& target) {
if (!target.wrapperPath.empty()) {
std::filesystem::remove(target.wrapperPath);
}
if (!target.tempScriptPath.empty()) {
std::filesystem::remove(target.tempScriptPath);
}
}
// Cleanup is handled automatically by ScriptCompileTarget's destructor (RAII).
// This function is retained for backwards compatibility with existing callers.
inline void cleanupScriptCompileTarget(const ScriptCompileTarget& /*target*/) {}

} // namespace script_compile_helpers
63 changes: 63 additions & 0 deletions src/input/DemoControlsTarget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// DemoControlsTarget.cpp - Adapts IDemoControls to IKeyActionTarget

#include "input/DemoControlsTarget.h"
#include "input/IDemoControls.h"
#include "io/IInputProvider.h"

#include <algorithm>

namespace input {

DemoControlsTarget::DemoControlsTarget(IDemoControls* controls)
: controls_(controls) {
}

void DemoControlsTarget::setDemoProvider(IInputProvider* provider) {
demoProvider_ = provider;
}

void DemoControlsTarget::quit() {
if (controls_) controls_->requestExit();
}

void DemoControlsTarget::setThrottle(double level) {
currentThrottle_ = level;
if (controls_) controls_->setThrottle(level);
}

void DemoControlsTarget::adjustThrottle(double delta) {
currentThrottle_ = std::clamp(currentThrottle_ + delta, 0.0, 1.0);
if (controls_) controls_->setThrottle(currentThrottle_);
}

void DemoControlsTarget::shiftUp() {
if (controls_) controls_->shiftUp();
}

void DemoControlsTarget::shiftDown() {
if (controls_) controls_->shiftDown();
}

void DemoControlsTarget::toggleIgnition() {
if (controls_) controls_->toggleIgnition();
}

void DemoControlsTarget::setStarter() {
starterPressed_ = true;
}

void DemoControlsTarget::setBrake(double level) {
if (controls_) controls_->setBrake(level);
}

EngineInput DemoControlsTarget::buildEngineInput(double dt) {
if (demoProvider_) {
auto input = demoProvider_->OnUpdateSimulation(dt);
input.starterButton = starterPressed_;
starterPressed_ = false;
return input;
}
return {};
}

} // namespace input
Loading