summaryrefslogtreecommitdiffstats
path: root/src/control
diff options
context:
space:
mode:
Diffstat (limited to 'src/control')
-rw-r--r--src/control/EventBus.hpp58
-rw-r--r--src/control/InputHandler.hpp37
-rw-r--r--src/control/MapContext.cpp74
-rw-r--r--src/control/MapContext.hpp15
-rw-r--r--src/control/RPGEdit.cpp95
-rw-r--r--src/control/RPGEdit.hpp17
-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;
+ }
};
}