Cause found by profiling: room/SDF graph-cache churn (exact XY bounds + sample order rebuilt it constantly). Region grouping (one LOD3 tile: 1,084 -> 229 builds), taller-tile traversal for LOD6 (36,480 -> 12,160), extent-dependent cache slots, lambdas instead of recursive std::function. Serial ms/tile, 64 nearest tiles per level (before -> after): L0 50.4 -> 50.6, L1 112 -> 110, L2 24 -> 22, L3 223 -> 73, L4 269 -> 134, L5 393 -> 127, L6 283 -> 145. Hashes of all levels unchanged; parity 3,436/3,436 bit-identical. Full clipmap, 16 workers BelowNormal: full detail 66.6 s, all 12,054 tiles 175 s. In-stream L0 tail is high (median 83 ms, mean 198 ms, p95 669 ms) vs 51 ms serial. Streaming: recentre keeps the intersection, generates only missing tiles nearest/finest first; one crossing = exactly 441 new tiles in 6.1 s, no stale results. Lighting: normals already come from the density gradient; the striping was the squared headlamp; broad linear fill + warmer palette. Daytime CPU rule documented in Standalone/README.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
707 lines
48 KiB
C++
707 lines
48 KiB
C++
#pragma once
|
|
|
|
// Deliberately small UE-style surface used by the copied VoxelForge CPU sources.
|
|
// This is a value/container compatibility layer; it does not start or link Unreal.
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <bit>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <charconv>
|
|
#include <cctype>
|
|
#include <cstdarg>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <immintrin.h>
|
|
#include <initializer_list>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <memory_resource>
|
|
#include <mutex>
|
|
#include <new>
|
|
#include <optional>
|
|
#include <shared_mutex>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <thread>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#define UCLASS(...)
|
|
#define USTRUCT(...)
|
|
#define UENUM(...)
|
|
#define UPROPERTY(...)
|
|
#define UFUNCTION(...)
|
|
#define UMETA(...)
|
|
#define GENERATED_BODY()
|
|
#define GENERATED_UCLASS_BODY()
|
|
#define VOXELFORGE_API
|
|
#define FORCEINLINE inline
|
|
#define WITH_EDITOR 0
|
|
#define TRACE_CPUPROFILER_EVENT_SCOPE(...)
|
|
#define PRAGMA_DISABLE_SHADOW_VARIABLE_WARNINGS
|
|
#define PRAGMA_ENABLE_SHADOW_VARIABLE_WARNINGS
|
|
#define UE_ARRAY_COUNT(Array) (sizeof(Array) / sizeof((Array)[0]))
|
|
#define TEXT(x) x
|
|
#define UE_LOG(...)
|
|
#define LogTemp 0
|
|
#define check(x) assert(x)
|
|
#define checkf(x, ...) assert(x)
|
|
#define ensure(x) (x)
|
|
#define ensureMsgf(x, ...) (x)
|
|
#define verify(x) (x)
|
|
#define INDEX_NONE -1
|
|
#define MIN_int32 (std::numeric_limits<int32>::min())
|
|
#define MAX_int32 (std::numeric_limits<int32>::max())
|
|
#define MAX_uint32 (std::numeric_limits<uint32>::max())
|
|
#define MIN_flt (-std::numeric_limits<float>::max())
|
|
#define MAX_flt (std::numeric_limits<float>::max())
|
|
#define PI 3.14159265358979323846f
|
|
#define HALF_PI 1.57079632679489661923f
|
|
#define SMALL_NUMBER 1.0e-8f
|
|
#define KINDA_SMALL_NUMBER 1.0e-4f
|
|
#define MoveTemp(x) std::move(x)
|
|
|
|
using int8 = std::int8_t;
|
|
using uint8 = std::uint8_t;
|
|
using int16 = std::int16_t;
|
|
using uint16 = std::uint16_t;
|
|
using int32 = std::int32_t;
|
|
using uint32 = std::uint32_t;
|
|
using int64 = std::int64_t;
|
|
using uint64 = std::uint64_t;
|
|
using UPTRINT = std::uintptr_t;
|
|
using TCHAR = char;
|
|
using SIZE_T = std::size_t;
|
|
constexpr uint64 MAX_uint64 = std::numeric_limits<uint64>::max();
|
|
enum EForceInit { ForceInit };
|
|
enum class ESearchCase : uint8 { CaseSensitive, IgnoreCase };
|
|
|
|
struct FDefaultAllocator {};
|
|
template<int32 N> struct TInlineAllocator {};
|
|
enum class EAllowShrinking : uint8 { No, Yes };
|
|
template<class Signature> using TFunction = std::function<Signature>;
|
|
template<class Signature> using TFunctionRef = std::function<Signature>;
|
|
template<class T> constexpr T&& Forward(std::remove_reference_t<T>& Value){return std::forward<T>(Value);}
|
|
template<class T> constexpr T&& Forward(std::remove_reference_t<T>&& Value){return std::forward<T>(Value);}
|
|
|
|
struct FCompatAllocationHeader
|
|
{
|
|
std::pmr::memory_resource* Pool=nullptr;
|
|
void* Base=nullptr;
|
|
std::size_t Bytes=0;
|
|
std::size_t Alignment=0;
|
|
};
|
|
|
|
inline std::pmr::memory_resource* FCompatThreadPool()
|
|
{
|
|
// A synchronized pool can return a worker's allocations from the result-drain thread. Keep
|
|
// one process-lifetime pool per allocating thread, matching Unreal's per-thread small bins.
|
|
thread_local auto* Pool=new std::pmr::synchronized_pool_resource();
|
|
return Pool;
|
|
}
|
|
|
|
template<class T>
|
|
class FCompatAllocator
|
|
{
|
|
public:
|
|
using value_type=T;
|
|
using is_always_equal=std::true_type;
|
|
using propagate_on_container_move_assignment=std::true_type;
|
|
FCompatAllocator() noexcept=default;
|
|
template<class U> FCompatAllocator(const FCompatAllocator<U>&) noexcept {}
|
|
|
|
[[nodiscard]] T* allocate(std::size_t Count)
|
|
{
|
|
if(Count>std::numeric_limits<std::size_t>::max()/sizeof(T))throw std::bad_array_new_length();
|
|
const std::size_t Bytes=Count*sizeof(T);
|
|
const std::size_t Alignment=std::max(alignof(T),alignof(FCompatAllocationHeader));
|
|
const std::size_t Total=Bytes+sizeof(FCompatAllocationHeader)+Alignment;
|
|
std::pmr::memory_resource* Pool=Bytes<=65536?FCompatThreadPool():nullptr;
|
|
void* Base=Pool?Pool->allocate(Total,Alignment): ::operator new(Total,std::align_val_t(Alignment));
|
|
void* Candidate=static_cast<std::byte*>(Base)+sizeof(FCompatAllocationHeader);
|
|
std::size_t Space=Total-sizeof(FCompatAllocationHeader);
|
|
void* Data=std::align(alignof(T),Bytes,Candidate,Space);
|
|
if(!Data)
|
|
{
|
|
if(Pool)Pool->deallocate(Base,Total,Alignment);
|
|
else ::operator delete(Base,std::align_val_t(Alignment));
|
|
throw std::bad_alloc();
|
|
}
|
|
auto* Header=reinterpret_cast<FCompatAllocationHeader*>(static_cast<std::byte*>(Data)-sizeof(FCompatAllocationHeader));
|
|
::new (Header) FCompatAllocationHeader{Pool,Base,Total,Alignment};
|
|
return static_cast<T*>(Data);
|
|
}
|
|
|
|
void deallocate(T* Data,std::size_t) noexcept
|
|
{
|
|
if(!Data)return;
|
|
auto* Header=reinterpret_cast<FCompatAllocationHeader*>(reinterpret_cast<std::byte*>(Data)-sizeof(FCompatAllocationHeader));
|
|
const FCompatAllocationHeader Allocation=*Header;
|
|
Header->~FCompatAllocationHeader();
|
|
if(Allocation.Pool)Allocation.Pool->deallocate(Allocation.Base,Allocation.Bytes,Allocation.Alignment);
|
|
else ::operator delete(Allocation.Base,std::align_val_t(Allocation.Alignment));
|
|
}
|
|
|
|
template<class U> bool operator==(const FCompatAllocator<U>&)const noexcept{return true;}
|
|
template<class U> bool operator!=(const FCompatAllocator<U>&)const noexcept{return false;}
|
|
};
|
|
|
|
template<class Allocator> struct FCompatInlineCapacity { static constexpr std::size_t Value=0; };
|
|
template<int32 N> struct FCompatInlineCapacity<TInlineAllocator<N>>
|
|
{ static constexpr std::size_t Value=static_cast<std::size_t>(N); };
|
|
|
|
template<class T,std::size_t InlineCount,class Allocator=FCompatAllocator<T>>
|
|
class FCompatSmallVector
|
|
{
|
|
public:
|
|
using value_type=T;
|
|
using iterator=T*;
|
|
using const_iterator=const T*;
|
|
using reference=T&;
|
|
using const_reference=const T&;
|
|
|
|
FCompatSmallVector()=default;
|
|
FCompatSmallVector(const FCompatSmallVector& Other){assign(Other.begin(),Other.end());}
|
|
FCompatSmallVector(FCompatSmallVector&& Other) noexcept
|
|
{
|
|
if(Other.bHeap){Heap=std::move(Other.Heap);bHeap=true;Other.bHeap=false;Other.InlineSize=0;}
|
|
else for(T& Value:Other)emplace_back(std::move(Value));
|
|
Other.clear();
|
|
}
|
|
FCompatSmallVector& operator=(const FCompatSmallVector& Other)
|
|
{if(this!=&Other)assign(Other.begin(),Other.end());return *this;}
|
|
FCompatSmallVector& operator=(FCompatSmallVector&& Other) noexcept
|
|
{
|
|
if(this==&Other)return *this;
|
|
clear();
|
|
if(Other.bHeap){Heap=std::move(Other.Heap);bHeap=true;Other.bHeap=false;Other.InlineSize=0;}
|
|
else for(T& Value:Other)emplace_back(std::move(Value));
|
|
Other.clear();return *this;
|
|
}
|
|
~FCompatSmallVector(){clear();}
|
|
|
|
T* data(){return bHeap?Heap.data():InlineData();}
|
|
const T* data()const{return bHeap?Heap.data():InlineData();}
|
|
std::size_t size()const{return bHeap?Heap.size():InlineSize;}
|
|
bool empty()const{return size()==0;}
|
|
std::size_t capacity()const{return bHeap?Heap.capacity():InlineCount;}
|
|
std::size_t heap_capacity()const{return bHeap?Heap.capacity():0;}
|
|
iterator begin(){return data();} iterator end(){return data()+size();}
|
|
const_iterator begin()const{return data();} const_iterator end()const{return data()+size();}
|
|
reference operator[](std::size_t I){return data()[I];}
|
|
const_reference operator[](std::size_t I)const{return data()[I];}
|
|
reference back(){return data()[size()-1];} const_reference back()const{return data()[size()-1];}
|
|
|
|
void reserve(std::size_t Count)
|
|
{
|
|
if(Count<=capacity())return;
|
|
if(bHeap){Heap.reserve(Count);return;}
|
|
std::vector<T,Allocator> NewHeap;NewHeap.reserve(Count);
|
|
for(std::size_t I=0;I<InlineSize;++I)NewHeap.emplace_back(std::move(InlineData()[I]));
|
|
DestroyInline();Heap=std::move(NewHeap);InlineSize=0;bHeap=true;
|
|
}
|
|
|
|
void clear()noexcept
|
|
{if(bHeap)Heap.clear();else DestroyInline();}
|
|
void resize(std::size_t Count)
|
|
{
|
|
const std::size_t Old=size();
|
|
if(Count<Old)
|
|
{
|
|
if(bHeap)Heap.resize(Count);
|
|
else{while(InlineSize>Count)InlineData()[--InlineSize].~T();}
|
|
return;
|
|
}
|
|
reserve(Count);
|
|
while(size()<Count)emplace_back();
|
|
}
|
|
void assign(std::size_t Count,const T& Value)
|
|
{clear();reserve(Count);for(std::size_t I=0;I<Count;++I)emplace_back(Value);}
|
|
template<class It> void assign(It First,It Last)
|
|
{clear();for(;First!=Last;++First)emplace_back(*First);}
|
|
void push_back(const T& Value){emplace_back(Value);}
|
|
void push_back(T&& Value){emplace_back(std::move(Value));}
|
|
template<class... Args> reference emplace_back(Args&&... ArgsIn)
|
|
{
|
|
if(bHeap){Heap.emplace_back(std::forward<Args>(ArgsIn)...);return Heap.back();}
|
|
if(InlineSize<InlineCount)
|
|
{
|
|
T* Result=::new (static_cast<void*>(InlineData()+InlineSize)) T(std::forward<Args>(ArgsIn)...);
|
|
++InlineSize;return *Result;
|
|
}
|
|
reserve(std::max<std::size_t>(InlineCount?InlineCount*2:1,InlineSize+1));
|
|
Heap.emplace_back(std::forward<Args>(ArgsIn)...);return Heap.back();
|
|
}
|
|
void pop_back(){if(bHeap)Heap.pop_back();else InlineData()[--InlineSize].~T();}
|
|
template<class It> iterator insert(iterator Position,It First,It Last)
|
|
{
|
|
const std::size_t Offset=static_cast<std::size_t>(Position-begin());
|
|
assert(Offset==size());
|
|
for(;First!=Last;++First)emplace_back(*First);
|
|
return begin()+Offset;
|
|
}
|
|
iterator erase(iterator First,iterator Last)
|
|
{
|
|
const std::size_t Start=static_cast<std::size_t>(First-begin());
|
|
const std::size_t Finish=static_cast<std::size_t>(Last-begin());
|
|
const std::size_t Old=size();
|
|
if(Finish<=Start)return begin()+Start;
|
|
const std::size_t Removed=Finish-Start;
|
|
for(std::size_t I=Finish;I<Old;++I)data()[I-Removed]=std::move(data()[I]);
|
|
if(bHeap)Heap.resize(Old-Removed);
|
|
else while(InlineSize>Old-Removed)InlineData()[--InlineSize].~T();
|
|
return begin()+Start;
|
|
}
|
|
|
|
private:
|
|
T* InlineData(){return reinterpret_cast<T*>(InlineBytes);}
|
|
const T* InlineData()const{return reinterpret_cast<const T*>(InlineBytes);}
|
|
void DestroyInline()noexcept{while(InlineSize>0)InlineData()[--InlineSize].~T();}
|
|
alignas(T) std::byte InlineBytes[(InlineCount?InlineCount:1)*sizeof(T)]{};
|
|
std::vector<T,Allocator> Heap;
|
|
std::size_t InlineSize=0;
|
|
bool bHeap=false;
|
|
};
|
|
|
|
template<class T, class Allocator = FDefaultAllocator>
|
|
class TArray
|
|
{
|
|
public:
|
|
using value_type = T;
|
|
using FStored = std::conditional_t<std::is_same_v<T,bool>,uint8,T>;
|
|
using FStorage = FCompatSmallVector<FStored,FCompatInlineCapacity<Allocator>::Value,FCompatAllocator<FStored>>;
|
|
using iterator = typename FStorage::iterator;
|
|
using const_iterator = typename FStorage::const_iterator;
|
|
using reference = typename FStorage::reference;
|
|
using const_reference = typename FStorage::const_reference;
|
|
|
|
TArray() = default;
|
|
TArray(std::initializer_list<T> Values) : Values(Values) {}
|
|
int32 Num() const { return static_cast<int32>(Values.size()); }
|
|
bool IsEmpty() const { return Values.empty(); }
|
|
bool IsValidIndex(int32 Index) const { return Index >= 0 && Index < Num(); }
|
|
void Reserve(int32 Count) { Values.reserve(static_cast<std::size_t>(std::max(0, Count))); }
|
|
void Reset(int32 Slack = 0) { Values.clear(); if (Slack > 0) Reserve(Slack); }
|
|
void Empty(int32 Slack = 0) { Reset(Slack); }
|
|
void SetNum(int32 Count) { Values.resize(static_cast<std::size_t>(std::max(0, Count))); }
|
|
void Init(const T& Value,int32 Count) { Values.assign(static_cast<std::size_t>(std::max(0,Count)),static_cast<FStored>(Value)); }
|
|
void SetNumZeroed(int32 Count) { SetNum(Count); if constexpr (std::is_trivially_copyable_v<T>) if (!Values.empty()) std::memset(Values.data(), 0, Values.size() * sizeof(T)); }
|
|
void SetNumUninitialized(int32 Count) { SetNum(Count); }
|
|
int32 Add(const T& Value) { Values.push_back(Value); return Num() - 1; }
|
|
int32 Add(T&& Value) { Values.push_back(std::move(Value)); return Num() - 1; }
|
|
template<class... Args> int32 Emplace(Args&&... ArgsIn) { Values.emplace_back(std::forward<Args>(ArgsIn)...); return Num() - 1; }
|
|
reference AddDefaulted_GetRef() { Values.emplace_back(); return Values.back(); }
|
|
int32 AddDefaulted() { Values.emplace_back(); return Num() - 1; }
|
|
template<class... Args> reference Emplace_GetRef(Args&&... ArgsIn) { Values.emplace_back(std::forward<Args>(ArgsIn)...); return Values.back(); }
|
|
template<class... Args> reference Add_GetRef(Args&&... ArgsIn) { Values.emplace_back(std::forward<Args>(ArgsIn)...); return Values.back(); }
|
|
int32 AddUnique(const T& Value) { if (Contains(Value)) return IndexOfByKey(Value); return Add(Value); }
|
|
void Append(const TArray& Other) { Values.insert(Values.end(), Other.Values.begin(), Other.Values.end()); }
|
|
void Append(TArray&& Other) { Values.insert(Values.end(), std::make_move_iterator(Other.Values.begin()), std::make_move_iterator(Other.Values.end())); }
|
|
void RemoveAt(int32 Index, int32 Count = 1, bool = true) { if (Index >= 0 && Count > 0 && Index < Num()) Values.erase(Values.begin()+Index, Values.begin()+std::min(Num(),Index+Count)); }
|
|
void RemoveAt(int32 Index, int32 Count, EAllowShrinking) { RemoveAt(Index, Count, true); }
|
|
void RemoveAtSwap(int32 Index, int32 Count = 1, bool = true) { if (Index < 0 || Count <= 0 || Index >= Num()) return; const int32 End = std::min(Num(), Index + Count); for (int32 I = Index; I < End; ++I) Values[I] = std::move(Values.back()); Values.resize(Values.size() - static_cast<std::size_t>(End - Index)); }
|
|
void RemoveAtSwap(int32 Index, int32 Count, EAllowShrinking) { RemoveAtSwap(Index, Count, true); }
|
|
template<class Pred> int32 RemoveAll(Pred P) { const auto Before=Values.size(); Values.erase(std::remove_if(Values.begin(),Values.end(),P),Values.end()); return static_cast<int32>(Before-Values.size()); }
|
|
bool Contains(const T& Value) const { return std::find(Values.begin(), Values.end(), Value) != Values.end(); }
|
|
int32 IndexOfByKey(const T& Value) const { auto I=std::find(Values.begin(),Values.end(),Value); return I==Values.end()?INDEX_NONE:static_cast<int32>(I-Values.begin()); }
|
|
T* Find(const T& Value) { const int32 I=IndexOfByKey(Value); return I==INDEX_NONE?nullptr:&Values[I]; }
|
|
const T* Find(const T& Value) const { const int32 I=IndexOfByKey(Value); return I==INDEX_NONE?nullptr:&Values[I]; }
|
|
T* GetData() { return reinterpret_cast<T*>(Values.data()); }
|
|
const T* GetData() const { return reinterpret_cast<const T*>(Values.data()); }
|
|
std::size_t GetAllocatedSize() const { return Values.heap_capacity() * sizeof(T); }
|
|
T& Last() { return Values.back(); }
|
|
const T& Last() const { return Values.back(); }
|
|
T Pop(bool = false) { T Out=std::move(Values.back()); Values.pop_back(); return Out; }
|
|
void Swap(int32 A,int32 B) { std::swap(Values[A],Values[B]); }
|
|
template<class Pred> void Sort(Pred P) { std::sort(Values.begin(),Values.end(),P); }
|
|
void Sort() { std::sort(Values.begin(),Values.end()); }
|
|
reference operator[](int32 I) { return Values[static_cast<std::size_t>(I)]; }
|
|
const_reference operator[](int32 I) const { return Values[static_cast<std::size_t>(I)]; }
|
|
iterator begin() { return Values.begin(); } iterator end() { return Values.end(); }
|
|
const_iterator begin() const { return Values.begin(); } const_iterator end() const { return Values.end(); }
|
|
const_iterator cbegin() const { return Values.cbegin(); } const_iterator cend() const { return Values.cend(); }
|
|
private:
|
|
FStorage Values;
|
|
};
|
|
|
|
template<class K, class V> struct TPair { K Key{}; V Value{}; TPair()=default; TPair(const K& K0,const V& V0):Key(K0),Value(V0){}; };
|
|
|
|
template<class K>
|
|
struct FCompatKeyHash
|
|
{
|
|
std::size_t operator()(const K& Key) const noexcept
|
|
{
|
|
if constexpr (std::is_integral_v<K> || std::is_enum_v<K>)
|
|
return std::hash<K>{}(Key);
|
|
else if constexpr (requires { GetTypeHash(Key); })
|
|
return static_cast<std::size_t>(GetTypeHash(Key));
|
|
else
|
|
static_assert(sizeof(K) == 0, "Standalone TMap/TSet key type needs GetTypeHash");
|
|
}
|
|
};
|
|
|
|
template<class K, class V, class Allocator = FDefaultAllocator>
|
|
class TMap
|
|
{
|
|
public:
|
|
int32 Num() const { return static_cast<int32>(Pairs.size()); }
|
|
bool IsEmpty() const { return Pairs.empty(); }
|
|
void Reserve(int32 N)
|
|
{
|
|
const size_t Count=static_cast<size_t>(std::max(0,N));
|
|
Pairs.reserve(Count);
|
|
const size_t Needed=std::max<size_t>(8,std::bit_ceil(std::max<size_t>(1,(Count*10+6)/7)));
|
|
if(Buckets.size()<Needed)RebuildIndex(Needed);
|
|
}
|
|
void Reset()
|
|
{
|
|
Pairs.clear();Tombstones=0;
|
|
if(++Generation==0){for(FBucket& B:Buckets)B.Generation=0;Generation=1;}
|
|
}
|
|
void Empty(int32 = 0) { Reset(); }
|
|
bool Contains(const K& Key) const { return FindIndex(Key)!=INDEX_NONE; }
|
|
V* Find(const K& Key) { const int32 I=FindIndex(Key);return I==INDEX_NONE?nullptr:&Pairs[static_cast<size_t>(I)].Value; }
|
|
const V* Find(const K& Key) const { const int32 I=FindIndex(Key);return I==INDEX_NONE?nullptr:&Pairs[static_cast<size_t>(I)].Value; }
|
|
V& FindOrAdd(const K& Key) { if (V* P=Find(Key)) return *P; return Add(Key,V{}); }
|
|
V& Add(const K& Key,const V& Value) { if (V* P=Find(Key)) { *P=Value; return *P; } PrepareInsert();const int32 I=static_cast<int32>(Pairs.size());Pairs.emplace_back(Key,Value);InsertIndex(Key,I);return Pairs.back().Value; }
|
|
V& Add(const K& Key,V&& Value) { if (V* P=Find(Key)) { *P=std::move(Value); return *P; } PrepareInsert();const int32 I=static_cast<int32>(Pairs.size());Pairs.emplace_back(Key,std::move(Value));InsertIndex(Key,I);return Pairs.back().Value; }
|
|
int32 Remove(const K& Key)
|
|
{
|
|
const int32 I=FindIndex(Key);if(I==INDEX_NONE)return 0;
|
|
MarkRemoved(Key);const int32 Last=static_cast<int32>(Pairs.size()-1);
|
|
if(I!=Last){Pairs[static_cast<size_t>(I)]=std::move(Pairs.back());UpdateIndex(Pairs[static_cast<size_t>(I)].Key,I);}
|
|
Pairs.pop_back();
|
|
if(Tombstones>Pairs.size()&&Tombstones>Buckets.size()/8)RebuildIndex(Buckets.size());
|
|
return 1;
|
|
}
|
|
V& operator[](const K& Key) { return FindOrAdd(Key); }
|
|
const V& operator[](const K& Key) const { const V* P=Find(Key); assert(P); return *P; }
|
|
auto begin(){return Pairs.begin();} auto end(){return Pairs.end();}
|
|
auto begin()const{return Pairs.begin();} auto end()const{return Pairs.end();}
|
|
private:
|
|
struct FBucket { uint32 Generation=0;int32 Index=INDEX_NONE; };
|
|
void PrepareInsert()
|
|
{
|
|
const size_t Count=Pairs.size()+1;
|
|
const size_t Needed=std::max<size_t>(8,std::bit_ceil(std::max<size_t>(1,(Count*10+6)/7)));
|
|
if(Buckets.size()<Needed||Tombstones>Buckets.size()/8)RebuildIndex(std::max(Needed,Buckets.size()));
|
|
}
|
|
int32 FindIndex(const K& Key)const
|
|
{
|
|
if(Buckets.empty())return INDEX_NONE;
|
|
size_t Slot=FCompatKeyHash<K>{}(Key)&(Buckets.size()-1);
|
|
for(size_t Probe=0;Probe<Buckets.size();++Probe)
|
|
{
|
|
const FBucket& B=Buckets[Slot];
|
|
if(B.Generation!=Generation)return INDEX_NONE;
|
|
if(B.Index>=0&&Pairs[static_cast<size_t>(B.Index)].Key==Key)return B.Index;
|
|
Slot=(Slot+1)&(Buckets.size()-1);
|
|
}
|
|
return INDEX_NONE;
|
|
}
|
|
void InsertIndex(const K& Key,int32 PairIndex)
|
|
{
|
|
size_t Slot=FCompatKeyHash<K>{}(Key)&(Buckets.size()-1),FirstTombstone=Buckets.size();
|
|
for(size_t Probe=0;Probe<Buckets.size();++Probe)
|
|
{
|
|
FBucket& B=Buckets[Slot];
|
|
if(B.Generation!=Generation){if(FirstTombstone!=Buckets.size())Slot=FirstTombstone;Buckets[Slot]={Generation,PairIndex};return;}
|
|
if(B.Index==INDEX_NONE&&FirstTombstone==Buckets.size())FirstTombstone=Slot;
|
|
Slot=(Slot+1)&(Buckets.size()-1);
|
|
}
|
|
assert(FirstTombstone!=Buckets.size());Buckets[FirstTombstone]={Generation,PairIndex};
|
|
}
|
|
void MarkRemoved(const K& Key)
|
|
{
|
|
size_t Slot=FCompatKeyHash<K>{}(Key)&(Buckets.size()-1);
|
|
for(size_t Probe=0;Probe<Buckets.size();++Probe)
|
|
{
|
|
FBucket& B=Buckets[Slot];if(B.Generation!=Generation)return;
|
|
if(B.Index>=0&&Pairs[static_cast<size_t>(B.Index)].Key==Key){B.Index=INDEX_NONE;++Tombstones;return;}
|
|
Slot=(Slot+1)&(Buckets.size()-1);
|
|
}
|
|
}
|
|
void UpdateIndex(const K& Key,int32 PairIndex)
|
|
{
|
|
size_t Slot=FCompatKeyHash<K>{}(Key)&(Buckets.size()-1);
|
|
for(size_t Probe=0;Probe<Buckets.size();++Probe)
|
|
{
|
|
FBucket& B=Buckets[Slot];if(B.Generation!=Generation)return;
|
|
if(B.Index>=0&&Pairs[static_cast<size_t>(B.Index)].Key==Key){B.Index=PairIndex;return;}
|
|
Slot=(Slot+1)&(Buckets.size()-1);
|
|
}
|
|
}
|
|
void RebuildIndex(size_t Capacity)
|
|
{
|
|
Capacity=std::max<size_t>(8,std::bit_ceil(Capacity));Buckets.assign(Capacity,FBucket{});Generation=1;Tombstones=0;
|
|
for(int32 I=0;I<static_cast<int32>(Pairs.size());++I)InsertIndex(Pairs[static_cast<size_t>(I)].Key,I);
|
|
}
|
|
std::vector<TPair<K,V>,FCompatAllocator<TPair<K,V>>> Pairs;
|
|
std::vector<FBucket,FCompatAllocator<FBucket>> Buckets;
|
|
uint32 Generation=1;
|
|
size_t Tombstones=0;
|
|
};
|
|
|
|
template<class T, class Allocator = FDefaultAllocator>
|
|
class TSet
|
|
{
|
|
public:
|
|
int32 Num()const{return Values.Num();} void Reset(){Values.Reset();Indices.Reset();} void Empty(){Reset();}
|
|
void Reserve(int32 N){Values.Reserve(N);Indices.Reserve(N);}
|
|
bool Contains(const T& V)const{return Indices.Contains(V);}
|
|
void Add(const T& V){if(!Indices.Contains(V)){const int32 I=Values.Add(V);Indices.Add(V,I);}}
|
|
void Add(T&& V){if(!Indices.Contains(V)){const int32 I=Values.Add(std::move(V));Indices.Add(Values[I],I);}}
|
|
auto begin(){return Values.begin();}auto end(){return Values.end();}auto begin()const{return Values.begin();}auto end()const{return Values.end();}
|
|
private:TArray<T,Allocator> Values;TMap<T,int32,Allocator> Indices;
|
|
};
|
|
|
|
template<class T> class TUniquePtr
|
|
{
|
|
public:
|
|
TUniquePtr()=default; TUniquePtr(std::nullptr_t){} explicit TUniquePtr(std::unique_ptr<T>&&P):Ptr(std::move(P)){}
|
|
template<class U> requires std::is_convertible_v<U*,T*> TUniquePtr(TUniquePtr<U>&& P):Ptr(P.Release()){}
|
|
TUniquePtr(TUniquePtr&&)=default; TUniquePtr& operator=(TUniquePtr&&)=default; TUniquePtr(const TUniquePtr&)=delete; TUniquePtr& operator=(const TUniquePtr&)=delete;
|
|
template<class U> requires std::is_convertible_v<U*,T*> TUniquePtr& operator=(TUniquePtr<U>&& P){Ptr.reset(P.Release());return *this;}
|
|
T* Get()const{return Ptr.get();} T* get()const{return Ptr.get();} T* operator->()const{return Ptr.get();} T& operator*()const{return *Ptr;} explicit operator bool()const{return !!Ptr;} bool IsValid()const{return !!Ptr;}
|
|
void Reset(T*P=nullptr){Ptr.reset(P);} void reset(T*P=nullptr){Ptr.reset(P);} T* Release(){return Ptr.release();} void Swap(TUniquePtr&O){Ptr.swap(O.Ptr);}
|
|
private:std::unique_ptr<T> Ptr;
|
|
};
|
|
enum class ESPMode : uint8 { NotThreadSafe, ThreadSafe };
|
|
template<class T, ESPMode Mode=ESPMode::NotThreadSafe> class TSharedPtr { public:TSharedPtr()=default;TSharedPtr(std::nullptr_t){}TSharedPtr(std::shared_ptr<T>P):Ptr(std::move(P)){}template<class U,ESPMode Other> requires std::is_convertible_v<U*,T*> TSharedPtr(const TSharedPtr<U,Other>&P):Ptr(P.Std()){}T*Get()const{return Ptr.get();}T*operator->()const{return Ptr.get();}T&operator*()const{return *Ptr;}explicit operator bool()const{return !!Ptr;}bool IsValid()const{return !!Ptr;}void Reset(){Ptr.reset();}void Reset(T*P){Ptr.reset(P);}std::shared_ptr<T>&Std(){return Ptr;}const std::shared_ptr<T>&Std()const{return Ptr;}friend bool operator==(const TSharedPtr&P,std::nullptr_t){return !P.Ptr;}friend bool operator!=(const TSharedPtr&P,std::nullptr_t){return !!P.Ptr;}private:std::shared_ptr<T>Ptr; };
|
|
template<class T,ESPMode Mode=ESPMode::NotThreadSafe> using TSharedRef = TSharedPtr<T,Mode>;
|
|
template<class T> class TOptional { public:TOptional()=default;TOptional(const T&V):Value(V){}TOptional(T&&V):Value(std::move(V)){}bool IsSet()const{return Value.has_value();}explicit operator bool()const{return Value.has_value();}T&GetValue(){return Value.value();}const T&GetValue()const{return Value.value();}T&Get(){return Value.value();}const T&Get()const{return Value.value();}void Reset(){Value.reset();}private:std::optional<T>Value;};
|
|
template<class T, class... A> TUniquePtr<T> MakeUnique(A&&... Args) { return TUniquePtr<T>(std::make_unique<T>(std::forward<A>(Args)...)); }
|
|
template<class T, ESPMode Mode=ESPMode::NotThreadSafe, class... A> TSharedRef<T,Mode> MakeShared(A&&... Args) { return TSharedRef<T,Mode>(std::make_shared<T>(std::forward<A>(Args)...)); }
|
|
|
|
class FString : public std::string
|
|
{
|
|
public:
|
|
using std::string::string;
|
|
FString()=default; FString(const std::string& S):std::string(S){} FString(std::string&& S):std::string(std::move(S)){}
|
|
bool IsEmpty()const{return empty();} void Reset(){clear();}
|
|
int32 Len()const{return static_cast<int32>(size());}
|
|
bool Contains(std::string_view S)const{return find(S)!=npos;}
|
|
bool StartsWith(std::string_view S)const{return rfind(S,0)==0;}
|
|
bool EndsWith(std::string_view S)const{return size()>=S.size()&&compare(size()-S.size(),S.size(),S)==0;}
|
|
bool Equals(std::string_view S, ESearchCase SearchCase = ESearchCase::CaseSensitive)const{if(SearchCase==ESearchCase::CaseSensitive)return *this==S;if(size()!=S.size())return false;for(size_t I=0;I<size();++I)if(std::tolower(static_cast<unsigned char>((*this)[I]))!=std::tolower(static_cast<unsigned char>(S[I])))return false;return true;}
|
|
void ToLowerInline(){for(char& C:*this)C=static_cast<char>(std::tolower(static_cast<unsigned char>(C)));}
|
|
void TrimStartAndEndInline(){auto B=find_first_not_of(" \t\r\n"),E=find_last_not_of(" \t\r\n");*this=(B==npos)?FString{}:substr(B,E-B+1);}
|
|
void LeftChopInline(std::size_t N){if(N>=size())clear();else resize(size()-N);}
|
|
int32 ReplaceInline(std::string_view From,std::string_view To){if(From.empty())return 0;int32 C=0;std::size_t P=0;while((P=find(From,P))!=npos){replace(P,From.size(),To);P+=To.size();++C;}return C;}
|
|
static FString Join(const TArray<FString>& Parts, std::string_view Delimiter){FString Result;for(int32 I=0;I<Parts.Num();++I){if(I)Result+=Delimiter;Result+=Parts[I];}return Result;}
|
|
static FString Printf(const char* Format,...){char Buf[4096];va_list A;va_start(A,Format);int N=std::vsnprintf(Buf,sizeof(Buf),Format,A);va_end(A);return N<0?FString{}:FString(Buf,static_cast<std::size_t>(std::min<int>(N,sizeof(Buf)-1)));}
|
|
};
|
|
inline uint32 GetTypeHash(const FString& Value)
|
|
{
|
|
const std::string_view View(Value.data(),Value.size());
|
|
return static_cast<uint32>(std::hash<std::string_view>{}(View));
|
|
}
|
|
inline const char* operator*(const FString& S){return S.c_str();}
|
|
using FName = FString;
|
|
constexpr const char* NAME_None = "";
|
|
class UObject;
|
|
template<class T> T* FindStandaloneAsset(const FString& Path);
|
|
void RegisterStandaloneAsset(const FString& Path, UObject* Asset);
|
|
class FText { FString Value; public: FText()=default; FText(FString S):Value(std::move(S)){} static FText FromString(const FString& S){return FText(S);} static FText FromName(const FName& S){return FText(S);} FString ToString()const{return Value;} bool IsEmpty()const{return Value.IsEmpty();} };
|
|
class UEnum { public: FString GetNameStringByValue(int64 V)const{return FString::Printf("Value_%lld",static_cast<long long>(V));} };
|
|
template<class T> const UEnum* StaticEnum(){static const UEnum Value;return &Value;}
|
|
constexpr uint32 RF_Transient = 1;
|
|
template<class T> T* NewObject(UObject* = nullptr,FName = NAME_None,uint32 = 0){return new T;}
|
|
template<class T> T* DuplicateObject(const T* Source,UObject*){return Source?new T(*Source):nullptr;}
|
|
template<class T> class TGuardValue { public:TGuardValue(T& Target,const T& NewValue):Ref(Target),Old(Target){Ref=NewValue;}~TGuardValue(){Ref=Old;}TGuardValue(const TGuardValue&)=delete;private:T&Ref;T Old;};
|
|
|
|
struct FIntPoint { int32 X=0,Y=0; constexpr FIntPoint()=default; constexpr FIntPoint(int32 A,int32 B):X(A),Y(B){} static const FIntPoint ZeroValue; bool operator==(const FIntPoint&O)const{return X==O.X&&Y==O.Y;} bool operator!=(const FIntPoint&O)const{return !(*this==O);} FIntPoint operator+(const FIntPoint&O)const{return {X+O.X,Y+O.Y};} FIntPoint operator-(const FIntPoint&O)const{return {X-O.X,Y-O.Y};} };
|
|
inline const FIntPoint FIntPoint::ZeroValue{0,0};
|
|
struct FIntVector { int32 X=0,Y=0,Z=0; constexpr FIntVector()=default; constexpr FIntVector(int32 A,int32 B,int32 C):X(A),Y(B),Z(C){} static const FIntVector ZeroValue; bool operator==(const FIntVector&O)const{return X==O.X&&Y==O.Y&&Z==O.Z;} bool operator!=(const FIntVector&O)const{return !(*this==O);} FIntVector operator+(const FIntVector&O)const{return {X+O.X,Y+O.Y,Z+O.Z};} FIntVector operator-(const FIntVector&O)const{return {X-O.X,Y-O.Y,Z-O.Z};} FIntVector operator*(int32 S)const{return {X*S,Y*S,Z*S};} FIntVector operator/(int32 S)const{return {X/S,Y/S,Z/S};} FIntVector& operator+=(const FIntVector&O){X+=O.X;Y+=O.Y;Z+=O.Z;return *this;} };
|
|
inline const FIntVector FIntVector::ZeroValue{0,0,0};
|
|
inline FIntVector operator*(int32 S,const FIntVector& V){return V*S;}
|
|
|
|
struct FVector2D { double X=0,Y=0; constexpr FVector2D()=default; constexpr FVector2D(double A,double B):X(A),Y(B){} static const FVector2D ZeroVector; double SizeSquared()const{return X*X+Y*Y;} double Size()const{return std::sqrt(SizeSquared());} bool Normalize(double Tol=1.e-8){double L=Size();if(L<=Tol)return false;X/=L;Y/=L;return true;} FVector2D GetSafeNormal(double Tol=1.e-8)const{FVector2D V=*this;V.Normalize(Tol);return V;} double DistSquared(const FVector2D&O)const{return (*this-O).SizeSquared();} static double DistSquared(const FVector2D&A,const FVector2D&B){return (A-B).SizeSquared();} FVector2D operator-()const{return {-X,-Y};} FVector2D operator+(const FVector2D&O)const{return {X+O.X,Y+O.Y};} FVector2D operator-(const FVector2D&O)const{return {X-O.X,Y-O.Y};} FVector2D operator*(double S)const{return {X*S,Y*S};} FVector2D operator/(double S)const{return {X/S,Y/S};} FVector2D&operator+=(const FVector2D&O){X+=O.X;Y+=O.Y;return *this;} FVector2D&operator*=(double S){X*=S;Y*=S;return *this;} static double DotProduct(const FVector2D&A,const FVector2D&B){return A.X*B.X+A.Y*B.Y;} };
|
|
inline const FVector2D FVector2D::ZeroVector{0,0};
|
|
struct FVector2f { float X=0,Y=0; constexpr FVector2f()=default; constexpr FVector2f(float A,float B):X(A),Y(B){} };
|
|
struct FVector3f { float X=0,Y=0,Z=0; constexpr FVector3f()=default; constexpr FVector3f(float A,float B,float C):X(A),Y(B),Z(C){} FVector3f operator+(const FVector3f&O)const{return {X+O.X,Y+O.Y,Z+O.Z};} FVector3f operator-(const FVector3f&O)const{return {X-O.X,Y-O.Y,Z-O.Z};} FVector3f operator*(float S)const{return {X*S,Y*S,Z*S};} FVector3f&operator+=(const FVector3f&O){X+=O.X;Y+=O.Y;Z+=O.Z;return *this;} bool operator==(const FVector3f&O)const{return X==O.X&&Y==O.Y&&Z==O.Z;} };
|
|
struct FVector
|
|
{
|
|
using FReal = double;
|
|
double X=0,Y=0,Z=0; constexpr FVector()=default; constexpr explicit FVector(double S):X(S),Y(S),Z(S){} constexpr FVector(double A,double B,double C):X(A),Y(B),Z(C){} constexpr explicit FVector(const FIntVector& V):X(V.X),Y(V.Y),Z(V.Z){}
|
|
static const FVector ZeroVector, OneVector, UpVector, DownVector, ForwardVector, RightVector;
|
|
FVector operator+(const FVector&O)const{return {X+O.X,Y+O.Y,Z+O.Z};} FVector operator-(const FVector&O)const{return {X-O.X,Y-O.Y,Z-O.Z};}
|
|
FVector operator-()const{return {-X,-Y,-Z};} FVector operator*(double S)const{return {X*S,Y*S,Z*S};} FVector operator/(double S)const{return {X/S,Y/S,Z/S};} FVector operator/(const FVector&O)const{return {X/O.X,Y/O.Y,Z/O.Z};}
|
|
FVector& operator+=(const FVector&O){X+=O.X;Y+=O.Y;Z+=O.Z;return *this;} FVector& operator-=(const FVector&O){X-=O.X;Y-=O.Y;Z-=O.Z;return *this;} FVector& operator*=(double S){X*=S;Y*=S;Z*=S;return *this;} FVector& operator/=(double S){X/=S;Y/=S;Z/=S;return *this;}
|
|
bool operator==(const FVector&O)const{return X==O.X&&Y==O.Y&&Z==O.Z;} bool operator!=(const FVector&O)const{return !(*this==O);}
|
|
double SizeSquared()const{return X*X+Y*Y+Z*Z;} double Size()const{return std::sqrt(SizeSquared());}
|
|
double Dist(const FVector&O)const{return (*this-O).Size();} double DistSquared(const FVector&O)const{return (*this-O).SizeSquared();}
|
|
static double Dist(const FVector&A,const FVector&B){return (A-B).Size();} static double DistSquared(const FVector&A,const FVector&B){return (A-B).SizeSquared();}
|
|
bool Normalize(double Tolerance=1.e-8){double L=Size();if(L<=Tolerance)return false;*this=*this/L;return true;}
|
|
FVector GetAbs()const{return {std::abs(X),std::abs(Y),std::abs(Z)};}
|
|
FVector GetSafeNormal(double Tolerance=1.e-8)const{FVector V=*this;V.Normalize(Tolerance);return V;}
|
|
bool IsNearlyZero(double Tolerance=1.e-8)const{return SizeSquared()<=Tolerance*Tolerance;}
|
|
bool ContainsNaN()const{return !std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z);}
|
|
bool Equals(const FVector&O,double Tolerance=1.e-8)const{return (*this-O).IsNearlyZero(Tolerance);}
|
|
double Dot(const FVector&O)const{return X*O.X+Y*O.Y+Z*O.Z;} FVector Cross(const FVector&O)const{return {Y*O.Z-Z*O.Y,Z*O.X-X*O.Z,X*O.Y-Y*O.X};}
|
|
static double DotProduct(const FVector&A,const FVector&B){return A.Dot(B);} static FVector CrossProduct(const FVector&A,const FVector&B){return A.Cross(B);}
|
|
};
|
|
inline const FVector FVector::ZeroVector{0,0,0}; inline const FVector FVector::OneVector{1,1,1}; inline const FVector FVector::UpVector{0,0,1}; inline const FVector FVector::DownVector{0,0,-1}; inline const FVector FVector::ForwardVector{1,0,0}; inline const FVector FVector::RightVector{0,1,0};
|
|
inline FVector operator*(double S,const FVector&V){return V*S;}
|
|
inline FVector operator*(float S,const FVector&V){return V*static_cast<double>(S);}
|
|
|
|
struct FBox { FVector Min{MAX_flt,MAX_flt,MAX_flt},Max{MIN_flt,MIN_flt,MIN_flt}; bool IsValid=false; FBox()=default; explicit FBox(EForceInit):FBox(){} FBox(const FVector&A,const FVector&B):Min(A),Max(B),IsValid(A.X<=B.X&&A.Y<=B.Y&&A.Z<=B.Z){} FVector GetCenter()const{return (Min+Max)*0.5;} FVector GetExtent()const{return (Max-Min)*0.5;} bool IsInside(const FVector&P)const{return P.X>=Min.X&&P.X<=Max.X&&P.Y>=Min.Y&&P.Y<=Max.Y&&P.Z>=Min.Z&&P.Z<=Max.Z;} bool operator==(const FBox&O)const{return Min==O.Min&&Max==O.Max&&IsValid==O.IsValid;} bool operator!=(const FBox&O)const{return !(*this==O);} };
|
|
struct FColor { uint8 R=0,G=0,B=0,A=255; constexpr FColor()=default; constexpr FColor(uint8 r,uint8 g,uint8 b,uint8 a=255):R(r),G(g),B(b),A(a){} static const FColor White, Black, Red, Green, Blue, Transparent; };
|
|
inline const FColor FColor::White{255,255,255,255}; inline const FColor FColor::Black{0,0,0,255}; inline const FColor FColor::Red{255,0,0,255}; inline const FColor FColor::Green{0,255,0,255}; inline const FColor FColor::Blue{0,0,255,255}; inline const FColor FColor::Transparent{0,0,0,0};
|
|
struct FLinearColor { float R=0,G=0,B=0,A=1; constexpr FLinearColor()=default; constexpr FLinearColor(float r,float g,float b,float a=1):R(r),G(g),B(b),A(a){} static const FLinearColor White,Black,Red,Green,Blue; FColor ToFColor(bool bSRGB)const{auto cv=[](float V,bool S){V=std::clamp(V,0.0f,1.0f);if(S)V=V<=0.0031308f?12.92f*V:1.055f*std::pow(V,1.0f/2.4f)-0.055f;return static_cast<uint8>(std::clamp(static_cast<int>(std::floor(V*255.0f+0.5f)),0,255));};return {cv(R,bSRGB),cv(G,bSRGB),cv(B,bSRGB),cv(A,false)};} };
|
|
inline const FLinearColor FLinearColor::White{1,1,1,1}; inline const FLinearColor FLinearColor::Black{0,0,0,1}; inline const FLinearColor FLinearColor::Red{1,0,0,1}; inline const FLinearColor FLinearColor::Green{0,1,0,1}; inline const FLinearColor FLinearColor::Blue{0,0,1,1};
|
|
struct FRotator { double Pitch=0,Yaw=0,Roll=0; constexpr FRotator()=default; constexpr FRotator(double P,double Y,double R):Pitch(P),Yaw(Y),Roll(R){} static const FRotator ZeroRotator; };
|
|
inline const FRotator FRotator::ZeroRotator{0,0,0};
|
|
|
|
namespace FMath
|
|
{
|
|
template<class A,class B> constexpr auto Min(A A0,B B0){using R=std::common_type_t<A,B>;R A1=static_cast<R>(A0),B1=static_cast<R>(B0);return A1<B1?A1:B1;} template<class A,class B> constexpr auto Max(A A0,B B0){using R=std::common_type_t<A,B>;R A1=static_cast<R>(A0),B1=static_cast<R>(B0);return A1>B1?A1:B1;}
|
|
template<class T,class A,class B> constexpr T Clamp(T V,A A0,B B0){T Lo=static_cast<T>(A0),Hi=static_cast<T>(B0);return V<Lo?Lo:(V>Hi?Hi:V);} template<class T> constexpr T Abs(T V){return V<T(0)?-V:V;}
|
|
inline int32 FloorToInt(double V){return static_cast<int32>(std::floor(V));} inline int32 CeilToInt(double V){return static_cast<int32>(std::ceil(V));} inline int32 RoundToInt(double V){return static_cast<int32>(std::floor(V+0.5));}
|
|
inline double FloorToDouble(double V){return std::floor(V);} inline double CeilToDouble(double V){return std::ceil(V);} inline double RoundToDouble(double V){return std::floor(V+0.5);}
|
|
inline float FloorToFloat(float V){return std::floor(V);} inline float CeilToFloat(float V){return std::ceil(V);} inline float RoundToFloat(float V){return std::floor(V+0.5f);}
|
|
inline bool IsFinite(double V){return std::isfinite(V);} inline bool IsNearlyZero(double V,double T=1.e-8){return std::abs(V)<=T;} inline bool IsNearlyEqual(double A,double B,double T=1.e-8){return std::abs(A-B)<=T;}
|
|
inline float Sqrt(float V){return std::sqrt(V);} inline double Sqrt(double V){return std::sqrt(V);} inline float InvSqrt(float V){return 1.0f/std::sqrt(V);} inline double InvSqrt(double V){return 1.0/std::sqrt(V);}
|
|
inline float Sin(float V){return std::sin(V);} inline double Sin(double V){return std::sin(V);} inline float Cos(float V){return std::cos(V);} inline double Cos(double V){return std::cos(V);}
|
|
inline float Tan(float V){return std::tan(V);} inline double Tan(double V){return std::tan(V);} inline float Asin(float V){return std::asin(V);} inline double Asin(double V){return std::asin(V);} inline float Acos(float V){return std::acos(V);} inline double Acos(double V){return std::acos(V);}
|
|
inline float Atan2(float Y,float X){return std::atan2(Y,X);} inline double Atan2(double Y,double X){return std::atan2(Y,X);} inline float Pow(float A,float B){return std::pow(A,B);} inline double Pow(double A,double B){return std::pow(A,B);}
|
|
inline float Exp(float V){return std::exp(V);} inline double Exp(double V){return std::exp(V);} inline float Loge(float V){return std::log(V);} inline float Fmod(float A,float B){return std::fmod(A,B);} inline double Fmod(double A,double B){return std::fmod(A,B);}
|
|
template<class T> constexpr T Square(T V){return V*V;} template<class T> constexpr T Sign(T V){return V<T(0)?T(-1):(V>T(0)?T(1):T(0));}
|
|
template<class T> constexpr T Max3(T A,T B,T C){return Max(Max(A,B),C);} template<class T> constexpr T Min3(T A,T B,T C){return Min(Min(A,B),C);}
|
|
inline double DegreesToRadians(double D){return D*(3.1415926535897932384626433832795/180.0);} inline double RadiansToDegrees(double R){return R*(180.0/3.1415926535897932384626433832795);}
|
|
inline float Frac(float V){return V-std::floor(V);} inline double Frac(double V){return V-std::floor(V);}
|
|
inline int32 DivideAndRoundUp(int32 A,int32 B){return (A+B-1)/B;}
|
|
template<class T> bool IsPowerOfTwo(T V){return V>0&&(V&(V-1))==0;}
|
|
inline int32 FloorLog2(uint32 V){return V?31-std::countl_zero(V):0;}
|
|
float PerlinNoise2D(const FVector2D& Location);
|
|
float PerlinNoise3D(const FVector& Location);
|
|
template<class A,class B,class C> inline auto Lerp(const A& A0,const B& B0,C Alpha)->decltype(A0+(B0-A0)*Alpha){return A0+(B0-A0)*Alpha;}
|
|
inline float SmoothStep(float A,float B,float X){float T=Clamp((X-A)/(B-A),0.0f,1.0f);return T*T*(3-2*T);}
|
|
}
|
|
|
|
inline uint32 HashCombine(uint32 A,uint32 B){return A^(B+0x9e3779b9u+(A<<6)+(A>>2));}
|
|
inline uint32 GetTypeHash(int32 V){return static_cast<uint32>(V)*0x9e3779b9u;}
|
|
inline uint32 GetTypeHash(uint32 V){return V*0x9e3779b9u;}
|
|
inline uint32 GetTypeHash(float V){return std::bit_cast<uint32>(V);}
|
|
inline uint32 GetTypeHash(double V){return static_cast<uint32>(std::bit_cast<uint64>(V)^ (std::bit_cast<uint64>(V)>>32));}
|
|
inline uint32 GetTypeHash(int64 V){return static_cast<uint32>(static_cast<uint64>(V)^(static_cast<uint64>(V)>>32));}
|
|
inline uint32 GetTypeHash(uint64 V){return static_cast<uint32>(V^(V>>32));}
|
|
inline uint32 GetTypeHash(const FIntVector& V){return HashCombine(HashCombine(GetTypeHash(V.X),GetTypeHash(V.Y)),GetTypeHash(V.Z));}
|
|
inline uint32 GetTypeHash(const FIntPoint& V){return HashCombine(GetTypeHash(V.X),GetTypeHash(V.Y));}
|
|
struct FMemory { static void Memcpy(void*D,const void*S,std::size_t N){std::memcpy(D,S,N);} static void Memzero(void*D,std::size_t N){std::memset(D,0,N);} static int32 Memcmp(const void*A,const void*B,std::size_t N){return std::memcmp(A,B,N);} static SIZE_T GetAllocSize(const void* P){return P?sizeof(void*):0;} };
|
|
|
|
struct FPrimaryAssetId { FString Type,Name; FPrimaryAssetId()=default; FPrimaryAssetId(FString T,FName N):Type(std::move(T)),Name(std::move(N)){} };
|
|
class UObject { public: virtual ~UObject()=default; virtual void PostLoad(){} virtual FName GetFName()const{return FName{};} virtual FString GetPathName()const{return FString{};} virtual bool Modify(){return true;} virtual void MarkPackageDirty(){} };
|
|
inline std::unordered_map<std::string, UObject*>& StandaloneAssetRegistry()
|
|
{
|
|
static std::unordered_map<std::string, UObject*> Assets;
|
|
return Assets;
|
|
}
|
|
inline void RegisterStandaloneAsset(const FString& Path, UObject* Asset)
|
|
{
|
|
if (!Path.IsEmpty() && Asset) StandaloneAssetRegistry()[Path] = Asset;
|
|
}
|
|
template<class T> inline T* FindStandaloneAsset(const FString& Path)
|
|
{
|
|
auto It = StandaloneAssetRegistry().find(Path);
|
|
return It == StandaloneAssetRegistry().end() ? nullptr : dynamic_cast<T*>(It->second);
|
|
}
|
|
class UDataAsset : public UObject {};
|
|
class UPrimaryDataAsset : public UDataAsset { public: virtual FPrimaryAssetId GetPrimaryAssetId()const{return {};} };
|
|
using FSoftObjectPath = FString;
|
|
template<class T> class TSoftObjectPtr { public: TSoftObjectPtr()=default; TSoftObjectPtr(T*P):Ptr(P){} TSoftObjectPtr(const FSoftObjectPath&P):Path(P){} bool IsNull()const{return Ptr==nullptr&&Path.IsEmpty();} T* Get()const{return Ptr?Ptr:FindStandaloneAsset<T>(Path);} T* LoadSynchronous()const{return Get();} FString ToString()const{return Path;} void Set(T*P,FString S={}){Ptr=P;Path=std::move(S);} T* operator->()const{return Get();} explicit operator bool()const{return Get()!=nullptr;} private:T*Ptr=nullptr;FString Path; };
|
|
template<class T> class TWeakObjectPtr { public:TWeakObjectPtr()=default;TWeakObjectPtr(T*P):Ptr(P){}T*Get()const{return Ptr;}T*operator->()const{return Ptr;}private:T*Ptr=nullptr;};
|
|
template<class T> class TSubclassOf { public:TSubclassOf()=default; };
|
|
class FGameplayTag {}; class FGameplayTagContainer {};
|
|
struct FRandomStream { explicit FRandomStream(int32 S=0):Seed(S){} int32 RandRange(int32 Min,int32 Max){Seed=Seed*196314165+907633515;uint32 R=(Seed>>16)&0x7fff;return Min+static_cast<int32>((uint64(R)*(uint64(Max-Min)+1))>>15);} int32 GetCurrentSeed()const{return Seed;} private:int32 Seed; };
|
|
struct FCString { static int32 Strcmp(const char*A,const char*B){return std::strcmp(A,B);} static float Atof(const char*S){return static_cast<float>(std::atof(S));} static int32 Atoi(const char*S){return std::atoi(S);} };
|
|
struct FPlatformTime { using Clock=std::chrono::steady_clock; static uint64 Cycles64(){return static_cast<uint64>(std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now().time_since_epoch()).count());} static double Seconds(){return Cycles64()*1e-9;} static double GetSecondsPerCycle64(){return 1e-9;} };
|
|
struct FEvent { void Trigger(){} bool Wait(uint32=0,bool=false){return true;} };
|
|
struct FPlatformProcess { static void Sleep(float S){std::this_thread::sleep_for(std::chrono::duration<double>(S));} static void YieldThread(){std::this_thread::yield();} static void Yield(){std::this_thread::yield();} static FEvent* GetSynchEventFromPool(bool=false){return new FEvent;} static void ReturnSynchEventToPool(FEvent*E){delete E;} };
|
|
using FCriticalSection = std::mutex;
|
|
struct FScopeLock { explicit FScopeLock(std::mutex&M):Lock(M){} explicit FScopeLock(std::mutex* M):Lock(*M){} std::lock_guard<std::mutex> Lock; };
|
|
struct FRWLock { mutable std::shared_mutex Mutex; };
|
|
struct FReadScopeLock { explicit FReadScopeLock(FRWLock&L):Lock(L.Mutex){} std::shared_lock<std::shared_mutex> Lock; };
|
|
struct FWriteScopeLock { explicit FWriteScopeLock(FRWLock&L):Lock(L.Mutex){} std::unique_lock<std::shared_mutex> Lock; };
|
|
enum EConsoleVariableFlags { ECVF_Default=0, ECVF_SetByCommandline=1, ECVF_SetByCode=2 };
|
|
class FAutoConsoleVariableRef { public:FAutoConsoleVariableRef(const char*,int32&,const char*,uint32=0){} FAutoConsoleVariableRef(const char*,float&,const char*,uint32=0){} FAutoConsoleVariableRef(const char*,bool&,const char*,uint32=0){} };
|
|
template<class T> class TAutoConsoleVariable { public:TAutoConsoleVariable(const char*,T Default,const char*,uint32=0):Value(Default){}T GetValueOnAnyThread()const{return Value.load();}T GetValueOnGameThread()const{return Value.load();}void Set(T V,uint32=0){Value.store(V);} TAutoConsoleVariable*operator->(){return this;}private:std::atomic<T>Value;};
|
|
struct FCommandLine
|
|
{
|
|
inline static std::string Arguments;
|
|
static const char* Get(){return Arguments.c_str();}
|
|
static void Set(std::string_view InArguments){Arguments.assign(InArguments);}
|
|
};
|
|
struct FParse
|
|
{
|
|
static bool ReadValue(const char* CommandLine,const char* Key,std::string& Out)
|
|
{
|
|
if(!CommandLine||!Key||!*Key)return false;
|
|
const std::string_view Command(CommandLine),Token(Key);
|
|
std::size_t Position=0;
|
|
while((Position=Command.find(Token,Position))!=std::string_view::npos)
|
|
{
|
|
const bool bTokenStart=Position==0||Command[Position-1]==' '
|
|
||Command[Position-1]=='\t'||Command[Position-1]=='-';
|
|
const std::size_t ValueStart=Position+Token.size();
|
|
if(bTokenStart&&ValueStart<=Command.size())
|
|
{
|
|
std::size_t ValueEnd=Command.find_first_of(" \t\r\n",ValueStart);
|
|
if(ValueEnd==std::string_view::npos)ValueEnd=Command.size();
|
|
if(ValueEnd>ValueStart)
|
|
{Out.assign(Command.substr(ValueStart,ValueEnd-ValueStart));return true;}
|
|
}
|
|
Position+=Token.size();
|
|
}
|
|
return false;
|
|
}
|
|
static bool Value(const char* CommandLine,const char* Key,int32& Out)
|
|
{
|
|
std::string Text;
|
|
if(!ReadValue(CommandLine,Key,Text))return false;
|
|
const auto Result=std::from_chars(Text.data(),Text.data()+Text.size(),Out);
|
|
return Result.ec==std::errc{}&&Result.ptr==Text.data()+Text.size();
|
|
}
|
|
static bool Value(const char* CommandLine,const char* Key,float& Out)
|
|
{
|
|
std::string Text;
|
|
if(!ReadValue(CommandLine,Key,Text))return false;
|
|
char* End=nullptr;Out=std::strtof(Text.c_str(),&End);
|
|
return End==Text.c_str()+Text.size();
|
|
}
|
|
static bool Value(const char* CommandLine,const char* Key,FString& Out)
|
|
{
|
|
std::string Text;
|
|
if(!ReadValue(CommandLine,Key,Text))return false;
|
|
Out=FString(Text);return true;
|
|
}
|
|
static bool Param(const char* CommandLine,const char* Name)
|
|
{
|
|
if(!CommandLine||!Name||!*Name)return false;
|
|
const std::string_view Command(CommandLine),Token(Name);
|
|
std::size_t Position=0;
|
|
while((Position=Command.find(Token,Position))!=std::string_view::npos)
|
|
{
|
|
const bool bTokenStart=Position==0||Command[Position-1]==' '
|
|
||Command[Position-1]=='\t'||Command[Position-1]=='-';
|
|
const std::size_t End=Position+Token.size();
|
|
const bool bTokenEnd=End==Command.size()||Command[End]==' '
|
|
||Command[End]=='\t'||Command[End]=='\r'||Command[End]=='\n';
|
|
if(bTokenStart&&bTokenEnd)return true;
|
|
Position=End;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
struct FCrc { static uint32 MemCrc32(const void*Data,int32 Length,uint32 Crc=0){const auto*P=static_cast<const uint8*>(Data);uint32 V=~Crc;for(int32 I=0;I<Length;++I){V^=P[I];for(int J=0;J<8;++J)V=(V>>1)^(0xEDB88320u&static_cast<uint32>(-static_cast<int32>(V&1)));}return ~V;} static uint32 StrCrc32(const char*S,uint32 Crc=0){return MemCrc32(S,static_cast<int32>(std::strlen(S)),Crc);} };
|
|
template<class T> using TAtomic = std::atomic<T>;
|
|
class FThreadSafeCounter { public:int32 Increment(){return ++Value;}int32 Decrement(){return --Value;}int32 GetValue()const{return Value.load();}void Reset(){Value.store(0);}private:std::atomic<int32>Value{0};};
|