Compare commits
9 commits
ca29c61521
...
9badcddeae
| Author | SHA1 | Date | |
|---|---|---|---|
| 9badcddeae | |||
| 120b6c24d9 | |||
| d72ee8d9ef | |||
| 03225b3ae6 | |||
| 7266451b45 | |||
| 91d86545dc | |||
| 21e7291189 | |||
| 0c35c13ac1 | |||
| b179149597 |
41 changed files with 988 additions and 530 deletions
|
|
@ -6,7 +6,6 @@ set(CMAKE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake)
|
||||||
include(CheckCXXSourceCompiles)
|
include(CheckCXXSourceCompiles)
|
||||||
include(${CMAKE_DIR}/functions.cmake)
|
include(${CMAKE_DIR}/functions.cmake)
|
||||||
include(${CMAKE_DIR}/definitions.cmake)
|
include(${CMAKE_DIR}/definitions.cmake)
|
||||||
include(${CMAKE_DIR}/dependencies.cmake)
|
|
||||||
|
|
||||||
add_option(ENABLE_UNIT_TESTS "Enables the building of the unit test modules")
|
add_option(ENABLE_UNIT_TESTS "Enables the building of the unit test modules")
|
||||||
add_option(ENABLE_FUZZ_TESTS "Enables the building of the fuzz test modules")
|
add_option(ENABLE_FUZZ_TESTS "Enables the building of the fuzz test modules")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
from conan import ConanFile
|
from conan import ConanFile
|
||||||
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
|
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
|
||||||
import shutil
|
|
||||||
import os
|
|
||||||
import git
|
import git
|
||||||
|
|
||||||
class LightRecipe(ConanFile):
|
class LightRecipe(ConanFile):
|
||||||
|
|
@ -28,9 +26,6 @@ class LightRecipe(ConanFile):
|
||||||
"export_compile_commands": True,
|
"export_compile_commands": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
def requirements(self):
|
|
||||||
self.requires("entt/3.15.0")
|
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
cmake_layout(self)
|
cmake_layout(self)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <flat_map>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include <camera/scene.hpp>
|
#include <camera/camera.hpp>
|
||||||
|
#include <camera/component.hpp>
|
||||||
#include <math/algebra.hpp>
|
#include <math/algebra.hpp>
|
||||||
#include <math/trig.hpp>
|
#include <math/trig.hpp>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ template<typename Expression_T, typename... Args_T>
|
||||||
struct ensure
|
struct ensure
|
||||||
{
|
{
|
||||||
ensure(
|
ensure(
|
||||||
Expression_T expression,
|
const Expression_T &expression,
|
||||||
std::format_string<Args_T...> fmt,
|
std::format_string<Args_T...> fmt,
|
||||||
Args_T &&...args,
|
Args_T &&...args,
|
||||||
const std::source_location &location = std::source_location::current()
|
const std::source_location &location = std::source_location::current()
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
add_library_module(ecs entity.cpp scene.cpp uuid.cpp)
|
add_library_module(ecs sparse_set.cpp)
|
||||||
target_link_libraries(ecs PUBLIC logger lt_debug EnTT::EnTT camera math)
|
target_link_libraries(ecs PUBLIC logger lt_debug)
|
||||||
|
|
||||||
|
add_test_module(ecs sparse_set.test.cpp registry.test.cpp)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
#include <ecs/entity.hpp>
|
|
||||||
#include <ecs/scene.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
Entity::Entity(entt::entity handle, Scene *scene): m_handle(handle), m_scene(scene)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
349
modules/ecs/private/registry.test.cpp
Normal file
349
modules/ecs/private/registry.test.cpp
Normal file
|
|
@ -0,0 +1,349 @@
|
||||||
|
#include <ecs/registry.hpp>
|
||||||
|
#include <ranges>
|
||||||
|
#include <test/expects.hpp>
|
||||||
|
#include <test/test.hpp>
|
||||||
|
|
||||||
|
using lt::test::Case;
|
||||||
|
using lt::test::expect_unreachable;
|
||||||
|
using lt::test::Suite;
|
||||||
|
|
||||||
|
using lt::test::expect_eq;
|
||||||
|
using lt::test::expect_ne;
|
||||||
|
using lt::test::expect_throw;
|
||||||
|
|
||||||
|
using lt::test::expect_false;
|
||||||
|
using lt::test::expect_true;
|
||||||
|
|
||||||
|
using lt::ecs::Entity;
|
||||||
|
using lt::ecs::Registry;
|
||||||
|
|
||||||
|
struct Component
|
||||||
|
{
|
||||||
|
int m_int;
|
||||||
|
std::string m_string;
|
||||||
|
|
||||||
|
[[nodiscard]] friend auto operator==(const Component &lhs, const Component &rhs) -> bool
|
||||||
|
{
|
||||||
|
return lhs.m_int == rhs.m_int && lhs.m_string == rhs.m_string;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template<>
|
||||||
|
struct std::formatter<Component>
|
||||||
|
{
|
||||||
|
constexpr auto parse(std::format_parse_context &context)
|
||||||
|
{
|
||||||
|
return context.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto format(const Component &val, std::format_context &context) const
|
||||||
|
{
|
||||||
|
return std::format_to(context.out(), "{}, {}", val.m_int, val.m_string);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Component_B
|
||||||
|
{
|
||||||
|
float m_float;
|
||||||
|
|
||||||
|
[[nodiscard]] friend auto operator==(const Component_B lhs, const Component_B &rhs) -> bool
|
||||||
|
{
|
||||||
|
return lhs.m_float == rhs.m_float;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template<>
|
||||||
|
struct std::formatter<Component_B>
|
||||||
|
{
|
||||||
|
constexpr auto parse(std::format_parse_context &context)
|
||||||
|
{
|
||||||
|
return context.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto format(const Component_B &val, std::format_context &context) const
|
||||||
|
{
|
||||||
|
return std::format_to(context.out(), "{}", val.m_float);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Suite raii = [] {
|
||||||
|
Case { "happy path won't throw" } = [] {
|
||||||
|
std::ignore = Registry {};
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "many won't throw" } = [] {
|
||||||
|
for (auto idx : std::views::iota(0, 100'000))
|
||||||
|
{
|
||||||
|
std::ignore = Registry {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "unhappy path throws" } = [] {
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "post construct has correct state" } = [] {
|
||||||
|
auto registry = Registry {};
|
||||||
|
expect_eq(registry.get_entity_count(), 0);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Suite entity_raii = [] {
|
||||||
|
Case { "create_entity returns unique values" } = [] {
|
||||||
|
auto registry = Registry {};
|
||||||
|
auto set = std::unordered_set<Entity> {};
|
||||||
|
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
auto entity = registry.create_entity();
|
||||||
|
expect_false(set.contains(entity));
|
||||||
|
|
||||||
|
set.insert(entity);
|
||||||
|
expect_eq(set.size(), idx + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "post create/destroy_entity has correct state" } = [] {
|
||||||
|
auto registry = Registry {};
|
||||||
|
|
||||||
|
auto entities = std::vector<Entity> {};
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
entities.emplace_back(registry.create_entity());
|
||||||
|
expect_eq(registry.get_entity_count(), idx + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
auto entity = entities.back();
|
||||||
|
registry.destroy_entity(entity);
|
||||||
|
|
||||||
|
entities.pop_back();
|
||||||
|
expect_eq(registry.get_entity_count(), 10'000 - (idx + 1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Suite component_raii = [] {
|
||||||
|
Case { "add has correct state" } = [] {
|
||||||
|
auto registry = Registry {};
|
||||||
|
for (auto idx : std::views::iota(0, 100'000))
|
||||||
|
{
|
||||||
|
auto entity = registry.create_entity();
|
||||||
|
auto &component = registry.add<Component>(
|
||||||
|
entity,
|
||||||
|
{ .m_int = idx, .m_string = std::to_string(idx) }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_eq(component.m_int, idx);
|
||||||
|
expect_eq(component.m_string, std::to_string(idx));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "remove has correct state" } = [] {
|
||||||
|
auto registry = Registry {};
|
||||||
|
for (auto idx : std::views::iota(0, 100'000))
|
||||||
|
{
|
||||||
|
auto entity = registry.create_entity();
|
||||||
|
auto &component = registry.add<Component>(
|
||||||
|
entity,
|
||||||
|
{ .m_int = idx, .m_string = std::to_string(idx) }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_eq(component.m_int, idx);
|
||||||
|
expect_eq(component.m_string, std::to_string(idx));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Suite callbacks = [] {
|
||||||
|
Case { "connecting on_construct/on_destruct won't throw" } = [] {
|
||||||
|
auto registry = Registry {};
|
||||||
|
registry.connect_on_construct<Component>([&](Registry &, Entity) {});
|
||||||
|
registry.connect_on_destruct<Component>([&](Registry &, Entity) {});
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "on_construct/on_destruct won't get called on unrelated component" } = [] {
|
||||||
|
auto registry = Registry {};
|
||||||
|
registry.connect_on_construct<Component>([&](Registry &, Entity) { expect_unreachable(); });
|
||||||
|
registry.connect_on_destruct<Component>([&](Registry &, Entity) { expect_unreachable(); });
|
||||||
|
|
||||||
|
for (auto idx : std::views::iota(0, 100'000))
|
||||||
|
{
|
||||||
|
registry.add<Component_B>(registry.create_entity(), {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "on_construct/on_destruct gets called" } = [] {
|
||||||
|
auto registry = Registry {};
|
||||||
|
auto all_entities = std::vector<Entity> {};
|
||||||
|
auto on_construct_called = std::vector<Entity> {};
|
||||||
|
auto on_destruct_called = std::vector<Entity> {};
|
||||||
|
|
||||||
|
registry.connect_on_construct<Component>([&](Registry &, Entity entity) {
|
||||||
|
on_construct_called.emplace_back(entity);
|
||||||
|
});
|
||||||
|
registry.connect_on_destruct<Component>([&](Registry &, Entity entity) {
|
||||||
|
on_destruct_called.emplace_back(entity);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect_true(on_construct_called.empty());
|
||||||
|
expect_true(on_destruct_called.empty());
|
||||||
|
for (auto idx : std::views::iota(0, 100'000))
|
||||||
|
{
|
||||||
|
auto entity = all_entities.emplace_back(registry.create_entity());
|
||||||
|
registry.add<Component>(entity, {});
|
||||||
|
}
|
||||||
|
expect_eq(on_construct_called, all_entities);
|
||||||
|
expect_true(on_destruct_called.empty());
|
||||||
|
|
||||||
|
for (auto &entity : all_entities)
|
||||||
|
{
|
||||||
|
registry.remove<Component>(entity);
|
||||||
|
}
|
||||||
|
expect_eq(on_construct_called, all_entities);
|
||||||
|
expect_eq(on_destruct_called, all_entities);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Suite each = [] {
|
||||||
|
auto registry = Registry {};
|
||||||
|
|
||||||
|
auto shared_entity_counter = 0u;
|
||||||
|
|
||||||
|
auto component_map_a = std::unordered_map<Entity, Component> {};
|
||||||
|
auto entities_a = std::vector<Entity> {};
|
||||||
|
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
auto entity = entities_a.emplace_back(registry.create_entity());
|
||||||
|
auto &component = registry.add<Component>(
|
||||||
|
entity,
|
||||||
|
{ .m_int = idx, .m_string = std::to_string(idx) }
|
||||||
|
);
|
||||||
|
|
||||||
|
component_map_a[entity] = component;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto component_map_b = std::unordered_map<lt::ecs::Entity, Component_B> {};
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
auto entity = Entity {};
|
||||||
|
if (idx % 3 == 0)
|
||||||
|
{
|
||||||
|
entity = entities_a[idx];
|
||||||
|
++shared_entity_counter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entity = registry.create_entity();
|
||||||
|
}
|
||||||
|
auto &component = registry.add<Component_B>(
|
||||||
|
entity,
|
||||||
|
{ .m_float = static_cast<float>(idx) / 2.0f }
|
||||||
|
);
|
||||||
|
|
||||||
|
component_map_b[entity] = component;
|
||||||
|
}
|
||||||
|
|
||||||
|
Case { "each one element" } = [&] {
|
||||||
|
auto counter = 0u;
|
||||||
|
registry.each<Component>([&](Entity entity, Component &component) {
|
||||||
|
++counter;
|
||||||
|
expect_eq(component_map_a[entity], component);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect_eq(component_map_a.size(), counter);
|
||||||
|
|
||||||
|
counter = 0u;
|
||||||
|
registry.each<Component_B>([&](Entity entity, Component_B &component) {
|
||||||
|
++counter;
|
||||||
|
expect_eq(component_map_b[entity], component);
|
||||||
|
});
|
||||||
|
expect_eq(component_map_b.size(), counter);
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "each two element" } = [&] {
|
||||||
|
auto counter = 0u;
|
||||||
|
registry.each<Component, Component_B>(
|
||||||
|
[&](Entity entity, Component &component_a, Component_B &component_b) {
|
||||||
|
expect_eq(component_map_a[entity], component_a);
|
||||||
|
expect_eq(component_map_b[entity], component_b);
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_eq(counter, shared_entity_counter);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Suite views = [] {
|
||||||
|
auto registry = Registry {};
|
||||||
|
|
||||||
|
auto shared_entity_counter = 0u;
|
||||||
|
|
||||||
|
auto component_map_a = std::unordered_map<Entity, Component> {};
|
||||||
|
auto entities_a = std::vector<Entity> {};
|
||||||
|
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
auto entity = entities_a.emplace_back(registry.create_entity());
|
||||||
|
auto &component = registry.add<Component>(
|
||||||
|
entity,
|
||||||
|
{ .m_int = idx, .m_string = std::to_string(idx) }
|
||||||
|
);
|
||||||
|
|
||||||
|
component_map_a[entity] = component;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto component_map_b = std::unordered_map<Entity, Component_B> {};
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
auto entity = Entity {};
|
||||||
|
if (idx % 3 == 0)
|
||||||
|
{
|
||||||
|
entity = entities_a[idx];
|
||||||
|
++shared_entity_counter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entity = registry.create_entity();
|
||||||
|
}
|
||||||
|
auto &component = registry.add<Component_B>(
|
||||||
|
entity,
|
||||||
|
{ .m_float = static_cast<float>(idx) / 2.0f }
|
||||||
|
);
|
||||||
|
|
||||||
|
component_map_b[entity] = component;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Case { "view one component" } = [&] {
|
||||||
|
for (const auto &[entity, component] : registry.view<Component>())
|
||||||
|
{
|
||||||
|
expect_eq(component_map_a[entity], component);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &[entity, component] : registry.view<Component_B>())
|
||||||
|
{
|
||||||
|
expect_eq(component_map_b[entity], component);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "view two component" } = [&] {
|
||||||
|
auto counter = 0u;
|
||||||
|
for (const auto &[entity, component, component_b] : registry.view<Component, Component_B>())
|
||||||
|
{
|
||||||
|
expect_eq(component_map_a[entity], component);
|
||||||
|
expect_eq(component_map_b[entity], component_b);
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
expect_eq(counter, shared_entity_counter);
|
||||||
|
|
||||||
|
counter = 0u;
|
||||||
|
for (const auto &[entity, component_b, component] : registry.view<Component_B, Component>())
|
||||||
|
{
|
||||||
|
expect_eq(component_map_b[entity], component_b);
|
||||||
|
expect_eq(component_map_a[entity], component);
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
expect_eq(counter, shared_entity_counter);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
#include <ecs/components.hpp>
|
|
||||||
#include <ecs/entity.hpp>
|
|
||||||
#include <ecs/scene.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
auto Scene::create_entity(const std::string &name, const TransformComponent &transform) -> Entity
|
|
||||||
{
|
|
||||||
return create_entity_with_uuid(name, UUID(), transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Scene::get_entity_by_tag(const std::string &tag) -> Entity
|
|
||||||
{
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Scene::create_entity_with_uuid(
|
|
||||||
const std::string &name,
|
|
||||||
UUID uuid,
|
|
||||||
const TransformComponent &transform
|
|
||||||
) -> Entity
|
|
||||||
{
|
|
||||||
auto entity = Entity { m_registry.create(), this };
|
|
||||||
entity.add_component<TagComponent>(name);
|
|
||||||
entity.add_component<TransformComponent>(transform);
|
|
||||||
entity.add_component<UUIDComponent>(uuid);
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
0
modules/ecs/private/sparse_set.cpp
Normal file
0
modules/ecs/private/sparse_set.cpp
Normal file
138
modules/ecs/private/sparse_set.test.cpp
Normal file
138
modules/ecs/private/sparse_set.test.cpp
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
#include <ecs/sparse_set.hpp>
|
||||||
|
#include <ranges>
|
||||||
|
#include <test/expects.hpp>
|
||||||
|
#include <test/test.hpp>
|
||||||
|
|
||||||
|
using lt::test::Case;
|
||||||
|
using lt::test::Suite;
|
||||||
|
|
||||||
|
using lt::test::expect_eq;
|
||||||
|
using lt::test::expect_false;
|
||||||
|
using lt::test::expect_ne;
|
||||||
|
using lt::test::expect_throw;
|
||||||
|
using lt::test::expect_true;
|
||||||
|
|
||||||
|
using Set = lt::ecs::SparseSet<int>;
|
||||||
|
constexpr auto capacity = 100;
|
||||||
|
|
||||||
|
Suite raii = [] {
|
||||||
|
Case { "happy path won't throw" } = [] {
|
||||||
|
std::ignore = Set {};
|
||||||
|
std::ignore = Set { Set::max_capacity };
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "unhappy path throws" } = [] {
|
||||||
|
expect_throw([] { std::ignore = Set { Set::max_capacity + 1 }; });
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "post construct has correct state" } = [&] {
|
||||||
|
auto set = Set { capacity };
|
||||||
|
expect_eq(set.get_size(), 0);
|
||||||
|
expect_eq(set.get_capacity(), capacity);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Suite element_raii = [] {
|
||||||
|
Case { "many inserts/removes won't throw" } = [] {
|
||||||
|
auto set = Set {};
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
set.insert(idx, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
set.remove(idx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "insert returns reference to inserted value" } = [] {
|
||||||
|
auto set = Set {};
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
const auto val = Set::Dense_T { idx, {} };
|
||||||
|
expect_eq(set.insert(val.first, val.second), val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "post insert/remove has correct state" } = [] {
|
||||||
|
auto set = Set {};
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
set.insert(idx, idx * 2);
|
||||||
|
expect_eq(set.get_size(), idx + 1);
|
||||||
|
expect_eq(set.at(idx), Set::Dense_T { idx, idx * 2 });
|
||||||
|
expect_true(set.contains(idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
expect_eq(set.at(idx), Set::Dense_T { idx, idx * 2 });
|
||||||
|
expect_true(set.contains(idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
set.remove(idx);
|
||||||
|
|
||||||
|
expect_eq(set.get_size(), 10'000 - (idx + 1));
|
||||||
|
expect_throw([&] { std::ignore = set.at(idx); });
|
||||||
|
expect_false(set.contains(idx));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Suite getters = [] {
|
||||||
|
Case { "get_size returns correct values" } = [] {
|
||||||
|
auto set = Set {};
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
expect_eq(set.get_size(), idx);
|
||||||
|
set.insert(idx, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_eq(set.get_size(), 10'000);
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "get_capacity returns correct values" } = [] {
|
||||||
|
auto set = Set { 10'000 };
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
expect_eq(set.get_capacity(), 10'000); // are we testing std::vector's implementation?
|
||||||
|
set.insert(idx, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_eq(set.get_capacity(), 10'000);
|
||||||
|
|
||||||
|
set.insert(set.get_size(), {});
|
||||||
|
expect_ne(set.get_capacity(), 10'000);
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "at throws with out of bound access" } = [] {
|
||||||
|
auto set = Set {};
|
||||||
|
|
||||||
|
for (auto idx : std::views::iota(0, 50))
|
||||||
|
{
|
||||||
|
expect_throw([&] {
|
||||||
|
set.insert(idx, {});
|
||||||
|
std::ignore = set.at(50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
set.insert(50, {});
|
||||||
|
std::ignore = set.at(50); // should not throw
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Suite clear = [] {
|
||||||
|
Case { "post clear has correct state" } = [] {
|
||||||
|
auto set = Set { 0 };
|
||||||
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
|
{
|
||||||
|
set.insert(idx, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
set.clear();
|
||||||
|
expect_eq(set.get_size(), 0);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#include <ecs/uuid.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
std::mt19937_64 UUID::s_engine = std::mt19937_64(std::random_device()());
|
|
||||||
|
|
||||||
std::uniform_int_distribution<uint64_t>
|
|
||||||
UUID::s_distribution = std::uniform_int_distribution<uint64_t> {};
|
|
||||||
|
|
||||||
UUID::UUID(uint64_t uuid /* = -1 */): m_uuid(uuid == -1 ? s_distribution(s_engine) : uuid)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ecs/components/native_script.hpp>
|
|
||||||
#include <ecs/components/sprite_renderer.hpp>
|
|
||||||
#include <ecs/components/tag.hpp>
|
|
||||||
#include <ecs/components/transform.hpp>
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ecs/components/scriptable_entity.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
struct NativeScriptComponent
|
|
||||||
{
|
|
||||||
NativeScript *(*CreateInstance)();
|
|
||||||
|
|
||||||
void (*DestroyInstance)(NativeScriptComponent *);
|
|
||||||
|
|
||||||
template<typename t>
|
|
||||||
void bind()
|
|
||||||
{
|
|
||||||
CreateInstance = []() {
|
|
||||||
return static_cast<NativeScript *>(new t());
|
|
||||||
};
|
|
||||||
DestroyInstance = [](NativeScriptComponent *nsc) {
|
|
||||||
delete (t *)(nsc->instance);
|
|
||||||
nsc->instance = nullptr;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeScript *instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ecs/entity.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
class NativeScript
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
friend class Scene;
|
|
||||||
|
|
||||||
NativeScript() = default;
|
|
||||||
|
|
||||||
virtual ~NativeScript() = default;
|
|
||||||
|
|
||||||
[[nodiscard]] auto get_uid() const -> unsigned int
|
|
||||||
{
|
|
||||||
return m_unique_identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename t>
|
|
||||||
auto GetComponent() -> t &
|
|
||||||
{
|
|
||||||
return m_entity.get_component<t>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void on_create()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void on_destroy()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void on_update(float ts)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Entity m_entity;
|
|
||||||
|
|
||||||
unsigned int m_unique_identifier = 0; // :#todo
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <math/vec4.hpp>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
class Texture;
|
|
||||||
|
|
||||||
struct SpriteRendererComponent
|
|
||||||
{
|
|
||||||
SpriteRendererComponent() = default;
|
|
||||||
|
|
||||||
SpriteRendererComponent(const SpriteRendererComponent &) = default;
|
|
||||||
|
|
||||||
SpriteRendererComponent(
|
|
||||||
Ref<Texture> _texture,
|
|
||||||
const math::vec4 &_tint = math::vec4 { 1.0f, 1.0f, 1.0f, 1.0f }
|
|
||||||
)
|
|
||||||
: texture(std::move(std::move(_texture)))
|
|
||||||
, tint(_tint)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
operator Ref<Texture>() const
|
|
||||||
{
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ref<Texture> texture;
|
|
||||||
|
|
||||||
math::vec4 tint {};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
struct TagComponent
|
|
||||||
{
|
|
||||||
TagComponent() = default;
|
|
||||||
|
|
||||||
TagComponent(const TagComponent &) = default;
|
|
||||||
|
|
||||||
TagComponent(std::string _tag): tag(std::move(_tag))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
operator std::string() const
|
|
||||||
{
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator const std::string &() const
|
|
||||||
{
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string tag = "Unnamed";
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <math/mat4.hpp>
|
|
||||||
#include <math/vec3.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
struct TransformComponent
|
|
||||||
{
|
|
||||||
TransformComponent(const TransformComponent &) = default;
|
|
||||||
|
|
||||||
TransformComponent(
|
|
||||||
const math::vec3 &_translation = math::vec3(0.0f, 0.0f, 0.0f),
|
|
||||||
const math::vec3 &_scale = math::vec3(1.0f, 1.0f, 1.0f),
|
|
||||||
const math::vec3 &_rotation = math::vec3(0.0f, 0.0f, 0.0f)
|
|
||||||
)
|
|
||||||
|
|
||||||
: translation(_translation)
|
|
||||||
, scale(_scale)
|
|
||||||
, rotation(_rotation)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto get_transform() const -> math::mat4
|
|
||||||
{
|
|
||||||
return math::translate(translation)
|
|
||||||
* math::rotate(rotation.z, math::vec3 { 0.0f, 0.0f, 1.0f }) //
|
|
||||||
* math::scale(scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
operator const math::mat4() const
|
|
||||||
{
|
|
||||||
return get_transform();
|
|
||||||
}
|
|
||||||
|
|
||||||
math::vec3 translation;
|
|
||||||
|
|
||||||
math::vec3 scale;
|
|
||||||
|
|
||||||
math::vec3 rotation;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ecs/uuid.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
struct UUIDComponent
|
|
||||||
{
|
|
||||||
UUIDComponent(UUID _uuid): uuid(_uuid)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
UUIDComponent(const UUIDComponent &) = default;
|
|
||||||
|
|
||||||
UUID uuid;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,59 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ecs/components/uuid.hpp>
|
|
||||||
#include <ecs/scene.hpp>
|
|
||||||
#include <entt/entt.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
namespace lt {
|
||||||
|
|
||||||
class Entity
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Entity(entt::entity handle = entt::null, Scene *scene = nullptr);
|
|
||||||
|
|
||||||
template<typename t, typename... Args>
|
|
||||||
auto add_component(Args &&...args) -> t &
|
|
||||||
{
|
|
||||||
return m_scene->m_registry.emplace<t>(m_handle, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename t>
|
|
||||||
auto get_component() -> t &
|
|
||||||
{
|
|
||||||
return m_scene->m_registry.get<t>(m_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename t>
|
|
||||||
auto has_component() -> bool
|
|
||||||
{
|
|
||||||
return m_scene->m_registry.any_of<t>(m_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename t>
|
|
||||||
void remove_component()
|
|
||||||
{
|
|
||||||
m_scene->m_registry.remove<t>(m_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto get_uuid() -> uint64_t
|
|
||||||
{
|
|
||||||
return get_component<UUIDComponent>().uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto is_valid() const -> bool
|
|
||||||
{
|
|
||||||
return m_handle != entt::null && m_scene != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator uint32_t()
|
|
||||||
{
|
|
||||||
return (uint32_t)m_handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
entt::entity m_handle;
|
|
||||||
|
|
||||||
Scene *m_scene;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
} // namespace lt
|
||||||
|
|
|
||||||
243
modules/ecs/public/registry.hpp
Normal file
243
modules/ecs/public/registry.hpp
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ecs/sparse_set.hpp>
|
||||||
|
|
||||||
|
namespace lt::ecs {
|
||||||
|
|
||||||
|
using Entity = uint32_t;
|
||||||
|
|
||||||
|
/** A registry of components, the heart of an ECS architecture.
|
||||||
|
*
|
||||||
|
* @todo(Light): optimize multi-component views
|
||||||
|
* @todo(Light): support more than 2-component views
|
||||||
|
* @todo(Light): handle edge cases or specify the undefined behaviors
|
||||||
|
*/
|
||||||
|
class Registry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using UnderlyingSparseSet_T = TypeErasedSparseSet<Entity>;
|
||||||
|
|
||||||
|
using Callback_T = std::function<void(Registry &, Entity)>;
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void connect_on_construct(Callback_T callback)
|
||||||
|
{
|
||||||
|
m_on_construct_hooks[get_type_id<Component_T>()] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void connect_on_destruct(Callback_T callback)
|
||||||
|
{
|
||||||
|
m_on_destruct_hooks[get_type_id<Component_T>()] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void disconnect_on_construct()
|
||||||
|
{
|
||||||
|
m_on_construct_hooks.erase(get_type_id<Component_T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void disconnect_on_destruct()
|
||||||
|
{
|
||||||
|
m_on_destruct_hooks.erase(get_type_id<Component_T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto create_entity() -> Entity
|
||||||
|
{
|
||||||
|
++m_entity_count;
|
||||||
|
return m_current++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_entity(Entity entity)
|
||||||
|
{
|
||||||
|
for (const auto &[key, set] : m_sparsed_sets)
|
||||||
|
{
|
||||||
|
set->remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
--m_entity_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
auto get(Entity entity) -> Component_T &
|
||||||
|
{
|
||||||
|
auto &derived_set = get_derived_set<Component_T>();
|
||||||
|
return derived_set.at(entity).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
auto add(Entity entity, Component_T component) -> Component_T &
|
||||||
|
{
|
||||||
|
auto &derived_set = get_derived_set<Component_T>();
|
||||||
|
auto &added_component = derived_set.insert(entity, std::move(component)).second;
|
||||||
|
|
||||||
|
if (m_on_construct_hooks.contains(get_type_id<Component_T>()))
|
||||||
|
{
|
||||||
|
m_on_construct_hooks[get_type_id<Component_T>()](*this, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return added_component;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void remove(Entity entity)
|
||||||
|
{
|
||||||
|
if (m_on_destruct_hooks.contains(get_type_id<Component_T>()))
|
||||||
|
{
|
||||||
|
m_on_destruct_hooks[get_type_id<Component_T>()](*this, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &derived_set = get_derived_set<Component_T>();
|
||||||
|
derived_set.remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
auto view() -> SparseSet<Component_T, Entity>&
|
||||||
|
{
|
||||||
|
return get_derived_set<Component_T>();
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename ComponentA_T, typename ComponentB_T>
|
||||||
|
requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
|
||||||
|
auto view() -> std::vector<std::tuple<Entity, ComponentA_T &, ComponentB_T &>>
|
||||||
|
{
|
||||||
|
auto &set_a = get_derived_set<ComponentA_T>();
|
||||||
|
auto &set_b = get_derived_set<ComponentB_T>();
|
||||||
|
auto view = std::vector<std::tuple<Entity, ComponentA_T &, ComponentB_T &>> {};
|
||||||
|
|
||||||
|
/* iterate over the "smaller" component-set, and check if its entities have the other
|
||||||
|
* component */
|
||||||
|
if (set_a.get_size() > set_b.get_size())
|
||||||
|
{
|
||||||
|
for (auto &[entity, component_b] : set_b)
|
||||||
|
{
|
||||||
|
if (set_a.contains(entity))
|
||||||
|
{
|
||||||
|
view.emplace_back(std::tie(entity, set_a.at(entity).second, component_b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto &[entity, component_a] : set_a)
|
||||||
|
{
|
||||||
|
if (set_b.contains(entity))
|
||||||
|
{
|
||||||
|
view.emplace_back(std::tie(entity, component_a, set_b.at(entity).second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void each(std::function<void(Entity, Component_T &)> functor)
|
||||||
|
{
|
||||||
|
for (auto &[entity, component] : get_derived_set<Component_T>())
|
||||||
|
{
|
||||||
|
functor(entity, component);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename ComponentA_T, typename ComponentB_T>
|
||||||
|
requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
|
||||||
|
void each(std::function<void(Entity, ComponentA_T &, ComponentB_T &)> functor)
|
||||||
|
{
|
||||||
|
auto &set_a = get_derived_set<ComponentA_T>();
|
||||||
|
auto &set_b = get_derived_set<ComponentB_T>();
|
||||||
|
|
||||||
|
/* iterate over the "smaller" component-set, and check if its entities have the other
|
||||||
|
* component */
|
||||||
|
if (set_a.get_size() > set_b.get_size())
|
||||||
|
{
|
||||||
|
for (auto &[entity, component_b] : set_b)
|
||||||
|
{
|
||||||
|
if (set_a.contains(entity))
|
||||||
|
{
|
||||||
|
functor(entity, set_a.at(entity).second, component_b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &[entity, component_a] : set_a)
|
||||||
|
{
|
||||||
|
if (set_b.contains(entity))
|
||||||
|
{
|
||||||
|
functor(entity, component_a, set_b.at(entity).second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_entity_count() const -> size_t
|
||||||
|
{
|
||||||
|
return static_cast<size_t>(m_entity_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using TypeId = size_t;
|
||||||
|
|
||||||
|
static consteval auto hash_cstr(const char *str) -> TypeId
|
||||||
|
{
|
||||||
|
constexpr auto fnv_offset_basis = size_t { 14695981039346656037ull };
|
||||||
|
constexpr auto fnv_prime = size_t { 1099511628211ull };
|
||||||
|
|
||||||
|
auto hash = fnv_offset_basis;
|
||||||
|
|
||||||
|
for (const auto &ch : std::string_view { str })
|
||||||
|
{
|
||||||
|
hash *= fnv_prime;
|
||||||
|
hash ^= static_cast<uint8_t>(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static consteval auto get_type_id() -> TypeId
|
||||||
|
{
|
||||||
|
#if defined _MSC_VER
|
||||||
|
#define GENERATOR_PRETTY_FUNCTION __FUNCSIG__
|
||||||
|
#elif defined __clang__ || (defined __GNUC__)
|
||||||
|
#define GENERATOR_PRETTY_FUNCTION __PRETTY_FUNCTION__
|
||||||
|
#else
|
||||||
|
#error "Compiler not supported"
|
||||||
|
#endif
|
||||||
|
constexpr auto value = hash_cstr(GENERATOR_PRETTY_FUNCTION);
|
||||||
|
|
||||||
|
#undef GENERATOR_PRETTY_FUNCTION
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto get_derived_set() -> SparseSet<T, Entity> &
|
||||||
|
{
|
||||||
|
constexpr auto type_id = get_type_id<T>();
|
||||||
|
if (!m_sparsed_sets.contains(type_id))
|
||||||
|
{
|
||||||
|
m_sparsed_sets[type_id] = create_scope<SparseSet<T, Entity>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *base_set = m_sparsed_sets[type_id].get();
|
||||||
|
auto *derived_set = dynamic_cast<SparseSet<T, Entity> *>(base_set);
|
||||||
|
ensure(derived_set, "Failed to downcast to derived set");
|
||||||
|
|
||||||
|
return *derived_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity m_current;
|
||||||
|
|
||||||
|
TypeId m_entity_count;
|
||||||
|
|
||||||
|
std::flat_map<TypeId, Scope<UnderlyingSparseSet_T>> m_sparsed_sets;
|
||||||
|
|
||||||
|
std::flat_map<TypeId, Callback_T> m_on_construct_hooks;
|
||||||
|
|
||||||
|
std::flat_map<TypeId, Callback_T> m_on_destruct_hooks;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::ecs
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ecs/components/transform.hpp>
|
|
||||||
#include <ecs/uuid.hpp>
|
|
||||||
#include <entt/entt.hpp>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
class Entity;
|
|
||||||
class Framebuffer;
|
|
||||||
|
|
||||||
class Scene
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
template<typename... T>
|
|
||||||
auto group()
|
|
||||||
{
|
|
||||||
return m_registry.group(entt::get<T...>);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
auto view()
|
|
||||||
{
|
|
||||||
return m_registry.view<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto create_entity(
|
|
||||||
const std::string &name,
|
|
||||||
const TransformComponent &transform = TransformComponent()
|
|
||||||
) -> Entity;
|
|
||||||
|
|
||||||
auto get_entity_by_tag(const std::string &tag) -> Entity;
|
|
||||||
|
|
||||||
auto get_entt_registry() -> entt::registry &
|
|
||||||
{
|
|
||||||
return m_registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class Entity;
|
|
||||||
|
|
||||||
friend class SceneSerializer;
|
|
||||||
|
|
||||||
friend class SceneHierarchyPanel;
|
|
||||||
|
|
||||||
entt::registry m_registry;
|
|
||||||
|
|
||||||
auto create_entity_with_uuid(
|
|
||||||
const std::string &name,
|
|
||||||
UUID uuid,
|
|
||||||
const TransformComponent &transform = TransformComponent()
|
|
||||||
) -> Entity;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace ecs {
|
|
||||||
|
|
||||||
using Registry = Scene;
|
|
||||||
|
|
||||||
using Entity = ::lt::Entity;
|
|
||||||
|
|
||||||
} // namespace ecs
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
141
modules/ecs/public/sparse_set.hpp
Normal file
141
modules/ecs/public/sparse_set.hpp
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace lt::ecs {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @ref https://programmingpraxis.com/2012/03/09/sparse-sets/
|
||||||
|
*/
|
||||||
|
template<typename Identifier_T = uint32_t>
|
||||||
|
class TypeErasedSparseSet
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TypeErasedSparseSet() = default;
|
||||||
|
|
||||||
|
TypeErasedSparseSet(TypeErasedSparseSet &&) = default;
|
||||||
|
|
||||||
|
TypeErasedSparseSet(const TypeErasedSparseSet &) = default;
|
||||||
|
|
||||||
|
auto operator=(TypeErasedSparseSet &&) -> TypeErasedSparseSet & = default;
|
||||||
|
|
||||||
|
auto operator=(const TypeErasedSparseSet &) -> TypeErasedSparseSet & = default;
|
||||||
|
|
||||||
|
virtual ~TypeErasedSparseSet() = default;
|
||||||
|
|
||||||
|
virtual void remove(Identifier_T identifier) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @todo(Light): implement identifier recycling.
|
||||||
|
*/
|
||||||
|
template<typename Value_T, typename Identifier_T = uint32_t>
|
||||||
|
class SparseSet: public TypeErasedSparseSet<Identifier_T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Dense_T = std::pair<Identifier_T, Value_T>;
|
||||||
|
|
||||||
|
static constexpr auto max_capacity = size_t { 1'000'000 };
|
||||||
|
|
||||||
|
static constexpr auto null_identifier = std::numeric_limits<Identifier_T>().max();
|
||||||
|
|
||||||
|
explicit SparseSet(size_t initial_capacity = 1)
|
||||||
|
{
|
||||||
|
ensure(
|
||||||
|
initial_capacity <= max_capacity,
|
||||||
|
"Failed to create SparseSet: capacity too large ({} > {})",
|
||||||
|
initial_capacity,
|
||||||
|
max_capacity
|
||||||
|
);
|
||||||
|
|
||||||
|
m_dense.reserve(initial_capacity);
|
||||||
|
m_sparse.resize(initial_capacity, null_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto insert(Identifier_T identifier, Value_T value) -> Dense_T &
|
||||||
|
{
|
||||||
|
if (m_sparse.size() < identifier + 1)
|
||||||
|
{
|
||||||
|
auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2);
|
||||||
|
new_capacity = std::min(new_capacity, max_capacity);
|
||||||
|
|
||||||
|
// log_dbg("Increasing sparse vector size:", m_dead_count);
|
||||||
|
// log_dbg("\tdead_count: {}", m_dead_count);
|
||||||
|
// log_dbg("\talive_count: {}", m_alive_count);
|
||||||
|
// log_dbg("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
|
||||||
|
|
||||||
|
m_sparse.resize(new_capacity, null_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
++m_alive_count;
|
||||||
|
m_sparse[identifier] = m_dense.size();
|
||||||
|
return m_dense.emplace_back(identifier, std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(Identifier_T identifier) override
|
||||||
|
{
|
||||||
|
auto &idx = m_sparse[identifier];
|
||||||
|
auto &[entity, component] = m_dense[idx];
|
||||||
|
|
||||||
|
idx = null_identifier;
|
||||||
|
++m_dead_count;
|
||||||
|
--m_alive_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
m_dense.clear();
|
||||||
|
m_sparse.clear();
|
||||||
|
m_alive_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto contains(Identifier_T identifier) const -> bool
|
||||||
|
{
|
||||||
|
return m_sparse.size() > identifier //
|
||||||
|
&& m_sparse[identifier] != null_identifier //
|
||||||
|
&& m_dense[m_sparse[identifier]].first == identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto begin() -> std::vector<Dense_T>::iterator
|
||||||
|
{
|
||||||
|
return m_dense.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end() -> std::vector<Dense_T>::iterator
|
||||||
|
{
|
||||||
|
return m_dense.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto at(Identifier_T identifier) -> Dense_T &
|
||||||
|
{
|
||||||
|
return m_dense.at(m_sparse.at(identifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @warn unsafe, for bound-checked access: use `.at` */
|
||||||
|
[[nodiscard]] auto &&operator[](this auto &&self, Identifier_T identifier)
|
||||||
|
{
|
||||||
|
using Self_T = decltype(self);
|
||||||
|
return std::forward<Self_T>(self).m_dense[std::forward<Self_T>(self).m_sparse[identifier]];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_size() const noexcept -> size_t
|
||||||
|
{
|
||||||
|
return m_alive_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_capacity() const noexcept -> size_t
|
||||||
|
{
|
||||||
|
return m_sparse.capacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Dense_T> m_dense;
|
||||||
|
|
||||||
|
std::vector<Identifier_T> m_sparse;
|
||||||
|
|
||||||
|
size_t m_alive_count {};
|
||||||
|
|
||||||
|
size_t m_dead_count {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::ecs
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
class UUID
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
UUID(uint64_t uuid = -1);
|
|
||||||
|
|
||||||
operator uint64_t() const
|
|
||||||
{
|
|
||||||
return m_uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::mt19937_64 s_engine;
|
|
||||||
|
|
||||||
static std::uniform_int_distribution<uint64_t> s_distribution;
|
|
||||||
|
|
||||||
uint64_t m_uuid;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
||||||
namespace std {
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct hash<lt::UUID>
|
|
||||||
{
|
|
||||||
std::size_t operator()(const lt::UUID &uuid) const
|
|
||||||
{
|
|
||||||
return hash<uint64_t>()(static_cast<uint64_t>(uuid));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace std
|
|
||||||
|
|
@ -16,15 +16,16 @@ System::System(Ref<ecs::Registry> registry): m_registry(std::move(registry))
|
||||||
|
|
||||||
auto System::tick() -> bool
|
auto System::tick() -> bool
|
||||||
{
|
{
|
||||||
m_registry->view<surface::SurfaceComponent>().each([&](const entt::entity,
|
for (auto &[entity, surface] : m_registry->view<surface::SurfaceComponent>())
|
||||||
surface::SurfaceComponent &surface) {
|
{
|
||||||
for (const auto &event : surface.peek_events())
|
for (const auto &event : surface.peek_events())
|
||||||
{
|
{
|
||||||
handle_event(event);
|
handle_event(event);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
m_registry->view<InputComponent>().each([&](const entt::entity, InputComponent &input) {
|
for (auto &[entity, input] : m_registry->view<InputComponent>())
|
||||||
|
{
|
||||||
// TODO(Light): instead of iterating over all actions each frame,
|
// TODO(Light): instead of iterating over all actions each frame,
|
||||||
// make a list of "dirty" actions to reset
|
// make a list of "dirty" actions to reset
|
||||||
// and a surface_input->input_action mapping to get to action through input
|
// and a surface_input->input_action mapping to get to action through input
|
||||||
|
|
@ -48,7 +49,7 @@ auto System::tick() -> bool
|
||||||
action.state = InputAction::State::inactive;
|
action.state = InputAction::State::inactive;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include <ecs/entity.hpp>
|
#include <ecs/entity.hpp>
|
||||||
#include <input/components.hpp>
|
#include <input/components.hpp>
|
||||||
#include <input/system.hpp>
|
#include <input/system.hpp>
|
||||||
|
#include <ranges>
|
||||||
#include <test/test.hpp>
|
#include <test/test.hpp>
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
|
|
@ -27,16 +28,19 @@ public:
|
||||||
|
|
||||||
auto add_input_component() -> ecs::Entity
|
auto add_input_component() -> ecs::Entity
|
||||||
{
|
{
|
||||||
auto entity = m_registry->create_entity("");
|
auto entity = m_registry->create_entity();
|
||||||
entity.add_component<InputComponent>();
|
m_registry->add<InputComponent>(entity, {});
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto add_surface_component() -> ecs::Entity
|
auto add_surface_component() -> ecs::Entity
|
||||||
{
|
{
|
||||||
auto entity = m_registry->create_entity("");
|
auto entity = m_registry->create_entity();
|
||||||
entity.add_component<surface::SurfaceComponent>(surface::SurfaceComponent::CreateInfo {});
|
m_registry->add<surface::SurfaceComponent>(
|
||||||
|
entity,
|
||||||
|
surface::SurfaceComponent::CreateInfo {}
|
||||||
|
);
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
@ -66,53 +70,58 @@ Suite raii = [] {
|
||||||
Suite system_events = [] {
|
Suite system_events = [] {
|
||||||
Case { "on_register won't throw" } = [] {
|
Case { "on_register won't throw" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
auto system = System { fixture.registry() };
|
auto registry = fixture.registry();
|
||||||
|
auto system = System { registry };
|
||||||
|
|
||||||
system.on_register();
|
system.on_register();
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 0);
|
expect_eq(registry->view<InputComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "on_unregister won't throw" } = [] {
|
Case { "on_unregister won't throw" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
auto system = System { fixture.registry() };
|
auto registry = fixture.registry();
|
||||||
|
auto system = System { registry };
|
||||||
|
|
||||||
system.on_register();
|
system.on_register();
|
||||||
system.on_unregister();
|
system.on_unregister();
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 0);
|
expect_eq(registry->view<InputComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Suite registry_events = [] {
|
Suite registry_events = [] {
|
||||||
Case { "on_construct<InputComnent>" } = [] {
|
Case { "on_construct<InputComnent>" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
auto system = System { fixture.registry() };
|
auto registry = fixture.registry();
|
||||||
|
auto system = System { registry };
|
||||||
|
|
||||||
const auto &entity = fixture.add_input_component();
|
const auto &entity = fixture.add_input_component();
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 1);
|
expect_eq(registry->view<InputComponent>().get_size(), 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "on_destrroy<InputComponent>" } = [] {
|
Case { "on_destrroy<InputComponent>" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
auto system = create_scope<System>(fixture.registry());
|
auto registry = fixture.registry();
|
||||||
|
auto system = create_scope<System>(registry);
|
||||||
|
|
||||||
auto entity_a = fixture.add_input_component();
|
auto entity_a = fixture.add_input_component();
|
||||||
auto entity_b = fixture.add_input_component();
|
auto entity_b = fixture.add_input_component();
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 2);
|
expect_eq(registry->view<InputComponent>().get_size(), 2);
|
||||||
|
|
||||||
entity_a.remove_component<InputComponent>();
|
registry->remove<InputComponent>(entity_a);
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 1);
|
expect_eq(registry->view<InputComponent>().get_size(), 1);
|
||||||
|
|
||||||
system.reset();
|
system.reset();
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 1);
|
expect_eq(registry->view<InputComponent>().get_size(), 1);
|
||||||
|
|
||||||
entity_b.remove_component<InputComponent>();
|
registry->remove<InputComponent>(entity_b);
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 0);
|
expect_eq(registry->view<InputComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Suite tick = [] {
|
Suite tick = [] {
|
||||||
Case { "Empty tick won't throw" } = [] {
|
Case { "Empty tick won't throw" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
|
auto registry = fixture.registry();
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
|
|
||||||
expect_false(system.tick());
|
expect_false(system.tick());
|
||||||
|
|
@ -120,10 +129,14 @@ Suite tick = [] {
|
||||||
|
|
||||||
Case { "Tick triggers input action" } = [] {
|
Case { "Tick triggers input action" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
|
auto registry = fixture.registry();
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
|
|
||||||
auto &surface = fixture.add_surface_component().get_component<surface::SurfaceComponent>();
|
auto surface_entity = fixture.add_surface_component();
|
||||||
auto &input = fixture.add_input_component().get_component<InputComponent>();
|
auto &surface = registry->get<surface::SurfaceComponent>(surface_entity);
|
||||||
|
|
||||||
|
auto input_entity = fixture.add_input_component();
|
||||||
|
auto &input = registry->get<InputComponent>(input_entity);
|
||||||
|
|
||||||
auto action_key = input.add_action(
|
auto action_key = input.add_action(
|
||||||
{
|
{
|
||||||
|
|
@ -155,10 +168,15 @@ Suite tick = [] {
|
||||||
|
|
||||||
Case { "Tick triggers" } = [] {
|
Case { "Tick triggers" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
|
auto registry = fixture.registry();
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
|
|
||||||
auto &surface = fixture.add_surface_component().get_component<surface::SurfaceComponent>();
|
auto surface_entity = fixture.add_surface_component();
|
||||||
auto &input = fixture.add_input_component().get_component<InputComponent>();
|
auto &surface = registry->get<surface::SurfaceComponent>(surface_entity);
|
||||||
|
|
||||||
|
auto input_entity = fixture.add_input_component();
|
||||||
|
auto &input = registry->get<InputComponent>(input_entity);
|
||||||
|
|
||||||
|
|
||||||
auto action_key = input.add_action(
|
auto action_key = input.add_action(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <app/system.hpp>
|
#include <app/system.hpp>
|
||||||
#include <ecs/scene.hpp>
|
#include <ecs/registry.hpp>
|
||||||
#include <surface/components.hpp>
|
#include <surface/components.hpp>
|
||||||
#include <surface/events/keyboard.hpp>
|
#include <surface/events/keyboard.hpp>
|
||||||
#include <surface/events/mouse.hpp>
|
#include <surface/events/mouse.hpp>
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ public:
|
||||||
using Surface = lt::surface::SurfaceComponent;
|
using Surface = lt::surface::SurfaceComponent;
|
||||||
using Input = lt::input::InputComponent;
|
using Input = lt::input::InputComponent;
|
||||||
|
|
||||||
auto view = m_registry->get_entt_registry().view<Surface, Input>();
|
for (auto &[entity, surface, input] : m_registry->view<Surface, Input>())
|
||||||
|
{
|
||||||
view.each([&](Surface &surface, Input &input) {});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tick() -> bool override
|
auto tick() -> bool override
|
||||||
|
|
@ -42,8 +42,8 @@ public:
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds { 10 });
|
std::this_thread::sleep_for(std::chrono::milliseconds { 10 });
|
||||||
auto should_quit = false;
|
auto should_quit = false;
|
||||||
auto view = m_registry->get_entt_registry().view<Surface, Input>();
|
for (auto &[entity, surface, input] : m_registry->view<Surface, Input>())
|
||||||
view.each([&](Surface &surface, Input &input) {
|
{
|
||||||
using State = lt::input::InputAction::State;
|
using State = lt::input::InputAction::State;
|
||||||
const auto &[x, y] = surface.get_position();
|
const auto &[x, y] = surface.get_position();
|
||||||
const auto &[width, height] = surface.get_resolution();
|
const auto &[width, height] = surface.get_resolution();
|
||||||
|
|
@ -76,7 +76,7 @@ public:
|
||||||
log_dbg("Deubg action 4");
|
log_dbg("Deubg action 4");
|
||||||
surface.push_request(surface::ModifyResolutionRequest({ width - 5, height - 5 }));
|
surface.push_request(surface::ModifyResolutionRequest({ width - 5, height - 5 }));
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
timer.reset();
|
timer.reset();
|
||||||
return should_quit;
|
return should_quit;
|
||||||
|
|
@ -125,15 +125,18 @@ public:
|
||||||
using lt::surface::SurfaceComponent;
|
using lt::surface::SurfaceComponent;
|
||||||
m_surface_system = create_ref<lt::surface::System>(m_editor_registry);
|
m_surface_system = create_ref<lt::surface::System>(m_editor_registry);
|
||||||
|
|
||||||
m_window = m_editor_registry->create_entity("Editor Window");
|
m_window = m_editor_registry->create_entity();
|
||||||
m_window.add_component<SurfaceComponent>(SurfaceComponent::CreateInfo {
|
m_editor_registry->add<SurfaceComponent>(
|
||||||
.title = "Editor Window",
|
m_window,
|
||||||
.resolution = { 400u, 400u },
|
SurfaceComponent::CreateInfo {
|
||||||
.vsync = true,
|
.title = "Editor Window",
|
||||||
.visible = true,
|
.resolution = { 400u, 400u },
|
||||||
});
|
.vsync = true,
|
||||||
|
.visible = true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
auto &input = m_window.add_component<InputComponent>();
|
auto &input = m_editor_registry->add<InputComponent>(m_window, {});
|
||||||
auto quit_action_key = input.add_action(
|
auto quit_action_key = input.add_action(
|
||||||
input::InputAction {
|
input::InputAction {
|
||||||
.name = "quit",
|
.name = "quit",
|
||||||
|
|
@ -142,7 +145,6 @@ public:
|
||||||
);
|
);
|
||||||
|
|
||||||
auto debug_action_keys = std::array<lt::input::InputAction::Key, 4> {};
|
auto debug_action_keys = std::array<lt::input::InputAction::Key, 4> {};
|
||||||
|
|
||||||
debug_action_keys[0] = input.add_action(
|
debug_action_keys[0] = input.add_action(
|
||||||
input::InputAction {
|
input::InputAction {
|
||||||
.name = "debug_1",
|
.name = "debug_1",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
#include <asset_manager/asset_manager.hpp>
|
#include <asset_manager/asset_manager.hpp>
|
||||||
#include <camera/component.hpp>
|
#include <camera/component.hpp>
|
||||||
#include <ecs/components.hpp>
|
#include <ecs/components.hpp>
|
||||||
#include <ecs/scene.hpp>
|
#include <ecs/registry.hpp>
|
||||||
#include <ecs/serializer.hpp>
|
#include <ecs/serializer.hpp>
|
||||||
#include <input/input.hpp>
|
#include <input/input.hpp>
|
||||||
#include <input/key_codes.hpp>
|
#include <input/key_codes.hpp>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#include <asset_manager/asset_manager.hpp>
|
#include <asset_manager/asset_manager.hpp>
|
||||||
#include <ecs/scene.hpp>
|
#include <ecs/registry.hpp>
|
||||||
#include <ecs/serializer.hpp>
|
#include <ecs/serializer.hpp>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <mirror/panels/asset_browser.hpp>
|
#include <mirror/panels/asset_browser.hpp>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
#include <ecs/components.hpp>
|
#include <ecs/components.hpp>
|
||||||
#include <entt/entt.hpp>
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <mirror/panels/properties.hpp>
|
#include <mirror/panels/properties.hpp>
|
||||||
#include <mirror/panels/scene_hierarchy.hpp>
|
#include <mirror/panels/scene_hierarchy.hpp>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ecs/entity.hpp>
|
#include <ecs/entity.hpp>
|
||||||
#include <ecs/scene.hpp>
|
#include <ecs/registry.hpp>
|
||||||
#include <mirror/panels/panel.hpp>
|
#include <mirror/panels/panel.hpp>
|
||||||
|
|
||||||
namespace lt {
|
namespace lt {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ target_link_libraries(
|
||||||
PUBLIC logger
|
PUBLIC logger
|
||||||
PUBLIC imgui
|
PUBLIC imgui
|
||||||
PUBLIC asset_parser
|
PUBLIC asset_parser
|
||||||
PUBLIC EnTT::EnTT
|
|
||||||
PRIVATE lt_debug
|
PRIVATE lt_debug
|
||||||
PRIVATE window
|
PRIVATE window
|
||||||
PUBLIC vulkan
|
PUBLIC vulkan
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include <camera/scene.hpp>
|
#include <ecs/registry.hpp>
|
||||||
#include <input/events/window.hpp>
|
#include <input/events/window.hpp>
|
||||||
#include <lt_debug/assertions.hpp>
|
#include <lt_debug/assertions.hpp>
|
||||||
#include <renderer/blender.hpp>
|
#include <renderer/blender.hpp>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <base/base.hpp>
|
#include <base/base.hpp>
|
||||||
#include <ecs/scene.hpp>
|
#include <ecs/registry.hpp>
|
||||||
|
|
||||||
namespace lt::renderer {
|
namespace lt::renderer {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ endif()
|
||||||
target_link_libraries(surface PUBLIC
|
target_link_libraries(surface PUBLIC
|
||||||
ecs
|
ecs
|
||||||
app
|
app
|
||||||
|
math
|
||||||
PRIVATE
|
PRIVATE
|
||||||
logger
|
logger
|
||||||
lt_debug
|
lt_debug
|
||||||
|
|
|
||||||
|
|
@ -40,40 +40,32 @@ System::System(Ref<ecs::Registry> registry): m_registry(std::move(registry))
|
||||||
ensure(m_registry, "Failed to initialize surface system: null registry");
|
ensure(m_registry, "Failed to initialize surface system: null registry");
|
||||||
|
|
||||||
ensure(
|
ensure(
|
||||||
m_registry->view<SurfaceComponent>().size() == 0,
|
m_registry->view<SurfaceComponent>().get_size() == 0,
|
||||||
"Failed to initialize surface system: registry has surface component(s)"
|
"Failed to initialize surface system: registry has surface component(s)"
|
||||||
);
|
);
|
||||||
|
|
||||||
m_registry->get_entt_registry()
|
m_registry->connect_on_construct<SurfaceComponent>(
|
||||||
.on_construct<SurfaceComponent>()
|
[this](ecs::Registry ®istry, ecs::Entity entity) {
|
||||||
.connect<&System::on_surface_construct>(this);
|
on_surface_construct(registry, entity);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
m_registry->get_entt_registry()
|
m_registry->connect_on_destruct<SurfaceComponent>(
|
||||||
.on_update<SurfaceComponent>()
|
[this](ecs::Registry ®istry, ecs::Entity entity) {
|
||||||
.connect<&System::on_surface_update>(this);
|
on_surface_destruct(registry, entity);
|
||||||
|
}
|
||||||
m_registry->get_entt_registry()
|
);
|
||||||
.on_destroy<SurfaceComponent>()
|
|
||||||
.connect<&System::on_surface_destroy>(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
System::~System()
|
System::~System()
|
||||||
{
|
{
|
||||||
m_registry->view<SurfaceComponent>().each([&](const entt::entity entity, SurfaceComponent &) {
|
for (auto &[entity, surface] : m_registry->view<SurfaceComponent>())
|
||||||
m_registry->get_entt_registry().remove<SurfaceComponent>(entity);
|
{
|
||||||
});
|
m_registry->remove<SurfaceComponent>(entity);
|
||||||
|
}
|
||||||
|
|
||||||
m_registry->get_entt_registry()
|
m_registry->disconnect_on_construct<SurfaceComponent>();
|
||||||
.on_construct<SurfaceComponent>()
|
m_registry->disconnect_on_destruct<SurfaceComponent>();
|
||||||
.disconnect<&System::on_surface_construct>(this);
|
|
||||||
|
|
||||||
m_registry->get_entt_registry()
|
|
||||||
.on_update<SurfaceComponent>()
|
|
||||||
.connect<&System::on_surface_update>(this);
|
|
||||||
|
|
||||||
m_registry->get_entt_registry()
|
|
||||||
.on_destroy<SurfaceComponent>()
|
|
||||||
.disconnect<&System::on_surface_destroy>(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::on_register()
|
void System::on_register()
|
||||||
|
|
@ -84,7 +76,7 @@ void System::on_unregister()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::on_surface_construct(entt::registry ®istry, entt::entity entity)
|
void System::on_surface_construct(ecs::Registry ®istry, ecs::Entity entity)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -173,12 +165,7 @@ void System::on_surface_construct(entt::registry ®istry, entt::entity entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::on_surface_update(entt::registry ®istry, entt::entity entity)
|
void System::on_surface_destruct(ecs::Registry ®istry, ecs::Entity entity)
|
||||||
{
|
|
||||||
auto &surface = registry.get<SurfaceComponent>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void System::on_surface_destroy(entt::registry ®istry, entt::entity entity)
|
|
||||||
{
|
{
|
||||||
const auto &[display, window, _] = registry.get<SurfaceComponent>(entity).get_native_data();
|
const auto &[display, window, _] = registry.get<SurfaceComponent>(entity).get_native_data();
|
||||||
if (!display)
|
if (!display)
|
||||||
|
|
@ -374,11 +361,12 @@ void System::modify_visiblity(SurfaceComponent &surface, const ModifyVisibilityR
|
||||||
|
|
||||||
auto System::tick() -> bool
|
auto System::tick() -> bool
|
||||||
{
|
{
|
||||||
m_registry->view<SurfaceComponent>().each([this](SurfaceComponent &surface) {
|
for (auto &dense : m_registry->view<SurfaceComponent>())
|
||||||
|
{
|
||||||
|
auto &surface = dense.second;
|
||||||
handle_requests(surface);
|
handle_requests(surface);
|
||||||
|
|
||||||
handle_events(surface);
|
handle_events(surface);
|
||||||
});
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#include <ecs/entity.hpp>
|
#include <ecs/entity.hpp>
|
||||||
#include <ecs/scene.hpp>
|
#include <ecs/registry.hpp>
|
||||||
#include <surface/components.hpp>
|
#include <surface/components.hpp>
|
||||||
#include <surface/system.hpp>
|
#include <surface/system.hpp>
|
||||||
#include <test/fuzz.hpp>
|
#include <test/fuzz.hpp>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include <ecs/entity.hpp>
|
#include <ecs/entity.hpp>
|
||||||
|
#include <ranges>
|
||||||
#include <surface/components.hpp>
|
#include <surface/components.hpp>
|
||||||
#include <surface/requests/surface.hpp>
|
#include <surface/requests/surface.hpp>
|
||||||
#include <surface/system.hpp>
|
#include <surface/system.hpp>
|
||||||
|
|
@ -45,8 +46,8 @@ public:
|
||||||
}
|
}
|
||||||
) -> SurfaceComponent &
|
) -> SurfaceComponent &
|
||||||
{
|
{
|
||||||
auto entity = m_registry->create_entity("");
|
auto entity = m_registry->create_entity();
|
||||||
return entity.add_component<SurfaceComponent>(info);
|
return m_registry->add<SurfaceComponent>(entity, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
void check_values(const SurfaceComponent &component)
|
void check_values(const SurfaceComponent &component)
|
||||||
|
|
@ -92,7 +93,7 @@ Suite raii = [] {
|
||||||
Case { "post construct has correct state" } = [] {
|
Case { "post construct has correct state" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
expect_eq(fixture.registry()->view<SurfaceComponent>()->size(), 0);
|
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "post destruct has correct state" } = [] {
|
Case { "post destruct has correct state" } = [] {
|
||||||
|
|
@ -100,10 +101,10 @@ Suite raii = [] {
|
||||||
auto system = create_scope<System>(fixture.registry());
|
auto system = create_scope<System>(fixture.registry());
|
||||||
|
|
||||||
fixture.add_surface_component();
|
fixture.add_surface_component();
|
||||||
expect_eq(fixture.registry()->view<SurfaceComponent>()->size(), 1);
|
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
|
||||||
|
|
||||||
system.reset();
|
system.reset();
|
||||||
expect_eq(fixture.registry()->view<SurfaceComponent>()->size(), 0);
|
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -113,7 +114,7 @@ Suite system_events = [] {
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
|
|
||||||
system.on_register();
|
system.on_register();
|
||||||
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
|
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "on_unregister won't throw" } = [] {
|
Case { "on_unregister won't throw" } = [] {
|
||||||
|
|
@ -122,7 +123,7 @@ Suite system_events = [] {
|
||||||
|
|
||||||
system.on_register();
|
system.on_register();
|
||||||
system.on_unregister();
|
system.on_unregister();
|
||||||
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
|
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -132,7 +133,7 @@ Suite registry_events = [] {
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
|
|
||||||
const auto &component = fixture.add_surface_component();
|
const auto &component = fixture.add_surface_component();
|
||||||
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 1);
|
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
|
||||||
fixture.check_values(component);
|
fixture.check_values(component);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -168,7 +169,7 @@ Suite registry_events = [] {
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
|
|
||||||
expect_throw([&] { fixture.add_surface_component({ .resolution = { width, 0 } }); });
|
expect_throw([&] { fixture.add_surface_component({ .resolution = { width, 0 } }); });
|
||||||
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
|
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "on_destrroy<SurfaceComponent> cleans up component" } = [] {
|
Case { "on_destrroy<SurfaceComponent> cleans up component" } = [] {
|
||||||
|
|
@ -176,11 +177,11 @@ Suite registry_events = [] {
|
||||||
auto system = create_scope<System>(fixture.registry());
|
auto system = create_scope<System>(fixture.registry());
|
||||||
|
|
||||||
const auto &component = fixture.add_surface_component();
|
const auto &component = fixture.add_surface_component();
|
||||||
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 1);
|
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
|
||||||
fixture.check_values(component);
|
fixture.check_values(component);
|
||||||
|
|
||||||
system.reset();
|
system.reset();
|
||||||
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
|
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <app/system.hpp>
|
#include <app/system.hpp>
|
||||||
#include <ecs/scene.hpp>
|
#include <ecs/registry.hpp>
|
||||||
|
#include <math/vec2.hpp>
|
||||||
|
|
||||||
namespace lt::surface {
|
namespace lt::surface {
|
||||||
|
|
||||||
|
|
@ -27,11 +28,9 @@ public:
|
||||||
auto tick() -> bool override;
|
auto tick() -> bool override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void on_surface_construct(entt::registry ®istry, entt::entity entity);
|
void on_surface_construct(ecs::Registry ®istry, ecs::Entity entity);
|
||||||
|
|
||||||
void on_surface_update(entt::registry ®istry, entt::entity entity);
|
void on_surface_destruct(ecs::Registry ®istry, ecs::Entity entity);
|
||||||
|
|
||||||
void on_surface_destroy(entt::registry ®istry, entt::entity entity);
|
|
||||||
|
|
||||||
void handle_requests(struct SurfaceComponent &surface);
|
void handle_requests(struct SurfaceComponent &surface);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
find_package(EnTT REQUIRED)
|
|
||||||
Loading…
Add table
Reference in a new issue