There are still some things left to do: * Implement despawn * Maybe shrink SlotVector if there's a lot more free slots than occupied slots * Implement iteration over entitiesopengl-renderer-broken
@@ -1,3 +1,4 @@ | |||
/build | |||
.tags | |||
compile_commands.json | |||
.clangd |
@@ -19,15 +19,6 @@ if(CMAKE_GENERATOR STREQUAL "Ninja") | |||
add_compile_options(-fdiagnostics-color=always) | |||
endif() | |||
if(CMAKE_EXPORT_COMPILE_COMMANDS) | |||
add_custom_target(compile_commands ALL | |||
BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json | |||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} | |||
COMMAND | |||
compdb -p ${CMAKE_CURRENT_BINARY_DIR} list | |||
> ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json) | |||
endif() | |||
add_compile_options(-std=c++2a -Wall -Wextra -Wpedantic -Wno-unused-parameter) | |||
if(CMAKE_BUILD_TYPE STREQUAL Sanitize OR CMAKE_BUILD_TYPE STREQUAL "") | |||
message(STATUS "Build mode: Sanitize") |
@@ -74,9 +74,8 @@ void DefaultWorldGen::genChunk(Swan::WorldPlane &plane, Swan::Chunk &chunk) { | |||
} | |||
} | |||
Swan::BodyTrait::HasBody *DefaultWorldGen::spawnPlayer(const Swan::Context &ctx) { | |||
Swan::EntityRef DefaultWorldGen::spawnPlayer(const Swan::Context &ctx) { | |||
int x = 0; | |||
return dynamic_cast<Swan::BodyTrait::HasBody *>( | |||
ctx.plane.spawnEntity(std::make_unique<PlayerEntity>( | |||
ctx, Swan::Vec2{ (float)x, (float)grassLevel(perlin_, x) - 4 }))); | |||
return ctx.plane.spawnEntity<PlayerEntity>( | |||
ctx, Swan::Vec2{ (float)x, (float)grassLevel(perlin_, x) - 4 }); | |||
} |
@@ -18,7 +18,7 @@ public: | |||
void drawBackground(const Swan::Context &ctx, Swan::Win &win, Swan::Vec2 pos) override; | |||
SDL_Color backgroundColor(Swan::Vec2 pos) override; | |||
void genChunk(Swan::WorldPlane &plane, Swan::Chunk &chunk) override; | |||
Swan::BodyTrait::HasBody *spawnPlayer(const Swan::Context &ctx) override; | |||
Swan::EntityRef spawnPlayer(const Swan::Context &ctx) override; | |||
private: | |||
Swan::Tile::ID genTile(Swan::TilePos pos); |
@@ -69,14 +69,14 @@ public: | |||
void onTileBreak(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_)); | |||
ctx.plane.spawnEntity<ItemStackEntity>( | |||
ctx, pos, *tile.dropped_item_); | |||
} | |||
} | |||
Swan::EventListener break_listener_; | |||
}; | |||
extern "C" std::unique_ptr<Swan::Mod> mod_create(Swan::World &world) { | |||
return std::make_unique<CoreMod>(world); | |||
extern "C" Swan::Mod *mod_create(Swan::World &world) { | |||
return new CoreMod(world); | |||
} |
@@ -0,0 +1,193 @@ | |||
#pragma once | |||
#include <vector> | |||
#include <string> | |||
#include <typeindex> | |||
#include "common.h" | |||
#include "Entity.h" | |||
#include "SlotVector.h" | |||
#include "SmallOptional.h" | |||
namespace Swan { | |||
class EntityCollection; | |||
class EntityRef { | |||
public: | |||
EntityRef() = default; | |||
EntityRef(EntityCollection *coll, size_t index, size_t generation): | |||
coll_(coll), index_(index), generation_(generation) {} | |||
template<typename Func> | |||
EntityRef &then(Func func); | |||
bool hasValue(); | |||
Entity *get(); | |||
private: | |||
EntityCollection *coll_; | |||
size_t index_; | |||
size_t generation_; | |||
}; | |||
class EntityCollection { | |||
public: | |||
struct Factory { | |||
const std::string name; | |||
std::unique_ptr<EntityCollection> (*const create)(std::string name); | |||
}; | |||
template<typename Ent> | |||
struct EntWrapper { | |||
size_t generation; | |||
Ent ent; | |||
bool operator==(const EntWrapper &other) const { | |||
return generation == other.generation; | |||
} | |||
}; | |||
template<typename Ent> | |||
struct OptionalPolicy { | |||
static void setEmpty(unsigned char *ptr) { | |||
((EntWrapper<Ent> *)ptr)->generation = ~0ull; | |||
} | |||
static bool isEmpty(const unsigned char *ptr) { | |||
return ((EntWrapper<Ent> *)ptr)->generation == ~0ull; | |||
} | |||
}; | |||
template<typename Ent> | |||
using OptEnt = SmallOptional<EntWrapper<Ent>, OptionalPolicy<Ent>>; | |||
virtual ~EntityCollection() = default; | |||
template<typename Ent, typename... Args> | |||
EntityRef spawn(Args&&... args); | |||
virtual const std::string &name() = 0; | |||
virtual std::type_index type() = 0; | |||
virtual size_t size() = 0; | |||
virtual Entity *get(size_t idx) = 0; | |||
virtual Entity *get(size_t idx, size_t version) = 0; | |||
virtual EntityRef spawn(const Context &ctx, const Entity::PackObject &obj) = 0; | |||
virtual void update(const Context &ctx, float dt) = 0; | |||
virtual void tick(const Context &ctx, float dt) = 0; | |||
virtual void draw(const Context &ctx, Win &win) = 0; | |||
private: | |||
virtual void *getEntityVector() = 0; | |||
virtual size_t nextGeneration() = 0; | |||
}; | |||
template<typename Ent> | |||
class EntityCollectionImpl: public EntityCollection { | |||
public: | |||
EntityCollectionImpl(std::string name): name_(std::move(name)) {} | |||
size_t size() override { return entities_.size(); } | |||
Entity *get(size_t idx) override { return &entities_[idx]->ent; } | |||
Entity *get(size_t idx, size_t generation) override; | |||
const std::string &name() override { return name_; } | |||
std::type_index type() override { return typeid(Ent); } | |||
EntityRef spawn(const Context &ctx, const Entity::PackObject &obj) override; | |||
void update(const Context &ctx, float dt) override; | |||
void tick(const Context &ctx, float dt) override; | |||
void draw(const Context &ctx, Win &win) override; | |||
private: | |||
void *getEntityVector() override { return (void *)&entities_; } | |||
size_t nextGeneration() override { return generation_++; } | |||
const std::string name_; | |||
SlotVector<EntityCollection::OptEnt<Ent>> entities_; | |||
size_t generation_ = 0; | |||
}; | |||
/* | |||
* EntityRef | |||
*/ | |||
template<typename Func> | |||
inline EntityRef &EntityRef::then(Func func) { | |||
Entity *ent = coll_->get(index_, generation_); | |||
if (ent != nullptr) | |||
func(ent); | |||
return *this; | |||
} | |||
inline Entity *EntityRef::get() { | |||
return coll_->get(index_, generation_); | |||
} | |||
inline bool EntityRef::hasValue() { | |||
return coll_->get(index_, generation_) != nullptr; | |||
} | |||
/* | |||
* EntityCollection | |||
*/ | |||
template<typename Ent, typename... Args> | |||
inline EntityRef EntityCollection::spawn(Args&&... args) { | |||
auto entities = (SlotVector<OptEnt<Ent>> *)getEntityVector(); | |||
size_t generation = nextGeneration(); | |||
size_t idx = entities->emplace(EntWrapper<Ent>{ | |||
generation, std::move(Ent(std::forward<Args>(args)...)) }); | |||
return { this, idx, generation }; | |||
} | |||
/* | |||
* EntityCollectionImpl | |||
*/ | |||
template<typename Ent> | |||
inline Entity *EntityCollectionImpl<Ent>::get(size_t idx, size_t generation) { | |||
if (idx >=entities_.size()) | |||
return nullptr; | |||
auto &e = entities_[idx]; | |||
// We don't even need to check if e.hasValue(), because if it doesn't, | |||
// its generation will be 0xffff... and the check will fail | |||
if (e->generation != generation) | |||
return nullptr; | |||
return &e->ent; | |||
} | |||
template<typename Ent> | |||
inline EntityRef EntityCollectionImpl<Ent>::spawn(const Context &ctx, const Entity::PackObject &obj) { | |||
size_t generation = nextGeneration(); | |||
size_t idx = entities_.emplace(EntWrapper<Ent>{ | |||
generation, std::move(Ent(ctx, obj)) }); | |||
return { this, idx, generation }; | |||
} | |||
template<typename Ent> | |||
inline void EntityCollectionImpl<Ent>::update(const Context &ctx, float dt) { | |||
for (auto &ent: entities_) | |||
ent->ent.update(ctx, dt); | |||
} | |||
template<typename Ent> | |||
inline void EntityCollectionImpl<Ent>::tick(const Context &ctx, float dt) { | |||
for (auto &ent: entities_) | |||
ent->ent.tick(ctx, dt); | |||
} | |||
template<typename Ent> | |||
inline void EntityCollectionImpl<Ent>::draw(const Context &ctx, Win &win) { | |||
for (auto &ent: entities_) | |||
ent->ent.draw(ctx, win); | |||
} | |||
} |
@@ -14,7 +14,7 @@ class World; | |||
class WorldPlane; | |||
class Game; | |||
class Entity { | |||
class Entity: NonCopyable { | |||
public: | |||
using PackObject = std::unordered_map<std::string_view, msgpack::object>; | |||
@@ -23,6 +23,9 @@ public: | |||
std::unique_ptr<Entity> (*create)(const Context &ctx, const PackObject &obj); | |||
}; | |||
Entity() = default; | |||
Entity(Entity &&) = default; | |||
virtual ~Entity() = default; | |||
virtual void draw(const Context &ctx, Win &win) {} |
@@ -20,12 +20,12 @@ public: | |||
EventListener(EventEmitterInterface *emitter, size_t id): | |||
emitter_(emitter), id_(id) {} | |||
EventListener(EventListener &&other): | |||
EventListener(EventListener &&other) noexcept: | |||
emitter_(other.emitter_), id_(other.id_) { | |||
other.emitter_ = nullptr; | |||
} | |||
EventListener &operator=(EventListener &&other) { | |||
EventListener &operator=(EventListener &&other) noexcept { | |||
emitter_ = other.emitter_; | |||
id_ = other.id_; | |||
other.emitter_ = nullptr; | |||
@@ -69,7 +69,7 @@ public: | |||
} | |||
private: | |||
SlotVector<Callback, SlotVectorDefaultSentinel<nullptr_t>> callbacks_; | |||
SlotVector<Callback, SlotVectorDefaultSentinel<std::nullptr_t>> callbacks_; | |||
}; | |||
} |
@@ -20,7 +20,7 @@ public: | |||
mouse_pos_(0, 0) {} | |||
std::optional<ModWrapper> loadMod(std::string path, World &world); | |||
void createWorld(const std::string &worldgen, std::vector<std::string> mods); | |||
void createWorld(const std::string &worldgen, const std::vector<std::string> &mods); | |||
void onKeyDown(SDL_Keysym sym) { | |||
pressed_keys_[sym.scancode] = true; |
@@ -4,12 +4,14 @@ | |||
#include <string> | |||
#include <vector> | |||
#include <memory> | |||
#include <type_traits> | |||
#include <SDL2/SDL.h> | |||
#include "Tile.h" | |||
#include "Item.h" | |||
#include "WorldGen.h" | |||
#include "Entity.h" | |||
#include "Collection.h" | |||
#include "Resource.h" | |||
#include "OS.h" | |||
#include "util.h" | |||
@@ -30,20 +32,21 @@ public: | |||
void registerWorldGen(const std::string &name) { | |||
worldgens_.push_back(WorldGen::Factory{ | |||
.name = name_ + "::" + name, | |||
.create = [](World &world) { | |||
return static_cast<std::unique_ptr<WorldGen>>( | |||
std::make_unique<WG>(world)); | |||
.create = [](World &world) -> std::unique_ptr<WorldGen> { | |||
return std::make_unique<WG>(world); | |||
} | |||
}); | |||
} | |||
template<typename Ent> | |||
void registerEntity(const std::string &name) { | |||
entities_.push_back(Entity::Factory{ | |||
static_assert( | |||
std::is_move_constructible_v<Ent>, | |||
"Entities must be movable"); | |||
entities_.push_back(EntityCollection::Factory{ | |||
.name = name_ + "::" + name, | |||
.create = [](const Context &ctx, const Entity::PackObject &obj) { | |||
return static_cast<std::unique_ptr<Entity>>( | |||
std::make_unique<Ent>(ctx, obj)); | |||
.create = [](std::string name) -> std::unique_ptr<EntityCollection> { | |||
return std::make_unique<EntityCollectionImpl<Ent>>(std::move(name)); | |||
} | |||
}); | |||
} | |||
@@ -53,7 +56,7 @@ public: | |||
std::vector<Tile::Builder> tiles_; | |||
std::vector<Item::Builder> items_; | |||
std::vector<WorldGen::Factory> worldgens_; | |||
std::vector<Entity::Factory> entities_; | |||
std::vector<EntityCollection::Factory> entities_; | |||
}; | |||
class ModWrapper { | |||
@@ -73,7 +76,7 @@ public: | |||
Iter<std::unique_ptr<Tile>> buildTiles(const ResourceManager &resources); | |||
Iter<std::unique_ptr<Item>> buildItems(const ResourceManager &resources); | |||
Iter<WorldGen::Factory> getWorldGens(); | |||
Iter<Entity::Factory> getEntities(); | |||
Iter<EntityCollection::Factory> getEntities(); | |||
std::unique_ptr<Mod> mod_; | |||
std::string path_; |
@@ -47,10 +47,6 @@ public: | |||
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_; | |||
@@ -59,6 +55,7 @@ public: | |||
T &operator[](size_t idx) { return vec_[idx]; } | |||
T &at(size_t idx) { return vec_.at(idx); } | |||
size_t size() { return vec_.size(); } | |||
bool empty() { return vec_.size() == free_.size(); } | |||
Iterator begin() { Iterator it(this, 0); it.seek(); return it; } |
@@ -0,0 +1,173 @@ | |||
#pragma once | |||
#include <new> | |||
#include <stdlib.h> | |||
#include <utility> | |||
#include <iostream> | |||
namespace Swan { | |||
// The initial bytes policy assumes that no T object will ever start with | |||
// 'size' 'empty_byte' bytes. This works for types where, for example, | |||
// the first few bytes are a pointer. | |||
template<typename T, size_t size = sizeof(T), unsigned char empty_byte = 0xFF> | |||
struct SmallOptionalInitialBytesPolicy { | |||
static_assert(sizeof(T) >= size); | |||
static void setEmpty(unsigned char *ptr) { | |||
for (size_t i = 0; i < size; ++i) | |||
ptr[i] = empty_byte; | |||
} | |||
static bool isEmpty(const unsigned char *ptr) { | |||
for (size_t i = 0; i < size; ++i) | |||
if (ptr[i] != empty_byte) | |||
return false; | |||
return true; | |||
} | |||
}; | |||
// The value policy lets you define a particular T value which can be a sentinel | |||
// and is considered different from valid T values according to T::operator==. | |||
// For example, if T is int, -1 could be a sentinel if only positive values are expected. | |||
template<typename T, T empty_value> | |||
struct SmallOptionalValuePolicy { | |||
static void setEmpty(unsigned char *ptr) { | |||
*(T *)ptr = empty_value; | |||
} | |||
static bool isEmpty(const unsigned char *ptr) { | |||
return *(T *)ptr == empty_value; | |||
} | |||
}; | |||
// This is probably UB but I don't care, it avoids wasting 8 bytes per entity | |||
template<typename T, typename Policy = SmallOptionalInitialBytesPolicy<T>> | |||
class SmallOptional { | |||
public: | |||
SmallOptional() { | |||
Policy::setEmpty(data_); | |||
} | |||
SmallOptional(const T &other) { | |||
new (data_) T(other); | |||
} | |||
SmallOptional(T &&other) noexcept { | |||
new (data_) T(std::move(other)); | |||
} | |||
SmallOptional(const SmallOptional<T, Policy> &other) { | |||
if (other.hasValue()) { | |||
new (data_) T(*other.get()); | |||
} else { | |||
Policy::setEmpty(data_); | |||
} | |||
} | |||
SmallOptional(SmallOptional<T, Policy> &&other) noexcept { | |||
if (other.hasValue()) { | |||
new (data_) T(std::move(*other)); | |||
} else { | |||
Policy::setEmpty(data_); | |||
} | |||
} | |||
~SmallOptional() { | |||
if (hasValue()) { | |||
get()->~T(); | |||
} | |||
} | |||
void reset() { | |||
if (hasValue()) { | |||
get()->~T(); | |||
Policy::setEmpty(data_); | |||
} | |||
} | |||
template<typename... Args> | |||
T &emplace(Args&&... args) { | |||
if (hasValue()) { | |||
get()->~T(); | |||
} | |||
new (data_) T(std::forward<Args>(args)...); | |||
return *get(); | |||
} | |||
T *get() { | |||
return std::launder<T>((T *)data_); | |||
} | |||
const T *get() const { | |||
return std::launder<T>((T *)data_); | |||
} | |||
bool hasValue() const { | |||
return !Policy::isEmpty(data_); | |||
} | |||
SmallOptional<T, Policy> &operator=(const T &other) { | |||
if (hasValue()) { | |||
*get() = other; | |||
} else { | |||
new (data_) T(other); | |||
} | |||
return *this; | |||
} | |||
SmallOptional<T, Policy> &operator=(const T &&other) noexcept { | |||
if (hasValue()) { | |||
*get() = std::move(other); | |||
} else { | |||
new (data_) T(std::move(other)); | |||
} | |||
return *this; | |||
} | |||
SmallOptional<T, Policy> &operator=(const SmallOptional<T, Policy> &other) { | |||
if (other.hasValue()) { | |||
if (hasValue()) { | |||
*get() = *other.get(); | |||
} else { | |||
new (data_) T(*other.get()); | |||
} | |||
} else { | |||
reset(); | |||
} | |||
return *this; | |||
} | |||
SmallOptional &operator=(SmallOptional<T, Policy> &&other) noexcept { | |||
if (other.hasValue()) { | |||
if (hasValue()) { | |||
*get() = std::move(*other.get()); | |||
} else { | |||
new (data_) T(std::move(*other.get())); | |||
} | |||
} else { | |||
reset(); | |||
} | |||
return *this; | |||
} | |||
bool operator==(const SmallOptional<T, Policy> &other) const { | |||
bool a = hasValue(), b = other.hasValue(); | |||
if (!a && !b) return true; | |||
if (!a || !b) return false; | |||
return *get() == *other.get(); | |||
} | |||
operator bool() const noexcept { | |||
return !Policy::isEmpty(data_); | |||
} | |||
T *operator->() noexcept { | |||
return get(); | |||
} | |||
const T *operator->() const noexcept { | |||
return get(); | |||
} | |||
T &operator*() noexcept { | |||
return *get(); | |||
} | |||
const T &operator*() const noexcept { | |||
return *get(); | |||
} | |||
private: | |||
alignas(alignof(T)) unsigned char data_[sizeof(T)]; | |||
}; | |||
} |
@@ -12,6 +12,7 @@ | |||
#include "WorldPlane.h" | |||
#include "WorldGen.h" | |||
#include "Entity.h" | |||
#include "Collection.h" | |||
#include "Resource.h" | |||
#include "Mod.h" | |||
#include "EventEmitter.h" | |||
@@ -54,10 +55,9 @@ public: | |||
std::unordered_map<std::string, Tile::ID> tiles_map_; | |||
std::unordered_map<std::string, std::unique_ptr<Item>> items_; | |||
// The mods themselves retain ownership of world gens and entities, | |||
// the world just has non-owning pointers to them | |||
std::unordered_map<std::string, WorldGen::Factory> worldgens_; | |||
std::unordered_map<std::string, Entity::Factory> ents_; | |||
// Mods give us factories to create new world gens and new entity collections | |||
std::unordered_map<std::string, WorldGen::Factory> worldgen_factories_; | |||
std::vector<EntityCollection::Factory> ent_coll_factories_; | |||
BodyTrait::HasBody *player_; | |||
Game *game_; |
@@ -6,6 +6,7 @@ | |||
#include "common.h" | |||
#include "Chunk.h" | |||
#include "Entity.h" | |||
#include "Collection.h" | |||
#include "traits/BodyTrait.h" | |||
#include "Vector2.h" | |||
@@ -19,7 +20,7 @@ class WorldGen { | |||
public: | |||
struct Factory { | |||
const std::string name; | |||
std::unique_ptr<WorldGen> (*create)(World &world); | |||
std::unique_ptr<WorldGen> (*const create)(World &world); | |||
}; | |||
virtual ~WorldGen() = default; | |||
@@ -28,7 +29,7 @@ public: | |||
virtual SDL_Color backgroundColor(Vec2 pos) = 0; | |||
virtual void genChunk(WorldPlane &plane, Chunk &chunk) = 0; | |||
virtual BodyTrait::HasBody *spawnPlayer(const Context &ctx) = 0; | |||
virtual EntityRef spawnPlayer(const Context &ctx) = 0; | |||
}; | |||
} |
@@ -6,6 +6,7 @@ | |||
#include <memory> | |||
#include <map> | |||
#include <set> | |||
#include <typeindex> | |||
#include "common.h" | |||
#include "traits/BodyTrait.h" | |||
@@ -14,6 +15,7 @@ | |||
#include "Tile.h" | |||
#include "WorldGen.h" | |||
#include "Entity.h" | |||
#include "Collection.h" | |||
namespace Swan { | |||
@@ -24,11 +26,13 @@ class WorldPlane: NonCopyable { | |||
public: | |||
using ID = uint16_t; | |||
WorldPlane(ID id, World *world, std::unique_ptr<WorldGen> gen): | |||
id_(id), world_(world), gen_(std::move(gen)) {} | |||
WorldPlane( | |||
ID id, World *world, std::unique_ptr<WorldGen> gen, | |||
std::vector<std::unique_ptr<EntityCollection>> &&colls); | |||
Entity *spawnEntity(const std::string &name, const Entity::PackObject ¶ms); | |||
Entity *spawnEntity(std::unique_ptr<Entity> ent); | |||
EntityRef spawnEntity(const std::string &name, const Entity::PackObject ¶ms); | |||
template<typename Ent, typename... Args> | |||
EntityRef spawnEntity(Args&&... args); | |||
void despawnEntity(Entity &ent); | |||
Context getContext(); | |||
@@ -46,14 +50,17 @@ public: | |||
template<typename T> | |||
Iter<T *>getEntsOfType() { | |||
return Iter<T *>([] { return std::nullopt; }); | |||
/* TODO | |||
return mapFilter(entities_.begin(), entities_.end(), [](std::unique_ptr<Entity> &ent) -> std::optional<T *> { | |||
if (T *e = dynamic_cast<T *>(ent.get()); e != nullptr) | |||
return e; | |||
return std::nullopt; | |||
}); | |||
*/ | |||
} | |||
BodyTrait::HasBody *spawnPlayer(); | |||
EntityRef spawnPlayer(); | |||
void breakTile(TilePos pos); | |||
SDL_Color backgroundColor(); | |||
@@ -71,12 +78,21 @@ private: | |||
std::map<std::pair<int, int>, Chunk> chunks_; | |||
std::vector<Chunk *> active_chunks_; | |||
std::vector<std::pair<ChunkPos, Chunk *>> tick_chunks_; | |||
std::vector<std::unique_ptr<Entity>> entities_; | |||
std::vector<std::unique_ptr<EntityCollection>> ent_colls_; | |||
std::unordered_map<std::type_index, EntityCollection *> ent_colls_by_type_; | |||
std::unordered_map<std::string, EntityCollection *> ent_colls_by_name_; | |||
std::deque<Chunk *> chunk_init_list_; | |||
std::vector<std::unique_ptr<Entity>> spawn_list_; | |||
std::vector<Entity *> despawn_list_; | |||
std::vector<TilePos> debug_boxes_; | |||
}; | |||
/* | |||
* WorldPlane | |||
*/ | |||
template<typename Ent, typename... Args> | |||
inline EntityRef WorldPlane::spawnEntity(Args&&... args) { | |||
return ent_colls_by_type_.at(typeid(Ent))->spawn<Ent, Args...>(std::forward<Args>(args)...); | |||
} | |||
} |
@@ -13,7 +13,6 @@ namespace BodyTrait { | |||
class Body; | |||
class HasBody { | |||
public: | |||
virtual ~HasBody() = default; | |||
virtual Body &getBody() = 0; | |||
}; | |||
@@ -13,7 +13,6 @@ namespace InventoryTrait { | |||
class Inventory; | |||
class HasInventory { | |||
public: | |||
virtual ~HasInventory() = default; | |||
virtual Inventory &getInventory() = 0; | |||
}; | |||
@@ -13,18 +13,18 @@ namespace Swan { | |||
std::optional<ModWrapper> Game::loadMod(std::string path, World &world) { | |||
OS::Dynlib dl(path + "/mod"); | |||
auto create = dl.get<std::unique_ptr<Mod> (*)(World &)>("mod_create"); | |||
auto create = dl.get<Mod *(*)(World &)>("mod_create"); | |||
if (create == NULL) { | |||
warn << path << ": No 'mod_create' function!"; | |||
return std::nullopt; | |||
} | |||
std::unique_ptr<Mod> mod = create(world); | |||
std::unique_ptr<Mod> mod(create(world)); | |||
return std::make_optional<ModWrapper>( | |||
std::move(mod), std::move(path), std::move(dl)); | |||
} | |||
void Game::createWorld(const std::string &worldgen, std::vector<std::string> modpaths) { | |||
void Game::createWorld(const std::string &worldgen, const std::vector<std::string> &modpaths) { | |||
world_.reset(new World(this, time(NULL))); | |||
for (auto &modpath: modpaths) { |
@@ -53,9 +53,9 @@ Iter<WorldGen::Factory> ModWrapper::getWorldGens() { | |||
}); | |||
} | |||
Iter<Entity::Factory> ModWrapper::getEntities() { | |||
Iter<EntityCollection::Factory> ModWrapper::getEntities() { | |||
return map(begin(mod_->entities_), end(mod_->entities_), | |||
[](Entity::Factory &fact){ | |||
[](EntityCollection::Factory &fact){ | |||
return fact; | |||
}); | |||
} |
@@ -9,7 +9,6 @@ | |||
#include "log.h" | |||
#include "common.h" | |||
#include "Game.h" | |||
#include "Win.h" | |||
namespace Swan { |
@@ -63,18 +63,15 @@ void World::addMod(ModWrapper &&mod) { | |||
items_[i->name_] = std::move(i); | |||
} | |||
for (auto gen: mod.getWorldGens()) { | |||
worldgens_.emplace( | |||
for (auto fact: mod.getWorldGens()) { | |||
worldgen_factories_.emplace( | |||
std::piecewise_construct, | |||
std::forward_as_tuple(gen.name), | |||
std::forward_as_tuple(gen)); | |||
std::forward_as_tuple(fact.name), | |||
std::forward_as_tuple(fact)); | |||
} | |||
for (auto ent: mod.getEntities()) { | |||
ents_.emplace( | |||
std::piecewise_construct, | |||
std::forward_as_tuple(ent.name), | |||
std::forward_as_tuple(ent)); | |||
for (auto fact: mod.getEntities()) { | |||
ent_coll_factories_.push_back(fact); | |||
} | |||
mods_.push_back(std::move(mod)); | |||
@@ -85,7 +82,8 @@ void World::setWorldGen(std::string gen) { | |||
} | |||
void World::spawnPlayer() { | |||
player_ = planes_[current_plane_].spawnPlayer(); | |||
player_ = dynamic_cast<BodyTrait::HasBody *>( | |||
planes_[current_plane_].spawnPlayer().get()); | |||
} | |||
void World::setCurrentPlane(WorldPlane &plane) { | |||
@@ -94,15 +92,21 @@ void World::setCurrentPlane(WorldPlane &plane) { | |||
WorldPlane &World::addPlane(const std::string &gen) { | |||
WorldPlane::ID id = planes_.size(); | |||
auto it = worldgens_.find(gen); | |||
if (it == end(worldgens_)) { | |||
auto it = worldgen_factories_.find(gen); | |||
if (it == worldgen_factories_.end()) { | |||
panic << "Tried to add plane with non-existant world gen " << gen << "!"; | |||
abort(); | |||
} | |||
std::vector<std::unique_ptr<EntityCollection>> colls; | |||
colls.reserve(ent_coll_factories_.size()); | |||
for (auto &fact: ent_coll_factories_) { | |||
colls.emplace_back(fact.create(fact.name)); | |||
} | |||
WorldGen::Factory &factory = it->second; | |||
std::unique_ptr<WorldGen> g = factory.create(*this); | |||
planes_.emplace_back(id, this, std::move(g)); | |||
planes_.emplace_back(id, this, std::move(g), std::move(colls)); | |||
return planes_[id]; | |||
} | |||
@@ -37,32 +37,24 @@ Context WorldPlane::getContext() { | |||
}; | |||
} | |||
Entity *WorldPlane::spawnEntity(const std::string &name, const Entity::PackObject &obj) { | |||
if (world_->ents_.find(name) == world_->ents_.end()) { | |||
panic << "Tried to spawn a non-existant entity " << name << "!"; | |||
abort(); | |||
WorldPlane::WorldPlane( | |||
ID id, World *world, std::unique_ptr<WorldGen> gen, | |||
std::vector<std::unique_ptr<EntityCollection>> &&colls): | |||
id_(id), world_(world), gen_(std::move(gen)), ent_colls_(std::move(colls)) { | |||
for (auto &coll: ent_colls_) { | |||
ent_colls_by_type_[coll->type()] = coll.get(); | |||
ent_colls_by_name_[coll->name()] = coll.get(); | |||
} | |||
return spawnEntity(world_->ents_[name].create(getContext(), obj)); | |||
} | |||
Entity *WorldPlane::spawnEntity(std::unique_ptr<Entity> ent) { | |||
Entity *ptr = ent.get(); | |||
if (auto has_body = dynamic_cast<BodyTrait::HasBody *>(ent.get()); has_body) { | |||
BodyTrait::Body &body = has_body->getBody(); | |||
BodyTrait::Bounds bounds = body.getBounds(); | |||
body.move({ 0.5f - bounds.size.x / 2, 0 }); | |||
} | |||
spawn_list_.push_back(std::move(ent)); | |||
info << "Spawned entity."; | |||
return ptr; | |||
EntityRef WorldPlane::spawnEntity(const std::string &name, const Entity::PackObject &obj) { | |||
return ent_colls_by_name_.at(name)->spawn(getContext(), obj); | |||
} | |||
void WorldPlane::despawnEntity(Entity &ent) { | |||
// TODO: this | |||
info << "Despawned entity."; | |||
despawn_list_.push_back(&ent); | |||
} | |||
bool WorldPlane::hasChunk(ChunkPos pos) { | |||
@@ -128,7 +120,9 @@ Tile &WorldPlane::getTile(TilePos pos) { | |||
} | |||
Iter<Entity *> WorldPlane::getEntsInArea(Vec2 center, float radius) { | |||
// TODO: Optimize this using fancy data structures. | |||
return Iter<Entity *>([] { return std::nullopt; }); | |||
// TODO: this | |||
/* | |||
return mapFilter(entities_.begin(), entities_.end(), [=](std::unique_ptr<Entity> &ent) | |||
-> std::optional<Entity *> { | |||
@@ -147,9 +141,10 @@ Iter<Entity *> WorldPlane::getEntsInArea(Vec2 center, float radius) { | |||
return ent.get(); | |||
}); | |||
*/ | |||
} | |||
BodyTrait::HasBody *WorldPlane::spawnPlayer() { | |||
EntityRef WorldPlane::spawnPlayer() { | |||
return gen_->spawnPlayer(getContext()); | |||
} | |||
@@ -171,9 +166,10 @@ SDL_Color WorldPlane::backgroundColor() { | |||
} | |||
void WorldPlane::draw(Win &win) { | |||
auto ctx = getContext(); | |||
auto pbounds = world_->player_->getBody().getBounds(); | |||
gen_->drawBackground(getContext(), win, pbounds.pos); | |||
gen_->drawBackground(ctx, win, pbounds.pos); | |||
ChunkPos pcpos = ChunkPos( | |||
(int)floor(pbounds.pos.x / CHUNK_WIDTH), | |||
@@ -184,19 +180,19 @@ void WorldPlane::draw(Win &win) { | |||
Chunk *chunk = chunk_init_list_.front(); | |||
info << "render chunk " << chunk->pos_; | |||
chunk_init_list_.pop_front(); | |||
chunk->render(getContext(), win.renderer_); | |||
chunk->render(ctx, win.renderer_); | |||
} | |||
for (int x = -1; x <= 1; ++x) { | |||
for (int y = -1; y <= 1; ++y) { | |||
auto iter = chunks_.find(pcpos + ChunkPos(x, y)); | |||
if (iter != chunks_.end()) | |||
iter->second.draw(getContext(), win); | |||
iter->second.draw(ctx, win); | |||
} | |||
} | |||
for (auto &ent: entities_) | |||
ent->draw(getContext(), win); | |||
for (auto &coll: ent_colls_) | |||
coll->draw(ctx, win); | |||
if (debug_boxes_.size() > 0) { | |||
for (auto &pos: debug_boxes_) { | |||
@@ -206,35 +202,25 @@ void WorldPlane::draw(Win &win) { | |||
} | |||
void WorldPlane::update(float dt) { | |||
auto ctx = getContext(); | |||
debug_boxes_.clear(); | |||
for (auto &ent: entities_) | |||
ent->update(getContext(), dt); | |||
for (auto &ent: spawn_list_) | |||
entities_.push_back(std::move(ent)); | |||
spawn_list_.clear(); | |||
for (auto entptr: despawn_list_) { | |||
for (auto it = entities_.begin(); it != entities_.end(); ++it) { | |||
if (it->get() == entptr) { | |||
entities_.erase(it); | |||
break; | |||
} | |||
} | |||
} | |||
despawn_list_.clear(); | |||
for (auto &coll: ent_colls_) | |||
coll->update(ctx, dt); | |||
} | |||
void WorldPlane::tick(float dt) { | |||
auto ctx = getContext(); | |||
// Any chunk which has been in use since last tick should be kept alive | |||
for (std::pair<ChunkPos, Chunk *> &ch: tick_chunks_) | |||
ch.second->keepActive(); | |||
tick_chunks_.clear(); | |||
for (auto &ent: entities_) | |||
ent->tick(getContext(), dt); | |||
for (auto &coll: ent_colls_) | |||
coll->tick(ctx, dt); | |||
// Tick all chunks, figure out if any of them should be deleted or compressed | |||
auto iter = active_chunks_.begin(); | |||
auto last = active_chunks_.end(); | |||
while (iter != last) { |
@@ -111,7 +111,7 @@ int main(int argc, char **argv) { | |||
// Create a world | |||
Game game(win); | |||
std::vector<std::string> mods{ "core.mod" }; | |||
game.createWorld("core::default", std::move(mods)); | |||
game.createWorld("core::default", mods); | |||
PerfCounter pcounter; | |||