// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "UniversalObjectLocatorResolveParams.h" namespace UE::UniversalObjectLocator { template T* FResolveParameterBuffer::AddParameter(TParameterTypeHandle ParameterTypeHandle, ArgTypes&&... InArgs) { const UScriptStruct* ParameterStruct = ParameterTypeHandle.Resolve(); if (!ensureMsgf(ParameterStruct, TEXT("Parameter struct is no longer registered!"))) { return nullptr; } const uint8 ParameterIndex = ParameterTypeHandle.GetIndex(); const uint32 ParameterBit = 1 << ParameterIndex; return static_cast(AddParameterImpl(ParameterBit, Forward(InArgs)...)); } template ParameterType* FResolveParameterBuffer::AddParameterImpl(uint32 ParameterBit, ArgTypes&&... InArgs) { checkf((AllParameters & ParameterBit) == 0, TEXT("Parameter already exists!")); // Add the enum entry AllParameters |= ParameterBit; // Find the index of the new Parameter by counting how many bits are set before it // For example, given ParameterBit=0b00010000 and AllParameters=0b00011011: // (ParameterBit-1) = 0b00001111 // AllParameters & (ParameterBit-1) = 0b00001011 // CountBits(0b00001011) = 3 const int32 NewParameterIndex = static_cast(FMath::CountBits(AllParameters & (ParameterBit-1u))); FResolveParameterHeader* ExistingHeaders = reinterpret_cast(Memory); uint64 RequiredAlignment = FMath::Max(alignof(ParameterType), alignof(FResolveParameterHeader)); const int32 ExistingNum = static_cast(Num); // Compute our required alignment for the allocation { for (int32 Index = 0; Index < ExistingNum; ++Index) { RequiredAlignment = FMath::Max(RequiredAlignment, (uint64)ExistingHeaders[Index].Alignment); } } uint64 RequiredSizeof = 0u; // Compute the required size of our allocation { // Allocate space for headers RequiredSizeof += (ExistingNum+1) * sizeof(FResolveParameterHeader); int32 Index = 0; // Count up the sizes and alignments of pre-existing parameters that exist before this new entry for (; Index < NewParameterIndex; ++Index) { RequiredSizeof = Align(RequiredSizeof, (uint64)ExistingHeaders[Index].Alignment); RequiredSizeof += ExistingHeaders[Index].Sizeof; } // Count up the size and alignment for the new parameter RequiredSizeof = Align(RequiredSizeof, alignof(ParameterType)); RequiredSizeof += sizeof(ParameterType); ++Index; // Now count up the sizes and alignments of pre-existing parameters that exist after this new entry for (; Index < ExistingNum+1; ++Index) { RequiredSizeof = Align(RequiredSizeof, (uint64)ExistingHeaders[Index-1].Alignment); RequiredSizeof += ExistingHeaders[Index-1].Sizeof; } } check( RequiredAlignment <= 0XFF ); /// ---------------------------------------- uint8* OldAllocation = Memory; const bool bShouldFreeMemory = bCanFreeMemory; // Make a new allocation if necessary const bool bNeedsReallocation = !IsAligned(Memory, RequiredAlignment) || RequiredSizeof > Capacity; if (bNeedsReallocation) { const uint64 RequiredCapacity = FMath::Max(RequiredSizeof, uint64(Capacity)*2); check(RequiredCapacity <= std::numeric_limits::max()); // Use the greater of the required size or double the current size to allow some additional capcity Capacity = static_cast(FMath::Max(RequiredSizeof, uint64(Capacity)*2)); Memory = reinterpret_cast(FMemory::Malloc(Capacity, RequiredAlignment)); bCanFreeMemory = true; } // We now have an extra entry ++Num; uint8* ParameterPtr = Memory + RequiredSizeof; auto RelocateParameter = [this, ExistingHeaders, OldAllocation, &ParameterPtr](int32 OldIndex) { // Go back ParameterPtr -= ExistingHeaders[OldIndex].Sizeof; ParameterPtr = AlignDown(ParameterPtr, ExistingHeaders[OldIndex].Alignment); void* OldParameter = ExistingHeaders[OldIndex].Resolve(OldAllocation); FMemory::Memmove(ParameterPtr, OldParameter, ExistingHeaders[OldIndex].Sizeof); // Overwrite the old header with its new position for error checking when we write the new header ExistingHeaders[OldIndex].ParameterOffset = static_cast(ParameterPtr-Memory); }; // Relocate old structs that proceed the new index for (int32 Index = static_cast(Num) - 1; Index > NewParameterIndex; --Index) { RelocateParameter(Index-1); } // Make the new entry { ParameterPtr -= sizeof(ParameterType); ParameterPtr = AlignDown(ParameterPtr, alignof(ParameterType)); ParameterType* NewParameterPtr = reinterpret_cast(ParameterPtr); // Allocate the new type new (NewParameterPtr) ParameterType { Forward(InArgs)... }; static_assert(alignof(ParameterType) < 0x7F, "Required alignment of parameter must fit in 7 bytes"); } FResolveParameterHeader NewHeader = { static_cast(ParameterPtr-Memory), sizeof(ParameterType), alignof(ParameterType) }; // Relocate the entries that are before the new one for (int32 Index = NewParameterIndex-1; Index >= 0; --Index) { RelocateParameter(Index); } // Check that we have enough space before for the headers (this should always be the case) check(ParameterPtr >= Memory + sizeof(FResolveParameterHeader)*Num); FResolveParameterHeader* NewHeaders = reinterpret_cast(Memory); for (int32 Index = static_cast(Num)-1; Index > NewParameterIndex; --Index) { new (&NewHeaders[Index]) FResolveParameterHeader(ExistingHeaders[Index-1]); }; { new (&NewHeaders[NewParameterIndex]) FResolveParameterHeader(NewHeader); } if (Memory != OldAllocation) { for (int32 Index = NewParameterIndex-1; Index >= 0; --Index) { new (&NewHeaders[Index]) FResolveParameterHeader(ExistingHeaders[Index]); }; } // Tidy up the old allocation. We do not call destructors here because we relocated everything. if (bNeedsReallocation && OldAllocation && bShouldFreeMemory) { FMemory::Free(OldAllocation); } return static_cast(NewHeaders[NewParameterIndex].Resolve(Memory)); } } // namespace UE::UniversalObjectLocator