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:
@@ -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, ¶m) != 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 ¤t = ts[offset];
|
||||
if (¤t != &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;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user