Skip to content

Instantly share code, notes, and snippets.

@unrays
Created May 22, 2026 17:29
Show Gist options
  • Select an option

  • Save unrays/158d58e795bf9d0606ec344fd57fcdb8 to your computer and use it in GitHub Desktop.

Select an option

Save unrays/158d58e795bf9d0606ec344fd57fcdb8 to your computer and use it in GitHub Desktop.
A C++ data-oriented registry with compile-time type mapping and cache-efficient handle-based storage.
// Copyright (c) May 2026 Félix-Olivier Dumas. All rights reserved.
// Licensed under the terms described in the LICENSE file
#pragma once
#include <cstddef>
#include <tuple>
#include <utility>
#include <type_traits>
#include <iostream>
/***************************************************************************/
/* SENTINEL */
/***************************************************************************/
struct SENTINEL {};
/***************************************************************************/
/* LINEAR TABLE */
/***************************************************************************/
template<typename... Types>
struct LinearTable : Types... {};
template<typename Key, typename Value>
struct Entry {};
/***************************************************************************/
/* ENTRY TRAITS */
/***************************************************************************/
template<typename...>
struct entry_traits;
template<typename K, typename V>
struct entry_traits<Entry<K, V>>
{
using key = K;
using value = V;
};
/***************************************************************************/
/* LOOKUP */
/***************************************************************************/
template<typename...>
struct lookup;
template<typename Key, typename Target, typename... Rest>
struct lookup<Key, LinearTable<Entry<Key, Target>, Rest...>>
{
using type = Target;
};
template<typename Key, typename Head, typename... Rest>
struct lookup<Key, LinearTable<Head, Rest...>>
{
using type = typename lookup<Key, Rest...>::type;
};
template<typename Key, typename Empty>
struct lookup<Key, Empty>
{
using type = SENTINEL;
};
template<typename Key, typename Table>
using table_lookup_t = typename lookup<Key, Table>::type;
/***************************************************************************/
/* SIZE OF TABLE */
/***************************************************************************/
template<typename>
struct size_of;
template<typename... Types>
struct size_of<LinearTable<Types...>>
{
static constexpr std::size_t value = sizeof...(Types);
};
template<typename Table>
static constexpr std::size_t size_of_v = size_of<Table>::value;
/***************************************************************************/
/* HANDLE PROVIDERS */
/***************************************************************************/
struct DefaultHandleProvider
{
template<typename Tp>
[[nodiscard]] constexpr std::size_t operator()(const Tp* obj)
{
return obj->id;
}
};
/***************************************************************************/
/* REGISTRY STORAGE BUILDER */
/***************************************************************************/
template<typename Table, template<typename> typename StorageType>
struct make_registry_storage;
template<typename... Entries, template<typename...> typename StorageType>
struct make_registry_storage<LinearTable<Entries...>, StorageType>
{
using type = std::tuple<
StorageType<typename entry_traits<Entries>::value>...
>;
};
template<typename Table, template<typename...> typename StorageType>
using make_registry_storage_t =
typename make_registry_storage<Table, StorageType>::type;
/***************************************************************************/
/* SMART STORAGE */
/***************************************************************************/
template<typename Tp, std::size_t N>
class SmartStorage final
{
protected:
static constexpr std::uint16_t MaxStack = 1 << 14;
using OnlyIfStackEligible =
std::conditional_t<
(N * sizeof(Tp) <= MaxStack),
std::byte[N * sizeof(Tp)],
std::monostate
>;
public:
SmartStorage()
: is_constructed_{}
{
if constexpr (is_heap_eligible_) {
buffer_ptr_ = static_cast<std::byte*>(
::operator new(N * sizeof(Tp), std::align_val_t(alignof(Tp)))
);
}
else {
buffer_ptr_ = &stack_buffer_[0];
}
}
~SmartStorage() noexcept
{
reset();
if constexpr (is_heap_eligible_) {
::operator delete(buffer_ptr_, std::align_val_t(alignof(Tp)));
}
}
protected:
void throw_if_out_of_range(std::size_t index) const
{
if (index >= N) [[unlikely]] {
throw std::out_of_range(
"[EXOTIC::SmartStorage] index out of range: "
+ std::to_string(index)
+ " (valid range: 0.."
+ std::to_string(N - 1)
+ ")"
);
}
}
void throw_if_existing(std::size_t index) const
{
if (is_constructed_[index]) [[unlikely]] {
throw std::runtime_error(
"[EXOTIC::SmartStorage] construction conflict: slot already occupied at index "
+ std::to_string(index)
);
}
}
void throw_if_nonexistent(std::size_t index) const
{
if (!is_constructed_[index]) [[unlikely]] {
throw std::runtime_error(
"[EXOTIC::SmartStorage] access violation: no object constructed at index "
+ std::to_string(index)
);
}
}
public:
[[nodiscard]] Tp& get(std::size_t index)
{
throw_if_out_of_range(index);
throw_if_nonexistent(index);
return *reinterpret_cast<Tp*>(buffer_ptr_ + index * sizeof(Tp));
}
[[nodiscard]] const Tp& get(std::size_t index) const
{
throw_if_out_of_range(index);
throw_if_nonexistent(index);
return *reinterpret_cast<const Tp*>(buffer_ptr_ + index * sizeof(Tp));
}
public:
template<typename... Types>
Tp& emplace(std::size_t index, Types&&... args)
noexcept(std::is_nothrow_constructible_v<std::decay<Tp>, Types&&...>)
{
throw_if_out_of_range(index);
throw_if_existing(index);
Tp* ptr = reinterpret_cast<Tp*>(buffer_ptr_ + index * sizeof(Tp));
new (ptr) Tp(std::forward<Types>(args)...);
is_constructed_[index] = true;
return *ptr;
}
public:
Tp& insert(std::size_t index, Tp&& obj)
{
throw_if_out_of_range(index);
Tp* ptr = reinterpret_cast<Tp*>(buffer_ptr_ + index * sizeof(Tp));
if (is_constructed_[index]) {
*ptr = obj;
}
else {
new (ptr) Tp(obj);
is_constructed_[index] = true;
}
return *ptr;
}
Tp& insert(std::size_t index, Tp& obj)
{
throw_if_out_of_range(index);
Tp* ptr = reinterpret_cast<Tp*>(buffer_ptr_ + index * sizeof(Tp));
if (is_constructed_[index]) {
*ptr = obj;
}
else {
new (ptr) Tp(obj);
is_constructed_[index] = true;
}
return *ptr;
}
public:
void destroy(std::size_t index) noexcept
{
throw_if_out_of_range(index);
throw_if_nonexistent(index);
Tp* ptr = reinterpret_cast<Tp*>(buffer_ptr_ + index * sizeof(Tp));
ptr->~Tp();
is_constructed_[index] = false;
}
void reset() noexcept
{
for (std::size_t i = 0; i < N; ++i) {
if (is_constructed_[i]) {
Tp* ptr = reinterpret_cast<Tp*>(buffer_ptr_ + i * sizeof(Tp));
ptr->~Tp();
is_constructed_[i] = false;
}
}
std::memset(buffer_ptr_, 0, N * sizeof(Tp));
}
public:
[[nodiscard]] constexpr std::size_t capacity() const noexcept
{
return N;
}
private:
alignas(Tp) OnlyIfStackEligible stack_buffer_;
std::byte* buffer_ptr_;
static constexpr bool is_heap_eligible_ =
std::is_same_v<OnlyIfStackEligible, std::monostate>;
std::array<bool, N> is_constructed_;
};
/***************************************************************************/
/* MULTI STORAGE REGISTRY */
/***************************************************************************/
template<typename Table, typename HandleProvider, std::size_t N = 1 << 14>
class MultiStorageRegistry final
{
protected:
static constexpr std::size_t BytesPerStorage = N / size_of_v<Table>;
template<typename Up>
using RegistryStorageStrategy =
SmartStorage<Up, BytesPerStorage / sizeof(Up)>;
public:
explicit MultiStorageRegistry(HandleProvider provider = {})
: handleProvider_(provider), storage_{}
{
std::cout << "[NODE REGISTRY CTOR] this = " << this << "\n";
}
~MultiStorageRegistry() noexcept
{
reset();
}
public:
template<typename Tp>
auto& resolve_storage() noexcept
{
using Result = std::decay_t<
table_lookup_t<std::decay_t<Tp>, Table>
>;
static_assert(
!std::is_same_v<Result, PRYSMA_SENTINEL>,
"Unable to resolve the requested type from the LinearTable."
);
return std::get<RegistryStorageStrategy<Result>>(storage_);
}
public:
template<typename Tp>
[[nodiscard]] const auto& get(const Tp* obj) const noexcept
{
const auto& storage = resolve_storage<Tp>();
return storage.get(handleProvider_(obj));
}
template<typename Up, typename Tp>
[[nodiscard]] const auto& get_for(const Tp* obj) const noexcept
{
const auto& storage = std::get<RegistryStorageStrategy<Up>>(storage_);
return storage.get(handleProvider_(obj));
}
public:
template<typename Tp>
[[nodiscard]] auto& get(const Tp* obj) noexcept
{
auto& storage = resolve_storage<Tp>();
return storage.get(handleProvider_(obj));
}
template<typename Up, typename Tp>
[[nodiscard]] auto& get_for(const Tp* obj) noexcept
{
auto& storage = std::get<RegistryStorageStrategy<Up>>(storage_);
return storage.get(handleProvider_(obj));
}
public:
template<typename Tp, typename... Types>
auto& construct(const Tp* obj, Types&&... args)
{
auto& storage = resolve_storage<Tp>();
return storage.emplace(handleProvider_(obj), std::forward<Types>(args)...);
}
template<typename Up, typename Tp, typename... Types>
auto& construct_for(const Tp* obj, Types&&... args)
{
auto& storage = std::get<RegistryStorageStrategy<Up>>(storage_);
return storage.emplace(handleProvider_(obj), std::forward<Types>(args)...);
}
public:
template<typename Tp, typename Up>
auto& assign(const Tp* obj, Up&& arg)
{
auto& storage = resolve_storage<Tp>();
return storage.insert(handleProvider_(obj), std::forward<Up>(arg));
}
template<typename Up, typename Tp>
auto& assign_for(const Tp* obj, Up&& arg)
{
auto& storage = std::get<RegistryStorageStrategy<Up>>(storage_);
return storage.insert(handleProvider_(obj), std::forward<Up>(arg));
}
public:
void reset() noexcept
{
std::apply([](auto&... storage) {
(storage.reset(), ...);
}, storage_);
}
template<typename Up>
void reset_for() noexcept
{
auto& storage = std::get<RegistryStorageStrategy<Up>>(storage_);
storage.reset();
}
private:
make_registry_storage_t<Table, RegistryStorageStrategy> storage_;
HandleProvider handleProvider_;
};
using ExampleRegistry = MultiStorageRegistry<ExampleRegistryTable, DefaultHandleProvider, 1 << 10>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment