From ea8840291cdf66784c6a2cb465b63ccfb5483c38 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Wed, 24 Sep 2014 01:38:30 +0200 Subject: New event-driven goodness --- src/control/Event.hpp | 43 -------------------- src/control/EventBus.hpp | 58 ++++++++++++++++++++------- src/control/InputHandler.hpp | 37 ++++++++--------- src/control/MapContext.cpp | 74 ++++++++++++++++++++++------------ src/control/MapContext.hpp | 15 ++++--- src/control/RPGEdit.cpp | 95 ++++++++++++++++++++++++++++++-------------- src/control/RPGEdit.hpp | 17 +++++++- src/control/TimeProvider.hpp | 61 ++++++++++++++++++++++++++++ src/model/Entity.hpp | 54 ++++++++++--------------- src/model/Map.cpp | 2 +- src/model/Map.hpp | 4 +- src/view/MapView.cpp | 6 +-- src/view/MapView.hpp | 2 +- 13 files changed, 291 insertions(+), 177 deletions(-) delete mode 100644 src/control/Event.hpp create mode 100644 src/control/TimeProvider.hpp diff --git a/src/control/Event.hpp b/src/control/Event.hpp deleted file mode 100644 index 1b2d30a..0000000 --- a/src/control/Event.hpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright (c) 2014, Matthias Schiffer - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#pragma once - - -namespace RPGEdit { - -namespace Control { - -class Event { -public: - virtual void handle() = 0; - - virtual ~Event() {} -}; - -} - -} 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 +#include +#include #include +#include #include -#include #include +#include namespace RPGEdit { @@ -40,35 +44,59 @@ namespace RPGEdit { namespace Control { class EventBus { -private: - typedef std::pair> event_entry; +public: + typedef std::function Event; + typedef std::pair 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, bool (*)(const event_entry &, const event_entry &)> events; + std::priority_queue, 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, uint64_t time) { - events.push(std::pair>(time, event)); + void enqueue(const Event &event, uint64_t time) { + std::lock_guard lock(mutex); + events.push(EventEntry(time, event)); + cond.notify_one(); } - std::pair> get(uint64_t time) { - if (events.empty()) - return std::make_pair>(std::numeric_limits::max(), nullptr); + EventEntry get(TimeProvider *timeProvider) { + std::unique_lock lock(mutex); + + while (true) { + if (events.empty()) { + cond.wait(lock); + continue; + } - std::pair> 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>(top.first, std::shared_ptr()); + } + + uint64_t peek() { + std::lock_guard lock(mutex); + + if (events.empty()) + return std::numeric_limits::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 - #include +#include #include +#include namespace RPGEdit { @@ -37,34 +37,35 @@ namespace RPGEdit { namespace Control { class InputHandler { +public: + typedef std::function Listener; + private: + std::vector listeners; + std::unordered_set pressedKeys; - std::unordered_set 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 &map0) - : imageLoader(imageLoader0), map(map0) { +MapContext::MapContext(EventBus *eventBus0, InputHandler *inputHandler0, ImageLoader *imageLoader0, const std::shared_ptr &map0) + : eventBus(eventBus0), inputHandler(inputHandler0), imageLoader(imageLoader0), map(map0) { const std::vector &tileset = map->getTileset(); tiles.resize(tileset.size()); @@ -43,38 +43,62 @@ MapContext::MapContext(ImageLoader *imageLoader0, const std::shared_ptr &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 map; @@ -50,13 +53,15 @@ private: uint64_t totalTicks = 0; -public: - MapContext(ImageLoader *imageLoader0, const std::shared_ptr &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 &map0); - Model::Position getViewPosition() { - return map->getPlayerEntity().getPosition(); + Model::Position getViewPosition(uint64_t time) { + return map->getPlayerEntity()->getPosition(time); } std::shared_ptr initView(const std::shared_ptr &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 +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 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 lock(modelMutex); + + event.second(); + + handledTime = eventBus.peek(); + modelCond.notify_one(); + } } } void RPGEdit::run() { std::shared_ptr map = Model::Map::load("test"); - ctx = std::make_shared(&tileLoader, map); + ctx = std::make_shared(&eventBus, &inputHandler, &tileLoader, map); window = std::make_shared(); 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 #include #include +#include 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 window; std::shared_ptr mapView; - std::mutex modelLock; + std::thread eventThread; + std::mutex modelMutex; + std::condition_variable modelCond; + uint64_t handledTime = std::numeric_limits::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/TimeProvider.hpp b/src/control/TimeProvider.hpp new file mode 100644 index 0000000..1880f7b --- /dev/null +++ b/src/control/TimeProvider.hpp @@ -0,0 +1,61 @@ +/* + Copyright (c) 2014, Matthias Schiffer + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#pragma once + +#include + +#include +#include + + +namespace RPGEdit { + +namespace Control { + +class TimeProvider { +private: + uint64_t time; + std::mutex mutex; + +public: + TimeProvider() { + time = SDL_GetTicks(); + } + + uint64_t now() { + std::lock_guard lock(mutex); + + uint32_t ticks = SDL_GetTicks() - uint32_t(time); + time += ticks; + + return time; + } +}; + +} + +} diff --git a/src/model/Entity.hpp b/src/model/Entity.hpp index fa4477d..1ff444a 100644 --- a/src/model/Entity.hpp +++ b/src/model/Entity.hpp @@ -46,8 +46,8 @@ private: Position pos; Direction direction; - unsigned transition = 0; - unsigned step = 0; + uint64_t transitionStart = 0; + uint64_t transitionEnd = 0; Position translate(Direction dir, float amount) const { Position p = pos; @@ -81,9 +81,14 @@ public: return name; } - Position getPosition() const { - if (transition) - return translate(direction, (float)step / transition); + Position getPosition(uint64_t time) const { + if (hasTransition()) + if (time <= transitionStart) + return pos; + else if (time >= transitionEnd) + return translate(direction, 1); + else + return translate(direction, float(time-transitionStart)/float(transitionEnd-transitionStart)); else return pos; } @@ -92,43 +97,28 @@ public: return direction; } - void moveTo(float newX, float newY) { - pos.x = newX; - pos.y = newY; + void moveTo(Position newPos) { + pos = newPos; + transitionStart = transitionEnd = 0; } - void move(Direction dir, unsigned steps = 0) { - if (transition) + void move(Direction dir, uint64_t start, uint64_t end) { + if (hasTransition()) return; direction = dir; - if (steps) - transition = steps; - else - pos = translate(direction, 1); + transitionStart = start; + transitionEnd = end; } - bool hasTransition() const { - return transition; + void finishTransition() { + if (hasTransition()) + moveTo(translate(direction, 1)); } - unsigned advance(unsigned ticks) { - if (!transition) - return ticks; - - step += ticks; - - if (step >= transition) { - ticks = step - transition; - transition = step = 0; - pos = translate(direction, 1); - - return ticks; - } - else { - return 0; - } + bool hasTransition() const { + return transitionEnd; } }; diff --git a/src/model/Map.cpp b/src/model/Map.cpp index 8081255..db18baf 100644 --- a/src/model/Map.cpp +++ b/src/model/Map.cpp @@ -46,7 +46,7 @@ std::shared_ptr Map::load(__attribute__((unused)) const std::string &name) } map->playerEntity = std::make_shared("square"); - map->playerEntity->moveTo(6, 6); + map->playerEntity->moveTo(Model::Position{6, 6}); map->entities.push_back(map->playerEntity); diff --git a/src/model/Map.hpp b/src/model/Map.hpp index 5d6ba01..9d4eded 100644 --- a/src/model/Map.hpp +++ b/src/model/Map.hpp @@ -67,8 +67,8 @@ public: return entities; } - Entity & getPlayerEntity() const { - return *playerEntity; + const std::shared_ptr & getPlayerEntity() const { + return playerEntity; } size_t getWidth() const { diff --git a/src/view/MapView.cpp b/src/view/MapView.cpp index 580014e..5e55290 100644 --- a/src/view/MapView.cpp +++ b/src/view/MapView.cpp @@ -79,7 +79,7 @@ MapView::~MapView() { SDL_DestroyTexture(entity.second); } -void MapView::render(float centerX, float centerY) { +void MapView::render(float centerX, float centerY, uint64_t time) { SDL_RenderClear(window->getRenderer()); std::pair viewport = window->getViewport(); @@ -120,7 +120,7 @@ void MapView::render(float centerX, float centerY) { } for (const std::shared_ptr &entity : map->getEntities()) { - Model::Position pos = entity->getPosition(); + Model::Position pos = entity->getPosition(time); Model::Direction dir = entity->getDirection(); SDL_Rect src = { @@ -139,8 +139,6 @@ void MapView::render(float centerX, float centerY) { SDL_RenderCopy(window->getRenderer(), entityTextures[entity->getName()], &src, &dst); } - - SDL_RenderPresent(window->getRenderer()); } } diff --git a/src/view/MapView.hpp b/src/view/MapView.hpp index 98dfe56..7f83006 100644 --- a/src/view/MapView.hpp +++ b/src/view/MapView.hpp @@ -55,7 +55,7 @@ public: const std::map &entities); ~MapView(); - void render(float centerX, float centerY); + void render(float centerX, float centerY, uint64_t time); }; } -- cgit v1.2.3