Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • gamedev/fggl
  • onuralpsezer/fggl
2 results
Show changes
Showing
with 947 additions and 1397 deletions
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Wrappers for types that probably should be implemented, but can be faked for now.
// These are placeholders for the more advanced/specialist data structures we should be using, but mocked out using
// more basic ones.
//
#ifndef FGGL_DS_PLACEHOLDER_HPP
#define FGGL_DS_PLACEHOLDER_HPP
#include <map>
#include <cassert>
namespace fggl::ds {
template<typename T, std::size_t N>
class FakeSlotMap {
public:
using WeakRef = std::size_t;
constexpr static WeakRef BAD_INDEX = 0;
inline bool valid(WeakRef idx) const {
return m_data.find(idx) != m_data.end();
}
WeakRef allocate() {
if (N <= m_data.size()) {
assert(0 && "Fake slot map emulated out of space");
return BAD_INDEX;
}
auto myIdx = m_nextIdx++;
m_data[myIdx] = T();
}
void free(WeakRef idx) {
m_data.erase(idx);
}
T &get(WeakRef idx) const {
assert(valid(idx));
return m_data[idx];
}
T *tryGet(WeakRef idx) const {
if (valid(idx)) {
return &m_data[idx];
}
return nullptr;
}
private:
std::map<WeakRef, T> m_data;
std::size_t m_nextIdx = 1;
};
} // namespace fggl::ds
#endif //FGGL_DS_PLACEHOLDER_HPP
......@@ -12,19 +12,20 @@
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef FGGL_DATA_PROCEDURE_HPP
#define FGGL_DATA_PROCEDURE_HPP
//
// Created by webpigeon on 23/07/22.
//
namespace fggl::data {
#ifndef FGGL_DS_SLOT_MAP_HPP
#define FGGL_DS_SLOT_MAP_HPP
class DataRegistry {
#include "fggl/ds/placeholder.hpp"
public:
DataRegistry();
~DataRegistry();
namespace fggl::ds {
};
template<typename T, std::size_t N>
using SlotMap = FakeSlotMap<T, N>;
}
} // namespace fggl::ds
#endif
#endif //FGGL_DS_SLOT_MAP_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef FGGL_ECS_COMPONENT_HPP
#define FGGL_ECS_COMPONENT_HPP
#include "utility.hpp"
#include "fggl/debug/logging.hpp"
#include <cassert>
#include <cstring>
#include <string>
#include <new>
#include <utility>
#include <iostream>
#include <vector>
#include <unordered_map>
#include "yaml-cpp/yaml.h"
namespace fggl::ecs {
template<typename T>
bool restore_config(T* comp, const YAML::Node& node) {
return false;
}
class ComponentBase {
public:
using data_t = unsigned char;
virtual ~ComponentBase() {};
// in place
virtual void destroy(data_t *data) const = 0;
virtual void move(data_t *src, data_t *dest) const = 0;
virtual void bulkMove(data_t *src, data_t *dest, std::size_t count) = 0;
virtual void construct(data_t *data) const = 0;
// virtual
virtual void *construct() const = 0;
virtual void *restore(const YAML::Node& config) const = 0;
virtual void *copyConstruct(const void *src) = 0;
virtual const char *name() const = 0;
virtual const component_type_t id() const = 0;
virtual std::size_t size() const = 0;
};
template<class C>
class Component : public ComponentBase {
public:
virtual void destroy(data_t *data) const override {
C *location = std::launder(reinterpret_cast<C *>(data));
location->~C();
}
virtual const char *name() const override {
return C::name;
}
virtual const component_type_t id() const {
return Component<C>::typeID();
}
virtual void construct(unsigned char *data) const override {
new(data) C();
}
virtual void* restore(const YAML::Node& config) const override {
C* ptr = new C();
bool restored = restore_config<C>(ptr, config);
if ( !restored ) {
debug::warning("error restoring {}", C::name);
assert( false && "failed to restore configuration when loading type!" );
}
return ptr;
}
void *copyConstruct(const void *src) override {
const C *srcPtr = (C *) src;
return new C(*srcPtr);
}
void *construct() const override {
return new C();
}
virtual void move(data_t *src, data_t *dest) const override {
assert(src != nullptr);
assert(dest != nullptr);
new(&dest[0]) C(std::move(*reinterpret_cast<C *>(src)));
}
virtual void bulkMove(data_t *src, data_t *dest, std::size_t count) {
if (std::is_trivially_copyable<C>::value) {
std::memcpy(dest, src, count * size());
} else {
unsigned char *srcPtr = src;
unsigned char *destPtr = dest;
for (std::size_t i = 0; i < count; ++i) {
new(destPtr) C(std::move(*reinterpret_cast<C *>(srcPtr)));
srcPtr += sizeof(C);
destPtr += sizeof(C);
}
}
}
virtual std::size_t size() const {
return sizeof(C);
}
static component_type_t typeID() {
return TypeIdGenerator<ComponentBase>::GetNewID<C>();
}
};
}
#endif
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef FGGL_ECS_ECS_HPP
#define FGGL_ECS_ECS_HPP
#include "utility.hpp"
#include "component.hpp"
#include <iostream>
#include <algorithm>
#include <cassert>
#include <string>
#include <vector>
#include <unordered_map>
namespace fggl::ecs {
using archToken_t = std::vector<component_type_t>;
struct Archetype {
constexpr static unsigned int default_cap = 0;
const archToken_t type;
std::vector<ComponentBase::data_t *> data;
std::vector<std::size_t> dataSizes;
std::vector<entity_t> entities;
Archetype(const archToken_t &type_a);
inline archToken_t create(const component_type_t cid) const {
assert(!contains(cid));
// create the new type
auto newType = type;
newType.push_back(cid);
std::sort(newType.begin(), newType.end());
return newType;
}
inline bool contains(const component_type_t cid) const {
return (std::find(type.begin(), type.end(), cid) != type.end());
}
};
class ECS {
struct Record {
Archetype *archetype;
std::size_t index;
};
using componentmap_t = std::unordered_map<component_type_t, ComponentBase *>;
using entitymap_t = std::unordered_map<entity_t, Record>;
using archetype_t = std::vector<Archetype *>;
public:
ECS();
~ECS();
entity_t getNewID();
entity_t createEntity();
void removeEntity(const entity_t eid);
template<class C>
void registerComponent() {
component_type_t type = Component<C>::typeID();
if (m_componentMap.find(type) != m_componentMap.end())
return;
m_componentMap.emplace(type, new Component<C>);
}
template<class C>
bool isComponentRegistered() {
component_type_t type = Component<C>::typeID();
return (m_componentMap.find(type) != m_componentMap.end());
}
template<class C, typename... Args>
C *addComponent(const entity_t &id, Args &&... args) {
component_type_t type = Component<C>::typeID();
assert(isComponentRegistered<C>());
Record &record = m_entityArchtypes[id];
Archetype *oldArch = record.archetype;
C *newComp = nullptr;
Archetype *newArch = nullptr;
if (!oldArch) {
archToken_t newID(1, type);
const ComponentBase *const newCompType = m_componentMap[type];
// fetch type
newArch = getArchetype(newID);
assert(newArch->type.size() == 1);
// calculate if we have enouph space to allocate
std::size_t emplacementPos = ensureCapacity(newArch, 0, newCompType);
newComp = new(&newArch->data[0][emplacementPos])C(std::forward<Args>(args)...);
} else {
// check if the arch contains the component
if (oldArch->contains(type)) {
return nullptr;
}
// create a new archetype with the component
auto newID = oldArch->create(type);
newArch = getArchetype(newID);
// relocate the old data to the new archetype
for (std::size_t j = 0; j < newID.size(); ++j) {
const component_type_t compType = newID[j];
const ComponentBase *const comp = m_componentMap.at(compType);
// TODO this seems a little suspect - surely we could allocate all blocks at once?
// if per component the arrays could become out of sync...
int newOffset = ensureCapacity(newArch, j, comp);
int oldIdx = getComponentIdx(oldArch, compType);
if (oldIdx != -1) {
assert(oldArch->contains(compType));
const std::size_t compSize = comp->size();
const std::size_t oldOffset = record.index * compSize;
comp->move(&oldArch->data[oldIdx][oldOffset],
&newArch->data[j][newOffset]);
comp->destroy(&oldArch->data[oldIdx][oldOffset]);
} else {
assert(!oldArch->contains(compType));
newComp = new(&newArch->data[j][newOffset])
C(std::forward<Args>(args)...);
}
}
// ensure the old archetype is still contigious
const int lastEnt = oldArch->entities.size() - 1;
if (lastEnt != record.index) {
for (std::size_t i = 0; i < oldArch->type.size(); ++i) {
const component_type_t typeID = oldArch->type[i];
const ComponentBase *const comp = m_componentMap[typeID];
const std::size_t &compSize = comp->size();
// shift the empty record to the end of the list
std::size_t slotOffset = record.index * compSize;
std::size_t lastOffset = lastEnt * compSize;
// if we're not the last entity, swap
if (slotOffset != lastOffset) {
comp->move(&oldArch->data[i][lastOffset],
&oldArch->data[i][slotOffset]);
comp->destroy(&oldArch->data[i][lastOffset]);
}
}
// fix the position
oldArch->entities[record.index] = oldArch->entities[lastEnt];
}
oldArch->entities.pop_back();
}
// register the new data with the new archetype
newArch->entities.push_back(id);
record.index = newArch->entities.size() - 1;
record.archetype = newArch;
return newComp;
}
template<class C>
void removeComponent(const entity_t &entityId);
template<class C>
bool hasComponent(const entity_t &entityId) const {
const component_type_t componentID = Component<C>::typeID();
const auto *arch = m_entityArchtypes.at(entityId).archetype;
if (arch == nullptr) {
return false;
}
return (std::find(arch->type.begin(), arch->type.end(), componentID) !=
arch->type.end());
}
template<class C>
C *getComponent(const entity_t &entityId) {
assert(hasComponent<C>(entityId));
const auto type = Component<C>::typeID();
const ComponentBase *const newComp = m_componentMap[type];
const auto record = m_entityArchtypes.at(entityId);
const auto *arch = record.archetype;
// JWR: linear search... seems a little suspect, they're ordered after all
for (std::size_t i = 0; i < arch->type.size(); ++i) {
if (arch->type[i] == type) {
return reinterpret_cast<C *>(&(arch->data[i][record.index * newComp->size()]));
}
}
return nullptr;
}
template<class C>
const C *getComponent(const entity_t &entityId) const {
assert(hasComponent<C>(entityId));
const auto type = Component<C>::typeID();
const ComponentBase *const newComp = m_componentMap.at(type);
const auto record = m_entityArchtypes.at(entityId);
const auto *arch = record.archetype;
// JWR: linear search... seems a little suspect, they're ordered after all
for (std::size_t i = 0; i < arch->type.size(); ++i) {
if (arch->type[i] == type) {
return reinterpret_cast<C *>(&(arch->data[i][record.index * newComp->size()]));
}
}
return nullptr;
}
template<class... Cs>
std::vector<entity_t> getEntityWith() const {
// construct the key
archToken_t key;
(key.push_back(Component<Cs>::typeID()), ...);
// entities
std::vector<entity_t> entities;
for (Archetype *arch : m_archetypes) {
if (std::includes(arch->type.begin(), arch->type.end(), key.begin(), key.end())) {
if (!arch->entities.empty()) {
entities.insert(entities.begin(), arch->entities.begin(), arch->entities.end());
}
}
}
return entities;
}
private:
entitymap_t m_entityArchtypes;
archetype_t m_archetypes;
entity_t m_entityIDCounter;
componentmap_t m_componentMap;
Archetype *getArchetype(const archToken_t &id);
inline std::string arch2str(Archetype *arch) {
std::string str;
for (const auto &type : arch->type) {
str += std::to_string(type);
}
return str;
}
inline std::size_t ensureCapacity(Archetype *arch, const int idx, const ComponentBase *const comp) {
const std::size_t &compSize = comp->size();
std::size_t currSize = arch->entities.size() * compSize;
std::size_t newSize = currSize + compSize;
std::size_t cap = arch->dataSizes[idx];
if (newSize > arch->dataSizes[idx]) {
arch->dataSizes[idx] *= 2;
arch->dataSizes[idx] += compSize;
// copy data over
unsigned char *newData = new unsigned char[arch->dataSizes[idx]];
for (std::size_t e = 0; e < arch->entities.size(); ++e) {
const int offset = e * compSize;
comp->move(&arch->data[idx][offset], &newData[offset]);
comp->destroy(&arch->data[idx][offset]);
}
// free the old data and swap the pointers
delete[] arch->data[idx];
arch->data[idx] = newData;
}
return currSize;
}
inline int getComponentIdx(const Archetype *arch, const component_type_t goal) {
// JWR could do binary search for speedup
for (std::size_t i = 0; i < arch->type.size(); ++i) {
if (arch->type[i] == goal)
return i;
}
return -1;
}
};
}
#endif
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 23/10/2021.
//
#ifndef FGGL_ECS3_FAST_CONTAINER_HPP
#define FGGL_ECS3_FAST_CONTAINER_HPP
#include <cstdint>
#include <cstdarg>
#include <cassert>
#include <fggl/ecs3/types.hpp>
namespace fggl::ecs3 {
class Container {
public:
const RecordIdentifier m_identifier;
Container(const TypeRegistry &reg, RecordIdentifier id) :
m_identifier(id),
m_types(reg),
backingStore(nullptr),
m_size(0),
m_capacity(0) {};
~Container() {
delete[] backingStore;
}
std::size_t create();
void remove(std::size_t pos);
std::size_t expand(Container &other, std::size_t otherPos, component_type_t newComp);
void contract(Container &other, std::size_t otherPos);
void ensure(std::size_t size);
inline unsigned char *data_raw(component_type_t type) {
auto seek_id = m_identifier.idx(type);
// you asked for something I don't contain...
if (seek_id == m_identifier.count) {
std::cerr << "asked for " << type << " from " << m_identifier << std::endl;
assert(seek_id != m_identifier.count);
return nullptr;
}
// figure out the offset
return backingStore + offsets[seek_id];
}
template<typename T>
inline T *data() {
auto comp_id = Component<T>::typeID();
return (T *) data_raw(comp_id);
}
template<typename T>
T *set(std::size_t entity, T *compData) {
auto *comps = data<T>();
auto entityPos = idx(entity);
auto compMeta = m_types.template meta<T>();
unsigned char *usrPtr = (unsigned char *) &comps[entityPos];
compMeta->destroy(usrPtr);
compMeta->move((unsigned char *) compData, usrPtr);
return &comps[entityPos];
}
[[nodiscard]]
inline std::size_t size() const {
return m_size;
}
inline std::size_t idx(entity_t entity) {
auto *entityData = data<EntityMeta>();
for (std::size_t i = 0; i < m_size; i++) {
if (entityData[i].id == entity) {
return i;
}
}
return m_size;
}
private:
const TypeRegistry &m_types;
unsigned char *backingStore;
std::size_t offsets[RecordIdentifier::MAX_COMPS]{};
std::size_t m_size;
std::size_t m_capacity;
void move(std::size_t newPos, Container &oldContainer, std::size_t oldPos);
};
}
#endif //FGGL_ECS3_FAST_CONTAINER_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef FGGL_ECS3_FAST_ECS_HPP
#define FGGL_ECS3_FAST_ECS_HPP
#include <cstddef>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <map>
#include <algorithm>
#include <iostream>
#include <type_traits>
#include <fggl/ecs3/utils.hpp>
#include <fggl/ecs3/types.hpp>
#include <fggl/ecs3/fast/Container.hpp>
namespace fggl::ecs3::fast {
using entity_t = unsigned int;
constexpr entity_t NULL_ENTITY = 0;
class World {
public:
explicit World(TypeRegistry &reg) : m_registry(reg), m_last(NULL_ENTITY) {}
entity_t create() {
auto next = m_last++;
auto arch = make_id(1, Component<EntityMeta>::typeID());
auto &container = getContainer(arch);
m_entities[next] = container.m_identifier;
auto pos = container.create();
auto *entityMeta = container.data<EntityMeta>();
entityMeta[pos].id = next;
return next;
}
void remove(entity_t entity) {
auto arch = m_entities.at(entity);
auto container = m_records.at(arch);
auto entPos = container.idx(entity);
container.remove(entPos);
}
inline Container &getContainer(RecordIdentifier &arch) {
try {
return m_records.at(arch);
} catch (std::out_of_range &e) {
auto v = m_records.emplace(std::pair<RecordIdentifier, Container>(arch, {m_registry, arch}));
return v.first->second;
}
}
template<typename T>
T *add(const entity_t entity) {
auto currArch = m_entities.at(entity);
auto newArch = currArch.with<T>();
m_entities[entity] = newArch;
auto &oldContainer = m_records.at(currArch);
auto &newContainer = getContainer(newArch);
auto oldPos = oldContainer.idx(entity);
auto newPos = newContainer.expand(oldContainer, oldPos, Component<T>::typeID());
auto *data = newContainer.template data<T>();
return &data[newPos];
}
template<typename T>
T *set(const entity_t entity, T *record) {
auto currArch = m_entities.at(entity);
// check we already have that component type...
if (currArch.idx(Component<T>::typeID()) == currArch.count) {
add<T>(entity);
currArch = m_entities.at(entity);
}
auto &container = m_records.at(currArch);
auto pos = container.idx(entity);
return container.set<T>(pos, record);
}
template<typename T>
T *get(entity_t entity) {
auto currArch = m_entities.at(entity);
auto pos = currArch.idx(entity);
auto &container = m_records.at(currArch);
auto *data = container.template data<T>();
return &data[pos];
}
template<typename T>
const T *get(entity_t entity) const {
auto currArch = m_entities.at(entity);
auto pos = currArch.idx(entity);
auto &container = m_records.at(currArch);
auto *data = container.template data<T>();
return &data[pos];
}
template<typename T>
void remove(entity_t entity) {
auto currArch = m_entities.at(entity);
auto newArch = currArch.without<T>();
auto &oldContainer = m_records[currArch];
auto &newContainer = m_records[newArch];
auto oldPos = oldContainer.idx(entity);
auto newPos = newContainer.create();
m_records[newArch].contract(newPos, oldContainer, oldPos);
}
template<typename... T>
std::vector<entity_t> findMatching() const {
return {};
}
private:
TypeRegistry &m_registry;
std::map<RecordIdentifier, Container> m_records;
std::map<entity_t, RecordIdentifier> m_entities;
entity_t m_last{};
};
}
#endif
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 23/10/2021.
//
#ifndef FGGL_ECS3_PROTOTYPE_WORLD_HPP
#define FGGL_ECS3_PROTOTYPE_WORLD_HPP
#include <map>
#include <functional>
#include <unordered_set>
#include "fggl/ecs3/types.hpp"
#include "fggl/debug/logging.hpp"
#include <yaml-cpp/yaml.h>
/**
* A component based implementation of a game world.
*
* This is not a true ECS but exposes a similar API to it for testing (with a lot less headaches).
*/
namespace fggl::ecs3::prototype {
using EntityCallback = std::function<void(const entity_t)>;
class Entity {
public:
bool m_abstract;
explicit Entity(entity_t id) : m_abstract(false), m_id(id) {};
Entity(const Entity &entity) : m_id(entity.m_id), m_components(entity.m_components) {
//spdlog::info("entity created fro copy: {}", m_id);
}
~Entity() = default;
template<typename C>
C *add() {
C *ptr = new C();
m_components[Component<C>::typeID()] = ptr;
return ptr;
}
void *add(std::shared_ptr<ComponentBase> t) {
void *ptr = t->construct();
m_components[t->id()] = ptr;
return ptr;
}
void* add(const std::shared_ptr<ComponentBase>& compMeta, const YAML::Node& config) {
void* ptr = compMeta->restore(config);
m_components[ compMeta->id() ] = ptr;
return ptr;
}
template<typename C>
C *set(const C *ptr) {
C *newPtr = new C(*ptr);
m_components[Component<C>::typeID()] = newPtr;
return newPtr;
}
void *set(const std::shared_ptr<ComponentBase> &t, const void *ptr) {
void *newPtr = t->copyConstruct(ptr);
m_components[t->id()] = newPtr;
return newPtr;
}
template<typename C>
C *get() const {
void *ptr = m_components.at(Component<C>::typeID());
return (C *) ptr;
}
template<typename C>
void remove(){
m_components.erase(Component<C>::typeID());
}
inline void *get(component_type_t t) {
return m_components.at(t);
}
std::vector<component_type_t> getComponentIDs() {
std::vector<component_type_t> comps{};
for (auto &[k, _] : m_components) {
comps.push_back(k);
}
return comps;
}
bool hasComponents(std::vector<component_type_t> &Cs) const {
for (auto c : Cs) {
if (m_components.find(c) == m_components.end()) {
return false;
}
}
return true;
}
private:
entity_t m_id;
std::map<component_type_t, void *> m_components;
};
class World {
public:
explicit World(TypeRegistry &reg) : m_types(reg), m_next(1), m_entities() {};
~World() = default;
entity_t create(bool abstract) {
auto nextID = m_next++;
m_entities.emplace(nextID, nextID);
auto &entity = m_entities.at(nextID);
entity.m_abstract = abstract;
// meta data
auto *meta = entity.add<ecs3::EntityMeta>();
meta->id = nextID;
meta->abstract = abstract;
meta->typeName = "";
return nextID;
}
inline entity_t createFromPrototype(const std::string& name) {
auto prototype = findPrototype(name);
if ( prototype == NULL_ENTITY) {
debug::log(debug::Level::warning, "attempted to create from non-existant prototype: {}", name);
return NULL_ENTITY;
}
return copy( prototype );
}
entity_t copy(entity_t prototype) {
auto clone = create(false);
auto components = getComponents(prototype);
for (auto component : components) {
auto protoComp = get(prototype, component);
set(clone, component, protoComp);
}
return clone;
}
void addFromConfig(entity_t entity, component_type_t type, const YAML::Node& node) {
auto meta = m_types.meta(type);
auto& entityObj = m_entities.at(entity);
entityObj.add(meta, node);
m_types.fireAdd(this, entity, meta->id());
}
void createFromSpec(entity_t entity, const YAML::Node& compConfig) {
if ( compConfig ) {
for (const auto& itr : compConfig ) {
const auto name = itr.first.as<std::string>();
const auto& config = itr.second;
auto compType = m_types.find( name.c_str() );
addFromConfig(entity, compType, config);
}
}
}
inline auto alive(entity_t entity) const -> bool {
return entity != NULL_ENTITY
&& m_killList.find( entity ) == m_killList.end()
&& m_entities.find( entity ) != m_entities.end();
}
inline auto exists(entity_t entity) const -> bool {
return entity != NULL_ENTITY
&& m_entities.find( entity ) != m_entities.end();
}
void destroy(entity_t entity) {
assert( alive(entity) && "attempted to kill null entity" );
// TOOD resolve and clean components
//m_entities.erase(entity);
m_killList.insert(entity);
}
void reapEntities() {
for (const auto& entity : m_killList) {
//auto& entityObj = m_entities.at(entity);
//entityObj.clear();
for (auto& listener : m_deathListeners) {
listener( entity );
}
m_entities.erase(entity);
}
m_killList.clear();
}
inline TypeRegistry &types() {
return m_types;
}
std::vector<entity_t> all() {
std::vector<entity_t> entities{};
for (auto &[eid, entity] : m_entities) {
entities.push_back(eid);
}
return entities;
}
std::vector<component_type_t> getComponents(entity_t entityID) {
assert(alive(entityID) && "attempted to get components on dead entity");
std::vector<component_type_t> components{};
auto &entity = m_entities.at(entityID);
auto comps = entity.getComponentIDs();
for (auto id : comps) {
components.push_back(id);
}
return components;
}
template<typename... Cs>
std::vector<entity_t> findMatching() const {
// construct the key
std::vector<ecs::component_type_t> key;
(key.push_back(Component<Cs>::typeID()), ...);
// entities
std::vector<entity_t> entities{};
for (auto &[eid, entity] : m_entities) {
if (entity.hasComponents(key) && !entity.m_abstract) {
entities.push_back(eid);
}
}
return entities;
}
template<typename ...Cs>
bool has(entity_t entityIdx) const {
if ( !alive(entityIdx)) {
return false;
}
std::vector<ecs::component_type_t> key;
(key.push_back(Component<Cs>::typeID()), ...);
return m_entities.at(entityIdx).hasComponents(key);
}
template<typename C>
C *add(entity_t entity_id) {
assert( alive(entity_id) && "attempted to add component on null entity" );
//spdlog::info("component '{}' added to '{}'", C::name, entity_id);
auto &entity = m_entities.at(entity_id);
auto comp = entity.template add<C>();
m_types.fireAdd(this, entity_id, Component<C>::typeID());
return comp;
}
void *add(entity_t entity_id, component_type_t component_id) {
assert( alive(entity_id) && "attempted to add component on null entity" );
auto meta = m_types.meta(component_id);
auto &entity = m_entities.at(entity_id);
void *ptr = entity.add(meta);
m_types.fireAdd(this, entity_id, meta->id());
return ptr;
}
template<typename C>
C *set(entity_t entity_id, const C *ptr) {
assert( alive( entity_id ) && "attempted to set component on null entity" );
//spdlog::info("component '{}' set on '{}'", C::name, entity_id);
auto &entity = m_entities.at(entity_id);
auto comp = entity.set<C>(ptr);
m_types.fireAdd(this, entity_id, Component<C>::typeID());
return comp;
}
void *set(entity_t entity_id, component_type_t cid, const void *ptr) {
assert( alive( entity_id ) && "attempted to set component on null entity" );
auto &entity = m_entities.at(entity_id);
auto cMeta = m_types.meta(cid);
auto comp = entity.set(cMeta, ptr);
m_types.fireAdd(this, entity_id, cid);
return comp;
}
template<typename C>
C* tryGet(entity_t entity_id) const {
if ( entity_id == NULL_ENTITY) {
return nullptr;
}
try {
return get<C>(entity_id);
} catch ( std::out_of_range& e) {
fggl::debug::info("component {} on {} did not exist", C::name, entity_id);
return nullptr;
}
}
template<typename C>
C *get(entity_t entity_id) const {
assert( exists(entity_id) && "attempted to get component on null entity" );
const auto& entity = m_entities.at(entity_id);
return entity.get<C>();
}
template<typename C>
void remove(entity_t entity_id) {
assert( alive(entity_id) && "attempted to remove component on null entity" );
try {
auto &entity = m_entities.at(entity_id);
try {
return entity.remove<C>();
} catch ( std::out_of_range& e ) {
std::cerr << "entity " << entity_id << " does not have component "<< C::name << std::endl;
}
} catch ( std::out_of_range& e) {
std::cerr << "tried to delete component on entity that didn't exist, entity was: " << entity_id << std::endl;
}
}
void *get(entity_t entity_id, component_type_t componentType) {
assert( exists(entity_id) && "attempted to get component on null entity" );
auto &entity = m_entities.at(entity_id);
return entity.get(componentType);
}
void addDeathListener(const EntityCallback& callback) {
m_deathListeners.emplace_back(callback);
}
entity_t findPrototype(const std::string& name) const {
for ( const auto& [entity, obj] : m_entities ) {
if ( !obj.m_abstract ){
continue;
}
auto* metaData = obj.get<EntityMeta>();
if ( metaData->typeName == name){
return entity;
}
}
return NULL_ENTITY;
}
private:
std::vector<EntityCallback > m_deathListeners;
TypeRegistry &m_types;
entity_t m_next;
std::map<entity_t, Entity> m_entities;
std::unordered_set<entity_t> m_killList;
};
}
#endif //FGGL_ECS3_PROTOTYPE_WORLD_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef FGGL_ECS3_TYPES_HPP
#define FGGL_ECS3_TYPES_HPP
#include <cstdarg>
#include <utility>
#include <functional>
#include <fggl/ecs/component.hpp>
#include <fggl/ecs3/utils.hpp>
#include <iostream>
#include <memory>
#include <algorithm>
#include <map>
#include <unordered_map>
namespace fggl::ecs3 {
namespace {
using namespace fggl::ecs;
};
namespace prototype {
class World;
}
using fggl::ecs::component_type_t;
class ModuleManager;
using callback_t = std::function<void(prototype::World *, ecs3::entity_t)>;
struct TypeCallbacks {
std::vector<callback_t> add;
};
// core component types
struct EntityMeta {
constexpr static const char name[] = "meta";
entity_t id;
bool abstract;
std::string typeName;
};
struct RecordIdentifier {
constexpr static std::size_t MAX_COMPS = 32;
component_type_t types[MAX_COMPS];
std::size_t count;
[[nodiscard]]
inline std::size_t idx(component_type_t t) const {
return utils::search(types, count, t);
}
template<typename T>
[[nodiscard]]
RecordIdentifier with() const {
// check the caller wasn't a muppet
const auto typeID = ecs::Component<T>::typeID();
if (idx(typeID) != count) {
return *this;
}
RecordIdentifier re{};
re.count = count + 1;
re.types[count] = ecs::Component<T>::typeID();
// add old types
for (std::size_t i = 0; i < count; ++i) {
re.types[i] = types[i];
}
std::sort(re.types, re.types + re.count);
return re;
}
template<typename T>
[[nodiscard]]
RecordIdentifier without() const {
// check the caller wasn't a muppet
const auto typeID = ecs::Component<T>::typeID();
const auto typeIdx = idx(typeID);
if (typeIdx == count) {
return *this;
}
RecordIdentifier re{};
re.count = count - 1;
// add old types
for (std::size_t i = 0, j = 0; i < count; ++i) {
if (typeIdx != i) {
re.types[j] = types[i];
j++;
}
}
std::sort(re.types, re.types + re.count);
return re;
}
bool operator<(const RecordIdentifier &other) const {
if (count < other.count) {
return true;
} else if (count > other.count) {
return false;
} else {
for (std::size_t i = 0; i < count; i++) {
if (types[i] != other.types[i]) {
return types[i] < other.types[i];
}
}
return false;
}
}
bool operator==(const RecordIdentifier &arg) const {
if (arg.count != count) {
return false;
}
for (std::size_t i = 0; i < count; i++) {
if (types[i] != arg.types[i]) {
return false;
}
}
return true;
}
bool operator!=(const RecordIdentifier &arg) const {
return !(*this == arg);
}
};
std::ostream &operator<<(std::ostream &out, RecordIdentifier const &curr);
inline RecordIdentifier make_id(std::size_t count, ...) {
assert(count < RecordIdentifier::MAX_COMPS);
RecordIdentifier re{};
std::va_list args;
va_start(args, count);
for (std::size_t i = 0; i < count; ++i) {
re.types[i] = va_arg(args, component_type_t);
}
va_end(args);
re.count = count;
std::sort(re.types, re.types + count);
return re;
}
class TypeRegistry {
public:
TypeRegistry() : m_last_virtual(9000), m_callbacks() {
// core types always exist
make<EntityMeta>();
}
template<typename T>
void make() {
auto type_id = Component<T>::typeID();
if (m_types.find(type_id) != m_types.end())
return;
m_types[type_id] = std::make_shared<Component<T>>();
}
template<typename T>
bool exists() {
auto type_id = Component<T>::typeID();
return m_types.find(type_id) != m_types.end();
}
template<typename T>
std::shared_ptr<fggl::ecs::ComponentBase> meta() const {
auto type_id = Component<T>::typeID();
return m_types.at(type_id);
}
inline std::shared_ptr<fggl::ecs::ComponentBase> meta(component_type_t type_id) const {
try {
return m_types.at(type_id);
} catch (std::out_of_range& err) {
std::cerr << "asked for metadata on type " << type_id << " but no such type is in the type system" << std::endl;
return nullptr;
}
}
inline component_type_t find(const char *name) const {
for (const auto &[type, meta] : m_types) {
if (std::strcmp(name, meta->name()) == 0) {
return type;
}
}
debug::warning("asked for unknown/unregistered component type: {}", name);
assert(false && "unknown component type, are you sure it was registered?");
return 0;
}
inline void make_virtual(std::shared_ptr<ComponentBase> vtype) {
auto type_id = m_last_virtual++;
m_types[type_id] = std::move(vtype);
}
void callbackAdd(component_type_t component, const callback_t &callback) {
m_callbacks[component].add.push_back(callback);
}
void fireAdd(prototype::World *world, entity_t entity, component_type_t type) {
try {
auto &callbacks = m_callbacks.at(type).add;
for (auto &callback : callbacks) {
callback(world, entity);
}
} catch (std::out_of_range &e) {
//spdlog::debug("no callbacks for {}", m_types[type]->name());
}
}
private:
std::unordered_map<component_type_t, std::shared_ptr<fggl::ecs::ComponentBase>> m_types;
component_type_t m_last_virtual;
std::map<component_type_t, TypeCallbacks> m_callbacks;
};
};
#endif
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 24/07/22.
//
#ifndef FGGL_ENTITY_ENTITY_HPP
#define FGGL_ENTITY_ENTITY_HPP
#include <cstdint>
#include <map>
#include <vector>
#include "fggl/debug/logging.hpp"
#include "fggl/vendor/entt.hpp"
namespace fggl::entity {
using EntityID = entt::entity;
constexpr EntityID INVALID = entt::null;
class EntityManager {
public:
inline EntityID create() {
return m_registry.create();
}
inline void destroy(EntityID entity) {
m_registry.destroy(entity);
}
template<typename Component, typename... Args>
inline Component &add(EntityID entity, Args &&... args) {
return m_registry.get_or_emplace<Component>(entity, std::forward<Args>(args)...);
}
template<typename Component>
Component &get(EntityID entity) {
#ifndef NDEBUG
if (!has<Component>(entity)) {
debug::error("Entity {} has no component of type {}", (uint64_t) entity, debug::demangle(typeid(Component).name()));
assert(false && "Entity was missing component - use tryGet or fix definition");
}
#endif
return m_registry.get<Component>(entity);
}
template<typename Component>
const Component &get(EntityID entity) const {
return m_registry.get<Component>(entity);
}
template<typename Component>
Component *tryGet(EntityID entity) {
return m_registry.try_get<Component>(entity);
}
template<typename Component>
const Component *tryGet(EntityID entity, const Component* defaultValue = nullptr) const {
auto* comp = m_registry.try_get<Component>(entity);
return comp == nullptr ? defaultValue : comp;
}
template<typename ...Components>
auto find() const {
return m_registry.view<Components...>();
}
EntityID findByName(const std::string& name) const {
auto itr = m_names.find(name);
if ( itr == m_names.end() ){
return INVALID;
}
return m_registry.valid(itr->second) ? itr->second : INVALID;
}
inline void setName(const std::string& name, EntityID eid ) {
if ( eid == INVALID ) {
m_names.erase(name);
} else {
assert(m_registry.valid(eid));
m_names[name] = eid;
}
}
template<typename ...Components>
bool has(EntityID idx) const {
return m_registry.template all_of<Components...>(idx);
}
bool hasTag(EntityID entity, fggl::util::GUID tag) {
const auto mapItr = m_tags.find( tag );
if ( mapItr == m_tags.end() || !m_registry.valid(entity) ) {
return false;
}
return std::find(mapItr->second.begin(), mapItr->second.end(), entity) != mapItr->second.end();
}
void addTag(const EntityID entity, const fggl::util::GUID tag) {
assert( m_registry.valid(entity) );
auto& tagged = m_tags[ tag ];
tagged.push_back( entity );
}
void removeTag(const EntityID entity, const fggl::util::GUID tag) {
auto mapItr = m_tags.find(tag);
if ( mapItr == m_tags.end() ) {
return;
}
std::remove_if( mapItr->second.begin(), mapItr->second.end(), [entity](auto other) { return other == entity;} );
}
inline std::vector<EntityID> findByTag(const char* tag) {
return findByTag( util::make_guid_rt(tag) );
}
std::vector<EntityID> findByTag(const fggl::util::GUID tag){
auto mapItr = m_tags.find(tag);
if ( mapItr == m_tags.end() ) {
return {};
}
return mapItr->second;
}
inline bool exists(EntityID idx) const {
return m_registry.valid(idx);
}
inline bool alive(EntityID idx) const {
return m_registry.valid(idx);
}
private:
entt::registry m_registry;
std::map<std::string, EntityID> m_names;
std::map<fggl::util::GUID, std::vector<EntityID>> m_tags;
};
struct Entity {
static Entity make(EntityManager &manager, EntityID idx) {
return Entity{idx, manager};
}
EntityID id;
EntityManager &manager;
template<typename Component>
Component &get() {
return manager.get<Component>(id);
}
template<typename Component>
const Component &get() const {
return manager.get<Component>(id);
}
};
} // namespace fggl::entity
#endif //FGGL_ENTITY_ENTITY_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 20/08/22.
//
#ifndef FGGL_ENTITY_GRIDWORLD_ZONE_HPP
#define FGGL_ENTITY_GRIDWORLD_ZONE_HPP
#include <array>
#include <vector>
#include "fggl/math/types.hpp"
#include "fggl/assets/types.hpp"
#include "fggl/entity/entity.hpp"
namespace fggl::entity::grid {
using GridPos = math::vec2i;
constexpr auto ASSET_TILESET = assets::AssetType::make("tileset");
template<typename T, uint32_t width, uint32_t height>
struct Grid {
public:
Grid() = default;
inline T& get(GridPos pos) {
assert(inBounds(pos));
return m_cells[getCellIndex(pos)];
}
const T& get(GridPos pos) const {
assert(inBounds(pos));
return m_cells[getCellIndex(pos)];
}
inline void set(GridPos pos, T value) {
assert(inBounds(pos));
m_cells[getCellIndex(pos)] = value;
}
inline bool inBounds(GridPos pos) const {
return 0 <= pos.x && pos.x <= size.x &&
0 <= pos.y && pos.y <= size.y;
}
private:
constexpr static math::vec2i size = math::vec2ui(width, height);
std::array<T, size.x * size.y> m_cells;
inline uint32_t getCellIndex(GridPos pos) const {
assert( inBounds(pos));
return pos.y * size.x + pos.x;
}
};
struct FloorTile {
constexpr static uint8_t IMPOSSIBLE = 0;
uint8_t moveCost = IMPOSSIBLE;
math::vec3 colour = gfx::colours::CYAN;
};
struct WallTile {
bool render = false;
math::vec3 colour = gfx::colours::CYAN;
};
struct WallState {
uint32_t north = 0;
uint32_t west = 0;
};
struct TileSet {
std::vector<FloorTile> m_floors;
std::vector<WallTile> m_walls;
};
/**
* A 2D representation of a space.
*
* @tparam width the grid width in units
* @tparam height the grid height in units
*/
template<uint32_t width, uint32_t height>
struct Area2D {
public:
constexpr static std::array<math::vec2i, 4> DIRECTIONS{{ {-1, 0}, {0, -1}, {1, 0}, {0, 1} }};
inline explicit Area2D(TileSet& tiles) : m_tiles(tiles) {
clear();
}
[[nodiscard]]
inline bool inBounds(GridPos pos) const {
return 0 <= pos.x && pos.x <= width
&& 0 <= pos.y && pos.y <= height;
}
void clear() {
WallState noWall;
for (auto xPos = 0U; xPos<width; ++xPos) {
for (auto yPos=0U; yPos<height; ++yPos) {
m_floors.set({xPos, yPos}, 0);
m_walls.set({xPos, yPos}, noWall);
}
}
}
inline FloorTile& floorAt(uint32_t xPos, uint32_t yPos) {
return m_tiles.m_floors.at( m_floors.get({xPos, yPos}) );
}
inline void setFloorAt(uint32_t xPos, uint32_t yPos, uint32_t floor) {
m_floors.set({xPos, yPos}, floor);
}
inline WallTile& wallAt(uint32_t xPos, uint32_t yPos, bool north) {
if (north) {
return m_tiles.m_walls.at(m_walls.get({xPos, yPos}).north);
} else {
return m_tiles.m_walls.at(m_walls.get({xPos, yPos}).west);
}
}
inline void setWallAt(uint32_t xPos, uint32_t yPos, bool north, uint32_t wall) {
auto& state = m_walls.get({xPos, yPos});
if (north) {
state.north = wall;
} else {
state.west = wall;
}
}
inline bool canMove(GridPos pos) const {
if ( !inBounds(pos) ) {
return false;
}
return m_tiles.m_floors[m_floors.get(pos)].moveCost != FloorTile::IMPOSSIBLE;
}
inline bool canMove(GridPos pos, math::vec2i dir) const {
return canMove(pos + dir) && !blocked(pos, dir);
}
inline bool blocked(GridPos pos, math::vec2i dir) const;
EntityManager& entities() {
return m_entities;
}
inline void neighbours(math::vec2i pos, std::vector<math::vec2i> &neighbours) const {
for (auto direction : DIRECTIONS) {
if ( canMove(pos, direction) ) {
auto result = pos + direction;
neighbours.push_back(result);
}
}
}
private:
TileSet& m_tiles;
Grid<uint32_t, width, height> m_floors;
Grid<WallState, width + 1, height + 1> m_walls;
EntityManager m_entities;
};
template<uint32_t width, uint32_t height>
bool Area2D<width, height>::blocked(GridPos pos, math::vec2i dir) const {
auto targetPos = pos;
if ( dir.x == 1 || dir.y == 1 ) {
targetPos = pos + dir;
}
if ( !inBounds(targetPos) ) {
return true;
}
auto& wallObj = m_walls.get(targetPos);
if ( dir.y != 0 ) {
return wallObj.north != 0;
}
if (dir.x != 0) {
return wallObj.west != 0;
}
return true;
}
} // namespace fggl::entity::gridworld
#endif //FGGL_ENTITY_GRIDWORLD_ZONE_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 23/07/22.
//
#ifndef FGGL_ENTITY_LOADER_LOADER_HPP
#define FGGL_ENTITY_LOADER_LOADER_HPP
#include <functional>
#include <map>
#include <utility>
#include "fggl/util/guid.hpp"
#include "fggl/modules/module.hpp"
#include "fggl/entity/entity.hpp"
#include "fggl/entity/loader/spec.hpp"
#include "fggl/assets/loader.hpp"
namespace fggl::entity {
constexpr auto ENTITY_PROTOTYPE = assets::make_asset_type("entity/prototype");
constexpr auto ENTITY_SCENE = assets::make_asset_type("entity/scene");
using FactoryFunc = std::function<void(const ComponentSpec &config, EntityManager &, const EntityID &, modules::Services &svc)>;
using CustomiseFunc = std::function<void(EntityManager &, const EntityID &)>;
struct FactoryInfo {
FactoryFunc factory;
CustomiseFunc finalise = nullptr;
};
class EntityFactory {
public:
constexpr static const modules::ServiceName service = modules::make_service("fggl::entity:Factory");
inline EntityFactory(modules::Services &services) : m_services(services) {}
EntityID create(const EntityType &spec, EntityManager &manager, const CustomiseFunc &customise = nullptr) {
std::vector<CustomiseFunc> finishers;
// build the setup
auto entity = setupComponents(spec, manager, finishers);
if ( entity == entity::INVALID ) {
debug::error("EntityFactory: failed to build from prototype {}", std::to_string(spec.get()));
return entity::INVALID;
}
// if requested, allow the user to customize the creation
if ( customise != nullptr ) {
customise(manager, entity);
}
// run finishers for components
for ( auto& finisher : finishers ) {
finisher( manager, entity );
}
// use metadata to finalise
processTags(manager, entity, spec);
return entity;
}
void processTags(EntityManager& manager, EntityID id, EntityType spec) {
auto type = m_prototypes.at(spec);
for ( auto& tag : type.tags ) {
manager.addTag(id, tag);
}
}
void log_known_types() const {
debug::debug("dumping known types:");
for(const auto& [k,v] : m_factories) {
debug::debug("\ttype: {}", k);
}
}
void define(EntityType type, const EntitySpec &spec) {
m_prototypes[type] = spec;
}
// ability to set and unset factory functions
inline void bind(const ComponentID &configNode, FactoryFunc factory, CustomiseFunc finalise = nullptr) {
m_factories[configNode].factory = std::move(factory);
m_factories[configNode].finalise = std::move(finalise);
}
inline void unbind(const ComponentID &configNode) {
m_factories.erase(configNode);
}
inline FactoryInfo& getInfo(ComponentID comp) {
return m_factories.at(comp);
}
private:
modules::Services m_services;
std::map<ComponentID, FactoryInfo> m_factories;
std::map<EntityType, EntitySpec> m_prototypes;
entity::EntityID setupComponents(EntityType entityType,
EntityManager &manager,
std::vector<CustomiseFunc> &finishers) {
assert(entityType != NO_PARENT && "setup components called with NO_PARENT?!");
auto entity = manager.create();
std::vector<ComponentID> loadedComps;
auto currentType = entityType;
while (currentType != NO_PARENT) {
const auto& specEntry = m_prototypes.find( currentType );
if ( specEntry == m_prototypes.end() ) {
debug::warning("Asked to setup {}, for {} but was not a known prototype", specEntry->first, entityType);
return entity::INVALID;
}
auto entitySpec = specEntry->second;
debug::debug("constructing {} for {} ({} comps)", currentType, entityType, entitySpec.components.size());
assert( entitySpec.ordering.size() == entitySpec.components.size() && "ordering incorrect size, bad things happend!" );
for (auto &component : entitySpec.ordering) {
// skip comps loaded by children
if (std::find(loadedComps.begin(), loadedComps.end(), component) != loadedComps.end()) {
continue;
}
try {
auto &data = getComponent(entitySpec, component);
loadedComps.push_back(component);
auto &info = m_factories.at(component);
info.factory(data, manager, entity, m_services);
if (info.finalise != nullptr) {
finishers.push_back(info.finalise);
}
} catch (std::out_of_range &ex) {
debug::error( "EntityFactory: Unknown component factory type '{}'", component );
log_known_types();
manager.destroy(entity);
return entity::INVALID;
}
}
currentType = entitySpec.parent;
}
return entity;
}
ComponentSpec &getComponent(EntitySpec &prototype, util::GUID compToken) {
auto compItr = prototype.components.find(compToken);
if (compItr != prototype.components.end()) {
return compItr->second;
}
if (prototype.parent == NO_PARENT) {
throw std::out_of_range("EntityFactory: no such component!");
}
return getComponent(m_prototypes.at(prototype.parent), compToken);
}
};
assets::AssetRefRaw load_prototype(assets::Loader* loader, const assets::AssetID &guid, const assets::LoaderContext& data, EntityFactory* factory);
assets::AssetRefRaw load_scene(assets::Loader* loader, const assets::AssetID& asset, const assets::LoaderContext& data, void* ptr);
} // namespace fggl::entity
#endif //FGGL_ENTITY_LOADER_LOADER_HPP
......@@ -13,36 +13,23 @@
*/
//
// Created by webpigeon on 04/06/22.
// Created by webpigeon on 24/07/22.
//
#ifndef FGGL_ECS3_PROTOTYPE_LOADER_HPP
#define FGGL_ECS3_PROTOTYPE_LOADER_HPP
#ifndef FGGL_ENTITY_LOADER_SERIALISE_HPP
#define FGGL_ENTITY_LOADER_SERIALISE_HPP
#include "yaml-cpp/yaml.h"
#include "fggl/math/types.hpp"
#include "fggl/phys/types.hpp"
#include "fggl/gfx/phong.hpp"
#include "fggl/data/storage.hpp"
#include "fggl/data/model.hpp"
#include "fggl/data/procedural.hpp"
#include "fggl/ecs3/ecs.hpp"
namespace fggl::ecs3 {
void load_prototype_node( ecs3::World& world, const YAML::Node& node);
void load_prototype_file( ecs3::World& world, data::Storage& storage, const std::string& name );
} // namespace fggl::ecs3
#include "fggl/phys/types.hpp"
namespace YAML {
template<>
struct convert<fggl::math::vec3> {
static Node encode(const fggl::math::vec3& rhs){
static Node encode(const fggl::math::vec3 &rhs) {
Node node;
node.push_back(rhs.x);
node.push_back(rhs.y);
......@@ -50,8 +37,8 @@ namespace YAML {
return node;
}
static bool decode(const Node& node, fggl::math::vec3& rhs) {
if ( !node.IsSequence() || node.size() != 3) {
static bool decode(const Node &node, fggl::math::vec3 &rhs) {
if (!node.IsSequence() || node.size() != 3) {
return false;
}
......@@ -63,88 +50,108 @@ namespace YAML {
};
template<>
struct convert<fggl::math::quat> {
static Node encode(const fggl::math::quat& rhs){
struct convert<fggl::math::vec2> {
static Node encode(const fggl::math::vec2 &rhs) {
Node node;
node.push_back(rhs.x);
node.push_back(rhs.y);
node.push_back(rhs.z);
node.push_back(rhs.w);
return node;
}
static bool decode(const Node& node, fggl::math::quat& rhs) {
if ( !node.IsSequence() || node.size() != 3) {
static bool decode(const Node &node, fggl::math::vec2 &rhs) {
if (!node.IsSequence() || node.size() != 2) {
return false;
}
rhs.x = node[0].as<float>();
rhs.y = node[1].as<float>();
rhs.z = node[2].as<float>();
rhs.w = node[3].as<float>();
return true;
}
};
constexpr const char* TYPE_KINEMATIC = "kinematic";
constexpr const char* TYPE_STATIC = "static";
constexpr const char* TYPE_DYNAMIC = "dynamic";
template<>
struct convert<fggl::phys::BodyType> {
static Node encode(const fggl::phys::BodyType& rhs) {
switch (rhs) {
case fggl::phys::BodyType::KINEMATIC:
return Node(TYPE_KINEMATIC);
case fggl::phys::BodyType::STATIC:
return Node(TYPE_STATIC);
default:
case fggl::phys::BodyType::DYNAMIC:
return Node(TYPE_DYNAMIC);
}
struct convert<fggl::data::Vertex> {
static Node encode(const fggl::data::Vertex &rhs) {
Node node;
node["position"] = rhs.posititon;
node["normal"] = rhs.normal;
node["colour"] = rhs.colour;
node["texPos"] = rhs.texPos;
return node;
}
static bool decode(const Node& node, fggl::phys::BodyType& rhs) {
const auto value = node.as<std::string>();
if ( value == TYPE_KINEMATIC ) {
rhs = fggl::phys::BodyType::KINEMATIC;
} else if ( value == TYPE_STATIC ) {
rhs = fggl::phys::BodyType::STATIC;
} else {
rhs = fggl::phys::BodyType::DYNAMIC;
static bool decode(const Node &node, fggl::data::Vertex &rhs) {
if (!node.IsSequence() || node.size() != 2) {
return false;
}
rhs.posititon = node["position"].as<fggl::math::vec3>();
rhs.normal = node["normal"].as<fggl::math::vec3>();
rhs.colour = node["colour"].as<fggl::math::vec3>();
rhs.texPos = node["texPos"].as<fggl::math::vec2>();
return true;
}
};
template<>
struct convert<fggl::phys::BodyType> {
static Node encode(const fggl::phys::BodyType &rhs) {
Node node;
if (rhs == fggl::phys::BodyType::STATIC) {
node = "static";
} else if (rhs == fggl::phys::BodyType::DYNAMIC) {
node = "dynamic";
} else if (rhs == fggl::phys::BodyType::KINEMATIC) {
node = "kinematic";
}
return node;
}
}
static bool decode(const Node &node, fggl::phys::BodyType &rhs) {
auto strVal = node.as<std::string>();
if (strVal == "static") {
rhs = fggl::phys::BodyType::STATIC;
return true;
}
namespace fggl::ecs {
if (strVal == "dynamic") {
rhs = fggl::phys::BodyType::DYNAMIC;
return true;
}
constexpr int DEFAULT_STACKS = 16;
constexpr int DEFAULT_SLICES = 16;
constexpr const char* SHAPE_SPHERE_VALUE{"sphere"};
constexpr const char* SHAPE_BOX_VALUE{"box"};
if (strVal == "kinematic") {
rhs = fggl::phys::BodyType::KINEMATIC;
return true;
}
// scene template specialisations
template<>
bool restore_config(math::Transform* comp, const YAML::Node& node);
return false;
}
};
template<>
bool restore_config(gfx::PhongMaterial* comp, const YAML::Node& node);
struct convert<fggl::util::GUID> {
static Node encode(const fggl::util::GUID &rhs) {
Node node;
node = rhs.get();
return node;
}
template<>
bool restore_config(data::StaticMesh* meshComp, const YAML::Node& node);
static bool decode(const Node &node, fggl::util::GUID &rhs) {
auto longVal = node.as<uint64_t>(0);
template<>
bool restore_config(phys::RigidBody* body, const YAML::Node& node);
if (longVal == 0) {
// probably meant to hash it...
auto stringVal = node.as<std::string>();
rhs = fggl::util::make_guid_rt(stringVal);
return true;
}
template<>
bool restore_config(phys::CollisionCallbacks* callbacks, const YAML::Node& node);
// it's probably pre-hashed...
rhs = fggl::util::GUID::make(longVal);
return true;
}
};
template<>
bool restore_config(phys::CollisionCache* cache, const YAML::Node& node);
}
#endif //FGGL_ECS3_PROTOTYPE_LOADER_HPP
#endif //FGGL_ENTITY_LOADER_SERIALISE_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 23/07/22.
//
#ifndef FGGL_ENTITY_LOADER_SPEC_HPP
#define FGGL_ENTITY_LOADER_SPEC_HPP
#include "fggl/util/guid.hpp"
#include "fggl/entity/loader/serialise.hpp"
#include <map>
namespace fggl::entity {
using ComponentID = util::GUID;
using EntityType = util::GUID;
constexpr EntityType NO_PARENT = util::make_guid("FGGL_NULL_PARENT");
struct ComponentSpec {
template<typename T>
T get(const std::string &key, const T &fallback) const {
return config[key].template as<T>(fallback);
}
template<typename T>
void set(const std::string &key, const T &value) {
config[key] = value;
}
inline bool has(const std::string &key) const {
return (bool) (config[key]);
}
YAML::Node config;
};
struct EntitySpec {
EntityType parent = NO_PARENT;
std::vector<util::GUID> tags;
std::vector<ComponentID> ordering;
std::map<ComponentID, ComponentSpec> components;
inline void addComp(ComponentID cmp, const ComponentSpec& spec) {
components[cmp] = spec;
ordering.push_back(cmp);
}
};
} // namespace fggl::entity
#endif //FGGL_ENTITY_LOADER_SPEC_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 24/07/22.
//
#ifndef FGGL_ENTITY_MODULE_HPP
#define FGGL_ENTITY_MODULE_HPP
#include "fggl/modules/module.hpp"
#include "fggl/assets/loader.hpp"
#include "fggl/assets/packed/adapter.hpp"
#include "fggl/entity/loader/loader.hpp"
namespace fggl::entity {
constexpr auto MIME_SCENE = assets::from_mime("x-fggl/scene");
struct ECS {
constexpr static const char *name = "fggl::entity::ECS";
constexpr static const std::array<modules::ServiceName, 1> provides = {
EntityFactory::service
};
constexpr static const std::array<modules::ServiceName, 2> depends = {
assets::Loader::service,
assets::CheckinAdapted::service
};
static bool factory(modules::ServiceName name, modules::Services &serviceManager);
};
void install_component_factories(EntityFactory *factory);
} // namespace fggl::entity
#endif //FGGL_ENTITY_MODULE_HPP
......@@ -29,6 +29,7 @@
#include "fggl/audio/null_audio.hpp"
#include "fggl/audio/openal/module.hpp"
//! Root namespace
namespace fggl {
}
......
......@@ -15,7 +15,8 @@
#ifndef FGGL_GFX_CAMERA_HPP
#define FGGL_GFX_CAMERA_HPP
#include <fggl/math/types.hpp>
#include "fggl/math/types.hpp"
#include "fggl/entity/entity.hpp"
namespace fggl::gfx {
......@@ -26,36 +27,42 @@ namespace fggl::gfx {
float fov = glm::radians(45.0f);
float nearPlane = 0.1f;
float farPlane = 100.0f;
inline math::mat4 perspective() const {
return glm::perspective(fov, aspectRatio, nearPlane, farPlane);
}
};
inline math::mat4 calc_proj_matrix(const Camera* camera) {
return glm::perspective(camera->fov, camera->aspectRatio, camera->nearPlane, camera->farPlane);
inline math::mat4 calc_proj_matrix(const Camera &camera) {
return glm::perspective(camera.fov, camera.aspectRatio, camera.nearPlane, camera.farPlane);
}
inline math::Ray get_camera_ray(const ecs3::World& world, const ecs3::entity_t camera, math::vec2 position) {
auto* const camTransform = world.get<fggl::math::Transform>(camera);
auto* const camComp = world.get<fggl::gfx::Camera>(camera);
inline math::Ray get_camera_ray(const entity::EntityManager &world,
const entity::EntityID camera,
math::vec2 position) {
auto &camTransform = world.get<fggl::math::Transform>(camera);
auto &camComp = world.get<fggl::gfx::Camera>(camera);
const auto projMatrix = fggl::gfx::calc_proj_matrix(camComp);
const auto viewMatrix = fggl::math::calc_view_matrix(camTransform);
glm::vec4 startNDC {
glm::vec4 startNDC{
position.x,
position.y,
-1.0f,
1.0f
};
glm::vec4 endNDC {
glm::vec4 endNDC{
position.x,
position.y,
0.0f,
1.0f
};
fggl::math::mat4 M = glm::inverse( projMatrix * viewMatrix );
fggl::math::mat4 M = glm::inverse(projMatrix * viewMatrix);
glm::vec3 start = M * startNDC;
glm::vec3 end = M * endNDC;
return { start, glm::normalize(end - start) };
return {start, glm::normalize(end - start)};
}
};
......
......@@ -20,9 +20,10 @@
#define FGGL_GFX_INTERFACES_HPP
#include "fggl/gfx/paint.hpp"
#include "fggl/ecs3/ecs.hpp"
#include "fggl/entity/entity.hpp"
#include "fggl/modules/module.hpp"
//! Classes responsible for rendering content
namespace fggl::gfx {
struct Bounds {
......@@ -34,7 +35,7 @@ namespace fggl::gfx {
class Graphics {
public:
constexpr static const modules::ModuleService service = modules::make_service("fggl::gfx::Graphics");
constexpr static const auto service = modules::make_service("fggl::gfx::Graphics");
virtual ~Graphics() = default;
virtual void clear() = 0;
......@@ -43,10 +44,9 @@ namespace fggl::gfx {
virtual Bounds canvasBounds() = 0;
virtual void draw2D(const Paint &paint) = 0;
virtual void drawScene(ecs3::World&) = 0;
virtual void drawScene(entity::EntityManager &, bool debugMode = false) = 0;
};
} // namespace fggl::gfx
#endif //FGGL_GFX_INTERFACES_HPP
......@@ -24,7 +24,6 @@
* FGGL OpenGL 4.x rendering backend.
*/
namespace fggl::gfx {
}
#endif
......@@ -24,6 +24,7 @@
#endif
#include <glad/glad.h>
typedef void* (* GLADloadproc)(const char *name);
typedef void *(*GLADloadproc)(const char *name);
#endif
......@@ -16,10 +16,9 @@
#define FGGL_GFX_OGL_RENDERER_HPP
#include <fggl/data/model.hpp>
#include <fggl/ecs3/ecs.hpp>
#include <fggl/gfx/ogl/backend.hpp>
#include <fggl/gfx/ogl/shader.hpp>
#include "fggl/gfx/ogl4/models.hpp"
#include "fggl/gfx/ogl4/canvas.hpp"
#include "fggl/gfx/ogl4/debug.hpp"
......@@ -31,32 +30,7 @@
namespace fggl::gfx {
enum GlRenderType {
triangles = GL_TRIANGLES,
triangle_strip = GL_TRIANGLE_STRIP
};
struct GlRenderToken {
constexpr static const char name[] = "RenderToken";
GLuint vao;
GLuint buffs[2];
GLuint idxOffset;
GLsizei idxSize;
GLuint pipeline;
GLuint restartVertex;
GlRenderType renderType = triangles;
};
class GlMeshRenderer {
public:
using token_t = GlRenderToken;
token_t upload(fggl::data::Mesh &mesh);
void render(ecs3::World &ecs, ecs3::entity_t camera, float dt);
float total;
};
using GlFunctionLoader = GLADloadproc;
/**
* Class responsible for managing the OpenGL context.
......@@ -67,16 +41,16 @@ namespace fggl::gfx {
*/
class OpenGL4Backend : public Graphics {
public:
explicit OpenGL4Backend(data::Storage* storage, gui::FontLibrary* fonts);
explicit OpenGL4Backend(data::Storage *storage, gui::FontLibrary *fonts, assets::AssetManager *assets, GlFunctionLoader loader);
~OpenGL4Backend() override = default;
// copy bad
OpenGL4Backend(const OpenGL4Backend&) = delete;
OpenGL4Backend& operator=(const OpenGL4Backend&) = delete;
OpenGL4Backend(const OpenGL4Backend &) = delete;
OpenGL4Backend &operator=(const OpenGL4Backend &) = delete;
// move (probably) bad
OpenGL4Backend(OpenGL4Backend&&) = delete;
OpenGL4Backend&& operator=(OpenGL4Backend&&) = delete;
OpenGL4Backend(OpenGL4Backend &&) = delete;
OpenGL4Backend &&operator=(OpenGL4Backend &&) = delete;
/**
* Clear the backing buffer.
......@@ -103,12 +77,12 @@ namespace fggl::gfx {
*
* @param world the world to render
*/
void drawScene(ecs3::World& world) override;
void drawScene(entity::EntityManager &world, bool debugMode=false) override;
/**
* Get the 2D canvas bounds.
*
* @return
* @return the canvas bounds
*/
inline Bounds canvasBounds() override {
return m_canvasRenderer->bounds();
......@@ -119,9 +93,9 @@ namespace fggl::gfx {
std::unique_ptr<ogl4::CanvasRenderer> m_canvasRenderer;
std::unique_ptr<ogl4::DebugRenderer> m_debugRenderer;
std::unique_ptr<ShaderCache> m_cache;
GLuint m_canvasPipeline;
data::Storage* m_storage;
gui::FontLibrary* m_fontLibrary;
std::shared_ptr<ogl::Shader> m_canvasPipeline;
data::Storage *m_storage;
gui::FontLibrary *m_fontLibrary;
};
}; // namespace fggl::gfx
......