diff options
Diffstat (limited to 'src/control')
-rw-r--r-- | src/control/EventBus.hpp | 58 | ||||
-rw-r--r-- | src/control/InputHandler.hpp | 37 | ||||
-rw-r--r-- | src/control/MapContext.cpp | 74 | ||||
-rw-r--r-- | src/control/MapContext.hpp | 15 | ||||
-rw-r--r-- | src/control/RPGEdit.cpp | 95 | ||||
-rw-r--r-- | src/control/RPGEdit.hpp | 17 | ||||
-rw-r--r-- | src/control/TimeProvider.hpp (renamed from src/control/Event.hpp) | 24 |
7 files changed, 223 insertions, 97 deletions
diff --git a/src/control/EventBus.hpp b/src/control/EventBus.hpp index ae4603f..7284883 100644 --- a/src/control/EventBus.hpp +++ b/src/control/EventBus.hpp @@ -27,12 +27,16 @@ #pragma once -#include "Event.hpp" +#include "TimeProvider.hpp" +#include <chrono> +#include <condition_variable> +#include <cstdint> #include <deque> +#include <functional> #include <limits> -#include <memory> #include <queue> +#include <thread> namespace RPGEdit { @@ -40,35 +44,59 @@ namespace RPGEdit { namespace Control { class EventBus { -private: - typedef std::pair<uint64_t, std::shared_ptr<Event>> event_entry; +public: + typedef std::function<void ()> Event; + typedef std::pair<uint64_t, Event> EventEntry; - static bool compare_events(const event_entry &e1, const event_entry &e2) { +private: + static bool compare_events(const EventEntry &e1, const EventEntry &e2) { return e1.first > e2.first; } - std::priority_queue<event_entry, std::deque<event_entry>, bool (*)(const event_entry &, const event_entry &)> events; + std::priority_queue<EventEntry, std::deque<EventEntry>, bool (*)(const EventEntry &, const EventEntry &)> events; + + std::mutex mutex; + std::condition_variable cond; public: EventBus() : events(compare_events) { } - void enqueue(const std::shared_ptr<Event> &event, uint64_t time) { - events.push(std::pair<uint64_t, std::shared_ptr<Event>>(time, event)); + void enqueue(const Event &event, uint64_t time) { + std::lock_guard<std::mutex> lock(mutex); + events.push(EventEntry(time, event)); + cond.notify_one(); } - std::pair<uint64_t, std::shared_ptr<Event>> get(uint64_t time) { - if (events.empty()) - return std::make_pair<uint64_t, std::shared_ptr<Event>>(std::numeric_limits<uint64_t>::max(), nullptr); + EventEntry get(TimeProvider *timeProvider) { + std::unique_lock<std::mutex> lock(mutex); + + while (true) { + if (events.empty()) { + cond.wait(lock); + continue; + } - std::pair<uint64_t, std::shared_ptr<Event>> top = events.top(); + EventEntry top = events.top(); + uint64_t time = timeProvider->now(); + + if (top.first > time) { + cond.wait_for(lock, std::chrono::milliseconds(top.first - time)); + continue; + } - if (top.first <= time) { events.pop(); return top; } - else - return std::pair<uint64_t, std::shared_ptr<Event>>(top.first, std::shared_ptr<Event>()); + } + + uint64_t peek() { + std::lock_guard<std::mutex> lock(mutex); + + if (events.empty()) + return std::numeric_limits<uint64_t>::max(); + + return events.top().first; } }; diff --git a/src/control/InputHandler.hpp b/src/control/InputHandler.hpp index eba94d1..1a34982 100644 --- a/src/control/InputHandler.hpp +++ b/src/control/InputHandler.hpp @@ -26,10 +26,10 @@ #pragma once -#include <SDL.h> - #include <cstdint> +#include <functional> #include <unordered_set> +#include <vector> namespace RPGEdit { @@ -37,34 +37,35 @@ namespace RPGEdit { namespace Control { class InputHandler { +public: + typedef std::function<void (uint16_t, bool, uint64_t)> Listener; + private: + std::vector<Listener> listeners; + std::unordered_set<uint16_t> pressedKeys; - std::unordered_set<uint16_t> unhandledKeys; public: - void keyPressed(SDL_Scancode key) { - pressedKeys.insert(key); - unhandledKeys.insert(key); + void registerListener(const Listener &listener) { + listeners.push_back(listener); } - void keyReleased(SDL_Scancode key) { - pressedKeys.erase(key); - } + void keyPressed(uint16_t key, uint64_t time) { + pressedKeys.insert(key); - void keyHandled(SDL_Scancode key) { - unhandledKeys.erase(key); + for (auto &listener : listeners) + listener(key, true, time); } - void resetHandled() { - unhandledKeys.clear(); - } + void keyReleased(uint16_t key, uint64_t time) { + pressedKeys.erase(key); - bool isKeyPressed(SDL_Scancode key) { - return pressedKeys.count(key); + for (auto &listener : listeners) + listener(key, false, time); } - bool isKeyUnhandled(SDL_Scancode key) { - return unhandledKeys.count(key); + bool isKeyPressed(uint16_t key) { + return pressedKeys.count(key); } }; diff --git a/src/control/MapContext.cpp b/src/control/MapContext.cpp index f4dd31e..a981e8a 100644 --- a/src/control/MapContext.cpp +++ b/src/control/MapContext.cpp @@ -31,8 +31,8 @@ namespace RPGEdit { namespace Control { -MapContext::MapContext(ImageLoader *imageLoader0, const std::shared_ptr<const Model::Map> &map0) - : imageLoader(imageLoader0), map(map0) { +MapContext::MapContext(EventBus *eventBus0, InputHandler *inputHandler0, ImageLoader *imageLoader0, const std::shared_ptr<const Model::Map> &map0) + : eventBus(eventBus0), inputHandler(inputHandler0), imageLoader(imageLoader0), map(map0) { const std::vector<std::string> &tileset = map->getTileset(); tiles.resize(tileset.size()); @@ -43,38 +43,62 @@ MapContext::MapContext(ImageLoader *imageLoader0, const std::shared_ptr<const Mo for (const std::shared_ptr<Model::Entity> &entity : mapEntities) entities[entity->getName()] = imageLoader->get("entity/" + entity->getName()); + + inputHandler->registerListener( + [this] (uint16_t key, bool pressed, uint64_t time) { + if (pressed) + keyPressed(key, time); + } + ); } -void MapContext::advance(InputHandler *inputHandler, uint32_t ticks) { - uint64_t totalTicksOld = totalTicks; - totalTicks += ticks; +void MapContext::movePlayer(Model::Direction dir, uint64_t time) { + if (map->getPlayerEntity()->hasTransition()) + return; - unsigned advance = totalTicks - totalTicksOld; + map->getPlayerEntity()->move(dir, time, time+250); - while (advance) { - advance = map->getPlayerEntity().advance(advance); + eventBus->enqueue( + [=] { + map->getPlayerEntity()->finishTransition(); + movePlayerContinue(time+250); + }, + time+250 + ); +} - if (map->getPlayerEntity().hasTransition()) - break; +void MapContext::movePlayerContinue(uint64_t time) { + if (inputHandler->isKeyPressed(SDL_SCANCODE_UP)) + movePlayer(Model::NORTH, time); + else if (inputHandler->isKeyPressed(SDL_SCANCODE_RIGHT)) + movePlayer(Model::EAST, time); + else if (inputHandler->isKeyPressed(SDL_SCANCODE_DOWN)) + movePlayer(Model::SOUTH, time); + else if (inputHandler->isKeyPressed(SDL_SCANCODE_LEFT)) + movePlayer(Model::WEST, time); +} - if (inputHandler->isKeyPressed(SDL_SCANCODE_UP)) { - map->getPlayerEntity().move(Model::NORTH, 250); - } - else if (inputHandler->isKeyPressed(SDL_SCANCODE_RIGHT)) { - map->getPlayerEntity().move(Model::EAST, 250); - } - else if (inputHandler->isKeyPressed(SDL_SCANCODE_DOWN)) { - map->getPlayerEntity().move(Model::SOUTH, 250); - } - else if (inputHandler->isKeyPressed(SDL_SCANCODE_LEFT)) { - map->getPlayerEntity().move(Model::WEST, 250); - } - else { - break; - } + +void MapContext::keyPressed(uint16_t key, uint64_t time) { + switch (key) { + case SDL_SCANCODE_UP: + movePlayer(Model::NORTH, time); + break; + + case SDL_SCANCODE_RIGHT: + movePlayer(Model::EAST, time); + break; + + case SDL_SCANCODE_DOWN: + movePlayer(Model::SOUTH, time); + break; + + case SDL_SCANCODE_LEFT: + movePlayer(Model::WEST, time); } } + } } diff --git a/src/control/MapContext.hpp b/src/control/MapContext.hpp index 26f7e34..446ca10 100644 --- a/src/control/MapContext.hpp +++ b/src/control/MapContext.hpp @@ -26,6 +26,7 @@ #pragma once +#include "EventBus.hpp" #include "ImageLoader.hpp" #include "InputHandler.hpp" #include "../model/Map.hpp" @@ -41,6 +42,8 @@ namespace Control { class MapContext { private: + EventBus *const eventBus; + InputHandler *const inputHandler; ImageLoader *const imageLoader; std::shared_ptr<const Model::Map> map; @@ -50,13 +53,15 @@ private: uint64_t totalTicks = 0; -public: - MapContext(ImageLoader *imageLoader0, const std::shared_ptr<const Model::Map> &map0); + void movePlayer(Model::Direction dir, uint64_t time); + void movePlayerContinue(uint64_t time); + void keyPressed(uint16_t key, uint64_t time); - void advance(InputHandler *inputHandler, unsigned ticks); +public: + MapContext(EventBus *eventBus0, InputHandler *inputHandler0, ImageLoader *imageLoader0, const std::shared_ptr<const Model::Map> &map0); - Model::Position getViewPosition() { - return map->getPlayerEntity().getPosition(); + Model::Position getViewPosition(uint64_t time) { + return map->getPlayerEntity()->getPosition(time); } std::shared_ptr<View::MapView> initView(const std::shared_ptr<View::Window> &window) { diff --git a/src/control/RPGEdit.cpp b/src/control/RPGEdit.cpp index 679796c..0c55a6b 100644 --- a/src/control/RPGEdit.cpp +++ b/src/control/RPGEdit.cpp @@ -28,62 +28,99 @@ #include "MapContext.hpp" #include "../view/MapView.hpp" -#include <SDL.h> +namespace RPGEdit { -#define MIN_FRAME_DELAY 10 +namespace Control { +bool RPGEdit::handleSystemEvent(const SDL_Event &event) { + uint64_t time = timeProvider.now(); -namespace RPGEdit { + switch (event.type) { + case SDL_KEYDOWN: + eventBus.enqueue([=] { inputHandler.keyPressed(event.key.keysym.scancode, time); }, time); + break; -namespace Control { + case SDL_KEYUP: + eventBus.enqueue([=] { inputHandler.keyReleased(event.key.keysym.scancode, time); }, time); + break; -bool RPGEdit::systemIter(unsigned ticks) { - int timeout = 0; + case SDL_QUIT: + return false; + } - SDL_Event event; - if (SDL_WaitEventTimeout(&event, timeout)) { - switch (event.type) { - case SDL_KEYDOWN: - inputHandler.keyPressed(event.key.keysym.scancode); - break; + return true; +} - case SDL_KEYUP: - inputHandler.keyReleased(event.key.keysym.scancode); - break; +void RPGEdit::systemLoop() { + const int MIN_FRAME_DELAY = 10; + + uint32_t lastFrameTicks = SDL_GetTicks(); + + while (true) { + uint32_t diff = SDL_GetTicks() - lastFrameTicks; + int timeout = std::max(MIN_FRAME_DELAY - int(diff), 0); - case SDL_QUIT: - return false; + SDL_Event event; + if (SDL_WaitEventTimeout(&event, timeout)) { + if (!handleSystemEvent(event)) + return; + + continue; } - } - ctx->advance(&inputHandler, ticks); + lastFrameTicks = SDL_GetTicks(); - Model::Position pos = ctx->getViewPosition(); - mapView->render(pos.x, pos.y); + { + std::unique_lock<std::mutex> lock(modelMutex); - return true; + uint64_t time = timeProvider.now(); + + while (time >= handledTime) { + modelCond.wait(lock); + time = timeProvider.now(); + } + + Model::Position pos = ctx->getViewPosition(time); + mapView->render(pos.x, pos.y, time); + } + + SDL_RenderPresent(window->getRenderer()); + } } -void RPGEdit::systemLoop() { - uint32_t ticks1 = SDL_GetTicks(); - uint32_t ticks2 = ticks1; +void RPGEdit::eventLoop() { + while (true) { + EventBus::EventEntry event = eventBus.get(&timeProvider); - while (systemIter(ticks2 - ticks1)) { - ticks1 = ticks2; - ticks2 = SDL_GetTicks(); + if (!event.second) + return; + + { + std::lock_guard<std::mutex> lock(modelMutex); + + event.second(); + + handledTime = eventBus.peek(); + modelCond.notify_one(); + } } } void RPGEdit::run() { std::shared_ptr<Model::Map> map = Model::Map::load("test"); - ctx = std::make_shared<MapContext>(&tileLoader, map); + ctx = std::make_shared<MapContext>(&eventBus, &inputHandler, &tileLoader, map); window = std::make_shared<View::Window>(); mapView = ctx->initView(window); + eventThread = std::thread([this] { eventLoop(); }); + systemLoop(); + + eventBus.enqueue(EventBus::Event(), timeProvider.now()); + eventThread.join(); } } diff --git a/src/control/RPGEdit.hpp b/src/control/RPGEdit.hpp index f045265..8b5e697 100644 --- a/src/control/RPGEdit.hpp +++ b/src/control/RPGEdit.hpp @@ -30,8 +30,10 @@ #include "InputHandler.hpp" #include "MapContext.hpp" +#include <condition_variable> #include <memory> #include <mutex> +#include <thread> namespace RPGEdit { @@ -40,6 +42,8 @@ namespace Control { class RPGEdit { private: + TimeProvider timeProvider; + EventBus eventBus; InputHandler inputHandler; ImageLoader tileLoader; @@ -49,11 +53,20 @@ private: std::shared_ptr<View::Window> window; std::shared_ptr<View::MapView> mapView; - std::mutex modelLock; + std::thread eventThread; + std::mutex modelMutex; + std::condition_variable modelCond; + uint64_t handledTime = std::numeric_limits<uint64_t>::max(); + + void enqueueNow(const EventBus::Event &event) { + eventBus.enqueue(event, timeProvider.now()); + } - bool systemIter(unsigned ticks); + bool handleSystemEvent(const SDL_Event &event); void systemLoop(); + void eventLoop(); + public: void run(); }; diff --git a/src/control/Event.hpp b/src/control/TimeProvider.hpp index 1b2d30a..1880f7b 100644 --- a/src/control/Event.hpp +++ b/src/control/TimeProvider.hpp @@ -26,16 +26,34 @@ #pragma once +#include <SDL.h> + +#include <cstdint> +#include <mutex> + namespace RPGEdit { namespace Control { -class Event { +class TimeProvider { +private: + uint64_t time; + std::mutex mutex; + public: - virtual void handle() = 0; + TimeProvider() { + time = SDL_GetTicks(); + } + + uint64_t now() { + std::lock_guard<std::mutex> lock(mutex); + + uint32_t ticks = SDL_GetTicks() - uint32_t(time); + time += ticks; - virtual ~Event() {} + return time; + } }; } |