I know, I know, this segfaults on exit. I need to make each mod subclass a Swan::Mod superclass so that they can have EventListener structs which are destroyed when the mod exits, and the mod has to exit before the world goes away so that mods' EventListener dtors can access their respective EventEmitters.opengl-renderer-broken
@@ -2,19 +2,26 @@ | |||
#include <random> | |||
ItemStackEntity::ItemStackEntity(const Swan::Context &ctx, const PackObject &obj): | |||
PhysicsEntity(SIZE, MASS) { | |||
PhysicsEntity::body_.bounciness_ = 0.6; | |||
deserialize(ctx, obj); | |||
ItemStackEntity::ItemStackEntity( | |||
const Swan::Context &ctx, Swan::Vec2 pos, const std::string &item): | |||
PhysicsEntity(SIZE, MASS) { | |||
static std::uniform_real_distribution vx(-2.3f, 2.3f); | |||
static std::uniform_real_distribution vy(-2.3f, -1.2f); | |||
item_ = &ctx.world.getItem(item); | |||
body_.pos_ = pos; | |||
body_.pos_.y += 0.5 - body_.size_.y / 2; | |||
body_.vel_ += Swan::Vec2{ vx(ctx.world.random_), vy(ctx.world.random_) }; | |||
} | |||
ItemStackEntity::ItemStackEntity(const Swan::Context &ctx, const PackObject &obj): | |||
PhysicsEntity(SIZE, MASS) { | |||
PhysicsEntity::body_.bounciness_ = 0.6; | |||
deserialize(ctx, obj); | |||
} | |||
void ItemStackEntity::draw(const Swan::Context &ctx, Swan::Win &win) { | |||
SDL_Rect rect = item_->image_.frameRect(); | |||
@@ -4,6 +4,7 @@ | |||
class ItemStackEntity: public Swan::PhysicsEntity { | |||
public: | |||
ItemStackEntity(const Swan::Context &ctx, Swan::Vec2 pos, const std::string &item); | |||
ItemStackEntity(const Swan::Context &ctx, const PackObject &obj); | |||
void draw(const Swan::Context &ctx, Swan::Win &win) override; |
@@ -4,24 +4,24 @@ | |||
#include "ItemStackEntity.h" | |||
PlayerEntity::PlayerEntity(const Swan::Context &ctx, const PackObject &obj): | |||
PlayerEntity::PlayerEntity(const Swan::Context &ctx, Swan::Vec2 pos): | |||
PhysicsEntity(SIZE, MASS), inventory_(INVENTORY_SIZE), | |||
anims_{ | |||
Swan::Animation(ctx.resources.getImage("core/entity/player-still"), 0.8), | |||
Swan::Animation(ctx.resources.getImage("core/entity/player-running"), 1, SDL_FLIP_HORIZONTAL), | |||
Swan::Animation(ctx.resources.getImage("core/entity/player-running"), 1) } { | |||
deserialize(ctx, obj); | |||
body_.pos_ = pos; | |||
} | |||
PlayerEntity::PlayerEntity(const Swan::Context &ctx, Swan::Vec2 pos): | |||
PlayerEntity::PlayerEntity(const Swan::Context &ctx, const PackObject &obj): | |||
PhysicsEntity(SIZE, MASS), inventory_(INVENTORY_SIZE), | |||
anims_{ | |||
Swan::Animation(ctx.resources.getImage("core/entity/player-still"), 0.8), | |||
Swan::Animation(ctx.resources.getImage("core/entity/player-running"), 1, SDL_FLIP_HORIZONTAL), | |||
Swan::Animation(ctx.resources.getImage("core/entity/player-running"), 1) } { | |||
body_.pos_ = pos; | |||
deserialize(ctx, obj); | |||
} | |||
void PlayerEntity::draw(const Swan::Context &ctx, Swan::Win &win) { | |||
@@ -39,7 +39,7 @@ void PlayerEntity::update(const Swan::Context &ctx, float dt) { | |||
// Break block | |||
if (ctx.game.isMousePressed(SDL_BUTTON_LEFT)) | |||
ctx.plane.breakBlock(mouse_tile_); | |||
ctx.plane.breakTile(mouse_tile_); | |||
// Move left | |||
if (ctx.game.isKeyPressed(SDL_SCANCODE_A) || ctx.game.isKeyPressed(SDL_SCANCODE_LEFT)) { |
@@ -5,8 +5,8 @@ | |||
class PlayerEntity: public Swan::PhysicsEntity, public Swan::InventoryTrait::HasInventory { | |||
public: | |||
PlayerEntity(const Swan::Context &ctx, const PackObject &obj); | |||
PlayerEntity(const Swan::Context &ctx, Swan::Vec2 pos); | |||
PlayerEntity(const Swan::Context &ctx, const PackObject &obj); | |||
Swan::InventoryTrait::Inventory &getInventory() override { return inventory_; } | |||
@@ -4,9 +4,20 @@ | |||
#include "entities/PlayerEntity.h" | |||
#include "entities/ItemStackEntity.h" | |||
extern "C" void mod_init(Swan::Mod &mod) { | |||
static Swan::EventListener break_listener; | |||
extern "C" void mod_init(Swan::Mod &mod, Swan::World &world) { | |||
mod.init("core"); | |||
break_listener = world.evt_tile_break_.subscribe([]( | |||
const Swan::Context &ctx, Swan::TilePos pos, Swan::Tile &tile) { | |||
if (tile.dropped_item_) { | |||
ctx.plane.spawnEntity(std::make_unique<ItemStackEntity>( | |||
ctx, pos, *tile.dropped_item_)); | |||
} | |||
}); | |||
mod.registerImage("tile/air"); | |||
mod.registerImage("tile/stone"); | |||
mod.registerImage("tile/dirt"); |
@@ -0,0 +1,75 @@ | |||
#pragma once | |||
#include <functional> | |||
#include "util.h" | |||
#include "SlotVector.h" | |||
namespace Swan { | |||
// So that EventListener doesn't have to be a template | |||
class EventEmitterInterface { | |||
public: | |||
virtual void unsubscribe(size_t id) = 0; | |||
}; | |||
class EventListener: NonCopyable { | |||
public: | |||
EventListener() {} | |||
EventListener(EventEmitterInterface *emitter, size_t id): | |||
emitter_(emitter), id_(id) {} | |||
EventListener(EventListener &&other): | |||
emitter_(other.emitter_), id_(other.id_) { | |||
other.emitter_ = nullptr; | |||
} | |||
EventListener &operator=(EventListener &&other) { | |||
emitter_ = other.emitter_; | |||
id_ = other.id_; | |||
other.emitter_ = nullptr; | |||
return *this; | |||
} | |||
~EventListener() { | |||
if (emitter_) | |||
emitter_->unsubscribe(id_); | |||
} | |||
void unsubscribe() { | |||
if (emitter_) { | |||
emitter_->unsubscribe(id_); | |||
emitter_ = nullptr; | |||
} | |||
} | |||
private: | |||
EventEmitterInterface *emitter_ = nullptr; | |||
size_t id_; | |||
}; | |||
template<typename... Evt> | |||
class EventEmitter: public EventEmitterInterface { | |||
public: | |||
using Callback = std::function<void(Evt...)>; | |||
void emit(Evt... evt) { | |||
for (auto &cb: callbacks_) | |||
cb(evt...); | |||
} | |||
EventListener subscribe(Callback cb) { | |||
size_t id = callbacks_.insert(std::move(cb)); | |||
return EventListener(this, id); | |||
} | |||
void unsubscribe(size_t id) { | |||
callbacks_.erase(id); | |||
} | |||
private: | |||
SlotVector<Callback, SlotVectorDefaultSentinel<nullptr_t>> callbacks_; | |||
}; | |||
} |
@@ -18,8 +18,8 @@ public: | |||
win_(win), | |||
mouse_pos_(0, 0) {} | |||
std::unique_ptr<Mod> loadMod(const std::string &path); | |||
void createWorld(const std::string &worldgen, std::vector<std::unique_ptr<Mod>> &&mods); | |||
std::unique_ptr<Mod> loadMod(const std::string &path, World &world); | |||
void createWorld(const std::string &worldgen, std::vector<std::string> mods); | |||
void onKeyDown(SDL_Keysym sym) { | |||
pressed_keys_[sym.scancode] = true; |
@@ -0,0 +1,112 @@ | |||
#pragma once | |||
#include <vector> | |||
#include <type_traits> | |||
namespace Swan { | |||
template<typename T> | |||
struct SlotVectorDefaultSentinel { | |||
static constexpr T sentinel() { return T{}; } | |||
}; | |||
template<typename T, T val> | |||
struct SlotVectorValueSentinel { | |||
static constexpr T sentinel() { return val; } | |||
}; | |||
template<typename T, typename Sentinel = SlotVectorDefaultSentinel<T>> | |||
class SlotVector { | |||
public: | |||
class Iterator { | |||
public: | |||
Iterator(SlotVector<T, Sentinel> *vec, size_t idx): vec_(vec), idx_(idx) {} | |||
Iterator(const Iterator &) = default; | |||
void seek() { | |||
size_t size = vec_->vec_.size(); | |||
while (idx_ < size && (*vec_)[idx_] == Sentinel::sentinel()) | |||
idx_ += 1; | |||
} | |||
void operator++() { | |||
idx_ += 1; | |||
seek(); | |||
} | |||
T &operator*() { | |||
return (*vec_)[idx_]; | |||
} | |||
bool operator==(const Iterator &other) const { | |||
return idx_ == other.idx_; | |||
} | |||
bool operator!=(const Iterator &other) const { | |||
return !(idx_ == other.idx_); | |||
} | |||
size_t operator-(const Iterator &other) const { | |||
return idx_ - other.idx_; | |||
} | |||
void operator=(const Iterator &other) { | |||
idx_ = other.idx_; | |||
vec_ = other.vec_; | |||
} | |||
private: | |||
SlotVector<T, Sentinel> *vec_; | |||
size_t idx_; | |||
}; | |||
T &operator[](size_t idx) { return vec_[idx]; } | |||
T &at(size_t idx) { return vec_.at(idx); } | |||
bool empty() { return vec_.size() == free_.size(); } | |||
Iterator begin() { Iterator it(this, 0); it.seek(); return it; } | |||
Iterator end() { return Iterator(this, vec_.size()); } | |||
void clear() noexcept { vec_.clear(); free_.clear(); } | |||
void shrink_to_fit() { | |||
vec_.shrink_to_fit(); | |||
free_.shrink_to_fit(); | |||
} | |||
size_t insert(T val) { | |||
if (free_.size() > 0) { | |||
size_t idx = free_.back(); | |||
vec_[idx] = std::move(val); | |||
free_.pop_back(); | |||
return idx; | |||
} else { | |||
size_t idx = vec_.size(); | |||
vec_.push_back(std::move(val)); | |||
return idx; | |||
} | |||
} | |||
template<typename... Args> | |||
size_t emplace(Args&&... args) { | |||
if (free_.size() > 0) { | |||
size_t idx = free_.back(); | |||
T *slot = vec_.data() + idx; | |||
slot->~T(); | |||
new (slot) T(std::forward<Args>(args)...); | |||
return idx; | |||
} else { | |||
size_t idx = vec_.size(); | |||
vec_.emplace_back(std::forward<Args>(args)...); | |||
return idx; | |||
} | |||
} | |||
void erase(size_t idx) { | |||
vec_[idx] = Sentinel::sentinel(); | |||
free_.push_back(idx); | |||
} | |||
private: | |||
std::vector<T> vec_; | |||
std::vector<size_t> free_; | |||
}; | |||
} |
@@ -14,6 +14,7 @@ | |||
#include "Entity.h" | |||
#include "Resource.h" | |||
#include "Mod.h" | |||
#include "EventEmitter.h" | |||
namespace Swan { | |||
@@ -24,7 +25,7 @@ public: | |||
World(Game *game, unsigned long rand_seed); | |||
void addMod(std::unique_ptr<Mod> mod); | |||
void setWorldGen(const std::string &gen); | |||
void setWorldGen(std::string gen); | |||
void spawnPlayer(); | |||
void setCurrentPlane(WorldPlane &plane); | |||
@@ -41,6 +42,10 @@ public: | |||
void update(float dt); | |||
void tick(float dt); | |||
// Event emitters | |||
EventEmitter<const Context &, TilePos, Tile &> | |||
evt_tile_break_; | |||
std::vector<std::unique_ptr<Mod>> mods_; | |||
// World owns tiles and items, the mod just has Builder objects |
@@ -54,7 +54,7 @@ public: | |||
} | |||
BodyTrait::HasBody *spawnPlayer(); | |||
void breakBlock(TilePos pos); | |||
void breakTile(TilePos pos); | |||
SDL_Color backgroundColor(); | |||
void draw(Win &win); |
@@ -6,6 +6,7 @@ | |||
#include <swan/Chunk.h> | |||
#include <swan/Clock.h> | |||
#include <swan/Entity.h> | |||
#include <swan/EventEmitter.h> | |||
#include <swan/Game.h> | |||
#include <swan/Item.h> | |||
#include <swan/ItemStack.h> | |||
@@ -13,6 +14,7 @@ | |||
#include <swan/PerfCounter.h> | |||
#include <swan/OS.h> | |||
#include <swan/Resource.h> | |||
#include <swan/SlotVector.h> | |||
#include <swan/Tile.h> | |||
#include <swan/Vector2.h> | |||
#include <swan/Win.h> |
@@ -11,24 +11,24 @@ | |||
namespace Swan { | |||
std::unique_ptr<Mod> Game::loadMod(const std::string &path) { | |||
std::unique_ptr<Mod> Game::loadMod(const std::string &path, World &world) { | |||
OS::Dynlib dl(path + "/mod"); | |||
auto init = dl.get<void (*)(Swan::Mod &)>("mod_init"); | |||
auto init = dl.get<void (*)(Mod &, World &)>("mod_init"); | |||
if (init == NULL) { | |||
warn << path << ": No 'mod_init' function!"; | |||
return nullptr; | |||
} | |||
std::unique_ptr<Mod> mod = std::make_unique<Mod>(path, std::move(dl)); | |||
init(*mod); | |||
init(*mod, world); | |||
return mod; | |||
} | |||
void Game::createWorld(const std::string &worldgen, std::vector<std::unique_ptr<Mod>> &&mods) { | |||
void Game::createWorld(const std::string &worldgen, std::vector<std::string> modpaths) { | |||
world_.reset(new World(this, time(NULL))); | |||
for (auto &mod: mods) { | |||
world_->addMod(std::move(mod)); | |||
for (auto &modpath: modpaths) { | |||
world_->addMod(std::move(loadMod(modpath, *world_))); | |||
} | |||
world_->setWorldGen(worldgen); |
@@ -76,8 +76,8 @@ void World::addMod(std::unique_ptr<Mod> mod) { | |||
mods_.push_back(std::move(mod)); | |||
} | |||
void World::setWorldGen(const std::string &gen) { | |||
default_world_gen_ = gen; | |||
void World::setWorldGen(std::string gen) { | |||
default_world_gen_ = std::move(gen); | |||
} | |||
void World::spawnPlayer() { |
@@ -153,7 +153,7 @@ BodyTrait::HasBody *WorldPlane::spawnPlayer() { | |||
return gen_->spawnPlayer(getContext()); | |||
} | |||
void WorldPlane::breakBlock(TilePos pos) { | |||
void WorldPlane::breakTile(TilePos pos) { | |||
// If the block is already air, do nothing | |||
Tile::ID id = getTileID(pos); | |||
@@ -161,9 +161,11 @@ void WorldPlane::breakBlock(TilePos pos) { | |||
if (id == air) | |||
return; | |||
// Change the block to air... | |||
// Change tile to air and emit event | |||
setTileID(pos, air); | |||
world_->evt_tile_break_.emit(getContext(), pos, world_->getTileByID(id)); | |||
/* | |||
// Then spawn an item stack entity. | |||
Tile &t = world_->getTileByID(id); | |||
if (t.dropped_item_) { | |||
@@ -173,6 +175,7 @@ void WorldPlane::breakBlock(TilePos pos) { | |||
{ "item", msgpack::object(*t.dropped_item_, zone) }, | |||
}); | |||
} | |||
*/ | |||
} | |||
SDL_Color WorldPlane::backgroundColor() { |
@@ -110,8 +110,7 @@ int main(int argc, char **argv) { | |||
// Create a world | |||
Game game(win); | |||
std::vector<std::unique_ptr<Mod>> mods; | |||
mods.push_back(game.loadMod("core.mod")); | |||
std::vector<std::string> mods{ "core.mod" }; | |||
game.createWorld("core::default", std::move(mods)); | |||
PerfCounter pcounter; |