// Copyright 2017-2020 David Romanski(Socke). All Rights Reserved. #pragma once #include "WebCommunication.h" #include "WebCommunicationBPLibrary.generated.h" class UWebCommunicationRequestCompleteObject; class FByteToFileThread; class FWebComTruncateUploadedFileThread; class UAsyncNodeWebCom; UCLASS() class WEBCOMMUNICATION_API UWebCommunicationBPLibrary : public UBlueprintFunctionLibrary { GENERATED_UCLASS_BODY() public: static UWebCommunicationBPLibrary* webcom; ~UWebCommunicationBPLibrary(); //Delegates DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FhttpRequestCompleteDelegate,const FString, dataString, const TArray&, dataArray, const TArray&, header, const int32, statusCode, const TArray&, byteData, const FString, requestID); UFUNCTION() void httpRequestCompleteDelegate(const FString dataString, const TArray& dataArray, const TArray& header, const int32 statusCode, const TArray& byteData, const FString requestID); UPROPERTY(BlueprintAssignable, Category = "WebCommunication|Events|RequestComplete") FhttpRequestCompleteDelegate onhttpRequestCompleteDelegate; DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FhttpRequestCompleteGoogleInfoDelegate, const FString, fileName, const int64, fileSizeInBytes, const int32, statusCode, const FString, downloadID, const FString, requestID); UFUNCTION() void httpRequestCompleteGoogleInfoDelegate(const FString fileName, const int64 fileSizeInBytes, const int32 statusCode, const FString downloadID, const FString requestID); UPROPERTY(BlueprintAssignable, Category = "WebCommunication|Events|RequestCompleteGoogleInfo") FhttpRequestCompleteGoogleInfoDelegate onhttpRequestCompleteGoogleInfoDelegate; DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FhttpFileProgressDelegate, const float, size, const int32, bytesSent, const float, percentUpload, const int32, bytesReceived, const float, percentDownload); /** * This function is deprecated and will be removed. Please use httpFileDownload or httpFileUpload. */ //UFUNCTION() // void httpFileProgressDelegate(const float size, const int32 bytesSent, const float percentUpload, const int32 bytesReceived, const float percentDownload); //UPROPERTY(BlueprintAssignable, Category = "WebCommunication|Deprecated|Events|FileProgress") // FhttpFileProgressDelegate onhttpFileProgressDelegate; DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FhttpFileDownloadDelegate, const float, size, const float, megaBytesReceived, const float, percentDownload, const float, megaBit, const FString, requestID); /** * @param size Filesize in Megabyte * @param megaBit Downloadspeed in Megabit/second */ UFUNCTION() void httpFileDownloadDelegate(const float size, const float megaBytesReceived, const float percentDownload, const float megaBit, const FString requestID); UPROPERTY(BlueprintAssignable, Category = "WebCommunication|Events|Download") FhttpFileDownloadDelegate onhttpFileDownloadDelegate; DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FhttpFileUploadDelegate, const float, size, const int32, bytesSent, const float, percentUpload, const FString, requestID); /** * @param size Filesize in Byte */ UFUNCTION() void httpFileUploadDelegate(const float size, const int32 bytesSent, const float percentUpload, const FString requestID); UPROPERTY(BlueprintAssignable, Category = "WebCommunication|Events|Upload") FhttpFileUploadDelegate onhttpFileUploadDelegate; DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FhttpServerSendEventDelegate, const FString, response, const FString, requestID); UFUNCTION() void httpServerSendEventDelegate(const FString response, const FString requestID); UPROPERTY(BlueprintAssignable, Category = "WebCommunication|Events|ServerSendEvent") FhttpServerSendEventDelegate onhttpServerSendEventDelegate; /** * This function is deprecated and will be removed. Please use getWebCommunicationTarget. */ //UFUNCTION(BlueprintCallable, BlueprintPure, Category = "WebCommunication|Deprecated") // static UWebCommunicationBPLibrary* getTarget(); /** * Return a Target for the Events */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "WebCommunication") static UWebCommunicationBPLibrary* getWebCommunicationTarget(); /** * This function is deprecated and will be removed. Please use httpRequestGET. */ //UFUNCTION(BlueprintCallable, Category = "WebCommunication|Deprecated") // static UWebCommunicationBPLibrary* httpRequestGET(FString url, FString optionalRequestID, FString &requestID); /** * Send a GET HTTP Request to Webserver. * @param optionalRequestID Your own RequestID. It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. * @param requestID It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. * @param bindServerSendEvent Activates the "Server Send Event" for this http request. If you as a client want to get multiple http responses from the server for one http request you can use the http technique "Server Sent Events". */ UFUNCTION(BlueprintCallable, Category = "WebCommunication|GET", meta = (AutoCreateRefTerm = "otherHttpRequests,header")) static int32 CreateHttpRequestGET(TArray otherHttpRequests, FString url, TMap header, FString optionalRequestID, bool bindServerSendEvent, TArray &httpRequests, FString &requestID); /** * For downloads. Data is copied to the hard disk if you cancel the download. * @param ActionIfFileExists Overwrite = overwrites an existing file otherwise a new one will be created. Cancel Download = If the file already exists, the download will be aborted. Resume = Download continues. If the file is missing, the download is started from the beginning. * @param optionalRequestID Your own RequestID. It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. * @param requestID It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication|Download", meta = (AutoCreateRefTerm = "otherHttpRequests,header")) static int32 CreateHttpRequestGETDownload(TArray& httpRequests, FString& requestID, TArray otherHttpRequests, FString url, TMap header, EHTTPWebComFileDownloadResumeType ActionIfFileExists, EHTTPWebComFileUpload DirectoryType, FString filePathWithFileName, FString optionalRequestID = ""); /** * For downloads. The file is downloaded in steps. This significantly reduces RAM consumption. The web server must support resuming aborted downloads. * @param ActionIfFileExists Overwrite = overwrites an existing file otherwise a new one will be created. Cancel Download = If the file already exists, the download will be aborted. Resume = Download continues. If the file is missing, the download is started from the beginning. * @param FileSizeStepsInBytes Default = 10 megabytes. Steps in which the download is aborted and restarted. Specified in bytes. You can use the node "MegabyteToByte" for conversion. * @param optionalRequestID Your own RequestID. It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. * @param requestID It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication|Download", meta = (AutoCreateRefTerm = "otherHttpRequests,header")) static int32 CreateHttpRequestGETLowRamDownload(TArray& httpRequests, FString& requestID, TArray otherHttpRequests, FString url, TMap header, EHTTPWebComFileDownloadResumeType ActionIfFileExists, EHTTPWebComFileUpload DirectoryType, FString filePathWithFileName, int32 FileSizeStepsInBytes = 10485760, FString optionalRequestID =""); UFUNCTION(BlueprintCallable, Category = "WebCommunication|GET", meta = (AutoCreateRefTerm = "otherHttpRequests")) static int32 CreateHttpRequestGoogleDrive(TArray otherHttpRequests, FString downloadID, FString optionalRequestID, int64 optionalFileSizeInByte, TArray &httpRequests, FString &requestID); UFUNCTION(BlueprintCallable, Category = "WebCommunication|GET", meta = (AutoCreateRefTerm = "otherHttpRequests")) static int32 CreateHttpRequestGoogleDriveFileInfo(TArray otherHttpRequests, FString downloadID, FString optionalRequestID, TArray& httpRequests, FString& requestID); UFUNCTION(BlueprintCallable, Category = "WebCommunication|GET", meta = (AutoCreateRefTerm = "otherHttpRequests")) static int32 CreateHttpRequestAnonfiles(TArray otherHttpRequests, FString url, FString optionalRequestID, TArray &httpRequests, FString &requestID); UFUNCTION(BlueprintCallable, Category = "WebCommunication") static void executeHttpRequests(TArray httpRequests, UWebCommunicationBPLibrary* &WebCommunicationTarget); /** * Send a POST HTTP Request to Webserver. * @param url * @param POSTData * @param requestID Optional parameter. It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. * @param bindServerSendEvent Activates the "Server Send Event" for this http request. If you as a client want to get multiple http responses from the server for one http request you can use the http technique "Server Sent Events". */ UFUNCTION(BlueprintCallable, Category = "WebCommunication|POST", meta = (AutoCreateRefTerm = "otherHttpRequests,header")) static int32 CreateHttpRequestPOST(TArray otherHttpRequests, FString url, TMap header, TMap POSTData, FString optionalRequestID, bool bindServerSendEvent, TArray &httpRequests, FString &requestID); /** * Individual HTTP Request. You have to set all parameters yourself. * * @param url Server URL * @param header User-Agent, Content-Type * @param verb POST, GET, PUT, PATCH, DELETE * @param content Request Body or Filepath * @param requestID Optional parameter. It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. * @param addContentLengthHeader add Content-Length header with the length of the content. * @param bindServerSendEvent Activates the "Server Send Event" for this http request. If you as a client want to get multiple http responses from the server for one http request you can use the http technique "Server Sent Events". */ UFUNCTION(BlueprintCallable, Category = "WebCommunication|Individual", meta = (AutoCreateRefTerm = "otherHttpRequests,binaryContent")) static void CreateHttpRequestIndividual(TArray otherHttpRequests, FString url, TMap header, FString verb, EHTTPWebComIndividualType contentBodyType, FString contentBody,TArray binaryContent, FString optionalRequestID, TArray &httpRequests, FString &requestID, bool addContentLengthHeader = true, bool bindDownloadProgressEvent = false, bool bindUploadProgressEvent = false, bool bindServerSendEvent = false); /** * Send a Fila to a Webserver. * * @param url Server URL * @param DirectoryType Absolute or Relative * @param id name equivalent to from an html upload * @param filePath File with path * @param uploadType Upload progress is only refreshed with type PUT * @param requestID Optional parameter. It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. */ /*UFUNCTION(BlueprintCallable, Category = "WebCommunication|Deprecated") static void httpRequestFileUpload(FString url, EHTTPWebComFileUpload DirectoryType, FString id, FString filePath, EHTTPWebComFileUploadType uploadType, FString optionalRequestID, FString &requestID);*/ /** * Send a file to a Webserver. * * @param otherHttpRequests * @param url Server URL * @param DirectoryType Absolute or Relative * @param filePath path with filename at the end * @param fileID name equivalent to from an html upload * @param requestID Optional parameter. It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication|File Upload", meta = (AutoCreateRefTerm = "otherHttpRequests,header,POSTData")) static int32 CreateHttpRequestFileUploadPOST(TArray otherHttpRequests, FString url, TMap header, EHTTPWebComFileUpload DirectoryType, FString filePath, FString fileID, TMap POSTData, FString optionalRequestID, TArray &httpRequests, FString &requestID); /** * Send multiple files to a Webserver. * * @param otherHttpRequests * @param url Server URL * @param DirectoryType Absolute or Relative * @param filePaths paths with filenames at the ends * @param fileIDs names equivalent to from an html upload. It is also possible to enter only one file ID for all files. * @param requestID Optional parameter. It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication|File Upload", meta = (AutoCreateRefTerm = "otherHttpRequests,header,POSTData")) static int32 CreateHttpRequestFileMultiUploadPOST(TArray otherHttpRequests, FString url, TMap header, EHTTPWebComFileUpload DirectoryType, TArray filePaths, TArray fileIDs, TMap POSTData, FString optionalRequestID, TArray& httpRequests, FString& requestID); /** * Send a file to a Webserver. * * @param otherHttpRequests * @param url Server URL * @param DirectoryType Absolute or Relative * @param filePath path with filename at the end * @param fileID name equivalent to from an html upload * @param requestID Optional parameter. It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. * @param uploadType PUT Stream (Blank):With this option only the file is written into the http body and nothing else. If your server can handle it then use this option. Additionally headers will be created. fileid, filename, fileformat, mimetype ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ PUT Stream (Multiplart):With this option a header is created which goes with a trick into the body and writes the necessary information of the file into the body. Important! The only way to write something at the end of the body is to write this information into the file. After the upload is finished or canceled the plugin removes this information from the file. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ PUT Stream (Copy Multiplart):This option also writes information to the file. But a copy of the file is created before and the info is written into the copy. The copy will be deleted after the upload. Making a copy of a large file can lead to freezing because it is done in the game thread. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication|File Upload", meta = (AutoCreateRefTerm = "otherHttpRequests,header")) static int32 CreateHttpRequestFileUploadPUT(TArray otherHttpRequests, FString url, TMap header, EHTTPWebComFileUpload DirectoryType, FString filePath, FString fileID, EHTTPWebComFileUploadType uploadType, FString optionalRequestID, TArray &httpRequests, FString &requestID); /** * Send multiple files to a Webserver. * * @param otherHttpRequests * @param url Server URL * @param DirectoryType Absolute or Relative * @param filePaths paths with filenames at the ends * @param fileIDs names equivalent to from an html upload. It is also possible to enter only one file ID for all files. * @param requestID Optional parameter. It will be output again in the httpRequestComplete event. This allows the request being assigned to the response. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication|File Upload", meta = (AutoCreateRefTerm = "otherHttpRequests,header")) static int32 CreateHttpRequestFileMultiUploadPUT(TArray otherHttpRequests, FString url, TMap header, EHTTPWebComFileUpload DirectoryType, TArray filePaths, TArray fileIDs, FString optionalRequestID, TArray& httpRequests, FString& requestID); /** * This function is deprecated and will be removed. Please use httpRequestPOST. */ //UFUNCTION(BlueprintCallable, Category = "WebCommunication|Deprecated") // static TArray createAndAppendPOSTData(FString id, FString value, TArray POSTData); /** * This function is deprecated and will be removed. Please use httpRequestPOST. */ //UFUNCTION(BlueprintCallable, Category = "WebCommunication|Deprecated") // static TArray createPOSTData(FString id, FString value); /** * Header is added to each request. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication|Header") static void addPersistentHTTPRequestHeader(FString id, FString value); UFUNCTION(BlueprintCallable, Category = "WebCommunication|Header") static void removePersistentHTTPRequestHeader(FString id); /** * Create a File from bytes. * * @param byteData from httpRequestCompleteDelegate Event * @param DirectoryType Absolute or Relative * @param filePath path with filename c:\myFile.zip or content\myFile.zip */ UFUNCTION(BlueprintCallable, Category = "WebCommunication") static void byteDataToFile(EHTTPWebComFileBytesToFileActionType fileAction,TArray byteData, EHTTPWebComFileUpload DirectoryType, FString filePath); /** * Returns a percent-encoded version of the passed in string. Do not use with strings with http::// but for the parameters. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication") static FString urlEncode(FString urlParameter); /** * Returns a percent-encoded version of the passed in string. Do not use with strings with http::// but for the parameters. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "WebCommunication") static FString urlEncodePure(FString urlParameter); /** * Cancel Request */ UFUNCTION(BlueprintCallable, Category = "WebCommunication") static void cancelRequest(FString requestID); UFUNCTION(BlueprintCallable, BlueprintPure, Category = "WebCommunication") static int32 megabyteToByte(int32 mb); UFUNCTION(BlueprintCallable, BlueprintPure, Category = "WebCommunication") static float byteToMegabyte(int32 byte); /** * With a multipart PUT STREAM upload something must be written at the end of the file. With this function you can check if the part is still in the file. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication") static void searchUploadLeftovers(EHTTPWebComFileUpload DirectoryType, FString filePath, bool& foundLeftovers, int64& originalFileSize); /** * With a multipart PUT STREAM upload something must be written at the end of the file. With this function the part can be removed. In this function the function searchUploadLeftovers is called. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication") static bool repairFileAfterUpload(EHTTPWebComFileUpload DirectoryType, FString filePath); /** * Determines the mime type based on the file extension. */ UFUNCTION(BlueprintCallable, Category = "WebCommunication") static void getMimeTypeByFile(EHTTPWebComFileUpload DirectoryType, FString filePath, FString& mimeType, bool& success); UFUNCTION(BlueprintCallable, Category = "WebCommunication") static TMap getMimeTypeMap(); void addRequestToTruncateThread(FhttpRequest& httpRequest); bool isFileBlocked(FString filePath); TMap additionalHeader; bool removeHeaders; FString getMimeTypeInternal(FString fileType); TMap mimeTypes; TMap toCancelRequests; TMap> startedRequests; FByteToFileThread* getByteToFileThread(); static FString Int64ToFString(int64 Num); void startRequests(TArray& httpRequests); void registerUAsyncNodeHttpRequest(FString requestID, UAsyncNodeWebCom* asyncNodeHttpRequest); void unregisterUAsyncNodeHttpRequest(FString requestID); UAsyncNodeWebCom* getUAsyncNodeHttpRequest(FString requestID); //void registerUAsyncNodeGoogleInfoRequest(FString requestID, UAsyncNodeHttpGoogleFileInfoRequest* asyncNodeHttpRequest); //void unregisterUAsyncNodeGoogleInfoRequest(FString requestID); //UAsyncNodeHttpGoogleFileInfoRequest* getUAsyncNodeGoogleInfoRequest(FString requestID); private: TMap asyncNodeHttpRequests; static void fireErrorMessageInGameThread(UAsyncNodeWebCom* asyncNodeHttpRequest, int32 errorCode, FString requestID); void fillRequestGET(struct FhttpRequest& requestStruct, TSharedRef request); void fillRequestPOST(struct FhttpRequest& requestStruct, TSharedRef request); void fillHeader(FhttpRequest& requestStruct, TSharedRef request); bool fillRequestUPLOAD(struct FhttpRequest& requestStruct, TSharedRef request); TArray fstringToByteArray(FString data); FString postMapToString(TMap POSTData); class FByteToFileThread* byteToFileThread = nullptr; class FWebComTruncateUploadedFileThread* truncateUploadedFileThread = nullptr; }; class FByteToFileThread : public FRunnable { public: FByteToFileThread(){ FString threadName = "FByteToFileThread" + FGuid::NewGuid().ToString(); thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Normal); } virtual uint32 Run() override { while (true) { while (fileQueue.IsEmpty() == false) { FfileToByteStruct fileToByteStruct; fileQueue.Dequeue(fileToByteStruct); if (fileToByteStruct.byteData.Num() <= 0) { UE_LOG(LogTemp, Error, TEXT("UWebCommunicationBPLibrary: Empty byteData Array.")); break; } if (fileToByteStruct.filePath.IsEmpty()) { UE_LOG(LogTemp, Error, TEXT("UWebCommunicationBPLibrary: FilePath not set.")); break; } FString dir; if (fileToByteStruct.DirectoryType == EHTTPWebComFileUpload::E_ad) { dir = FPaths::ConvertRelativePathToFull(fileToByteStruct.filePath); } else { FString gameDir = FPaths::ProjectDir(); dir = FPaths::ConvertRelativePathToFull(gameDir + fileToByteStruct.filePath); } switch (fileToByteStruct.fileAction) { case EHTTPWebComFileBytesToFileActionType::E_NOT_OVERWRITE: if (FPaths::FileExists(dir)) { UE_LOG(LogTemp, Warning, TEXT("UWebCommunicationBPLibrary: File exist. Cancel write to file.")); break; } break; case EHTTPWebComFileBytesToFileActionType::E_OVERWRITE: FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*dir); } FArchive* writer = IFileManager::Get().CreateFileWriter(*dir, EFileWrite::FILEWRITE_Append); if (!writer) { UE_LOG(LogTemp, Error, TEXT("ByteDataToFile: Can't write into %s "), *dir); } else { writer->Seek(writer->TotalSize()); writer->Serialize(fileToByteStruct.byteData.GetData(), fileToByteStruct.byteData.Num()); writer->Flush(); writer->Close(); fileToByteStruct.byteData.Empty(); fileToByteStruct.filePath.Empty(); } delete writer; } pauseThread(true); //workaround. suspend do not work on all platforms(IOS,Android). lets sleep while (paused) { FPlatformProcess::Sleep(0.01); } } thread = nullptr; return 0; } void saveFile(FfileToByteStruct& fileToByteStruct) { fileQueue.Enqueue(fileToByteStruct); pauseThread(false); } void pauseThread(bool pause) { paused = pause; if (thread != nullptr) thread->Suspend(pause); } protected: TQueue fileQueue; FRunnableThread* thread = nullptr; bool paused; }; class FWebComTruncateUploadedFileThread : public FRunnable { public: FWebComTruncateUploadedFileThread() { FString threadName = "FWebComTruncateUploadedFileThread" + FGuid::NewGuid().ToString(); thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Normal); } virtual uint32 Run() override { while (true) { while (httpRequestQueue.IsEmpty() == false) { FhttpRequest httpRequest; httpRequestQueue.Dequeue(httpRequest); //Wait until the file has been released. FPlatformProcess::Sleep(2); if (httpRequest.requestType == EHTTPWebComRequestType::PUT_STREAM_MP && !httpRequest.filePath.IsEmpty() && httpRequest.originalFileSize > 0) { IFileHandle* f = FPlatformFileManager::Get().GetPlatformFile().OpenWrite(*httpRequest.filePath, true, true); if (f != nullptr) { bool truncate = f->Truncate(httpRequest.originalFileSize); f->Flush(); if (truncate) { UE_LOG(LogTemp, Display, TEXT("UWebCommunicationBPLibrary: truncate okay: %s"),*httpRequest.filePath); } else { UE_LOG(LogTemp, Display, TEXT("UWebCommunicationBPLibrary: truncate fail: %s"), *httpRequest.filePath); } delete f; } blockedFiles.Remove(httpRequest.filePath); } if (httpRequest.requestType == EHTTPWebComRequestType::PUT_STREAM_COPY && !httpRequest.filePath.IsEmpty()) { if (FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*httpRequest.filePath)) { UE_LOG(LogTemp, Display, TEXT("UWebCommunicationBPLibrary: upload copy has been deleted: %s"), *httpRequest.filePath); } else { UE_LOG(LogTemp, Display, TEXT("UWebCommunicationBPLibrary: upload copy could not be deleted: %s"), *httpRequest.filePath); } blockedFiles.Remove(httpRequest.filePath); } } pauseThread(true); //workaround. suspend do not work on all platforms(IOS,Android). lets sleep while (paused) { FPlatformProcess::Sleep(1); } } thread = nullptr; return 0; } void addRequest(FhttpRequest& httpRequestP) { httpRequestQueue.Enqueue(httpRequestP); blockedFiles.Add(httpRequestP.filePath); pauseThread(false); } void pauseThread(bool pause) { paused = pause; if (thread != nullptr) thread->Suspend(pause); } TArray blockedFiles; protected: TQueue httpRequestQueue; FRunnableThread* thread = nullptr; bool paused; };