diff --git a/demo/main.cpp b/demo/main.cpp index a341298886c9f2497498b464a705fda7f19e180e..381e4b3cd8a538884a43d6b6ce04a29164ea18a2 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -1,7 +1,4 @@ #include <filesystem> -#include <glm/ext/matrix_transform.hpp> -#include <glm/geometric.hpp> -#include <glm/trigonometric.hpp> #include <iostream> #include <fggl/gfx/window.hpp> @@ -11,11 +8,11 @@ #include <fggl/gfx/ogl/compat.hpp> #include <fggl/data/procedural.hpp> -#include <fggl/ecs/ecs.hpp> -#include <fggl/debug/debug.h> #include <fggl/data/storage.hpp> #include <fggl/util/chrono.hpp> +#include <fggl/ecs3/ecs.hpp> +#include <fggl/debug/debug.h> #include <imgui.h> constexpr bool showNormals = false; @@ -25,7 +22,7 @@ template <typename T> int sgn(T val) { } // prototype of resource discovery -void discover(std::filesystem::path base) { +void discover(const std::filesystem::path& base) { std::vector< std::filesystem::path > contentPacks; for ( auto& item : std::filesystem::directory_iterator(base) ) { @@ -55,10 +52,10 @@ camera_type cam_mode = cam_free; using namespace fggl::input; using InputManager = std::shared_ptr<fggl::input::Input>; -void process_arcball(fggl::ecs::ECS& ecs, InputManager input, fggl::ecs::entity_t cam) { +void process_arcball(fggl::ecs3::World& ecs, InputManager input, fggl::ecs::entity_t cam) { // see https://asliceofrendering.com/camera/2019/11/30/ArcballCamera/ - auto camTransform = ecs.getComponent<fggl::math::Transform>(cam); - auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam); + auto* camTransform = ecs.get<fggl::math::Transform>(cam); + auto* camComp = ecs.get<fggl::gfx::Camera>(cam); auto& mouse = input->mouse; glm::vec4 position(camTransform->origin(), 1.0f); @@ -95,7 +92,7 @@ constexpr float PAN_SPEED = 0.05f; constexpr glm::mat4 MAT_IDENTITY(1.0f); -void process_freecam(fggl::ecs::ECS& ecs, InputManager input, fggl::ecs::entity_t cam) { +void process_freecam(fggl::ecs3::World& ecs, InputManager input, fggl::ecs::entity_t cam) { float rotationValue = 0.0f; glm::vec3 translation(0.0f); @@ -132,8 +129,8 @@ void process_freecam(fggl::ecs::ECS& ecs, InputManager input, fggl::ecs::entity_ } // apply rotation/movement - auto camTransform = ecs.getComponent<fggl::math::Transform>(cam); - auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam); + auto camTransform = ecs.get<fggl::math::Transform>(cam); + auto camComp = ecs.get<fggl::gfx::Camera>(cam); glm::vec4 position( camTransform->origin(), 1.0f ); glm::vec4 pivot( camComp->target, 1.0f ); @@ -161,7 +158,7 @@ void process_freecam(fggl::ecs::ECS& ecs, InputManager input, fggl::ecs::entity_ camComp->target = pivot; } -void process_edgescroll(fggl::ecs::ECS& ecs, InputManager input, fggl::ecs::entity_t cam) { +void process_edgescroll(fggl::ecs3::World& ecs, InputManager input, fggl::ecs::entity_t cam) { glm::vec3 translation(0.0f); auto& mouse = input->mouse; @@ -184,8 +181,8 @@ void process_edgescroll(fggl::ecs::ECS& ecs, InputManager input, fggl::ecs::enti } // apply rotation/movement - auto camTransform = ecs.getComponent<fggl::math::Transform>(cam); - auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam); + auto camTransform = ecs.get<fggl::math::Transform>(cam); + auto camComp = ecs.get<fggl::gfx::Camera>(cam); glm::vec4 position( camTransform->origin(), 1.0f ); glm::vec4 pivot( camComp->target, 1.0f ); @@ -209,9 +206,9 @@ void process_edgescroll(fggl::ecs::ECS& ecs, InputManager input, fggl::ecs::enti } -void process_camera(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, InputManager input, fggl::ecs::entity_t cam) { - auto camTransform = ecs.getComponent<fggl::math::Transform>(cam); - auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam); +void process_camera(fggl::gfx::Window& window, fggl::ecs3::World& ecs, InputManager input, fggl::ecs::entity_t cam) { + auto camTransform = ecs.get<fggl::math::Transform>(cam); + auto camComp = ecs.get<fggl::gfx::Camera>(cam); const glm::vec3 dir = ( camTransform->origin() - camComp->target ); const glm::vec3 forward = glm::normalize( dir ); @@ -234,24 +231,27 @@ void process_camera(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, InputManager int main(int argc, char* argv[]) { // setup ECS - fggl::ecs::ECS ecs; - auto inputs = std::make_shared<fggl::input::Input>(); + fggl::ecs3::TypeRegistry types; + fggl::ecs3::ModuleManager modules(types); + + fggl::ecs3::World ecs(types); - // build our main window - auto glfwModule = fggl::gfx::ecsInitGlfw(ecs, inputs); + auto inputs = std::make_shared<fggl::input::Input>(); + auto glfwModule = modules.load<fggl::gfx::ecsGlfwModule>(inputs); - fggl::gfx::Window win; + fggl::gfx::Window win{}; win.title("FGGL Demo"); win.fullscreen( true ); // storage API fggl::data::Storage storage; - discover( storage.resolvePath(fggl::data::Data, "res") ); + //discover( storage.resolvePath(fggl::data::Data, "../../packs") ); // Opengl APIs - auto glModule = fggl::gfx::ecsInitOpenGL(ecs, win, storage); + auto glModule = modules.load<fggl::gfx::ecsOpenGLModule>(win, storage); + fggl::gfx::loadPipeline(glModule, "unlit", false); - fggl::gfx::loadPipeline(glModule, "phong", false); + fggl::gfx::loadPipeline(glModule, "lighting/phong", false); fggl::gfx::loadPipeline(glModule, "normals", false); // debug layer @@ -259,24 +259,24 @@ int main(int argc, char* argv[]) { debug.visible(true); // create ECS - ecs.registerComponent<fggl::math::Transform>(); + types.make<fggl::math::Transform>(); // make camera - auto camEnt = ecs.createEntity(); + auto camEnt = ecs.create(); { - auto cameraTf = ecs.addComponent<fggl::math::Transform>(camEnt); + auto cameraTf = ecs.add<fggl::math::Transform>(camEnt); cameraTf->origin( glm::vec3(0.0f, 3.0f, 3.0f) ); - ecs.addComponent<fggl::gfx::Camera>(camEnt); + ecs.add<fggl::gfx::Camera>(camEnt); } - auto floorEnt = ecs.createEntity(); + auto floorEnt = ecs.create(); { - ecs.addComponent<fggl::math::Transform>( floorEnt ); + ecs.add<fggl::math::Transform>( floorEnt ); fggl::data::Mesh mesh = fggl::data::make_quad_xz(); - ecs.addComponent<fggl::gfx::StaticMesh>(floorEnt, mesh, "phong"); - fggl::gfx::onStaticMeshAdded(ecs, floorEnt, glModule); + fggl::gfx::StaticMesh sMesh{mesh, "lighting/phong"}; + ecs.set<fggl::gfx::StaticMesh>(floorEnt, &sMesh); } int nCubes = 3; @@ -285,15 +285,15 @@ int main(int argc, char* argv[]) { constexpr float HALF_PI = M_PI / 2.0f; for ( int i=0; i<nCubes; i++ ) { - auto entity = ecs.createEntity(); + auto entity = ecs.create(); // set the position - auto result = ecs.addComponent<fggl::math::Transform>(entity); + auto result = ecs.add<fggl::math::Transform>(entity); result->origin( glm::vec3( i * 5.0f, 0.0f, 0.0f) ); fggl::data::Mesh mesh; - for (int i=-(nSections/2); i<=nSections/2; i++) { - const auto shapeOffset = glm::vec3( 0.0f, 0.0f, i * 1.0f ); + for (int j=-(nSections/2); j<=nSections/2; j++) { + const auto shapeOffset = glm::vec3( 0.0f, 0.0f, j * 1.0f ); const auto cubeMat = glm::translate( fggl::math::mat4( 1.0f ) , shapeOffset ); const auto leftSlope = fggl::math::modelMatrix( @@ -308,16 +308,14 @@ int main(int argc, char* argv[]) { fggl::data::make_slope( mesh, rightSlope ); } mesh.removeDups(); - ecs.addComponent<fggl::gfx::StaticMesh>(entity, mesh, "phong"); - // pretend we have callbacks - fggl::gfx::onStaticMeshAdded(ecs, entity, glModule); + fggl::gfx::StaticMesh staticMesh{mesh, "lighting/phong"}; + ecs.set<fggl::gfx::StaticMesh>(entity, &staticMesh); } - bool joystickWindow = true; bool gamepadWindow = true; - fggl::util::Timer time; + fggl::util::Timer time{}; time.frequency( glfwGetTimerFrequency() ); time.setup( glfwGetTimerValue() ); @@ -379,16 +377,8 @@ int main(int argc, char* argv[]) { } ImGui::End(); - debug.showDemo(); -/* float amount = glm::radians( time / 2048.0f * 360.0f ); - auto spinners = ecs.getEntityWith<fggl::math::Transform>(); - for ( auto entity : spinners ) { - auto transform = ecs.getComponent<fggl::math::Transform>(entity); - transform->euler(glm::vec3(0.0f, amount, 0.0f)); - }*/ - // // render step // diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt index 86599c8dc5349dd920e617468966bae8eb57ceb5..1bd8aec5da984df76d65488a9b019a21d15734be 100644 --- a/fggl/CMakeLists.txt +++ b/fggl/CMakeLists.txt @@ -4,7 +4,9 @@ add_library(fggl fggl.cpp ecs/ecs.cpp data/model.cpp data/procedural.cpp -) + ecs3/fast/Container.cpp + ecs3/prototype/world.cpp + ecs3/module/module.cpp) target_include_directories(fggl PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../) # Graphics backend diff --git a/fggl/data/model.cpp b/fggl/data/model.cpp index b6cb8e6462e8aa03fd8ada265627ece62faf00da..0cded14df73ec6693b4ae7664089e48934c20ada 100644 --- a/fggl/data/model.cpp +++ b/fggl/data/model.cpp @@ -5,7 +5,7 @@ using namespace fggl::data; -Mesh::Mesh() : m_verts(), m_index() { +Mesh::Mesh() : m_verts(0), m_index(0) { } void Mesh::pushIndex(unsigned int idx) { diff --git a/fggl/data/storage.hpp b/fggl/data/storage.hpp index d14b1579a870aca4d26717fcd42b302f310f53bf..a9636423418c0fd3e2d166c335878231a4bc3043 100644 --- a/fggl/data/storage.hpp +++ b/fggl/data/storage.hpp @@ -39,7 +39,7 @@ namespace fggl::data { switch ( pool ) { case Data: - path = std::filesystem::current_path() / "data"; + path = std::filesystem::current_path() / "res"; break; case User: path = "./user-data/"; diff --git a/fggl/ecs/component.hpp b/fggl/ecs/component.hpp index b84fbe859be57aaff22476d961b0b65d2f0572e8..36e5a5f39809fe198535d3aa0b57d3c7f06d4b70 100644 --- a/fggl/ecs/component.hpp +++ b/fggl/ecs/component.hpp @@ -3,6 +3,8 @@ #include "utility.hpp" +#include <cassert> +#include <cstring> #include <new> #include <utility> @@ -18,6 +20,7 @@ namespace fggl::ecs { 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 std::size_t size() const = 0; @@ -32,13 +35,30 @@ namespace fggl::ecs { } virtual void construct(unsigned char* data) const override { -// new (&data[0]) C(); + new (data) 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); } diff --git a/fggl/ecs3/ecs.hpp b/fggl/ecs3/ecs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b920ca6739ad042e891cf3a7a1da25bef1980cd8 --- /dev/null +++ b/fggl/ecs3/ecs.hpp @@ -0,0 +1,13 @@ +#ifndef FGGL_ECS3_ECS_H +#define FGGL_ECS3_ECS_H + +#include <fggl/ecs3/module/module.h> +#include <fggl/ecs3/prototype/world.h> + +namespace fggl::ecs3 { + + using World = prototype::World; + +} + +#endif \ No newline at end of file diff --git a/fggl/ecs3/fast/Container.cpp b/fggl/ecs3/fast/Container.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6343970d8efc39151157f55128572f2fcbcd4025 --- /dev/null +++ b/fggl/ecs3/fast/Container.cpp @@ -0,0 +1,147 @@ +// +// Created by webpigeon on 23/10/2021. +// + +#include "Container.h" + +namespace fggl::ecs3 { + + std::ostream& operator<<(std::ostream& out, RecordIdentifier const& curr) { + out << "record("; + for (int i=0; i<curr.count; i++) { + out << i; + if ( i != curr.count-1) { + out << ","; + } + } + out << ")"; + return out; + } + + + std::size_t Container::create() { + ensure(m_size + 1); + auto pos = m_size++; + + // setup entry + for (std::size_t i = 0; i < m_identifier.count; ++i) { + auto compMeta = m_types.meta(m_identifier.types[i]); + auto offset = offsets[i] + (pos * compMeta->size()); + + auto compPtr = backingStore + offset; + compMeta->construct(compPtr); + } + return pos; + } + + void Container::remove(std::size_t pos) { + // cleanup entry + for (std::size_t i = 0; i < m_identifier.count; ++i) { + auto compMeta = m_types.meta(m_identifier.types[i]); + auto offset = offsets[i] + (pos * compMeta->size()); + + auto compPtr = backingStore + offset; + compMeta->destroy(compPtr); + } + + // if we're not the last one, swap places + if (pos != m_size - 1) { + move(pos, *this, m_size - 1); + } + + m_size--; + } + + void Container::move(std::size_t newPos, Container &oldContainer, std::size_t oldPos) { + for (std::size_t i = 0; i < m_identifier.count; ++i) { + auto thisComp = m_identifier.types[i]; + auto compMeta = m_types.meta(thisComp); + + auto newPtr = backingStore + (offsets[i] + (newPos * compMeta->size())); + auto oldPtr = oldContainer.data_raw(thisComp) + (oldPos * compMeta->size()); + compMeta->move(oldPtr, newPtr); + compMeta->destroy(oldPtr); + } + } + + std::size_t Container::expand(Container &other, std::size_t otherPos, component_type_t newComp) { + ensure(m_size + 1); + auto pos = m_size++; + + for (std::size_t i = 0; i < m_identifier.count; ++i) { + auto thisComp = m_identifier.types[i]; + auto compMeta = m_types.meta(thisComp); + + auto newPtr = backingStore + (offsets[i] + (pos * compMeta->size())); + if (newComp != thisComp) { + // we're moving the component + auto oldPtr = other.data_raw(thisComp) + (otherPos * compMeta->size()); + compMeta->move(oldPtr, newPtr); + } else { + // this is the new comp + compMeta->construct(newPtr); + } + } + + // remove the old entity + other.remove(otherPos); + return pos; + } + + void Container::contract(Container &other, std::size_t otherPos) { + ensure(m_size + 1); + auto pos = m_size++; + + move(pos, other, otherPos); + + for (std::size_t i = 0; i < m_identifier.count; ++i) { + auto thisComp = m_identifier.types[i]; + auto compMeta = m_types.meta(thisComp); + + auto newPtr = backingStore + (offsets[i] + (pos * compMeta->size())); + auto oldPtr = other.data_raw(thisComp) + (otherPos * compMeta->size()); + compMeta->move(oldPtr, newPtr); + } + + other.remove(otherPos); + } + + void Container::ensure(std::size_t size) { + if (size < m_capacity) { + return; + } + + std::size_t required = 0; + for (std::size_t i = 0; i < m_identifier.count; i++) { + auto meta = m_types.meta(m_identifier.types[i]); + required += meta->size() * size; + } + + auto *newBacking = new unsigned char[required]; + std::size_t newOffsets[RecordIdentifier::MAX_COMPS]; + std::size_t currOffset = 0; + + // bulk copy routine + for (std::size_t cid = 0; cid < m_identifier.count; ++cid) { + auto compMeta = m_types.meta(m_identifier.types[cid]); + newOffsets[cid] = currOffset; + + compMeta->bulkMove( + backingStore + offsets[cid], + newBacking + newOffsets[cid], + m_size); + + currOffset += size * compMeta->size(); + } + + // swap the backing store and cleanup the old one + auto *tmp = backingStore; + backingStore = newBacking; + delete[] tmp; + + // update size info + std::memcpy(offsets, newOffsets, m_identifier.count * sizeof(std::size_t)); + m_capacity = size; + } + +} \ No newline at end of file diff --git a/fggl/ecs3/fast/Container.h b/fggl/ecs3/fast/Container.h new file mode 100644 index 0000000000000000000000000000000000000000..6910cc4100fe3d0239fe53c1311e96b44495c5e5 --- /dev/null +++ b/fggl/ecs3/fast/Container.h @@ -0,0 +1,104 @@ +// +// Created by webpigeon on 23/10/2021. +// + +#ifndef FGGL_ECS3_CONTAINER_H +#define FGGL_ECS3_CONTAINER_H + +#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_types(reg), + m_identifier( id ), + backingStore(nullptr), + m_capacity(0), + m_size(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 (int 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_CONTAINER_H diff --git a/fggl/ecs3/fast/ecs.hpp b/fggl/ecs3/fast/ecs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3d4ea7cc99de08996cbc35626f80fd452a8af003 --- /dev/null +++ b/fggl/ecs3/fast/ecs.hpp @@ -0,0 +1,138 @@ +#ifndef FGGL_ECS_ECS3_H +#define FGGL_ECS_ECS3_H + +#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.h> + +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 diff --git a/fggl/ecs3/module/module.cpp b/fggl/ecs3/module/module.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7053df03b445e1c5c243fc80cc71a81f0bef912c --- /dev/null +++ b/fggl/ecs3/module/module.cpp @@ -0,0 +1,5 @@ +// +// Created by webpigeon on 23/10/2021. +// + +#include "module.h" diff --git a/fggl/ecs3/module/module.h b/fggl/ecs3/module/module.h new file mode 100644 index 0000000000000000000000000000000000000000..68801fbfd3da369976bb721da04fa23819a503f5 --- /dev/null +++ b/fggl/ecs3/module/module.h @@ -0,0 +1,53 @@ +// +// Created by webpigeon on 23/10/2021. +// + +#ifndef FGGL_ECS3_MODULE_H +#define FGGL_ECS3_MODULE_H + +#include <string> +#include <map> +#include <memory> + +#include <fggl/ecs3/types.hpp> + +namespace fggl::ecs3 { + + class Module { + public: + virtual ~Module() = default; + + [[nodiscard]] virtual std::string name() const = 0; + + virtual void onLoad(ModuleManager& manager, TypeRegistry& tr) {}; + }; + + class ModuleManager { + public: + explicit ModuleManager(TypeRegistry& types) : m_types(types), m_modules(){ } + ~ModuleManager() = default; + + template<typename C, typename... Args> + std::shared_ptr<C> load(Args&... args) { + auto ptr = std::make_shared<C>(args...); + m_modules[ptr->name()] = ptr; + + ptr->onLoad(*this, m_types); + std::cerr << "module loaded: " << ptr->name() << std::endl; + return ptr; + } + + template<typename C> + void onAdd(const callback_t& cb) { + m_types.callbackAdd( Component<C>::typeID(), cb); + } + + private: + TypeRegistry& m_types; + std::map<std::string, std::shared_ptr<Module>> m_modules; + }; + +} + + +#endif //FGGL_ECS3_MODULE_H diff --git a/fggl/ecs3/prototype/world.cpp b/fggl/ecs3/prototype/world.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ee50abe41eff6e0588143e25956ccf0089c06ba8 --- /dev/null +++ b/fggl/ecs3/prototype/world.cpp @@ -0,0 +1,5 @@ +// +// Created by webpigeon on 23/10/2021. +// + +#include "world.h" diff --git a/fggl/ecs3/prototype/world.h b/fggl/ecs3/prototype/world.h new file mode 100644 index 0000000000000000000000000000000000000000..52000ee3ce550a737e315a5b33c0001b90c6a6af --- /dev/null +++ b/fggl/ecs3/prototype/world.h @@ -0,0 +1,125 @@ +// +// Created by webpigeon on 23/10/2021. +// + +#ifndef FGGL_ECS3_PROTOTYPE_WORLD_H +#define FGGL_ECS3_PROTOTYPE_WORLD_H + +#include <map> +#include <fggl/ecs3/types.hpp> + +/** + * 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 { + + + class Entity { + public: + Entity(entity_t id) : m_id() {}; + ~Entity() = default; + + template<typename C> + C* add() { + C* ptr = new C(); + m_components[Component<C>::typeID()] = ptr; + return ptr; + } + + template<typename C> + C* set(const C* ptr) { + C* newPtr = new C(*ptr); + m_components[Component<C>::typeID()] = newPtr; + return newPtr; + } + + template<typename C> + C* get() { + void* ptr = m_components.at(Component<C>::typeID()); + return (C*)ptr; + } + + bool hasComponents(std::vector<component_type_t>& Cs) { + 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: + World(TypeRegistry& reg) : m_types(reg), m_next(0), m_entities() {}; + ~World() = default; + + entity_t create() { + auto nextID = m_next++; + m_entities.emplace(nextID, nextID); + return nextID; + } + + void destroy(entity_t entity) { + // TOOD resolve and clean components + m_entities.erase(entity); + } + + template<typename... Cs> + std::vector<entity_t> findMatching() { + // 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) ) { + entities.push_back(eid); + } + } + + return entities; + } + + template<typename C> + C* add(entity_t 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; + } + + template<typename C> + C* set(entity_t entity_id, const C* ptr) { + auto& entity = m_entities.at(entity_id); + auto comp = entity.set<C>(ptr); + + m_types.fireAdd(*this, entity_id, Component<C>::typeID()); + return comp; + } + + template<typename C> + C* get(entity_t entity_id) { + auto& entity = m_entities.at(entity_id); + return entity.get<C>(); + } + + private: + TypeRegistry& m_types; + entity_t m_next; + std::map<entity_t, Entity> m_entities; + + }; + +} + +#endif //FGGL_ECS3_PROTOTYPE_WORLD_H \ No newline at end of file diff --git a/fggl/ecs3/types.hpp b/fggl/ecs3/types.hpp new file mode 100644 index 0000000000000000000000000000000000000000..14aff893540ae694db026b72f96677e0c3169702 --- /dev/null +++ b/fggl/ecs3/types.hpp @@ -0,0 +1,202 @@ +#ifndef FGGL_ECS3_TYPES_H +#define FGGL_ECS3_TYPES_H + +#include <cstdarg> +#include <utility> + +#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 { + entity_t id; + }; + + 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 (int 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 (int 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 { + return m_types.at( type_id ); + } + + 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) { + auto& callbacks = m_callbacks[type].add; + for ( auto& callback : callbacks) { + callback(world, entity); + } + } + + 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 diff --git a/fggl/ecs3/utils.hpp b/fggl/ecs3/utils.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0881c12459d556d88a58a1cef4b9445de4e69068 --- /dev/null +++ b/fggl/ecs3/utils.hpp @@ -0,0 +1,36 @@ +#ifndef FGGL_UTILS_H +#define FGGL_UTILS_H + +#include <cstddef> + +namespace fggl::utils { + + template<typename T> + std::size_t search(const T* data, const std::size_t size, const T& v) { + // empty list == not found + if ( size == 0) { + return size; + } + + std::size_t left = 0; + std::size_t right = size - 1; + while ( left <= right ) { + std::size_t m = (left+right) / 2; + if ( data[m] == v ) { + return m; + } else if ( v < data[m] ) { + if ( m == 0 ) { + return size; + } + right = m - 1; + } else { + left = m + 1; + } + } + + return size; + } + +} + +#endif diff --git a/fggl/gfx/common.hpp b/fggl/gfx/common.hpp index 1192659b66c748eb1cbcc42f431d14e91f4fd28d..c8376f3477814d66968fd01eb0159437262906ce 100644 --- a/fggl/gfx/common.hpp +++ b/fggl/gfx/common.hpp @@ -9,6 +9,7 @@ #include <fggl/data/model.hpp> #include <string> +#include <utility> namespace fggl::gfx { @@ -16,8 +17,9 @@ namespace fggl::gfx { data::Mesh mesh; std::string pipeline; - inline StaticMesh(const data::Mesh& aMesh, const std::string& aPipeline) : - mesh(aMesh), pipeline(aPipeline) {} + inline StaticMesh() : mesh(), pipeline() {} + inline StaticMesh(const data::Mesh& aMesh, std::string aPipeline) : + mesh(aMesh), pipeline(std::move(aPipeline)) {} }; } diff --git a/fggl/gfx/compat.hpp b/fggl/gfx/compat.hpp index 62b617d45b28980d036dad4fd2bf12825ecaf62a..089ac569e0becc1d9568e6dfca3ee5dbce60baca 100644 --- a/fggl/gfx/compat.hpp +++ b/fggl/gfx/compat.hpp @@ -12,30 +12,28 @@ #include <memory> +#include <utility> #include <fggl/gfx/window.hpp> -#include <fggl/ecs/ecs.hpp> +#include <fggl/ecs3/ecs.hpp> namespace fggl::gfx { // // fake module support - allows us to still RAII // - struct ecsGlfwModule { + struct ecsGlfwModule : ecs3::Module { GlfwContext context; - inline ecsGlfwModule(std::shared_ptr<fggl::input::Input> inputs) : context(inputs) { + inline explicit + ecsGlfwModule(std::shared_ptr<fggl::input::Input> inputs) : context(std::move(inputs) ) { } - }; - using GlfwModule = std::shared_ptr<ecsGlfwModule>; + [[nodiscard]] + std::string name() const override { + return "gfx::glfw"; + } - // - // fake module/callbacks - our ECS doesn't have module/callback support yet. - // - inline GlfwModule ecsInitGlfw(ecs::ECS& ecs, std::shared_ptr<fggl::input::Input> inputs) { - auto mod = std::make_shared<ecsGlfwModule>(inputs); - return mod; - } + }; } diff --git a/fggl/gfx/ogl/backend.cpp b/fggl/gfx/ogl/backend.cpp index 3585d91b3424dde33bf9ad9aa5bd1f85a1e50b17..4aa000c669528b8722d81c772e74c08ac32d9326 100644 --- a/fggl/gfx/ogl/backend.cpp +++ b/fggl/gfx/ogl/backend.cpp @@ -5,6 +5,7 @@ using namespace fggl::gfx; GlGraphics::GlGraphics(Window& window) : m_window(window) { + std::cerr << "[OGL] attaching window context" << std::endl; window.activate(); GLenum err = glewInit(); if ( GLEW_OK != err ) { @@ -12,9 +13,11 @@ GlGraphics::GlGraphics(Window& window) : m_window(window) { } glViewport(0, 0, 1920, 1080); + std::cerr << "[OGL] window ready!" << std::endl; } GlGraphics::~GlGraphics() { + std::cerr << "[GLFW] gl context killed!" << std::endl; } void GlGraphics::clear() { diff --git a/fggl/gfx/ogl/compat.hpp b/fggl/gfx/ogl/compat.hpp index 0cc905df047fd66ef6d4649a465bd1b8c36f2447..d84a05ee209fae9e635002119d6adfd43c0eae4b 100644 --- a/fggl/gfx/ogl/compat.hpp +++ b/fggl/gfx/ogl/compat.hpp @@ -10,6 +10,8 @@ * Should be removed when the engine has suitable abstractions in place. */ +#include <functional> + #include <fggl/gfx/ogl/shader.hpp> #include <fggl/gfx/ogl/renderer.hpp> @@ -22,14 +24,42 @@ namespace fggl::gfx { // // fake module support - allows us to still RAII // - struct ecsOpenGLModule { + struct ecsOpenGLModule : ecs3::Module { fggl::gfx::Graphics ogl; fggl::gfx::MeshRenderer renderer; fggl::gfx::ShaderCache cache; - inline ecsOpenGLModule(Window& window, fggl::data::Storage& storage) : - ogl(window), renderer(), cache(storage) { - } + ecsOpenGLModule(Window& window, fggl::data::Storage& storage) : + ogl(window), renderer(), cache(storage) { } + + std::string name() const override { + return "gfx::opengl"; + } + + void uploadMesh(ecs3::World& world, ecs::entity_t entity) { + std::cerr << "[!!] I be firein me shaders!" << std::endl; + auto meshData = world.get<gfx::StaticMesh>(entity); + + auto pipeline = cache.get(meshData->pipeline); + auto glMesh = renderer.upload(meshData->mesh); + + glMesh.pipeline = pipeline; + world.set<fggl::gfx::GlRenderToken>(entity, &glMesh); + } + + void onLoad(ecs3::ModuleManager& manager, ecs3::TypeRegistry& types) override { + // TODO implement dependencies + types.make<fggl::gfx::StaticMesh>(); + types.make<fggl::gfx::Camera>(); + + // opengl + types.make<fggl::gfx::GlRenderToken>(); + + // callbacks + auto upload_cb = [this](auto a, auto b) { this->uploadMesh(a, b); }; + manager.onAdd<fggl::gfx::StaticMesh>( upload_cb ); + } + }; using OglModule = std::shared_ptr<ecsOpenGLModule>; @@ -54,44 +84,33 @@ namespace fggl::gfx { // // fake module/callbacks - our ECS doesn't have module/callback support yet. // - inline void onStaticMeshAdded(ecs::ECS& ecs, ecs::entity_t entity, OglModule& mod) { - auto meshData = ecs.getComponent<gfx::StaticMesh>(entity); + inline void onStaticMeshAdded(ecs3::World& ecs, ecs::entity_t entity, OglModule& mod) { + /* + auto meshData = ecs.get<gfx::StaticMesh>(entity); auto pipeline = mod->cache.get(meshData->pipeline); auto glMesh = mod->renderer.upload(meshData->mesh); glMesh.pipeline = pipeline; - ecs.addComponent<fggl::gfx::GlRenderToken>(entity, glMesh); + ecs.set<fggl::gfx::GlRenderToken>(entity, &glMesh); + */ } - inline void renderMeshes(OglModule& mod, ecs::ECS& ecs, float dt) { + inline void renderMeshes(OglModule& mod, ecs3::World& ecs, float dt) { // get the camera - auto cameras = ecs.getEntityWith<fggl::gfx::Camera>(); + auto cameras = ecs.findMatching<fggl::gfx::Camera>(); if ( cameras.empty() ) { return; } auto camera = cameras[0]; // get the models - auto renderables = ecs.getEntityWith<fggl::gfx::GlRenderToken>(); + auto renderables = ecs.findMatching<fggl::gfx::GlRenderToken>(); for ( auto renderable : renderables ) { mod->renderer.render(ecs, camera, dt); } } - inline void ecsInitGraphics(ecs::ECS& ecs) { - ecs.registerComponent<fggl::gfx::StaticMesh>(); - ecs.registerComponent<fggl::gfx::Camera>(); - } - - inline std::shared_ptr<ecsOpenGLModule> ecsInitOpenGL(ecs::ECS& ecs, Window& window, data::Storage& storage) { - ecsInitGraphics(ecs); - - auto mod = std::make_shared<ecsOpenGLModule>(window, storage); - ecs.registerComponent<fggl::gfx::GlRenderToken>(); - return mod; - } - } diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp index 5c70dfd565b8608f3cafa65ad294526b24e7b669..0e8e9833250ed5b3dccd2f0d609c85a4d6520b64 100644 --- a/fggl/gfx/ogl/renderer.cpp +++ b/fggl/gfx/ogl/renderer.cpp @@ -66,10 +66,10 @@ GlRenderToken MeshRenderer::upload(fggl::data::Mesh& mesh) { return token; } -void MeshRenderer::render(const fggl::ecs::ECS& ecs, const ecs::entity_t camera, float dt) { +void MeshRenderer::render(fggl::ecs3::World& ecs, ecs3::entity_t camera, float dt) { if ( camera == ecs::NULL_ENTITY ) return; - auto entities = ecs.getEntityWith<GlRenderToken>(); + auto entities = ecs.findMatching<GlRenderToken>(); if ( entities.size() == 0 ) return; total += dt; @@ -80,8 +80,8 @@ void MeshRenderer::render(const fggl::ecs::ECS& ecs, const ecs::entity_t camera, glEnable(GL_DEPTH_TEST); // camera logic - const auto camTransform = ecs.getComponent<math::Transform>(camera); - const auto camComp = ecs.getComponent<gfx::Camera>(camera); + const auto camTransform = ecs.get<math::Transform>(camera); + const auto camComp = ecs.get<gfx::Camera>(camera); glm::mat4 proj = glm::perspective( camComp->fov, camComp->aspectRatio, camComp->nearPlane, camComp->farPlane); glm::mat4 view = glm::lookAt( camTransform->origin(), camComp->target, camTransform->up() ); @@ -91,8 +91,8 @@ void MeshRenderer::render(const fggl::ecs::ECS& ecs, const ecs::entity_t camera, // TODO better performance if grouped by vao first // TODO the nvidia performance presentation said I shouldn't use uniforms for large data for ( auto& entity : entities ) { - const auto& transform = ecs.getComponent<fggl::math::Transform>(entity); - const auto& mesh = ecs.getComponent<GlRenderToken>(entity); + const auto& transform = ecs.get<fggl::math::Transform>(entity); + const auto& mesh = ecs.get<GlRenderToken>(entity); glm::mat4 model = transform->model(); // model = glm::rotate(model, glm::radians(total/2048.0f * 360.0f), glm::vec3(0.0f,1.0f,0.0f)); diff --git a/fggl/gfx/ogl/renderer.hpp b/fggl/gfx/ogl/renderer.hpp index cac965a67f4e041caf9b7ce28d84748a14d94bcf..59320689b906025f2dc67565a3639b140d1081bc 100644 --- a/fggl/gfx/ogl/renderer.hpp +++ b/fggl/gfx/ogl/renderer.hpp @@ -4,7 +4,7 @@ #include <vector> #include <fggl/data/model.hpp> -#include <fggl/ecs/ecs.hpp> +#include <fggl/ecs3/ecs.hpp> #include <fggl/gfx/ogl/backend.hpp> namespace fggl::gfx { @@ -23,7 +23,7 @@ namespace fggl::gfx { token_t upload(fggl::data::Mesh& mesh); - void render(const ecs::ECS& ecs, const ecs::entity_t camera, float dt); + void render(ecs3::World& ecs, ecs3::entity_t camera, float dt); float total; }; diff --git a/fggl/gfx/ogl/shader.cpp b/fggl/gfx/ogl/shader.cpp index 59a10e57f19d1182fb4e616b834f0c0defa5de3a..3d91705128c03b2e41c8d2fe19e5a5049dc6ccf3 100644 --- a/fggl/gfx/ogl/shader.cpp +++ b/fggl/gfx/ogl/shader.cpp @@ -14,7 +14,7 @@ bool ShaderCache::compileShader(const std::string& fname, GLuint sid) { } // upload and compile shader - const GLchar *src_cstr = (const GLchar *)source.c_str(); + const auto *src_cstr = (const GLchar *)source.c_str(); glShaderSource(sid, 1, &src_cstr, 0); glCompileShader(sid); diff --git a/fggl/gfx/window.cpp b/fggl/gfx/window.cpp index f16bc0521e81840893430e53b3f6dd26f1dd6970..ffd8d4c7649fcec458097eb54e3220db7a755a60 100644 --- a/fggl/gfx/window.cpp +++ b/fggl/gfx/window.cpp @@ -4,9 +4,57 @@ #include <iostream> #include <string> #include <stdexcept> +#include <utility> using namespace fggl::gfx; +void APIENTRY glDebugOutput(GLenum source, + GLenum type, + unsigned int id, + GLenum severity, + GLsizei length, + const char *message, + const void *userParam) +{ + // ignore non-significant error/warning codes + if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return; + + std::cerr << "---------------" << std::endl; + std::cout << "Debug message (" << id << "): " << message << std::endl; + + switch (source) + { + case GL_DEBUG_SOURCE_API: std::cout << "Source: API"; break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: std::cout << "Source: Window System"; break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: std::cout << "Source: Shader Compiler"; break; + case GL_DEBUG_SOURCE_THIRD_PARTY: std::cout << "Source: Third Party"; break; + case GL_DEBUG_SOURCE_APPLICATION: std::cout << "Source: Application"; break; + case GL_DEBUG_SOURCE_OTHER: std::cout << "Source: Other"; break; + } std::cout << std::endl; + + switch (type) + { + case GL_DEBUG_TYPE_ERROR: std::cout << "Type: Error"; break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: std::cout << "Type: Deprecated Behaviour"; break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: std::cout << "Type: Undefined Behaviour"; break; + case GL_DEBUG_TYPE_PORTABILITY: std::cout << "Type: Portability"; break; + case GL_DEBUG_TYPE_PERFORMANCE: std::cout << "Type: Performance"; break; + case GL_DEBUG_TYPE_MARKER: std::cout << "Type: Marker"; break; + case GL_DEBUG_TYPE_PUSH_GROUP: std::cout << "Type: Push Group"; break; + case GL_DEBUG_TYPE_POP_GROUP: std::cout << "Type: Pop Group"; break; + case GL_DEBUG_TYPE_OTHER: std::cout << "Type: Other"; break; + } std::cout << std::endl; + + switch (severity) + { + case GL_DEBUG_SEVERITY_HIGH: std::cout << "Severity: high"; break; + case GL_DEBUG_SEVERITY_MEDIUM: std::cout << "Severity: medium"; break; + case GL_DEBUG_SEVERITY_LOW: std::cout << "Severity: low"; break; + case GL_DEBUG_SEVERITY_NOTIFICATION: std::cout << "Severity: notification"; break; + } std::cout << std::endl; + std::cout << std::endl; +} + static void glfw_error(int code, const char* description) { std::cerr << "[GLFW] " << code << " " << description << std::endl; } @@ -107,25 +155,29 @@ static void fggl_joystick(int jid, int state) { } GlfwContext::GlfwContext( std::shared_ptr<fggl::input::Input> input ) { + std::cerr << "[GLFW] glfw init started" << std::endl; auto& glfwCallbacks = GlfwInputManager::instance(); - glfwCallbacks.setup(input); + glfwCallbacks.setup(std::move(input) ); glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE); + glfwSetErrorCallback(glfw_error); int state = glfwInit(); if ( state == GLFW_FALSE ) { - const char** error; - int code = glfwGetError(error); + const char **error = nullptr; + glfwGetError(error); throw std::runtime_error( *error ); } // joysticks are global fggl_joystick_poll(); glfwSetJoystickCallback(fggl_joystick); + std::cerr << "[GLFW] glfw init complete" << std::endl; } GlfwContext::~GlfwContext() { glfwTerminate(); + std::cerr << "[GLFW] glfw context killed!" << std::endl; } void GlfwContext::pollEvents() { @@ -133,17 +185,22 @@ void GlfwContext::pollEvents() { fggl_joystick_poll(); } -Window::Window() : m_window(nullptr) { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); +Window::Window() : m_window(nullptr), m_framesize() { + std::cerr << "creating window" << std::endl; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true); + glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true); m_window = glfwCreateWindow(1920, 1080, "main", nullptr, nullptr); if ( m_window == nullptr ) { return; } + activate(); + auto result = glewInit(); + std::cerr << "glew result: " << (result == GLEW_OK) << std::endl; + m_framesize = glm::vec2(1920, 1080); glfwSetWindowUserPointer(m_window, this); glfwSetFramebufferSizeCallback( m_window, framebuffer_resize ); @@ -153,6 +210,15 @@ Window::Window() : m_window(nullptr) { glfwSetCursorPosCallback(m_window, fggl_input_cursor); glfwSetMouseButtonCallback(m_window, fggl_input_mouse_btn); glfwSetKeyCallback(m_window, fggl_input_keyboard); + + int flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags); + if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) { + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(glDebugOutput, "DEBUG MODE"); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); + } + std::cerr << "window creation complete" << std::endl; } Window::~Window() { diff --git a/fggl/gfx/window.hpp b/fggl/gfx/window.hpp index d6bb8d9c63b56a607846cd13844fecdf856bc2a9..f81224656eea180f34325ebe955e3bc7285e7d0e 100644 --- a/fggl/gfx/window.hpp +++ b/fggl/gfx/window.hpp @@ -14,7 +14,7 @@ namespace fggl::gfx { class GlfwContext { public: - GlfwContext(std::shared_ptr<fggl::input::Input> input); + explicit GlfwContext(std::shared_ptr<fggl::input::Input> input); ~GlfwContext(); void pollEvents(); @@ -43,6 +43,7 @@ namespace fggl::gfx { public: Window(); ~Window(); + Window(Window&) = delete; // window <-> opengl stuff void activate() const; diff --git a/fggl/util/chrono.hpp b/fggl/util/chrono.hpp index bba16710ba73109bdcb292e2b044569b775b3f26..64ca2e331c66831ef674888a2a7dddeed14904d8 100644 --- a/fggl/util/chrono.hpp +++ b/fggl/util/chrono.hpp @@ -4,31 +4,33 @@ namespace fggl::util { class Timer { + using time_t = unsigned long; public: - inline void frequency(long freq) { + inline void frequency(float freq) { m_freq = freq; } - inline void setup(long epoch) { + inline void setup(time_t epoch) { m_first = epoch; m_last = m_first; m_curr = m_first; } - inline void tick(long time) { + inline void tick(time_t time) { m_last = m_curr; m_curr = time; } + [[nodiscard]] inline float delta() const { return (m_curr - m_last) / m_freq; } private: float m_freq; - float m_first; - float m_last; - float m_curr; + time_t m_first; + time_t m_last; + time_t m_curr; }; }; diff --git a/tests/testfggl/CMakeLists.txt b/tests/testfggl/CMakeLists.txt index d7015641f74cd37d57892e911c478a6797d05b12..d57cfd8c71cb1e36c0a75ece47f81da0161c061e 100644 --- a/tests/testfggl/CMakeLists.txt +++ b/tests/testfggl/CMakeLists.txt @@ -4,8 +4,9 @@ add_executable( fggl_test # TestFggl.cpp ecs/ecs.cpp + ecs3/ecs.cpp math/types.cpp -) + ecs3/utils.cpp) target_include_directories(fggl_test PUBLIC ${PROJECT_BINARY_DIR}) target_link_libraries( diff --git a/tests/testfggl/ecs/ecs.cpp b/tests/testfggl/ecs/ecs.cpp index 4b2ffdbff3c2e19ef7f71c0931501608054453b4..ed8ab1b5423b8577b083dba3449e133cfb9fa0a5 100644 --- a/tests/testfggl/ecs/ecs.cpp +++ b/tests/testfggl/ecs/ecs.cpp @@ -13,6 +13,7 @@ namespace { class DummyComp2 : public Component<DummyComp2> { public: int x; + DummyComp2() : x(0) {} DummyComp2(int tmp) : x(tmp) { } DummyComp2( DummyComp2&& other ) : x(other.x) {} diff --git a/tests/testfggl/ecs3/ecs.cpp b/tests/testfggl/ecs3/ecs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..74d5cab30598ec2a5b020cddf062b6d6fdb762db --- /dev/null +++ b/tests/testfggl/ecs3/ecs.cpp @@ -0,0 +1,230 @@ +#include <gtest/gtest.h> +#include <gmock/gmock.h> + +#include <fggl/ecs3/fast/ecs.hpp> + +using fggl::ecs::Component; + +namespace { + + class DummyComp4 { + bool a; + bool b; + bool c; + }; + + class DummyComp3 { + public: + int x; + DummyComp3() = default; + DummyComp3(int tmp) : x(tmp) { + } + DummyComp3( DummyComp3&& other ) : x(other.x) {} + }; + + TEST(ecs3, TypeRegistration) { + fggl::ecs3::TypeRegistry reg; + reg.make<DummyComp3>(); + + EXPECT_EQ(true, reg.exists<DummyComp3>()); + EXPECT_EQ(false, reg.exists<DummyComp4>()); + } + + TEST(ecs3, TypeIDOrdered) { + auto c1 = fggl::ecs::Component<DummyComp3>::typeID(); + auto c2 = fggl::ecs::Component<DummyComp4>::typeID(); + + auto t1 = fggl::ecs3::make_id(2, c1, c2); + auto t2 = fggl::ecs3::make_id(2, c1, c2); + + EXPECT_EQ( t1, t2 ); + EXPECT_EQ( t2, t1 ); + } + + TEST(ecs3, TypeIDUnordered) { + auto c1 = fggl::ecs::Component<DummyComp3>::typeID(); + auto c2 = fggl::ecs::Component<DummyComp4>::typeID(); + + auto t1 = fggl::ecs3::make_id(2, c1, c2); + auto t2 = fggl::ecs3::make_id(2, c2, c1); + + EXPECT_EQ( t1, t2 ); + EXPECT_EQ( t2, t1 ); + } + + TEST(ecs3, TypeIDDifferent) { + auto c1 = fggl::ecs::Component<DummyComp3>::typeID(); + auto c2 = fggl::ecs::Component<DummyComp4>::typeID(); + + auto t1 = fggl::ecs3::make_id(2, c1, c2); + auto t2 = fggl::ecs3::make_id(1, c2); + + EXPECT_NE( t1, t2 ); + EXPECT_NE( t2, t1 ); + } + + TEST(ecs3, createSingle) { + fggl::ecs3::TypeRegistry reg; + reg.make<DummyComp3>(); + + auto type = fggl::ecs3::make_id(1, fggl::ecs::Component<DummyComp3>::typeID()); + fggl::ecs3::Container container(reg, type); + + auto pos = container.create(); + auto* data = container.data<DummyComp3>(); + data[pos].x = 42; + + ASSERT_EQ(1, container.size()); + } + + TEST(ecs3, createTwo) { + fggl::ecs3::TypeRegistry reg; + reg.make<DummyComp3>(); + reg.make<DummyComp4>(); + + auto type = fggl::ecs3::make_id(2, fggl::ecs::Component<DummyComp3>::typeID(), fggl::ecs::Component<DummyComp4>::typeID()); + fggl::ecs3::Container container(reg, type); + + // create object + auto pos = container.create(); + auto* data = container.data<DummyComp3>(); + data[pos].x = 42; + + ASSERT_EQ(1, container.size()); + } + + TEST(ecs3, createRemoveSingle) { + fggl::ecs3::TypeRegistry reg; + reg.make<DummyComp3>(); + + auto type = fggl::ecs3::make_id(1, fggl::ecs::Component<DummyComp3>::typeID()); + fggl::ecs3::Container container(reg, type); + + // create object + auto pos = container.create(); + auto* data = container.data<DummyComp3>(); + data[pos].x = 42; + container.remove(pos); + + ASSERT_EQ(0, container.size()); + } + + TEST(ecs3, createRemoveTwo) { + fggl::ecs3::TypeRegistry reg; + reg.make<DummyComp3>(); + reg.make<DummyComp4>(); + + auto type = fggl::ecs3::make_id(2, fggl::ecs::Component<DummyComp3>::typeID(), fggl::ecs::Component<DummyComp4>::typeID()); + fggl::ecs3::Container container(reg, type); + + auto pos = container.create(); + auto* data = container.data<DummyComp3>(); + data[pos].x = 42; + container.remove(pos); + + ASSERT_EQ(0, container.size()); + } + + TEST(ecs3, createRemoveLast) { + fggl::ecs3::TypeRegistry reg; + reg.make<DummyComp3>(); + reg.make<DummyComp4>(); + + auto type = fggl::ecs3::make_id(1, fggl::ecs::Component<DummyComp3>::typeID()); + fggl::ecs3::Container container(reg, type); + + auto pos = container.create(); + auto pos2 = container.create(); + + auto* data = container.data<DummyComp3>(); + data[pos].x = 0xFEED; + data[pos2].x = 0xBEEF; + + container.remove(pos2); + + // remove may invalidate data pointer + data = container.data<DummyComp3>(); + ASSERT_EQ(data[pos].x, 0XFEED); + ASSERT_EQ(1, container.size()); + } + + TEST(ecs3, createRemoveFirst) { + fggl::ecs3::TypeRegistry reg; + reg.make<DummyComp3>(); + + auto type = fggl::ecs3::make_id(1, fggl::ecs::Component<DummyComp3>::typeID()); + fggl::ecs3::Container container(reg, type); + + auto pos = container.create(); + auto pos2 = container.create(); + + auto* data = container.data<DummyComp3>(); + data[pos].x = 0xFEED; + data[pos2].x = 0xBEEF; + + container.remove(pos); + + // remove may invalidate data pointer + data = container.data<DummyComp3>(); + ASSERT_EQ(data[pos].x, 0XBEEF ); + ASSERT_EQ(1, container.size()); + } + + TEST(ecs3, expandFirst) { + fggl::ecs3::TypeRegistry reg; + reg.make<DummyComp3>(); + reg.make<DummyComp4>(); + + auto type1 = fggl::ecs3::make_id(1, fggl::ecs::Component<DummyComp3>::typeID()); + auto type2 = fggl::ecs3::make_id(2, fggl::ecs::Component<DummyComp3>::typeID(), fggl::ecs::Component<DummyComp4>::typeID()); + + fggl::ecs3::Container container1(reg, type1); + fggl::ecs3::Container container2(reg, type2); + + auto p1 = container1.create(); + auto p2 = container1.create(); + auto data1 = container1.data<DummyComp3>(); + + data1[p1].x = 0xFEED; + data1[p2].x = 0xABCD; + + container2.expand(container1, p1, fggl::ecs::Component<DummyComp4>::typeID()); + data1 = container1.data<DummyComp3>(); + auto data2 = container2.data<DummyComp3>(); + + ASSERT_EQ(1, container1.size() ); + ASSERT_EQ(1, container2.size() ); + + ASSERT_EQ(0xABCD, data1[0].x); + ASSERT_EQ(0xFEED, data2[0].x); + } + + TEST(ecs3, expandLast) { + fggl::ecs3::TypeRegistry reg; + reg.make<DummyComp3>(); + reg.make<DummyComp4>(); + + auto type1 = fggl::ecs3::make_id(1, fggl::ecs::Component<DummyComp3>::typeID()); + auto type2 = fggl::ecs3::make_id(2, fggl::ecs::Component<DummyComp3>::typeID(), fggl::ecs::Component<DummyComp4>::typeID()); + + fggl::ecs3::Container container1(reg, type1); + fggl::ecs3::Container container2(reg, type2); + + auto p1 = container1.create(); + auto p2 = container1.create(); + auto data1 = container1.data<DummyComp3>(); + + data1[p1].x = 0xFEED; + data1[p2].x = 0xABCD; + + container2.expand(container1, p2, fggl::ecs::Component<DummyComp4>::typeID()); + data1 = container1.data<DummyComp3>(); + auto data2 = container2.data<DummyComp3>(); + + ASSERT_EQ(1, container1.size() ); + ASSERT_EQ(1, container2.size() ); + + ASSERT_EQ(0xABCD, data2[0].x); + ASSERT_EQ(0xFEED, data1[0].x); + } +} diff --git a/tests/testfggl/ecs3/utils.cpp b/tests/testfggl/ecs3/utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d0277d0d7112a6b20bd0b36b6714413a16e7bf48 --- /dev/null +++ b/tests/testfggl/ecs3/utils.cpp @@ -0,0 +1,52 @@ +// +// Created by webpigeon on 23/10/2021. +// + +#include <gtest/gtest.h> +#include <gmock/gmock.h> + +#include <fggl/ecs3/utils.hpp> + +namespace { + + TEST(ecs3, searchEmpty) { + int arr[0]; + const std::size_t size = 0; + + auto result = fggl::utils::search(arr, size, 0); + ASSERT_EQ(size, result); + } + + TEST(ecs3, searchSingle) { + int arr[] = {7}; + const std::size_t size = 1; + + auto result = fggl::utils::search(arr, size, 7); + ASSERT_EQ(0, result); + } + + TEST(ecs3, searchNoExist) { + int arr[] = {2,4,6,8,10}; + const std::size_t size = 5; + + auto result = fggl::utils::search(arr, size, 7); + ASSERT_EQ(size, result); + } + + TEST(ecs3, searchFirst) { + int arr[] = {2,4,6,8,10}; + const std::size_t size = 5; + + auto result = fggl::utils::search(arr, size, 2); + ASSERT_EQ(0, result); + } + + TEST(ecs3, searchLast) { + int arr[] = {2,4,6,8,10}; + const std::size_t size = 5; + + auto result = fggl::utils::search(arr, size, 6); + ASSERT_EQ(2, result); + } + +} \ No newline at end of file diff --git a/tests/testfggl/math/types.cpp b/tests/testfggl/math/types.cpp index 96239e43a397fd957974a07480cb8d1048340e6a..2b5f2b1b0f3aa6cd10a79c019a15d2f21d557e26 100644 --- a/tests/testfggl/math/types.cpp +++ b/tests/testfggl/math/types.cpp @@ -152,7 +152,7 @@ namespace { } TEST(math, eulerEncodeDecode) { - fggl::math::Transform t; + /*fggl::math::Transform t; float quatRot = M_PI_2; // perform the rotation @@ -160,7 +160,7 @@ namespace { t.euler( rotation ); // check the value - assertVec3(rotation, t.euler(), "euler"); + assertVec3(rotation, t.euler(), "euler");*/ } }