New event-driven goodness

This commit is contained in:
Matthias Schiffer 2014-09-24 01:38:30 +02:00
parent 24ae848613
commit ea8840291c
12 changed files with 255 additions and 141 deletions

View file

@ -27,12 +27,16 @@
#pragma once #pragma once
#include "Event.hpp" #include "TimeProvider.hpp"
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <deque> #include <deque>
#include <functional>
#include <limits> #include <limits>
#include <memory>
#include <queue> #include <queue>
#include <thread>
namespace RPGEdit { namespace RPGEdit {
@ -40,35 +44,59 @@ namespace RPGEdit {
namespace Control { namespace Control {
class EventBus { class EventBus {
private: public:
typedef std::pair<uint64_t, std::shared_ptr<Event>> event_entry; 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; 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: public:
EventBus() : events(compare_events) { EventBus() : events(compare_events) {
} }
void enqueue(const std::shared_ptr<Event> &event, uint64_t time) { void enqueue(const Event &event, uint64_t time) {
events.push(std::pair<uint64_t, std::shared_ptr<Event>>(time, event)); 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) { EventEntry get(TimeProvider *timeProvider) {
if (events.empty()) std::unique_lock<std::mutex> lock(mutex);
return std::make_pair<uint64_t, std::shared_ptr<Event>>(std::numeric_limits<uint64_t>::max(), nullptr);
std::pair<uint64_t, std::shared_ptr<Event>> top = events.top(); while (true) {
if (events.empty()) {
cond.wait(lock);
continue;
}
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(); events.pop();
return top; 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;
} }
}; };

View file

@ -26,10 +26,10 @@
#pragma once #pragma once
#include <SDL.h>
#include <cstdint> #include <cstdint>
#include <functional>
#include <unordered_set> #include <unordered_set>
#include <vector>
namespace RPGEdit { namespace RPGEdit {
@ -37,35 +37,36 @@ namespace RPGEdit {
namespace Control { namespace Control {
class InputHandler { class InputHandler {
public:
typedef std::function<void (uint16_t, bool, uint64_t)> Listener;
private: private:
std::vector<Listener> listeners;
std::unordered_set<uint16_t> pressedKeys; std::unordered_set<uint16_t> pressedKeys;
std::unordered_set<uint16_t> unhandledKeys;
public: public:
void keyPressed(SDL_Scancode key) { void registerListener(const Listener &listener) {
listeners.push_back(listener);
}
void keyPressed(uint16_t key, uint64_t time) {
pressedKeys.insert(key); pressedKeys.insert(key);
unhandledKeys.insert(key);
for (auto &listener : listeners)
listener(key, true, time);
} }
void keyReleased(SDL_Scancode key) { void keyReleased(uint16_t key, uint64_t time) {
pressedKeys.erase(key); pressedKeys.erase(key);
for (auto &listener : listeners)
listener(key, false, time);
} }
void keyHandled(SDL_Scancode key) { bool isKeyPressed(uint16_t key) {
unhandledKeys.erase(key);
}
void resetHandled() {
unhandledKeys.clear();
}
bool isKeyPressed(SDL_Scancode key) {
return pressedKeys.count(key); return pressedKeys.count(key);
} }
bool isKeyUnhandled(SDL_Scancode key) {
return unhandledKeys.count(key);
}
}; };
} }

View file

@ -31,8 +31,8 @@ namespace RPGEdit {
namespace Control { namespace Control {
MapContext::MapContext(ImageLoader *imageLoader0, const std::shared_ptr<const Model::Map> &map0) MapContext::MapContext(EventBus *eventBus0, InputHandler *inputHandler0, ImageLoader *imageLoader0, const std::shared_ptr<const Model::Map> &map0)
: imageLoader(imageLoader0), map(map0) { : eventBus(eventBus0), inputHandler(inputHandler0), imageLoader(imageLoader0), map(map0) {
const std::vector<std::string> &tileset = map->getTileset(); const std::vector<std::string> &tileset = map->getTileset();
tiles.resize(tileset.size()); 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) for (const std::shared_ptr<Model::Entity> &entity : mapEntities)
entities[entity->getName()] = imageLoader->get("entity/" + entity->getName()); 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) { void MapContext::movePlayer(Model::Direction dir, uint64_t time) {
uint64_t totalTicksOld = totalTicks; if (map->getPlayerEntity()->hasTransition())
totalTicks += ticks; return;
unsigned advance = totalTicks - totalTicksOld; map->getPlayerEntity()->move(dir, time, time+250);
while (advance) { eventBus->enqueue(
advance = map->getPlayerEntity().advance(advance); [=] {
map->getPlayerEntity()->finishTransition();
movePlayerContinue(time+250);
},
time+250
);
}
if (map->getPlayerEntity().hasTransition()) void MapContext::movePlayerContinue(uint64_t time) {
break; 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); void MapContext::keyPressed(uint16_t key, uint64_t time) {
} switch (key) {
else if (inputHandler->isKeyPressed(SDL_SCANCODE_RIGHT)) { case SDL_SCANCODE_UP:
map->getPlayerEntity().move(Model::EAST, 250); movePlayer(Model::NORTH, time);
} break;
else if (inputHandler->isKeyPressed(SDL_SCANCODE_DOWN)) {
map->getPlayerEntity().move(Model::SOUTH, 250); case SDL_SCANCODE_RIGHT:
} movePlayer(Model::EAST, time);
else if (inputHandler->isKeyPressed(SDL_SCANCODE_LEFT)) { break;
map->getPlayerEntity().move(Model::WEST, 250);
} case SDL_SCANCODE_DOWN:
else { movePlayer(Model::SOUTH, time);
break; break;
}
case SDL_SCANCODE_LEFT:
movePlayer(Model::WEST, time);
} }
} }
} }
} }

View file

@ -26,6 +26,7 @@
#pragma once #pragma once
#include "EventBus.hpp"
#include "ImageLoader.hpp" #include "ImageLoader.hpp"
#include "InputHandler.hpp" #include "InputHandler.hpp"
#include "../model/Map.hpp" #include "../model/Map.hpp"
@ -41,6 +42,8 @@ namespace Control {
class MapContext { class MapContext {
private: private:
EventBus *const eventBus;
InputHandler *const inputHandler;
ImageLoader *const imageLoader; ImageLoader *const imageLoader;
std::shared_ptr<const Model::Map> map; std::shared_ptr<const Model::Map> map;
@ -50,13 +53,15 @@ private:
uint64_t totalTicks = 0; uint64_t totalTicks = 0;
void movePlayer(Model::Direction dir, uint64_t time);
void movePlayerContinue(uint64_t time);
void keyPressed(uint16_t key, uint64_t time);
public: public:
MapContext(ImageLoader *imageLoader0, const std::shared_ptr<const Model::Map> &map0); MapContext(EventBus *eventBus0, InputHandler *inputHandler0, ImageLoader *imageLoader0, const std::shared_ptr<const Model::Map> &map0);
void advance(InputHandler *inputHandler, unsigned ticks); Model::Position getViewPosition(uint64_t time) {
return map->getPlayerEntity()->getPosition(time);
Model::Position getViewPosition() {
return map->getPlayerEntity().getPosition();
} }
std::shared_ptr<View::MapView> initView(const std::shared_ptr<View::Window> &window) { std::shared_ptr<View::MapView> initView(const std::shared_ptr<View::Window> &window) {

View file

@ -28,62 +28,99 @@
#include "MapContext.hpp" #include "MapContext.hpp"
#include "../view/MapView.hpp" #include "../view/MapView.hpp"
#include <SDL.h>
#define MIN_FRAME_DELAY 10
namespace RPGEdit { namespace RPGEdit {
namespace Control { namespace Control {
bool RPGEdit::systemIter(unsigned ticks) { bool RPGEdit::handleSystemEvent(const SDL_Event &event) {
int timeout = 0; uint64_t time = timeProvider.now();
SDL_Event event; switch (event.type) {
if (SDL_WaitEventTimeout(&event, timeout)) { case SDL_KEYDOWN:
switch (event.type) { eventBus.enqueue([=] { inputHandler.keyPressed(event.key.keysym.scancode, time); }, time);
case SDL_KEYDOWN: break;
inputHandler.keyPressed(event.key.keysym.scancode);
break;
case SDL_KEYUP: case SDL_KEYUP:
inputHandler.keyReleased(event.key.keysym.scancode); eventBus.enqueue([=] { inputHandler.keyReleased(event.key.keysym.scancode, time); }, time);
break; break;
case SDL_QUIT: case SDL_QUIT:
return false; return false;
}
} }
ctx->advance(&inputHandler, ticks);
Model::Position pos = ctx->getViewPosition();
mapView->render(pos.x, pos.y);
return true; return true;
} }
void RPGEdit::systemLoop() { void RPGEdit::systemLoop() {
uint32_t ticks1 = SDL_GetTicks(); const int MIN_FRAME_DELAY = 10;
uint32_t ticks2 = ticks1;
while (systemIter(ticks2 - ticks1)) { uint32_t lastFrameTicks = SDL_GetTicks();
ticks1 = ticks2;
ticks2 = SDL_GetTicks(); while (true) {
uint32_t diff = SDL_GetTicks() - lastFrameTicks;
int timeout = std::max(MIN_FRAME_DELAY - int(diff), 0);
SDL_Event event;
if (SDL_WaitEventTimeout(&event, timeout)) {
if (!handleSystemEvent(event))
return;
continue;
}
lastFrameTicks = SDL_GetTicks();
{
std::unique_lock<std::mutex> lock(modelMutex);
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::eventLoop() {
while (true) {
EventBus::EventEntry event = eventBus.get(&timeProvider);
if (!event.second)
return;
{
std::lock_guard<std::mutex> lock(modelMutex);
event.second();
handledTime = eventBus.peek();
modelCond.notify_one();
}
} }
} }
void RPGEdit::run() { void RPGEdit::run() {
std::shared_ptr<Model::Map> map = Model::Map::load("test"); 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>(); window = std::make_shared<View::Window>();
mapView = ctx->initView(window); mapView = ctx->initView(window);
eventThread = std::thread([this] { eventLoop(); });
systemLoop(); systemLoop();
eventBus.enqueue(EventBus::Event(), timeProvider.now());
eventThread.join();
} }
} }

View file

@ -30,8 +30,10 @@
#include "InputHandler.hpp" #include "InputHandler.hpp"
#include "MapContext.hpp" #include "MapContext.hpp"
#include <condition_variable>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <thread>
namespace RPGEdit { namespace RPGEdit {
@ -40,6 +42,8 @@ namespace Control {
class RPGEdit { class RPGEdit {
private: private:
TimeProvider timeProvider;
EventBus eventBus; EventBus eventBus;
InputHandler inputHandler; InputHandler inputHandler;
ImageLoader tileLoader; ImageLoader tileLoader;
@ -49,11 +53,20 @@ private:
std::shared_ptr<View::Window> window; std::shared_ptr<View::Window> window;
std::shared_ptr<View::MapView> mapView; 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();
bool systemIter(unsigned ticks); void enqueueNow(const EventBus::Event &event) {
eventBus.enqueue(event, timeProvider.now());
}
bool handleSystemEvent(const SDL_Event &event);
void systemLoop(); void systemLoop();
void eventLoop();
public: public:
void run(); void run();
}; };

View file

@ -26,16 +26,34 @@
#pragma once #pragma once
#include <SDL.h>
#include <cstdint>
#include <mutex>
namespace RPGEdit { namespace RPGEdit {
namespace Control { namespace Control {
class Event { class TimeProvider {
public: private:
virtual void handle() = 0; uint64_t time;
std::mutex mutex;
virtual ~Event() {} public:
TimeProvider() {
time = SDL_GetTicks();
}
uint64_t now() {
std::lock_guard<std::mutex> lock(mutex);
uint32_t ticks = SDL_GetTicks() - uint32_t(time);
time += ticks;
return time;
}
}; };
} }

View file

@ -46,8 +46,8 @@ private:
Position pos; Position pos;
Direction direction; Direction direction;
unsigned transition = 0; uint64_t transitionStart = 0;
unsigned step = 0; uint64_t transitionEnd = 0;
Position translate(Direction dir, float amount) const { Position translate(Direction dir, float amount) const {
Position p = pos; Position p = pos;
@ -81,9 +81,14 @@ public:
return name; return name;
} }
Position getPosition() const { Position getPosition(uint64_t time) const {
if (transition) if (hasTransition())
return translate(direction, (float)step / transition); if (time <= transitionStart)
return pos;
else if (time >= transitionEnd)
return translate(direction, 1);
else
return translate(direction, float(time-transitionStart)/float(transitionEnd-transitionStart));
else else
return pos; return pos;
} }
@ -92,43 +97,28 @@ public:
return direction; return direction;
} }
void moveTo(float newX, float newY) { void moveTo(Position newPos) {
pos.x = newX; pos = newPos;
pos.y = newY; transitionStart = transitionEnd = 0;
} }
void move(Direction dir, unsigned steps = 0) { void move(Direction dir, uint64_t start, uint64_t end) {
if (transition) if (hasTransition())
return; return;
direction = dir; direction = dir;
if (steps) transitionStart = start;
transition = steps; transitionEnd = end;
else }
pos = translate(direction, 1);
void finishTransition() {
if (hasTransition())
moveTo(translate(direction, 1));
} }
bool hasTransition() const { bool hasTransition() const {
return transition; return transitionEnd;
}
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;
}
} }
}; };

View file

@ -46,7 +46,7 @@ std::shared_ptr<Map> Map::load(__attribute__((unused)) const std::string &name)
} }
map->playerEntity = std::make_shared<Entity>("square"); map->playerEntity = std::make_shared<Entity>("square");
map->playerEntity->moveTo(6, 6); map->playerEntity->moveTo(Model::Position{6, 6});
map->entities.push_back(map->playerEntity); map->entities.push_back(map->playerEntity);

View file

@ -67,8 +67,8 @@ public:
return entities; return entities;
} }
Entity & getPlayerEntity() const { const std::shared_ptr<Entity> & getPlayerEntity() const {
return *playerEntity; return playerEntity;
} }
size_t getWidth() const { size_t getWidth() const {

View file

@ -79,7 +79,7 @@ MapView::~MapView() {
SDL_DestroyTexture(entity.second); 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()); SDL_RenderClear(window->getRenderer());
std::pair<int, int> viewport = window->getViewport(); std::pair<int, int> viewport = window->getViewport();
@ -120,7 +120,7 @@ void MapView::render(float centerX, float centerY) {
} }
for (const std::shared_ptr<Model::Entity> &entity : map->getEntities()) { for (const std::shared_ptr<Model::Entity> &entity : map->getEntities()) {
Model::Position pos = entity->getPosition(); Model::Position pos = entity->getPosition(time);
Model::Direction dir = entity->getDirection(); Model::Direction dir = entity->getDirection();
SDL_Rect src = { SDL_Rect src = {
@ -139,8 +139,6 @@ void MapView::render(float centerX, float centerY) {
SDL_RenderCopy(window->getRenderer(), entityTextures[entity->getName()], &src, &dst); SDL_RenderCopy(window->getRenderer(), entityTextures[entity->getName()], &src, &dst);
} }
SDL_RenderPresent(window->getRenderer());
} }
} }

View file

@ -55,7 +55,7 @@ public:
const std::map<std::string, SDL_Surface *> &entities); const std::map<std::string, SDL_Surface *> &entities);
~MapView(); ~MapView();
void render(float centerX, float centerY); void render(float centerX, float centerY, uint64_t time);
}; };
} }