feat(host): vendor PyroWave + minimal Granite subset as crates/pyrowave-sys

Phase 0 of design/pyrowave-codec-plan.md — the opt-in wired-LAN ultra-low-
latency codec. Vendored at upstream 509e4f88 (API 0.4.0, Granite 44362775,
volk + vulkan-headers pins in PUNKTFUNK-VENDOR.txt), pruned to the 6.6 MB
the standalone no-renderer build needs; scripts/vendor-pyrowave.sh
reproduces the tree (a pin bump is protocol-affecting, plan §4.2).

build.rs drives the wrapper CMakeLists (static archives incl. a static
C-API lib upstream only ships shared) + bindgen over pyrowave.h; Linux and
Windows only, empty stub elsewhere (Apple gets a native Metal port, §4.7).
Offline-safe by construction: no network, no system lib, vendored Vulkan
headers — same model as the opus dep (flatpak builder has no network).

Phase-0 validation on .21 (RTX 5070 Ti, driver 610.43.03):
- upstream pyrowave-c-test + interop test (incl. dmabuf/DRM-modifier
  Vulkan<->Vulkan) pass, from the pristine AND the pruned tree
- GPU kernel times at ~1.6 bpp noise: encode/decode 0.090/0.042 ms @800p,
  0.146/0.067 @1080p, 0.226/0.103 @1440p, 0.477/0.201 @4K — order of
  magnitude under NVENC's 1-2 ms retrieve, CBR lands within ~100 B of
  target
- cargo test -p pyrowave-sys green (static link + API-version pin check)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 00:35:10 +02:00
parent 1b73361372
commit 4c3b11445c
396 changed files with 140058 additions and 0 deletions
@@ -0,0 +1,52 @@
add_granite_internal_lib(granite-util
logging.hpp logging.cpp
aligned_alloc.cpp aligned_alloc.hpp
bitops.hpp
array_view.hpp
variant.hpp
enum_cast.hpp
hash.hpp
intrusive.hpp
intrusive_list.hpp
object_pool.hpp
stack_allocator.hpp
temporary_hashmap.hpp
read_write_lock.hpp
async_object_sink.hpp
unstable_remove_if.hpp
intrusive_hash_map.hpp
timer.hpp timer.cpp
small_vector.hpp
thread_id.hpp thread_id.cpp
string_helpers.hpp string_helpers.cpp
timeline_trace_file.hpp timeline_trace_file.cpp
thread_name.hpp thread_name.cpp
thread_priority.hpp thread_priority.cpp
cli_parser.cpp cli_parser.hpp
dynamic_library.cpp dynamic_library.hpp
generational_handle.hpp
atomic_append_buffer.hpp
lru_cache.hpp
unordered_array.hpp
message_queue.hpp message_queue.cpp
small_callable.hpp radix_sorter.hpp
dynamic_array.hpp
arena_allocator.hpp arena_allocator.cpp
environment.hpp environment.cpp
slab_allocator.hpp slab_allocator.cpp
no_init_pod.hpp)
target_include_directories(granite-util PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(granite-util PUBLIC granite-application-global-interface)
if (NOT WIN32)
target_link_libraries(granite-util PUBLIC dl)
find_library(LIBRT_LIBRARY rt)
if (EXISTS ${LIBRT_LIBRARY})
target_link_libraries(granite-util PUBLIC rt)
endif()
endif()
if (GRANITE_SHIPPING)
target_compile_definitions(granite-util PUBLIC GRANITE_SHIPPING)
endif()
@@ -0,0 +1,83 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "aligned_alloc.hpp"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <malloc.h>
#endif
namespace Util
{
void *memalign_alloc(size_t boundary, size_t size)
{
#if defined(_WIN32)
return _aligned_malloc(size, boundary);
#elif defined(_ISOC11_SOURCE)
return aligned_alloc(boundary, (size + boundary - 1) & ~(boundary - 1));
#elif (_POSIX_C_SOURCE >= 200112L) || (_XOPEN_SOURCE >= 600)
void *ptr = nullptr;
if (posix_memalign(&ptr, boundary, size) < 0)
return nullptr;
return ptr;
#else
// Align stuff ourselves. Kinda ugly, but will work anywhere.
void **place;
uintptr_t addr = 0;
void *ptr = malloc(boundary + size + sizeof(uintptr_t));
if (ptr == nullptr)
return nullptr;
addr = ((uintptr_t)ptr + sizeof(uintptr_t) + boundary) & ~(boundary - 1);
place = (void **) addr;
place[-1] = ptr;
return (void *) addr;
#endif
}
void *memalign_calloc(size_t boundary, size_t size)
{
void *ret = memalign_alloc(boundary, size);
if (ret)
memset(ret, 0, size);
return ret;
}
void memalign_free(void *ptr)
{
#if defined(_WIN32)
_aligned_free(ptr);
#elif !defined(_ISOC11_SOURCE) && !((_POSIX_C_SOURCE >= 200112L) || (_XOPEN_SOURCE >= 600))
if (ptr != nullptr)
{
void **p = (void **) ptr;
free(p[-1]);
}
#else
free(ptr);
#endif
}
}
@@ -0,0 +1,68 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stddef.h>
#include <stdexcept>
#include <new>
namespace Util
{
void *memalign_alloc(size_t boundary, size_t size);
void *memalign_calloc(size_t boundary, size_t size);
void memalign_free(void *ptr);
struct AlignedDeleter { void operator()(void *ptr) { memalign_free(ptr); }};
template <typename T>
struct AlignedAllocation
{
static void *operator new(size_t size)
{
void *ret = ::Util::memalign_alloc(alignof(T), size);
#ifdef __EXCEPTIONS
if (!ret) throw std::bad_alloc();
#endif
return ret;
}
static void *operator new[](size_t size)
{
void *ret = ::Util::memalign_alloc(alignof(T), size);
#ifdef __EXCEPTIONS
if (!ret) throw std::bad_alloc();
#endif
return ret;
}
static void operator delete(void *ptr)
{
return ::Util::memalign_free(ptr);
}
static void operator delete[](void *ptr)
{
return ::Util::memalign_free(ptr);
}
};
}
@@ -0,0 +1,197 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "arena_allocator.hpp"
#include "bitops.hpp"
#include <assert.h>
namespace Util
{
void LegionAllocator::allocate(uint32_t num_blocks, uint32_t &out_mask, uint32_t &out_offset)
{
assert(NumSubBlocks >= num_blocks);
assert(num_blocks != 0);
uint32_t block_mask;
if (num_blocks == NumSubBlocks)
block_mask = ~0u;
else
block_mask = ((1u << num_blocks) - 1u);
uint32_t mask = free_blocks[num_blocks - 1];
uint32_t b = trailing_zeroes(mask);
assert(((free_blocks[0] >> b) & block_mask) == block_mask);
uint32_t sb = block_mask << b;
free_blocks[0] &= ~sb;
update_longest_run();
out_mask = sb;
out_offset = b;
}
void LegionAllocator::free(uint32_t mask)
{
assert((free_blocks[0] & mask) == 0);
free_blocks[0] |= mask;
update_longest_run();
}
void LegionAllocator::update_longest_run()
{
uint32_t f = free_blocks[0];
longest_run = 0;
while (f)
{
free_blocks[longest_run++] = f;
f &= f >> 1;
}
}
bool SliceSubAllocator::allocate_backing_heap(AllocatedSlice *allocation)
{
uint32_t count = sub_block_size * Util::LegionAllocator::NumSubBlocks;
if (parent)
{
return parent->allocate(count, allocation);
}
else if (global_allocator)
{
uint32_t index = global_allocator->allocate(count);
if (index == UINT32_MAX)
return false;
*allocation = {};
allocation->count = count;
allocation->buffer_index = index;
return true;
}
else
{
return false;
}
}
void SliceSubAllocator::free_backing_heap(AllocatedSlice *allocation) const
{
if (parent)
parent->free(allocation->heap, allocation->mask);
else if (global_allocator)
global_allocator->free(allocation->buffer_index);
}
void SliceSubAllocator::prepare_allocation(AllocatedSlice *allocation, Util::IntrusiveList<MiniHeap>::Iterator heap,
const Util::SuballocationResult &suballoc)
{
allocation->buffer_index = heap->allocation.buffer_index;
allocation->offset = heap->allocation.offset + suballoc.offset;
allocation->count = suballoc.size;
allocation->mask = suballoc.mask;
allocation->heap = heap;
allocation->alloc = this;
}
void SliceAllocator::init(uint32_t sub_block_size, uint32_t num_sub_blocks_in_arena_log2,
Util::SliceBackingAllocator *alloc)
{
global_allocator = alloc;
assert(num_sub_blocks_in_arena_log2 < SliceAllocatorCount * 5 && num_sub_blocks_in_arena_log2 >= 5);
unsigned num_hierarchies = (num_sub_blocks_in_arena_log2 + 4) / 5;
assert(num_hierarchies <= SliceAllocatorCount);
for (unsigned i = 0; i < num_hierarchies - 1; i++)
allocators[i].parent = &allocators[i + 1];
allocators[num_hierarchies - 1].global_allocator = alloc;
unsigned shamt[SliceAllocatorCount] = {};
shamt[num_hierarchies - 1] = num_sub_blocks_in_arena_log2 - Util::floor_log2(Util::LegionAllocator::NumSubBlocks);
// Spread out the multiplier if possible.
for (unsigned i = num_hierarchies - 1; i > 1; i--)
{
shamt[i - 1] = shamt[i] - shamt[i] / (i);
assert(shamt[i] - shamt[i - 1] <= Util::floor_log2(Util::LegionAllocator::NumSubBlocks));
}
for (unsigned i = 0; i < num_hierarchies; i++)
{
allocators[i].set_sub_block_size(sub_block_size << shamt[i]);
allocators[i].set_object_pool(&object_pool);
}
}
void SliceAllocator::free(const Util::AllocatedSlice &slice)
{
if (slice.alloc)
slice.alloc->free(slice.heap, slice.mask);
else if (slice.buffer_index != UINT32_MAX)
global_allocator->free(slice.buffer_index);
}
void SliceAllocator::prime(const void *opaque_meta)
{
for (auto &alloc : allocators)
{
if (alloc.global_allocator)
{
alloc.global_allocator->prime(alloc.get_sub_block_size() * Util::LegionAllocator::NumSubBlocks, opaque_meta);
break;
}
}
}
bool SliceAllocator::allocate(uint32_t count, Util::AllocatedSlice *slice)
{
for (auto &alloc : allocators)
{
uint32_t max_alloc_size = alloc.get_max_allocation_size();
if (count <= max_alloc_size)
return alloc.allocate(count, slice);
}
LOGE("Allocation of %u elements is too large for SliceAllocator.\n", count);
return false;
}
void SliceBackingAllocatorVA::free(uint32_t)
{
allocated = false;
}
uint32_t SliceBackingAllocatorVA::allocate(uint32_t)
{
if (allocated)
return UINT32_MAX;
else
{
allocated = true;
return 0;
}
}
void SliceBackingAllocatorVA::prime(uint32_t, const void *)
{
}
}
@@ -0,0 +1,336 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <assert.h>
#include "intrusive_list.hpp"
#include "logging.hpp"
#include "object_pool.hpp"
#include "bitops.hpp"
namespace Util
{
// Expands the buddy allocator to consider 32 "buddies".
// The allocator is logical and works in terms of units, not bytes.
class LegionAllocator
{
public:
enum
{
NumSubBlocks = 32u,
AllFree = ~0u
};
LegionAllocator(const LegionAllocator &) = delete;
void operator=(const LegionAllocator &) = delete;
LegionAllocator()
{
for (auto &v : free_blocks)
v = AllFree;
longest_run = 32;
}
~LegionAllocator()
{
if (free_blocks[0] != AllFree)
LOGE("Memory leak in block detected.\n");
}
inline bool full() const
{
return free_blocks[0] == 0;
}
inline bool empty() const
{
return free_blocks[0] == AllFree;
}
inline uint32_t get_longest_run() const
{
return longest_run;
}
void allocate(uint32_t num_blocks, uint32_t &mask, uint32_t &offset);
void free(uint32_t mask);
private:
uint32_t free_blocks[NumSubBlocks];
uint32_t longest_run = 0;
void update_longest_run();
};
// Represents that a legion heap is backed by some kind of allocation.
template <typename BackingAllocation>
struct LegionHeap : Util::IntrusiveListEnabled<LegionHeap<BackingAllocation>>
{
BackingAllocation allocation;
Util::LegionAllocator heap;
};
template <typename BackingAllocation>
struct AllocationArena
{
Util::IntrusiveList<LegionHeap<BackingAllocation>> heaps[Util::LegionAllocator::NumSubBlocks];
Util::IntrusiveList<LegionHeap<BackingAllocation>> full_heaps;
uint32_t heap_availability_mask = 0;
};
struct SuballocationResult
{
uint32_t offset;
uint32_t size;
uint32_t mask;
};
template <typename DerivedAllocator, typename BackingAllocation>
class ArenaAllocator
{
public:
using MiniHeap = LegionHeap<BackingAllocation>;
~ArenaAllocator()
{
bool error = false;
if (heap_arena.full_heaps.begin())
error = true;
for (auto &h : heap_arena.heaps)
if (h.begin())
error = true;
if (error)
LOGE("Memory leaked in class allocator!\n");
}
inline void set_sub_block_size(uint32_t size)
{
assert(Util::is_pow2(size));
sub_block_size_log2 = Util::floor_log2(size);
sub_block_size = size;
}
inline uint32_t get_max_allocation_size() const
{
return sub_block_size * Util::LegionAllocator::NumSubBlocks;
}
inline uint32_t get_sub_block_size() const
{
return sub_block_size;
}
inline uint32_t get_block_alignment() const
{
return get_sub_block_size();
}
inline bool allocate(uint32_t size, BackingAllocation *alloc)
{
unsigned num_blocks = (size + sub_block_size - 1) >> sub_block_size_log2;
uint32_t size_mask = (1u << (num_blocks - 1)) - 1;
uint32_t index = trailing_zeroes(heap_arena.heap_availability_mask & ~size_mask);
if (index < LegionAllocator::NumSubBlocks)
{
auto itr = heap_arena.heaps[index].begin();
assert(itr);
assert(index >= (num_blocks - 1));
auto &heap = *itr;
static_cast<DerivedAllocator *>(this)->prepare_allocation(alloc, itr, suballocate(num_blocks, heap));
unsigned new_index = heap.heap.get_longest_run() - 1;
if (heap.heap.full())
{
heap_arena.full_heaps.move_to_front(heap_arena.heaps[index], itr);
if (!heap_arena.heaps[index].begin())
heap_arena.heap_availability_mask &= ~(1u << index);
}
else if (new_index != index)
{
auto &new_heap = heap_arena.heaps[new_index];
new_heap.move_to_front(heap_arena.heaps[index], itr);
heap_arena.heap_availability_mask |= 1u << new_index;
if (!heap_arena.heaps[index].begin())
heap_arena.heap_availability_mask &= ~(1u << index);
}
return true;
}
// We didn't find a vacant heap, make a new one.
auto *node = object_pool->allocate();
if (!node)
return false;
auto &heap = *node;
if (!static_cast<DerivedAllocator *>(this)->allocate_backing_heap(&heap.allocation))
{
object_pool->free(node);
return false;
}
// This cannot fail.
static_cast<DerivedAllocator *>(this)->prepare_allocation(alloc, node, suballocate(num_blocks, heap));
if (heap.heap.full())
{
heap_arena.full_heaps.insert_front(node);
}
else
{
unsigned new_index = heap.heap.get_longest_run() - 1;
heap_arena.heaps[new_index].insert_front(node);
heap_arena.heap_availability_mask |= 1u << new_index;
}
return true;
}
inline void free(typename IntrusiveList<MiniHeap>::Iterator itr, uint32_t mask)
{
auto *heap = itr.get();
auto &block = heap->heap;
bool was_full = block.full();
unsigned index = block.get_longest_run() - 1;
block.free(mask);
unsigned new_index = block.get_longest_run() - 1;
if (block.empty())
{
static_cast<DerivedAllocator *>(this)->free_backing_heap(&heap->allocation);
if (was_full)
heap_arena.full_heaps.erase(heap);
else
{
heap_arena.heaps[index].erase(heap);
if (!heap_arena.heaps[index].begin())
heap_arena.heap_availability_mask &= ~(1u << index);
}
object_pool->free(heap);
}
else if (was_full)
{
heap_arena.heaps[new_index].move_to_front(heap_arena.full_heaps, heap);
heap_arena.heap_availability_mask |= 1u << new_index;
}
else if (index != new_index)
{
heap_arena.heaps[new_index].move_to_front(heap_arena.heaps[index], heap);
heap_arena.heap_availability_mask |= 1u << new_index;
if (!heap_arena.heaps[index].begin())
heap_arena.heap_availability_mask &= ~(1u << index);
}
}
inline void set_object_pool(ObjectPool<MiniHeap> *object_pool_)
{
object_pool = object_pool_;
}
protected:
AllocationArena<BackingAllocation> heap_arena;
ObjectPool<LegionHeap<BackingAllocation>> *object_pool = nullptr;
uint32_t sub_block_size = 1;
uint32_t sub_block_size_log2 = 0;
private:
inline SuballocationResult suballocate(uint32_t num_blocks, MiniHeap &heap)
{
SuballocationResult res = {};
res.size = num_blocks << sub_block_size_log2;
heap.heap.allocate(num_blocks, res.mask, res.offset);
res.offset <<= sub_block_size_log2;
return res;
}
};
struct SliceSubAllocator;
struct AllocatedSlice
{
uint32_t buffer_index = UINT32_MAX;
uint32_t offset = 0;
uint32_t count = 0;
uint32_t mask = 0;
SliceSubAllocator *alloc = nullptr;
Util::IntrusiveList<Util::LegionHeap<AllocatedSlice>>::Iterator heap = {};
};
struct SliceBackingAllocator
{
virtual ~SliceBackingAllocator() = default;
virtual uint32_t allocate(uint32_t count) = 0;
virtual void free(uint32_t index) = 0;
virtual void prime(uint32_t count, const void *opaque_meta) = 0;
};
struct SliceBackingAllocatorVA : SliceBackingAllocator
{
uint32_t allocate(uint32_t count) override;
void free(uint32_t index) override;
void prime(uint32_t count, const void *opaque_meta) override;
bool allocated = false;
};
struct SliceSubAllocator : Util::ArenaAllocator<SliceSubAllocator, AllocatedSlice>
{
SliceSubAllocator *parent = nullptr;
SliceBackingAllocator *global_allocator = nullptr;
// Implements curious recurring template pattern calls.
bool allocate_backing_heap(AllocatedSlice *allocation);
void free_backing_heap(AllocatedSlice *allocation) const;
void prepare_allocation(AllocatedSlice *allocation, Util::IntrusiveList<MiniHeap>::Iterator heap,
const Util::SuballocationResult &suballoc);
};
class SliceAllocator
{
public:
bool allocate(uint32_t count, Util::AllocatedSlice *slice);
void free(const Util::AllocatedSlice &slice);
void prime(const void *opaque_meta);
protected:
SliceAllocator() = default;
void init(uint32_t sub_block_size, uint32_t num_sub_blocks_in_arena_log2, SliceBackingAllocator *alloc);
private:
Util::ObjectPool<Util::LegionHeap<Util::AllocatedSlice>> object_pool;
SliceBackingAllocator *global_allocator = nullptr;
enum { SliceAllocatorCount = 5 };
Util::SliceSubAllocator allocators[SliceAllocatorCount];
};
}
@@ -0,0 +1,109 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <vector>
#include <type_traits>
namespace Util
{
template<typename T>
class ArrayView
{
public:
using ConstT = const typename std::remove_const<T>::type;
ArrayView(T *t, size_t size)
: ptr(t), array_size(size)
{
}
template<typename U>
ArrayView(U &u)
: ptr(u.data()), array_size(u.size())
{
}
ArrayView() = default;
T *begin()
{
return ptr;
}
T *end()
{
return ptr + array_size;
}
ConstT *begin() const
{
return ptr;
}
ConstT *end() const
{
return ptr + array_size;
}
T &operator[](size_t n)
{
return ptr[n];
}
ConstT &operator[](size_t n) const
{
return ptr[n];
}
size_t size() const
{
return array_size;
}
T *data()
{
return ptr;
}
ConstT *data() const
{
return ptr;
}
bool empty() const
{
return array_size == 0;
}
void reset()
{
ptr = nullptr;
array_size = 0;
}
private:
T *ptr = nullptr;
size_t array_size = 0;
};
}
@@ -0,0 +1,108 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <condition_variable>
#include <mutex>
#include <utility>
#include "read_write_lock.hpp"
namespace Util
{
template <typename T>
class AsyncObjectSink
{
public:
AsyncObjectSink()
{
has_object.store(false);
}
auto get_nowait()
{
return raw_object.load(std::memory_order_acquire);
}
auto get()
{
if (has_object.load(std::memory_order_acquire))
{
return get_nowait();
}
else
{
{
std::unique_lock<std::mutex> holder{lock};
cond.wait(holder, [&]()
{
return async_object_exists;
});
}
return get_nowait();
}
}
T write_object(T new_object)
{
auto *raw_ptr = new_object.get();
spin.lock_write();
std::swap(object, new_object);
// Need to release here since the content inside the pointer needs to be made visible
// before the pointer itself.
// The pointer needs to be written before has_object.
raw_object.store(raw_ptr, std::memory_order_release);
if (!has_object.exchange(true, std::memory_order_acq_rel))
{
std::lock_guard<std::mutex> holder{lock};
async_object_exists = true;
cond.notify_all();
}
spin.unlock_write();
return new_object;
}
void reset()
{
spin.lock_write();
std::lock_guard<std::mutex> holder{lock};
async_object_exists = false;
has_object.store(false);
object = {};
spin.unlock_write();
}
private:
T object;
using RawT = decltype(object.get());
std::atomic<RawT> raw_object;
std::condition_variable cond;
std::mutex lock;
Util::RWSpinLock spin;
std::atomic_bool has_object;
bool async_object_exists = false;
};
}
@@ -0,0 +1,157 @@
/* Copyright (c) 2021-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <atomic>
#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
#include "aligned_alloc.hpp"
#include "bitops.hpp"
#include <type_traits>
#include <utility>
#include <algorithm>
namespace Util
{
template <typename T, int MinimumMSB = 8>
class AtomicAppendBuffer
{
public:
static_assert(std::is_trivially_destructible<T>::value, "T is not trivially destructible.");
AtomicAppendBuffer()
{
count.store(0, std::memory_order_relaxed);
for (auto &l : lists)
l.store(nullptr, std::memory_order_relaxed);
assert(count.is_lock_free());
assert(lists[0].is_lock_free());
}
~AtomicAppendBuffer()
{
for (auto &l : lists)
{
auto *ptr = l.load(std::memory_order_relaxed);
Util::memalign_free(ptr);
}
}
AtomicAppendBuffer(const AtomicAppendBuffer &) = delete;
void operator=(const AtomicAppendBuffer &) = delete;
void clear()
{
count.store(0, std::memory_order_relaxed);
}
// Only thing that is thread-safe.
template <typename U>
void push(U &&u)
{
uint32_t offset = count.fetch_add(1, std::memory_order_relaxed);
auto w = reserve_write(offset);
w.first[w.second] = std::forward<U>(u);
}
uint32_t size() const
{
return count.load(std::memory_order_relaxed);
}
template <typename Func>
void for_each_ranged(Func &&func)
{
uint32_t offset = count.load(std::memory_order_relaxed);
if (!offset)
return;
offset--;
int msb = 31 - int(leading_zeroes(offset));
msb = std::max<int>(msb, MinimumMSB);
int list_index = msb - MinimumMSB;
// Iterate over the complete lists.
for (int index = 0; index < list_index; index++)
{
uint32_t num_elements = num_elements_per_list_index(index);
auto *t = lists[index].load(std::memory_order_relaxed);
for (uint32_t i = 0; i < num_elements; i += 1u << MinimumMSB)
func(&t[i], 1u << MinimumMSB);
}
// Iterate over the final list.
if (list_index != 0)
offset -= 1u << msb;
uint32_t num_elements = offset + 1;
auto *t = lists[list_index].load(std::memory_order_relaxed);
for (uint32_t i = 0; i < num_elements; i += 1u << MinimumMSB)
func(&t[i], std::min<uint32_t>(num_elements - i, 1u << MinimumMSB));
}
private:
static_assert(MinimumMSB < 32, "MinimumMSB must be < 32.");
std::atomic<T *> lists[32 - MinimumMSB];
std::atomic_uint32_t count;
static uint32_t num_elements_per_list_index(unsigned index)
{
return 1u << (index + MinimumMSB + unsigned(index == 0));
}
std::pair<T *, uint32_t> reserve_write(uint32_t required_offset)
{
int msb = 31 - int(leading_zeroes(uint32_t(required_offset)));
if (msb < MinimumMSB)
msb = MinimumMSB;
int list_index = msb - MinimumMSB;
uint32_t offset = required_offset;
if (list_index != 0)
offset -= 1u << msb;
// If we can observe the pointer, we're good.
if (auto *new_t = lists[list_index].load(std::memory_order_relaxed))
return { new_t, offset };
uint32_t required_count = num_elements_per_list_index(list_index);
size_t required_size = size_t(required_count) * sizeof(T);
auto *new_t = static_cast<T *>(Util::memalign_alloc(std::max<size_t>(64, alignof(T)), required_size));
T *expected_t = nullptr;
// Relaxed order is fine. We will not read anything from this buffer until we have synchronized, and
// thus memory order is moot.
if (!lists[list_index].compare_exchange_strong(expected_t, new_t,
std::memory_order_relaxed,
std::memory_order_relaxed))
{
// Another thread allocated early, free.
Util::memalign_free(new_t);
new_t = expected_t;
}
return { new_t, offset };
}
};
}
@@ -0,0 +1,199 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#ifdef _MSC_VER
#include <intrin.h>
#endif
namespace Util
{
#ifdef __GNUC__
#define leading_zeroes_(x) ((x) == 0 ? 32 : __builtin_clz(x))
#define trailing_zeroes_(x) ((x) == 0 ? 32 : __builtin_ctz(x))
#define trailing_ones_(x) __builtin_ctz(~uint32_t(x))
#define leading_zeroes64_(x) ((x) == 0 ? 64 : __builtin_clzll(x))
#define trailing_zeroes64_(x) ((x) == 0 ? 64 : __builtin_ctzll(x))
#define trailing_ones64_(x) __builtin_ctzll(~uint64_t(x))
#define popcount32_(x) __builtin_popcount(x)
#define popcount64_(x) __builtin_popcountll(x)
static inline uint32_t leading_zeroes(uint32_t x) { return leading_zeroes_(x); }
static inline uint32_t trailing_zeroes(uint32_t x) { return trailing_zeroes_(x); }
static inline uint32_t trailing_ones(uint32_t x) { return trailing_ones_(x); }
static inline uint32_t leading_zeroes64(uint64_t x) { return leading_zeroes64_(x); }
static inline uint32_t trailing_zeroes64(uint64_t x) { return trailing_zeroes64_(x); }
static inline uint32_t trailing_ones64(uint64_t x) { return trailing_ones64_(x); }
static inline uint32_t popcount32(uint32_t x) { return popcount32_(x); }
static inline uint32_t popcount64(uint64_t x) { return popcount64_(x); }
#elif defined(_MSC_VER)
namespace Internal
{
static inline uint32_t popcount32(uint32_t x)
{
return __popcnt(x);
}
static inline uint32_t popcount64(uint64_t x)
{
#ifdef _WIN64
return __popcnt64(x);
#else
return popcount32(uint32_t(x)) + popcount32(uint32_t(x >> 32));
#endif
}
static inline uint32_t clz(uint32_t x)
{
unsigned long result;
if (_BitScanReverse(&result, x))
return 31 - result;
else
return 32;
}
static inline uint32_t ctz(uint32_t x)
{
unsigned long result;
if (_BitScanForward(&result, x))
return result;
else
return 32;
}
static inline uint32_t clz64(uint64_t x)
{
#ifdef _WIN64
unsigned long result;
if (_BitScanReverse64(&result, x))
return 63 - result;
else
return 64;
#else
if (x > UINT32_MAX)
return clz(uint32_t(x >> 32));
else
return clz(uint32_t(x)) + 32;
#endif
}
static inline uint32_t ctz64(uint64_t x)
{
#ifdef _WIN64
unsigned long result;
if (_BitScanForward64(&result, x))
return result;
else
return 64;
#else
if ((x & UINT32_MAX) != 0)
return ctz(uint32_t(x));
else
return ctz(uint32_t(x >> 32)) + 32;
#endif
}
}
static inline uint32_t leading_zeroes(uint32_t x) { return Internal::clz(x); }
static inline uint32_t trailing_zeroes(uint32_t x) { return Internal::ctz(x); }
static inline uint32_t trailing_ones(uint32_t x) { return Internal::ctz(~x); }
static inline uint32_t leading_zeroes64(uint64_t x) { return Internal::clz64(x); }
static inline uint32_t trailing_zeroes64(uint64_t x) { return Internal::ctz64(x); }
static inline uint32_t trailing_ones64(uint64_t x) { return Internal::ctz64(~x); }
static inline uint32_t popcount32(uint32_t x) { return Internal::popcount32(x); }
static inline uint32_t popcount64(uint64_t x) { return Internal::popcount64(x); }
#else
#error "Implement me."
#endif
template <typename T>
inline void for_each_bit64(uint64_t value, const T &func)
{
while (value)
{
uint32_t bit = trailing_zeroes64(value);
func(bit);
value &= ~(1ull << bit);
}
}
template <typename T>
inline void for_each_bit(uint32_t value, const T &func)
{
while (value)
{
uint32_t bit = trailing_zeroes(value);
func(bit);
value &= ~(1u << bit);
}
}
template <typename T>
inline void for_each_bit_range(uint32_t value, const T &func)
{
if (value == ~0u)
{
func(0, 32);
return;
}
uint32_t bit_offset = 0;
while (value)
{
uint32_t bit = trailing_zeroes(value);
bit_offset += bit;
value >>= bit;
uint32_t range = trailing_ones(value);
func(bit_offset, range);
value &= ~((1u << range) - 1);
}
}
template <typename T>
inline bool is_pow2(T value)
{
return (value & (value - T(1))) == T(0);
}
inline uint32_t next_pow2(uint32_t v)
{
v--;
v |= v >> 16;
v |= v >> 8;
v |= v >> 4;
v |= v >> 2;
v |= v >> 1;
return v + 1;
}
inline uint32_t prev_pow2(uint32_t v)
{
return next_pow2(v + 1) >> 1;
}
inline uint32_t floor_log2(uint32_t v)
{
return 31 - leading_zeroes(v);
}
}
@@ -0,0 +1,189 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define NOMINMAX
#include "cli_parser.hpp"
#include "logging.hpp"
#include <limits>
#include <stdexcept>
#include <vector>
namespace Util
{
CLIParser::CLIParser(CLICallbacks cbs_, int argc_, char *argv_[])
: cbs(std::move(cbs_)), argc(argc_), argv(argv_)
{
}
bool CLIParser::parse()
{
#if defined(__EXCEPTIONS) || defined(__HAS_EXCEPTIONS)
try
#endif
{
while (argc && !ended_state)
{
const char *next = *argv++;
argc--;
if (*next != '-' && cbs.default_handler)
{
cbs.default_handler(next);
}
else
{
auto itr = cbs.callbacks.find(next);
if (itr == std::end(cbs.callbacks))
{
if (unknown_argument_is_default)
cbs.default_handler(next);
#if defined(__EXCEPTIONS) || defined(__HAS_EXCEPTIONS)
else
throw std::invalid_argument("Invalid argument");
#else
else
return false;
#endif
}
else
itr->second(*this);
}
}
return true;
}
#if defined(__EXCEPTIONS) || defined(__HAS_EXCEPTIONS)
catch (const std::exception &e)
{
LOGE("Failed to parse arguments: %s\n", e.what());
if (cbs.error_handler)
{
cbs.error_handler();
}
return false;
}
#endif
}
void CLIParser::end()
{
ended_state = true;
}
unsigned CLIParser::next_uint()
{
if (!argc)
{
#ifdef __EXCEPTIONS
throw std::invalid_argument("Tried to parse uint, but nothing left in arguments");
#else
return 0;
#endif
}
auto val = std::stoul(*argv);
if (val > std::numeric_limits<unsigned>::max())
{
#ifdef __EXCEPTIONS
throw std::invalid_argument("next_uint() out of range");
#else
return 0;
#endif
}
argc--;
argv++;
return unsigned(val);
}
double CLIParser::next_double()
{
if (!argc)
{
#ifdef __EXCEPTIONS
throw std::invalid_argument("Tried to parse double, but nothing left in arguments");
#else
return 0;
#endif
}
double val = std::stod(*argv);
argc--;
argv++;
return val;
}
const char *CLIParser::next_string()
{
if (!argc)
{
#ifdef __EXCEPTIONS
throw std::invalid_argument("Tried to parse string, but nothing left in arguments");
#else
return nullptr;
#endif
}
const char *ret = *argv;
argc--;
argv++;
return ret;
}
bool parse_cli_filtered(CLICallbacks cbs, int &argc, char *argv[], int &exit_code)
{
if (argc == 0)
{
exit_code = 1;
return false;
}
exit_code = 0;
std::vector<char *> filtered;
filtered.reserve(argc + 1);
filtered.push_back(argv[0]);
cbs.default_handler = [&](const char *arg) { filtered.push_back(const_cast<char *>(arg)); };
CLIParser parser(std::move(cbs), argc - 1, argv + 1);
parser.ignore_unknown_arguments();
if (!parser.parse())
{
exit_code = 1;
return false;
}
else if (parser.is_ended_state())
{
exit_code = 0;
return false;
}
argc = int(filtered.size());
std::copy(filtered.begin(), filtered.end(), argv);
argv[argc] = nullptr;
return true;
}
}
@@ -0,0 +1,82 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <functional>
#include <string>
#include <unordered_map>
#include <utility>
namespace Util
{
class CLIParser;
struct CLICallbacks
{
void add(const char *cli, const std::function<void(CLIParser &)> &func)
{
callbacks[cli] = func;
}
std::unordered_map<std::string, std::function<void(CLIParser &)>> callbacks;
std::function<void()> error_handler;
std::function<void(const char *)> default_handler;
};
class CLIParser
{
public:
// Don't pass in argv[0], which is the application name.
// Pass in argc - 1, argv + 1.
CLIParser(CLICallbacks cbs_, int argc_, char *argv_[]);
bool parse();
void end();
unsigned next_uint();
double next_double();
const char *next_string();
bool is_ended_state() const
{
return ended_state;
}
void ignore_unknown_arguments()
{
unknown_argument_is_default = true;
}
private:
CLICallbacks cbs;
int argc;
char **argv;
bool ended_state = false;
bool unknown_argument_is_default = false;
};
// Returns false is parsing requires an exit, either because of error, or by request.
// In that case, exit_code should be returned from main().
// argc / argv must contain the full argc, argv, where argv[0] holds program name.
bool parse_cli_filtered(CLICallbacks cbs, int &argc, char *argv[], int &exit_code);
}
@@ -0,0 +1,86 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stdint.h>
namespace Util
{
#ifdef _MSC_VER
// MSVC generates bogus warnings here.
#pragma warning(disable: 4307)
#endif
constexpr uint64_t fnv_iterate(uint64_t hash, uint8_t c)
{
return (hash * 0x100000001b3ull) ^ c;
}
template<size_t index>
constexpr uint64_t compile_time_fnv1_inner(uint64_t hash, const char *str)
{
return compile_time_fnv1_inner<index - 1>(fnv_iterate(hash, uint8_t(str[index])), str);
}
template<>
constexpr uint64_t compile_time_fnv1_inner<size_t(-1)>(uint64_t hash, const char *)
{
return hash;
}
template<size_t len>
constexpr uint64_t compile_time_fnv1(const char (&str)[len])
{
return compile_time_fnv1_inner<len - 1>(0xcbf29ce484222325ull, str);
}
constexpr uint64_t compile_time_fnv1_merge(uint64_t a, uint64_t b)
{
return fnv_iterate(
fnv_iterate(
fnv_iterate(
fnv_iterate(
fnv_iterate(
fnv_iterate(
fnv_iterate(
fnv_iterate(a, uint8_t(b >> 0)),
uint8_t(b >> 8)),
uint8_t(b >> 16)),
uint8_t(b >> 24)),
uint8_t(b >> 32)),
uint8_t(b >> 40)),
uint8_t(b >> 48)),
uint8_t(b >> 56));
}
constexpr uint64_t compile_time_fnv1_merged(uint64_t hash)
{
return hash;
}
template <typename T, typename... Ts>
constexpr uint64_t compile_time_fnv1_merged(T hash, T hash2, Ts... hashes)
{
return compile_time_fnv1_merged(compile_time_fnv1_merge(hash, hash2), hashes...);
}
}
@@ -0,0 +1,68 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "aligned_alloc.hpp"
#include <string.h>
#include <memory>
#include <algorithm>
#include <type_traits>
namespace Util
{
template <typename T>
class DynamicArray
{
public:
// Only POD-like types work here since we don't invoke placement new or delete.
static_assert(std::is_trivially_default_constructible<T>::value, "T must be trivially constructible.");
static_assert(std::is_trivially_destructible<T>::value, "T must be trivially destructible.");
void reserve(size_t n)
{
if (n > N)
{
n = std::max<size_t>(n, N * 3 / 2);
auto *new_ptr = static_cast<T *>(
memalign_alloc(std::max<size_t>(64, alignof(T)), n * sizeof(T)));
if (buffer)
memcpy(new_ptr, buffer.get(), N * sizeof(T));
buffer.reset(new_ptr);
N = n;
}
}
T &operator[](size_t index) { return buffer.get()[index]; }
const T &operator[](size_t index) const { return buffer.get()[index]; }
T *data() { return buffer.get(); }
const T *data() const { return buffer.get(); }
size_t get_capacity() const { return N; }
private:
std::unique_ptr<T, AlignedDeleter> buffer;
size_t N = 0;
};
}
@@ -0,0 +1,99 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "dynamic_library.hpp"
#include "logging.hpp"
#include <stdexcept>
#include <utility>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#else
#include <dlfcn.h>
#endif
namespace Util
{
DynamicLibrary::DynamicLibrary(const char *path)
{
#ifdef _WIN32
module = LoadLibraryA(path);
if (!module)
LOGE("Failed to load dynamic library.\n");
#else
dylib = dlopen(path, RTLD_NOW);
if (!dylib)
LOGE("Failed to load dynamic library.\n");
#endif
}
DynamicLibrary::DynamicLibrary(Util::DynamicLibrary &&other) noexcept
{
*this = std::move(other);
}
DynamicLibrary &DynamicLibrary::operator=(Util::DynamicLibrary &&other) noexcept
{
close();
#ifdef _WIN32
module = other.module;
other.module = nullptr;
#else
dylib = other.dylib;
other.dylib = nullptr;
#endif
return *this;
}
void DynamicLibrary::close()
{
#ifdef _WIN32
if (module)
FreeLibrary(module);
module = nullptr;
#else
if (dylib)
dlclose(dylib);
dylib = nullptr;
#endif
}
DynamicLibrary::~DynamicLibrary()
{
close();
}
void *DynamicLibrary::get_symbol_internal(const char *symbol)
{
#ifdef _WIN32
if (module)
return (void*)GetProcAddress(module, symbol);
else
return nullptr;
#else
if (dylib)
return dlsym(dylib, symbol);
else
return nullptr;
#endif
}
}
@@ -0,0 +1,67 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
namespace Util
{
class DynamicLibrary
{
public:
DynamicLibrary() = default;
explicit DynamicLibrary(const char *path);
~DynamicLibrary();
DynamicLibrary(DynamicLibrary &&other) noexcept;
DynamicLibrary &operator=(DynamicLibrary &&other) noexcept;
template <typename Func>
Func get_symbol(const char *symbol)
{
return reinterpret_cast<Func>(get_symbol_internal(symbol));
}
explicit operator bool() const
{
#if _WIN32
return module != nullptr;
#else
return dylib != nullptr;
#endif
}
private:
#if _WIN32
HMODULE module = nullptr;
#else
void *dylib = nullptr;
#endif
void *get_symbol_internal(const char *symbol);
void close();
};
}
@@ -0,0 +1,34 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <type_traits>
namespace Util
{
template <typename T>
constexpr typename std::underlying_type<T>::type ecast(T x)
{
return static_cast<typename std::underlying_type<T>::type>(x);
}
}
@@ -0,0 +1,96 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include "environment.hpp"
#include <string>
#include <stdlib.h>
namespace Util
{
bool get_environment(const char *env, std::string &str)
{
#ifdef _WIN32
char buf[4096];
DWORD count = GetEnvironmentVariableA(env, buf, sizeof(buf));
if (count)
{
str = { buf, buf + count };
return true;
}
else
return false;
#else
if (const char *v = getenv(env))
{
str = v;
return true;
}
else
return false;
#endif
}
void set_environment(const char *env, const char *value)
{
#ifdef _WIN32
SetEnvironmentVariableA(env, value);
#else
setenv(env, value, 1);
#endif
}
std::string get_environment_string(const char *env, const char *default_value)
{
std::string v;
if (!get_environment(env, v))
v = default_value;
return v;
}
unsigned get_environment_uint(const char *env, unsigned default_value)
{
unsigned value = default_value;
std::string v;
if (get_environment(env, v))
value = unsigned(std::stoul(v));
return value;
}
int get_environment_int(const char *env, int default_value)
{
int value = default_value;
std::string v;
if (get_environment(env, v))
value = int(std::stol(v));
return value;
}
bool get_environment_bool(const char *env, bool default_value)
{
return get_environment_int(env, int(default_value)) != 0;
}
}
@@ -0,0 +1,35 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <string>
namespace Util
{
bool get_environment(const char *env, std::string &str);
std::string get_environment_string(const char *env, const char *default_value);
unsigned get_environment_uint(const char *env, unsigned default_value);
int get_environment_int(const char *env, int default_value);
bool get_environment_bool(const char *env, bool default_value);
void set_environment(const char *env, const char *value);
}
@@ -0,0 +1,175 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <vector>
#include <stack>
#include <utility>
#include <stdint.h>
#include "object_pool.hpp"
#include <assert.h>
namespace Util
{
using GenerationalHandleID = uint32_t;
template <typename T>
class GenerationalHandlePool
{
public:
using ID = GenerationalHandleID;
GenerationalHandlePool()
{
elements.resize(16);
generation.resize(16);
for (unsigned i = 0; i < 16; i++)
vacant_indices.push(i);
}
~GenerationalHandlePool()
{
for (auto &elem : elements)
if (elem)
pool.free(elem);
}
GenerationalHandlePool(const GenerationalHandlePool &) = delete;
void operator=(const GenerationalHandlePool &) = delete;
template <typename... P>
ID emplace(P&&... p)
{
auto index = get_vacant_index();
auto generation_index = uint8_t(++generation[index]);
// Reserve generation index 0 for sentinel purposes.
if (!generation_index)
generation_index = ++generation[index];
elements[index] = pool.allocate(std::forward<P>(p)...);
return make_id(index, generation_index);
}
void remove(ID id)
{
auto index = memory_index(id);
auto gen_index = generation_index(id);
if (index >= elements.size())
return;
if (gen_index != generation[index])
return;
if (!elements[index])
return;
pool.free(elements[index]);
elements[index] = nullptr;
vacant_indices.push(index);
}
T *maybe_get(ID id) const
{
auto index = memory_index(id);
auto gen_index = generation_index(id);
if (index >= elements.size())
return nullptr;
if (gen_index != generation[index])
return nullptr;
return elements[index];
}
T &get(ID id) const
{
auto index = memory_index(id);
auto gen_index = generation_index(id);
if (index >= elements.size())
throw std::logic_error("Invalid ID.");
if (gen_index != generation[index])
throw std::logic_error("Invalid ID.");
if (!elements[index])
throw std::logic_error("Invalid ID.");
return *elements[index];
}
void clear()
{
for (size_t i = 0; i < elements.size(); i++)
{
if (elements[i])
{
pool.free(elements[i]);
vacant_indices.push(i);
elements[i] = nullptr;
}
}
}
private:
Util::ObjectPool<T> pool;
std::vector<T *> elements;
std::vector<uint8_t> generation;
std::stack<uint32_t> vacant_indices;
uint32_t get_vacant_index()
{
if (vacant_indices.empty())
{
size_t current_size = elements.size();
// If this is ever a problem, we can bump to 64-bit IDs.
if (current_size >= (size_t(1) << 24u))
throw std::bad_alloc();
elements.resize(current_size * 2);
generation.resize(current_size * 2);
for (size_t index = current_size; index < 2 * current_size; index++)
vacant_indices.push(index);
}
auto ret = vacant_indices.top();
vacant_indices.pop();
return ret;
}
static ID make_id(uint32_t index, uint32_t generation_index)
{
assert(index <= 0x00ffffffu);
assert(generation_index <= 0xffu);
return (generation_index << 24u) | index;
}
static uint32_t generation_index(ID id)
{
return (id >> 24u) & 0xffu;
}
static uint32_t memory_index(ID id)
{
return id & 0xffffffu;
}
};
}
@@ -0,0 +1,105 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <string>
namespace Util
{
using Hash = uint64_t;
class Hasher
{
public:
explicit Hasher(Hash h_)
: h(h_)
{
}
Hasher() = default;
template <typename T>
inline void data(const T *data_, size_t size)
{
size /= sizeof(*data_);
for (size_t i = 0; i < size; i++)
h = (h * 0x100000001b3ull) ^ data_[i];
}
inline void u32(uint32_t value)
{
h = (h * 0x100000001b3ull) ^ value;
}
inline void s32(int32_t value)
{
u32(uint32_t(value));
}
inline void f32(float value)
{
union
{
float f32;
uint32_t u32;
} u;
u.f32 = value;
u32(u.u32);
}
inline void u64(uint64_t value)
{
u32(value & 0xffffffffu);
u32(value >> 32);
}
template <typename T>
inline void pointer(T *ptr)
{
u64(reinterpret_cast<uintptr_t>(ptr));
}
inline void string(const char *str)
{
char c;
u32(0xff);
while ((c = *str++) != '\0')
u32(uint8_t(c));
}
inline void string(const std::string &str)
{
u32(0xff);
for (auto &c : str)
u32(uint8_t(c));
}
inline Hash get() const
{
return h;
}
private:
Hash h = 0xcbf29ce484222325ull;
};
}
@@ -0,0 +1,40 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <unordered_map>
#include "hash.hpp"
namespace Util
{
struct UnityHasher
{
inline size_t operator()(uint64_t hash) const
{
return hash;
}
};
template <typename T>
using HashMap = std::unordered_map<Hash, T, UnityHasher>;
}
@@ -0,0 +1,310 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stddef.h>
#include <utility>
#include <memory>
#include <atomic>
#include <type_traits>
namespace Util
{
class SingleThreadCounter
{
public:
inline void add_ref()
{
count++;
}
inline bool release()
{
return --count == 0;
}
private:
uint32_t count = 1;
};
class MultiThreadCounter
{
public:
MultiThreadCounter()
{
count.store(1, std::memory_order_relaxed);
}
inline void add_ref()
{
count.fetch_add(1, std::memory_order_relaxed);
}
inline bool release()
{
auto result = count.fetch_sub(1, std::memory_order_acq_rel);
return result == 1;
}
private:
std::atomic_uint32_t count;
};
template <typename T>
class IntrusivePtr;
template <typename T, typename Deleter = std::default_delete<T>, typename ReferenceOps = SingleThreadCounter>
class IntrusivePtrEnabled
{
public:
using IntrusivePtrType = IntrusivePtr<T>;
using EnabledBase = T;
using EnabledDeleter = Deleter;
using EnabledReferenceOp = ReferenceOps;
void release_reference()
{
if (reference_count.release())
Deleter()(static_cast<T *>(this));
}
void add_reference()
{
reference_count.add_ref();
}
IntrusivePtrEnabled() = default;
IntrusivePtrEnabled(const IntrusivePtrEnabled &) = delete;
void operator=(const IntrusivePtrEnabled &) = delete;
protected:
Util::IntrusivePtr<T> reference_from_this();
private:
ReferenceOps reference_count;
};
template <typename T>
class IntrusivePtr
{
public:
template <typename U>
friend class IntrusivePtr;
IntrusivePtr() = default;
explicit IntrusivePtr(T *handle)
: data(handle)
{
}
T &operator*()
{
return *data;
}
const T &operator*() const
{
return *data;
}
T *operator->()
{
return data;
}
const T *operator->() const
{
return data;
}
explicit operator bool() const
{
return data != nullptr;
}
bool operator==(const IntrusivePtr &other) const
{
return data == other.data;
}
bool operator!=(const IntrusivePtr &other) const
{
return data != other.data;
}
T *get()
{
return data;
}
const T *get() const
{
return data;
}
void reset()
{
using ReferenceBase = IntrusivePtrEnabled<
typename T::EnabledBase,
typename T::EnabledDeleter,
typename T::EnabledReferenceOp>;
// Static up-cast here to avoid potential issues with multiple intrusive inheritance.
// Also makes sure that the pointer type actually inherits from this type.
if (data)
static_cast<ReferenceBase *>(data)->release_reference();
data = nullptr;
}
template <typename U>
IntrusivePtr &operator=(const IntrusivePtr<U> &other)
{
static_assert(std::is_base_of<T, U>::value,
"Cannot safely assign downcasted intrusive pointers.");
using ReferenceBase = IntrusivePtrEnabled<
typename T::EnabledBase,
typename T::EnabledDeleter,
typename T::EnabledReferenceOp>;
reset();
data = static_cast<T *>(other.data);
// Static up-cast here to avoid potential issues with multiple intrusive inheritance.
// Also makes sure that the pointer type actually inherits from this type.
if (data)
static_cast<ReferenceBase *>(data)->add_reference();
return *this;
}
IntrusivePtr &operator=(const IntrusivePtr &other)
{
using ReferenceBase = IntrusivePtrEnabled<
typename T::EnabledBase,
typename T::EnabledDeleter,
typename T::EnabledReferenceOp>;
if (this != &other)
{
reset();
data = other.data;
if (data)
static_cast<ReferenceBase *>(data)->add_reference();
}
return *this;
}
template <typename U>
IntrusivePtr(const IntrusivePtr<U> &other)
{
*this = other;
}
IntrusivePtr(const IntrusivePtr &other)
{
*this = other;
}
~IntrusivePtr()
{
reset();
}
template <typename U>
IntrusivePtr &operator=(IntrusivePtr<U> &&other) noexcept
{
reset();
data = other.data;
other.data = nullptr;
return *this;
}
IntrusivePtr &operator=(IntrusivePtr &&other) noexcept
{
if (this != &other)
{
reset();
data = other.data;
other.data = nullptr;
}
return *this;
}
template <typename U>
IntrusivePtr(IntrusivePtr<U> &&other) noexcept
{
*this = std::move(other);
}
template <typename U>
IntrusivePtr(IntrusivePtr &&other) noexcept
{
*this = std::move(other);
}
T *release() &
{
T *ret = data;
data = nullptr;
return ret;
}
T *release() &&
{
T *ret = data;
data = nullptr;
return ret;
}
private:
T *data = nullptr;
};
template <typename T, typename Deleter, typename ReferenceOps>
IntrusivePtr<T> IntrusivePtrEnabled<T, Deleter, ReferenceOps>::reference_from_this()
{
add_reference();
return IntrusivePtr<T>(static_cast<T *>(this));
}
template <typename Derived>
using DerivedIntrusivePtrType = IntrusivePtr<Derived>;
template <typename T, typename... P>
DerivedIntrusivePtrType<T> make_handle(P &&... p)
{
return DerivedIntrusivePtrType<T>(new T(std::forward<P>(p)...));
}
template <typename Base, typename Derived, typename... P>
typename Base::IntrusivePtrType make_derived_handle(P &&... p)
{
return typename Base::IntrusivePtrType(new Derived(std::forward<P>(p)...));
}
template <typename T>
using ThreadSafeIntrusivePtrEnabled = IntrusivePtrEnabled<T, std::default_delete<T>, MultiThreadCounter>;
}
@@ -0,0 +1,695 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "hash.hpp"
#include "intrusive_list.hpp"
#include "object_pool.hpp"
#include "read_write_lock.hpp"
#include <assert.h>
#include <vector>
namespace Util
{
template <typename T>
class IntrusiveHashMapEnabled : public IntrusiveListEnabled<T>
{
public:
IntrusiveHashMapEnabled() = default;
IntrusiveHashMapEnabled(Util::Hash hash)
: intrusive_hashmap_key(hash)
{
}
void set_hash(Util::Hash hash)
{
intrusive_hashmap_key = hash;
}
Util::Hash get_hash() const
{
return intrusive_hashmap_key;
}
private:
Hash intrusive_hashmap_key = 0;
};
template <typename T>
struct IntrusivePODWrapper : public IntrusiveHashMapEnabled<IntrusivePODWrapper<T>>
{
template <typename U>
explicit IntrusivePODWrapper(U&& value_)
: value(std::forward<U>(value_))
{
}
IntrusivePODWrapper() = default;
T& get()
{
return value;
}
const T& get() const
{
return value;
}
T value = {};
};
// This HashMap is non-owning. It just arranges a list of pointers.
// It's kind of special purpose container used by the Vulkan backend.
// Dealing with memory ownership is done through composition by a different class.
// T must inherit from IntrusiveHashMapEnabled<T>.
// Each instance of T can only be part of one hashmap.
template <typename T>
class IntrusiveHashMapHolder
{
public:
enum { InitialSize = 16, InitialLoadCount = 3 };
T *find(Hash hash) const
{
if (values.empty())
return nullptr;
Hash hash_mask = values.size() - 1;
auto masked = hash & hash_mask;
for (unsigned i = 0; i < load_count; i++)
{
if (values[masked] && get_hash(values[masked]) == hash)
return values[masked];
masked = (masked + 1) & hash_mask;
}
return nullptr;
}
template <typename P>
bool find_and_consume_pod(Hash hash, P &p) const
{
T *t = find(hash);
if (t)
{
p = t->get();
return true;
}
else
return false;
}
// Inserts, if value already exists, insertion does not happen.
// Return value is the data which is not part of the hashmap.
// It should be deleted or similar.
// Returns nullptr if nothing was in the hashmap for this key.
T *insert_yield(T *&value)
{
if (values.empty())
grow();
Hash hash_mask = values.size() - 1;
auto hash = get_hash(value);
auto masked = hash & hash_mask;
for (unsigned i = 0; i < load_count; i++)
{
if (values[masked] && get_hash(values[masked]) == hash)
{
T *ret = value;
value = values[masked];
return ret;
}
else if (!values[masked])
{
values[masked] = value;
list.insert_front(value);
return nullptr;
}
masked = (masked + 1) & hash_mask;
}
grow();
return insert_yield(value);
}
T *insert_replace(T *value)
{
if (values.empty())
grow();
Hash hash_mask = values.size() - 1;
auto hash = get_hash(value);
auto masked = hash & hash_mask;
for (unsigned i = 0; i < load_count; i++)
{
if (values[masked] && get_hash(values[masked]) == hash)
{
std::swap(values[masked], value);
list.erase(value);
list.insert_front(values[masked]);
return value;
}
else if (!values[masked])
{
assert(!values[masked]);
values[masked] = value;
list.insert_front(value);
return nullptr;
}
masked = (masked + 1) & hash_mask;
}
grow();
return insert_replace(value);
}
T *erase(Hash hash)
{
Hash hash_mask = values.size() - 1;
auto masked = hash & hash_mask;
for (unsigned i = 0; i < load_count; i++)
{
if (values[masked] && get_hash(values[masked]) == hash)
{
auto *value = values[masked];
list.erase(value);
values[masked] = nullptr;
return value;
}
masked = (masked + 1) & hash_mask;
}
return nullptr;
}
void erase(T *value)
{
erase(get_hash(value));
}
void clear()
{
list.clear();
values.clear();
load_count = 0;
}
typename IntrusiveList<T>::Iterator begin() const
{
return list.begin();
}
typename IntrusiveList<T>::Iterator end() const
{
return list.end();
}
IntrusiveList<T> &inner_list()
{
return list;
}
const IntrusiveList<T> &inner_list() const
{
return list;
}
private:
inline bool compare_key(Hash masked, Hash hash) const
{
return get_key_for_index(masked) == hash;
}
inline Hash get_hash(const T *value) const
{
return static_cast<const IntrusiveHashMapEnabled<T> *>(value)->get_hash();
}
inline Hash get_key_for_index(Hash masked) const
{
return get_hash(values[masked]);
}
bool insert_inner(T *value)
{
Hash hash_mask = values.size() - 1;
auto hash = get_hash(value);
auto masked = hash & hash_mask;
for (unsigned i = 0; i < load_count; i++)
{
if (!values[masked])
{
values[masked] = value;
return true;
}
masked = (masked + 1) & hash_mask;
}
return false;
}
void grow()
{
bool success;
do
{
for (auto &v : values)
v = nullptr;
if (values.empty())
{
values.resize(InitialSize);
load_count = InitialLoadCount;
//LOGI("Growing hashmap to %u elements.\n", InitialSize);
}
else
{
values.resize(values.size() * 2);
//LOGI("Growing hashmap to %u elements.\n", unsigned(values.size()));
load_count++;
}
// Re-insert.
success = true;
for (auto &t : list)
{
if (!insert_inner(&t))
{
success = false;
break;
}
}
} while (!success);
}
std::vector<T *> values;
IntrusiveList<T> list;
unsigned load_count = 0;
};
template <typename T>
class IntrusiveHashMap
{
public:
~IntrusiveHashMap()
{
clear();
}
IntrusiveHashMap() = default;
IntrusiveHashMap(const IntrusiveHashMap &) = delete;
void operator=(const IntrusiveHashMap &) = delete;
void clear()
{
auto &list = hashmap.inner_list();
auto itr = list.begin();
while (itr != list.end())
{
auto *to_free = itr.get();
itr = list.erase(itr);
pool.free(to_free);
}
hashmap.clear();
}
T *find(Hash hash) const
{
return hashmap.find(hash);
}
T &operator[](Hash hash)
{
auto *t = find(hash);
if (!t)
t = emplace_yield(hash);
return *t;
}
template <typename P>
bool find_and_consume_pod(Hash hash, P &p) const
{
return hashmap.find_and_consume_pod(hash, p);
}
void erase(T *value)
{
hashmap.erase(value);
pool.free(value);
}
void erase(Hash hash)
{
auto *value = hashmap.erase(hash);
if (value)
pool.free(value);
}
template <typename... P>
T *emplace_replace(Hash hash, P&&... p)
{
T *t = allocate(std::forward<P>(p)...);
return insert_replace(hash, t);
}
template <typename... P>
T *emplace_yield(Hash hash, P&&... p)
{
T *t = allocate(std::forward<P>(p)...);
return insert_yield(hash, t);
}
template <typename... P>
T *allocate(P&&... p)
{
return pool.allocate(std::forward<P>(p)...);
}
void free(T *value)
{
pool.free(value);
}
T *insert_replace(Hash hash, T *value)
{
static_cast<IntrusiveHashMapEnabled<T> *>(value)->set_hash(hash);
T *to_delete = hashmap.insert_replace(value);
if (to_delete)
pool.free(to_delete);
return value;
}
T *insert_yield(Hash hash, T *value)
{
static_cast<IntrusiveHashMapEnabled<T> *>(value)->set_hash(hash);
T *to_delete = hashmap.insert_yield(value);
if (to_delete)
pool.free(to_delete);
return value;
}
typename IntrusiveList<T>::Iterator begin() const
{
return hashmap.begin();
}
typename IntrusiveList<T>::Iterator end() const
{
return hashmap.end();
}
IntrusiveHashMap &get_thread_unsafe()
{
return *this;
}
const IntrusiveHashMap &get_thread_unsafe() const
{
return *this;
}
private:
IntrusiveHashMapHolder<T> hashmap;
ObjectPool<T> pool;
};
template <typename T>
using IntrusiveHashMapWrapper = IntrusiveHashMap<IntrusivePODWrapper<T>>;
template <typename T>
class ThreadSafeIntrusiveHashMap
{
public:
T *find(Hash hash) const
{
lock.lock_read();
T *t = hashmap.find(hash);
lock.unlock_read();
// We can race with the intrusive list internal pointers,
// but that's an internal detail which should never be touched outside the hashmap.
return t;
}
template <typename P>
bool find_and_consume_pod(Hash hash, P &p) const
{
lock.lock_read();
bool ret = hashmap.find_and_consume_pod(hash, p);
lock.unlock_read();
return ret;
}
void clear()
{
lock.lock_write();
hashmap.clear();
lock.unlock_write();
}
// Assumption is that readers will not be erased while in use by any other thread.
void erase(T *value)
{
lock.lock_write();
hashmap.erase(value);
lock.unlock_write();
}
void erase(Hash hash)
{
lock.lock_write();
hashmap.erase(hash);
lock.unlock_write();
}
template <typename... P>
T *allocate(P&&... p)
{
lock.lock_write();
T *t = hashmap.allocate(std::forward<P>(p)...);
lock.unlock_write();
return t;
}
void free(T *value)
{
lock.lock_write();
hashmap.free(value);
lock.unlock_write();
}
T *insert_replace(Hash hash, T *value)
{
lock.lock_write();
value = hashmap.insert_replace(hash, value);
lock.unlock_write();
return value;
}
T *insert_yield(Hash hash, T *value)
{
lock.lock_write();
value = hashmap.insert_yield(hash, value);
lock.unlock_write();
return value;
}
// This one is very sketchy, since callers need to make sure there are no readers of this hash.
template <typename... P>
T *emplace_replace(Hash hash, P&&... p)
{
lock.lock_write();
T *t = hashmap.emplace_replace(hash, std::forward<P>(p)...);
lock.unlock_write();
return t;
}
template <typename... P>
T *emplace_yield(Hash hash, P&&... p)
{
lock.lock_write();
T *t = hashmap.emplace_yield(hash, std::forward<P>(p)...);
lock.unlock_write();
return t;
}
// Not supposed to be called in racy conditions,
// we could have a global read lock and unlock while iterating if necessary.
typename IntrusiveList<T>::Iterator begin()
{
return hashmap.begin();
}
typename IntrusiveList<T>::Iterator end()
{
return hashmap.end();
}
IntrusiveHashMap<T> &get_thread_unsafe()
{
return hashmap;
}
const IntrusiveHashMap<T> &get_thread_unsafe() const
{
return hashmap;
}
private:
IntrusiveHashMap<T> hashmap;
mutable RWSpinLock lock;
};
// A special purpose hashmap which is split into a read-only, immutable portion and a plain thread-safe one.
// User can move read-write thread-safe portion to read-only portion when user knows it's safe to do so.
template <typename T>
class ThreadSafeIntrusiveHashMapReadCached
{
public:
~ThreadSafeIntrusiveHashMapReadCached()
{
clear();
}
T *find(Hash hash) const
{
T *t = read_only.find(hash);
if (t)
return t;
lock.lock_read();
t = read_write.find(hash);
lock.unlock_read();
return t;
}
void move_to_read_only()
{
auto &list = read_write.inner_list();
auto itr = list.begin();
while (itr != list.end())
{
auto *to_move = itr.get();
read_write.erase(to_move);
T *to_delete = read_only.insert_yield(to_move);
if (to_delete)
object_pool.free(to_delete);
itr = list.begin();
}
}
template <typename P>
bool find_and_consume_pod(Hash hash, P &p) const
{
if (read_only.find_and_consume_pod(hash, p))
return true;
lock.lock_read();
bool ret = read_write.find_and_consume_pod(hash, p);
lock.unlock_read();
return ret;
}
void clear()
{
lock.lock_write();
clear_list(read_only.inner_list());
clear_list(read_write.inner_list());
read_only.clear();
read_write.clear();
lock.unlock_write();
}
template <typename... P>
T *allocate(P&&... p)
{
lock.lock_write();
T *t = object_pool.allocate(std::forward<P>(p)...);
lock.unlock_write();
return t;
}
void free(T *ptr)
{
lock.lock_write();
object_pool.free(ptr);
lock.unlock_write();
}
T *insert_yield(Hash hash, T *value)
{
static_cast<IntrusiveHashMapEnabled<T> *>(value)->set_hash(hash);
lock.lock_write();
T *to_delete = read_write.insert_yield(value);
if (to_delete)
object_pool.free(to_delete);
lock.unlock_write();
return value;
}
template <typename... P>
T *emplace_yield(Hash hash, P&&... p)
{
T *t = allocate(std::forward<P>(p)...);
return insert_yield(hash, t);
}
IntrusiveHashMapHolder<T> &get_read_only()
{
return read_only;
}
const IntrusiveHashMapHolder<T> &get_read_only() const
{
return read_only;
}
IntrusiveHashMapHolder<T> &get_read_write()
{
return read_write;
}
private:
IntrusiveHashMapHolder<T> read_only;
IntrusiveHashMapHolder<T> read_write;
ObjectPool<T> object_pool;
mutable RWSpinLock lock;
void clear_list(IntrusiveList<T> &list)
{
auto itr = list.begin();
while (itr != list.end())
{
auto *to_free = itr.get();
itr = list.erase(itr);
object_pool.free(to_free);
}
}
};
}
@@ -0,0 +1,197 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
namespace Util
{
template <typename T>
struct IntrusiveListEnabled
{
IntrusiveListEnabled<T> *prev = nullptr;
IntrusiveListEnabled<T> *next = nullptr;
};
template <typename T>
class IntrusiveList
{
public:
void clear()
{
head = nullptr;
tail = nullptr;
}
class Iterator
{
public:
friend class IntrusiveList<T>;
Iterator(IntrusiveListEnabled<T> *node_)
: node(node_)
{
}
Iterator() = default;
explicit operator bool() const
{
return node != nullptr;
}
bool operator==(const Iterator &other) const
{
return node == other.node;
}
bool operator!=(const Iterator &other) const
{
return node != other.node;
}
T &operator*()
{
return *static_cast<T *>(node);
}
const T &operator*() const
{
return *static_cast<T *>(node);
}
T *get()
{
return static_cast<T *>(node);
}
const T *get() const
{
return static_cast<const T *>(node);
}
T *operator->()
{
return static_cast<T *>(node);
}
const T *operator->() const
{
return static_cast<T *>(node);
}
Iterator &operator++()
{
node = node->next;
return *this;
}
Iterator &operator--()
{
node = node->prev;
return *this;
}
private:
IntrusiveListEnabled<T> *node = nullptr;
};
Iterator begin() const
{
return Iterator(head);
}
Iterator rbegin() const
{
return Iterator(tail);
}
Iterator end() const
{
return Iterator();
}
Iterator erase(Iterator itr)
{
auto *node = itr.get();
auto *next = node->next;
auto *prev = node->prev;
if (prev)
prev->next = next;
else
head = next;
if (next)
next->prev = prev;
else
tail = prev;
return next;
}
void insert_front(Iterator itr)
{
auto *node = itr.get();
if (head)
head->prev = node;
else
tail = node;
node->next = head;
node->prev = nullptr;
head = node;
}
void insert_back(Iterator itr)
{
auto *node = itr.get();
if (tail)
tail->next = node;
else
head = node;
node->prev = tail;
node->next = nullptr;
tail = node;
}
void move_to_front(IntrusiveList<T> &other, Iterator itr)
{
other.erase(itr);
insert_front(itr);
}
void move_to_back(IntrusiveList<T> &other, Iterator itr)
{
other.erase(itr);
insert_back(itr);
}
bool empty() const
{
return head == nullptr;
}
private:
IntrusiveListEnabled<T> *head = nullptr;
IntrusiveListEnabled<T> *tail = nullptr;
};
}
@@ -0,0 +1,72 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "logging.hpp"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
namespace Util
{
static thread_local LoggingInterface *logging_iface;
bool interface_log(const char *tag, const char *fmt, ...)
{
if (!logging_iface)
return false;
va_list va;
va_start(va, fmt);
bool ret = logging_iface->log(tag, fmt, va);
va_end(va);
return ret;
}
void set_thread_logging_interface(LoggingInterface *iface)
{
logging_iface = iface;
}
#ifdef _WIN32
void debug_output_log(const char *tag, const char *fmt, ...)
{
if (!IsDebuggerPresent())
return;
va_list va;
va_start(va, fmt);
auto len = vsnprintf(nullptr, 0, fmt, va);
if (len > 0)
{
size_t tag_len = strlen(tag);
char *buf = new char[len + tag_len + 1];
memcpy(buf, tag, tag_len);
vsnprintf(buf + tag_len, len + 1, fmt, va);
OutputDebugStringA(buf);
delete[] buf;
}
va_end(va);
}
#endif
}
@@ -0,0 +1,162 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
namespace Util
{
class LoggingInterface
{
public:
virtual ~LoggingInterface() = default;
virtual bool log(const char *tag, const char *fmt, va_list va) = 0;
};
bool interface_log(const char *tag, const char *fmt, ...);
void set_thread_logging_interface(LoggingInterface *iface);
}
#if defined(_WIN32)
namespace Util
{
void debug_output_log(const char *tag, const char *fmt, ...);
}
#define LOGE_FALLBACK(...) do { \
fprintf(stderr, "[ERROR]: " __VA_ARGS__); \
fflush(stderr); \
::Util::debug_output_log("[ERROR]: ", __VA_ARGS__); \
} while(false)
#define LOGW_FALLBACK(...) do { \
fprintf(stderr, "[WARN]: " __VA_ARGS__); \
fflush(stderr); \
::Util::debug_output_log("[WARN]: ", __VA_ARGS__); \
} while(false)
#define LOGI_FALLBACK(...) do { \
fprintf(stderr, "[INFO]: " __VA_ARGS__); \
fflush(stderr); \
::Util::debug_output_log("[INFO]: ", __VA_ARGS__); \
} while(false)
#elif defined(ANDROID)
#include <android/log.h>
#define LOGE_FALLBACK(...) do { __android_log_print(ANDROID_LOG_ERROR, "Granite", __VA_ARGS__); } while(0)
#define LOGW_FALLBACK(...) do { __android_log_print(ANDROID_LOG_WARN, "Granite", __VA_ARGS__); } while(0)
#define LOGI_FALLBACK(...) do { __android_log_print(ANDROID_LOG_INFO, "Granite", __VA_ARGS__); } while(0)
#else
#define LOGE_FALLBACK(...) \
do \
{ \
fprintf(stderr, "[ERROR]: " __VA_ARGS__); \
fflush(stderr); \
} while (false)
#define LOGW_FALLBACK(...) \
do \
{ \
fprintf(stderr, "[WARN]: " __VA_ARGS__); \
fflush(stderr); \
} while (false)
#define LOGI_FALLBACK(...) \
do \
{ \
fprintf(stderr, "[INFO]: " __VA_ARGS__); \
fflush(stderr); \
} while (false)
#endif
#ifndef _MSC_VER
#include <stdio.h>
namespace Internal
{
// Stole idea from RenderDoc.
struct IsDebugged
{
IsDebugged()
{
FILE *f = ::fopen("/proc/self/status", "r");
if (!f)
return;
while (!feof(f))
{
constexpr int size = 512;
char line[size];
line[size - 1] = '\0';
if (!fgets(line, sizeof(line) - 1, f))
break;
int pid = 0;
if (sscanf(line, "TracerPid: %d", &pid) == 1 && pid != 0)
{
state = true;
break;
}
}
::fclose(f);
}
bool state = false;
};
static inline bool is_debugged()
{
static IsDebugged is_debugged;
return is_debugged.state;
}
}
#else
#define NOMINMAX
#include <windows.h>
#include <debugapi.h>
#endif
static inline void debug_break()
{
#ifdef _MSC_VER
if (IsDebuggerPresent())
__debugbreak();
#else
#if defined(__GNUC__) && defined(__linux__) && (defined(__i386__) || defined(__x86_64__))
// __builtin_trap on GCC is SIGILL, not SIGTRAP.
// Stole idea from RenderDoc.
if (Internal::is_debugged())
__asm__ volatile("int $0x03;");
#elif defined(__clang__)
if (Internal::is_debugged())
__builtin_debugtrap();
#endif
#endif
}
#define LOGE(...) do { if (!::Util::interface_log("[ERROR]: ", __VA_ARGS__)) { LOGE_FALLBACK(__VA_ARGS__); } debug_break(); } while(0)
#define LOGW(...) do { if (!::Util::interface_log("[WARN]: ", __VA_ARGS__)) { LOGW_FALLBACK(__VA_ARGS__); }} while(0)
#define LOGI(...) do { if (!::Util::interface_log("[INFO]: ", __VA_ARGS__)) { LOGI_FALLBACK(__VA_ARGS__); }} while(0)
@@ -0,0 +1,163 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "object_pool.hpp"
#include "intrusive_list.hpp"
#include "intrusive_hash_map.hpp"
namespace Util
{
template <typename T>
class LRUCache
{
public:
void set_total_cost(uint64_t cost)
{
total_cost_limit = cost;
}
uint64_t get_current_cost() const
{
return total_cost;
}
T *find_and_mark_as_recent(uint64_t cookie)
{
auto *entry = hashmap.find(get_hash(cookie));
if (entry)
{
lru.move_to_front(lru, entry->get());
return &entry->get()->t;
}
else
return nullptr;
}
T *allocate(uint64_t cookie, uint64_t cost)
{
Hash hash = get_hash(cookie);
auto *hash_entry = hashmap.find(hash);
if (hash_entry)
{
total_cost += cost - hash_entry->get()->cost;
hash_entry->get()->cost = cost;
lru.move_to_front(lru, hash_entry->get());
return &hash_entry->get()->t;
}
total_cost += cost;
auto *entry = pool.allocate();
entry->cost = cost;
entry->hash = hash;
lru.insert_front(entry);
hashmap.emplace_replace(get_hash(cookie), lru.begin());
return &entry->t;
}
uint64_t prune()
{
uint64_t total_pruned = 0;
while (total_cost > total_cost_limit)
{
auto itr = lru.rbegin();
total_cost -= itr->cost;
total_pruned += itr->cost;
lru.erase(itr);
hashmap.erase(itr->hash);
pool.free(itr.get());
}
return total_pruned;
}
bool evict(uint64_t cookie)
{
auto *entry = hashmap.find(get_hash(cookie));
if (entry)
{
lru.move_to_back(lru, entry->get());
return true;
}
else
return false;
}
bool erase(uint64_t cookie)
{
auto *entry = hashmap.find(get_hash(cookie));
if (entry)
{
hashmap.erase(entry);
lru.erase(entry->get());
pool.free(entry->get().get());
return true;
}
else
return false;
}
~LRUCache()
{
while (!lru.empty())
{
auto itr = lru.begin();
lru.erase(itr);
pool.free(itr.get());
}
}
struct CacheEntry : IntrusiveListEnabled<CacheEntry>
{
uint64_t cost;
Hash hash;
T t;
};
typename IntrusiveList<CacheEntry>::Iterator begin()
{
return lru.begin();
}
typename IntrusiveList<CacheEntry>::Iterator end()
{
return lru.end();
}
private:
uint64_t total_cost = 0;
uint64_t total_cost_limit = 0;
ObjectPool<CacheEntry> pool;
IntrusiveList<CacheEntry> lru;
IntrusiveHashMap<IntrusivePODWrapper<typename IntrusiveList<CacheEntry>::Iterator>> hashmap;
static Hash get_hash(uint64_t cookie)
{
Hasher h;
h.u64(cookie);
return h.get();
}
};
}
@@ -0,0 +1,182 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "message_queue.hpp"
#include "aligned_alloc.hpp"
#include "logging.hpp"
#include <algorithm>
#include <stdlib.h>
namespace Util
{
void MessageQueuePayloadDeleter::operator()(void *ptr)
{
memalign_free(ptr);
}
LockFreeMessageQueue::LockFreeMessageQueue()
{
for (unsigned i = 0; i < 8; i++)
payload_capacity[i] = 256u << i;
for (unsigned i = 0; i < 8; i++)
write_ring[i].reset((16u * 1024u) >> i);
read_ring.reset(32 * 1024);
// Pre-fill the rings.
for (unsigned i = 0; i < 8; i++)
{
unsigned count = 512u >> i;
for (unsigned j = 0; j < count; j++)
{
MessageQueuePayload payload;
payload.set_payload_data(memalign_calloc(64, payload_capacity[i]), payload_capacity[i]);
recycle_payload(std::move(payload));
}
}
}
size_t LockFreeMessageQueue::available_read_messages() const noexcept
{
return read_ring.read_avail();
}
MessageQueuePayload LockFreeMessageQueue::read_message() noexcept
{
MessageQueuePayload payload;
read_ring.read_and_move(payload);
return payload;
}
bool LockFreeMessageQueue::push_written_payload(MessageQueuePayload payload) noexcept
{
return read_ring.write_and_move(std::move(payload));
}
void LockFreeMessageQueue::recycle_payload(MessageQueuePayload payload) noexcept
{
for (unsigned i = 0; i < 8; i++)
{
if (payload.get_capacity() == payload_capacity[i])
{
write_ring[i].write_and_move(std::move(payload));
return;
}
}
}
MessageQueuePayload LockFreeMessageQueue::allocate_write_payload(size_t size) noexcept
{
MessageQueuePayload payload;
for (unsigned i = 0; i < 8; i++)
{
if (size <= payload_capacity[i])
{
if (!write_ring[i].read_and_move(payload))
payload.set_payload_data(memalign_calloc(64, payload_capacity[i]), payload_capacity[i]);
return payload;
}
}
payload.set_payload_data(memalign_calloc(64, size), size);
return payload;
}
MessageQueue::MessageQueue()
{
corked.store(true);
}
void MessageQueue::cork()
{
corked.store(true, std::memory_order_relaxed);
}
void MessageQueue::uncork()
{
corked.store(false, std::memory_order_relaxed);
}
bool MessageQueue::is_uncorked() const
{
return !corked.load(std::memory_order_relaxed);
}
MessageQueuePayload MessageQueue::allocate_write_payload(size_t size) noexcept
{
if (corked.load(std::memory_order_relaxed))
return {};
std::lock_guard<std::mutex> holder{lock};
return LockFreeMessageQueue::allocate_write_payload(size);
}
bool MessageQueue::push_written_payload(MessageQueuePayload payload) noexcept
{
std::lock_guard<std::mutex> holder{lock};
return LockFreeMessageQueue::push_written_payload(std::move(payload));
}
size_t MessageQueue::available_read_messages() const noexcept
{
std::lock_guard<std::mutex> holder{lock};
return LockFreeMessageQueue::available_read_messages();
}
MessageQueuePayload MessageQueue::read_message() noexcept
{
std::lock_guard<std::mutex> holder{lock};
return LockFreeMessageQueue::read_message();
}
void MessageQueue::recycle_payload(MessageQueuePayload payload) noexcept
{
std::lock_guard<std::mutex> holder{lock};
return LockFreeMessageQueue::recycle_payload(std::move(payload));
}
bool MessageQueue::log(const char *tag, const char *fmt, va_list va)
{
if (!is_uncorked())
return false;
char message_buffer[16 * 1024];
memcpy(message_buffer, tag, strlen(tag));
vsnprintf(message_buffer + strlen(tag), sizeof(message_buffer) - strlen(tag), fmt, va);
va_end(va);
size_t message_size = strlen(message_buffer) + 1;
while (message_size >= 2 && message_buffer[message_size - 2] == '\n')
{
message_buffer[message_size - 2] = '\0';
message_size--;
}
auto message_payload = allocate_write_payload(message_size);
if (message_payload)
{
memcpy(static_cast<char *>(message_payload.get_payload_data()), message_buffer, message_size);
push_written_payload(std::move(message_payload));
}
return true;
}
}
@@ -0,0 +1,241 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <atomic>
#include <vector>
#include <utility>
#include <stddef.h>
#include <assert.h>
#include <memory>
#include <mutex>
#include <condition_variable>
#include "global_managers_interface.hpp"
#include "logging.hpp"
namespace Util
{
// There can only be one concurrent reader, and one concurrent writer.
// This is useful for lock-less messaging between two threads, e.g. a worker thread and master thread.
template <typename T>
class LockFreeRingBuffer
{
public:
LockFreeRingBuffer()
{
reset(1);
assert(read_count.is_lock_free());
assert(write_count.is_lock_free());
}
void reset(size_t count)
{
//assert((count & (count - 1)) == 0);
ring.resize(count);
read_count.store(0);
write_count.store(0);
}
size_t read_avail() const noexcept
{
return write_count.load(std::memory_order_acquire) -
read_count.load(std::memory_order_relaxed);
}
size_t write_avail() const noexcept
{
return ring.size() -
(write_count.load(std::memory_order_relaxed) -
read_count.load(std::memory_order_acquire));
}
bool write_and_move(T *values, size_t count) noexcept
{
size_t current_written = read_count.load(std::memory_order_relaxed);
size_t current_read = write_count.load(std::memory_order_acquire);
if (count > ring.size() - (current_written - current_read))
return false;
size_t can_write_first = std::min<size_t>(ring.size() - write_offset, count);
size_t can_write_second = count - can_write_first;
std::move(values, values + can_write_first, ring.data() + write_offset);
write_offset += can_write_first;
values += can_write_first;
if (write_offset >= ring.size())
write_offset -= ring.size();
std::move(values, values + can_write_second, ring.data());
write_offset += can_write_second;
// Need release ordering so the reads here can't be ordered after the store.
write_count.store(write_count.load(std::memory_order_relaxed) + count, std::memory_order_release);
return true;
}
bool read_and_move(T *values, size_t count) noexcept
{
size_t current_read = read_count.load(std::memory_order_relaxed);
size_t current_written = write_count.load(std::memory_order_acquire);
if (count > current_written - current_read)
return false;
size_t can_read_first = std::min<size_t>(ring.size() - read_offset, count);
size_t can_read_second = count - can_read_first;
std::move(ring.data() + read_offset, ring.data() + read_offset + can_read_first, values);
read_offset += can_read_first;
values += can_read_first;
if (read_offset >= ring.size())
read_offset -= ring.size();
std::move(ring.data(), ring.data() + can_read_second, values);
read_offset += can_read_second;
// Need release ordering so the reads here can't be ordered after the store.
read_count.store(read_count.load(std::memory_order_relaxed) + count, std::memory_order_release);
return true;
}
bool write_and_move(T value) noexcept
{
return write_and_move(&value, 1);
}
bool read_and_move(T &value) noexcept
{
return read_and_move(&value, 1);
}
private:
std::atomic_size_t read_count;
std::atomic_size_t write_count;
size_t read_offset = 0;
size_t write_offset = 0;
std::vector<T> ring;
};
struct MessageQueuePayloadDeleter
{
void operator()(void *ptr);
};
class MessageQueuePayload
{
public:
template <typename T>
T &as()
{
assert(handle);
return *static_cast<T *>(handle);
}
// The handle might be slightly different from payload if we allocated
// with multiple-inheritance and the base class we care about is not the first one in the inheritance list.
template <typename T>
void set_payload_handle(T *t)
{
handle = t;
}
explicit operator bool() const
{
return bool(payload);
}
size_t get_size() const
{
return payload_size;
}
void set_size(size_t size)
{
assert(size <= payload_capacity);
payload_size = size;
}
void set_payload_data(void *ptr, size_t size)
{
payload.reset(ptr);
payload_capacity = size;
}
void *get_payload_data() const
{
return payload.get();
}
size_t get_capacity() const
{
return payload_capacity;
}
private:
std::unique_ptr<void, MessageQueuePayloadDeleter> payload;
void *handle = nullptr;
size_t payload_size = 0;
size_t payload_capacity = 0;
};
class LockFreeMessageQueue
{
public:
LockFreeMessageQueue();
MessageQueuePayload allocate_write_payload(size_t size) noexcept;
bool push_written_payload(MessageQueuePayload payload) noexcept;
size_t available_read_messages() const noexcept;
MessageQueuePayload read_message() noexcept;
void recycle_payload(MessageQueuePayload payload) noexcept;
private:
LockFreeRingBuffer<MessageQueuePayload> read_ring;
LockFreeRingBuffer<MessageQueuePayload> write_ring[8];
size_t payload_capacity[8] = {};
};
class MessageQueue final : private LockFreeMessageQueue, public MessageQueueInterface
{
public:
MessageQueue();
void cork();
void uncork();
bool is_uncorked() const;
MessageQueuePayload allocate_write_payload(size_t size) noexcept;
bool push_written_payload(MessageQueuePayload payload) noexcept;
size_t available_read_messages() const noexcept;
MessageQueuePayload read_message() noexcept;
void recycle_payload(MessageQueuePayload payload) noexcept;
private:
mutable std::mutex lock;
mutable std::condition_variable cond;
std::atomic_bool corked;
bool log(const char *tag, const char *fmt, va_list va) override;
};
}
@@ -0,0 +1,42 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <type_traits>
namespace Util
{
template <typename T>
struct NoInitPOD
{
T value;
NoInitPOD()
{
static_assert(sizeof(NoInitPOD<T>) == sizeof(T), "Sizes do not match.");
static_assert(alignof(NoInitPOD<T>) == alignof(T), "Alignments do not match.");
}
static_assert(std::is_pod<T>::value, "Type is not POD.");
};
}
@@ -0,0 +1,132 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <memory>
#include <mutex>
#include <vector>
#include <algorithm>
#include <stdlib.h>
#include "aligned_alloc.hpp"
//#define OBJECT_POOL_DEBUG
namespace Util
{
template<typename T>
class ObjectPool
{
public:
template<typename... P>
T *allocate(P &&... p)
{
#ifndef OBJECT_POOL_DEBUG
if (vacants.empty())
{
unsigned num_objects = 64u << memory.size();
T *ptr = static_cast<T *>(memalign_alloc(std::max<size_t>(64, alignof(T)),
num_objects * sizeof(T)));
if (!ptr)
return nullptr;
for (unsigned i = 0; i < num_objects; i++)
vacants.push_back(&ptr[i]);
memory.emplace_back(ptr);
}
T *ptr = vacants.back();
vacants.pop_back();
new(ptr) T(std::forward<P>(p)...);
return ptr;
#else
return new T(std::forward<P>(p)...);
#endif
}
void free(T *ptr)
{
#ifndef OBJECT_POOL_DEBUG
ptr->~T();
vacants.push_back(ptr);
#else
delete ptr;
#endif
}
void clear()
{
#ifndef OBJECT_POOL_DEBUG
vacants.clear();
memory.clear();
#endif
}
protected:
#ifndef OBJECT_POOL_DEBUG
std::vector<T *> vacants;
struct MallocDeleter
{
void operator()(T *ptr)
{
memalign_free(ptr);
}
};
std::vector<std::unique_ptr<T, MallocDeleter>> memory;
#endif
};
template<typename T>
class ThreadSafeObjectPool : private ObjectPool<T>
{
public:
template<typename... P>
T *allocate(P &&... p)
{
std::lock_guard<std::mutex> holder{lock};
return ObjectPool<T>::allocate(std::forward<P>(p)...);
}
void free(T *ptr)
{
#ifndef OBJECT_POOL_DEBUG
ptr->~T();
std::lock_guard<std::mutex> holder{lock};
this->vacants.push_back(ptr);
#else
delete ptr;
#endif
}
void clear()
{
std::lock_guard<std::mutex> holder{lock};
ObjectPool<T>::clear();
}
private:
std::mutex lock;
};
}
@@ -0,0 +1,149 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "dynamic_array.hpp"
#include <memory>
namespace Util
{
template <int offset, int bits, typename ValueT, typename IndexT>
static inline void radix_sort_pass(ValueT * __restrict outputs, const ValueT * __restrict inputs,
IndexT * __restrict output_indices,
const IndexT * __restrict input_indices,
IndexT * __restrict scratch_indices,
size_t count)
{
constexpr int num_values = 1 << bits;
IndexT per_value_counts[num_values] = {};
for (size_t i = 0; i < count; i++)
{
ValueT c = (inputs[i] >> offset) & ((ValueT(1) << bits) - ValueT(1));
scratch_indices[i] = per_value_counts[c]++;
}
IndexT per_value_counts_prefix[num_values];
IndexT prefix_sum = 0;
for (int i = 0; i < num_values; i++)
{
per_value_counts_prefix[i] = prefix_sum;
prefix_sum += per_value_counts[i];
}
for (size_t i = 0; i < count; i++)
{
ValueT inp = inputs[i];
ValueT c = (inp >> offset) & ((ValueT(1) << bits) - ValueT(1));
IndexT effective_index = scratch_indices[i] + per_value_counts_prefix[c];
IndexT input_index = input_indices ? input_indices[i] : i;
output_indices[effective_index] = input_index;
outputs[effective_index] = inp;
}
}
template <typename CodeT, int... pattern>
class RadixSorter
{
public:
static_assert(sizeof...(pattern) % 2 == 0, "Need even number of radix passes.");
static_assert(sizeof...(pattern) > 0, "Need at least one radix pass.");
void resize(size_t count)
{
codes.reserve(count * 2);
indices.reserve(count * 3);
N = count;
}
void sort()
{
sort_inner_first<pattern...>();
}
size_t size() const
{
return N;
}
CodeT *code_data()
{
return codes.data();
}
const CodeT *code_data() const
{
return codes.data();
}
const uint32_t *indices_data() const
{
return indices.data();
}
private:
DynamicArray<CodeT> codes;
DynamicArray<uint32_t> indices;
size_t N = 0;
template <int offset>
void sort_inner(CodeT *, CodeT *, uint32_t *, uint32_t *, uint32_t *)
{
}
template <int offset, int count, int... counts>
void sort_inner(CodeT *output_values, CodeT *input_values,
uint32_t *output_indices, uint32_t *input_indices,
uint32_t *scratch_indices)
{
radix_sort_pass<offset, count>(output_values, input_values,
output_indices, input_indices,
scratch_indices, N);
sort_inner<offset + count, counts...>(input_values, output_values,
input_indices, output_indices,
scratch_indices);
}
template <int count, int... counts>
void sort_inner_first()
{
auto *output_values = codes.data();
auto *input_values = codes.data() + N;
auto *output_indices = indices.data();
auto *input_indices = indices.data() + 1 * N;
auto *scratch_indices = indices.data() + 2 * N;
radix_sort_pass<0, count>(input_values, output_values,
input_indices, static_cast<const uint32_t *>(nullptr),
scratch_indices, N);
sort_inner<count, counts...>(output_values, input_values,
output_indices, input_indices,
scratch_indices);
}
};
}
@@ -0,0 +1,149 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <atomic>
#ifdef __SSE2__
#include <emmintrin.h>
#endif
namespace Util
{
class RWSpinLock
{
public:
enum { Reader = 2, Writer = 1 };
RWSpinLock()
{
counter.store(0);
}
inline void lock_read()
{
unsigned v = counter.fetch_add(Reader, std::memory_order_acquire);
while ((v & Writer) != 0)
{
#ifdef __SSE2__
_mm_pause();
#endif
v = counter.load(std::memory_order_acquire);
}
}
inline bool try_lock_read()
{
unsigned v = counter.fetch_add(Reader, std::memory_order_acquire);
if ((v & Writer) != 0)
{
unlock_read();
return false;
}
return true;
}
inline void unlock_read()
{
counter.fetch_sub(Reader, std::memory_order_release);
}
inline void lock_write()
{
uint32_t expected = 0;
while (!counter.compare_exchange_weak(expected, Writer,
std::memory_order_acquire,
std::memory_order_relaxed))
{
#ifdef __SSE2__
_mm_pause();
#endif
expected = 0;
}
}
inline bool try_lock_write()
{
uint32_t expected = 0;
return counter.compare_exchange_strong(expected, Writer,
std::memory_order_acquire,
std::memory_order_relaxed);
}
inline void unlock_write()
{
counter.fetch_and(~Writer, std::memory_order_release);
}
inline void promote_reader_to_writer()
{
uint32_t expected = Reader;
if (!counter.compare_exchange_strong(expected, Writer,
std::memory_order_acquire,
std::memory_order_relaxed))
{
unlock_read();
lock_write();
}
}
private:
std::atomic_uint32_t counter;
};
class RWSpinLockReadHolder
{
public:
explicit RWSpinLockReadHolder(RWSpinLock &lock_)
: lock(lock_)
{
lock.lock_read();
}
~RWSpinLockReadHolder()
{
lock.unlock_read();
}
private:
RWSpinLock &lock;
};
class RWSpinLockWriteHolder
{
public:
explicit RWSpinLockWriteHolder(RWSpinLock &lock_)
: lock(lock_)
{
lock.lock_write();
}
~RWSpinLockWriteHolder()
{
lock.unlock_write();
}
private:
RWSpinLock &lock;
};
}
@@ -0,0 +1,58 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "slab_allocator.hpp"
namespace Util
{
SlabAllocator::SlabAllocator(size_t object_size_)
: object_size(object_size_)
{
}
uint8_t *SlabAllocator::allocate()
{
if (vacants.empty())
{
size_t count = size_t(64) << memory.size();
memory.emplace_back(static_cast<uint8_t *>(memalign_alloc(64, count * object_size)));
auto *ptr = memory.back().get();
vacants.reserve(vacants.size() + count);
for (size_t i = 0; i < count; i++, ptr += object_size)
vacants.push_back(ptr);
}
auto *v = vacants.back();
vacants.pop_back();
return v;
}
void SlabAllocator::free(uint8_t *ptr)
{
vacants.push_back(ptr);
}
void ThreadSafeSlabAllocator::init(size_t object_size)
{
slab = SlabAllocator(object_size);
}
}
@@ -0,0 +1,68 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <memory>
#include <vector>
#include <mutex>
#include "aligned_alloc.hpp"
namespace Util
{
class SlabAllocator
{
public:
SlabAllocator() = default;
explicit SlabAllocator(size_t object_size);
uint8_t *allocate();
void free(uint8_t *ptr);
private:
struct MallocDeleter
{
void operator()(uint8_t *ptr)
{
memalign_free(ptr);
}
};
std::vector<uint8_t *> vacants;
std::vector<std::unique_ptr<uint8_t, MallocDeleter>> memory;
size_t object_size = 0;
};
class ThreadSafeSlabAllocator
{
public:
ThreadSafeSlabAllocator() = default;
void init(size_t object_size);
inline uint8_t *allocate() { std::lock_guard<std::mutex> holder{lock}; return slab.allocate(); }
inline void free(uint8_t *ptr) { std::lock_guard<std::mutex> holder{lock}; slab.free(ptr); }
private:
SlabAllocator slab;
std::mutex lock;
};
}
@@ -0,0 +1,141 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stddef.h>
#include <type_traits>
#include <utility>
#include <functional>
namespace Util
{
template <typename Sig, size_t PayloadSize, size_t Align>
class SmallCallable;
template <typename R, typename... Params, size_t PayloadSize, size_t Align>
class SmallCallable<R (Params...), PayloadSize, Align>
{
public:
inline R call(Params... params)
{
return get_invokable().call(std::forward<Params>(params)...);
}
inline explicit operator bool() const
{
return get_invokable().active();
}
~SmallCallable()
{
get_invokable().~Invokable();
}
template <typename Func>
inline explicit SmallCallable(Func&& fn)
{
using PlainFunc = std::remove_cv_t<std::remove_reference_t<Func>>;
// Avoid mistaken double-wrap.
static_assert(!std::is_same<std::function<R (Params...)>, PlainFunc>::value, "std::function not supported.");
static_assert(sizeof(CapturedInvokable<PlainFunc>) <= sizeof(payload), "Callback payload is too large.");
new(payload) CapturedInvokable<PlainFunc>(std::forward<Func>(fn));
}
inline explicit SmallCallable(R (*fn)(Params...))
{
static_assert(sizeof(CapturedPlain) <= sizeof(payload), "Callback payload is too large.");
new(payload) CapturedPlain(fn);
}
template <typename T>
inline SmallCallable(T *ptr, R (T::*memb)(Params...))
{
using MemberFunc = CapturedMemberFunc<T>;
static_assert(sizeof(MemberFunc) <= sizeof(payload), "Callback payload is too large.");
new(payload) MemberFunc(ptr, memb);
}
inline SmallCallable()
{
new(payload) NullInvoker();
}
SmallCallable(const SmallCallable &) = delete;
SmallCallable(SmallCallable &&) = delete;
void operator=(const SmallCallable &) = delete;
void operator=(SmallCallable &&) = delete;
private:
struct Invokable
{
virtual ~Invokable() = default;
virtual R call(Params...) = 0;
virtual bool active() const = 0;
};
template <typename I>
struct CapturedInvokable final : Invokable
{
explicit CapturedInvokable(I&& holder_) : holder(std::move(holder_)) {}
R call(Params... params) override { return holder(std::forward<Params>(params)...); };
bool active() const override { return true; }
I holder;
};
struct NullInvoker final : Invokable
{
R call(Params...) override { return R(); }
bool active() const override { return false; }
};
struct CapturedPlain final : Invokable
{
explicit CapturedPlain(R (*func_)(Params...)) : func(func_) {}
R call(Params... params) override { return func(std::forward<Params>(params)...); }
bool active() const override { return func != nullptr; }
R (*func)(Params...);
};
template <typename T>
struct CapturedMemberFunc final : Invokable
{
explicit CapturedMemberFunc(T *ptr_, R (T::*func_)(Params...)) : ptr(ptr_), func(func_) {}
R call(Params... params) override { return (ptr->*func)(std::forward<Params>(params)...); }
bool active() const override { return ptr != nullptr && func != nullptr; }
T *ptr;
R (T::*func)(Params...);
};
Invokable &get_invokable()
{
return *reinterpret_cast<Invokable *>(&payload[0]);
}
const Invokable &get_invokable() const
{
return *reinterpret_cast<const Invokable *>(&payload[0]);
}
alignas(Align) char payload[PayloadSize];
};
}
@@ -0,0 +1,456 @@
/* Copyright (c) 2019-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stddef.h>
#include <stdlib.h>
#include <utility>
#include <exception>
#include <algorithm>
#include <initializer_list>
namespace Util
{
// std::aligned_storage does not support size == 0, so roll our own.
template <typename T, size_t N>
class AlignedBuffer
{
public:
T *data()
{
return reinterpret_cast<T *>(aligned_char);
}
private:
alignas(T) char aligned_char[sizeof(T) * N];
};
template <typename T>
class AlignedBuffer<T, 0>
{
public:
T *data()
{
return nullptr;
}
};
// An immutable version of SmallVector which erases type information about storage.
template <typename T>
class VectorView
{
public:
T &operator[](size_t i)
{
return ptr[i];
}
const T &operator[](size_t i) const
{
return ptr[i];
}
bool empty() const
{
return buffer_size == 0;
}
size_t size() const
{
return buffer_size;
}
T *data()
{
return ptr;
}
const T *data() const
{
return ptr;
}
T *begin()
{
return ptr;
}
T *end()
{
return ptr + buffer_size;
}
const T *begin() const
{
return ptr;
}
const T *end() const
{
return ptr + buffer_size;
}
T &front()
{
return ptr[0];
}
const T &front() const
{
return ptr[0];
}
T &back()
{
return ptr[buffer_size - 1];
}
const T &back() const
{
return ptr[buffer_size - 1];
}
// Avoid sliced copies. Base class should only be read as a reference.
VectorView(const VectorView &) = delete;
void operator=(const VectorView &) = delete;
protected:
VectorView() = default;
T *ptr = nullptr;
size_t buffer_size = 0;
};
// Simple vector which supports up to N elements inline, without malloc/free.
// We use a lot of throwaway vectors all over the place which triggers allocations.
// This class only implements the subset of std::vector we need in SPIRV-Cross.
// It is *NOT* a drop-in replacement in general projects.
template <typename T, size_t N = 8>
class SmallVector : public VectorView<T>
{
public:
SmallVector()
{
this->ptr = stack_storage.data();
buffer_capacity = N;
}
SmallVector(const T *arg_list_begin, const T *arg_list_end)
: SmallVector()
{
auto count = size_t(arg_list_end - arg_list_begin);
reserve(count);
for (size_t i = 0; i < count; i++, arg_list_begin++)
new (&this->ptr[i]) T(*arg_list_begin);
this->buffer_size = count;
}
SmallVector(SmallVector &&other) noexcept : SmallVector()
{
*this = std::move(other);
}
SmallVector(const std::initializer_list<T> &init_list) : SmallVector()
{
insert(this->end(), init_list.begin(), init_list.end());
}
SmallVector &operator=(SmallVector &&other) noexcept
{
clear();
if (other.ptr != other.stack_storage.data())
{
// Pilfer allocated pointer.
if (this->ptr != stack_storage.data())
free(this->ptr);
this->ptr = other.ptr;
this->buffer_size = other.buffer_size;
buffer_capacity = other.buffer_capacity;
other.ptr = nullptr;
other.buffer_size = 0;
other.buffer_capacity = 0;
}
else
{
// Need to move the stack contents individually.
reserve(other.buffer_size);
for (size_t i = 0; i < other.buffer_size; i++)
{
new (&this->ptr[i]) T(std::move(other.ptr[i]));
other.ptr[i].~T();
}
this->buffer_size = other.buffer_size;
other.buffer_size = 0;
}
return *this;
}
SmallVector(const SmallVector &other)
: SmallVector()
{
*this = other;
}
SmallVector &operator=(const SmallVector &other)
{
clear();
reserve(other.buffer_size);
for (size_t i = 0; i < other.buffer_size; i++)
new (&this->ptr[i]) T(other.ptr[i]);
this->buffer_size = other.buffer_size;
return *this;
}
explicit SmallVector(size_t count)
: SmallVector()
{
resize(count);
}
~SmallVector()
{
clear();
if (this->ptr != stack_storage.data())
free(this->ptr);
}
void clear()
{
for (size_t i = 0; i < this->buffer_size; i++)
this->ptr[i].~T();
this->buffer_size = 0;
}
void push_back(const T &t)
{
reserve(this->buffer_size + 1);
new (&this->ptr[this->buffer_size]) T(t);
this->buffer_size++;
}
void push_back(T &&t)
{
reserve(this->buffer_size + 1);
new (&this->ptr[this->buffer_size]) T(std::move(t));
this->buffer_size++;
}
void pop_back()
{
// Work around false positive warning on GCC 8.3.
// Calling pop_back on empty vector is undefined.
if (!this->empty())
resize(this->buffer_size - 1);
}
template <typename... Ts>
void emplace_back(Ts &&... ts)
{
reserve(this->buffer_size + 1);
new (&this->ptr[this->buffer_size]) T(std::forward<Ts>(ts)...);
this->buffer_size++;
}
void reserve(size_t count)
{
if (count > buffer_capacity)
{
size_t target_capacity = buffer_capacity;
if (target_capacity == 0)
target_capacity = 1;
if (target_capacity < N)
target_capacity = N;
while (target_capacity < count)
target_capacity <<= 1u;
T *new_buffer =
target_capacity > N ? static_cast<T *>(malloc(target_capacity * sizeof(T))) : stack_storage.data();
if (!new_buffer)
std::terminate();
// In case for some reason two allocations both come from same stack.
if (new_buffer != this->ptr)
{
// We don't deal with types which can throw in move constructor.
for (size_t i = 0; i < this->buffer_size; i++)
{
new (&new_buffer[i]) T(std::move(this->ptr[i]));
this->ptr[i].~T();
}
}
if (this->ptr != stack_storage.data())
free(this->ptr);
this->ptr = new_buffer;
buffer_capacity = target_capacity;
}
}
void insert(T *itr, const T *insert_begin, const T *insert_end)
{
auto count = size_t(insert_end - insert_begin);
if (itr == this->end())
{
reserve(this->buffer_size + count);
for (size_t i = 0; i < count; i++, insert_begin++)
new (&this->ptr[this->buffer_size + i]) T(*insert_begin);
this->buffer_size += count;
}
else
{
if (this->buffer_size + count > buffer_capacity)
{
auto target_capacity = this->buffer_size + count;
if (target_capacity == 0)
target_capacity = 1;
if (target_capacity < N)
target_capacity = N;
while (target_capacity < count)
target_capacity <<= 1u;
// Need to allocate new buffer. Move everything to a new buffer.
T *new_buffer =
target_capacity > N ? static_cast<T *>(malloc(target_capacity * sizeof(T))) : stack_storage.data();
if (!new_buffer)
std::terminate();
// First, move elements from source buffer to new buffer.
// We don't deal with types which can throw in move constructor.
auto *target_itr = new_buffer;
auto *original_source_itr = this->begin();
if (new_buffer != this->ptr)
{
while (original_source_itr != itr)
{
new (target_itr) T(std::move(*original_source_itr));
original_source_itr->~T();
++original_source_itr;
++target_itr;
}
}
// Copy-construct new elements.
for (auto *source_itr = insert_begin; source_itr != insert_end; ++source_itr, ++target_itr)
new (target_itr) T(*source_itr);
// Move over the other half.
if (new_buffer != this->ptr || insert_begin != insert_end)
{
while (original_source_itr != this->end())
{
new (target_itr) T(std::move(*original_source_itr));
original_source_itr->~T();
++original_source_itr;
++target_itr;
}
}
if (this->ptr != stack_storage.data())
free(this->ptr);
this->ptr = new_buffer;
buffer_capacity = target_capacity;
}
else
{
// Move in place, need to be a bit careful about which elements are constructed and which are not.
// Move the end and construct the new elements.
auto *target_itr = this->end() + count;
auto *source_itr = this->end();
while (target_itr != this->end() && source_itr != itr)
{
--target_itr;
--source_itr;
new (target_itr) T(std::move(*source_itr));
}
// For already constructed elements we can move-assign.
std::move_backward(itr, source_itr, target_itr);
// For the inserts which go to already constructed elements, we can do a plain copy.
while (itr != this->end() && insert_begin != insert_end)
*itr++ = *insert_begin++;
// For inserts into newly allocated memory, we must copy-construct instead.
while (insert_begin != insert_end)
{
new (itr) T(*insert_begin);
++itr;
++insert_begin;
}
}
this->buffer_size += count;
}
}
void insert(T *itr, const T &value)
{
insert(itr, &value, &value + 1);
}
T *erase(T *itr)
{
std::move(itr + 1, this->end(), itr);
this->ptr[--this->buffer_size].~T();
return itr;
}
void erase(T *start_erase, T *end_erase)
{
if (end_erase == this->end())
{
resize(size_t(start_erase - this->begin()));
}
else
{
auto new_size = this->buffer_size - (end_erase - start_erase);
std::move(end_erase, this->end(), start_erase);
resize(new_size);
}
}
void resize(size_t new_size)
{
if (new_size < this->buffer_size)
{
for (size_t i = new_size; i < this->buffer_size; i++)
this->ptr[i].~T();
}
else if (new_size > this->buffer_size)
{
reserve(new_size);
for (size_t i = this->buffer_size; i < new_size; i++)
new (&this->ptr[i]) T();
}
this->buffer_size = new_size;
}
private:
size_t buffer_capacity = 0;
AlignedBuffer<T, N> stack_storage;
};
}
@@ -0,0 +1,62 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <algorithm>
namespace Util
{
template <typename T, size_t N>
class StackAllocator
{
public:
T *allocate(size_t count)
{
if (count == 0)
return nullptr;
if (offset + count > N)
return nullptr;
T *ret = buffer + offset;
offset += count;
return ret;
}
T *allocate_cleared(size_t count)
{
T *ret = allocate(count);
if (ret)
std::fill(ret, ret + count, T());
return ret;
}
void reset()
{
offset = 0;
}
private:
T buffer[N];
size_t offset = 0;
};
}
@@ -0,0 +1,73 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "string_helpers.hpp"
namespace Util
{
static std::vector<std::string> split(const std::string &str, const char *delim, bool allow_empty)
{
if (str.empty())
return {};
std::vector<std::string> ret;
size_t start_index = 0;
size_t index = 0;
while ((index = str.find_first_of(delim, start_index)) != std::string::npos)
{
if (allow_empty || index > start_index)
ret.push_back(str.substr(start_index, index - start_index));
start_index = index + 1;
if (allow_empty && (index == str.size() - 1))
ret.emplace_back();
}
if (start_index < str.size())
ret.push_back(str.substr(start_index));
return ret;
}
std::vector<std::string> split(const std::string &str, const char *delim)
{
return split(str, delim, true);
}
std::vector<std::string> split_no_empty(const std::string &str, const char *delim)
{
return split(str, delim, false);
}
std::string strip_whitespace(const std::string &str)
{
std::string ret;
auto index = str.find_first_not_of(" \t");
if (index == std::string::npos)
return "";
ret = str.substr(index, std::string::npos);
index = ret.find_last_not_of(" \t");
if (index != std::string::npos)
return ret.substr(0, index + 1);
else
return ret;
}
}
@@ -0,0 +1,59 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <string>
#include <sstream>
#include <vector>
#include <type_traits>
namespace inner
{
template<typename T>
void join_helper(std::ostringstream &stream, T &&t)
{
stream << std::forward<T>(t);
}
template<typename T, typename... Ts>
void join_helper(std::ostringstream &stream, T &&t, Ts &&... ts)
{
stream << std::forward<T>(t);
join_helper(stream, std::forward<Ts>(ts)...);
}
}
namespace Util
{
template<typename... Ts>
inline std::string join(Ts &&... ts)
{
std::ostringstream stream;
inner::join_helper(stream, std::forward<Ts>(ts)...);
return stream.str();
}
std::vector<std::string> split(const std::string &str, const char *delim);
std::vector<std::string> split_no_empty(const std::string &str, const char *delim);
std::string strip_whitespace(const std::string &str);
}
@@ -0,0 +1,177 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "hash.hpp"
#include "object_pool.hpp"
#include "intrusive_list.hpp"
#include "intrusive_hash_map.hpp"
#include <vector>
namespace Util
{
template <typename T>
class TemporaryHashmapEnabled
{
public:
void set_hash(Hash hash_)
{
hash = hash_;
}
void set_index(unsigned index_)
{
index = index_;
}
Hash get_hash()
{
return hash;
}
unsigned get_index() const
{
return index;
}
private:
Hash hash = 0;
unsigned index = 0;
};
template <typename T, unsigned RingSize = 4, bool ReuseObjects = false>
class TemporaryHashmap
{
public:
~TemporaryHashmap()
{
clear();
}
void clear()
{
for (auto &ring : rings)
{
while (!ring.empty())
{
auto itr = ring.begin();
ring.erase(itr);
auto &node = *itr;
object_pool.free(static_cast<T *>(&node));
}
}
hashmap.clear();
for (auto &vacant : vacants)
object_pool.free(static_cast<T *>(&*vacant));
vacants.clear();
object_pool.clear();
}
void begin_frame()
{
index = (index + 1) & (RingSize - 1);
auto &ring = rings[index];
while (!ring.empty())
{
auto itr = ring.begin();
ring.erase(itr);
auto &node = *itr;
hashmap.erase(node.get_hash());
free_object(&node, ReuseTag<ReuseObjects>());
}
}
T *request(Hash hash)
{
auto *v = hashmap.find(hash);
if (v)
{
auto node = v->get();
if (node->get_index() != index)
{
rings[index].move_to_front(rings[node->get_index()], node);
node->set_index(index);
}
return &*node;
}
else
return nullptr;
}
template <typename... P>
void make_vacant(P &&... p)
{
vacants.push_back(object_pool.allocate(std::forward<P>(p)...));
}
T *request_vacant(Hash hash)
{
if (vacants.empty())
return nullptr;
auto top = vacants.back();
vacants.pop_back();
top->set_index(index);
top->set_hash(hash);
hashmap.emplace_replace(hash, top);
rings[index].insert_front(top);
return &*top;
}
template <typename... P>
T *emplace(Hash hash, P &&... p)
{
auto *node = object_pool.allocate(std::forward<P>(p)...);
node->set_index(index);
node->set_hash(hash);
hashmap.emplace_replace(hash, node);
rings[index].insert_front(node);
return node;
}
private:
IntrusiveList<T> rings[RingSize];
ObjectPool<T> object_pool;
unsigned index = 0;
IntrusiveHashMap<IntrusivePODWrapper<typename IntrusiveList<T>::Iterator>> hashmap;
std::vector<typename IntrusiveList<T>::Iterator> vacants;
template <bool reuse>
struct ReuseTag
{
};
void free_object(T *object, const ReuseTag<false> &)
{
object_pool.free(object);
}
void free_object(T *object, const ReuseTag<true> &)
{
vacants.push_back(object);
}
};
}
@@ -0,0 +1,45 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "thread_id.hpp"
#include "logging.hpp"
namespace Util
{
static thread_local unsigned thread_id_to_index = ~0u;
unsigned get_current_thread_index()
{
auto ret = thread_id_to_index;
if (ret == ~0u)
{
LOGE("Thread does not exist in thread manager or is not the main thread.\n");
return 0;
}
return ret;
}
void register_thread_index(unsigned index)
{
thread_id_to_index = index;
}
}
@@ -0,0 +1,29 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
namespace Util
{
unsigned get_current_thread_index();
void register_thread_index(unsigned thread_index);
}
@@ -0,0 +1,59 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "thread_name.hpp"
#if !defined(_WIN32)
#include <pthread.h>
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string>
#endif
namespace Util
{
void set_current_thread_name(const char *name)
{
#if defined(__linux__)
pthread_setname_np(pthread_self(), name);
#elif defined(__APPLE__)
pthread_setname_np(name);
#elif defined(_WIN32)
using PFN_SetThreadDescription = HRESULT (WINAPI *)(HANDLE, PCWSTR);
auto module = GetModuleHandleA("kernel32.dll");
PFN_SetThreadDescription SetThreadDescription = module ? reinterpret_cast<PFN_SetThreadDescription>(
(void *)GetProcAddress(module, "SetThreadDescription")) : nullptr;
if (SetThreadDescription)
{
std::wstring wname;
while (*name != '\0')
{
wname.push_back(*name);
name++;
}
SetThreadDescription(GetCurrentThread(), wname.c_str());
}
#endif
}
}
@@ -0,0 +1,28 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
namespace Util
{
void set_current_thread_name(const char *name);
}
@@ -0,0 +1,67 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "thread_priority.hpp"
#include "logging.hpp"
#if defined(__linux__)
#include <pthread.h>
#elif defined(_WIN32)
#include <windows.h>
#endif
namespace Util
{
void set_current_thread_priority(ThreadPriority priority)
{
#if defined(__linux__)
if (priority == ThreadPriority::Low)
{
struct sched_param param = {};
int policy = 0;
param.sched_priority = sched_get_priority_min(SCHED_BATCH);
policy = SCHED_BATCH;
if (pthread_setschedparam(pthread_self(), policy, &param) != 0)
LOGE("Failed to set thread priority.\n");
}
#elif defined(_WIN32)
if (priority == ThreadPriority::Low)
{
if (!SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN))
LOGE("Failed to set background thread priority.\n");
}
else if (priority == ThreadPriority::Default)
{
if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL))
LOGE("Failed to set normal thread priority.\n");
}
else if (priority == ThreadPriority::High)
{
if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST))
LOGE("Failed to set high thread priority.\n");
}
#else
#warning "Unimplemented set_current_thread_priority."
(void)priority;
#endif
}
}
@@ -0,0 +1,35 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
namespace Util
{
enum class ThreadPriority
{
High,
Default,
Low
};
void set_current_thread_priority(ThreadPriority priority);
}
@@ -0,0 +1,185 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "logging.hpp"
#include "timeline_trace_file.hpp"
#include "thread_name.hpp"
#include "timer.hpp"
#include <string.h>
#include <stdio.h>
namespace Util
{
static thread_local char trace_tid[32];
static thread_local TimelineTraceFile *trace_file;
void TimelineTraceFile::set_tid(const char *tid)
{
snprintf(trace_tid, sizeof(trace_tid), "%s", tid);
}
void TimelineTraceFile::set_per_thread(TimelineTraceFile *file)
{
trace_file = file;
}
TimelineTraceFile *TimelineTraceFile::get_per_thread()
{
return trace_file;
}
void TimelineTraceFile::Event::set_desc(const char *desc_)
{
snprintf(desc, sizeof(desc), "%s", desc_);
}
void TimelineTraceFile::Event::set_tid(const char *tid_)
{
snprintf(tid, sizeof(tid), "%s", tid_);
}
TimelineTraceFile::Event *TimelineTraceFile::begin_event(const char *desc, uint32_t pid)
{
auto *e = event_pool.allocate();
e->pid = pid;
e->set_tid(trace_tid);
e->set_desc(desc);
e->start_ns = get_current_time_nsecs();
return e;
}
TimelineTraceFile::Event *TimelineTraceFile::allocate_event()
{
auto *e = event_pool.allocate();
e->desc[0] = '\0';
e->tid[0] = '\0';
e->pid = 0;
e->start_ns = 0;
e->end_ns = 0;
return e;
}
void TimelineTraceFile::submit_event(Event *e)
{
std::lock_guard<std::mutex> holder{lock};
queued_events.push(e);
cond.notify_one();
}
void TimelineTraceFile::end_event(Event *e)
{
e->end_ns = get_current_time_nsecs();
submit_event(e);
}
TimelineTraceFile::TimelineTraceFile(const std::string &path)
{
thr = std::thread(&TimelineTraceFile::looper, this, path);
}
void TimelineTraceFile::looper(std::string path)
{
set_current_thread_name("json-trace-io");
FILE *file = fopen(path.c_str(), "w");
if (!file)
LOGE("Failed to open file: %s.\n", path.c_str());
if (file)
fputs("[\n", file);
uint64_t base_ts = get_current_time_nsecs();
for (;;)
{
Event *e;
{
std::unique_lock<std::mutex> holder{lock};
cond.wait(holder, [this]() {
return !queued_events.empty();
});
e = queued_events.front();
queued_events.pop();
}
if (!e)
break;
auto start_us = int64_t(e->start_ns - base_ts) * 1e-3;
auto end_us = int64_t(e->end_ns - base_ts) * 1e-3;
if (file && start_us <= end_us)
{
fprintf(file, "{ \"name\": \"%s\", \"ph\": \"B\", \"tid\": \"%s\", \"pid\": \"%u\", \"ts\": %f },\n",
e->desc, e->tid, e->pid, start_us);
fprintf(file, "{ \"name\": \"%s\", \"ph\": \"E\", \"tid\": \"%s\", \"pid\": \"%u\", \"ts\": %f },\n",
e->desc, e->tid, e->pid, end_us);
}
event_pool.free(e);
}
// Intentionally truncate the JSON so that we can emit "," after the last element.
if (file)
fclose(file);
}
TimelineTraceFile::~TimelineTraceFile()
{
submit_event(nullptr);
if (thr.joinable())
thr.join();
}
TimelineTraceFile::ScopedEvent::ScopedEvent(TimelineTraceFile *file_, const char *tag, uint32_t pid)
: file(file_)
{
if (file && tag && *tag != '\0')
event = file->begin_event(tag, pid);
}
TimelineTraceFile::ScopedEvent::~ScopedEvent()
{
if (event)
file->end_event(event);
}
TimelineTraceFile::ScopedEvent &
TimelineTraceFile::ScopedEvent::operator=(TimelineTraceFile::ScopedEvent &&other) noexcept
{
if (this != &other)
{
if (event)
file->end_event(event);
event = other.event;
file = other.file;
other.event = nullptr;
other.file = nullptr;
}
return *this;
}
TimelineTraceFile::ScopedEvent::ScopedEvent(TimelineTraceFile::ScopedEvent &&other) noexcept
{
*this = std::move(other);
}
}
@@ -0,0 +1,96 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <string>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <memory>
#include <queue>
#include "object_pool.hpp"
namespace Util
{
class TimelineTraceFile
{
public:
explicit TimelineTraceFile(const std::string &path);
~TimelineTraceFile();
static void set_tid(const char *tid);
static TimelineTraceFile *get_per_thread();
static void set_per_thread(TimelineTraceFile *file);
struct Event
{
char desc[256];
char tid[32];
uint32_t pid;
uint64_t start_ns, end_ns;
void set_desc(const char *desc);
void set_tid(const char *tid);
};
Event *begin_event(const char *desc, uint32_t pid = 0);
void end_event(Event *e);
Event *allocate_event();
void submit_event(Event *e);
struct ScopedEvent
{
ScopedEvent(TimelineTraceFile *file, const char *tag, uint32_t pid = 0);
ScopedEvent() = default;
~ScopedEvent();
void operator=(const ScopedEvent &) = delete;
ScopedEvent(const ScopedEvent &) = delete;
ScopedEvent(ScopedEvent &&other) noexcept;
ScopedEvent &operator=(ScopedEvent &&other) noexcept;
TimelineTraceFile *file = nullptr;
Event *event = nullptr;
};
private:
void looper(std::string path);
std::thread thr;
std::mutex lock;
std::condition_variable cond;
ThreadSafeObjectPool<Event> event_pool;
std::queue<Event *> queued_events;
};
#ifndef GRANITE_SHIPPING
#define GRANITE_MACRO_CONCAT_IMPL(a, b) a##b
#define GRANITE_MACRO_CONCAT(a, b) GRANITE_MACRO_CONCAT_IMPL(a, b)
#define GRANITE_SCOPED_TIMELINE_EVENT(str) \
::Util::TimelineTraceFile::ScopedEvent GRANITE_MACRO_CONCAT(_timeline_scoped_count_, __COUNTER__){GRANITE_THREAD_GROUP() ? GRANITE_THREAD_GROUP()->get_timeline_trace_file() : nullptr, str}
#define GRANITE_SCOPED_TIMELINE_EVENT_FILE(file, str) \
::Util::TimelineTraceFile::ScopedEvent GRANITE_MACRO_CONCAT(_timeline_scoped_count_, __COUNTER__){file, str}
#else
#define GRANITE_SCOPED_TIMELINE_EVENT(...) ((void)0)
#define GRANITE_SCOPED_TIMELINE_EVENT_FILE(...) ((void)0)
#endif
}
@@ -0,0 +1,162 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "timer.hpp"
#include "logging.hpp"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <time.h>
#include <errno.h>
#endif
#ifdef __SSE2__
#include <emmintrin.h>
#endif
namespace Util
{
FrameTimer::FrameTimer()
{
reset();
}
void FrameTimer::reset()
{
start = get_time();
last = start;
last_period = 0;
}
void FrameTimer::enter_idle()
{
idle_start = get_time();
}
void FrameTimer::leave_idle()
{
auto idle_end = get_time();
idle_time += idle_end - idle_start;
}
double FrameTimer::get_frame_time() const
{
return double(last_period) * 1e-9;
}
double FrameTimer::frame()
{
auto new_time = get_time() - idle_time;
last_period = new_time - last;
last = new_time;
return double(last_period) * 1e-9;
}
double FrameTimer::frame(double frame_time)
{
last_period = int64_t(frame_time * 1e9);
last += last_period;
return frame_time;
}
double FrameTimer::get_elapsed() const
{
return double(last - start) * 1e-9;
}
int64_t FrameTimer::get_time()
{
return get_current_time_nsecs();
}
#ifdef _WIN32
struct QPCFreq
{
QPCFreq()
{
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
inv_freq = 1e9 / double(freq.QuadPart);
}
double inv_freq;
} static static_qpc_freq;
#endif
int64_t get_current_time_nsecs()
{
#ifdef _WIN32
LARGE_INTEGER li;
if (!QueryPerformanceCounter(&li))
return 0;
return int64_t(double(li.QuadPart) * static_qpc_freq.inv_freq);
#else
struct timespec ts = {};
constexpr auto timebase = CLOCK_MONOTONIC;
if (clock_gettime(timebase, &ts) < 0)
return 0;
return ts.tv_sec * 1000000000ll + ts.tv_nsec;
#endif
}
void sleep_until_nsecs(int64_t timepoint)
{
#ifdef _WIN32
// Somewhat naive path. Improve this later.
int64_t d = get_current_time_nsecs() - timepoint;
if (d <= 0)
return;
// Assumes timer resolution has been set.
Sleep(d / 1000000);
// Spin the rest of the way.
while (get_current_time_nsecs() < timepoint)
{
#ifdef __SSE2__
_mm_pause();
#endif
}
#else
constexpr auto timebase = CLOCK_MONOTONIC;
struct timespec ts = {};
ts.tv_sec = timepoint / 1000000000ll;
ts.tv_nsec = timepoint % 1000000000ll;
// Linux does not support clock_nanosleep with MONOTONIC_RAW :(
int ret;
while ((ret = clock_nanosleep(timebase, TIMER_ABSTIME, &ts, nullptr)) == EINTR) {}
#endif
}
void Timer::start()
{
t = get_current_time_nsecs();
}
double Timer::end()
{
auto nt = get_current_time_nsecs();
return double(nt - t) * 1e-9;
}
}
@@ -0,0 +1,64 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stdint.h>
namespace Util
{
class FrameTimer
{
public:
FrameTimer();
void reset();
double frame();
double frame(double frame_time);
double get_elapsed() const;
double get_frame_time() const;
void enter_idle();
void leave_idle();
private:
int64_t start;
int64_t last;
int64_t last_period;
int64_t idle_start;
int64_t idle_time = 0;
int64_t get_time();
};
class Timer
{
public:
void start();
double end();
private:
int64_t t = 0;
};
int64_t get_current_time_nsecs();
void sleep_until_nsecs(int64_t timepoint);
}
@@ -0,0 +1,115 @@
/* Copyright (c) 2021-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <vector>
#include <type_traits>
#include <utility>
#include <stddef.h>
#include <assert.h>
namespace Util
{
struct IntrusiveUnorderedArrayEnabled
{
size_t unordered_array_offset;
};
// A special kind of vector where we only care about contiguous storage, not relative order between elements.
// This allows for O(1) removal. The stored type needs to store its own array offset.
template <typename T>
class IntrusiveUnorderedArray
{
public:
static_assert(std::is_base_of<IntrusiveUnorderedArrayEnabled, T>::value,
"T is not derived from IntrusiveUnorderedArrayEnabled.");
void add(T *t)
{
t->unordered_array_offset = ts.size();
ts.push_back(t);
}
void erase(T *t)
{
erase_offset(t->unordered_array_offset);
}
typename std::vector<T *>::const_iterator begin() const
{
return ts.begin();
}
typename std::vector<T *>::const_iterator end() const
{
return ts.end();
}
size_t size() const
{
return ts.size();
}
void clear()
{
ts.clear();
}
// If functor returns true, the pointer can be freed in the callback.
// Implementation must not dereference the pointer if true is returned.
template <typename Func>
void garbage_collect_if(const Func &func)
{
auto begin_itr = begin();
auto end_itr = end();
while (begin_itr != end_itr)
{
size_t offset = (*begin_itr)->unordered_array_offset;
if (func(*begin_itr))
{
--end_itr;
erase_offset(offset);
}
else
{
++begin_itr;
}
}
}
private:
std::vector<T *> ts;
void erase_offset(size_t offset)
{
assert(offset < ts.size());
auto &current = ts[offset];
if (&current != &ts.back())
{
std::swap(current, ts.back());
current->unordered_array_offset = offset;
}
ts.pop_back();
}
};
}
@@ -0,0 +1,45 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <utility>
namespace Util
{
template <typename BidirectionalItr, typename UnaryPredicate>
BidirectionalItr unstable_remove_if(BidirectionalItr first, BidirectionalItr last, UnaryPredicate &&p)
{
while (first != last)
{
if (p(*first))
{
--last;
std::swap(*first, *last);
}
else
++first;
}
return first;
}
}
@@ -0,0 +1,77 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <memory>
namespace Granite
{
class Variant
{
public:
Variant() = default;
template <typename T>
explicit Variant(T &&t)
{
set(std::forward<T>(t));
}
template <typename T>
void set(T &&t)
{
value = std::make_shared<HolderValue<T>>(std::forward<T>(t));
}
template <typename T>
T &get()
{
return static_cast<HolderValue<T> *>(value.get())->value;
}
template <typename T>
const T &get() const
{
return static_cast<const HolderValue<T> *>(value.get())->value;
}
private:
struct Holder
{
virtual ~Holder() = default;
};
template <typename U>
struct HolderValue : Holder
{
template <typename P>
HolderValue(P &&p)
: value(std::forward<P>(p))
{
}
U value;
};
std::shared_ptr<Holder> value;
};
}