New event-driven goodness
This commit is contained in:
parent
24ae848613
commit
ea8840291c
12 changed files with 255 additions and 141 deletions
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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,37 +43,61 @@ 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())
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
void MapContext::keyPressed(uint16_t key, uint64_t time) {
|
||||
switch (key) {
|
||||
case SDL_SCANCODE_UP:
|
||||
movePlayer(Model::NORTH, time);
|
||||
break;
|
||||
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
eventBus.enqueue([=] { inputHandler.keyPressed(event.key.keysym.scancode, time); }, time);
|
||||
break;
|
||||
|
||||
case SDL_KEYUP:
|
||||
inputHandler.keyReleased(event.key.keysym.scancode);
|
||||
eventBus.enqueue([=] { inputHandler.keyReleased(event.key.keysym.scancode, time); }, time);
|
||||
break;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Reference in a new issue