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
/build | /build | ||||
.tags | .tags | ||||
compile_commands.json | compile_commands.json | ||||
.clangd |
add_compile_options(-fdiagnostics-color=always) | add_compile_options(-fdiagnostics-color=always) | ||||
endif() | 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) | add_compile_options(-std=c++2a -Wall -Wextra -Wpedantic -Wno-unused-parameter) | ||||
if(CMAKE_BUILD_TYPE STREQUAL Sanitize OR CMAKE_BUILD_TYPE STREQUAL "") | if(CMAKE_BUILD_TYPE STREQUAL Sanitize OR CMAKE_BUILD_TYPE STREQUAL "") | ||||
message(STATUS "Build mode: Sanitize") | message(STATUS "Build mode: Sanitize") |
} | } | ||||
} | } | ||||
Swan::BodyTrait::HasBody *DefaultWorldGen::spawnPlayer(const Swan::Context &ctx) { | |||||
Swan::EntityRef DefaultWorldGen::spawnPlayer(const Swan::Context &ctx) { | |||||
int x = 0; | 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 }); | |||||
} | } |
void drawBackground(const Swan::Context &ctx, Swan::Win &win, Swan::Vec2 pos) override; | void drawBackground(const Swan::Context &ctx, Swan::Win &win, Swan::Vec2 pos) override; | ||||
SDL_Color backgroundColor(Swan::Vec2 pos) override; | SDL_Color backgroundColor(Swan::Vec2 pos) override; | ||||
void genChunk(Swan::WorldPlane &plane, Swan::Chunk &chunk) 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: | private: | ||||
Swan::Tile::ID genTile(Swan::TilePos pos); | Swan::Tile::ID genTile(Swan::TilePos pos); |
void onTileBreak(const Swan::Context &ctx, Swan::TilePos pos, Swan::Tile &tile) { | void onTileBreak(const Swan::Context &ctx, Swan::TilePos pos, Swan::Tile &tile) { | ||||
if (tile.dropped_item_) { | 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_; | 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); | |||||
} | } |
#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); | |||||
} | |||||
} |
class WorldPlane; | class WorldPlane; | ||||
class Game; | class Game; | ||||
class Entity { | |||||
class Entity: NonCopyable { | |||||
public: | public: | ||||
using PackObject = std::unordered_map<std::string_view, msgpack::object>; | using PackObject = std::unordered_map<std::string_view, msgpack::object>; | ||||
std::unique_ptr<Entity> (*create)(const Context &ctx, const PackObject &obj); | std::unique_ptr<Entity> (*create)(const Context &ctx, const PackObject &obj); | ||||
}; | }; | ||||
Entity() = default; | |||||
Entity(Entity &&) = default; | |||||
virtual ~Entity() = default; | virtual ~Entity() = default; | ||||
virtual void draw(const Context &ctx, Win &win) {} | virtual void draw(const Context &ctx, Win &win) {} |
EventListener(EventEmitterInterface *emitter, size_t id): | EventListener(EventEmitterInterface *emitter, size_t id): | ||||
emitter_(emitter), id_(id) {} | emitter_(emitter), id_(id) {} | ||||
EventListener(EventListener &&other): | |||||
EventListener(EventListener &&other) noexcept: | |||||
emitter_(other.emitter_), id_(other.id_) { | emitter_(other.emitter_), id_(other.id_) { | ||||
other.emitter_ = nullptr; | other.emitter_ = nullptr; | ||||
} | } | ||||
EventListener &operator=(EventListener &&other) { | |||||
EventListener &operator=(EventListener &&other) noexcept { | |||||
emitter_ = other.emitter_; | emitter_ = other.emitter_; | ||||
id_ = other.id_; | id_ = other.id_; | ||||
other.emitter_ = nullptr; | other.emitter_ = nullptr; | ||||
} | } | ||||
private: | private: | ||||
SlotVector<Callback, SlotVectorDefaultSentinel<nullptr_t>> callbacks_; | |||||
SlotVector<Callback, SlotVectorDefaultSentinel<std::nullptr_t>> callbacks_; | |||||
}; | }; | ||||
} | } |
mouse_pos_(0, 0) {} | mouse_pos_(0, 0) {} | ||||
std::optional<ModWrapper> loadMod(std::string path, World &world); | 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) { | void onKeyDown(SDL_Keysym sym) { | ||||
pressed_keys_[sym.scancode] = true; | pressed_keys_[sym.scancode] = true; |
#include <string> | #include <string> | ||||
#include <vector> | #include <vector> | ||||
#include <memory> | #include <memory> | ||||
#include <type_traits> | |||||
#include <SDL2/SDL.h> | #include <SDL2/SDL.h> | ||||
#include "Tile.h" | #include "Tile.h" | ||||
#include "Item.h" | #include "Item.h" | ||||
#include "WorldGen.h" | #include "WorldGen.h" | ||||
#include "Entity.h" | #include "Entity.h" | ||||
#include "Collection.h" | |||||
#include "Resource.h" | #include "Resource.h" | ||||
#include "OS.h" | #include "OS.h" | ||||
#include "util.h" | #include "util.h" | ||||
void registerWorldGen(const std::string &name) { | void registerWorldGen(const std::string &name) { | ||||
worldgens_.push_back(WorldGen::Factory{ | worldgens_.push_back(WorldGen::Factory{ | ||||
.name = name_ + "::" + name, | .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> | template<typename Ent> | ||||
void registerEntity(const std::string &name) { | 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, | .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)); | |||||
} | } | ||||
}); | }); | ||||
} | } | ||||
std::vector<Tile::Builder> tiles_; | std::vector<Tile::Builder> tiles_; | ||||
std::vector<Item::Builder> items_; | std::vector<Item::Builder> items_; | ||||
std::vector<WorldGen::Factory> worldgens_; | std::vector<WorldGen::Factory> worldgens_; | ||||
std::vector<Entity::Factory> entities_; | |||||
std::vector<EntityCollection::Factory> entities_; | |||||
}; | }; | ||||
class ModWrapper { | class ModWrapper { | ||||
Iter<std::unique_ptr<Tile>> buildTiles(const ResourceManager &resources); | Iter<std::unique_ptr<Tile>> buildTiles(const ResourceManager &resources); | ||||
Iter<std::unique_ptr<Item>> buildItems(const ResourceManager &resources); | Iter<std::unique_ptr<Item>> buildItems(const ResourceManager &resources); | ||||
Iter<WorldGen::Factory> getWorldGens(); | Iter<WorldGen::Factory> getWorldGens(); | ||||
Iter<Entity::Factory> getEntities(); | |||||
Iter<EntityCollection::Factory> getEntities(); | |||||
std::unique_ptr<Mod> mod_; | std::unique_ptr<Mod> mod_; | ||||
std::string path_; | std::string path_; |
size_t operator-(const Iterator &other) const { | size_t operator-(const Iterator &other) const { | ||||
return idx_ - other.idx_; | return idx_ - other.idx_; | ||||
} | } | ||||
void operator=(const Iterator &other) { | |||||
idx_ = other.idx_; | |||||
vec_ = other.vec_; | |||||
} | |||||
private: | private: | ||||
SlotVector<T, Sentinel> *vec_; | SlotVector<T, Sentinel> *vec_; | ||||
T &operator[](size_t idx) { return vec_[idx]; } | T &operator[](size_t idx) { return vec_[idx]; } | ||||
T &at(size_t idx) { return vec_.at(idx); } | T &at(size_t idx) { return vec_.at(idx); } | ||||
size_t size() { return vec_.size(); } | |||||
bool empty() { return vec_.size() == free_.size(); } | bool empty() { return vec_.size() == free_.size(); } | ||||
Iterator begin() { Iterator it(this, 0); it.seek(); return it; } | Iterator begin() { Iterator it(this, 0); it.seek(); return it; } |
#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)]; | |||||
}; | |||||
} |
#include "WorldPlane.h" | #include "WorldPlane.h" | ||||
#include "WorldGen.h" | #include "WorldGen.h" | ||||
#include "Entity.h" | #include "Entity.h" | ||||
#include "Collection.h" | |||||
#include "Resource.h" | #include "Resource.h" | ||||
#include "Mod.h" | #include "Mod.h" | ||||
#include "EventEmitter.h" | #include "EventEmitter.h" | ||||
std::unordered_map<std::string, Tile::ID> tiles_map_; | std::unordered_map<std::string, Tile::ID> tiles_map_; | ||||
std::unordered_map<std::string, std::unique_ptr<Item>> items_; | 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_; | BodyTrait::HasBody *player_; | ||||
Game *game_; | Game *game_; |
#include "common.h" | #include "common.h" | ||||
#include "Chunk.h" | #include "Chunk.h" | ||||
#include "Entity.h" | #include "Entity.h" | ||||
#include "Collection.h" | |||||
#include "traits/BodyTrait.h" | #include "traits/BodyTrait.h" | ||||
#include "Vector2.h" | #include "Vector2.h" | ||||
public: | public: | ||||
struct Factory { | struct Factory { | ||||
const std::string name; | const std::string name; | ||||
std::unique_ptr<WorldGen> (*create)(World &world); | |||||
std::unique_ptr<WorldGen> (*const create)(World &world); | |||||
}; | }; | ||||
virtual ~WorldGen() = default; | virtual ~WorldGen() = default; | ||||
virtual SDL_Color backgroundColor(Vec2 pos) = 0; | virtual SDL_Color backgroundColor(Vec2 pos) = 0; | ||||
virtual void genChunk(WorldPlane &plane, Chunk &chunk) = 0; | virtual void genChunk(WorldPlane &plane, Chunk &chunk) = 0; | ||||
virtual BodyTrait::HasBody *spawnPlayer(const Context &ctx) = 0; | |||||
virtual EntityRef spawnPlayer(const Context &ctx) = 0; | |||||
}; | }; | ||||
} | } |
#include <memory> | #include <memory> | ||||
#include <map> | #include <map> | ||||
#include <set> | #include <set> | ||||
#include <typeindex> | |||||
#include "common.h" | #include "common.h" | ||||
#include "traits/BodyTrait.h" | #include "traits/BodyTrait.h" | ||||
#include "Tile.h" | #include "Tile.h" | ||||
#include "WorldGen.h" | #include "WorldGen.h" | ||||
#include "Entity.h" | #include "Entity.h" | ||||
#include "Collection.h" | |||||
namespace Swan { | namespace Swan { | ||||
public: | public: | ||||
using ID = uint16_t; | 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); | void despawnEntity(Entity &ent); | ||||
Context getContext(); | Context getContext(); | ||||
template<typename T> | template<typename T> | ||||
Iter<T *>getEntsOfType() { | Iter<T *>getEntsOfType() { | ||||
return Iter<T *>([] { return std::nullopt; }); | |||||
/* TODO | |||||
return mapFilter(entities_.begin(), entities_.end(), [](std::unique_ptr<Entity> &ent) -> std::optional<T *> { | return mapFilter(entities_.begin(), entities_.end(), [](std::unique_ptr<Entity> &ent) -> std::optional<T *> { | ||||
if (T *e = dynamic_cast<T *>(ent.get()); e != nullptr) | if (T *e = dynamic_cast<T *>(ent.get()); e != nullptr) | ||||
return e; | return e; | ||||
return std::nullopt; | return std::nullopt; | ||||
}); | }); | ||||
*/ | |||||
} | } | ||||
BodyTrait::HasBody *spawnPlayer(); | |||||
EntityRef spawnPlayer(); | |||||
void breakTile(TilePos pos); | void breakTile(TilePos pos); | ||||
SDL_Color backgroundColor(); | SDL_Color backgroundColor(); | ||||
std::map<std::pair<int, int>, Chunk> chunks_; | std::map<std::pair<int, int>, Chunk> chunks_; | ||||
std::vector<Chunk *> active_chunks_; | std::vector<Chunk *> active_chunks_; | ||||
std::vector<std::pair<ChunkPos, Chunk *>> tick_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::deque<Chunk *> chunk_init_list_; | ||||
std::vector<std::unique_ptr<Entity>> spawn_list_; | |||||
std::vector<Entity *> despawn_list_; | |||||
std::vector<TilePos> debug_boxes_; | 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)...); | |||||
} | |||||
} | } |
class Body; | class Body; | ||||
class HasBody { | class HasBody { | ||||
public: | public: | ||||
virtual ~HasBody() = default; | |||||
virtual Body &getBody() = 0; | virtual Body &getBody() = 0; | ||||
}; | }; | ||||
class Inventory; | class Inventory; | ||||
class HasInventory { | class HasInventory { | ||||
public: | public: | ||||
virtual ~HasInventory() = default; | |||||
virtual Inventory &getInventory() = 0; | virtual Inventory &getInventory() = 0; | ||||
}; | }; | ||||
std::optional<ModWrapper> Game::loadMod(std::string path, World &world) { | std::optional<ModWrapper> Game::loadMod(std::string path, World &world) { | ||||
OS::Dynlib dl(path + "/mod"); | 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) { | if (create == NULL) { | ||||
warn << path << ": No 'mod_create' function!"; | warn << path << ": No 'mod_create' function!"; | ||||
return std::nullopt; | return std::nullopt; | ||||
} | } | ||||
std::unique_ptr<Mod> mod = create(world); | |||||
std::unique_ptr<Mod> mod(create(world)); | |||||
return std::make_optional<ModWrapper>( | return std::make_optional<ModWrapper>( | ||||
std::move(mod), std::move(path), std::move(dl)); | 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))); | world_.reset(new World(this, time(NULL))); | ||||
for (auto &modpath: modpaths) { | for (auto &modpath: modpaths) { |
}); | }); | ||||
} | } | ||||
Iter<Entity::Factory> ModWrapper::getEntities() { | |||||
Iter<EntityCollection::Factory> ModWrapper::getEntities() { | |||||
return map(begin(mod_->entities_), end(mod_->entities_), | return map(begin(mod_->entities_), end(mod_->entities_), | ||||
[](Entity::Factory &fact){ | |||||
[](EntityCollection::Factory &fact){ | |||||
return fact; | return fact; | ||||
}); | }); | ||||
} | } |
#include "log.h" | #include "log.h" | ||||
#include "common.h" | #include "common.h" | ||||
#include "Game.h" | |||||
#include "Win.h" | #include "Win.h" | ||||
namespace Swan { | namespace Swan { |
items_[i->name_] = std::move(i); | 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::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)); | mods_.push_back(std::move(mod)); | ||||
} | } | ||||
void World::spawnPlayer() { | void World::spawnPlayer() { | ||||
player_ = planes_[current_plane_].spawnPlayer(); | |||||
player_ = dynamic_cast<BodyTrait::HasBody *>( | |||||
planes_[current_plane_].spawnPlayer().get()); | |||||
} | } | ||||
void World::setCurrentPlane(WorldPlane &plane) { | void World::setCurrentPlane(WorldPlane &plane) { | ||||
WorldPlane &World::addPlane(const std::string &gen) { | WorldPlane &World::addPlane(const std::string &gen) { | ||||
WorldPlane::ID id = planes_.size(); | 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 << "!"; | panic << "Tried to add plane with non-existant world gen " << gen << "!"; | ||||
abort(); | 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; | WorldGen::Factory &factory = it->second; | ||||
std::unique_ptr<WorldGen> g = factory.create(*this); | 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]; | return planes_[id]; | ||||
} | } | ||||
}; | }; | ||||
} | } | ||||
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) { | void WorldPlane::despawnEntity(Entity &ent) { | ||||
// TODO: this | |||||
info << "Despawned entity."; | info << "Despawned entity."; | ||||
despawn_list_.push_back(&ent); | |||||
} | } | ||||
bool WorldPlane::hasChunk(ChunkPos pos) { | bool WorldPlane::hasChunk(ChunkPos pos) { | ||||
} | } | ||||
Iter<Entity *> WorldPlane::getEntsInArea(Vec2 center, float radius) { | 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) | return mapFilter(entities_.begin(), entities_.end(), [=](std::unique_ptr<Entity> &ent) | ||||
-> std::optional<Entity *> { | -> std::optional<Entity *> { | ||||
return ent.get(); | return ent.get(); | ||||
}); | }); | ||||
*/ | |||||
} | } | ||||
BodyTrait::HasBody *WorldPlane::spawnPlayer() { | |||||
EntityRef WorldPlane::spawnPlayer() { | |||||
return gen_->spawnPlayer(getContext()); | return gen_->spawnPlayer(getContext()); | ||||
} | } | ||||
} | } | ||||
void WorldPlane::draw(Win &win) { | void WorldPlane::draw(Win &win) { | ||||
auto ctx = getContext(); | |||||
auto pbounds = world_->player_->getBody().getBounds(); | auto pbounds = world_->player_->getBody().getBounds(); | ||||
gen_->drawBackground(getContext(), win, pbounds.pos); | |||||
gen_->drawBackground(ctx, win, pbounds.pos); | |||||
ChunkPos pcpos = ChunkPos( | ChunkPos pcpos = ChunkPos( | ||||
(int)floor(pbounds.pos.x / CHUNK_WIDTH), | (int)floor(pbounds.pos.x / CHUNK_WIDTH), | ||||
Chunk *chunk = chunk_init_list_.front(); | Chunk *chunk = chunk_init_list_.front(); | ||||
info << "render chunk " << chunk->pos_; | info << "render chunk " << chunk->pos_; | ||||
chunk_init_list_.pop_front(); | chunk_init_list_.pop_front(); | ||||
chunk->render(getContext(), win.renderer_); | |||||
chunk->render(ctx, win.renderer_); | |||||
} | } | ||||
for (int x = -1; x <= 1; ++x) { | for (int x = -1; x <= 1; ++x) { | ||||
for (int y = -1; y <= 1; ++y) { | for (int y = -1; y <= 1; ++y) { | ||||
auto iter = chunks_.find(pcpos + ChunkPos(x, y)); | auto iter = chunks_.find(pcpos + ChunkPos(x, y)); | ||||
if (iter != chunks_.end()) | 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) { | if (debug_boxes_.size() > 0) { | ||||
for (auto &pos: debug_boxes_) { | for (auto &pos: debug_boxes_) { | ||||
} | } | ||||
void WorldPlane::update(float dt) { | void WorldPlane::update(float dt) { | ||||
auto ctx = getContext(); | |||||
debug_boxes_.clear(); | 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) { | void WorldPlane::tick(float dt) { | ||||
auto ctx = getContext(); | |||||
// Any chunk which has been in use since last tick should be kept alive | // Any chunk which has been in use since last tick should be kept alive | ||||
for (std::pair<ChunkPos, Chunk *> &ch: tick_chunks_) | for (std::pair<ChunkPos, Chunk *> &ch: tick_chunks_) | ||||
ch.second->keepActive(); | ch.second->keepActive(); | ||||
tick_chunks_.clear(); | 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 iter = active_chunks_.begin(); | ||||
auto last = active_chunks_.end(); | auto last = active_chunks_.end(); | ||||
while (iter != last) { | while (iter != last) { |
// Create a world | // Create a world | ||||
Game game(win); | Game game(win); | ||||
std::vector<std::string> mods{ "core.mod" }; | std::vector<std::string> mods{ "core.mod" }; | ||||
game.createWorld("core::default", std::move(mods)); | |||||
game.createWorld("core::default", mods); | |||||
PerfCounter pcounter; | PerfCounter pcounter; | ||||