For consistency with CS code, move to an event driven SDL3 model. (Probably part 1)

This commit is contained in:
Adrian Conlon committed 2026-08-26 13:50:36 +01:00
1 parent 3a64cc74d1
commit 7fcf123248
4 files changed
+106 -96

No files matched your search

+8 -9
View File
@@ -19,11 +19,17 @@ namespace Gaming {
class Game : public EightBit::Device {
public:
Game(bool verbose = false);
Game(SDL_LogPriority logging = SDL_LOG_PRIORITY_WARN);
virtual ~Game();
virtual void runLoop();
virtual SDL_AppResult runFrame();
virtual SDL_AppResult handleEvent(SDL_Event& e);
void raisePOWER() noexcept override;
void lowerPOWER() noexcept override;
virtual void initialise();
virtual void terminate();
protected:
[[nodiscard]] virtual float fps() const noexcept = 0;
@@ -39,11 +45,8 @@ namespace Gaming {
[[nodiscard]] virtual std::string title() const noexcept = 0;
virtual void handleEvents();
virtual void update();
virtual void draw();
virtual bool maybeSynchronise(); // true, if manual synchronisation required
virtual void synchronise();
virtual void runRasterLines() {};
virtual void runVerticalBlank() {}
@@ -88,10 +91,6 @@ namespace Gaming {
SDL_PixelFormat m_pixelType = SDL_PIXELFORMAT_ARGB8888;
bool m_vsync = false;
Uint64 m_performanceFrequency = 0;
double m_targetFrameTime = 0.0; // in seconds
Uint64 m_frameStartTime = 0; // in performance frequency
Uint64 m_frameEndTime = 0; // in performance frequency
std::map<int, std::shared_ptr<GameController>> m_gameControllers;
+15 -3
View File
@@ -5,10 +5,19 @@
#include <sstream>
namespace Gaming {
class Wrapper final {
class Wrapper final : public EightBit::Device {
using base = Device;
public:
Wrapper(bool verbose = false);
~Wrapper();
Wrapper(SDL_LogPriority logging = SDL_LOG_PRIORITY_WARN);
~Wrapper() = default;
void raisePOWER() noexcept final;
void lowerPOWER() noexcept final;
void initialise();
void terminate();
static void throwException(const std::string& failure) {
std::ostringstream output;
@@ -24,5 +33,8 @@ namespace Gaming {
static void maybeThrowException(void* handle, const std::string& failure) {
maybeThrowException(handle != nullptr, failure);
}
private:
SDL_LogPriority m_logging;
};
}
+61 -76
View File
@@ -3,10 +3,12 @@
#include "../inc/GameController.h"
#include <format>
namespace Gaming {
Game::Game(bool verbose /* = false */)
: m_wrapper(verbose) {}
Game::Game(SDL_LogPriority logging /* = SDL_LOG_PRIORITY_WARN */)
: m_wrapper(logging) {}
Game::~Game() {}
@@ -27,8 +29,18 @@ int Game::displayHeight() const noexcept {
}
void Game::raisePOWER() noexcept {
Device::raisePOWER();
initialise();
}
void Game::lowerPOWER() noexcept {
terminate();
Device::lowerPOWER();
}
void Game::initialise() {
m_wrapper.raisePOWER();
m_window.reset(::SDL_CreateWindow(title().c_str(), windowWidth(), windowHeight(), 0), ::SDL_DestroyWindow);
Wrapper::maybeThrowException(m_window.get(), "Unable to create window");
@@ -59,14 +71,24 @@ void Game::raisePOWER() noexcept {
}
}
if (!m_vsync) {
SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, "Setting callback rate hint");
const auto success = SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, std::format("{}", fps()).c_str());
Wrapper::maybeThrowException(success, "Unable to set event loop callback rate hint");
}
m_pixelFormat = SDL_GetPixelFormatDetails(m_pixelType);
Wrapper::maybeThrowException(m_pixelFormat, "Unable to obtain pixel format details");
configureBackground();
createBitmapTexture();
}
m_performanceFrequency = ::SDL_GetPerformanceFrequency();
m_targetFrameTime = 1.0 / fps();
void Game::terminate() {
m_bitmapTexture.reset();
m_renderer.reset();
m_window.reset();
m_wrapper.lowerPOWER();
}
void Game::configureBackground() const {
@@ -79,54 +101,48 @@ void Game::createBitmapTexture() {
Wrapper::maybeThrowException(m_bitmapTexture.get(), "Unable to create bitmap texture");
}
void Game::runLoop() {
while (powered()) {
update();
draw();
maybeSynchronise();
}
SDL_AppResult Game::runFrame() {
update();
draw();
return SDL_APP_CONTINUE;
}
void Game::update() {
m_frameStartTime = ::SDL_GetPerformanceCounter();
handleEvents();
runVerticalBlank();
runRasterLines();
}
void Game::handleEvents() {
::SDL_Event e;
while (::SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_EVENT_QUIT:
lowerPOWER();
break;
case SDL_EVENT_KEY_DOWN:
handleKeyDown(e.key.key);
break;
case SDL_EVENT_KEY_UP:
handleKeyUp(e.key.key);
break;
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
handleJoyButtonDown(e.jbutton);
break;
case SDL_EVENT_JOYSTICK_BUTTON_UP:
handleJoyButtonUp(e.jbutton);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
handleGamepadButtonDown(e.gbutton);
break;
case SDL_EVENT_GAMEPAD_BUTTON_UP:
handleGamepadButtonUp(e.gbutton);
break;
case SDL_EVENT_GAMEPAD_ADDED:
addGamepad(e);
break;
case SDL_EVENT_GAMEPAD_REMOVED:
removeGamepad(e);
break;
}
SDL_AppResult Game::handleEvent(SDL_Event& e) {
switch (e.type) {
case SDL_EVENT_QUIT:
return SDL_APP_SUCCESS;
break;
case SDL_EVENT_KEY_DOWN:
handleKeyDown(e.key.key);
break;
case SDL_EVENT_KEY_UP:
handleKeyUp(e.key.key);
break;
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
handleJoyButtonDown(e.jbutton);
break;
case SDL_EVENT_JOYSTICK_BUTTON_UP:
handleJoyButtonUp(e.jbutton);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
handleGamepadButtonDown(e.gbutton);
break;
case SDL_EVENT_GAMEPAD_BUTTON_UP:
handleGamepadButtonUp(e.gbutton);
break;
case SDL_EVENT_GAMEPAD_ADDED:
addGamepad(e);
break;
case SDL_EVENT_GAMEPAD_REMOVED:
removeGamepad(e);
break;
}
return SDL_APP_CONTINUE;
}
void Game::draw() {
@@ -135,37 +151,6 @@ void Game::draw() {
displayTexture();
}
bool Game::maybeSynchronise() {
const bool synchronising = !m_vsync;
if (synchronising)
synchronise();
return synchronising;
}
void Game::synchronise() {
m_frameEndTime = ::SDL_GetPerformanceCounter();
const auto frameTime = m_frameEndTime - m_frameStartTime; // In performance frequency
::SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Frame time (ticks): %ld", frameTime);
const auto elapsedFrameTime = double(frameTime) / m_performanceFrequency; // In seconds
::SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Frame time (seconds): %f", elapsedFrameTime);
const auto gap = m_targetFrameTime - elapsedFrameTime; // in seconds
::SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Timing gap (seconds): %f", gap);
if (gap > 0) {
const auto delay = Uint32(gap * 1000.0);
::SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Delay (ticks): %d", delay);
SDL_Delay(delay);
}
if (gap < 0) {
::SDL_LogWarn(SDL_LOG_CATEGORY_RENDER, "Running slowly");
}
}
void Game::removeGamepad(SDL_Event& e) {
const auto which = e.gdevice.which;
const auto found = m_gameControllers.find(which);
+22 -8
View File
@@ -3,13 +3,27 @@
using namespace Gaming;
Wrapper::Wrapper(bool verbose /* = false */) {
const auto success = ::SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC);
maybeThrowException(success, "Unable to initialise SDL library");
const auto priority = verbose ? SDL_LogPriority::SDL_LOG_PRIORITY_VERBOSE : SDL_LogPriority::SDL_LOG_PRIORITY_ERROR;
::SDL_SetLogPriorities(priority);
Wrapper::Wrapper(SDL_LogPriority logging /* = SDL_LOG_PRIORITY_WARN */)
: m_logging(logging) {}
void Wrapper::raisePOWER() noexcept {
base::raisePOWER();
initialise();
}
Wrapper::~Wrapper() {
::SDL_Quit();
}
void Wrapper::lowerPOWER() noexcept {
terminate();
base::lowerPOWER();
}
void Wrapper::initialise() {
const auto success = ::SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC);
maybeThrowException(success, "Unable to initialise SDL library");
::SDL_SetLogPriorities(m_logging);
::SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL library initialised");
}
void Wrapper::terminate() {
::SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL library terminating");
::SDL_Quit();
}