initial work to split into dart_filament and flutter_filament

This commit is contained in:
Nick Fisher
2024-04-30 12:07:26 +08:00
parent b81f34cd29
commit 8f9e309c34
1624 changed files with 165260 additions and 6619 deletions

View File

@@ -0,0 +1,840 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ALLOCATOR_H
#define TNT_UTILS_ALLOCATOR_H
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/memalign.h>
#include <utils/Mutex.h>
#include <utils/SpinLock.h>
#include <atomic>
#include <cstddef>
#include <mutex>
#include <type_traits>
#include <assert.h>
#include <stdlib.h>
namespace utils {
namespace pointermath {
template <typename P, typename T>
static inline P* add(P* a, T b) noexcept {
return (P*)(uintptr_t(a) + uintptr_t(b));
}
template <typename P>
static inline P* align(P* p, size_t alignment) noexcept {
// alignment must be a power-of-two
assert(alignment && !(alignment & alignment-1));
return (P*)((uintptr_t(p) + alignment - 1) & ~(alignment - 1));
}
template <typename P>
static inline P* align(P* p, size_t alignment, size_t offset) noexcept {
P* const r = align(add(p, offset), alignment);
assert(r >= add(p, offset));
return r;
}
}
/* ------------------------------------------------------------------------------------------------
* LinearAllocator
*
* + Allocates blocks linearly
* + Cannot free individual blocks
* + Can free top of memory back up to a specified point
* + Doesn't call destructors
* ------------------------------------------------------------------------------------------------
*/
class LinearAllocator {
public:
// use memory area provided
LinearAllocator(void* begin, void* end) noexcept;
template <typename AREA>
explicit LinearAllocator(const AREA& area) : LinearAllocator(area.begin(), area.end()) { }
// Allocators can't be copied
LinearAllocator(const LinearAllocator& rhs) = delete;
LinearAllocator& operator=(const LinearAllocator& rhs) = delete;
// Allocators can be moved
LinearAllocator(LinearAllocator&& rhs) noexcept;
LinearAllocator& operator=(LinearAllocator&& rhs) noexcept;
~LinearAllocator() noexcept = default;
// our allocator concept
void* alloc(size_t size, size_t alignment = alignof(std::max_align_t), size_t extra = 0) UTILS_RESTRICT {
// branch-less allocation
void* const p = pointermath::align(current(), alignment, extra);
void* const c = pointermath::add(p, size);
bool success = c <= end();
set_current(success ? c : current());
return success ? p : nullptr;
}
// API specific to this allocator
void *getCurrent() UTILS_RESTRICT noexcept {
return current();
}
// free memory back to the specified point
void rewind(void* p) UTILS_RESTRICT noexcept {
assert(p>=mBegin && p<end());
set_current(p);
}
// frees all allocated blocks
void reset() UTILS_RESTRICT noexcept {
rewind(mBegin);
}
size_t allocated() const UTILS_RESTRICT noexcept {
return mSize;
}
size_t available() const UTILS_RESTRICT noexcept {
return mSize - mCur;
}
void swap(LinearAllocator& rhs) noexcept;
void *base() noexcept { return mBegin; }
void free(void*, size_t) UTILS_RESTRICT noexcept { }
private:
void* end() UTILS_RESTRICT noexcept { return pointermath::add(mBegin, mSize); }
void* current() UTILS_RESTRICT noexcept { return pointermath::add(mBegin, mCur); }
void set_current(void* p) UTILS_RESTRICT noexcept {
mCur = uint32_t(uintptr_t(p) - uintptr_t(mBegin));
}
void* mBegin = nullptr;
uint32_t mSize = 0;
uint32_t mCur = 0;
};
/* ------------------------------------------------------------------------------------------------
* HeapAllocator
*
* + uses malloc() for all allocations
* + frees blocks with free()
* ------------------------------------------------------------------------------------------------
*/
class HeapAllocator {
public:
HeapAllocator() noexcept = default;
template <typename AREA>
explicit HeapAllocator(const AREA&) { }
// our allocator concept
void* alloc(size_t size, size_t alignment = alignof(std::max_align_t), size_t extra = 0) {
// this allocator doesn't support 'extra'
assert(extra == 0);
return aligned_alloc(size, alignment);
}
void free(void* p) noexcept {
aligned_free(p);
}
void free(void* p, size_t) noexcept {
this->free(p);
}
~HeapAllocator() noexcept = default;
void swap(HeapAllocator&) noexcept { }
};
// ------------------------------------------------------------------------------------------------
class FreeList {
public:
FreeList() noexcept = default;
FreeList(void* begin, void* end, size_t elementSize, size_t alignment, size_t extra) noexcept;
FreeList(const FreeList& rhs) = delete;
FreeList& operator=(const FreeList& rhs) = delete;
FreeList(FreeList&& rhs) noexcept = default;
FreeList& operator=(FreeList&& rhs) noexcept = default;
void* pop() noexcept {
Node* const head = mHead;
mHead = head ? head->next : nullptr;
// this could indicate a use after free
assert(!mHead || mHead >= mBegin && mHead < mEnd);
return head;
}
void push(void* p) noexcept {
assert(p);
assert(p >= mBegin && p < mEnd);
// TODO: assert this is one of our pointer (i.e.: it's address match one of ours)
Node* const head = static_cast<Node*>(p);
head->next = mHead;
mHead = head;
}
void *getFirst() noexcept {
return mHead;
}
private:
struct Node {
Node* next;
};
static Node* init(void* begin, void* end,
size_t elementSize, size_t alignment, size_t extra) noexcept;
Node* mHead = nullptr;
#ifndef NDEBUG
// These are needed only for debugging...
void* mBegin = nullptr;
void* mEnd = nullptr;
#endif
};
class AtomicFreeList {
public:
AtomicFreeList() noexcept = default;
AtomicFreeList(void* begin, void* end,
size_t elementSize, size_t alignment, size_t extra) noexcept;
AtomicFreeList(const FreeList& rhs) = delete;
AtomicFreeList& operator=(const FreeList& rhs) = delete;
void* pop() noexcept {
Node* const storage = mStorage;
HeadPtr currentHead = mHead.load();
while (currentHead.offset >= 0) {
// The value of "next" we load here might already contain application data if another
// thread raced ahead of us. But in that case, the computed "newHead" will be discarded
// since compare_exchange_weak fails. Then this thread will loop with the updated
// value of currentHead, and try again.
Node* const next = storage[currentHead.offset].next.load(std::memory_order_relaxed);
const HeadPtr newHead{ next ? int32_t(next - storage) : -1, currentHead.tag + 1 };
// In the rare case that the other thread that raced ahead of us already returned the
// same mHead we just loaded, but it now has a different "next" value, the tag field will not
// match, and compare_exchange_weak will fail and prevent that particular race condition.
if (mHead.compare_exchange_weak(currentHead, newHead)) {
// This assert needs to occur after we have validated that there was no race condition
// Otherwise, next might already contain application data, if another thread
// raced ahead of us after we loaded mHead, but before we loaded mHead->next.
assert(!next || next >= storage);
break;
}
}
void* p = (currentHead.offset >= 0) ? (storage + currentHead.offset) : nullptr;
assert(!p || p >= storage);
return p;
}
void push(void* p) noexcept {
Node* const storage = mStorage;
assert(p && p >= storage);
Node* const node = static_cast<Node*>(p);
HeadPtr currentHead = mHead.load();
HeadPtr newHead = { int32_t(node - storage), currentHead.tag + 1 };
do {
newHead.tag = currentHead.tag + 1;
Node* const n = (currentHead.offset >= 0) ? (storage + currentHead.offset) : nullptr;
node->next.store(n, std::memory_order_relaxed);
} while(!mHead.compare_exchange_weak(currentHead, newHead));
}
void* getFirst() noexcept {
return mStorage + mHead.load(std::memory_order_relaxed).offset;
}
private:
struct Node {
// This should be a regular (non-atomic) pointer, but this causes TSAN to complain
// about a data-race that exists but is benin. We always use this atomic<> in
// relaxed mode.
// The data race TSAN complains about is when a pop() is interrupted by a
// pop() + push() just after mHead->next is read -- it appears as though it is written
// without synchronization (by the push), however in that case, the pop's CAS will fail
// and things will auto-correct.
//
// Pop() |
// | |
// read head->next |
// | pop()
// | |
// | read head->next
// | CAS, tag++
// | |
// | push()
// | |
// [TSAN: data-race here] write head->next
// | CAS, tag++
// CAS fails
// |
// read head->next
// |
// CAS, tag++
//
std::atomic<Node*> next;
};
// This struct is using a 32-bit offset into the arena rather than
// a direct pointer, because together with the 32-bit tag, it needs to
// fit into 8 bytes. If it was any larger, it would not be possible to
// access it atomically.
struct alignas(8) HeadPtr {
int32_t offset;
uint32_t tag;
};
std::atomic<HeadPtr> mHead{};
Node* mStorage = nullptr;
};
// ------------------------------------------------------------------------------------------------
template <
size_t ELEMENT_SIZE,
size_t ALIGNMENT = alignof(std::max_align_t),
size_t OFFSET = 0,
typename FREELIST = FreeList>
class PoolAllocator {
static_assert(ELEMENT_SIZE >= sizeof(void*), "ELEMENT_SIZE must accommodate at least a pointer");
public:
// our allocator concept
void* alloc(size_t size = ELEMENT_SIZE,
size_t alignment = ALIGNMENT, size_t offset = OFFSET) noexcept {
assert(size <= ELEMENT_SIZE);
assert(alignment <= ALIGNMENT);
assert(offset == OFFSET);
return mFreeList.pop();
}
void free(void* p, size_t = ELEMENT_SIZE) noexcept {
mFreeList.push(p);
}
constexpr size_t getSize() const noexcept { return ELEMENT_SIZE; }
PoolAllocator(void* begin, void* end) noexcept
: mFreeList(begin, end, ELEMENT_SIZE, ALIGNMENT, OFFSET) {
}
template <typename AREA>
explicit PoolAllocator(const AREA& area) noexcept
: PoolAllocator(area.begin(), area.end()) {
}
// Allocators can't be copied
PoolAllocator(const PoolAllocator& rhs) = delete;
PoolAllocator& operator=(const PoolAllocator& rhs) = delete;
// Allocators can be moved
PoolAllocator(PoolAllocator&& rhs) = default;
PoolAllocator& operator=(PoolAllocator&& rhs) = default;
PoolAllocator() noexcept = default;
~PoolAllocator() noexcept = default;
// API specific to this allocator
void *getCurrent() noexcept {
return mFreeList.getFirst();
}
private:
FREELIST mFreeList;
};
#define UTILS_MAX(a,b) ((a) > (b) ? (a) : (b))
template <typename T, size_t OFFSET = 0>
using ObjectPoolAllocator = PoolAllocator<sizeof(T),
UTILS_MAX(alignof(FreeList), alignof(T)), OFFSET>;
template <typename T, size_t OFFSET = 0>
using ThreadSafeObjectPoolAllocator = PoolAllocator<sizeof(T),
UTILS_MAX(alignof(FreeList), alignof(T)), OFFSET, AtomicFreeList>;
// ------------------------------------------------------------------------------------------------
// Areas
// ------------------------------------------------------------------------------------------------
namespace AreaPolicy {
class StaticArea {
public:
StaticArea() noexcept = default;
StaticArea(void* b, void* e) noexcept
: mBegin(b), mEnd(e) {
}
~StaticArea() noexcept = default;
StaticArea(const StaticArea& rhs) = default;
StaticArea& operator=(const StaticArea& rhs) = default;
StaticArea(StaticArea&& rhs) noexcept = default;
StaticArea& operator=(StaticArea&& rhs) noexcept = default;
void* data() const noexcept { return mBegin; }
void* begin() const noexcept { return mBegin; }
void* end() const noexcept { return mEnd; }
size_t size() const noexcept { return uintptr_t(mEnd) - uintptr_t(mBegin); }
friend void swap(StaticArea& lhs, StaticArea& rhs) noexcept {
using std::swap;
swap(lhs.mBegin, rhs.mBegin);
swap(lhs.mEnd, rhs.mEnd);
}
private:
void* mBegin = nullptr;
void* mEnd = nullptr;
};
class HeapArea {
public:
HeapArea() noexcept = default;
explicit HeapArea(size_t size) {
if (size) {
// TODO: policy committing memory
mBegin = malloc(size);
mEnd = pointermath::add(mBegin, size);
}
}
~HeapArea() noexcept {
// TODO: policy for returning memory to system
free(mBegin);
}
HeapArea(const HeapArea& rhs) = delete;
HeapArea& operator=(const HeapArea& rhs) = delete;
HeapArea(HeapArea&& rhs) noexcept = delete;
HeapArea& operator=(HeapArea&& rhs) noexcept = delete;
void* data() const noexcept { return mBegin; }
void* begin() const noexcept { return mBegin; }
void* end() const noexcept { return mEnd; }
size_t size() const noexcept { return uintptr_t(mEnd) - uintptr_t(mBegin); }
friend void swap(HeapArea& lhs, HeapArea& rhs) noexcept {
using std::swap;
swap(lhs.mBegin, rhs.mBegin);
swap(lhs.mEnd, rhs.mEnd);
}
private:
void* mBegin = nullptr;
void* mEnd = nullptr;
};
class NullArea {
public:
void* data() const noexcept { return nullptr; }
size_t size() const noexcept { return 0; }
};
} // namespace AreaPolicy
// ------------------------------------------------------------------------------------------------
// Policies
// ------------------------------------------------------------------------------------------------
namespace LockingPolicy {
struct NoLock {
void lock() noexcept { }
void unlock() noexcept { }
};
using SpinLock = utils::SpinLock;
using Mutex = utils::Mutex;
} // namespace LockingPolicy
namespace TrackingPolicy {
// default no-op tracker
struct Untracked {
Untracked() noexcept = default;
Untracked(const char* name, void* base, size_t size) noexcept {
(void)name, void(base), (void)size;
}
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) noexcept {
(void)p, (void)size, (void)alignment, (void)extra;
}
void onFree(void* p, size_t = 0) noexcept { (void)p; }
void onReset() noexcept { }
void onRewind(void* addr) noexcept { (void)addr; }
};
// This just track the max memory usage and logs it in the destructor
struct HighWatermark {
HighWatermark() noexcept = default;
HighWatermark(const char* name, void* base, size_t size) noexcept
: mName(name), mBase(base), mSize(uint32_t(size)) { }
~HighWatermark() noexcept;
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) noexcept;
void onFree(void* p, size_t size) noexcept;
void onReset() noexcept;
void onRewind(void const* addr) noexcept;
uint32_t getHighWatermark() const noexcept { return mHighWaterMark; }
protected:
const char* mName = nullptr;
void* mBase = nullptr;
uint32_t mSize = 0;
uint32_t mCurrent = 0;
uint32_t mHighWaterMark = 0;
};
// This just fills buffers with known values to help catch uninitialized access and use after free.
struct Debug {
Debug() noexcept = default;
Debug(const char* name, void* base, size_t size) noexcept
: mName(name), mBase(base), mSize(uint32_t(size)) { }
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) noexcept;
void onFree(void* p, size_t size) noexcept;
void onReset() noexcept;
void onRewind(void* addr) noexcept;
protected:
const char* mName = nullptr;
void* mBase = nullptr;
uint32_t mSize = 0;
};
struct DebugAndHighWatermark : protected HighWatermark, protected Debug {
DebugAndHighWatermark() noexcept = default;
DebugAndHighWatermark(const char* name, void* base, size_t size) noexcept
: HighWatermark(name, base, size), Debug(name, base, size) { }
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) noexcept {
HighWatermark::onAlloc(p, size, alignment, extra);
Debug::onAlloc(p, size, alignment, extra);
}
void onFree(void* p, size_t size) noexcept {
HighWatermark::onFree(p, size);
Debug::onFree(p, size);
}
void onReset() noexcept {
HighWatermark::onReset();
Debug::onReset();
}
void onRewind(void* addr) noexcept {
HighWatermark::onRewind(addr);
Debug::onRewind(addr);
}
};
} // namespace TrackingPolicy
// ------------------------------------------------------------------------------------------------
// Arenas
// ------------------------------------------------------------------------------------------------
template<typename AllocatorPolicy, typename LockingPolicy,
typename TrackingPolicy = TrackingPolicy::Untracked,
typename AreaPolicy = AreaPolicy::HeapArea>
class Arena {
public:
Arena() = default;
// construct an arena with a name and forward argument to its allocator
template<typename ... ARGS>
Arena(const char* name, size_t size, ARGS&& ... args)
: mArenaName(name),
mArea(size),
mAllocator(mArea, std::forward<ARGS>(args) ... ),
mListener(name, mArea.data(), mArea.size()) {
}
template<typename ... ARGS>
Arena(const char* name, AreaPolicy&& area, ARGS&& ... args)
: mArenaName(name),
mArea(std::forward<AreaPolicy>(area)),
mAllocator(mArea, std::forward<ARGS>(args) ... ),
mListener(name, mArea.data(), mArea.size()) {
}
// allocate memory from arena with given size and alignment
// (acceptable size/alignment may depend on the allocator provided)
void* alloc(size_t size, size_t alignment = alignof(std::max_align_t), size_t extra = 0) noexcept {
std::lock_guard<LockingPolicy> guard(mLock);
void* p = mAllocator.alloc(size, alignment, extra);
mListener.onAlloc(p, size, alignment, extra);
return p;
}
// Allocate an array of trivially destructible objects
// for safety, we disable the object-based alloc method if the object type is not
// trivially destructible, since free() won't call the destructor and this is allocating
// an array.
template <typename T,
typename = typename std::enable_if<std::is_trivially_destructible<T>::value>::type>
T* alloc(size_t count, size_t alignment = alignof(T), size_t extra = 0) noexcept {
return (T*)alloc(count * sizeof(T), alignment, extra);
}
// return memory pointed by p to the arena
// (actual behaviour may depend on allocator provided)
void free(void* p) noexcept {
if (p) {
std::lock_guard<LockingPolicy> guard(mLock);
mListener.onFree(p);
mAllocator.free(p);
}
}
// some allocators require the size of the allocation for free
void free(void* p, size_t size) noexcept {
if (p) {
std::lock_guard<LockingPolicy> guard(mLock);
mListener.onFree(p, size);
mAllocator.free(p, size);
}
}
// some allocators don't have a free() call, but a single reset() or rewind() instead
void reset() noexcept {
std::lock_guard<LockingPolicy> guard(mLock);
mListener.onReset();
mAllocator.reset();
}
void* getCurrent() noexcept { return mAllocator.getCurrent(); }
void rewind(void *addr) noexcept {
std::lock_guard<LockingPolicy> guard(mLock);
mListener.onRewind(addr);
mAllocator.rewind(addr);
}
// Allocate and construct an object
template<typename T, size_t ALIGN = alignof(T), typename... ARGS>
T* make(ARGS&& ... args) noexcept {
void* const p = this->alloc(sizeof(T), ALIGN);
return p ? new(p) T(std::forward<ARGS>(args)...) : nullptr;
}
// destroys an object created with make<T>() above, and frees associated memory
template<typename T>
void destroy(T* p) noexcept {
if (p) {
p->~T();
this->free((void*)p, sizeof(T));
}
}
char const* getName() const noexcept { return mArenaName; }
AllocatorPolicy& getAllocator() noexcept { return mAllocator; }
AllocatorPolicy const& getAllocator() const noexcept { return mAllocator; }
TrackingPolicy& getListener() noexcept { return mListener; }
TrackingPolicy const& getListener() const noexcept { return mListener; }
AreaPolicy& getArea() noexcept { return mArea; }
AreaPolicy const& getArea() const noexcept { return mArea; }
void setListener(TrackingPolicy listener) noexcept {
std::swap(mListener, listener);
}
template <typename ... ARGS>
void emplaceListener(ARGS&& ... args) noexcept {
mListener.~TrackingPolicy();
new (&mListener) TrackingPolicy(std::forward<ARGS>(args)...);
}
// An arena can't be copied
Arena(Arena const& rhs) noexcept = delete;
Arena& operator=(Arena const& rhs) noexcept = delete;
friend void swap(Arena& lhs, Arena& rhs) noexcept {
using std::swap;
swap(lhs.mArea, rhs.mArea);
swap(lhs.mAllocator, rhs.mAllocator);
swap(lhs.mLock, rhs.mLock);
swap(lhs.mListener, rhs.mListener);
swap(lhs.mArenaName, rhs.mArenaName);
}
private:
char const* mArenaName = nullptr;
AreaPolicy mArea;
// note: we should use something like compressed_pair for the members below
AllocatorPolicy mAllocator;
LockingPolicy mLock;
TrackingPolicy mListener;
};
// ------------------------------------------------------------------------------------------------
template<typename TrackingPolicy = TrackingPolicy::Untracked>
using HeapArena = Arena<HeapAllocator, LockingPolicy::NoLock, TrackingPolicy>;
// ------------------------------------------------------------------------------------------------
// This doesn't implement our allocator concept, because it's too risky to use this as an allocator
// in particular, doing ArenaScope<ArenaScope>.
template<typename ARENA>
class ArenaScope {
struct Finalizer {
void (*finalizer)(void* p) = nullptr;
Finalizer* next = nullptr;
};
template <typename T>
static void destruct(void* p) noexcept {
static_cast<T*>(p)->~T();
}
public:
explicit ArenaScope(ARENA& allocator)
: mArena(allocator), mRewind(allocator.getCurrent()) {
}
ArenaScope& operator=(const ArenaScope& rhs) = delete;
ArenaScope(ArenaScope&& rhs) noexcept = delete;
ArenaScope& operator=(ArenaScope&& rhs) noexcept = delete;
~ArenaScope() {
// run the finalizer chain
Finalizer* head = mFinalizerHead;
while (head) {
void* p = pointermath::add(head, sizeof(Finalizer));
head->finalizer(p);
head = head->next;
}
// ArenaScope works only with Arena that implements rewind()
mArena.rewind(mRewind);
}
template<typename T, size_t ALIGN = alignof(T), typename... ARGS>
T* make(ARGS&& ... args) noexcept {
T* o = nullptr;
if (std::is_trivially_destructible<T>::value) {
o = mArena.template make<T, ALIGN>(std::forward<ARGS>(args)...);
} else {
void* const p = (Finalizer*)mArena.alloc(sizeof(T), ALIGN, sizeof(Finalizer));
if (p != nullptr) {
Finalizer* const f = static_cast<Finalizer*>(p) - 1;
// constructor must be called before adding the dtor to the list
// so that the ctor can allocate objects in a nested scope and have the
// finalizers called in reverse order.
o = new(p) T(std::forward<ARGS>(args)...);
f->finalizer = &destruct<T>;
f->next = mFinalizerHead;
mFinalizerHead = f;
}
}
return o;
}
void* allocate(size_t size, size_t alignment = 1) noexcept {
return mArena.template alloc<uint8_t>(size, alignment, 0);
}
template <typename T>
T* allocate(size_t size, size_t alignment = alignof(T), size_t extra = 0) noexcept {
return mArena.template alloc<T>(size, alignment, extra);
}
// use with caution
ARENA& getAllocator() noexcept { return mArena; }
private:
ARENA& mArena;
void* mRewind = nullptr;
Finalizer* mFinalizerHead = nullptr;
};
template <typename TYPE, typename ARENA>
class STLAllocator {
public:
using value_type = TYPE;
using pointer = TYPE*;
using const_pointer = const TYPE*;
using reference = TYPE&;
using const_reference = const TYPE&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;
template<typename OTHER>
struct rebind { using other = STLAllocator<OTHER, ARENA>; };
public:
// we don't make this explicit, so that we can initialize a vector using a STLAllocator
// from an Arena, avoiding to have to repeat the vector type.
STLAllocator(ARENA& arena) : mArena(arena) { } // NOLINT(google-explicit-constructor)
template<typename U>
explicit STLAllocator(STLAllocator<U, ARENA> const& rhs) : mArena(rhs.mArena) { }
TYPE* allocate(std::size_t n) {
auto p = static_cast<TYPE *>(mArena.alloc(n * sizeof(TYPE), alignof(TYPE)));
assert_invariant(p);
return p;
}
void deallocate(TYPE* p, std::size_t n) {
mArena.free(p, n * sizeof(TYPE));
}
// these should be out-of-class friends, but this doesn't seem to work with some compilers
// which complain about multiple definition each time a STLAllocator<> is instantiated.
template <typename U, typename A>
bool operator==(const STLAllocator<U, A>& rhs) const noexcept {
return std::addressof(mArena) == std::addressof(rhs.mArena);
}
template <typename U, typename A>
bool operator!=(const STLAllocator<U, A>& rhs) const noexcept {
return !operator==(rhs);
}
private:
template<typename U, typename A>
friend class STLAllocator;
ARENA& mArena;
};
} // namespace utils
#endif // TNT_UTILS_ALLOCATOR_H

View File

@@ -0,0 +1,145 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_BITMASKENUM_H
#define TNT_UTILS_BITMASKENUM_H
#include <utils/compiler.h>
#include <type_traits> // for std::false_type
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
namespace utils {
template<typename Enum>
struct EnableBitMaskOperators : public std::false_type { };
template<typename Enum>
struct EnableIntegerOperators : public std::false_type { };
namespace Enum {
template<typename Enum>
size_t count();
} // namespace enum
} // namespace utils
// ------------------------------------------------------------------------------------------------
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableIntegerOperators<Enum>::value, int> = 0>
inline constexpr int operator+(Enum value) noexcept {
return int(value);
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableIntegerOperators<Enum>::value, int> = 0>
inline constexpr bool operator==(Enum lhs, size_t rhs) noexcept {
using underlying_t = std::underlying_type_t<Enum>;
return underlying_t(lhs) == rhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableIntegerOperators<Enum>::value, int> = 0>
inline constexpr bool operator==(size_t lhs, Enum rhs) noexcept {
return rhs == lhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableIntegerOperators<Enum>::value, int> = 0>
inline constexpr bool operator!=(Enum lhs, size_t rhs) noexcept {
return !(rhs == lhs);
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableIntegerOperators<Enum>::value, int> = 0>
inline constexpr bool operator!=(size_t lhs, Enum rhs) noexcept {
return rhs != lhs;
}
// ------------------------------------------------------------------------------------------------
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr bool operator!(Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return underlying(rhs) == 0;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator~(Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return Enum(~underlying(rhs));
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator|(Enum lhs, Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return Enum(underlying(lhs) | underlying(rhs));
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator&(Enum lhs, Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return Enum(underlying(lhs) & underlying(rhs));
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator^(Enum lhs, Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return Enum(underlying(lhs) ^ underlying(rhs));
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator|=(Enum& lhs, Enum rhs) noexcept {
return lhs = lhs | rhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator&=(Enum& lhs, Enum rhs) noexcept {
return lhs = lhs & rhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator^=(Enum& lhs, Enum rhs) noexcept {
return lhs = lhs ^ rhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr bool none(Enum lhs) noexcept {
return !lhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr bool any(Enum lhs) noexcept {
return !none(lhs);
}
#endif // TNT_UTILS_BITMASKENUM_H

View File

@@ -0,0 +1,230 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_CSTRING_H
#define TNT_UTILS_CSTRING_H
// NOTE: this header should not include STL headers
#include <utils/compiler.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
namespace utils {
//! \privatesection
struct hashCStrings {
typedef const char* argument_type;
typedef size_t result_type;
result_type operator()(argument_type cstr) const noexcept {
size_t hash = 5381;
while (int c = *cstr++) {
hash = (hash * 33u) ^ size_t(c);
}
return hash;
}
};
template <size_t N>
using StringLiteral = const char[N];
// ------------------------------------------------------------------------------------------------
class UTILS_PUBLIC CString {
public:
using value_type = char;
using size_type = uint32_t;
using difference_type = int32_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using iterator = value_type*;
using const_iterator = const value_type*;
CString() noexcept {} // NOLINT(modernize-use-equals-default), Ubuntu compiler bug
// Allocates memory and appends a null. This constructor can be used to hold arbitrary data
// inside the string (i.e. it can contain nulls or non-ASCII encodings).
CString(const char* cstr, size_t length);
// Allocates memory for a string of size length plus space for the null terminating character.
// Also initializes the memory to 0. This constructor can be used to hold arbitrary data
// inside the string.
explicit CString(size_t length);
// Allocates memory and copies traditional C string content. Unlike the above constructor, this
// does not allow embedded nulls. This is explicit because this operation is costly.
explicit CString(const char* cstr);
template<size_t N>
CString(StringLiteral<N> const& other) noexcept // NOLINT(google-explicit-constructor)
: CString(other, N - 1) {
}
CString(const CString& rhs);
CString(CString&& rhs) noexcept {
this->swap(rhs);
}
CString& operator=(const CString& rhs);
CString& operator=(CString&& rhs) noexcept {
this->swap(rhs);
return *this;
}
~CString() noexcept {
if (mData) {
free(mData - 1);
}
}
void swap(CString& other) noexcept {
// don't use std::swap(), we don't want an STL dependency in this file
auto *temp = mCStr;
mCStr = other.mCStr;
other.mCStr = temp;
}
const_pointer c_str() const noexcept { return mCStr; }
pointer c_str() noexcept { return mCStr; }
const_pointer c_str_safe() const noexcept { return mData ? c_str() : ""; }
const_pointer data() const noexcept { return c_str(); }
pointer data() noexcept { return c_str(); }
size_type size() const noexcept { return mData ? mData[-1].length : 0; }
size_type length() const noexcept { return size(); }
bool empty() const noexcept { return size() == 0; }
iterator begin() noexcept { return mCStr; }
iterator end() noexcept { return begin() + length(); }
const_iterator begin() const noexcept { return data(); }
const_iterator end() const noexcept { return begin() + length(); }
const_iterator cbegin() const noexcept { return begin(); }
const_iterator cend() const noexcept { return end(); }
CString& replace(size_type pos, size_type len, const CString& str) noexcept;
CString& insert(size_type pos, const CString& str) noexcept { return replace(pos, 0, str); }
const_reference operator[](size_type pos) const noexcept {
assert(pos < size());
return begin()[pos];
}
reference operator[](size_type pos) noexcept {
assert(pos < size());
return begin()[pos];
}
const_reference at(size_type pos) const noexcept {
assert(pos < size());
return begin()[pos];
}
reference at(size_type pos) noexcept {
assert(pos < size());
return begin()[pos];
}
reference front() noexcept {
assert(size());
return begin()[0];
}
const_reference front() const noexcept {
assert(size());
return begin()[0];
}
reference back() noexcept {
assert(size());
return begin()[size() - 1];
}
const_reference back() const noexcept {
assert(size());
return begin()[size() - 1];
}
// placement new declared as "throw" to avoid the compiler's null-check
inline void* operator new(size_t, void* ptr) {
assert(ptr);
return ptr;
}
struct Hasher : private hashCStrings {
typedef CString argument_type;
typedef size_t result_type;
result_type operator()(const argument_type& s) const noexcept {
return hashCStrings::operator()(s.c_str());
}
};
private:
struct Data {
size_type length;
};
// mCStr points to the C-string or nullptr. if non-null, mCStr is preceded by the string's size
union {
value_type *mCStr = nullptr;
Data* mData; // Data is stored at mData[-1]
};
int compare(const CString& rhs) const noexcept {
size_type lhs_size = size();
size_type rhs_size = rhs.size();
if (lhs_size < rhs_size) return -1;
if (lhs_size > rhs_size) return 1;
return strncmp(data(), rhs.data(), size());
}
friend bool operator==(CString const& lhs, CString const& rhs) noexcept {
return (lhs.data() == rhs.data()) ||
((lhs.size() == rhs.size()) && !strncmp(lhs.data(), rhs.data(), lhs.size()));
}
friend bool operator!=(CString const& lhs, CString const& rhs) noexcept {
return !(lhs == rhs);
}
friend bool operator<(CString const& lhs, CString const& rhs) noexcept {
return lhs.compare(rhs) < 0;
}
friend bool operator>(CString const& lhs, CString const& rhs) noexcept {
return lhs.compare(rhs) > 0;
}
friend bool operator>=(CString const& lhs, CString const& rhs) noexcept {
return !(lhs < rhs);
}
friend bool operator<=(CString const& lhs, CString const& rhs) noexcept {
return !(lhs > rhs);
}
};
// implement this for your type for automatic conversion to CString. Failing to do so leads
// to a compile-time failure.
template<typename T>
CString to_string(T value) noexcept;
} // namespace utils
#endif // TNT_UTILS_CSTRING_H

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UTILS_CALLSTACK_H
#define UTILS_CALLSTACK_H
#include <stddef.h>
#include <stdint.h>
#include <typeinfo>
#include <utils/CString.h>
#include <utils/Log.h>
namespace utils {
/**
* CallStack captures the current's thread call stack.
*/
class CallStack {
public:
/**
* Creates an empty call stack
* @see CallStack::capture()
*/
CallStack() = default;
CallStack(const CallStack&) = default;
~CallStack() = default;
/**
* A convenience method to create and capture the stack trace in one go.
* @param ignore number frames to ignore at the top of the stack.
* @return A CallStack object
*/
static CallStack unwind(size_t ignore = 0) noexcept;
/**
* Capture the current thread's stack and replaces the existing one if any.
* @param ignore number frames to ignore at the top of the stack.
*/
void update(size_t ignore = 0) noexcept;
/**
* Get the number of stack frames this object has recorded.
* @return How many stack frames are accessible through operator[]
*/
size_t getFrameCount() const noexcept;
/**
* Return the program-counter of each stack frame captured
* @param index of the frame between 0 and getFrameCount()-1
* @return the program-counter of the stack-frame recorded at index \p index
* @throw std::out_of_range if the index is out of range
*/
intptr_t operator [](size_t index) const;
/** Demangles a C++ type name */
static utils::CString demangleTypeName(const char* mangled);
template<typename T>
static utils::CString typeName() {
#if UTILS_HAS_RTTI
return demangleTypeName(typeid(T).name());
#else
return CString("<no-rtti>");
#endif
}
/**
* Outputs a CallStack into a stream.
* This will print, when possible, the demangled names of functions corresponding to the
* program-counter recorded.
*/
friend utils::io::ostream& operator <<(utils::io::ostream& stream, const CallStack& callstack);
bool operator <(const CallStack& rhs) const;
inline bool operator >(const CallStack& rhs) const {
return rhs < *this;
}
inline bool operator !=(const CallStack& rhs) const {
return *this < rhs || rhs < *this;
}
inline bool operator >=(const CallStack& rhs) const {
return !operator <(rhs);
}
inline bool operator <=(const CallStack& rhs) const {
return !operator >(rhs);
}
inline bool operator ==(const CallStack& rhs) const {
return !operator !=(rhs);
}
private:
void update_gcc(size_t ignore) noexcept;
static utils::CString demangle(const char* mangled);
static constexpr size_t NUM_FRAMES = 20;
struct StackFrameInfo {
intptr_t pc;
};
size_t m_frame_count = 0;
StackFrameInfo m_stack[NUM_FRAMES];
};
} // namespace utils
#endif // UTILS_CALLSTACK_H

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ENTITY_H
#define TNT_UTILS_ENTITY_H
#include <utils/compiler.h>
#include <stdint.h>
#include <stddef.h>
namespace utils {
class UTILS_PUBLIC Entity {
public:
// this can be used to create an array of to-be-filled entities (see create())
Entity() noexcept { } // NOLINT(modernize-use-equals-default), Ubuntu compiler bug
// Entities can be copied
Entity(const Entity& e) noexcept = default;
Entity(Entity&& e) noexcept = default;
Entity& operator=(const Entity& e) noexcept = default;
Entity& operator=(Entity&& e) noexcept = default;
// Entities can be compared
bool operator==(Entity e) const { return e.mIdentity == mIdentity; }
bool operator!=(Entity e) const { return e.mIdentity != mIdentity; }
// Entities can be sorted
bool operator<(Entity e) const { return e.mIdentity < mIdentity; }
bool isNull() const noexcept {
return mIdentity == 0;
}
// an id that can be used for debugging/printing
uint32_t getId() const noexcept {
return mIdentity;
}
explicit operator bool() const noexcept { return !isNull(); }
void clear() noexcept { mIdentity = 0; }
// Exports an entity to an int32_t which can be used "as is" in the Java programing language.
static int32_t smuggle(Entity entity) noexcept {
return int32_t(entity.getId());
}
// Imports an entity from an int32_t generated by smuggle() above.
static Entity import(int32_t identity) noexcept {
return Entity{ Type(identity) };
}
struct Hasher {
typedef Entity argument_type;
typedef size_t result_type;
result_type operator()(argument_type const& e) const {
return e.getId();
}
};
private:
friend class EntityManager;
friend class EntityManagerImpl;
using Type = uint32_t;
explicit Entity(Type identity) noexcept : mIdentity(identity) { }
Type mIdentity = 0;
};
} // namespace utils
#endif // TNT_UTILS_ENTITY_H

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ENTITYINSTANCE_H
#define TNT_UTILS_ENTITYINSTANCE_H
#include <utils/compiler.h>
#include <type_traits>
#include <stdint.h>
namespace utils {
class UTILS_PUBLIC EntityInstanceBase {
public:
using Type = uint32_t;
protected:
Type mInstance = 0;
};
template <typename T, bool EDIT = false>
class UTILS_PUBLIC EntityInstance : public EntityInstanceBase {
public:
// default Instance is invalid
constexpr EntityInstance() noexcept = default;
// check if this Instance is valid
constexpr bool isValid() const noexcept { return mInstance != 0; }
// Instances of same type can be copied/assigned
constexpr EntityInstance(EntityInstance const& other) noexcept = default;
constexpr EntityInstance& operator=(EntityInstance const& other) noexcept = default;
// EDIT instances can be converted to "read" Instances of same type
template <typename = std::enable_if<!EDIT>>
constexpr explicit EntityInstance(EntityInstance<T, true> const& other) noexcept {
mInstance = other.asValue();
}
template <typename = std::enable_if<!EDIT>>
EntityInstance& operator=(EntityInstance<T, true> const& other) noexcept {
mInstance = other.asValue();
return *this;
}
// Instances can be compared
constexpr bool operator!=(EntityInstance e) const { return mInstance != e.mInstance; }
constexpr bool operator==(EntityInstance e) const { return mInstance == e.mInstance; }
// Instances can be sorted
constexpr bool operator<(EntityInstance e) const { return mInstance < e.mInstance; }
constexpr bool operator<=(EntityInstance e) const { return mInstance <= e.mInstance; }
constexpr bool operator>(EntityInstance e) const { return mInstance > e.mInstance; }
constexpr bool operator>=(EntityInstance e) const { return mInstance >= e.mInstance; }
// and we can iterate
constexpr EntityInstance& operator++() noexcept { ++mInstance; return *this; }
constexpr EntityInstance& operator--() noexcept { --mInstance; return *this; }
constexpr const EntityInstance operator++(int) const noexcept { return EntityInstance{ mInstance + 1 }; }
constexpr const EntityInstance operator--(int) const noexcept { return EntityInstance{ mInstance - 1 }; }
// return a value for this Instance (mostly needed for debugging
constexpr uint32_t asValue() const noexcept { return mInstance; }
// auto convert to Type so it can be used as an index
constexpr operator Type() const noexcept { return mInstance; } // NOLINT(google-explicit-constructor)
// conversion from Type so we can initialize from an index
constexpr EntityInstance(Type value) noexcept { mInstance = value; } // NOLINT(google-explicit-constructor)
};
} // namespace utils
#endif // TNT_UTILS_ENTITYINSTANCE_H

View File

@@ -0,0 +1,133 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ENTITYMANAGER_H
#define TNT_UTILS_ENTITYMANAGER_H
#include <assert.h>
#include <stdint.h>
#include <utils/Entity.h>
#include <utils/compiler.h>
#ifndef FILAMENT_UTILS_TRACK_ENTITIES
#define FILAMENT_UTILS_TRACK_ENTITIES false
#endif
#if FILAMENT_UTILS_TRACK_ENTITIES
#include <utils/ostream.h>
#include <vector>
#endif
namespace utils {
class UTILS_PUBLIC EntityManager {
public:
// Get the global EntityManager. It is recommended to cache this value.
// Thread Safe.
static EntityManager& get() noexcept;
class Listener {
public:
virtual void onEntitiesDestroyed(size_t n, Entity const* entities) noexcept = 0;
protected:
~Listener() noexcept;
};
// maximum number of entities that can exist at the same time
static size_t getMaxEntityCount() noexcept {
// because index 0 is reserved, we only have 2^GENERATION_SHIFT - 1 valid indices
return RAW_INDEX_COUNT - 1;
}
// create n entities. Thread safe.
void create(size_t n, Entity* entities);
// destroys n entities. Thread safe.
void destroy(size_t n, Entity* entities) noexcept;
// create a new Entity. Thread safe.
// Return Entity.isNull() if the entity cannot be allocated.
Entity create() {
Entity e;
create(1, &e);
return e;
}
// destroys an Entity. Thread safe.
void destroy(Entity e) noexcept {
destroy(1, &e);
}
// return whether the given Entity has been destroyed (false) or not (true).
// Thread safe.
bool isAlive(Entity e) const noexcept {
assert(getIndex(e) < RAW_INDEX_COUNT);
return (!e.isNull()) && (getGeneration(e) == mGens[getIndex(e)]);
}
// registers a listener to be called when an entity is destroyed. thread safe.
// if the listener is already register, this method has no effect.
void registerListener(Listener* l) noexcept;
// unregisters a listener.
void unregisterListener(Listener* l) noexcept;
/* no user serviceable parts below */
// current generation of the given index. Use for debugging and testing.
uint8_t getGenerationForIndex(size_t index) const noexcept {
return mGens[index];
}
// singleton, can't be copied
EntityManager(const EntityManager& rhs) = delete;
EntityManager& operator=(const EntityManager& rhs) = delete;
#if FILAMENT_UTILS_TRACK_ENTITIES
std::vector<Entity> getActiveEntities() const;
void dumpActiveEntities(utils::io::ostream& out) const;
#endif
private:
friend class EntityManagerImpl;
EntityManager();
~EntityManager();
// GENERATION_SHIFT determines how many simultaneous Entities are available, the
// minimum memory requirement is 2^GENERATION_SHIFT bytes.
static constexpr const int GENERATION_SHIFT = 17;
static constexpr const size_t RAW_INDEX_COUNT = (1 << GENERATION_SHIFT);
static constexpr const Entity::Type INDEX_MASK = (1 << GENERATION_SHIFT) - 1u;
static inline Entity::Type getGeneration(Entity e) noexcept {
return e.getId() >> GENERATION_SHIFT;
}
static inline Entity::Type getIndex(Entity e) noexcept {
return e.getId() & INDEX_MASK;
}
static inline Entity::Type makeIdentity(Entity::Type g, Entity::Type i) noexcept {
return (g << GENERATION_SHIFT) | (i & INDEX_MASK);
}
// stores the generation of each index.
uint8_t * const mGens;
};
} // namespace utils
#endif // TNT_UTILS_ENTITYMANAGER_H

View File

@@ -0,0 +1,420 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_FIXEDCAPACITYVECTOR_H
#define TNT_UTILS_FIXEDCAPACITYVECTOR_H
#include <utils/compressed_pair.h>
#include <utils/Panic.h>
#include <initializer_list>
#include <limits>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector> // TODO: is this necessary?
#include <stddef.h>
#include <stdint.h>
#ifndef NDEBUG
#define FILAMENT_FORCE_CAPACITY_CHECK true
#else
#define FILAMENT_FORCE_CAPACITY_CHECK false
#endif
namespace utils {
/**
* FixedCapacityVector is (almost) a drop-in replacement for std::vector<> except it has a
* fixed capacity decided at runtime. The vector storage is never reallocated unless reserve()
* is called. Operations that add elements to the vector can fail if there is not enough
* capacity.
*
* An empty vector with a given capacity is created with
* FixedCapacityVector<T>::with_capacity( capacity );
*
* NOTE: When passing an initial size into the FixedCapacityVector constructor, default construction
* of the elements is skipped when their construction is trivial. This behavior is different from
* std::vector. e.g., std::vector<int>(4) constructs 4 zeros while FixedCapacityVector<int>(4)
* allocates 4 uninitialized values. Note that zero initialization is easily achieved by passing in
* the optional value argument, e.g. FixedCapacityVector<int>(4, 0) or foo.resize(4, 0).
*/
template<typename T, typename A = std::allocator<T>, bool CapacityCheck = true>
class UTILS_PUBLIC FixedCapacityVector {
public:
using allocator_type = A;
using value_type = T;
using reference = T&;
using const_reference = T const&;
using size_type = uint32_t;
using difference_type = int32_t;
using pointer = T*;
using const_pointer = T const*;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
private:
using storage_traits = std::allocator_traits<allocator_type>;
public:
/** returns an empty vector with the specified capacity */
static FixedCapacityVector with_capacity(
size_type capacity, const allocator_type& allocator = allocator_type()) {
return FixedCapacityVector(construct_with_capacity, capacity, allocator);
}
FixedCapacityVector() = default;
explicit FixedCapacityVector(const allocator_type& allocator) noexcept
: mCapacityAllocator({}, allocator) {
}
explicit FixedCapacityVector(size_type size, const allocator_type& allocator = allocator_type())
: mSize(size),
mCapacityAllocator(size, allocator) {
mData = this->allocator().allocate(this->capacity());
construct(begin(), end());
}
FixedCapacityVector(std::initializer_list<T> list,
const allocator_type& alloc = allocator_type())
: mSize(list.size()),
mCapacityAllocator(list.size(), alloc) {
mData = this->allocator().allocate(this->capacity());
std::uninitialized_copy(list.begin(), list.end(), begin());
}
FixedCapacityVector(size_type size, const_reference value,
const allocator_type& alloc = allocator_type())
: mSize(size),
mCapacityAllocator(size, alloc) {
mData = this->allocator().allocate(this->capacity());
construct(begin(), end(), value);
}
FixedCapacityVector(FixedCapacityVector const& rhs)
: mSize(rhs.mSize),
mCapacityAllocator(rhs.capacity(),
storage_traits::select_on_container_copy_construction(rhs.allocator())) {
mData = allocator().allocate(capacity());
std::uninitialized_copy(rhs.begin(), rhs.end(), begin());
}
FixedCapacityVector(FixedCapacityVector&& rhs) noexcept {
this->swap(rhs);
}
~FixedCapacityVector() noexcept {
destroy(begin(), end());
allocator().deallocate(data(), capacity());
}
FixedCapacityVector& operator=(FixedCapacityVector const& rhs) {
if (this != &rhs) {
FixedCapacityVector t(rhs);
this->swap(t);
}
return *this;
}
FixedCapacityVector& operator=(FixedCapacityVector&& rhs) noexcept {
this->swap(rhs);
return *this;
}
allocator_type get_allocator() const noexcept {
return mCapacityAllocator.second();
}
// --------------------------------------------------------------------------------------------
iterator begin() noexcept { return data(); }
iterator end() noexcept { return data() + size(); }
const_iterator begin() const noexcept { return data(); }
const_iterator end() const noexcept { return data() + size(); }
reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); }
reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
const_iterator cbegin() const noexcept { return begin(); }
const_iterator cend() const noexcept { return end(); }
const_reverse_iterator crbegin() const noexcept { return rbegin(); }
const_reverse_iterator crend() const noexcept { return rend(); }
// --------------------------------------------------------------------------------------------
size_type size() const noexcept { return mSize; }
size_type capacity() const noexcept { return mCapacityAllocator.first(); }
bool empty() const noexcept { return size() == 0; }
size_type max_size() const noexcept {
return std::min(storage_traits::max_size(allocator()),
std::numeric_limits<size_type>::max());
}
// --------------------------------------------------------------------------------------------
reference operator[](size_type n) noexcept {
assert(n < size());
return *(begin() + n);
}
const_reference operator[](size_type n) const noexcept {
assert(n < size());
return *(begin() + n);
}
reference front() noexcept { return *begin(); }
const_reference front() const noexcept { return *begin(); }
reference back() noexcept { return *(end() - 1); }
const_reference back() const noexcept { return *(end() - 1); }
value_type* data() noexcept { return mData; }
const value_type* data() const noexcept { return mData; }
// --------------------------------------------------------------------------------------------
void push_back(const_reference v) {
auto pos = assertCapacityForSize(size() + 1);
++mSize;
storage_traits::construct(allocator(), pos, v);
}
void push_back(value_type&& v) {
auto pos = assertCapacityForSize(size() + 1);
++mSize;
storage_traits::construct(allocator(), pos, std::move(v));
}
template<typename ... ARGS>
reference emplace_back(ARGS&& ... args) {
auto pos = assertCapacityForSize(size() + 1);
++mSize;
storage_traits::construct(allocator(), pos, std::forward<ARGS>(args)...);
return *pos;
}
void pop_back() {
assert(!empty());
--mSize;
destroy(end(), end() + 1);
}
iterator insert(const_iterator position, const_reference v) {
if (position == end()) {
push_back(v);
} else {
assertCapacityForSize(size() + 1);
pointer p = const_cast<pointer>(position);
move_range(p, end(), p + 1);
++mSize;
// here we handle inserting an element of this vector!
const_pointer pv = std::addressof(v);
if (p <= pv && pv < end()) {
*p = *(pv + 1);
} else {
*p = v;
}
}
return const_cast<iterator>(position);
}
iterator insert(const_iterator position, value_type&& v) {
if (position == end()) {
push_back(std::move(v));
} else {
assertCapacityForSize(size() + 1);
pointer p = const_cast<pointer>(position);
move_range(p, end(), p + 1);
++mSize;
*p = std::move(v);
}
return const_cast<iterator>(position);
}
iterator erase(const_iterator pos) {
assert(pos != end());
return erase(pos, pos + 1);
}
iterator erase(const_iterator first, const_iterator last) {
assert(first <= last);
auto e = std::move(const_cast<iterator>(last), end(), const_cast<iterator>(first));
destroy(e, end());
mSize -= std::distance(first, last);
return const_cast<iterator>(first);
}
void clear() noexcept {
destroy(begin(), end());
mSize = 0;
}
void resize(size_type count) {
assertCapacityForSize(count);
if constexpr(std::is_trivially_constructible_v<value_type> &&
std::is_trivially_destructible_v<value_type>) {
// we check for triviality here so that the implementation could be non-inline
mSize = count;
} else {
resize_non_trivial(count);
}
}
void resize(size_type count, const_reference v) {
assertCapacityForSize(count);
resize_non_trivial(count, v);
}
void swap(FixedCapacityVector& other) {
using std::swap;
swap(mData, other.mData);
swap(mSize, other.mSize);
mCapacityAllocator.swap(other.mCapacityAllocator);
}
UTILS_NOINLINE
void reserve(size_type c) {
if (c > capacity()) {
FixedCapacityVector t(construct_with_capacity, c, allocator());
t.mSize = size();
std::uninitialized_move(begin(), end(), t.begin());
this->swap(t);
}
}
private:
enum construct_with_capacity_tag{ construct_with_capacity };
FixedCapacityVector(construct_with_capacity_tag,
size_type capacity, const allocator_type& allocator = allocator_type())
: mCapacityAllocator(capacity, allocator) {
mData = this->allocator().allocate(this->capacity());
}
allocator_type& allocator() noexcept {
return mCapacityAllocator.second();
}
allocator_type const& allocator() const noexcept {
return mCapacityAllocator.second();
}
iterator assertCapacityForSize(size_type s) {
if constexpr(CapacityCheck || FILAMENT_FORCE_CAPACITY_CHECK) {
ASSERT_PRECONDITION(capacity() >= s,
"capacity exceeded: requested size %lu, available capacity %lu.",
(unsigned long)s, (unsigned long)capacity());
}
return end();
}
inline void construct(iterator first, iterator last) noexcept {
// we check for triviality here so that the implementation could be non-inline
if constexpr(!std::is_trivially_constructible_v<value_type>) {
construct_non_trivial(first, last);
}
}
void construct(iterator first, iterator last, const_reference proto) noexcept {
UTILS_NOUNROLL
while (first != last) {
storage_traits::construct(allocator(), first++, proto);
}
}
// should this be NOINLINE?
void construct_non_trivial(iterator first, iterator last) noexcept {
UTILS_NOUNROLL
while (first != last) {
storage_traits::construct(allocator(), first++);
}
}
inline void destroy(iterator first, iterator last) noexcept {
// we check for triviality here so that the implementation could be non-inline
if constexpr(!std::is_trivially_destructible_v<value_type>) {
destroy_non_trivial(first, last);
}
}
// should this be NOINLINE?
void destroy_non_trivial(iterator first, iterator last) noexcept {
UTILS_NOUNROLL
while (first != last) {
storage_traits::destroy(allocator(), --last);
}
}
// should this be NOINLINE?
void resize_non_trivial(size_type count) {
if (count > size()) {
construct(end(), begin() + count);
} else if (count < size()) {
destroy(begin() + count, end());
}
mSize = count;
}
// should this be NOINLINE?
void resize_non_trivial(size_type count, const_reference v) {
if (count > size()) {
construct(end(), begin() + count, v);
} else if (count < size()) {
destroy(begin() + count, end());
}
mSize = count;
}
// should this be NOINLINE?
void move_range(pointer s, pointer e, pointer to) {
if constexpr(std::is_trivially_copy_assignable_v<value_type>
&& std::is_trivially_destructible_v<value_type>) {
// this generates memmove -- which doesn't happen otherwise
std::move_backward(s, e, to + std::distance(s, e));
} else {
pointer our_end = end();
difference_type n = our_end - to; // nb of elements to move by operator=
pointer i = s + n; // 1st element to move by move ctor
for (pointer d = our_end ; i < our_end ; ++i, ++d) {
storage_traits::construct(allocator(), d, std::move(*i));
}
std::move_backward(s, s + n, our_end);
}
}
template<typename TYPE>
class SizeTypeWrapper {
TYPE value{};
public:
SizeTypeWrapper() noexcept = default;
SizeTypeWrapper(SizeTypeWrapper const& rhs) noexcept = default;
explicit SizeTypeWrapper(TYPE value) noexcept : value(value) { }
SizeTypeWrapper& operator=(TYPE rhs) noexcept { value = rhs; return *this; }
SizeTypeWrapper& operator=(SizeTypeWrapper& rhs) noexcept = delete;
operator TYPE() const noexcept { return value; }
};
pointer mData{};
size_type mSize{};
compressed_pair<SizeTypeWrapper<size_type>, allocator_type> mCapacityAllocator{};
};
} // namespace utils
#endif // TNT_UTILS_FIXEDCAPACITYVECTOR_H

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_INVOKABLE_H
#define TNT_UTILS_INVOKABLE_H
#include <type_traits>
#include <utility>
#include <assert.h>
namespace utils {
/*
* Invocable is a move-only general purpose function wrapper. Instances can
* store and invoke lambda expressions and other function objects.
*
* It is similar to std::function, with the following differences:
* - Invocable is move only.
* - Invocable can capture move only types.
* - No conversion between 'compatible' functions.
* - No small buffer optimization
*/
// Helper for enabling methods if Fn matches the function signature
// requirements of the Invocable type.
#if __cplusplus >= 201703L
// only available on C++17 and up
template<typename Fn, typename R, typename... Args>
using EnableIfFnMatchesInvocable =
std::enable_if_t<std::is_invocable_v<Fn, Args...> &&
std::is_same_v<R, std::invoke_result_t<Fn, Args...>>, int>;
#else
template<typename Fn, typename R, typename... Args>
using EnableIfFnMatchesInvocable = std::enable_if_t<true, int>;
#endif
template<typename Signature>
class Invocable;
template<typename R, typename... Args>
class Invocable<R(Args...)> {
public:
// Creates an Invocable that does not contain a functor.
// Will evaluate to false.
Invocable() = default;
~Invocable() noexcept;
// Creates an Invocable from the functor passed in.
template<typename Fn, EnableIfFnMatchesInvocable<Fn, R, Args...> = 0>
Invocable(Fn&& fn) noexcept; // NOLINT(google-explicit-constructor)
Invocable(const Invocable&) = delete;
Invocable(Invocable&& rhs) noexcept;
Invocable& operator=(const Invocable&) = delete;
Invocable& operator=(Invocable&& rhs) noexcept;
// Invokes the invocable with the args passed in.
// If the Invocable is empty, this will assert.
template<typename... OperatorArgs>
R operator()(OperatorArgs&& ... args);
template<typename... OperatorArgs>
R operator()(OperatorArgs&& ... args) const;
// Evaluates to true if Invocable contains a functor, false otherwise.
explicit operator bool() const noexcept;
private:
void* mInvocable = nullptr;
void (*mDeleter)(void*) = nullptr;
R (* mInvoker)(void*, Args...) = nullptr;
};
template<typename R, typename... Args>
template<typename Fn, EnableIfFnMatchesInvocable<Fn, R, Args...>>
Invocable<R(Args...)>::Invocable(Fn&& fn) noexcept
: mInvocable(new Fn(std::forward<std::decay_t<Fn>>(fn))),
mDeleter(+[](void* erased_invocable) {
auto typed_invocable = static_cast<Fn*>(erased_invocable);
delete typed_invocable;
}),
mInvoker(+[](void* erased_invocable, Args... args) -> R {
auto typed_invocable = static_cast<Fn*>(erased_invocable);
return (*typed_invocable)(std::forward<Args>(args)...);
})
{
}
template<typename R, typename... Args>
Invocable<R(Args...)>::~Invocable() noexcept {
if (mDeleter) {
mDeleter(mInvocable);
}
}
template<typename R, typename... Args>
Invocable<R(Args...)>::Invocable(Invocable&& rhs) noexcept
: mInvocable(rhs.mInvocable),
mDeleter(rhs.mDeleter),
mInvoker(rhs.mInvoker) {
rhs.mInvocable = nullptr;
rhs.mDeleter = nullptr;
rhs.mInvoker = nullptr;
}
template<typename R, typename... Args>
Invocable<R(Args...)>& Invocable<R(Args...)>::operator=(Invocable&& rhs) noexcept {
if (this != &rhs) {
std::swap(mInvocable, rhs.mInvocable);
std::swap(mDeleter, rhs.mDeleter);
std::swap(mInvoker, rhs.mInvoker);
}
return *this;
}
template<typename R, typename... Args>
template<typename... OperatorArgs>
R Invocable<R(Args...)>::operator()(OperatorArgs&& ... args) {
assert(mInvoker && mInvocable);
return mInvoker(mInvocable, std::forward<OperatorArgs>(args)...);
}
template<typename R, typename... Args>
template<typename... OperatorArgs>
R Invocable<R(Args...)>::operator()(OperatorArgs&& ... args) const {
assert(mInvoker && mInvocable);
return mInvoker(mInvocable, std::forward<OperatorArgs>(args)...);
}
template<typename R, typename... Args>
Invocable<R(Args...)>::operator bool() const noexcept {
return mInvoker != nullptr && mInvocable != nullptr;
}
} // namespace utils
#endif // TNT_UTILS_INVOKABLE_H

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_LOG_H
#define TNT_UTILS_LOG_H
#include <utils/compiler.h>
#include <utils/ostream.h>
namespace utils {
struct UTILS_PUBLIC Loggers {
// DEBUG level logging stream
io::ostream& d;
// ERROR level logging stream
io::ostream& e;
// WARNING level logging stream
io::ostream& w;
// INFORMATION level logging stream
io::ostream& i;
// VERBOSE level logging stream
io::ostream& v;
};
extern UTILS_PUBLIC Loggers const slog;
} // namespace utils
#endif // TNT_UTILS_LOG_H

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_MUTEX_H
#define TNT_UTILS_MUTEX_H
#if defined(__ANDROID__)
#include <utils/linux/Mutex.h>
#else
#include <utils/generic/Mutex.h>
#endif
#endif // TNT_UTILS_MUTEX_H

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_NAMECOMPONENTMANAGER_H
#define TNT_UTILS_NAMECOMPONENTMANAGER_H
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/Entity.h>
#include <utils/EntityInstance.h>
#include <utils/SingleInstanceComponentManager.h>
#include <stddef.h>
#include <stdint.h>
namespace utils {
class EntityManager;
/**
* \class NameComponentManager NameComponentManager.h utils/NameComponentManager.h
* \brief Allows clients to associate string labels with entities.
*
* To access the name of an existing entity, clients should first use NameComponentManager to get a
* temporary handle called an \em instance. Please note that instances are ephemeral; clients should
* store entities, not instances.
*
* Usage example:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* auto names = new NameComponentManager(EntityManager::get());
* names->addComponent(myEntity);
* names->setName(names->getInstance(myEntity), "Jeanne d'Arc");
* ...
* printf("%s\n", names->getName(names->getInstance(myEntity));
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
class UTILS_PUBLIC NameComponentManager : public SingleInstanceComponentManager<utils::CString> {
public:
using Instance = EntityInstance<NameComponentManager>;
/**
* Creates a new name manager associated with the given entity manager.
*
* Note that multiple string labels could be associated with each entity simply by
* creating multiple instances of NameComponentManager.
*/
explicit NameComponentManager(EntityManager& em);
~NameComponentManager();
/**
* Checks if the given entity already has a name component.
*/
using SingleInstanceComponentManager::hasComponent;
/**
* Gets a temporary handle that can be used to access the name.
*
* @return Non-zero handle if the entity has a name component, 0 otherwise.
*/
Instance getInstance(Entity e) const noexcept {
return { SingleInstanceComponentManager::getInstance(e) };
}
/*! \cond PRIVATE */
// these are implemented in SingleInstanceComponentManager<>, but we need to
// reimplement them in each manager, to ensure they are generated in an implementation file
// for backward binary compatibility reasons.
size_t getComponentCount() const noexcept;
Entity const* getEntities() const noexcept;
void gc(const EntityManager& em, size_t ratio = 4) noexcept;
/*! \endcond */
/**
* Adds a name component to the given entity if it doesn't already exist.
*/
void addComponent(Entity e);
/**
* Removes the name component to the given entity if it exists.
*/
void removeComponent(Entity e);
/**
* Stores a copy of the given string and associates it with the given instance.
*/
void setName(Instance instance, const char* name) noexcept;
/**
* Retrieves the string associated with the given instance, or nullptr if none exists.
*
* @return pointer to the copy that was made during setName()
*/
const char* getName(Instance instance) const noexcept;
};
} // namespace utils
#endif // TNT_UTILS_NAMECOMPONENTMANAGER_H

View File

@@ -0,0 +1,563 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_PANIC_H
#define TNT_UTILS_PANIC_H
#include <string>
#include <utils/CallStack.h>
#include <utils/compiler.h>
#ifdef __EXCEPTIONS
# define UTILS_EXCEPTIONS 1
#else
#endif
/**
* @defgroup errors Handling Catastrophic Failures (Panics)
*
* @brief Failure detection and reporting facilities
*
* ## What's a Panic? ##
*
* In the context of this document, a _panic_ is a type of error due to a _contract violation_,
* it shouldn't be confused with a _result_ or _status_ code. The POSIX API for instance,
* unfortunately often conflates the two.
* @see <http://en.wikipedia.org/wiki/Design_by_contract>
*
*
* Here we give the following definition of a _panic_:
*
* 1. Failures to meet a function's own **postconditions**\n
* The function cannot establish one of its own postconditions, such as (but not limited to)
* producing a valid return value object.
*
* Often these failures are only detectable at runtime, for instance they can be caused by
* arithmetic errors, as it was the case for the Ariane 5 rocket. Ariane 5 crashed because it
* reused an inertial module from Ariane 4, which didn't account for the greater horizontal
* acceleration of Ariane 5 and caused an overflow in the computations. Ariane 4's module
* wasn't per-say buggy, but was improperly used and failed to meet, obviously, certain
* postconditions.
* @see <http://en.wikipedia.org/wiki/Cluster_(spacecraft)>
*
* 2. Failures to meet the **preconditions** of any of a function's callees\n
* The function cannot meet a precondition of another function it must call, such as a
* restriction on a parameter.
*
* Not to be confused with the case where the preconditions of a function are already
* violated upon entry, which indicates a programming error from the caller.
*
* Typically these failures can be avoided and arise because of programming errors.
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* ## Failure reporting vs. handling ##
*
* Very often when a panic, as defined above, is detected, the program has little other choice
* but to terminate.\n
* Typically these situations can be handled by _assert()_. However, _assert()_ also conflates two
* very different concepts: detecting and handling failures.\n
* The place where a failure is detected is rarely the place where there is enough
* context to decide what to do. _assert()_ terminates the program which, may or may not be
* appropriate. At the very least the failure must be logged (which _assert()_ does in a crude way),
* but some other actions might need to happen, such as:\n
*
* - logging the failure in the system-wide logger
* - providing enough information in development builds to analyze/debug the problem
* - cleanly releasing some resources, such as communication channels with other processes\n
* e.g.: to avoid their pre- or postconditions from being violated as well.
*
* In some _rare_ cases, the failure might even be ignored altogether because it doesn't matter in
* the context where it happened. This decision clearly doesn't always lie at the failure-site.
*
* It is therefore important to separate failure detection from handling.
*
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* ## Failure detection and handling facilities ##
*
* Clearly, catastrophic failures should be **rare**; in fact they should
* never happen, except possibly for "failures to meet a function's own postconditions", which
* may depend on external factors and should still be very rare. Yet, when a failure happens, it
* must be detected and handled appropriately.\n
* Since panics are rare, it is desirable that the handling mechanism be as unobtrusive
* as possible, without allowing such failures to go unnoticed or swallowed by mistake. Ideally, the
* programmer using an API should have nothing special to do to handle that API's failure
* conditions.\n\n
*
* An important feature of the Panic Handling facilities here is that **panics are not part of
* the API of a function or method**\n\n
*
*
* The panic handling facility has the following benefits:
* - provides an easy way to detect and report failure
* - separates failure detection from handling
* - makes it hard for detected failures to be ignored (i.e.: not handled)
* - doesn't add burden on the API design
* - doesn't add overhead (visual or otherwise) at call sites
* - has very little performance overhead for failure detection
* - has little to no performance impact for failure handling in the common (success) case
* - is flexible and extensible
*
* Since we have established that failures are **rare**, **exceptional** situations, it would be
* appropriate to handle them with an _assert_ mechanism and that's what the API below
* provides. However, under-the-hood it uses C++ exceptions as a means to separate
* _reporting_ from _handling_.
*
* \note On devices where exceptions are not supported or appropriate, these APIs can be turned
* into a regular _std::terminate()_.
*
*
* ASSERT_PRECONDITION(condition, format, ...)
* ASSERT_POSTCONDITION(condition, format, ...)
* ASSERT_ARITHMETIC(condition, format, ...)
* ASSERT_DESTRUCTOR(condition, format, ...)
*
*
* @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
* @see ASSERT_DESTRUCTOR
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* ## Writing code that can assert ##
*
* Because we've separated failure reporting from failure handling, there are some considerations
* that need to be thought about when writing code that calls the macros above (i.e.: the program
* won't terminate at the point where the failure is detected).\n\n
*
*
* ### Panic guarantees ###
*
* After the failure condition is reported by a function, additional guarantees may be provided
* with regards to the state of the program. The following four levels of guarantee are
* generally recognized, each of which is a strict superset of its successors:
*
* 1. Nothrow exception guarantee\n
* The function never asserts. e.g.: This should always be the case with destructors.\n\n
*
* 2. Strong exception guarantee\n
* If the function asserts, the state of the program is rolled back to the state just before
* the function call.\n\n
*
* 3. Basic exception guarantee\n
* If the function asserts, the program is in a valid state. It may require cleanup,
* but all invariants are intact.\n\n
*
* 4. No exception guarantee\n
* If the function asserts, the program may not be in a valid state: resource leaks, memory
* corruption, or other invariant-destroying failures may have occurred.
*
* In each function, give the **strongest** safety guarantee that won't penalize callers who
* don't need it, but **always give at least the basic guarantee**. The RAII (Resource
* Acquisition Is Initialization) pattern can help with achieving these guarantees.
*
* @see [RAII](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)
*
* ### Special considerations for Constructors ###
*
* Constructors are a bit special because if a failure occurs during their execution, the
* destructor won't be called (how could it? since the object wasn't constructed!). This can lead
* to leaked resources allocated in the constructor prior to the failure. Thankfully there is
* a nice C++ syntax to handle this case:
*
* @code
* Foo::Foo(size_t s) try : m_size(s), m_buf(new uint32_t[s]) {
* ASSERT_POSTCONDITION(s&0xF==0,
* "object size is %u, but must be multiple of 16", s);
* } catch (...) {
* delete [] m_buf;
* // the exception will be automatically re-thrown
* }
* @endcode
*
* Unfortunately, this usage leaks the underlying, exception-based, implementation of the
* panic handling macros. For this reason, it is best to keep constructors simple and guarantee
* they can't fail. An _init()_ function with a factory can be used for actual initialization.
*
*
* ### Special considerations for Destructors ###
*
* In C++ destructors cannot throw exceptions and since the above macros internally use exceptions
* they cannot be used in destructors. Doing so will result in immediate termination of the
* program by _std::terminate()_.\n
* It is therefore best to always guarantee that destructors won't fail. In case of such a
* failure in a destructor the ASSERT_DESTRUCTOR() macro can be used instead, it
* will log the failure but won't terminate the program, instead it'll proceed as if nothing
* happened. Generally this will result in some resource leak which, eventually, will cause
* another failure (typically a postcondition violation).\n\n
*
* Rationale for this behavior: There are fundamentally no way to report a failure from a
* destructor in C++, violently terminating the process is inadequate because it again conflates
* failure reporting and failure handling; for instance a failure in glDeleteTextures() shouldn't
* be necessarily fatal (certainly not without saving the user's data first). The alternative
* would be for the caller to swallow the failure entirely, but that's not great either because the
* failure would go unnoticed. The solution retained here is a compromise.
*
* @see ASSERT_DESTRUCTOR
*
* ### Testing Code that Uses Panics ###
*
* Since panics use exceptions for their underlying implementation, you can test code that uses
* panics with EXPECT_THROW by doing the following things:
* \li Set panic mode to THROW (default is TERMINATE)
* \li Pass Panic to EXPECT_THROW as the exception type
*
* Example code for your test file:
*
* @code
* #include <MyClass.hpp> // since your code uses panics, this should include utils/Panic.hpp
*
* using utils::Panic;
*
* TEST(MyClassTest, value_that_causes_panic) {
* EXPECT_THROW(MyClass::function(value_that_causes_panic), Panic);
* }
*
* // ... other tests ...
*
* int main(int argc, char** argv) {
* ::testing::InitGoogleTest(&argc, argv);
* Panic::setMode(Panic::Mode::THROW);
* return RUN_ALL_TESTS();
* }
* @endcode
*
*/
namespace utils {
// -----------------------------------------------------------------------------------------------
/**
* @ingroup errors
*
* \brief Base class of all exceptions thrown by all the ASSERT macros
*
* The Panic class provides the std::exception protocol, it is the base exception object
* used for all thrown exceptions.
*/
class UTILS_PUBLIC Panic {
public:
virtual ~Panic() noexcept;
/**
* @return a detailed description of the error
* @see std::exception
*/
virtual const char* what() const noexcept = 0;
/**
* Get the function name where the panic was detected
* @return a C string containing the function name where the panic was detected
*/
virtual const char* getFunction() const noexcept = 0;
/**
* Get the file name where the panic was detected
* @return a C string containing the file name where the panic was detected
*/
virtual const char* getFile() const noexcept = 0;
/**
* Get the line number in the file where the panic was detected
* @return an integer containing the line number in the file where the panic was detected
*/
virtual int getLine() const noexcept = 0;
/**
* Logs this exception to the system-log
*/
virtual void log() const noexcept = 0;
/**
* Get the CallStack when the panic was detected
* @return the CallStack when the panic was detected
*/
virtual const CallStack& getCallStack() const noexcept = 0;
};
// -----------------------------------------------------------------------------------------------
/**
* @ingroup errors
*
* \brief Concrete implementation of the Panic interface.
*
* The TPanic<> class implements the std::exception protocol as well as the Panic
* interface common to all exceptions thrown by the framework.
*/
template <typename T>
class UTILS_PUBLIC TPanic : public Panic {
public:
// std::exception protocol
const char* what() const noexcept override;
// Panic interface
const char* getFunction() const noexcept override;
const char* getFile() const noexcept override;
int getLine() const noexcept override;
const CallStack& getCallStack() const noexcept override;
void log() const noexcept override;
/**
* Depending on the mode set, either throws an exception of type T with the given reason plus
* extra information about the error-site, or logs the error and calls std::terminate().
* This function never returns.
* @param function the name of the function where the error was detected
* @param file the file where the above function in implemented
* @param line the line in the above file where the error was detected
* @param format printf style string describing the error
* @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
* @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC
* @see setMode()
*/
static void panic(char const* function, char const* file, int line, const char* format, ...)
UTILS_NORETURN;
/**
* Depending on the mode set, either throws an exception of type T with the given reason plus
* extra information about the error-site, or logs the error and calls std::terminate().
* This function never returns.
* @param function the name of the function where the error was detected
* @param file the file where the above function in implemented
* @param line the line in the above file where the error was detected
* @param s std::string describing the error
* @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
* @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC
* @see setMode()
*/
static inline void panic(char const* function, char const* file, int line, const std::string& s)
UTILS_NORETURN {
panic(function, file, line, s.c_str());
}
protected:
/**
* Creates a Panic.
* @param reason a description of the cause of the error
*/
explicit TPanic(std::string reason);
/**
* Creates a Panic with extra information about the error-site.
* @param function the name of the function where the error was detected
* @param file the file where the above function in implemented
* @param line the line in the above file where the error was detected
* @param reason a description of the cause of the error
*/
TPanic(char const* function, char const* file, int line, std::string reason);
~TPanic() override;
private:
void buildMessage();
CallStack m_callstack;
std::string m_reason;
char const* const m_function = nullptr;
char const* const m_file = nullptr;
const int m_line = -1;
mutable std::string m_msg;
};
namespace details {
// these are private, don't use
void panicLog(
char const* function, char const* file, int line, const char* format, ...) noexcept;
} // namespace details
// -----------------------------------------------------------------------------------------------
/**
* @ingroup errors
*
* ASSERT_PRECONDITION uses this Panic to report a precondition failure.
* @see ASSERT_PRECONDITION
*/
class UTILS_PUBLIC PreconditionPanic : public TPanic<PreconditionPanic> {
// Programming error, can be avoided
// e.g.: invalid arguments
using TPanic<PreconditionPanic>::TPanic;
friend class TPanic<PreconditionPanic>;
};
/**
* @ingroup errors
*
* ASSERT_POSTCONDITION uses this Panic to report a postcondition failure.
* @see ASSERT_POSTCONDITION
*/
class UTILS_PUBLIC PostconditionPanic : public TPanic<PostconditionPanic> {
// Usually only detectable at runtime
// e.g.: dead-lock would occur, arithmetic errors
using TPanic<PostconditionPanic>::TPanic;
friend class TPanic<PostconditionPanic>;
};
/**
* @ingroup errors
*
* ASSERT_ARITHMETIC uses this Panic to report an arithmetic (postcondition) failure.
* @see ASSERT_ARITHMETIC
*/
class UTILS_PUBLIC ArithmeticPanic : public TPanic<ArithmeticPanic> {
// A common case of post-condition error
// e.g.: underflow, overflow, internal computations errors
using TPanic<ArithmeticPanic>::TPanic;
friend class TPanic<ArithmeticPanic>;
};
// -----------------------------------------------------------------------------------------------
} // namespace utils
#ifndef NDEBUG
# define PANIC_FILE(F) (F)
# define PANIC_FUNCTION __PRETTY_FUNCTION__
#else
# define PANIC_FILE(F) ""
# define PANIC_FUNCTION __func__
#endif
/**
* PANIC_PRECONDITION is a macro that reports a PreconditionPanic
* @param format printf-style string describing the error in more details
*/
#define PANIC_PRECONDITION(format, ...) \
::utils::PreconditionPanic::panic(PANIC_FUNCTION, \
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
/**
* PANIC_POSTCONDITION is a macro that reports a PostconditionPanic
* @param format printf-style string describing the error in more details
*/
#define PANIC_POSTCONDITION(format, ...) \
::utils::PostconditionPanic::panic(PANIC_FUNCTION, \
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
/**
* PANIC_ARITHMETIC is a macro that reports a ArithmeticPanic
* @param format printf-style string describing the error in more details
*/
#define PANIC_ARITHMETIC(format, ...) \
::utils::ArithmeticPanic::panic(PANIC_FUNCTION, \
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
/**
* PANIC_LOG is a macro that logs a Panic, and continues as usual.
* @param format printf-style string describing the error in more details
*/
#define PANIC_LOG(format, ...) \
::utils::details::panicLog(PANIC_FUNCTION, \
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
/**
* @ingroup errors
*
* ASSERT_PRECONDITION is a macro that checks the given condition and reports a PreconditionPanic
* if it evaluates to false.
* @param cond a boolean expression
* @param format printf-style string describing the error in more details
*/
#define ASSERT_PRECONDITION(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__) : (void)0)
#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
#define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__), false : true)
#else
#define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
#endif
/**
* @ingroup errors
*
* ASSERT_POSTCONDITION is a macro that checks the given condition and reports a PostconditionPanic
* if it evaluates to false.
* @param cond a boolean expression
* @param format printf-style string describing the error in more details
*
* Example:
* @code
* int& Foo::operator[](size_t index) {
* ASSERT_POSTCONDITION(index<m_size, "cannot produce a valid return value");
* return m_array[index];
* }
* @endcode
*/
#define ASSERT_POSTCONDITION(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__) : (void)0)
#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
#define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__), false : true)
#else
#define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
#endif
/**
* @ingroup errors
*
* ASSERT_ARITHMETIC is a macro that checks the given condition and reports a ArithmeticPanic
* if it evaluates to false.
* @param cond a boolean expression
* @param format printf-style string describing the error in more details
*
* Example:
* @code
* unt32_t floatToUInt1616(float v) {
* v *= 65536;
* ASSERT_ARITHMETIC(v>=0 && v<65536, "overflow occurred");
* return uint32_t(v);
* }
* @endcode
*/
#define ASSERT_ARITHMETIC(cond, format, ...) \
(!(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__) : (void)0)
#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
#define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__), false : true)
#else
#define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
#endif
/**
* @ingroup errors
*
* ASSERT_DESTRUCTOR is a macro that checks the given condition and logs an error
* if it evaluates to false.
* @param cond a boolean expression
* @param format printf-style string describing the error in more details
*
* @warning Use this macro if a destructor can fail, which should be avoided at all costs.
* Unlike the other ASSERT macros, this will never result in the process termination. Instead,
* the error will be logged and the program will continue as if nothing happened.
*
* Example:
* @code
* Foo::~Foo() {
* glDeleteTextures(1, &m_texture);
* GLint err = glGetError();
* ASSERT_DESTRUCTOR(err == GL_NO_ERROR, "cannot free GL resource!");
* }
* @endcode
*/
#define ASSERT_DESTRUCTOR(cond, format, ...) (!(cond) ? PANIC_LOG(format, ##__VA_ARGS__) : (void)0)
#endif // TNT_UTILS_PANIC_H

View File

@@ -0,0 +1,297 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_PATH_H
#define TNT_UTILS_PATH_H
#include <utils/compiler.h>
#include <iosfwd>
#include <string>
#include <vector>
namespace utils {
/**
* An abstract representation of file and directory paths.
*/
class UTILS_PUBLIC Path {
public:
/**
* Creates a new empty path.
*/
Path() = default;
~Path() = default;
/**
* Creates a new path with the specified pathname.
*
* @param pathname a non-null pathname string
*/
Path(const char* pathname);
/**
* Creates a new path with the specified pathname.
*
* @param pathname a pathname string view
*/
Path(std::string_view pathname);
/**
* Creates a new path with the specified pathname.
*
* @param pathname a pathname string
*/
Path(const std::string& pathname);
/**
* Tests whether the file or directory denoted by this abstract
* pathname exists.
*
* @return true if the file or directory denoted by this
* abstract pathname exists, false otherwise
*/
bool exists() const;
/**
* Tests whether this abstract pathname represents a regular file.
* This method can only return true if the path exists.
*
* @return true if this pathname represents an existing file,
* false if the path doesn't exist or represents something
* else (directory, symlink, etc.)
*/
bool isFile() const;
/**
* Tests whether this abstract pathname represents a directory.
* This method can only return true if the path exists.
*
* @return true if this pathname represents an existing directory,
* false if the path doesn't exist or represents a file
*/
bool isDirectory() const;
/**
* Tests whether this path is empty. An empty path does not
* exist.
*
* @return true if the underlying abstract pathname is empty,
* false otherwise
*/
bool isEmpty() const { return m_path.empty(); }
const char* c_str() const { return m_path.c_str(); }
/**
* Replaces the abstract pathname of this object with the
* specified pathname.
*
* @param pathname a pathname string
*/
void setPath(const std::string& pathname) {
m_path = getCanonicalPath(pathname);
}
/**
* @return the canonical pathname this path represents
*/
const std::string& getPath() const { return m_path; }
/**
* Returns the parent of this path as Path.
* @return a new path containing the parent of this path
*/
Path getParent() const;
/**
* Returns ancestor path where "0" is the immediate parent.
* @return a new path containing the ancestor of this path
*/
Path getAncestor(int n) const;
/**
* Returns the name of the file or directory represented by
* this abstract pathname.
*
* @return the name of the file or directory represented by
* this abstract pathname, or an empty string if
* this path is empty
*/
std::string getName() const;
/**
* Returns the name of the file or directory represented by
* this abstract pathname without its extension.
*
* @return the name of the file or directory represented by
* this abstract pathname, or an empty string if
* this path is empty
*/
std::string getNameWithoutExtension() const;
/**
* Returns the file extension (after the ".") if one is present.
* Returns the empty string if no filename is present or if the
* path is a directory.
*
* @return the file extension (if one is present and
* this is not a directory), else the empty string.
*/
std::string getExtension() const;
/**
* Returns the absolute representation of this path.
* If this path's pathname starts with a leading '/',
* the returned path is equal to this path. Otherwise,
* this path's pathname is concatenated with the current
* working directory and the result is returned.
*
* @return a new path containing the absolute representation
* of this path
*/
Path getAbsolutePath() const;
/**
* @return true if this abstract pathname is not empty
* and starts with a leading '/', false otherwise
*/
bool isAbsolute() const;
/**
* Splits this object's abstract pathname in a vector of file
* and directory name. If the underlying abstract pathname
* starts with a '/', the returned vector's first element
* will be the string "/".
*
* @return a vector of strings, empty if this path is empty
*/
std::vector<std::string> split() const;
/**
* Concatenates the specified path with this path in a new
* path object.
*
* @note if the pathname to concatenate with starts with
* a leading '/' then that pathname is returned without
* being concatenated to this object's pathname.
*
* @param path the path to concatenate with
*
* @return the concatenation of the two paths
*/
Path concat(const Path& path) const;
/**
* Concatenates the specified path with this path and
* stores the result in this path.
*
* @note if the pathname to concatenate with starts with
* a leading '/' then that pathname replaces this object's
* pathname.
*
* @param path the path to concatenate with
*/
void concatToSelf(const Path& path);
operator std::string const&() const { return m_path; }
Path operator+(const Path& rhs) const { return concat(rhs); }
Path& operator+=(const Path& rhs) {
concatToSelf(rhs);
return *this;
}
bool operator==(const Path& rhs) const { return m_path == rhs.m_path; }
bool operator!=(const Path& rhs) const { return m_path != rhs.m_path; }
bool operator<(const Path& rhs) const { return m_path < rhs.m_path; }
bool operator>(const Path& rhs) const { return m_path > rhs.m_path; }
friend std::ostream& operator<<(std::ostream& os, const Path& path);
/**
* Returns a canonical copy of the specified pathname by removing
* unnecessary path segments such as ".", ".." and "/".
*
* @param pathname a pathname string
*
* @return the canonical representation of the specified pathname
*/
static std::string getCanonicalPath(const std::string& pathname);
/**
* This method is equivalent to calling root.concat(leaf).
*/
static Path concat(const std::string& root, const std::string& leaf);
/**
* @return a path representing the current working directory
*/
static Path getCurrentDirectory();
/**
* @return a path representing the current executable
*/
static Path getCurrentExecutable();
/**
* @return a path representing a directory where temporary files can be stored
*/
static Path getTemporaryDirectory();
/**
* Creates a directory denoted by the given path.
* This is not recursive and doesn't create intermediate directories.
*
* @return True if directory was successfully created.
* When false, errno should have details on actual error.
*/
bool mkdir() const;
/**
* Creates a directory denoted by the given path.
* This is recursive and parent directories will be created if they do not
* exist.
*
* @return True if directory was successfully created or already exists.
* When false, errno should have details on actual error.
*/
bool mkdirRecursive() const;
/**
* Deletes this file.
*
* @return True if file was successfully deleted.
* When false, errno should have details on actual error.
*/
bool unlinkFile();
/**
* Lists the contents of this directory, skipping hidden files.
*
* @return A vector of paths of the contents of the directory. If the path points to a file,
* nonexistent directory, or empty directory, an empty vector is returned.
*/
std::vector<Path> listContents() const;
private:
std::string m_path;
};
} // namespace utils
#endif // TNT_UTILS_PATH_H

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UTILS_PRIVATEIMPLEMENTATION_IMPL_H
#define UTILS_PRIVATEIMPLEMENTATION_IMPL_H
/*
* This looks like a header file, but really it acts as a .cpp, because it's used to
* explicitly instantiate the methods of PrivateImplementation<>
*/
#include <utils/PrivateImplementation.h>
#include <utility>
namespace utils {
template<typename T>
PrivateImplementation<T>::PrivateImplementation() noexcept
: mImpl(new T) {
}
template<typename T>
template<typename ... ARGS>
PrivateImplementation<T>::PrivateImplementation(ARGS&& ... args) noexcept
: mImpl(new T(std::forward<ARGS>(args)...)) {
}
template<typename T>
PrivateImplementation<T>::~PrivateImplementation() noexcept {
delete mImpl;
}
#ifndef UTILS_PRIVATE_IMPLEMENTATION_NON_COPYABLE
template<typename T>
PrivateImplementation<T>::PrivateImplementation(PrivateImplementation const& rhs) noexcept
: mImpl(new T(*rhs.mImpl)) {
}
template<typename T>
PrivateImplementation<T>& PrivateImplementation<T>::operator=(PrivateImplementation<T> const& rhs) noexcept {
if (this != &rhs) {
*mImpl = *rhs.mImpl;
}
return *this;
}
#endif
} // namespace utils
#endif // UTILS_PRIVATEIMPLEMENTATION_IMPL_H

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UTILS_PRIVATEIMPLEMENTATION_H
#define UTILS_PRIVATEIMPLEMENTATION_H
#include <utils/compiler.h>
#include <stddef.h>
namespace utils {
/**
* \privatesection
* PrivateImplementation is used to hide the implementation details of a class and ensure a higher
* level of backward binary compatibility.
* The actual implementation is in src/PrivateImplementation-impl.h"
*/
template<typename T>
class PrivateImplementation {
public:
// none of these methods must be implemented inline because it's important that their
// implementation be hidden from the public headers.
template<typename ... ARGS>
explicit PrivateImplementation(ARGS&& ...) noexcept;
PrivateImplementation() noexcept;
~PrivateImplementation() noexcept;
PrivateImplementation(PrivateImplementation const& rhs) noexcept;
PrivateImplementation& operator = (PrivateImplementation const& rhs) noexcept;
// move ctor and copy operator can be implemented inline and don't need to be exported
PrivateImplementation(PrivateImplementation&& rhs) noexcept : mImpl(rhs.mImpl) { rhs.mImpl = nullptr; }
PrivateImplementation& operator = (PrivateImplementation&& rhs) noexcept {
auto temp = mImpl;
mImpl = rhs.mImpl;
rhs.mImpl = temp;
return *this;
}
protected:
T* mImpl = nullptr;
inline T* operator->() noexcept { return mImpl; }
inline T const* operator->() const noexcept { return mImpl; }
};
} // namespace utils
#endif // UTILS_PRIVATEIMPLEMENTATION_H

View File

@@ -0,0 +1,318 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_SINGLEINSTANCECOMPONENTMANAGER_H
#define TNT_UTILS_SINGLEINSTANCECOMPONENTMANAGER_H
#include <utils/compiler.h>
#include <utils/Entity.h>
#include <utils/EntityInstance.h>
#include <utils/EntityManager.h>
#include <utils/StructureOfArrays.h>
#include <tsl/robin_map.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
namespace utils {
class EntityManager;
/*
* Helper class to create single instance component managers.
*
* This handles the component's storage as a structure-of-arrays, as well
* as the garbage collection.
*
* This is intended to be used as base class for a real component manager. When doing so,
* and the real component manager is a public API, make sure to forward the public methods
* to the implementation.
*
*/
template <typename ... Elements>
class UTILS_PUBLIC SingleInstanceComponentManager {
private:
// this is just to avoid using std::default_random_engine, since we're in a public header.
class default_random_engine {
uint32_t mState = 1u; // must be 0 < seed < 0x7fffffff
public:
inline uint32_t operator()() noexcept {
return mState = uint32_t((uint64_t(mState) * 48271u) % 0x7fffffffu);
}
};
protected:
static constexpr size_t ENTITY_INDEX = sizeof ... (Elements);
public:
using SoA = StructureOfArrays<Elements ..., Entity>;
using Structure = typename SoA::Structure;
using Instance = EntityInstanceBase::Type;
SingleInstanceComponentManager() noexcept {
// We always start with a dummy entry because index=0 is reserved. The component
// at index = 0, is guaranteed to be default-initialized.
// Sub-classes can use this to their advantage.
mData.push_back(Structure{});
}
SingleInstanceComponentManager(SingleInstanceComponentManager&&) noexcept {/* = default */}
SingleInstanceComponentManager& operator=(SingleInstanceComponentManager&&) noexcept {/* = default */}
~SingleInstanceComponentManager() noexcept = default;
// not copyable
SingleInstanceComponentManager(SingleInstanceComponentManager const& rhs) = delete;
SingleInstanceComponentManager& operator=(SingleInstanceComponentManager const& rhs) = delete;
// returns true if the given Entity has a component of this Manager
bool hasComponent(Entity e) const noexcept {
return getInstance(e) != 0;
}
// Get instance of this Entity to be used to retrieve components
UTILS_NOINLINE
Instance getInstance(Entity e) const noexcept {
auto const& map = mInstanceMap;
// find() generates quite a bit of code
auto pos = map.find(e);
return pos != map.end() ? pos->second : 0;
}
// returns the number of components (i.e. size of each arrays)
size_t getComponentCount() const noexcept {
// The array as an extra dummy component at index 0, so the visible count is 1 less.
return mData.size() - 1;
}
bool empty() const noexcept {
return getComponentCount() == 0;
}
// returns a pointer to the Entity array. This is basically the list
// of entities this component manager handles.
// The pointer becomes invalid when adding or removing a component.
Entity const* getEntities() const noexcept {
return begin<ENTITY_INDEX>();
}
Entity getEntity(Instance i) const noexcept {
return elementAt<ENTITY_INDEX>(i);
}
// Add a component to the given Entity. If the entity already has a component from this
// manager, this function is a no-op.
// This invalidates all pointers components.
inline Instance addComponent(Entity e);
// Removes a component from the given entity.
// This invalidates all pointers components.
inline Instance removeComponent(Entity e);
// trigger one round of garbage collection. this is intended to be called on a regular
// basis. This gc gives up after it cannot randomly free 'ratio' component in a row.
void gc(const EntityManager& em, size_t ratio = 4) noexcept {
gc(em, ratio, [this](Entity e) {
removeComponent(e);
});
}
// return the first instance
Instance begin() const noexcept { return 1u; }
// return the past-the-last instance
Instance end() const noexcept { return Instance(begin() + getComponentCount()); }
// return a pointer to the first element of the ElementIndex'th array
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>* begin() noexcept {
return mData.template data<ElementIndex>() + 1;
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* begin() const noexcept {
return mData.template data<ElementIndex>() + 1;
}
// return a pointer to the past-the-end element of the ElementIndex'th array
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>* end() noexcept {
return begin<ElementIndex>() + getComponentCount();
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* end() const noexcept {
return begin<ElementIndex>() + getComponentCount();
}
// return a Slice<>
template<size_t ElementIndex>
Slice<typename SoA::template TypeAt<ElementIndex>> slice() noexcept {
return { begin<ElementIndex>(), end<ElementIndex>() };
}
template<size_t ElementIndex>
Slice<const typename SoA::template TypeAt<ElementIndex>> slice() const noexcept {
return { begin<ElementIndex>(), end<ElementIndex>() };
}
// return a reference to the index'th element of the ElementIndex'th array
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>& elementAt(Instance index) noexcept {
assert(index);
return data<ElementIndex>()[index];
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const& elementAt(Instance index) const noexcept {
assert(index);
return data<ElementIndex>()[index];
}
// returns a pointer to the RAW ARRAY of components including the first dummy component
// Use with caution.
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* raw_array() const noexcept {
return data<ElementIndex>();
}
// We need our own version of Field because mData is private
template<size_t E>
struct Field : public SoA::template Field<E> {
Field(SingleInstanceComponentManager& soa, EntityInstanceBase::Type i) noexcept
: SoA::template Field<E>{ soa.mData, i } {
}
using SoA::template Field<E>::operator =;
};
protected:
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>* data() noexcept {
return mData.template data<ElementIndex>();
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* data() const noexcept {
return mData.template data<ElementIndex>();
}
// swap only internals
void swap(Instance i, Instance j) noexcept {
assert(i);
assert(j);
if (i && j) {
// update the index map
auto& map = mInstanceMap;
Entity& ei = elementAt<ENTITY_INDEX>(i);
Entity& ej = elementAt<ENTITY_INDEX>(j);
std::swap(ei, ej);
if (ei) {
map[ei] = i;
}
if (ej) {
map[ej] = j;
}
}
}
template<typename REMOVE>
void gc(const EntityManager& em, size_t ratio,
REMOVE removeComponent) noexcept {
Entity const* entities = getEntities();
size_t count = getComponentCount();
size_t aliveInARow = 0;
default_random_engine& rng = mRng;
UTILS_NOUNROLL
while (count && aliveInARow < ratio) {
// note: using the modulo favorizes lower number
size_t i = rng() % count;
if (UTILS_LIKELY(em.isAlive(entities[i]))) {
++aliveInARow;
continue;
}
aliveInARow = 0;
count--;
removeComponent(entities[i]);
}
}
protected:
SoA mData;
private:
// maps an entity to an instance index
tsl::robin_map<Entity, Instance, Entity::Hasher> mInstanceMap;
default_random_engine mRng;
};
// Keep these outside of the class because CLion has trouble parsing them
template<typename ... Elements>
typename SingleInstanceComponentManager<Elements ...>::Instance
SingleInstanceComponentManager<Elements ...>::addComponent(Entity e) {
Instance ci = 0;
if (!e.isNull()) {
if (!hasComponent(e)) {
// this is like a push_back(e);
mData.push_back(Structure{}).template back<ENTITY_INDEX>() = e;
// index 0 is used when the component doesn't exist
ci = Instance(mData.size() - 1);
mInstanceMap[e] = ci;
} else {
// if the entity already has this component, just return its instance
ci = mInstanceMap[e];
}
}
assert(ci != 0);
return ci;
}
// Keep these outside of the class because CLion has trouble parsing them
template <typename ... Elements>
typename SingleInstanceComponentManager<Elements ...>::Instance
SingleInstanceComponentManager<Elements ... >::removeComponent(Entity e) {
auto& map = mInstanceMap;
auto pos = map.find(e);
if (UTILS_LIKELY(pos != map.end())) {
size_t index = pos->second;
assert(index != 0);
size_t last = mData.size() - 1;
if (last != index) {
// move the last item to where we removed this component, as to keep
// the array tightly packed.
mData.forEach([index, last](auto* p) {
p[index] = std::move(p[last]);
});
Entity lastEntity = mData.template elementAt<ENTITY_INDEX>(index);
map[lastEntity] = index;
}
mData.pop_back();
map.erase(pos);
return last;
}
return 0;
}
} // namespace filament
#endif // TNT_UTILS_SINGLEINSTANCECOMPONENTMANAGER_H

View File

@@ -0,0 +1,148 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_SLICE_H
#define TNT_UTILS_SLICE_H
#include <utils/compiler.h>
#include <utility>
#include <assert.h>
#include <stddef.h>
namespace utils {
/*
* A fixed-size slice of a container
*/
template <typename T>
class Slice {
public:
using iterator = T*;
using const_iterator = T const*;
using value_type = T;
using reference = T&;
using const_reference = T const&;
using pointer = T*;
using const_pointer = T const*;
using size_type = size_t;
Slice() noexcept = default;
Slice(const_iterator begin, const_iterator end) noexcept
: mBegin(const_cast<iterator>(begin)),
mEnd(const_cast<iterator>(end)) {
}
Slice(const_pointer begin, size_type count) noexcept
: mBegin(const_cast<iterator>(begin)),
mEnd(mBegin + count) {
}
Slice(Slice const& rhs) noexcept = default;
Slice(Slice&& rhs) noexcept = default;
Slice& operator=(Slice const& rhs) noexcept = default;
Slice& operator=(Slice&& rhs) noexcept = default;
void set(pointer begin, size_type count) UTILS_RESTRICT noexcept {
mBegin = begin;
mEnd = begin + count;
}
void set(iterator begin, iterator end) UTILS_RESTRICT noexcept {
mBegin = begin;
mEnd = end;
}
void swap(Slice& rhs) UTILS_RESTRICT noexcept {
std::swap(mBegin, rhs.mBegin);
std::swap(mEnd, rhs.mEnd);
}
void clear() UTILS_RESTRICT noexcept {
mBegin = nullptr;
mEnd = nullptr;
}
// size
size_t size() const UTILS_RESTRICT noexcept { return mEnd - mBegin; }
size_t sizeInBytes() const UTILS_RESTRICT noexcept { return size() * sizeof(T); }
bool empty() const UTILS_RESTRICT noexcept { return size() == 0; }
// iterators
iterator begin() UTILS_RESTRICT noexcept { return mBegin; }
const_iterator begin() const UTILS_RESTRICT noexcept { return mBegin; }
const_iterator cbegin() const UTILS_RESTRICT noexcept { return this->begin(); }
iterator end() UTILS_RESTRICT noexcept { return mEnd; }
const_iterator end() const UTILS_RESTRICT noexcept { return mEnd; }
const_iterator cend() const UTILS_RESTRICT noexcept { return this->end(); }
// data access
reference operator[](size_t n) UTILS_RESTRICT noexcept {
assert(n < size());
return mBegin[n];
}
const_reference operator[](size_t n) const UTILS_RESTRICT noexcept {
assert(n < size());
return mBegin[n];
}
reference at(size_t n) UTILS_RESTRICT noexcept {
return operator[](n);
}
const_reference at(size_t n) const UTILS_RESTRICT noexcept {
return operator[](n);
}
reference front() UTILS_RESTRICT noexcept {
assert(!empty());
return *mBegin;
}
const_reference front() const UTILS_RESTRICT noexcept {
assert(!empty());
return *mBegin;
}
reference back() UTILS_RESTRICT noexcept {
assert(!empty());
return *(this->end() - 1);
}
const_reference back() const UTILS_RESTRICT noexcept {
assert(!empty());
return *(this->end() - 1);
}
pointer data() UTILS_RESTRICT noexcept {
return this->begin();
}
const_pointer data() const UTILS_RESTRICT noexcept {
return this->begin();
}
protected:
iterator mBegin = nullptr;
iterator mEnd = nullptr;
};
} // namespace utils
#endif // TNT_UTILS_SLICE_H

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_SPINLOCK_H
#define TNT_UTILS_SPINLOCK_H
#include <utils/compiler.h>
#include <utils/Mutex.h>
#include <atomic>
#include <type_traits>
#include <assert.h>
#include <stddef.h>
namespace utils {
namespace details {
class SpinLock {
std::atomic_flag mLock = ATOMIC_FLAG_INIT;
public:
void lock() noexcept {
UTILS_PREFETCHW(&mLock);
#ifdef __ARM_ACLE
// we signal an event on this CPU, so that the first yield() will be a no-op,
// and falls through the test_and_set(). This is more efficient than a while { }
// construct.
UTILS_SIGNAL_EVENT();
do {
yield();
} while (mLock.test_and_set(std::memory_order_acquire));
#else
goto start;
do {
yield();
start: ;
} while (mLock.test_and_set(std::memory_order_acquire));
#endif
}
void unlock() noexcept {
mLock.clear(std::memory_order_release);
#ifdef __ARM_ARCH_7A__
// on ARMv7a SEL is needed
UTILS_SIGNAL_EVENT();
// as well as a memory barrier is needed
__dsb(0xA); // ISHST = 0xA (b1010)
#else
// on ARMv8 we could avoid the call to SE, but we'd need to write the
// test_and_set() above by hand, so the WFE only happens without a STRX first.
UTILS_BROADCAST_EVENT();
#endif
}
private:
inline void yield() noexcept {
// on x86 call pause instruction, on ARM call WFE
UTILS_WAIT_FOR_EVENT();
}
};
} // namespace details
#if UTILS_HAS_SANITIZE_THREAD
// Active spins with atomics slow down execution too much under ThreadSanitizer.
using SpinLock = Mutex;
#elif defined(__ARM_ARCH_7A__)
// We've had problems with "wfe" on some ARM-V7 devices, causing spurious SIGILL
using SpinLock = Mutex;
#else
using SpinLock = details::SpinLock;
#endif
} // namespace utils
#endif // TNT_UTILS_SPINLOCK_H

View File

@@ -0,0 +1,691 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_STRUCTUREOFARRAYS_H
#define TNT_UTILS_STRUCTUREOFARRAYS_H
#include <utils/Allocator.h>
#include <utils/compiler.h>
#include <utils/Slice.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <array> // note: this is safe, see how std::array is used below (inline / private)
#include <cstddef>
#include <iterator> // for std::random_access_iterator_tag
#include <tuple>
#include <utility>
namespace utils {
template <typename Allocator, typename ... Elements>
class StructureOfArraysBase {
// number of elements
static constexpr const size_t kArrayCount = sizeof...(Elements);
public:
using SoA = StructureOfArraysBase<Allocator, Elements...>;
using Structure = std::tuple<Elements...>;
// Type of the Nth array
template<size_t N>
using TypeAt = typename std::tuple_element_t<N, Structure>;
// Number of arrays
static constexpr size_t getArrayCount() noexcept { return kArrayCount; }
// Size needed to store "size" array elements
static size_t getNeededSize(size_t size) noexcept {
return getOffset(kArrayCount - 1, size) + sizeof(TypeAt<kArrayCount - 1>) * size;
}
// --------------------------------------------------------------------------------------------
class IteratorValue;
template<typename T> class Iterator;
using iterator = Iterator<StructureOfArraysBase*>;
using const_iterator = Iterator<StructureOfArraysBase const*>;
using size_type = size_t;
using difference_type = ptrdiff_t;
/*
* An object that represents a reference to the type dereferenced by iterator.
* In other words, it's the return type of iterator::operator*(), and since it
* cannot be a C++ reference (&), it's an object that acts like it.
*/
class IteratorValueRef {
friend class IteratorValue;
friend iterator;
friend const_iterator;
StructureOfArraysBase* const UTILS_RESTRICT soa;
size_t const index;
IteratorValueRef(StructureOfArraysBase* soa, size_t index) : soa(soa), index(index) { }
// assigns a value_type to a reference (i.e. assigns to what's pointed to by the reference)
template<size_t ... Is>
IteratorValueRef& assign(IteratorValue const& rhs, std::index_sequence<Is...>);
// assigns a value_type to a reference (i.e. assigns to what's pointed to by the reference)
template<size_t ... Is>
IteratorValueRef& assign(IteratorValue&& rhs, std::index_sequence<Is...>) noexcept;
// objects pointed to by reference can be swapped, so provide the special swap() function.
friend void swap(IteratorValueRef lhs, IteratorValueRef rhs) {
lhs.soa->swap(lhs.index, rhs.index);
}
public:
// references can be created by copy-assignment only
IteratorValueRef(IteratorValueRef const& rhs) noexcept : soa(rhs.soa), index(rhs.index) { }
// copy the content of a reference to the content of this one
IteratorValueRef& operator=(IteratorValueRef const& rhs);
// move the content of a reference to the content of this one
IteratorValueRef& operator=(IteratorValueRef&& rhs) noexcept;
// copy a value_type to the content of this reference
IteratorValueRef& operator=(IteratorValue const& rhs) {
return assign(rhs, std::make_index_sequence<kArrayCount>());
}
// move a value_type to the content of this reference
IteratorValueRef& operator=(IteratorValue&& rhs) noexcept {
return assign(rhs, std::make_index_sequence<kArrayCount>());
}
// access the elements of this reference (i.e. the "fields" of the structure)
template<size_t I> TypeAt<I> const& get() const { return soa->elementAt<I>(index); }
template<size_t I> TypeAt<I>& get() { return soa->elementAt<I>(index); }
};
/*
* The value_type of iterator. This is basically the "structure" of the SoA.
* Internally we're using a tuple<> to store the data.
* This object is not trivial to construct, as it copies an entry of the SoA.
*/
class IteratorValue {
friend class IteratorValueRef;
friend iterator;
friend const_iterator;
using Type = std::tuple<typename std::decay<Elements>::type...>;
Type elements;
template<size_t ... Is>
static Type init(IteratorValueRef const& rhs, std::index_sequence<Is...>) {
return Type{ rhs.soa->template elementAt<Is>(rhs.index)... };
}
template<size_t ... Is>
static Type init(IteratorValueRef&& rhs, std::index_sequence<Is...>) noexcept {
return Type{ std::move(rhs.soa->template elementAt<Is>(rhs.index))... };
}
public:
IteratorValue(IteratorValue const& rhs) = default;
IteratorValue(IteratorValue&& rhs) noexcept = default;
IteratorValue& operator=(IteratorValue const& rhs) = default;
IteratorValue& operator=(IteratorValue&& rhs) noexcept = default;
// initialize and assign from a StructureRef
IteratorValue(IteratorValueRef const& rhs)
: elements(init(rhs, std::make_index_sequence<kArrayCount>())) {}
IteratorValue(IteratorValueRef&& rhs) noexcept
: elements(init(rhs, std::make_index_sequence<kArrayCount>())) {}
IteratorValue& operator=(IteratorValueRef const& rhs) { return operator=(IteratorValue(rhs)); }
IteratorValue& operator=(IteratorValueRef&& rhs) noexcept { return operator=(IteratorValue(rhs)); }
// access the elements of this value_Type (i.e. the "fields" of the structure)
template<size_t I> TypeAt<I> const& get() const { return std::get<I>(elements); }
template<size_t I> TypeAt<I>& get() { return std::get<I>(elements); }
};
/*
* An iterator to the SoA. This is only intended to be used with STL's algorithm, e.g.: sort().
* Normally, SoA is not iterated globally, but rather an array at a time.
* Iterating itself is not too costly, as well as dereferencing by reference. However,
* dereferencing by value is.
*/
template<typename CVQualifiedSOAPointer>
class Iterator {
friend class StructureOfArraysBase;
CVQualifiedSOAPointer soa; // don't use restrict, can have aliases if multiple iterators are created
size_t index;
Iterator(CVQualifiedSOAPointer soa, size_t index) : soa(soa), index(index) {}
public:
using value_type = IteratorValue;
using reference = IteratorValueRef;
using pointer = IteratorValueRef*; // FIXME: this should be a StructurePtr type
using difference_type = ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
Iterator(Iterator const& rhs) noexcept = default;
Iterator& operator=(Iterator const& rhs) = default;
reference operator*() const { return { soa, index }; }
reference operator*() { return { soa, index }; }
reference operator[](size_t n) { return *(*this + n); }
template<size_t I> TypeAt<I> const& get() const { return soa->template elementAt<I>(index); }
template<size_t I> TypeAt<I>& get() { return soa->template elementAt<I>(index); }
Iterator& operator++() { ++index; return *this; }
Iterator& operator--() { --index; return *this; }
Iterator& operator+=(size_t n) { index += n; return *this; }
Iterator& operator-=(size_t n) { index -= n; return *this; }
Iterator operator+(size_t n) const { return { soa, index + n }; }
Iterator operator-(size_t n) const { return { soa, index - n }; }
difference_type operator-(Iterator const& rhs) const { return index - rhs.index; }
bool operator==(Iterator const& rhs) const { return (index == rhs.index); }
bool operator!=(Iterator const& rhs) const { return (index != rhs.index); }
bool operator>=(Iterator const& rhs) const { return (index >= rhs.index); }
bool operator> (Iterator const& rhs) const { return (index > rhs.index); }
bool operator<=(Iterator const& rhs) const { return (index <= rhs.index); }
bool operator< (Iterator const& rhs) const { return (index < rhs.index); }
// Postfix operator needed by Microsoft STL.
const Iterator operator++(int) { Iterator it(*this); index++; return it; }
const Iterator operator--(int) { Iterator it(*this); index--; return it; }
};
iterator begin() noexcept { return { this, 0u }; }
iterator end() noexcept { return { this, mSize }; }
const_iterator begin() const noexcept { return { this, 0u }; }
const_iterator end() const noexcept { return { this, mSize }; }
// --------------------------------------------------------------------------------------------
StructureOfArraysBase() = default;
explicit StructureOfArraysBase(size_t capacity) {
setCapacity(capacity);
}
// not copyable for now
StructureOfArraysBase(StructureOfArraysBase const& rhs) = delete;
StructureOfArraysBase& operator=(StructureOfArraysBase const& rhs) = delete;
// movability is trivial, so support it
StructureOfArraysBase(StructureOfArraysBase&& rhs) noexcept {
using std::swap;
swap(mCapacity, rhs.mCapacity);
swap(mSize, rhs.mSize);
swap(mArrays, rhs.mArrays);
swap(mAllocator, rhs.mAllocator);
}
StructureOfArraysBase& operator=(StructureOfArraysBase&& rhs) noexcept {
if (this != &rhs) {
using std::swap;
swap(mCapacity, rhs.mCapacity);
swap(mSize, rhs.mSize);
swap(mArrays, rhs.mArrays);
swap(mAllocator, rhs.mAllocator);
}
return *this;
}
~StructureOfArraysBase() {
destroy_each(0, mSize);
mAllocator.free(std::get<0>(mArrays));
}
// --------------------------------------------------------------------------------------------
// return the size the array
size_t size() const noexcept {
return mSize;
}
// return the capacity of the array
size_t capacity() const noexcept {
return mCapacity;
}
// set the capacity of the array. the capacity cannot be smaller than the current size,
// the call is a no-op in that case.
UTILS_NOINLINE
void setCapacity(size_t capacity) {
// allocate enough space for "capacity" elements of each array
// capacity cannot change when optional storage is specified
if (capacity >= mSize) {
// TODO: not entirely sure if "max" of all alignments is always correct
constexpr size_t align = std::max({ std::max(alignof(std::max_align_t), alignof(Elements))... });
const size_t sizeNeeded = getNeededSize(capacity);
void* buffer = mAllocator.alloc(sizeNeeded, align);
auto const oldBuffer = std::get<0>(mArrays);
// move all the items (one array at a time) from the old allocation to the new
// this also update the array pointers
move_each(buffer, capacity);
// free the old buffer
mAllocator.free(oldBuffer);
// and make sure to update the capacity
mCapacity = capacity;
}
}
void ensureCapacity(size_t needed) {
if (UTILS_UNLIKELY(needed > mCapacity)) {
// not enough space, increase the capacity
const size_t capacity = (needed * 3 + 1) / 2;
setCapacity(capacity);
}
}
// grow or shrink the array to the given size. When growing, new elements are constructed
// with their default constructor. when shrinking, discarded elements are destroyed.
// If the arrays don't have enough capacity, the capacity is increased accordingly
// (the capacity is set to 3/2 of the asked size).
UTILS_NOINLINE
void resize(size_t needed) {
ensureCapacity(needed);
resizeNoCheck(needed);
if (needed <= mCapacity) {
// TODO: see if we should shrink the arrays
}
}
void clear() noexcept {
resizeNoCheck(0);
}
inline void swap(size_t i, size_t j) noexcept {
forEach([i, j](auto p) {
using std::swap;
swap(p[i], p[j]);
});
}
// remove and destroy the last element of each array
inline void pop_back() noexcept {
if (mSize) {
destroy_each(mSize - 1, mSize);
mSize--;
}
}
// create an element at the end of each array
StructureOfArraysBase& push_back() noexcept {
resize(mSize + 1);
return *this;
}
StructureOfArraysBase& push_back(Structure&& args) noexcept {
ensureCapacity(mSize + 1);
return push_back_unsafe(std::forward<Structure>(args));
}
StructureOfArraysBase& push_back(Elements const& ... args) noexcept {
ensureCapacity(mSize + 1);
return push_back_unsafe(args...);
}
StructureOfArraysBase& push_back(Elements&& ... args) noexcept {
ensureCapacity(mSize + 1);
return push_back_unsafe(std::forward<Elements>(args)...);
}
// in C++20 we could use a lambda with explicit template parameter instead
struct PushBackUnsafeClosure {
size_t last;
std::tuple<Elements...> args;
inline explicit PushBackUnsafeClosure(size_t last, Structure&& args)
: last(last), args(std::forward<Structure>(args)) {}
template<size_t I>
inline void operator()(TypeAt<I>* p) {
new(p + last) TypeAt<I>{ std::get<I>(args) };
}
};
StructureOfArraysBase& push_back_unsafe(Structure&& args) noexcept {
for_each_index(mArrays,
PushBackUnsafeClosure{ mSize++, std::forward<Structure>(args) });
return *this;
}
StructureOfArraysBase& push_back_unsafe(Elements const& ... args) noexcept {
for_each_index(mArrays,
PushBackUnsafeClosure{ mSize++, { args... } });
return *this;
}
StructureOfArraysBase& push_back_unsafe(Elements&& ... args) noexcept {
for_each_index(mArrays,
PushBackUnsafeClosure{ mSize++, { std::forward<Elements>(args)... }});
return *this;
}
template<typename F, typename ... ARGS>
void forEach(F&& f, ARGS&& ... args) {
for_each(mArrays, [&](size_t, auto* p) {
f(p, std::forward<ARGS>(args)...);
});
}
// return a pointer to the first element of the ElementIndex]th array
template<size_t ElementIndex>
TypeAt<ElementIndex>* data() noexcept {
return std::get<ElementIndex>(mArrays);
}
template<size_t ElementIndex>
TypeAt<ElementIndex> const* data() const noexcept {
return std::get<ElementIndex>(mArrays);
}
template<size_t ElementIndex>
TypeAt<ElementIndex>* begin() noexcept {
return std::get<ElementIndex>(mArrays);
}
template<size_t ElementIndex>
TypeAt<ElementIndex> const* begin() const noexcept {
return std::get<ElementIndex>(mArrays);
}
template<size_t ElementIndex>
TypeAt<ElementIndex>* end() noexcept {
return std::get<ElementIndex>(mArrays) + size();
}
template<size_t ElementIndex>
TypeAt<ElementIndex> const* end() const noexcept {
return std::get<ElementIndex>(mArrays) + size();
}
template<size_t ElementIndex>
Slice<TypeAt<ElementIndex>> slice() noexcept {
return { begin<ElementIndex>(), end<ElementIndex>() };
}
template<size_t ElementIndex>
Slice<const TypeAt<ElementIndex>> slice() const noexcept {
return { begin<ElementIndex>(), end<ElementIndex>() };
}
// return a reference to the index'th element of the ElementIndex'th array
template<size_t ElementIndex>
TypeAt<ElementIndex>& elementAt(size_t index) noexcept {
return data<ElementIndex>()[index];
}
template<size_t ElementIndex>
TypeAt<ElementIndex> const& elementAt(size_t index) const noexcept {
return data<ElementIndex>()[index];
}
// return a reference to the last element of the ElementIndex'th array
template<size_t ElementIndex>
TypeAt<ElementIndex>& back() noexcept {
return data<ElementIndex>()[size() - 1];
}
template<size_t ElementIndex>
TypeAt<ElementIndex> const& back() const noexcept {
return data<ElementIndex>()[size() - 1];
}
template <size_t E, typename IndexType = uint32_t>
struct Field {
SoA& soa;
IndexType i;
using Type = typename SoA::template TypeAt<E>;
UTILS_ALWAYS_INLINE Field& operator = (Field&& rhs) noexcept {
soa.elementAt<E>(i) = soa.elementAt<E>(rhs.i);
return *this;
}
// auto-conversion to the field's type
UTILS_ALWAYS_INLINE operator Type&() noexcept {
return soa.elementAt<E>(i);
}
UTILS_ALWAYS_INLINE operator Type const&() const noexcept {
return soa.elementAt<E>(i);
}
// dereferencing the selected field
UTILS_ALWAYS_INLINE Type& operator ->() noexcept {
return soa.elementAt<E>(i);
}
UTILS_ALWAYS_INLINE Type const& operator ->() const noexcept {
return soa.elementAt<E>(i);
}
// address-of the selected field
UTILS_ALWAYS_INLINE Type* operator &() noexcept {
return &soa.elementAt<E>(i);
}
UTILS_ALWAYS_INLINE Type const* operator &() const noexcept {
return &soa.elementAt<E>(i);
}
// assignment to the field
UTILS_ALWAYS_INLINE Type const& operator = (Type const& other) noexcept {
return (soa.elementAt<E>(i) = other);
}
UTILS_ALWAYS_INLINE Type const& operator = (Type&& other) noexcept {
return (soa.elementAt<E>(i) = other);
}
// comparisons
UTILS_ALWAYS_INLINE bool operator==(Type const& other) const {
return (soa.elementAt<E>(i) == other);
}
UTILS_ALWAYS_INLINE bool operator!=(Type const& other) const {
return (soa.elementAt<E>(i) != other);
}
// calling the field
template <typename ... ARGS>
UTILS_ALWAYS_INLINE decltype(auto) operator()(ARGS&& ... args) noexcept {
return soa.elementAt<E>(i)(std::forward<ARGS>(args)...);
}
template <typename ... ARGS>
UTILS_ALWAYS_INLINE decltype(auto) operator()(ARGS&& ... args) const noexcept {
return soa.elementAt<E>(i)(std::forward<ARGS>(args)...);
}
};
private:
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
for_each(std::tuple<Tp...>&, FuncT) {}
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_each(std::tuple<Tp...>& t, FuncT f) {
f(I, std::get<I>(t));
for_each<I + 1, FuncT, Tp...>(t, f);
}
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
for_each_index(std::tuple<Tp...>&, FuncT) {}
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_each_index(std::tuple<Tp...>& t, FuncT f) {
f.template operator()<I>(std::get<I>(t));
for_each_index<I + 1, FuncT, Tp...>(t, f);
}
inline void resizeNoCheck(size_t needed) noexcept {
assert(mCapacity >= needed);
if (needed < mSize) {
// we shrink the arrays
destroy_each(needed, mSize);
} else if (needed > mSize) {
// we grow the arrays
construct_each(mSize, needed);
}
// record the new size of the arrays
mSize = needed;
}
// this calculates the offset adjusted for all data alignment of a given array
static inline size_t getOffset(size_t index, size_t capacity) noexcept {
auto offsets = getOffsets(capacity);
return offsets[index];
}
static inline std::array<size_t, kArrayCount> getOffsets(size_t capacity) noexcept {
// compute the required size of each array
const size_t sizes[] = { (sizeof(Elements) * capacity)... };
// we align each array to at least the same alignment guaranteed by malloc
constexpr size_t const alignments[] = { std::max(alignof(std::max_align_t), alignof(Elements))... };
// hopefully most of this gets unrolled and inlined
std::array<size_t, kArrayCount> offsets;
offsets[0] = 0;
UTILS_UNROLL
for (size_t i = 1; i < kArrayCount; i++) {
size_t unalignment = (offsets[i - 1] + sizes[i - 1]) % alignments[i];
size_t alignment = unalignment ? (alignments[i] - unalignment) : 0;
offsets[i] = offsets[i - 1] + (sizes[i - 1] + alignment);
assert_invariant(offsets[i] % alignments[i] == 0);
}
return offsets;
}
void construct_each(size_t from, size_t to) noexcept {
forEach([from, to](auto p) {
using T = typename std::decay<decltype(*p)>::type;
// note: scalar types like int/float get initialized to zero
if constexpr (!std::is_trivially_default_constructible_v<T>) {
for (size_t i = from; i < to; i++) {
new(p + i) T();
}
}
});
}
void destroy_each(size_t from, size_t to) noexcept {
forEach([from, to](auto p) {
using T = typename std::decay<decltype(*p)>::type;
if constexpr (!std::is_trivially_destructible_v<T>) {
for (size_t i = from; i < to; i++) {
p[i].~T();
}
}
});
}
void move_each(void* buffer, size_t capacity) noexcept {
auto offsets = getOffsets(capacity);
size_t index = 0;
if (mSize) {
auto size = mSize; // placate a compiler warning
forEach([buffer, &index, &offsets, size](auto p) {
using T = typename std::decay<decltype(*p)>::type;
T* UTILS_RESTRICT b = static_cast<T*>(buffer);
// go through each element and move them from the old array to the new
// then destroy them from the old array
T* UTILS_RESTRICT const arrayPointer =
reinterpret_cast<T*>(uintptr_t(b) + offsets[index]);
// for trivial cases, just call memcpy()
if constexpr (std::is_trivially_copyable_v<T> &&
std::is_trivially_destructible_v<T>) {
memcpy(arrayPointer, p, size * sizeof(T));
} else {
for (size_t i = 0; i < size; i++) {
// we move an element by using the in-place move-constructor
new(arrayPointer + i) T(std::move(p[i]));
if constexpr (!std::is_trivially_destructible_v<T>) {
// and delete them by calling the destructor directly
p[i].~T();
}
}
}
index++;
});
}
// update the pointers
for_each(mArrays, [buffer, &offsets](size_t i, auto&& p) {
using Type = std::remove_reference_t<decltype(p)>;
p = Type((char*)buffer + offsets[i]);
});
}
// capacity in array elements
size_t mCapacity = 0;
// size in array elements
size_t mSize = 0;
// N pointers to each arrays
std::tuple<std::add_pointer_t<Elements>...> mArrays{};
Allocator mAllocator;
};
template<typename Allocator, typename... Elements>
inline
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::operator=(
StructureOfArraysBase::IteratorValueRef const& rhs) {
return operator=(IteratorValue(rhs));
}
template<typename Allocator, typename... Elements>
inline
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::operator=(
StructureOfArraysBase::IteratorValueRef&& rhs) noexcept {
return operator=(IteratorValue(rhs));
}
template<typename Allocator, typename... Elements>
template<size_t... Is>
inline
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::assign(
StructureOfArraysBase::IteratorValue const& rhs, std::index_sequence<Is...>) {
// implements IteratorValueRef& IteratorValueRef::operator=(IteratorValue const& rhs)
auto UTILS_UNUSED l = { (soa->elementAt<Is>(index) = std::get<Is>(rhs.elements), 0)... };
return *this;
}
template<typename Allocator, typename... Elements>
template<size_t... Is>
inline
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::assign(
StructureOfArraysBase::IteratorValue&& rhs, std::index_sequence<Is...>) noexcept {
// implements IteratorValueRef& IteratorValueRef::operator=(IteratorValue&& rhs) noexcept
auto UTILS_UNUSED l = {
(soa->elementAt<Is>(index) = std::move(std::get<Is>(rhs.elements)), 0)... };
return *this;
}
template <typename ... Elements>
using StructureOfArrays = StructureOfArraysBase<HeapArena<>, Elements ...>;
} // namespace utils
#endif // TNT_UTILS_STRUCTUREOFARRAYS_H

View File

@@ -0,0 +1,214 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ALGORITHM_H
#define TNT_UTILS_ALGORITHM_H
#include <utils/compiler.h>
#include <type_traits> // for std::enable_if
#include <limits.h>
#include <stdint.h>
namespace utils {
namespace details {
template<typename T>
constexpr inline T popcount(T v) noexcept {
static_assert(sizeof(T) * CHAR_BIT <= 128, "details::popcount() only support up to 128 bits");
constexpr T ONES = ~T(0);
v = v - ((v >> 1u) & ONES / 3);
v = (v & ONES / 15 * 3) + ((v >> 2u) & ONES / 15 * 3);
v = (v + (v >> 4u)) & ONES / 255 * 15;
return (T) (v * (ONES / 255)) >> (sizeof(T) - 1) * CHAR_BIT;
}
template<typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
constexpr inline T clz(T x) noexcept {
static_assert(sizeof(T) * CHAR_BIT <= 128, "details::clz() only support up to 128 bits");
x |= (x >> 1u);
x |= (x >> 2u);
x |= (x >> 4u);
x |= (x >> 8u);
x |= (x >> 16u);
if constexpr (sizeof(T) * CHAR_BIT >= 64) { // just to silence compiler warning
x |= (x >> 32u);
}
if constexpr (sizeof(T) * CHAR_BIT >= 128) { // just to silence compiler warning
x |= (x >> 64u);
}
return T(sizeof(T) * CHAR_BIT) - details::popcount(x);
}
template<typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
constexpr inline T ctz(T x) noexcept {
static_assert(sizeof(T) * CHAR_BIT <= 64, "details::ctz() only support up to 64 bits");
T c = sizeof(T) * CHAR_BIT;
#if defined(_MSC_VER)
// equivalent to x & -x, but MSVC yield a warning for using unary minus operator on unsigned types
x &= (~x + 1);
#else
// equivalent to x & (~x + 1), but some compilers generate a better sequence on ARM
x &= -x;
#endif
if (x) c--;
if (sizeof(T) * CHAR_BIT >= 64) {
if (x & T(0x00000000FFFFFFFF)) c -= 32;
}
if (x & T(0x0000FFFF0000FFFF)) c -= 16;
if (x & T(0x00FF00FF00FF00FF)) c -= 8;
if (x & T(0x0F0F0F0F0F0F0F0F)) c -= 4;
if (x & T(0x3333333333333333)) c -= 2;
if (x & T(0x5555555555555555)) c -= 1;
return c;
}
} // namespace details
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned int UTILS_ALWAYS_INLINE clz(unsigned int x) noexcept {
#if __has_builtin(__builtin_clz)
return __builtin_clz(x);
#else
return details::clz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long UTILS_ALWAYS_INLINE clz(unsigned long x) noexcept {
#if __has_builtin(__builtin_clzl)
return __builtin_clzl(x);
#else
return details::clz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long long UTILS_ALWAYS_INLINE clz(unsigned long long x) noexcept {
#if __has_builtin(__builtin_clzll)
return __builtin_clzll(x);
#else
return details::clz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned int UTILS_ALWAYS_INLINE ctz(unsigned int x) noexcept {
#if __has_builtin(__builtin_ctz)
return __builtin_ctz(x);
#else
return details::ctz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long UTILS_ALWAYS_INLINE ctz(unsigned long x) noexcept {
#if __has_builtin(__builtin_ctzl)
return __builtin_ctzl(x);
#else
return details::ctz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long long UTILS_ALWAYS_INLINE ctz(unsigned long long x) noexcept {
#if __has_builtin(__builtin_ctzll)
return __builtin_ctzll(x);
#else
return details::ctz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned int UTILS_ALWAYS_INLINE popcount(unsigned int x) noexcept {
#if __has_builtin(__builtin_popcount)
return __builtin_popcount(x);
#else
return details::popcount(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long UTILS_ALWAYS_INLINE popcount(unsigned long x) noexcept {
#if __has_builtin(__builtin_popcountl)
return __builtin_popcountl(x);
#else
return details::popcount(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long long UTILS_ALWAYS_INLINE popcount(unsigned long long x) noexcept {
#if __has_builtin(__builtin_popcountll)
return __builtin_popcountll(x);
#else
return details::popcount(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
uint8_t UTILS_ALWAYS_INLINE popcount(uint8_t x) noexcept {
return (uint8_t)popcount((unsigned int)x);
}
template<typename T,
typename = std::enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value>>
constexpr inline UTILS_PUBLIC UTILS_PURE
T log2i(T x) noexcept {
return (sizeof(x) * 8 - 1u) - clz(x);
}
template<typename RandomAccessIterator, typename COMPARE>
inline UTILS_PUBLIC
RandomAccessIterator partition_point(
RandomAccessIterator first, RandomAccessIterator last, COMPARE pred,
bool assume_power_of_two = false) {
size_t len = last - first;
if (!assume_power_of_two) {
// handle non power-of-two sized arrays. If it's POT, the next line is a no-op
// and gets optimized out if the size is known at compile time.
len = 1u << (31 - clz(uint32_t(len))); // next power of two length / 2
size_t difference = (last - first) - len;
first += !difference || pred(first[len]) ? difference : 0;
}
while (len) {
// The number of repetitions here doesn't affect the result. We manually unroll the loop
// twice, to guarantee we have at least two iterations without branches (for the case
// where the size is not known at compile time
first += pred(first[len>>=1u]) ? len : 0;
first += pred(first[len>>=1u]) ? len : 0;
}
first += pred(*first);
return first;
}
template <class To, class From>
typename std::enable_if_t<
(sizeof(To) == sizeof(From)) &&
std::is_trivially_copyable<From>::value,
To>
// constexpr support needs compiler magic
bit_cast(const From &src) noexcept {
return reinterpret_cast<const To&>(src);
}
} // namespace utils
#endif // TNT_UTILS_ALGORITHM_H

View File

@@ -0,0 +1,330 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_BITSET_H
#define TNT_UTILS_BITSET_H
#include <utils/algorithm.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <algorithm> // for std::fill
#include <iterator>
#include <type_traits>
#if defined(__ARM_NEON)
# if defined(__ARM_ACLE) && defined(__aarch64__)
# include <arm_neon.h>
# define TNT_UTILS_BITSET_USE_NEON 1
# endif
#endif
namespace utils {
/*
* This bitset<> class is different from std::bitset<> in that it allows us to control
* the exact storage size. This is useful for small bitset (e.g. < 64, on 64-bits machines).
* It also allows for lexicographical compares (i.e. sorting).
*/
template<typename T, size_t N = 1,
typename = typename std::enable_if<std::is_integral<T>::value &&
std::is_unsigned<T>::value>::type>
class UTILS_PUBLIC bitset {
T storage[N];
public:
static constexpr T BITS_PER_WORD = sizeof(T) * 8;
static constexpr T BIT_COUNT = BITS_PER_WORD * N;
static constexpr T WORLD_COUNT = N;
using container_type = T;
bitset() noexcept {
std::fill(std::begin(storage), std::end(storage), 0);
}
T getBitsAt(size_t n) const noexcept {
assert_invariant(n<N);
return storage[n];
}
T& getBitsAt(size_t n) noexcept {
assert_invariant(n<N);
return storage[n];
}
T getValue() const noexcept {
static_assert(N == 1, "bitfield must only have one storage word");
return storage[0];
}
void setValue(T value) noexcept {
static_assert(N == 1, "bitfield must only have one storage word");
storage[0] = value;
}
template<typename F>
void forEachSetBit(F exec) const noexcept {
for (size_t i = 0; i < N; i++) {
T v = storage[i];
while (v) {
T k = utils::ctz(v);
v &= ~(T(1) << k);
exec(size_t(k + BITS_PER_WORD * i));
}
}
}
size_t size() const noexcept { return N * BITS_PER_WORD; }
bool test(size_t bit) const noexcept { return operator[](bit); }
void set(size_t b) noexcept {
assert_invariant(b / BITS_PER_WORD < N);
storage[b / BITS_PER_WORD] |= T(1) << (b % BITS_PER_WORD);
}
void set(size_t b, bool value) noexcept {
assert_invariant(b / BITS_PER_WORD < N);
storage[b / BITS_PER_WORD] &= ~(T(1) << (b % BITS_PER_WORD));
storage[b / BITS_PER_WORD] |= T(value) << (b % BITS_PER_WORD);
}
void unset(size_t b) noexcept {
assert_invariant(b / BITS_PER_WORD < N);
storage[b / BITS_PER_WORD] &= ~(T(1) << (b % BITS_PER_WORD));
}
void flip(size_t b) noexcept {
assert_invariant(b / BITS_PER_WORD < N);
storage[b / BITS_PER_WORD] ^= T(1) << (b % BITS_PER_WORD);
}
void reset() noexcept {
std::fill(std::begin(storage), std::end(storage), 0);
}
bool operator[](size_t b) const noexcept {
assert_invariant(b / BITS_PER_WORD < N);
return bool(storage[b / BITS_PER_WORD] & (T(1) << (b % BITS_PER_WORD)));
}
size_t count() const noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0 && BIT_COUNT / 128 < 31) {
// Use NEON for bitset multiple of 128 bits.
// The intermediate computation can't handle more than 31*128 bits because
// intermediate counts must be 8 bits.
uint8x16_t const* const p = (uint8x16_t const*) storage;
uint8x16_t counts = vcntq_u8(p[0]);
for (size_t i = 1; i < BIT_COUNT / 128; ++i) {
counts += vcntq_u8(p[i]);
}
return vaddlvq_u8(counts);
} else
#endif
{
T r = utils::popcount(storage[0]);
for (size_t i = 1; i < N; ++i) {
r += utils::popcount(storage[i]);
}
return r;
}
}
bool any() const noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint64x2_t const* const p = (uint64x2_t const*) storage;
uint64x2_t r = p[0];
for (size_t i = 1; i < BIT_COUNT / 128; ++i) {
r |= p[i];
}
return bool(r[0] | r[1]);
} else
#endif
{
T r = storage[0];
for (size_t i = 1; i < N; ++i) {
r |= storage[i];
}
return bool(r);
}
}
bool none() const noexcept {
return !any();
}
bool all() const noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint64x2_t const* const p = (uint64x2_t const*) storage;
uint64x2_t r = p[0];
for (size_t i = 1; i < BIT_COUNT / 128; ++i) {
r &= p[i];
}
return T(~(r[0] & r[1])) == T(0);
} else
#endif
{
T r = storage[0];
for (size_t i = 1; i < N; ++i) {
r &= storage[i];
}
return T(~r) == T(0);
}
}
bool operator!=(const bitset& b) const noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
bitset temp(*this ^ b);
uint64x2_t const* const p = (uint64x2_t const*) temp.storage;
uint64x2_t r = p[0];
for (size_t i = 1; i < BIT_COUNT / 128; ++i) {
r |= p[i];
}
return bool(r[0] | r[1]);
} else
#endif
{
T r = storage[0] ^ b.storage[0];
for (size_t i = 1; i < N; ++i) {
r |= storage[i] ^ b.storage[i];
}
return bool(r);
}
}
bool operator==(const bitset& b) const noexcept {
return !operator!=(b);
}
bitset& operator&=(const bitset& b) noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint8x16_t* const p = (uint8x16_t*) storage;
uint8x16_t const* const q = (uint8x16_t const*) b.storage;
for (size_t i = 0; i < BIT_COUNT / 128; ++i) {
p[i] &= q[i];
}
} else
#endif
{
for (size_t i = 0; i < N; ++i) {
storage[i] &= b.storage[i];
}
}
return *this;
}
bitset& operator|=(const bitset& b) noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint8x16_t* const p = (uint8x16_t*) storage;
uint8x16_t const* const q = (uint8x16_t const*) b.storage;
for (size_t i = 0; i < BIT_COUNT / 128; ++i) {
p[i] |= q[i];
}
} else
#endif
{
for (size_t i = 0; i < N; ++i) {
storage[i] |= b.storage[i];
}
}
return *this;
}
bitset& operator^=(const bitset& b) noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint8x16_t* const p = (uint8x16_t*) storage;
uint8x16_t const* const q = (uint8x16_t const*) b.storage;
for (size_t i = 0; i < BIT_COUNT / 128; ++i) {
p[i] ^= q[i];
}
} else
#endif
{
for (size_t i = 0; i < N; ++i) {
storage[i] ^= b.storage[i];
}
}
return *this;
}
bitset operator~() const noexcept {
bitset r;
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint8x16_t* const p = (uint8x16_t*) r.storage;
uint8x16_t const* const q = (uint8x16_t const*) storage;
for (size_t i = 0; i < BIT_COUNT / 128; ++i) {
p[i] = ~q[i];
}
} else
#endif
{
for (size_t i = 0; i < N; ++i) {
r.storage[i] = ~storage[i];
}
}
return r;
}
private:
friend bool operator<(bitset const& lhs, bitset const& rhs) noexcept {
return std::lexicographical_compare(
std::begin(lhs.storage), std::end(lhs.storage),
std::begin(rhs.storage), std::end(rhs.storage)
);
}
friend bitset operator&(const bitset& lhs, const bitset& rhs) noexcept {
return bitset(lhs) &= rhs;
}
friend bitset operator|(const bitset& lhs, const bitset& rhs) noexcept {
return bitset(lhs) |= rhs;
}
friend bitset operator^(const bitset& lhs, const bitset& rhs) noexcept {
return bitset(lhs) ^= rhs;
}
};
using bitset8 = bitset<uint8_t>;
using bitset32 = bitset<uint32_t>;
using bitset64 = bitset<uint64_t>;
using bitset128 = bitset<uint64_t, 2>;
using bitset256 = bitset<uint64_t, 4>;
static_assert(sizeof(bitset8) == 1, "bitset8 isn't 8 bits!");
static_assert(sizeof(bitset32) == 4, "bitset32 isn't 32 bits!");
static_assert(sizeof(bitset64) == 8, "bitset64 isn't 64 bits!");
static_assert(sizeof(bitset128) == 16, "bitset128 isn't 128 bits!");
static_assert(sizeof(bitset256) == 32, "bitset256 isn't 256 bits!");
} // namespace utils
#endif // TNT_UTILS_BITSET_H

View File

@@ -0,0 +1,263 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_COMPILER_H
#define TNT_UTILS_COMPILER_H
// compatibility with non-clang compilers...
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
#ifndef __has_feature
#define __has_feature(x) 0
#endif
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if __has_attribute(visibility)
# define UTILS_PUBLIC __attribute__((visibility("default")))
#else
# define UTILS_PUBLIC
#endif
#if __has_attribute(deprecated)
# define UTILS_DEPRECATED [[deprecated]]
#else
# define UTILS_DEPRECATED
#endif
#if __has_attribute(packed)
# define UTILS_PACKED __attribute__((packed))
#else
# define UTILS_PACKED
#endif
#if __has_attribute(noreturn)
# define UTILS_NORETURN __attribute__((noreturn))
#else
# define UTILS_NORETURN
#endif
#if __has_attribute(fallthrough)
# define UTILS_FALLTHROUGH [[fallthrough]]
#else
# define UTILS_FALLTHROUGH
#endif
#if __has_attribute(visibility)
# ifndef TNT_DEV
# define UTILS_PRIVATE __attribute__((visibility("hidden")))
# else
# define UTILS_PRIVATE
# endif
#else
# define UTILS_PRIVATE
#endif
#define UTILS_NO_SANITIZE_THREAD
#if __has_feature(thread_sanitizer)
#undef UTILS_NO_SANITIZE_THREAD
#define UTILS_NO_SANITIZE_THREAD __attribute__((no_sanitize("thread")))
#endif
#define UTILS_HAS_SANITIZE_THREAD 0
#if __has_feature(thread_sanitizer) || defined(__SANITIZE_THREAD__)
#undef UTILS_HAS_SANITIZE_THREAD
#define UTILS_HAS_SANITIZE_THREAD 1
#endif
#define UTILS_HAS_SANITIZE_MEMORY 0
#if __has_feature(memory_sanitizer)
#undef UTILS_HAS_SANITIZE_MEMORY
#define UTILS_HAS_SANITIZE_MEMORY 1
#endif
/*
* helps the compiler's optimizer predicting branches
*/
#if __has_builtin(__builtin_expect)
# ifdef __cplusplus
# define UTILS_LIKELY( exp ) (__builtin_expect( !!(exp), true ))
# define UTILS_UNLIKELY( exp ) (__builtin_expect( !!(exp), false ))
# else
# define UTILS_LIKELY( exp ) (__builtin_expect( !!(exp), 1 ))
# define UTILS_UNLIKELY( exp ) (__builtin_expect( !!(exp), 0 ))
# endif
#else
# define UTILS_LIKELY( exp ) (!!(exp))
# define UTILS_UNLIKELY( exp ) (!!(exp))
#endif
#if __has_builtin(__builtin_prefetch)
# define UTILS_PREFETCH( exp ) (__builtin_prefetch(exp))
#else
# define UTILS_PREFETCH( exp )
#endif
#if __has_builtin(__builtin_assume)
# define UTILS_ASSUME( exp ) (__builtin_assume(exp))
#else
# define UTILS_ASSUME( exp )
#endif
#if (defined(__i386__) || defined(__x86_64__))
# define UTILS_HAS_HYPER_THREADING 1 // on x86 we assume we have hyper-threading.
#else
# define UTILS_HAS_HYPER_THREADING 0
#endif
#if defined(FILAMENT_SINGLE_THREADED)
# define UTILS_HAS_THREADING 0
#elif defined(__EMSCRIPTEN__)
# if defined(__EMSCRIPTEN_PTHREADS__) && defined(FILAMENT_WASM_THREADS)
# define UTILS_HAS_THREADING 1
# else
# define UTILS_HAS_THREADING 0
# endif
#else
# define UTILS_HAS_THREADING 1
#endif
#if __has_attribute(noinline)
#define UTILS_NOINLINE __attribute__((noinline))
#else
#define UTILS_NOINLINE
#endif
#if __has_attribute(always_inline)
#define UTILS_ALWAYS_INLINE __attribute__((always_inline))
#else
#define UTILS_ALWAYS_INLINE
#endif
#if __has_attribute(pure)
#define UTILS_PURE __attribute__((pure))
#else
#define UTILS_PURE
#endif
#if __has_attribute(maybe_unused) || (defined(_MSC_VER) && _MSC_VER >= 1911)
#define UTILS_UNUSED [[maybe_unused]]
#define UTILS_UNUSED_IN_RELEASE [[maybe_unused]]
#elif __has_attribute(unused)
#define UTILS_UNUSED __attribute__((unused))
#define UTILS_UNUSED_IN_RELEASE __attribute__((unused))
#else
#define UTILS_UNUSED
#define UTILS_UNUSED_IN_RELEASE
#endif
#if defined(_MSC_VER) && _MSC_VER >= 1900
# define UTILS_RESTRICT __restrict
#elif (defined(__clang__) || defined(__GNUC__))
# define UTILS_RESTRICT __restrict__
#else
# define UTILS_RESTRICT
#endif
#if defined(_MSC_VER) && _MSC_VER >= 1900
# define UTILS_HAS_FEATURE_CXX_THREAD_LOCAL 1
#elif __has_feature(cxx_thread_local)
# define UTILS_HAS_FEATURE_CXX_THREAD_LOCAL 1
#else
# define UTILS_HAS_FEATURE_CXX_THREAD_LOCAL 0
#endif
#if defined(_MSC_VER)
// MSVC does not support loop unrolling hints
# define UTILS_UNROLL
# define UTILS_NOUNROLL
#else
// C++11 allows pragmas to be specified as part of defines using the _Pragma syntax.
# define UTILS_UNROLL _Pragma("unroll")
# define UTILS_NOUNROLL _Pragma("nounroll")
#endif
#if __has_feature(cxx_rtti) || defined(_CPPRTTI)
# define UTILS_HAS_RTTI 1
#else
# define UTILS_HAS_RTTI 0
#endif
#ifdef __ARM_ACLE
# include <arm_acle.h>
# define UTILS_WAIT_FOR_INTERRUPT() __wfi()
# define UTILS_WAIT_FOR_EVENT() __wfe()
# define UTILS_BROADCAST_EVENT() __sev()
# define UTILS_SIGNAL_EVENT() __sevl()
# define UTILS_PAUSE() __yield()
# define UTILS_PREFETCHW(addr) __pldx(1, 0, 0, addr)
#else // !__ARM_ACLE
# if (defined(__i386__) || defined(__x86_64__))
# define UTILS_X86_PAUSE {__asm__ __volatile__( "rep; nop" : : : "memory" );}
# define UTILS_WAIT_FOR_INTERRUPT() UTILS_X86_PAUSE
# define UTILS_WAIT_FOR_EVENT() UTILS_X86_PAUSE
# define UTILS_BROADCAST_EVENT()
# define UTILS_SIGNAL_EVENT()
# define UTILS_PAUSE() UTILS_X86_PAUSE
# define UTILS_PREFETCHW(addr) UTILS_PREFETCH(addr)
# else // !x86
# define UTILS_WAIT_FOR_INTERRUPT()
# define UTILS_WAIT_FOR_EVENT()
# define UTILS_BROADCAST_EVENT()
# define UTILS_SIGNAL_EVENT()
# define UTILS_PAUSE()
# define UTILS_PREFETCHW(addr) UTILS_PREFETCH(addr)
# endif // x86
#endif // __ARM_ACLE
// ssize_t is a POSIX type.
#if defined(WIN32) || defined(_WIN32)
#include <Basetsd.h>
typedef SSIZE_T ssize_t;
#endif
#ifdef _MSC_VER
# define UTILS_EMPTY_BASES __declspec(empty_bases)
#else
# define UTILS_EMPTY_BASES
#endif
#if defined(WIN32) || defined(_WIN32)
#define IMPORTSYMB __declspec(dllimport)
#else
#define IMPORTSYMB
#endif
#if defined(_MSC_VER) && !defined(__PRETTY_FUNCTION__)
# define __PRETTY_FUNCTION__ __FUNCSIG__
#endif
#if defined(_MSC_VER)
# define UTILS_WARNING_PUSH _Pragma("warning( push )")
# define UTILS_WARNING_POP _Pragma("warning( pop )")
# define UTILS_WARNING_ENABLE_PADDED _Pragma("warning(1: 4324)")
#elif defined(__clang__)
# define UTILS_WARNING_PUSH _Pragma("clang diagnostic push")
# define UTILS_WARNING_POP _Pragma("clang diagnostic pop")
# define UTILS_WARNING_ENABLE_PADDED _Pragma("clang diagnostic warning \"-Wpadded\"")
#else
# define UTILS_WARNING_PUSH
# define UTILS_WARNING_POP
# define UTILS_WARNING_ENABLE_PADDED
#endif
#endif // TNT_UTILS_COMPILER_H

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_COMPRESSED_PAIR_H
#define TNT_UTILS_COMPRESSED_PAIR_H
#include <type_traits>
#include <utility>
namespace utils {
template<typename T, bool>
struct dependent_type : public T {
};
template<typename T1, typename T2,
std::enable_if_t<!std::is_same_v<T1, T2>, bool> = true>
struct compressed_pair : private T1, private T2 {
template<bool Dummy = true, typename = std::enable_if_t<
dependent_type<std::is_default_constructible<T1>, Dummy>::value &&
dependent_type<std::is_default_constructible<T2>, Dummy>::value>>
compressed_pair() : T1(), T2() {}
template<typename U1, typename U2>
compressed_pair(U1&& other1, U2&& other2)
: T1(std::forward<U1>(other1)),
T2(std::forward<U2>(other2)) {}
T1& first() noexcept {
return static_cast<T1&>(*this);
}
T2& second() noexcept {
return static_cast<T2&>(*this);
}
T1 const& first() const noexcept {
return static_cast<T1 const&>(*this);
}
T2 const& second() const noexcept {
return static_cast<T2 const&>(*this);
}
void swap(compressed_pair& other) noexcept {
using std::swap;
swap(first(), other.first());
swap(second(), other.second());
}
};
} // namespace utils
#endif // TNT_UTILS_COMPRESSED_PAIR_H

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_DEBUG_H
#define TNT_UTILS_DEBUG_H
#include <utils/compiler.h>
namespace utils {
void panic(const char *func, const char * file, int line, const char *assertion) noexcept;
} // namespace filament
#ifdef NDEBUG
# define assert_invariant(e) ((void)0)
#else
# define assert_invariant(e) \
(UTILS_LIKELY(e) ? ((void)0) : utils::panic(__func__, __FILE__, __LINE__, #e))
#endif // NDEBUG
#endif // TNT_UTILS_DEBUG_H

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_GENERIC_MUTEX_H
#define TNT_UTILS_GENERIC_MUTEX_H
#include <mutex>
namespace utils {
using Mutex = std::mutex;
} // namespace utils
#endif // TNT_UTILS_GENERIC_MUTEX_H

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_MEMALIGN_H
#define TNT_UTILS_MEMALIGN_H
#include <type_traits>
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#if defined(WIN32)
#include <malloc.h>
#endif
namespace utils {
inline void* aligned_alloc(size_t size, size_t align) noexcept {
// 'align' must be a power of two and a multiple of sizeof(void*)
align = (align < sizeof(void*)) ? sizeof(void*) : align;
assert(align && !(align & align - 1));
assert((align % sizeof(void*)) == 0);
void* p = nullptr;
#if defined(WIN32)
p = ::_aligned_malloc(size, align);
#else
::posix_memalign(&p, align, size);
#endif
return p;
}
inline void aligned_free(void* p) noexcept {
#if defined(WIN32)
::_aligned_free(p);
#else
::free(p);
#endif
}
/*
* This allocator can be used with std::vector for instance to ensure all items are aligned
* to their alignof(). e.g.
*
* template<typename T>
* using aligned_vector = std::vector<T, utils::STLAlignedAllocator<T>>;
*
* aligned_vector<Foo> foos;
*
*/
template<typename TYPE>
class STLAlignedAllocator {
static_assert(!(alignof(TYPE) & (alignof(TYPE) - 1)), "alignof(T) must be a power of two");
public:
using value_type = TYPE;
using pointer = TYPE*;
using const_pointer = const TYPE*;
using reference = TYPE&;
using const_reference = const TYPE&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;
template<typename T>
struct rebind { using other = STLAlignedAllocator<T>; };
inline STLAlignedAllocator() noexcept = default;
template<typename T>
inline explicit STLAlignedAllocator(const STLAlignedAllocator<T>&) noexcept {}
inline ~STLAlignedAllocator() noexcept = default;
inline pointer allocate(size_type n) noexcept {
return (pointer)aligned_alloc(n * sizeof(value_type), alignof(TYPE));
}
inline void deallocate(pointer p, size_type) {
aligned_free(p);
}
// stateless allocators are always equal
template<typename T>
bool operator==(const STLAlignedAllocator<T>&) const noexcept {
return true;
}
template<typename T>
bool operator!=(const STLAlignedAllocator<T>&) const noexcept {
return false;
}
};
} // namespace utils
#endif // TNT_UTILS_MEMALIGN_H

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_OSTREAM_H
#define TNT_UTILS_OSTREAM_H
#include <utils/bitset.h>
#include <utils/compiler.h>
#include <utils/PrivateImplementation.h>
#include <string>
#include <string_view>
#include <utility>
namespace utils::io {
struct ostream_;
class UTILS_PUBLIC ostream : protected utils::PrivateImplementation<ostream_> {
friend struct ostream_;
public:
virtual ~ostream();
ostream& operator<<(short value) noexcept;
ostream& operator<<(unsigned short value) noexcept;
ostream& operator<<(char value) noexcept;
ostream& operator<<(unsigned char value) noexcept;
ostream& operator<<(int value) noexcept;
ostream& operator<<(unsigned int value) noexcept;
ostream& operator<<(long value) noexcept;
ostream& operator<<(unsigned long value) noexcept;
ostream& operator<<(long long value) noexcept;
ostream& operator<<(unsigned long long value) noexcept;
ostream& operator<<(float value) noexcept;
ostream& operator<<(double value) noexcept;
ostream& operator<<(long double value) noexcept;
ostream& operator<<(bool value) noexcept;
ostream& operator<<(const void* value) noexcept;
ostream& operator<<(const char* string) noexcept;
ostream& operator<<(const unsigned char* string) noexcept;
ostream& operator<<(std::string const& s) noexcept;
ostream& operator<<(std::string_view const& s) noexcept;
ostream& operator<<(ostream& (* f)(ostream&)) noexcept { return f(*this); }
ostream& dec() noexcept;
ostream& hex() noexcept;
protected:
ostream& print(const char* format, ...) noexcept;
class Buffer {
public:
Buffer() noexcept;
~Buffer() noexcept;
Buffer(const Buffer&) = delete;
Buffer& operator=(const Buffer&) = delete;
const char* get() const noexcept { return buffer; }
std::pair<char*, size_t> grow(size_t s) noexcept;
void advance(ssize_t n) noexcept;
void reset() noexcept;
private:
void reserve(size_t newSize) noexcept;
char* buffer = nullptr; // buffer address
char* curr = nullptr; // current pointer
size_t size = 0; // size remaining
size_t capacity = 0; // total capacity of the buffer
};
Buffer& getBuffer() noexcept;
Buffer const& getBuffer() const noexcept;
private:
virtual ostream& flush() noexcept = 0;
friend ostream& hex(ostream& s) noexcept;
friend ostream& dec(ostream& s) noexcept;
friend ostream& endl(ostream& s) noexcept;
friend ostream& flush(ostream& s) noexcept;
enum type {
SHORT, USHORT, CHAR, UCHAR, INT, UINT, LONG, ULONG, LONG_LONG, ULONG_LONG, FLOAT, DOUBLE,
LONG_DOUBLE
};
const char* getFormat(type t) const noexcept;
};
// handles utils::bitset
inline ostream& operator << (ostream& o, utils::bitset32 const& s) noexcept {
return o << (void*)uintptr_t(s.getValue());
}
// handles vectors from libmath (but we do this generically, without needing a dependency on libmath)
template<template<typename T> class VECTOR, typename T>
inline ostream& operator<<(ostream& stream, const VECTOR<T>& v) {
stream << "< ";
for (size_t i = 0; i < v.size() - 1; i++) {
stream << v[i] << ", ";
}
stream << v[v.size() - 1] << " >";
return stream;
}
inline ostream& hex(ostream& s) noexcept { return s.hex(); }
inline ostream& dec(ostream& s) noexcept { return s.dec(); }
inline ostream& endl(ostream& s) noexcept { s << '\n'; return s.flush(); }
inline ostream& flush(ostream& s) noexcept { return s.flush(); }
} // namespace utils::io
#endif // TNT_UTILS_OSTREAM_H

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if defined (WIN32)
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#ifdef far
#undef far
#endif
#ifdef near
#undef near
#endif
#ifdef ERROR
#undef ERROR
#endif
#ifdef OPAQUE
#undef OPAQUE
#endif
#ifdef TRANSPARENT
#undef TRANSPARENT
#endif
#ifdef PURE
#undef PURE
#endif
#endif