- Refactor on component system to support multiple scenes.

- node2d for 2d scenes
This commit is contained in:
2025-09-25 19:30:08 -04:00
parent f636feb4f1
commit 8925b3f2f0
10 changed files with 422 additions and 171 deletions

View File

@@ -92,6 +92,8 @@ add_library(fennec STATIC
# SCENE ================================================================================================================
include/fennec/scene/scene.h
include/fennec/scene/component.h
include/fennec/scene/scene_node.h
include/fennec/scene/node2d.h
# Renderers ============================================================================================================
@@ -217,9 +219,10 @@ add_library(fennec STATIC
# langproc ================================================================================================================
# Strings
include/fennec/langproc/strings/cstring.h
include/fennec/langproc/strings/locale.h
include/fennec/langproc/strings/cstring.h
include/fennec/langproc/strings/string.h
include/fennec/langproc/strings/format.h
include/fennec/langproc/strings/detail/_ctype.h
@@ -244,8 +247,6 @@ add_library(fennec STATIC
# EXTRA SOURCES ========================================================================================================
${FENNEC_EXTRA_SOURCES}
include/fennec/langproc/strings/format.h
include/fennec/scene/components/transform2d.h
)
add_dependencies(fennec metaprogramming fennec-dependencies)

View File

@@ -48,7 +48,8 @@ struct object_pool {
// Definitions =========================================================================================================
public:
using value_t = TypeT;
using table_t = allocation<value_t, AllocT>;
using elem_t = optional<value_t>;
using table_t = dynarray<elem_t, AllocT>;
using freed_t = list<size_t, AllocT>;
@@ -119,7 +120,8 @@ public:
/// \returns a reference to the object with id `i`
constexpr value_t& operator[](size_t i) {
assert(i < capacity(), "Index out of Bounds!");
return _table[i];
assert(_table[i], "Attempted to access null object.")
return *_table[i];
}
///
@@ -128,7 +130,8 @@ public:
/// \returns a const-qualified reference to the object with id `i`
constexpr const value_t& operator[](size_t i) const {
assert(i < capacity(), "Index out of Bounds!");
return _table[i];
assert(_table[i], "Attempted to access null object.")
return *_table[i];
}
/// @}
@@ -169,7 +172,7 @@ public:
/// \brief Erase an object from the pool
/// \param i The id of the object
constexpr void erase(size_t i) {
fennec::destruct(&_table[i]);
_table[i] = nullopt;
_freed.push_back(i);
--_size;
}
@@ -177,8 +180,11 @@ public:
///
/// \brief Clear the object pool
constexpr void clear() {
dynarray<bool> free(capacity(), false);
for (auto& it : _table) {
it = nullopt;
}
_size = 0;
_freed.clear();
}
/// @}
@@ -201,10 +207,10 @@ private:
template<typename...ArgsT>
size_t _insert(ArgsT&&...args) {
size_t i = _next_free();
if (i >= _table.capacity()) {
_table.creallocate(fennec::max(_table.size() * 2, size_t(8)));
if (i >= _table.size()) {
_table.resize(fennec::max(_table.size() * 2, size_t(8)));
}
fennec::construct(&_table[i], fennec::forward<ArgsT>(args)...);
_table[i].emplace(fennec::forward<ArgsT>(args)...);
return i;
}
};

View File

@@ -58,9 +58,9 @@ public:
protected:
struct node {
optional<value_t> value;
size_t parent, child, prev, next;
size_t depth, num_children;
value_t value;
size_t parent, child, prev, next;
size_t depth, num_children;
constexpr node()
: value(nullopt)
@@ -315,25 +315,15 @@ public:
///
/// \param i The id of the node to access
/// \returns A reference to the value of the node wrapped in an optional
constexpr value_t* operator[](size_t i) {
auto& it = _table[i].value;
if (it) {
return &*_table[i].value;
} else {
return nullptr;
}
constexpr value_t& operator[](size_t i) {
return _table[i].value;
}
///
/// \param i The id of the node to access
/// \returns A const-qualified reference to the value of the node wrapped in an optional
constexpr const value_t* operator[](size_t i) const {
const auto& it = _table[i].value;
if (it) {
return &*_table[i].value;
} else {
return nullptr;
}
constexpr const value_t& operator[](size_t i) const {
return _table[i].value;
}
@@ -420,7 +410,7 @@ public:
while (i != npos) {
uint8_t mode = traversal_control_continue;
if (_table[i].value) {
mode = visit(*_table[i].value, i);
mode = visit(_table[i].value, i);
}
if (mode == traversal_control_break) {
break;

View File

@@ -32,6 +32,8 @@
#define FENNEC_LANGPROC_FORMAT_TOKENIZER_H
#include <fennec/containers/list.h>
#include <fennec/containers/map.h>
#include <fennec/containers/priority_queue.h>
#include <fennec/langproc/strings/string.h>
//
@@ -55,7 +57,7 @@ namespace fennec
{
struct escape_sequence {
virtual size_t operator[](const std::string& str, size_t i) = 0;
virtual size_t operator[](const string& str, size_t i) = 0;
};
struct tokenizer {
@@ -91,12 +93,12 @@ private:
list<token> res;
priority_queue<pair<size_t, uint8_t>> idx;
for (size_t i = 0; i < line.size(); ++i) {
for (char c : delimiter) {
idx.emplace()
for (char c : delimiter) {
size_t i = 0;
while (i != line.size()) {
size_t n = line.find(c, i);
// TODO
}
}
return res;

View File

@@ -19,6 +19,7 @@
#ifndef FENNEC_LANGPROC_IO_PATH_H
#define FENNEC_LANGPROC_IO_PATH_H
#include <fennec/langproc/filesystem/path.h>
#include <fennec/langproc/strings/string.h>
namespace fennec
@@ -32,6 +33,12 @@ namespace fennec
struct path
{
public:
// Definitions =========================================================================================================
class iterator;
friend iterator;
// Static Functions ====================================================================================================
/// \brief Get the current working directory
@@ -154,6 +161,11 @@ public:
return _str == p._str;
}
string filename() const {
size_t i = _str.rfind('/');
return _str.substring(i + 1);
}
const string& str() const { return _str; }
const char* cstr() const { return _str.cstr(); }
@@ -238,6 +250,81 @@ public:
return working;
}
// Iterator ============================================================================================================
iterator begin() const {
return iterator(this, 0);
}
iterator end() const {
return iterator(this, _str.size());
}
class iterator {
public:
constexpr iterator(const path* path, size_t p)
: _str(&path->_str)
, _pos(p) {
// Handle end()
if (p == _str->size()) {
return;
}
// Handle rooted paths
#ifdef FENNEC_PLATFORM_WINDOWS
if ((*_str)[1] == ':') {
_pos = max(_pos, size_t(3));
}
#else
if ((*_str)[0] == '/') {
_pos = max(_pos, size_t(1));
}
#endif
// Ensure we are at the start of a directory/file name
if (_pos != 0 && (*_str)[_pos - 1] != '/') {
_pos = _str->find('/', _pos) + 1;
}
}
constexpr iterator(const iterator&) = default;
constexpr iterator(iterator&&) noexcept = default;
constexpr string operator*() const {
if ((*_str)[_pos] == '/') {
return string("");
}
size_t e = _str->find('/', _pos);
return _str->substring(_pos, e - _pos);
}
constexpr iterator& operator++() {
_pos = min(_str->find('/', _pos) + 1, _str->size());
return *this;
}
constexpr iterator operator++(int) {
iterator it = *this;
this->operator++();
return it;
}
constexpr bool operator==(const iterator& rhs) const {
return _str == rhs._str and _pos == rhs._pos;
}
constexpr bool operator!=(const iterator& rhs) const {
return _str != rhs._str or _pos != rhs._pos;
}
private:
const string* _str;
size_t _pos;
};
private:
string _str;
};

View File

@@ -627,7 +627,7 @@ public:
return _data;
}
private:
protected:
alloc_t _alloc; // Allocator object
value_t* _data; // Handle for the memory block
size_t _capacity; // Capacity of the memory block in elements.

View File

@@ -21,19 +21,27 @@
#include <fennec/containers/dynarray.h>
#include <fennec/containers/map.h>
#include <fennec/containers/object_pool.h>
#include <fennec/containers/optional.h>
#include <fennec/containers/rdtree.h>
#include <fennec/langproc/strings/string.h>
#include <fennec/lang/typed.h>
#include <fennec/langproc/filesystem/path.h>
namespace fennec
{
class component : typed<component> {
struct component_t {
uint64_t type;
size_t id;
};
class component : public typed<component> {
public:
// TYPEDEFS & CONSTANTS ================================================================================================
using component_create = component (*)(size_t);
using component_find = component* (*)(size_t);
using component_create = size_t (*)(size_t, size_t);
using component_get = component* (*)(size_t);
using component_destroy = void (*)(size_t);
using component_tick = void (*)(size_t, double);
using component_frame = void (*)(size_t, uint64_t);
@@ -41,44 +49,146 @@ public:
// TYPE OPERATIONS =====================================================================================================
struct type_info {
struct typeinfo {
string name;
component_create create;
component_find find;
component_destroy destroy;
component_get get;
component_tick tick;
component_frame frame;
typeinfo()
: name("")
, create(nullptr), destroy(nullptr)
, get(nullptr)
, tick(nullptr), frame(nullptr) {
}
typeinfo(const string& name,
component_create create, component_destroy destroy,
component_get get,
component_tick tick, component_frame frame)
: name(name)
, create(create), destroy(destroy), get(get)
, tick(tick), frame(frame) {
}
};
struct typeentry {
string name;
uint64_t id;
};
using typelist_t = dynarray<optional<typeinfo>>;
using typetree_t = rdtree<typeentry>;
// Private Registry ====================================================================================================
private:
// Private Registry
static auto& _type_list() {
static dynarray<optional<type_info>> type_list;
return type_list;
}
static typelist_t _typelist; // Actual list of types
static typetree_t _typetree; // Tree for displaying types under subfolders
static void _register_type( uint64_t id, const cstring& name,
component_create create, component_find find, component_destroy destroy,
component_tick tick, component_frame frame) {
auto& type_list = _type_list();
if (id > type_list.size()) {
type_list.resize(id + 1);
static constexpr size_t npos = typetree_t::npos;
static constexpr size_t root = typetree_t::root;
static void _register_type( uint64_t id, const path& path,
component_create create, component_destroy destroy,
component_get get,
component_tick tick, component_frame frame) {
// Register the type
if (id > _typelist.size()) {
_typelist.resize(id + 1);
}
_typelist[id] = typeinfo{ path.filename(), create, destroy, get, tick, frame };
// Create tree entry
size_t node = root;
path::iterator it = path.begin();
while (it != path.end()) {
string name = *it++;
size_t parent = node;
bool end = it == path.end();
node = _typetree.child(parent);
while (node != npos && _typetree[node].id != id) {
node = _typetree.next(node);
}
if (node == npos) {
node = _typetree.emplace(
parent, npos,
name, end ? id : nullid
);
}
}
type_list[id] = type_info{ name, create, find, destroy, tick, frame };
}
static void _unregister_type(uint64_t id) {
_type_list()[id] = nullopt;
struct noderef {
size_t scene, node;
};
using compstorage_t = object_pool<component*>; // Holds refs to default allocated components
using defaultstorage_t = map<uint64_t, compstorage_t>; // Maps types to respective storage
inline static defaultstorage_t _default_storage;
static auto& _type_info(uint64_t type) {
return _typelist[type];
}
static auto& _default_storage() {
static map<uint64_t, map<size_t, component*>> data;
return data;
template<typename ComponentT>
static auto& _type_info() {
return _typelist[uuid<ComponentT>()];
}
static auto& _type_storage(uint64_t type) {
return *_default_storage[type];
}
template<typename ComponentT>
static auto& _type_storage() {
return *_default_storage[uuid<ComponentT>()];
}
///
/// \brief Default creation function for a component
/// \tparam ComponentT Type of the component
/// \param node The node to associate the component with
/// \returns The component created by this function
template<typename ComponentT>
static constexpr component* default_create(size_t scene, size_t node) {
auto& storage = _type_storage<ComponentT>();
return storage.insert(new ComponentT(scene, node));
}
///
/// \brief Default destruction function for a component
/// \tparam ComponentT Type of the component
/// \param node The node the component is associated with
template<typename ComponentT>
static constexpr void default_destroy(size_t comp) {
auto& storage = _type_storage<ComponentT>();
delete storage[comp];
storage.erase(comp);
}
///
/// \brief Default retrieval function for a component
/// \tparam ComponentT Type of the component
/// \param node The node the component is associated with
/// \returns The component found by this function
template<typename ComponentT>
static constexpr component* default_get(size_t comp) {
auto& storage = _type_storage<ComponentT>();
return storage[comp];
}
// Public Registry =====================================================================================================
public:
// Public Registry
///
/// \brief Get an uuid for the specified component type
@@ -89,66 +199,30 @@ public:
return typeuuid<ComponentT, component>();
}
///
/// \brief Default storage function for default creation, retrieval, and destruction
template<typename ComponentT>
static constexpr auto& default_storage() {
auto& data = _default_storage();
return data[uuid<ComponentT>()];
}
///
/// \brief Default creation function for a component
/// \tparam ComponentT Type of the component
/// \param node The node to associate the component with
/// \returns The component created by this function
template<typename ComponentT>
static constexpr component* default_create(size_t node) {
auto& storage = default_storage<ComponentT>();
if (not storage[node]) {
storage.emplace(node);
}
return storage[node];
}
///
/// \brief Default retrieval function for a component
/// \tparam ComponentT Type of the component
/// \param node The node the component is associated with
/// \returns The component found by this function
template<typename ComponentT>
static constexpr component* default_find(size_t node) {
auto& storage = default_storage<ComponentT>();
return storage[node];
}
///
/// \brief Default destruction function for a component
/// \tparam ComponentT Type of the component
/// \param node The node the component is associated with
template<typename ComponentT>
static constexpr void default_destroy(size_t node) {
default_storage<ComponentT>().erase(node);
}
///
/// \brief Register a type with the component system
/// \tparam ComponentT The component type
/// \param name The name of the type
/// \param create The function used to create a component, given a scene node. **MUST NOT BE NULL**
/// \param find The function used to find a component, given a scene node. **MUST NOT BE NULL**
/// \param get The function used to find a component, given a scene node. **MUST NOT BE NULL**
/// \param destroy The function used to destroy a component of a scene node. **MUST NOT BE NULL**
template<typename ComponentT>
static void register_type(const cstring& name, component_tick tick, component_frame frame,
static void register_type(const cstring& name,
component_tick tick, component_frame frame,
component_create create = default_create<ComponentT>,
component_find find = default_find<ComponentT>,
component_get get = default_get<ComponentT>,
component_destroy destroy = default_destroy<ComponentT>) {
component::_register_type(uuid<ComponentT>(), name, create, find, destroy, tick, frame);
component::_register_type(uuid<ComponentT>(), name, create, destroy, get, tick, frame);
}
template<typename ComponentT>
static void unregister_type() {
component::_unregister_type(uuid<ComponentT>());
///
/// \brief Create a component of type `type`
/// \param type The type to create
/// \param node The node to associate the component with
/// \returns The created component
static size_t create(uint64_t type, size_t scene, size_t node) {
auto& typei = _typelist[type];
return typei->create(scene, node);
}
///
@@ -157,42 +231,60 @@ public:
/// \param node The node to associate the component with
/// \returns The created component
template<typename ComponentT>
static component* create(size_t node) {
auto& type = _type_list()[uuid<ComponentT>()];
return type ? *type->create(node) : nullptr;
static size_t create(size_t scene, size_t node) {
auto& typei = _typelist[uuid<ComponentT>()];
return typei->create(scene, node);
}
///
/// \brief Get a component of type `type` from id `id`
/// \param type The type of component
/// \param id The id of the component instance
/// \return
static component* get(uint64_t type, size_t id) {
auto& typei = _typelist[type];
return typei->get(id);
}
template<typename ComponentT>
static component* find(size_t node) {
auto& type = _type_list()[uuid<ComponentT>()];
return type ? *type->find(node) : nullptr;
static component* get(size_t comp) {
auto& typei = _typelist[uuid<ComponentT>()];
return typei->get(comp);
}
static void destroy(size_t type, size_t comp) {
auto& typei = _typelist[type];
return typei->destroy(comp);
}
template<typename ComponentT>
static void destroy(size_t node) {
auto& type = _type_list()[uuid<ComponentT>()];
return type ? *type->destroy(node) : nullptr;
static void destroy(size_t comp) {
auto& typei = _typelist[uuid<ComponentT>()];
return typei->destroy(comp);
}
static void tick(size_t node, double dT) {
for (auto& it : _type_list()) {
it->tick(node, dT);
static void tick(size_t comp, double dT) {
for (auto& it : _typelist) {
if (it->tick) {
it->tick(comp, dT);
}
}
}
static void frame(size_t node, uint64_t f) {
for (auto& it : _type_list()) {
it->tick(node, f);
static void frame(size_t comp, uint64_t f) {
for (auto& it : _typelist) {
if (it->frame) {
it->frame(comp, f);
}
}
}
static const auto& type_list() {
return _type_list();
return _typelist;
}
// MEMBERS =============================================================================================================
public:
const type_info* type;
const size_t node;
template<typename ComponentT>

View File

@@ -33,12 +33,12 @@
#include <fennec/math/matrix.h>
#include <fennec/math/ext/transform.h>
#include <fennec/scene/component.h>
#include <fennec/scene/scene_node.h>
namespace fennec
{
struct transform2d : component {
struct node2d : scene_node {
// Definitions =========================================================================================================
enum mobility_ : bool {
@@ -52,34 +52,19 @@ public:
///
/// \brief Default Constructor, initializes an identity matrix.
transform2d(size_t node)
: component(this, node)
node2d(size_t id, size_t scene, const string& name)
: scene_node(name, id, scene)
, _mobility(mobility_free)
, _position(0, 0)
, _scale(1, 1)
, _shear(0, 0)
, _rotation(0) {
}
///
/// \brief Component Constructor, composes the internal matrix using the components.
/// \param pos Position as vec2
/// \param scl Scale as vec2
/// \param rot Rotation as degrees
/// \param skw Skew as vec2
transform2d(size_t node, const vec2& pos, const vec2& scl, float rot, const vec2& skw = vec2(0, 0))
: component(this, node)
, _position(pos)
, _scale(scl)
, _shear(skw)
, _rotation(rot)
, _dirty(true)
, _local(matrix()) {
}
node2d(const node2d&) = default;
node2d(node2d&&) noexcept = default;
transform2d(const transform2d&) = default;
transform2d(transform2d&&) noexcept = default;
~transform2d() = default;
~node2d() = default;
// Access ==============================================================================================================
@@ -99,53 +84,79 @@ public:
return _shear;
}
constexpr bool mobility() const {
return _mobility;
}
// Modifiers ===========================================================================================================
constexpr void translate(const vec2& x) {
_position += x;
if (_mobility) {
_position += x;
}
}
constexpr void scale(const vec2& s) {
_scale *= s;
if (_mobility) {
_scale *= s;
}
}
constexpr void rotate(float r) {
_rotation += r;
if (_mobility) {
_rotation += r;
}
}
constexpr void shear(const vec2& s) {
_shear += s;
if (_mobility) {
_shear += s;
}
}
constexpr void commit() {
if (not _dirty) {
if (not _mobility) {
return;
}
_local = fennec::rotation(_rotation);
_local *= fennec::shear(_shear);
_local *= fennec::scaling(_scale);
_local *= fennec::translation(_position);
// Get parent
_recalculate();
// Propagate down
}
constexpr const mat3& local() {
commit();
return _local;
}
constexpr const mat3& global() {
return _global;
}
// Fields ==============================================================================================================
private:
bool _mobility;
vec2 _position;
vec2 _scale;
vec2 _shear;
float _rotation;
bool _dirty;
mat3 _local;
mat3 _local, _global;
// Helpers =============================================================================================================
constexpr void _recalculate(const mat3& parent) {
_local = fennec::rotation(_rotation);
_local *= fennec::shear(_shear);
_local *= fennec::scaling(_scale);
_local *= fennec::translation(_position);
_global = parent * _local;
}
};
}

View File

@@ -20,7 +20,10 @@
#define FENNEC_CORE_SCENE_H
#include <fennec/containers/rdtree.h>
#include <fennec/lang/typed.h>
#include <fennec/langproc/strings/string.h>
#include <fennec/scene/scene_node.h>
namespace fennec
{
@@ -30,9 +33,10 @@ namespace fennec
/// \details This structure only contains the names of nodes and defers storage for components to
/// their respective systems. Simple components may be isolated, \see components.h for more info.
/// The hierarchy should be displayed using pre-order traversal.
class scene : public rdtree<string> {
public:
class scene : public rdtree<scene_node*> {
// Access ==============================================================================================================
public:
///
/// \brief Find a node by name.
/// \details If multiple nodes have the same name, finds the first one in pre-order traversal.
@@ -42,13 +46,14 @@ public:
list<size_t> parse;
parse.push_back(root);
while (not parse.empty()) {
if (*_data[parse.front()].value == name) {
return parse.front();
size_t n = parse.front();
if (_table[n].value->name == name) {
return n;
}
// Pre-Order traversal
parse.push_front(next(parse.front()));
parse.push_front(child(parse.front()));
parse.push_front(next(n));
parse.push_front(child(n));
parse.pop_front();
}
return npos;
@@ -63,13 +68,14 @@ public:
list<size_t> parse;
parse.push_back(root);
while (not parse.empty()) {
if (*_data[parse.front()].value == name) {
return parse.front();
size_t n = parse.front();
if (_table[n].value->name == name) {
return n;
}
// Pre-Order traversal
parse.push_front(next(parse.front()));
parse.push_front(child(parse.front()));
parse.push_front(next(n));
parse.push_front(child(n));
parse.pop_front();
}
return npos;

View File

@@ -0,0 +1,56 @@
// =====================================================================================================================
// fennec, a free and open source game engine
// Copyright © 2025 Medusa Slockbower
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================
///
/// \file node.h
/// \brief
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///
#ifndef FENNEC_SCENE_NODE_H
#define FENNEC_SCENE_NODE_H
#include <fennec/lang/typed.h>
#include <fennec/langproc/strings/string.h>
#include <fennec/scene/component.h>
namespace fennec
{
struct scene_node : typed<scene_node> {
const size_t scene;
const size_t id;
string name;
scene_node(size_t id, size_t scene, const string& name)
: id(id), scene(scene), name(name) {
}
private:
dynarray<component_t> _components;
};
}
#endif // FENNEC_SCENE_NODE_H