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
#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);
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();
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
#include <SDL.h>
#include <cstdint>
#include <functional>
#include <unordered_set>
#include <vector>
namespace RPGEdit {
@ -37,35 +37,36 @@ 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) {
void registerListener(const Listener &listener) {
listeners.push_back(listener);
}
void keyPressed(uint16_t key, uint64_t time) {
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);
for (auto &listener : listeners)
listener(key, false, time);
}
void keyHandled(SDL_Scancode key) {
unhandledKeys.erase(key);
}
void resetHandled() {
unhandledKeys.clear();
}
bool isKeyPressed(SDL_Scancode key) {
bool isKeyPressed(uint16_t key) {
return pressedKeys.count(key);
}
bool isKeyUnhandled(SDL_Scancode key) {
return unhandledKeys.count(key);
}
};
}

View file

@ -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);
}
}
}
}

View file

@ -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;
void movePlayer(Model::Direction dir, uint64_t time);
void movePlayerContinue(uint64_t time);
void keyPressed(uint16_t key, uint64_t time);
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() {
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) {

View file

@ -28,62 +28,99 @@
#include "MapContext.hpp"
#include "../view/MapView.hpp"
#include <SDL.h>
#define MIN_FRAME_DELAY 10
namespace RPGEdit {
namespace Control {
bool RPGEdit::systemIter(unsigned ticks) {
int timeout = 0;
bool RPGEdit::handleSystemEvent(const SDL_Event &event) {
uint64_t time = timeProvider.now();
SDL_Event event;
if (SDL_WaitEventTimeout(&event, timeout)) {
switch (event.type) {
case SDL_KEYDOWN:
inputHandler.keyPressed(event.key.keysym.scancode);
break;
switch (event.type) {
case SDL_KEYDOWN:
eventBus.enqueue([=] { inputHandler.keyPressed(event.key.keysym.scancode, time); }, time);
break;
case SDL_KEYUP:
inputHandler.keyReleased(event.key.keysym.scancode);
break;
case SDL_KEYUP:
eventBus.enqueue([=] { inputHandler.keyReleased(event.key.keysym.scancode, time); }, time);
break;
case SDL_QUIT:
return false;
}
case SDL_QUIT:
return false;
}
ctx->advance(&inputHandler, ticks);
Model::Position pos = ctx->getViewPosition();
mapView->render(pos.x, pos.y);
return true;
}
void RPGEdit::systemLoop() {
uint32_t ticks1 = SDL_GetTicks();
uint32_t ticks2 = ticks1;
const int MIN_FRAME_DELAY = 10;
while (systemIter(ticks2 - ticks1)) {
ticks1 = ticks2;
ticks2 = SDL_GetTicks();
uint32_t lastFrameTicks = 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() {
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();
}
}

View file

@ -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();
bool systemIter(unsigned ticks);
void enqueueNow(const EventBus::Event &event) {
eventBus.enqueue(event, timeProvider.now());
}
bool handleSystemEvent(const SDL_Event &event);
void systemLoop();
void eventLoop();
public:
void run();
};

View file

@ -26,16 +26,34 @@
#pragma once
#include <SDL.h>
#include <cstdint>
#include <mutex>
namespace RPGEdit {
namespace Control {
class Event {
public:
virtual void handle() = 0;
class TimeProvider {
private:
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;
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;
}
void finishTransition() {
if (hasTransition())
moveTo(translate(direction, 1));
}
bool hasTransition() const {
return transition;
}
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;
}
return transitionEnd;
}
};

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->moveTo(6, 6);
map->playerEntity->moveTo(Model::Position{6, 6});
map->entities.push_back(map->playerEntity);

View file

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

View file

@ -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<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()) {
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());
}
}

View file

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