639 lines
18 KiB
C++
639 lines
18 KiB
C++
// Copyright 2018-current Getnamo. All Rights Reserved
|
|
|
|
|
|
#include "CUBlueprintLibrary.h"
|
|
#include "IImageWrapper.h"
|
|
#include "IImageWrapperModule.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Async/Async.h"
|
|
#include "Engine/Texture2D.h"
|
|
#include "HAL/ThreadSafeBool.h"
|
|
#include "RHI.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Decoders/OpusAudioInfo.h"
|
|
#include "Runtime/Launch/Resources/Version.h"
|
|
#include "Developer/TargetPlatform/Public/Interfaces/IAudioFormat.h"
|
|
#include "CoreMinimal.h"
|
|
#include "Engine/Engine.h"
|
|
#include "CULambdaRunnable.h"
|
|
#include "CUOpusCoder.h"
|
|
#include "CUMeasureTimer.h"
|
|
#include "Hash/CityHash.h"
|
|
#include "RenderingThread.h"
|
|
#include "TextureResource.h"
|
|
#include "Audio.h"
|
|
#include "Sound/SoundWaveProcedural.h"
|
|
#include "Runtime/Core/Public/Serialization/MemoryWriter.h"
|
|
#include "Runtime/Core/Public/Serialization/MemoryReader.h"
|
|
|
|
#pragma warning( push )
|
|
#pragma warning( disable : 5046)
|
|
|
|
//Render thread wrapper struct
|
|
struct FUpdateTextureData
|
|
{
|
|
UTexture2D* Texture2D;
|
|
FUpdateTextureRegion2D Region;
|
|
uint32 Pitch;
|
|
TArray64<uint8> BufferArray;
|
|
TSharedPtr<IImageWrapper> Wrapper; //to keep the uncompressed data alive
|
|
};
|
|
|
|
FString UCUBlueprintLibrary::Conv_BytesToString(const TArray<uint8>& InArray)
|
|
{
|
|
FString ResultString;
|
|
FFileHelper::BufferToString(ResultString, InArray.GetData(), InArray.Num());
|
|
return ResultString;
|
|
}
|
|
|
|
TArray<uint8> UCUBlueprintLibrary::Conv_StringToBytes(FString InString)
|
|
{
|
|
TArray<uint8> ResultBytes;
|
|
|
|
FTCHARToUTF8 UTF8String(*InString);
|
|
|
|
int32 UTF8Len = UTF8String.Length();
|
|
|
|
ResultBytes.Append((uint8*)UTF8String.Get(), UTF8Len);
|
|
return ResultBytes;
|
|
}
|
|
|
|
UTexture2D* UCUBlueprintLibrary::Conv_BytesToTexture(const TArray<uint8>& InBytes)
|
|
{
|
|
//Convert the UTexture2D back to an image
|
|
UTexture2D* Texture = nullptr;
|
|
|
|
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
|
|
EImageFormat DetectedFormat = ImageWrapperModule.DetectImageFormat(InBytes.GetData(), InBytes.Num());
|
|
|
|
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(DetectedFormat);
|
|
|
|
//Set the compressed bytes - we need this information on game thread to be able to determine texture size, otherwise we'll need a complete async callback
|
|
if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(InBytes.GetData(), InBytes.Num()))
|
|
{
|
|
//Create image given sizes
|
|
Texture = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8);
|
|
Texture->UpdateResource();
|
|
|
|
//Uncompress on a background thread pool
|
|
FCULambdaRunnable::RunLambdaOnBackGroundThreadPool([ImageWrapper, Texture] {
|
|
TArray64<uint8> UncompressedBGRA;
|
|
if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA))
|
|
{
|
|
|
|
FUpdateTextureData* UpdateData = new FUpdateTextureData;
|
|
UpdateData->Texture2D = Texture;
|
|
UpdateData->Region = FUpdateTextureRegion2D(0, 0, 0, 0, Texture->GetSizeX(), Texture->GetSizeY());
|
|
UpdateData->BufferArray = UncompressedBGRA;
|
|
UpdateData->Pitch = Texture->GetSizeX() * 4;
|
|
UpdateData->Wrapper = ImageWrapper;
|
|
|
|
//enqueue texture copy
|
|
ENQUEUE_RENDER_COMMAND(BytesToTextureCommand)(
|
|
[UpdateData](FRHICommandList& CommandList)
|
|
{
|
|
RHIUpdateTexture2D(
|
|
((FTextureResource*)UpdateData->Texture2D->GetResource())->GetTextureRHI()->GetTexture2D(),
|
|
0,
|
|
UpdateData->Region,
|
|
UpdateData->Pitch,
|
|
UpdateData->BufferArray.GetData()
|
|
);
|
|
delete UpdateData; //now that we've updated the texture data, we can finally release any data we're holding on to
|
|
});//End Enqueue
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("Invalid image format cannot decode %d"), (int32)DetectedFormat);
|
|
}
|
|
|
|
return Texture;
|
|
}
|
|
|
|
//one static coder, created based on need
|
|
TSharedPtr<FCUOpusCoder> OpusCoder;
|
|
|
|
TArray<uint8> UCUBlueprintLibrary::Conv_OpusBytesToWav(const TArray<uint8>& InBytes)
|
|
{
|
|
//FCUScopeTimer Timer(TEXT("Conv_OpusBytesToWav"));
|
|
|
|
TArray<uint8> WavBytes;
|
|
//Early exit condition
|
|
if (InBytes.Num() == 0)
|
|
{
|
|
return WavBytes;
|
|
}
|
|
if (!OpusCoder)
|
|
{
|
|
OpusCoder = MakeShareable(new FCUOpusCoder());
|
|
}
|
|
|
|
TArray<uint8> PCMBytes;
|
|
FCUOpusMinimalStream OpusStream;
|
|
OpusCoder->DeserializeMinimal(InBytes, OpusStream);
|
|
if (OpusCoder->DecodeStream(OpusStream, PCMBytes))
|
|
{
|
|
SerializeWaveFile(WavBytes, PCMBytes.GetData(), PCMBytes.Num(), OpusCoder->Channels, OpusCoder->SampleRate);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("OpusMinimal to Wave Failed. DecodeStream returned false"));
|
|
}
|
|
|
|
return WavBytes;
|
|
}
|
|
|
|
TArray<uint8> UCUBlueprintLibrary::Conv_PCMToWav(const TArray<uint8>& InPCM, int32 SampleRate, int32 Channels)
|
|
{
|
|
TArray<uint8> WavBytes;
|
|
SerializeWaveFile(WavBytes, InPCM.GetData(), InPCM.Num(), Channels, SampleRate);
|
|
return WavBytes;
|
|
}
|
|
|
|
TArray<uint8> UCUBlueprintLibrary::Conv_WavBytesToOpus(const TArray<uint8>& InBytes)
|
|
{
|
|
//FCUScopeTimer Timer(TEXT("Conv_WavBytesToOpus"));
|
|
|
|
TArray<uint8> OpusBytes;
|
|
|
|
FWaveModInfo WaveInfo;
|
|
|
|
if (!WaveInfo.ReadWaveInfo(InBytes.GetData(), InBytes.Num()))
|
|
{
|
|
return OpusBytes;
|
|
}
|
|
|
|
if (!OpusCoder)
|
|
{
|
|
OpusCoder = MakeShareable(new FCUOpusCoder());
|
|
}
|
|
|
|
TArray<uint8> PCMBytes = TArray<uint8>(WaveInfo.SampleDataStart, WaveInfo.SampleDataSize);
|
|
|
|
FCUOpusMinimalStream OpusStream;
|
|
|
|
OpusCoder->EncodeStream(PCMBytes, OpusStream);
|
|
|
|
TArray<uint8> SerializedBytes;
|
|
OpusCoder->SerializeMinimal(OpusStream, SerializedBytes);
|
|
|
|
return SerializedBytes;
|
|
}
|
|
|
|
USoundWave* UCUBlueprintLibrary::Conv_WavBytesToSoundWave(const TArray<uint8>& InBytes)
|
|
{
|
|
USoundWave* SoundWave;
|
|
|
|
//Allocate based on thread
|
|
if (IsInGameThread())
|
|
{
|
|
SoundWave = NewObject<USoundWaveProcedural>(USoundWaveProcedural::StaticClass());
|
|
SetSoundWaveFromWavBytes((USoundWaveProcedural*)SoundWave, InBytes);
|
|
}
|
|
else
|
|
{
|
|
//We will go to another thread, copy our bytes
|
|
TArray<uint8> CopiedBytes = InBytes;
|
|
FThreadSafeBool bAllocationComplete = false;
|
|
AsyncTask(ENamedThreads::GameThread, [&bAllocationComplete, &SoundWave]
|
|
{
|
|
SoundWave = NewObject<USoundWaveProcedural>(USoundWaveProcedural::StaticClass());
|
|
bAllocationComplete = true;
|
|
});
|
|
|
|
//block while not complete
|
|
while (!bAllocationComplete)
|
|
{
|
|
//100micros sleep, this should be very quick
|
|
FPlatformProcess::Sleep(0.0001f);
|
|
};
|
|
|
|
SetSoundWaveFromWavBytes((USoundWaveProcedural*)SoundWave, CopiedBytes);
|
|
}
|
|
|
|
return SoundWave;
|
|
}
|
|
|
|
TArray<uint8> UCUBlueprintLibrary::Conv_SoundWaveToWavBytes(USoundWave* SoundWave)
|
|
{
|
|
TArray<uint8> PCMBytes;
|
|
TArray<uint8> WavBytes;
|
|
|
|
//memcpy raw data from soundwave, hmm this won't work for procedurals...
|
|
const void* LockedData = SoundWave->GetResourceData();
|
|
PCMBytes.SetNumUninitialized(SoundWave->GetResourceSize());
|
|
FMemory::Memcpy(PCMBytes.GetData(), LockedData, PCMBytes.Num());
|
|
|
|
//add wav header
|
|
SerializeWaveFile(WavBytes, PCMBytes.GetData(), PCMBytes.Num(), SoundWave->NumChannels, SoundWave->GetSampleRateForCurrentPlatform());
|
|
|
|
return WavBytes;
|
|
}
|
|
|
|
void UCUBlueprintLibrary::Conv_CompactBytesToTransforms(const TArray<uint8>& InCompactBytes, TArray<FTransform>& OutTransforms)
|
|
{
|
|
TArray<float> FloatView;
|
|
FloatView.SetNumUninitialized(InCompactBytes.Num() / 4);
|
|
FPlatformMemory::Memcpy(FloatView.GetData(), InCompactBytes.GetData(), InCompactBytes.Num());
|
|
|
|
//is our float array exactly divisible by 9?
|
|
if (FloatView.Num() % 9 != 0)
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("Conv_CompactBytesToTransforms::float array is not divisible by 9"));
|
|
return;
|
|
}
|
|
|
|
int32 TransformNum = FloatView.Num() / 9;
|
|
OutTransforms.SetNumUninitialized(TransformNum);
|
|
|
|
for (int i = 0; i < FloatView.Num() - 8; i += 9)
|
|
{
|
|
OutTransforms[i/9] = FTransform(FRotator(FloatView[i], FloatView[i+1], FloatView[i+2]), FVector(FloatView[i+3], FloatView[i + 4], FloatView[i + 5]), FVector(FloatView[i+6], FloatView[i+7], FloatView[i+8]));
|
|
}
|
|
}
|
|
|
|
void UCUBlueprintLibrary::Conv_CompactPositionBytesToTransforms(const TArray<uint8>& InCompactBytes, TArray<FTransform>& OutTransforms)
|
|
{
|
|
TArray<float> FloatView;
|
|
FloatView.SetNumUninitialized(InCompactBytes.Num() / 4);
|
|
FPlatformMemory::Memcpy(FloatView.GetData(), InCompactBytes.GetData(), InCompactBytes.Num());
|
|
|
|
//is our float array exactly divisible by 3?
|
|
if (FloatView.Num() % 3 != 0)
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("Conv_CompactPositionBytesToTransforms::float array is not divisible by 3"));
|
|
return;
|
|
}
|
|
|
|
int32 TransformNum = FloatView.Num() / 3;
|
|
OutTransforms.SetNumUninitialized(TransformNum);
|
|
|
|
for (int i = 0; i < FloatView.Num() - 2; i += 3)
|
|
{
|
|
OutTransforms[i / 3] = FTransform(FVector(FloatView[i], FloatView[i + 1], FloatView[i + 2]));
|
|
}
|
|
}
|
|
|
|
void UCUBlueprintLibrary::SetSoundWaveFromWavBytes(USoundWaveProcedural* InSoundWave, const TArray<uint8>& InBytes)
|
|
{
|
|
FWaveModInfo WaveInfo;
|
|
|
|
FString ErrorReason;
|
|
if (WaveInfo.ReadWaveInfo(InBytes.GetData(), InBytes.Num(), &ErrorReason))
|
|
{
|
|
//copy header info
|
|
int32 DurationDiv = *WaveInfo.pChannels * *WaveInfo.pBitsPerSample * *WaveInfo.pSamplesPerSec;
|
|
if (DurationDiv)
|
|
{
|
|
InSoundWave->Duration = *WaveInfo.pWaveDataSize * 8.0f / DurationDiv;
|
|
}
|
|
else
|
|
{
|
|
InSoundWave->Duration = 0.0f;
|
|
}
|
|
|
|
InSoundWave->SetSampleRate(*WaveInfo.pSamplesPerSec);
|
|
InSoundWave->NumChannels = *WaveInfo.pChannels;
|
|
//SoundWaveProc->RawPCMDataSize = WaveInfo.SampleDataSize;
|
|
InSoundWave->bLooping = false;
|
|
InSoundWave->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
|
|
|
|
//Queue actual audio data
|
|
InSoundWave->QueueAudio(WaveInfo.SampleDataStart, WaveInfo.SampleDataSize);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("SetSoundWaveFromWavBytes::WaveRead error: %s"), *ErrorReason);
|
|
}
|
|
}
|
|
|
|
TFuture<UTexture2D*> UCUBlueprintLibrary::Conv_BytesToTexture_Async(const TArray<uint8>& InBytes)
|
|
{
|
|
//Running this on a background thread
|
|
return Async(EAsyncExecution::Thread, [InBytes]
|
|
{
|
|
//Create wrapper pointer we can share easily across threads
|
|
struct FDataHolder
|
|
{
|
|
UTexture2D* Texture = nullptr;
|
|
};
|
|
TSharedPtr<FDataHolder> Holder = MakeShareable(new FDataHolder);
|
|
|
|
FThreadSafeBool bLoadModuleComplete = false;
|
|
IImageWrapperModule* ImageWrapperModule;
|
|
|
|
AsyncTask(ENamedThreads::GameThread, [&bLoadModuleComplete, Holder, &ImageWrapperModule]
|
|
{
|
|
ImageWrapperModule = &FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
|
|
bLoadModuleComplete = true;
|
|
});
|
|
while (!bLoadModuleComplete)
|
|
{
|
|
FPlatformProcess::Sleep(0.001f);
|
|
}
|
|
|
|
EImageFormat DetectedFormat = ImageWrapperModule->DetectImageFormat(InBytes.GetData(), InBytes.Num());
|
|
|
|
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule->CreateImageWrapper(DetectedFormat);
|
|
|
|
if (!(ImageWrapper.IsValid() && ImageWrapper->SetCompressed(InBytes.GetData(), InBytes.Num())))
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("Invalid image format cannot decode %d"), (int32)DetectedFormat);
|
|
return (UTexture2D*)nullptr;
|
|
}
|
|
|
|
//Create image given sizes
|
|
|
|
//Creation of UTexture needs to happen on game thread
|
|
FThreadSafeBool bAllocationComplete = false;
|
|
FIntPoint Size;
|
|
Size.X = ImageWrapper->GetWidth();
|
|
Size.Y = ImageWrapper->GetHeight();
|
|
AsyncTask(ENamedThreads::GameThread, [&bAllocationComplete, Holder, Size]
|
|
{
|
|
Holder->Texture = UTexture2D::CreateTransient(Size.X, Size.Y, PF_B8G8R8A8);
|
|
Holder->Texture->UpdateResource();
|
|
bAllocationComplete = true;
|
|
});
|
|
|
|
while (!bAllocationComplete)
|
|
{
|
|
//sleep 10ms intervals
|
|
FPlatformProcess::Sleep(0.001f);
|
|
}
|
|
|
|
//Uncompress on a background thread pool
|
|
TArray64<uint8> UncompressedBGRA;
|
|
if (!ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA))
|
|
{
|
|
return (UTexture2D*)nullptr;
|
|
}
|
|
|
|
FUpdateTextureData* UpdateData = new FUpdateTextureData;
|
|
UpdateData->Texture2D = Holder->Texture;
|
|
UpdateData->Region = FUpdateTextureRegion2D(0, 0, 0, 0, Size.X, Size.Y);
|
|
UpdateData->BufferArray = UncompressedBGRA;
|
|
UpdateData->Pitch = Size.X * 4;
|
|
UpdateData->Wrapper = ImageWrapper;
|
|
|
|
//This command sends it to the render thread
|
|
ENQUEUE_RENDER_COMMAND(BytesToTextureAsyncCommand)(
|
|
[UpdateData](FRHICommandList& CommandList)
|
|
{
|
|
RHIUpdateTexture2D(
|
|
((FTextureResource*)UpdateData->Texture2D->GetResource())->GetTextureRHI()->GetTexture2D(),
|
|
0,
|
|
UpdateData->Region,
|
|
UpdateData->Pitch,
|
|
UpdateData->BufferArray.GetData()
|
|
);
|
|
delete UpdateData; //now that we've updated the texture data, we can finally release any data we're holding on to
|
|
});//End Enqueue
|
|
|
|
return Holder->Texture;
|
|
});//End async
|
|
}
|
|
|
|
bool UCUBlueprintLibrary::Conv_TextureToBytes(UTexture2D* Texture, TArray<uint8>& OutBuffer, EImageFormatBPType Format /*= EImageFormatBPType::JPEG*/)
|
|
{
|
|
if (!Texture || !Texture->IsValidLowLevel())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Get our wrapper module
|
|
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
|
|
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper((EImageFormat)Format);
|
|
|
|
int32 Width = Texture->GetPlatformData()->Mips[0].SizeX;
|
|
int32 Height = Texture->GetPlatformData()->Mips[0].SizeY;
|
|
int32 DataLength = Width * Height * 4;
|
|
|
|
void* TextureDataPointer = Texture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_ONLY);
|
|
|
|
ImageWrapper->SetRaw(TextureDataPointer, DataLength, Width, Height, ERGBFormat::BGRA, 8);
|
|
|
|
//This part can take a while, has performance implications
|
|
OutBuffer = ImageWrapper->GetCompressed();
|
|
|
|
Texture->GetPlatformData()->Mips[0].BulkData.Unlock();
|
|
|
|
return true;
|
|
}
|
|
|
|
FString UCUBlueprintLibrary::NowUTCString()
|
|
{
|
|
return FDateTime::UtcNow().ToString();
|
|
}
|
|
|
|
FString UCUBlueprintLibrary::GetLoginId()
|
|
{
|
|
return FPlatformMisc::GetLoginId();
|
|
}
|
|
|
|
int32 UCUBlueprintLibrary::ToHashCode(const FString& String)
|
|
{
|
|
return (int32)CityHash32(TCHAR_TO_ANSI(*String), String.Len());
|
|
}
|
|
|
|
void UCUBlueprintLibrary::MeasureTimerStart(const FString& Category /*= TEXT("TimeTaken")*/)
|
|
{
|
|
FCUMeasureTimer::Tick(Category);
|
|
}
|
|
|
|
float UCUBlueprintLibrary::MeasureTimerStop(const FString& Category /*= TEXT("TimeTaken")*/, bool bShouldLogResult /*= true*/)
|
|
{
|
|
return (float)FCUMeasureTimer::Tock(Category, bShouldLogResult);
|
|
}
|
|
|
|
void UCUBlueprintLibrary::CallFunctionOnThread(const FString& FunctionName, ESIOCallbackType ThreadType, UObject* WorldContextObject /*= nullptr*/)
|
|
{
|
|
UObject* Target = WorldContextObject;
|
|
|
|
if (!Target->IsValidLowLevel())
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("CallFunctionOnThread: Target not found for '%s'"), *FunctionName);
|
|
return;
|
|
}
|
|
|
|
UFunction* Function = Target->FindFunction(FName(*FunctionName));
|
|
if (nullptr == Function)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("CallFunctionOnThread: Function not found '%s'"), *FunctionName);
|
|
return;
|
|
}
|
|
|
|
switch (ThreadType)
|
|
{
|
|
case CALLBACK_GAME_THREAD:
|
|
if (IsInGameThread())
|
|
{
|
|
Target->ProcessEvent(Function, nullptr);
|
|
}
|
|
else
|
|
{
|
|
FCULambdaRunnable::RunShortLambdaOnGameThread([Function, Target]
|
|
{
|
|
if (Target->IsValidLowLevel())
|
|
{
|
|
Target->ProcessEvent(Function, nullptr);
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case CALLBACK_BACKGROUND_THREADPOOL:
|
|
FCULambdaRunnable::RunLambdaOnBackGroundThreadPool([Function, Target]
|
|
{
|
|
if (Target->IsValidLowLevel())
|
|
{
|
|
Target->ProcessEvent(Function, nullptr);
|
|
}
|
|
});
|
|
break;
|
|
case CALLBACK_BACKGROUND_TASKGRAPH:
|
|
FCULambdaRunnable::RunShortLambdaOnBackGroundTask([Function, Target]
|
|
{
|
|
if (Target->IsValidLowLevel())
|
|
{
|
|
Target->ProcessEvent(Function, nullptr);
|
|
}
|
|
});
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void UCUBlueprintLibrary::CallFunctionOnThreadGraphReturn(const FString& FunctionName, ESIOCallbackType ThreadType, struct FLatentActionInfo LatentInfo, UObject* WorldContextObject /*= nullptr*/)
|
|
{
|
|
UObject* Target = WorldContextObject;
|
|
FCULatentAction* LatentAction = FCULatentAction::CreateLatentAction(LatentInfo, Target);
|
|
|
|
if (!Target->IsValidLowLevel())
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("CallFunctionOnThread: Target not found for '%s'"), *FunctionName);
|
|
LatentAction->Call();
|
|
return;
|
|
}
|
|
|
|
UFunction* Function = Target->FindFunction(FName(*FunctionName));
|
|
if (nullptr == Function)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("CallFunctionOnThread: Function not found '%s'"), *FunctionName);
|
|
LatentAction->Call();
|
|
return;
|
|
}
|
|
|
|
switch (ThreadType)
|
|
{
|
|
case CALLBACK_GAME_THREAD:
|
|
if (IsInGameThread())
|
|
{
|
|
Target->ProcessEvent(Function, nullptr);
|
|
LatentAction->Call();
|
|
}
|
|
else
|
|
{
|
|
FCULambdaRunnable::RunShortLambdaOnGameThread([Function, Target, LatentAction]
|
|
{
|
|
if (Target->IsValidLowLevel())
|
|
{
|
|
Target->ProcessEvent(Function, nullptr);
|
|
LatentAction->Call();
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case CALLBACK_BACKGROUND_THREADPOOL:
|
|
FCULambdaRunnable::RunLambdaOnBackGroundThreadPool([Function, Target, LatentAction]
|
|
{
|
|
if (Target->IsValidLowLevel())
|
|
{
|
|
Target->ProcessEvent(Function, nullptr);
|
|
LatentAction->Call();
|
|
}
|
|
});
|
|
break;
|
|
case CALLBACK_BACKGROUND_TASKGRAPH:
|
|
FCULambdaRunnable::RunShortLambdaOnBackGroundTask([Function, Target, LatentAction]
|
|
{
|
|
if (Target->IsValidLowLevel())
|
|
{
|
|
Target->ProcessEvent(Function, nullptr);
|
|
LatentAction->Call();
|
|
}
|
|
});
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool UCUBlueprintLibrary::SerializeStruct(UStruct* Struct, void* StructPtr, TArray<uint8>& OutBytes)
|
|
{
|
|
if (!Struct || !StructPtr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FMemoryWriter MemoryWriter(OutBytes, true);
|
|
Struct->SerializeBin(MemoryWriter, StructPtr);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UCUBlueprintLibrary::DeserializeStruct(UStruct* Struct, void* StructPtr, const TArray<uint8>& InBytes)
|
|
{
|
|
if (!Struct || !StructPtr || InBytes.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FMemoryReader MemoryReader(InBytes, true);
|
|
|
|
//Bi-directional, also works as a deserialization
|
|
Struct->SerializeBin(MemoryReader, StructPtr);
|
|
|
|
return true;
|
|
}
|
|
|
|
DEFINE_FUNCTION(UCUBlueprintLibrary::execBytesToStruct)
|
|
{
|
|
// Extract the parameters
|
|
P_GET_TARRAY_REF(uint8, InBytes);
|
|
Stack.StepCompiledIn<FStructProperty>(NULL);
|
|
FStructProperty* StructProp = CastField<FStructProperty>(Stack.MostRecentProperty);
|
|
void* StructPtr = Stack.MostRecentPropertyAddress;
|
|
|
|
P_FINISH;
|
|
|
|
// Deserialize the struct
|
|
bool bSuccess = DeserializeStruct(StructProp->Struct, StructPtr, InBytes);
|
|
|
|
// Return the success status
|
|
*(bool*)RESULT_PARAM = bSuccess;
|
|
}
|
|
|
|
//custom thunk needed to handle wildcard structs
|
|
DEFINE_FUNCTION(UCUBlueprintLibrary::execStructToBytes)
|
|
{
|
|
// Extract the parameters
|
|
Stack.StepCompiledIn<FStructProperty>(NULL);
|
|
FStructProperty* StructProp = CastField<FStructProperty>(Stack.MostRecentProperty);
|
|
void* StructPtr = Stack.MostRecentPropertyAddress;
|
|
|
|
P_GET_TARRAY_REF(uint8, OutBytes);
|
|
|
|
P_FINISH;
|
|
|
|
// Serialize the struct
|
|
bool bSuccess = SerializeStruct(StructProp->Struct, StructPtr, OutBytes);
|
|
|
|
// Return the success status
|
|
*(bool*)RESULT_PARAM = bSuccess;
|
|
}
|
|
|
|
#pragma warning( pop )
|