#pragma once #include "CoreTypes.h" #include "Memory/Memory.h" #include "Templates/Meta.h" #include "Templates/Invoke.h" #include "Templates/Utility.h" #include "TypeTraits/TypeTraits.h" #include "Miscellaneous/AssertionMacros.h" // NOTE: In the STL, the assignment operation of the std::any type uses the copy-and-swap idiom // instead of directly calling the assignment operation of the contained value. // But we don't follow the the copy-and-swap idiom, see "Templates/Any.h". // This class implements assignment operations in a way that assumes no assignment operations of the type, // because the assignment operations of TFunction are in most cases different between LHS and RHS. NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) NAMESPACE_MODULE_BEGIN(Utility) template class TFunctionRef; template class TFunction; template class TUniqueFunction; NAMESPACE_PRIVATE_BEGIN template struct TIsTFunctionRef : FFalse { }; template struct TIsTFunctionRef> : FTrue { }; template struct TIsTFunction : FFalse { }; template struct TIsTFunction> : FTrue { }; template struct TIsTUniqueFunction : FFalse { }; template struct TIsTUniqueFunction> : FTrue { }; NAMESPACE_PRIVATE_END template concept CTFunctionRef = NAMESPACE_PRIVATE::TIsTFunctionRef>::Value; template concept CTFunction = NAMESPACE_PRIVATE::TIsTFunction>::Value; template concept CTUniqueFunction = NAMESPACE_PRIVATE::TIsTUniqueFunction>::Value; NAMESPACE_PRIVATE_BEGIN template class TFunctionStorage; template class TFunctionStorage { public: FORCEINLINE constexpr TFunctionStorage() = default; FORCEINLINE constexpr TFunctionStorage(const TFunctionStorage&) = default; FORCEINLINE constexpr TFunctionStorage(TFunctionStorage&&) = default; FORCEINLINE constexpr TFunctionStorage& operator=(const TFunctionStorage&) = delete; FORCEINLINE constexpr TFunctionStorage& operator=(TFunctionStorage&&) = delete; FORCEINLINE constexpr ~TFunctionStorage() = default; FORCEINLINE constexpr uintptr GetValuePtr() const { return ValuePtr; } FORCEINLINE constexpr uintptr GetCallable() const { return Callable; } FORCEINLINE constexpr bool IsValid() const { return ValuePtr != 0; } // Use Invalidate() to invalidate the storage or use Emplace() to emplace a new object after destruction. FORCEINLINE constexpr void Destroy() { } // Make sure you call this function after you have destroyed the held object using Destroy(). FORCEINLINE constexpr void Invalidate() { ValuePtr = 0; } // Make sure you call this function after you have destroyed the held object using Destroy(). template FORCEINLINE constexpr void Emplace(intptr InCallable, U&& Args) { static_assert(CSameAs, TDecay>); ValuePtr = reinterpret_cast(AddressOf(Args)); Callable = InCallable; } FORCEINLINE constexpr void Swap(TFunctionStorage& InValue) { NAMESPACE_REDCRAFT::Swap(ValuePtr, InValue.ValuePtr); NAMESPACE_REDCRAFT::Swap(Callable, InValue.Callable); } private: uintptr ValuePtr; uintptr Callable; }; // For non-unique storage, the memory layout should be compatible with unique storage, // i.e. it can be directly reinterpreted_cast. template class alignas(16) TFunctionStorage { public: FORCEINLINE constexpr TFunctionStorage() = default; TFunctionStorage(const TFunctionStorage& InValue) requires (!bIsUnique) : RTTI(InValue.RTTI) { if (!IsValid()) return; Callable = InValue.Callable; switch (GetRepresentation()) { case ERepresentation::Empty: break; case ERepresentation::Trivial: Memory::Memcpy(InternalStorage, InValue.InternalStorage); break; case ERepresentation::Small: GetRTTI().CopyConstruct(GetStorage(), InValue.GetStorage()); break; case ERepresentation::Big: ExternalStorage = Memory::Malloc(GetRTTI().TypeSize, GetRTTI().TypeAlignment); GetRTTI().CopyConstruct(GetStorage(), InValue.GetStorage()); break; default: check_no_entry(); } } TFunctionStorage(TFunctionStorage&& InValue) : RTTI(InValue.RTTI) { if (!IsValid()) return; Callable = InValue.Callable; switch (GetRepresentation()) { case ERepresentation::Empty: break; case ERepresentation::Trivial: Memory::Memcpy(InternalStorage, InValue.InternalStorage); break; case ERepresentation::Small: GetRTTI().MoveConstruct(GetStorage(), InValue.GetStorage()); break; case ERepresentation::Big: ExternalStorage = InValue.ExternalStorage; InValue.Invalidate(); break; default: check_no_entry(); } } FORCEINLINE ~TFunctionStorage() { Destroy(); } TFunctionStorage& operator=(const TFunctionStorage& InValue) requires (!bIsUnique) { if (&InValue == this) return *this; if (!InValue.IsValid()) { Destroy(); Invalidate(); } else { Destroy(); RTTI = InValue.RTTI; Callable = InValue.Callable; switch (GetRepresentation()) { case ERepresentation::Empty: break; case ERepresentation::Trivial: Memory::Memcpy(InternalStorage, InValue.InternalStorage); break; case ERepresentation::Small: GetRTTI().CopyConstruct(GetStorage(), InValue.GetStorage()); break; case ERepresentation::Big: ExternalStorage = Memory::Malloc(GetRTTI().TypeSize, GetRTTI().TypeAlignment); GetRTTI().CopyConstruct(GetStorage(), InValue.GetStorage()); break; default: check_no_entry(); } } return *this; } TFunctionStorage& operator=(TFunctionStorage&& InValue) { if (&InValue == this) return *this; if (!InValue.IsValid()) { Destroy(); Invalidate(); } else { Destroy(); RTTI = InValue.RTTI; Callable = InValue.Callable; switch (GetRepresentation()) { case ERepresentation::Empty: break; case ERepresentation::Trivial: Memory::Memcpy(InternalStorage, InValue.InternalStorage); break; case ERepresentation::Small: GetRTTI().MoveConstruct(GetStorage(), InValue.GetStorage()); break; case ERepresentation::Big: ExternalStorage = InValue.ExternalStorage; InValue.Invalidate(); break; default: check_no_entry(); } } return *this; } FORCEINLINE constexpr uintptr GetValuePtr() const { return reinterpret_cast(GetStorage()); } FORCEINLINE constexpr uintptr GetCallable() const { return Callable; } FORCEINLINE constexpr bool IsValid() const { return RTTI != 0; } // Use Invalidate() to invalidate the storage or use Emplace() to emplace a new object after destruction. void Destroy() { if (!IsValid()) return; switch (GetRepresentation()) { case ERepresentation::Empty: case ERepresentation::Trivial: break; case ERepresentation::Small: GetRTTI().Destruct(GetStorage()); break; case ERepresentation::Big: GetRTTI().Destruct(GetStorage()); Memory::Free(ExternalStorage); break; default: check_no_entry(); } } // Make sure you call this function after you have destroyed the held object using Destroy(). FORCEINLINE constexpr void Invalidate() { RTTI = 0; } // Make sure you call this function after you have destroyed the held object using Destroy(). template void Emplace(uintptr InCallable, Ts&&... Args) { Callable = InCallable; using DecayedType = TDecay; static constexpr const FRTTI SelectedRTTI(InPlaceType); RTTI = reinterpret_cast(&SelectedRTTI); if constexpr (CEmpty && CTrivial) return; // ERepresentation::Empty constexpr bool bIsTriviallyStorable = sizeof(DecayedType) <= sizeof(InternalStorage) && alignof(DecayedType) <= alignof(TFunctionStorage) && CTriviallyCopyable; constexpr bool bIsSmallStorable = sizeof(DecayedType) <= sizeof(InternalStorage) && alignof(DecayedType) <= alignof(TFunctionStorage); if constexpr (bIsTriviallyStorable) { new (&InternalStorage) DecayedType(Forward(Args)...); RTTI |= static_cast(ERepresentation::Trivial); } else if constexpr (bIsSmallStorable) { new (&InternalStorage) DecayedType(Forward(Args)...); RTTI |= static_cast(ERepresentation::Small); } else { ExternalStorage = new DecayedType(Forward(Args)...); RTTI |= static_cast(ERepresentation::Big); } } void Swap(TFunctionStorage& InValue) { if (!IsValid() && !InValue.IsValid()) return; if (IsValid() && !InValue.IsValid()) { InValue = MoveTemp(*this); Destroy(); Invalidate(); } else if (InValue.IsValid() && !IsValid()) { *this = MoveTemp(InValue); InValue.Destroy(); InValue.Invalidate(); } else { TFunctionStorage Temp = MoveTemp(*this); *this = MoveTemp(InValue); InValue = MoveTemp(Temp); } } private: struct FMovableRTTI { const size_t TypeSize; const size_t TypeAlignment; using FMoveConstruct = void(*)(void*, void*); using FDestruct = void(*)(void* ); const FMoveConstruct MoveConstruct; const FDestruct Destruct; template FORCEINLINE constexpr FMovableRTTI(TInPlaceType) : TypeSize(sizeof(T)), TypeAlignment(alignof(T)) , MoveConstruct( [](void* A, void* B) { new (A) T(MoveTemp(*reinterpret_cast(B))); } ) , Destruct( [](void* A) { reinterpret_cast(A)->~T(); } ) { } }; struct FCopyableRTTI : public FMovableRTTI { using FCopyConstruct = void(*)(void*, const void*); const FCopyConstruct CopyConstruct; template FORCEINLINE constexpr FCopyableRTTI(TInPlaceType) : FMovableRTTI(InPlaceType) , CopyConstruct( [](void* A, const void* B) { new (A) T(*reinterpret_cast(B)); } ) { } }; using FRTTI = TConditional; static_assert(alignof(FRTTI) >= 4); static constexpr uintptr_t RepresentationMask = 3; enum class ERepresentation : uintptr { Empty = 0, // EmptyType Trivial = 1, // Trivial & Internal Small = 2, // InternalStorage Big = 3, // ExternalStorage }; union { uint8 InternalStorage[64 - sizeof(uintptr) - sizeof(uintptr)]; void* ExternalStorage; }; uintptr RTTI; uintptr Callable; FORCEINLINE constexpr ERepresentation GetRepresentation() const { return static_cast(RTTI & RepresentationMask); } FORCEINLINE constexpr const FRTTI& GetRTTI() const { return *reinterpret_cast(RTTI & ~RepresentationMask); } FORCEINLINE constexpr void* GetStorage() { switch (GetRepresentation()) { case ERepresentation::Empty: return nullptr; case ERepresentation::Trivial: return &InternalStorage; case ERepresentation::Small: return &InternalStorage; case ERepresentation::Big: return ExternalStorage; default: check_no_entry(); return nullptr; } } FORCEINLINE constexpr const void* GetStorage() const { switch (GetRepresentation()) { case ERepresentation::Empty: return nullptr; case ERepresentation::Trivial: return &InternalStorage; case ERepresentation::Small: return &InternalStorage; case ERepresentation::Big: return ExternalStorage; default: check_no_entry(); return nullptr; } } }; template FORCEINLINE constexpr bool FunctionIsBound(const T& Func) { if constexpr (CPointer || CMemberPointer || CTFunctionRef || CTFunction || CTUniqueFunction) { return !!Func; } else { return true; } } template struct TIsInvocableSignature : FFalse { }; template struct TIsInvocableSignature : TBoolConstant && CInvocableResult> { }; template struct TIsInvocableSignature : TBoolConstant> { }; template struct TIsInvocableSignature : TBoolConstant> { }; template struct TIsInvocableSignature : TBoolConstant && CInvocableResult> { }; template struct TIsInvocableSignature : TBoolConstant> { }; template struct TIsInvocableSignature : TBoolConstant> { }; template struct TFunctionInfo; template struct TFunctionInfo { using Fn = Ret(Ts...); using CVRef = int; }; template struct TFunctionInfo { using Fn = Ret(Ts...); using CVRef = int&; }; template struct TFunctionInfo { using Fn = Ret(Ts...); using CVRef = int&&; }; template struct TFunctionInfo { using Fn = Ret(Ts...); using CVRef = const int; }; template struct TFunctionInfo { using Fn = Ret(Ts...); using CVRef = const int&; }; template struct TFunctionInfo { using Fn = Ret(Ts...); using CVRef = const int&&; }; template class TFunctionImpl; template class TFunctionImpl { public: using ResultType = Ret; using ArgumentType = TTypeSequence; FORCEINLINE constexpr TFunctionImpl() = default; FORCEINLINE constexpr TFunctionImpl(const TFunctionImpl&) = default; FORCEINLINE constexpr TFunctionImpl(TFunctionImpl&&) = default; FORCEINLINE constexpr TFunctionImpl& operator=(const TFunctionImpl&) = default; FORCEINLINE constexpr TFunctionImpl& operator=(TFunctionImpl&&) = default; FORCEINLINE constexpr ~TFunctionImpl() = default; FORCEINLINE ResultType operator()(Ts... Args) requires (CSameAs) { return CallImpl(Forward(Args)...); } FORCEINLINE ResultType operator()(Ts... Args) & requires (CSameAs) { return CallImpl(Forward(Args)...); } FORCEINLINE ResultType operator()(Ts... Args) && requires (CSameAs) { return CallImpl(Forward(Args)...); } FORCEINLINE ResultType operator()(Ts... Args) const requires (CSameAs) { return CallImpl(Forward(Args)...); } FORCEINLINE ResultType operator()(Ts... Args) const& requires (CSameAs) { return CallImpl(Forward(Args)...); } FORCEINLINE ResultType operator()(Ts... Args) const&& requires (CSameAs) { return CallImpl(Forward(Args)...); } FORCEINLINE constexpr bool operator==(nullptr_t) const& { return !IsValid(); } FORCEINLINE constexpr bool IsValid() const { return Storage.IsValid(); } FORCEINLINE constexpr explicit operator bool() const { return Storage.IsValid(); } FORCEINLINE constexpr void Swap(TFunctionImpl& InValue) { Storage.Swap(InValue.Storage); } private: using CallableType = ResultType(*)(uintptr, Ts&&...); TFunctionStorage Storage; FORCEINLINE ResultType CallImpl(Ts&&... Args) const { checkf(IsValid(), TEXT("Attempting to call an unbound TFunction!")); CallableType Callable = reinterpret_cast(Storage.GetCallable()); return Callable(Storage.GetValuePtr(), Forward(Args)...); } protected: // These functions should not be used by user-defined class // Use Invalidate() to invalidate the storage or use Emplace() to emplace a new object after destruction. FORCEINLINE constexpr void Destroy() { Storage.Destroy(); } // Make sure you call this function after you have destroyed the held object using Destroy(). FORCEINLINE constexpr void Invalidate() { Storage.Invalidate(); } // Make sure you call this function after you have destroyed the held object using Destroy(). template FORCEINLINE constexpr TDecay& Emplace(ArgTypes&&... Args) { using DecayedType = TDecay; // This add a l-value reference to a non-reference type, while preserving the r-value reference. using ObjectType = TCopyCVRef; using InvokeType = TConditional, ObjectType, ObjectType&>; CallableType Callable = [](uintptr ObjectPtr, Ts&&... Args) -> ResultType { return InvokeResult( static_cast(*reinterpret_cast(ObjectPtr)), Forward(Args)... ); }; Storage.template Emplace( reinterpret_cast(Callable), Forward(Args)... ); return *reinterpret_cast(Storage.GetValuePtr()); } }; NAMESPACE_PRIVATE_END template class TFunctionRef : public NAMESPACE_PRIVATE::TFunctionImpl< typename NAMESPACE_PRIVATE::TFunctionInfo::Fn, typename NAMESPACE_PRIVATE::TFunctionInfo::CVRef, true> { private: using Impl = NAMESPACE_PRIVATE::TFunctionImpl< typename NAMESPACE_PRIVATE::TFunctionInfo::Fn, typename NAMESPACE_PRIVATE::TFunctionInfo::CVRef, true>; public: FORCEINLINE constexpr TFunctionRef() = delete; FORCEINLINE constexpr TFunctionRef(const TFunctionRef& InValue) = default; FORCEINLINE constexpr TFunctionRef(TFunctionRef&& InValue) = default; // We delete the assignment operators because we don't want it to be confused with being related to // regular C++ reference assignment - i.e. calling the assignment operator of whatever the reference // is bound to - because that's not what TFunctionRef does, nor is it even capable of doing that. FORCEINLINE constexpr TFunctionRef& operator=(const TFunctionRef& InValue) = delete; FORCEINLINE constexpr TFunctionRef& operator=(TFunctionRef&& InValue) = delete; template requires (!CTFunctionRef> && NAMESPACE_PRIVATE::TIsInvocableSignature>::Value) FORCEINLINE constexpr TFunctionRef(T&& InValue) { checkf(NAMESPACE_PRIVATE::FunctionIsBound(InValue), TEXT("Cannot bind a null/unbound callable to a TFunctionRef")); Impl::template Emplace(Forward(InValue)); } template TFunctionRef(const T&& InValue) = delete; }; template class TFunction : public NAMESPACE_PRIVATE::TFunctionImpl< typename NAMESPACE_PRIVATE::TFunctionInfo::Fn, typename NAMESPACE_PRIVATE::TFunctionInfo::CVRef, false, false> { private: using Impl = NAMESPACE_PRIVATE::TFunctionImpl< typename NAMESPACE_PRIVATE::TFunctionInfo::Fn, typename NAMESPACE_PRIVATE::TFunctionInfo::CVRef, false, false>; public: FORCEINLINE constexpr TFunction(nullptr_t = nullptr) { Impl::Invalidate(); } FORCEINLINE TFunction(const TFunction& InValue) = default; FORCEINLINE TFunction(TFunction&& InValue) = default; FORCEINLINE TFunction& operator=(const TFunction& InValue) = default; FORCEINLINE TFunction& operator=(TFunction&& InValue) = default; template requires (!CTInPlaceType> && !CTFunctionRef> && !CTFunction> && !CTUniqueFunction> && CConstructibleFrom, T&&> && CCopyConstructible> && CMoveConstructible> && CDestructible> && NAMESPACE_PRIVATE::TIsInvocableSignature>::Value) FORCEINLINE TFunction(T&& InValue) { if (!NAMESPACE_PRIVATE::FunctionIsBound(InValue)) Impl::Invalidate(); else Impl::template Emplace(Forward(InValue)); } template requires (NAMESPACE_PRIVATE::TIsInvocableSignature>::Value && CConstructibleFrom, ArgTypes...> && CCopyConstructible> && CMoveConstructible> && CDestructible>) FORCEINLINE explicit TFunction(TInPlaceType, ArgTypes&&... Args) { Impl::template Emplace(Forward(Args)...); } FORCEINLINE constexpr TFunction& operator=(nullptr_t) { Reset(); return *this; } template requires (NAMESPACE_PRIVATE::TIsInvocableSignature>::Value && !CTFunctionRef> && !CTFunction> && !CTUniqueFunction> && CConstructibleFrom, T&&> && CCopyConstructible> && CMoveConstructible> && CDestructible>) FORCEINLINE TFunction& operator=(T&& InValue) { if (!NAMESPACE_PRIVATE::FunctionIsBound(InValue)) Reset(); else Emplace(Forward(InValue)); return *this; } template requires (NAMESPACE_PRIVATE::TIsInvocableSignature>::Value && CConstructibleFrom, ArgTypes...> && CCopyConstructible> && CMoveConstructible> && CDestructible>) FORCEINLINE TDecay& Emplace(ArgTypes&&... Args) { Impl::Destroy(); return Impl::template Emplace(Forward(Args)...); } FORCEINLINE constexpr void Reset() { Impl::Destroy(); Impl::Invalidate(); } }; template class TUniqueFunction : public NAMESPACE_PRIVATE::TFunctionImpl< typename NAMESPACE_PRIVATE::TFunctionInfo::Fn, typename NAMESPACE_PRIVATE::TFunctionInfo::CVRef, false, true> { private: using Impl = NAMESPACE_PRIVATE::TFunctionImpl< typename NAMESPACE_PRIVATE::TFunctionInfo::Fn, typename NAMESPACE_PRIVATE::TFunctionInfo::CVRef, false, true>; public: FORCEINLINE constexpr TUniqueFunction(nullptr_t = nullptr) { Impl::Invalidate(); } FORCEINLINE TUniqueFunction(const TUniqueFunction& InValue) = delete; FORCEINLINE TUniqueFunction(TUniqueFunction&& InValue) = default; FORCEINLINE TUniqueFunction& operator=(const TUniqueFunction& InValue) = delete; FORCEINLINE TUniqueFunction& operator=(TUniqueFunction&& InValue) = default; FORCEINLINE TUniqueFunction(const TFunction& InValue) { new (this) TFunction(InValue); } FORCEINLINE TUniqueFunction(TFunction&& InValue) { new (this) TFunction(MoveTemp(InValue)); } FORCEINLINE TUniqueFunction& operator=(const TFunction& InValue) { *reinterpret_cast*>(this) = InValue; return *this; } FORCEINLINE TUniqueFunction& operator=(TFunction&& InValue) { *reinterpret_cast*>(this) = MoveTemp(InValue); return *this; } template requires (!CTInPlaceType> && !CTFunctionRef> && !CTFunction> && !CTUniqueFunction> && CConstructibleFrom, T&&> && CMoveConstructible> && CDestructible> && NAMESPACE_PRIVATE::TIsInvocableSignature>::Value) FORCEINLINE TUniqueFunction(T&& InValue) { if (!NAMESPACE_PRIVATE::FunctionIsBound(InValue)) Impl::Invalidate(); else Impl::template Emplace(Forward(InValue)); } template requires (NAMESPACE_PRIVATE::TIsInvocableSignature>::Value && CConstructibleFrom, ArgTypes...> && CMoveConstructible> && CDestructible>) FORCEINLINE explicit TUniqueFunction(TInPlaceType, ArgTypes&&... Args) { Impl::template Emplace(Forward(Args)...); } FORCEINLINE constexpr TUniqueFunction& operator=(nullptr_t) { Impl::Destroy(); Impl::Invalidate(); return *this; } template requires (NAMESPACE_PRIVATE::TIsInvocableSignature>::Value && !CTFunctionRef> && !CTFunction> && !CTUniqueFunction> && CConstructibleFrom, T&&> && CMoveConstructible> && CDestructible>) FORCEINLINE TUniqueFunction& operator=(T&& InValue) { if (!NAMESPACE_PRIVATE::FunctionIsBound(InValue)) Reset(); else Emplace(Forward(InValue)); return *this; } template requires (NAMESPACE_PRIVATE::TIsInvocableSignature>::Value && CConstructibleFrom, ArgTypes...> && CMoveConstructible> && CDestructible>) FORCEINLINE TDecay& Emplace(ArgTypes&&... Args) { Impl::Destroy(); using DecayedType = TDecay; return Impl::template Emplace(Forward(Args)...); } FORCEINLINE constexpr void Reset() { Impl::Destroy(); Impl::Invalidate(); } }; static_assert(sizeof(TFunction) == 64, "The byte size of TFunction is unexpected"); static_assert(sizeof(TUniqueFunction) == 64, "The byte size of TUniqueFunction is unexpected"); static_assert(alignof(TFunction) == 16, "The byte alignment of TFunction is unexpected"); static_assert(alignof(TUniqueFunction) == 16, "The byte alignment of TUniqueFunction is unexpected"); NAMESPACE_PRIVATE_BEGIN template struct TNotFunction { F Storage; template requires (CInvocable) FORCEINLINE constexpr auto operator()(Ts&&... Args) & -> decltype(!Invoke(Storage, Forward(Args)...)) { return !Invoke(Storage, Forward(Args)...); } template requires (CInvocable) FORCEINLINE constexpr auto operator()(Ts&&... Args) && -> decltype(!Invoke(MoveTemp(Storage), Forward(Args)...)) { return !Invoke(MoveTemp(Storage), Forward(Args)...); } template requires (CInvocable) FORCEINLINE constexpr auto operator()(Ts&&... Args) const& -> decltype(!Invoke(Storage, Forward(Args)...)) { return !Invoke(Storage, Forward(Args)...); } template requires (CInvocable) FORCEINLINE constexpr auto operator()(Ts&&... Args) const&& -> decltype(!Invoke(MoveTemp(Storage), Forward(Args)...)) { return !Invoke(MoveTemp(Storage), Forward(Args)...); } }; NAMESPACE_PRIVATE_END template requires (CConstructibleFrom) FORCEINLINE constexpr NAMESPACE_PRIVATE::TNotFunction> NotFn(F&& Func) { return { Forward(Func) }; } NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END