增加多屏显示,视频播放

This commit is contained in:
liuyunhui
2025-09-19 14:40:23 +08:00
parent fded7b9a51
commit 9076a5ea41
112 changed files with 24986 additions and 21 deletions
-1
View File
@@ -24,7 +24,6 @@
*.lai *.lai
*.la *.la
*.a *.a
*.lib
# Executables # Executables
*.exe *.exe
+75
View File
@@ -0,0 +1,75 @@
# ---> UnrealEngine
# Visual Studio 2015 user specific files
.vs/
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
# Executables
*.exe
*.out
*.app
*.ipa
# These project files can be generated by the engine
*.xcodeproj
*.xcworkspace
*.sln
*.suo
*.opensdf
*.sdf
*.VC.db
*.VC.opendb
# Precompiled Assets
SourceArt/**/*.png
SourceArt/**/*.tga
# Binary Files
Binaries/*
Plugins/**/Binaries/*
# Builds
Build/*
# Whitelist PakBlacklist-<BuildConfiguration>.txt files
!Build/*/
Build/*/**
!Build/*/PakBlacklist*.txt
# Don't ignore icon files in Build
!Build/**/*.ico
# Built data for maps
*_BuiltData.uasset
# Configuration files generated by the Editor
Saved/*
# Compiled source files for the engine to use
Intermediate/*
Plugins/**/Intermediate/*
# Cache files for the editor to use
DerivedDataCache/*
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@@ -0,0 +1,30 @@
{
"FileVersion": 3,
"Version": 255,
"VersionName": "2.55",
"FriendlyName": "SimpleTCPUDPSocketClient",
"Description": "TCP and UDP Blueprint Socket Client",
"Category": "Sockets",
"CreatedBy": "Socke",
"CreatedByURL": "",
"DocsURL": "http://virtualbird.de/ue4Doku/SimpleTCPUDPSocketClient2/",
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/5d4ddd5493ce4d2295ef9be6c67eff82",
"SupportURL": "mailto:unrealmarketplace@virtualbird.de",
"EngineVersion": "5.3.0",
"CanContainContent": false,
"Installed": true,
"Modules": [
{
"Name": "SocketClient",
"Type": "Runtime",
"LoadingPhase": "PreLoadingScreen",
"PlatformAllowList": [
"Win64",
"Mac",
"IOS",
"Android",
"Linux"
]
}
]
}
@@ -0,0 +1,115 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#include "DNSClientSocketClient.h"
UDNSClientSocketClient::UDNSClientSocketClient(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer) {
}
void UDNSClientSocketClient::resolveDomain(ISocketSubsystem* socketSubSystem, FString domainP, bool useDNSCache, FString dnsIP) {
domain = domainP;
resolving = true;
if (useDNSCache) {
if (dnsCache.Find(domain) != nullptr) {
ip = *dnsCache.Find(domain);
resolving = false;
return;
}
}
FIPv4Endpoint Endpoint;
FString socketName;
//FIPv4Endpoint::Parse("127.0.0.1:12345", Endpoint);
//socket = FUdpSocketBuilder(*socketName).AsReusable().WithBroadcast().BoundToEndpoint(Endpoint);
socket = FUdpSocketBuilder(*socketName).AsReusable().WithBroadcast();
if (socket == nullptr || socket == NULL) {
UE_LOG(LogTemp, Error, TEXT("UE4 could not init a UDP socket to resolve %s on %s."), *domain, *dnsIP);
resolving = false;
return;
}
else {
FTimespan ThreadWaitTime = FTimespan::FromMilliseconds(100);
FUdpSocketReceiver* udpSocketReceiver = new FUdpSocketReceiver(socket, ThreadWaitTime, TEXT("UE4 DNSClient"));
udpSocketReceiver->OnDataReceived().BindUObject(this, &UDNSClientSocketClient::UDPReceiver);
udpSocketReceiver->Start();
TSharedRef<FInternetAddr> addr = socketSubSystem->CreateInternetAddr();
bool bIsValid;
addr->SetIp(*dnsIP, bIsValid);
addr->SetPort(53);
if (bIsValid) {
TArray<unsigned char> dnsRequest;
//id (ramdom choose)
dnsRequest.Add(0x22);
dnsRequest.Add(0x76);
//head
dnsRequest.Add(0x1);
dnsRequest.Add(0x0);
dnsRequest.Add(0x0);
dnsRequest.Add(0x1);
dnsRequest.Add(0x0);
dnsRequest.Add(0x0);
dnsRequest.Add(0x0);
dnsRequest.Add(0x0);
dnsRequest.Add(0x0);
dnsRequest.Add(0x0);
//domain
//split in domain parts than add the length as byte and the part as bytes
TArray<FString> domainParts;
domain.ParseIntoArray(domainParts, TEXT("."), true);
for (int i = 0; i < domainParts.Num(); i++) {
FTCHARToUTF8 Convert(*domainParts[i]);
dnsRequest.Add(Convert.Length());
dnsRequest.Append((uint8*)Convert.Get(), Convert.Length());
}
//foot
dnsRequest.Add(0x0);
dnsRequest.Add(0x0);
dnsRequest.Add(0x1);
dnsRequest.Add(0x0);
dnsRequest.Add(0x1);
int32 sent;
socket->SendTo((uint8*)dnsRequest.GetData(), dnsRequest.Num(), sent, *addr);
}
}
}
void UDNSClientSocketClient::UDPReceiver(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt) {
//TSharedPtr<FInternetAddr> peerAddr = EndPt.ToInternetAddr();
//FString ip = peerAddr->ToString(false);
//int32 port = peerAddr->GetPort();
////UE_LOG(LogTemp, Error, TEXT("peerAddr:%s port:%i"), *peerAddr->ToString(false), peerAddr->GetPort());
TArray<uint8> byteArray;
byteArray.Append(ArrayReaderPtr->GetData(), ArrayReaderPtr->Num());
FString ipAdress;
if (byteArray.Num() > 10) {
for (int32 i = (byteArray.Num() - 4); i < byteArray.Num(); i++) {
uint32 ipTmp = byteArray.GetData()[i] << 0;
ipAdress += FString::FromInt(ipTmp) + ".";
}
}
ipAdress.RemoveFromEnd(".");
//UE_LOG(LogTemp, Display, TEXT("DNS Resolved IP:%s"), *recvMessage);
ip = ipAdress;
dnsCache.Add(domain, ip);
resolving = false;
}
bool UDNSClientSocketClient::isResloving() {
return resolving;
}
FString UDNSClientSocketClient::getIP() {
return ip;
}
@@ -0,0 +1,572 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#include "FileFunctionsSocketClient.h"
UFileFunctionsSocketClient* UFileFunctionsSocketClient::fileFunctionsSocketClient;
UFileFunctionsSocketClient::UFileFunctionsSocketClient(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {
fileFunctionsSocketClient = this;
}
UFileFunctionsSocketClient* UFileFunctionsSocketClient::getFileFunctionsSocketClientTarget() {
return fileFunctionsSocketClient;
}
FString UFileFunctionsSocketClient::getCleanDirectory(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
if (directoryType == EFileFunctionsSocketClientDirectoryType::E_ad) {
return FPaths::ConvertRelativePathToFull(filePath);
}
else {
FString ProjectDir = FPaths::ProjectDir();
return FPaths::ConvertRelativePathToFull(ProjectDir + filePath);
}
}
void UFileFunctionsSocketClient::writeBytesToFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, TArray<uint8> bytes, bool& success) {
success = FFileHelper::SaveArrayToFile(bytes, *getCleanDirectory(directoryType, filePath));
}
void UFileFunctionsSocketClient::addBytesToFileAndCloseIt(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, TArray<uint8> bytes, bool& success) {
FArchive* writer = IFileManager::Get().CreateFileWriter(*getCleanDirectory(directoryType, filePath), EFileWrite::FILEWRITE_Append);
if (!writer) {
success = false;
return;
}
writer->Seek(writer->TotalSize());
writer->Serialize(bytes.GetData(), bytes.Num());
writer->Close();
delete writer;
success = true;
}
//void UFileFunctionsSocketClient::splittFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, int32 parts, bool& success){
// if (parts <= 0)
// parts = 1;
// FArchive* reader = IFileManager::Get().CreateFileReader(*getCleanDirectory(directoryType, filePath));
// if (!reader) {
// success = false;
// return;
// }
//
// int64 splittAfterBytes = reader->TotalSize()/ ((int64)parts);
// TArray<uint8> bytes;
//
// for (int32 i = 0; i < parts; i++){
// bytes.AddUninitialized(splittAfterBytes);
// reader->Serialize(bytes.GetData(), splittAfterBytes);
// if (FFileHelper::SaveArrayToFile(bytes, *getCleanDirectory(directoryType, filePath)) == false) {
// success = false;
// return;
// }
// splittAfterBytes =
// reader->Seek();
// }
//
//}
TArray<uint8> UFileFunctionsSocketClient::readBytesFromFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, bool& success) {
TArray<uint8> result;
success = FFileHelper::LoadFileToArray(result, *getCleanDirectory(directoryType, filePath));
return result;
}
void UFileFunctionsSocketClient::readStringFromFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, bool& success, FString& data) {
data.Empty();
success = FFileHelper::LoadFileToString(data, *getCleanDirectory(directoryType, filePath));
}
void UFileFunctionsSocketClient::writeStringToFile(EFileFunctionsSocketClientDirectoryType directoryType, FString data, FString filePath, EFileFunctionsSocketClientEncodingOptions fileEncoding, bool& success) {
success = FFileHelper::SaveStringToFile(data, *getCleanDirectory(directoryType, filePath), (FFileHelper::EEncodingOptions)fileEncoding);
}
void UFileFunctionsSocketClient::getMD5FromFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, bool& success, FString& MD5) {
getMD5FromFileAbsolutePath(getCleanDirectory(directoryType, filePath), success, MD5);
}
void UFileFunctionsSocketClient::getMD5FromFileAbsolutePath(FString filePath, bool& success, FString& MD5) {
MD5.Empty();
FArchive* reader = IFileManager::Get().CreateFileReader(*filePath);
if (!reader) {
success = false;
return;
}
TArray<uint8> byteArrayTmp;
int64 totalSize = reader->TotalSize();
int64 loadedBytes = 0;
int64 leftUploadBytes = 1024;
if (totalSize < leftUploadBytes)
leftUploadBytes = totalSize;
uint8 Digest[16];
FMD5 Md5Gen;
while ((loadedBytes + leftUploadBytes) <= totalSize) {
byteArrayTmp.Reset(leftUploadBytes);
byteArrayTmp.AddUninitialized(leftUploadBytes);
reader->Serialize(byteArrayTmp.GetData(), byteArrayTmp.Num());
loadedBytes += leftUploadBytes;
reader->Seek(loadedBytes);
Md5Gen.Update(byteArrayTmp.GetData(), byteArrayTmp.Num());
}
leftUploadBytes = totalSize - loadedBytes;
if (leftUploadBytes > 0) {
byteArrayTmp.Reset(leftUploadBytes);
byteArrayTmp.AddUninitialized(leftUploadBytes);
reader->Serialize(byteArrayTmp.GetData(), byteArrayTmp.Num());
loadedBytes += leftUploadBytes;
Md5Gen.Update(byteArrayTmp.GetData(), byteArrayTmp.Num());
}
if (reader != nullptr) {
reader->Close();
delete reader;
}
if (totalSize != loadedBytes) {
success = false;
return;
}
Md5Gen.Final(Digest);
for (int32 i = 0; i < 16; i++) {
MD5 += FString::Printf(TEXT("%02x"), Digest[i]);
}
success = true;
}
void UFileFunctionsSocketClient::stringToBase64String(FString string, FString& base64String) {
base64String.Empty();
FTCHARToUTF8 Convert(*string);
TArray<uint8> bytes;
bytes.Append(((uint8*)((ANSICHAR*)Convert.Get())), Convert.Length());
base64String = FBase64::Encode(bytes);
}
void UFileFunctionsSocketClient::base64StringToString(FString& string, FString base64String) {
string.Empty();
TArray<uint8> bytes;
if (FBase64::Decode(*base64String, bytes)) {
bytes.Add(0x00);// null-terminator
char* Data = (char*)bytes.GetData();
string = FString(UTF8_TO_TCHAR(Data));
}
}
void UFileFunctionsSocketClient::bytesToBase64String(TArray<uint8> bytes, FString& base64String) {
base64String.Empty();
base64String = FBase64::Encode(bytes);
}
TArray<uint8> UFileFunctionsSocketClient::base64StringToBytes(FString base64String, bool& success) {
TArray<uint8> fileData;
if (base64String.Len() % 2 != 0 || base64String.Len() < 4) {
success = false;
return fileData;
}
success = FBase64::Decode(*base64String, fileData);
return fileData;
}
void UFileFunctionsSocketClient::fileToBase64String(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, bool& success, FString& base64String, FString& fileName) {
base64String.Empty();
fileName.Empty();
TArray<uint8> fileData;
if (!FFileHelper::LoadFileToArray(fileData, *getCleanDirectory(directoryType, filePath))) {
success = false;
return;
}
base64String = FBase64::Encode(fileData);
success = true;
}
bool UFileFunctionsSocketClient::fileExists(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
return FPaths::FileExists(*getCleanDirectory(directoryType, filePath));
}
bool UFileFunctionsSocketClient::fileExistsAbsolutePath(FString filePath) {
return FPaths::FileExists(*filePath);
}
bool UFileFunctionsSocketClient::directoryExists(EFileFunctionsSocketClientDirectoryType directoryType, FString path) {
return FPaths::DirectoryExists(*getCleanDirectory(directoryType, path));
}
int64 UFileFunctionsSocketClient::fileSize(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
return FPlatformFileManager::Get().GetPlatformFile().FileSize(*getCleanDirectory(directoryType, filePath));
}
int64 UFileFunctionsSocketClient::fileSizeAbsolutePath(FString filePath) {
return FPlatformFileManager::Get().GetPlatformFile().FileSize(*filePath);
}
bool UFileFunctionsSocketClient::deleteFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
return FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*getCleanDirectory(directoryType, filePath));
}
bool UFileFunctionsSocketClient::deleteFileAbsolutePath(FString filePath) {
return FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*filePath);
}
bool UFileFunctionsSocketClient::deleteDirectory(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
return FPlatformFileManager::Get().GetPlatformFile().DeleteDirectory(*getCleanDirectory(directoryType, filePath));
}
bool UFileFunctionsSocketClient::isReadOnly(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
return FPlatformFileManager::Get().GetPlatformFile().IsReadOnly(*getCleanDirectory(directoryType, filePath));
}
bool UFileFunctionsSocketClient::moveFile(EFileFunctionsSocketClientDirectoryType directoryTypeTo, FString filePathTo, EFileFunctionsSocketClientDirectoryType directoryTypeFrom, FString filePathFrom) {
return FPlatformFileManager::Get().GetPlatformFile().MoveFile(*getCleanDirectory(directoryTypeTo, filePathTo), *getCleanDirectory(directoryTypeFrom, filePathFrom));
}
bool UFileFunctionsSocketClient::setReadOnly(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, bool bNewReadOnlyValue) {
return FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*getCleanDirectory(directoryType, filePath), bNewReadOnlyValue);
}
FDateTime UFileFunctionsSocketClient::getTimeStamp(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
return FPlatformFileManager::Get().GetPlatformFile().GetTimeStamp(*getCleanDirectory(directoryType, filePath));
}
void UFileFunctionsSocketClient::setTimeStamp(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FDateTime DateTime) {
FPlatformFileManager::Get().GetPlatformFile().SetTimeStamp(*getCleanDirectory(directoryType, filePath), DateTime);
}
FDateTime UFileFunctionsSocketClient::getAccessTimeStamp(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
return FPlatformFileManager::Get().GetPlatformFile().GetAccessTimeStamp(*getCleanDirectory(directoryType, filePath));
}
FString UFileFunctionsSocketClient::getFilenameOnDisk(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
return FPlatformFileManager::Get().GetPlatformFile().GetFilenameOnDisk(*getCleanDirectory(directoryType, filePath));
}
bool UFileFunctionsSocketClient::createDirectory(EFileFunctionsSocketClientDirectoryType directoryType, FString path) {
return FPlatformFileManager::Get().GetPlatformFile().CreateDirectory(*getCleanDirectory(directoryType, path));
}
void UFileFunctionsSocketClient::getAllFilesFromDirectory(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, int32& count, TArray<FString>& files, TArray<FString>& filePaths, FString fileType) {
if (filePath.Len() < 1) {
return;
}
files.Empty();
filePaths.Empty();
FString dir = getCleanDirectory(directoryType, filePath);
FPaths::NormalizeDirectoryName(filePath);
if (!FPaths::DirectoryExists(dir)) {
return;
}
IFileManager& FileManager = IFileManager::Get();
dir += "/" + fileType;
FileManager.FindFiles(files, *dir, true, false);
filePath += "/";
for (int32 i = 0; i < files.Num(); i++) {
filePaths.Add((filePath + files[i]));
//UE_LOG(LogTemp, Display, TEXT("->%s"), *files[i]);
}
count = files.Num();
}
FFileFunctionsSocketClientOpenFile UFileFunctionsSocketClient::openFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath){
FArchive* writer = IFileManager::Get().CreateFileWriter(*getCleanDirectory(directoryType, filePath), EFileWrite::FILEWRITE_Append);
FFileFunctionsSocketClientOpenFile file;
file.writer = writer;
return file;
}
int64 UFileFunctionsSocketClient::addBytesToFile(FFileFunctionsSocketClientOpenFile openFile, TArray<uint8> bytes){
//UE_LOG(LogTemp, Warning, TEXT("xxxxx WRITE: %i"), bytes.Num());
if (openFile.writer != nullptr) {
openFile.writer->Seek(openFile.writer->TotalSize());
openFile.writer->Serialize(bytes.GetData(), bytes.Num());
//openFile.writer->Flush();
return openFile.writer->TotalSize();
}
return 0;
}
void UFileFunctionsSocketClient::closeFile(FFileFunctionsSocketClientOpenFile openFile){
if (openFile.writer != nullptr) {
openFile.writer->Close();
openFile.writer = nullptr;
}
}
bool UFileFunctionsSocketClient::encryptFileWithAES(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FString newFileName, FString key, bool writeEncryptedFileSizeToFile){
if (filePath.IsEmpty() || newFileName.IsEmpty() || key.Len() != 32) {
UE_LOG(LogTemp, Error, TEXT("encryptFileWithAES: FilePath or newFileName empty or wrong key length. The key must consist of 32 ANSI compatible characters."));
return false;
}
FString dir = getCleanDirectory(directoryType, filePath);
FString fileName = FPaths::GetCleanFilename(dir);
int64 fileSize = FPlatformFileManager::Get().GetPlatformFile().FileSize(*dir);
TArray64<uint8> data;
if (!FFileHelper::LoadFileToArray(data, *dir)) {
UE_LOG(LogTemp, Error, TEXT("encryptFileWithAES: File could not be loaded: %s"),*dir);
return false;
}
const int64 encryptedFileSize = Align(data.Num(), FAES::AESBlockSize);
if (data.Num() < encryptedFileSize) {
data.AddUninitialized(encryptedFileSize - data.Num());
}
FAES::EncryptData(data.GetData(), encryptedFileSize, TCHAR_TO_ANSI(*key));
dir = dir.Replace(*fileName, *newFileName);
if (FPaths::FileExists(*dir)) {
FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*dir);
}
FArchive* writer = IFileManager::Get().CreateFileWriter(*dir, EFileWrite::FILEWRITE_Append);
if (!writer) {
UE_LOG(LogTemp, Error, TEXT("encryptFileWithAES: File could not be saved: %s"), *dir);
data.Empty();
return false;
}
if (writeEncryptedFileSizeToFile) {
TArray<uint8> byteArray;
union {
int64 tmpVal;
uint8 tmpArray[8];
} u;
u.tmpVal = fileSize;
byteArray.AddZeroed(8);
FMemory::Memcpy(byteArray.GetData(), u.tmpArray, 8);
writer->Serialize(byteArray.GetData(), byteArray.Num());
byteArray.Empty();
}
writer->Serialize(data.GetData(), data.Num());
writer->Close();
delete writer;
data.Empty();
return true;
}
bool UFileFunctionsSocketClient::decryptFileWithAES(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FString newFileName, FString key,
bool hasEncryptedFileSizeInFile, int64 originalFileLength){
if (filePath.IsEmpty() || newFileName.IsEmpty() || key.Len() != 32) {
UE_LOG(LogTemp, Error, TEXT("decryptFileWithAES: FilePath or newFileName empty or wrong key length. The key must consist of 32 ANSI compatible characters."));
return false;
}
FString dir = getCleanDirectory(directoryType, filePath);
FString fileName = FPaths::GetCleanFilename(dir);
int64 fileSize = FPlatformFileManager::Get().GetPlatformFile().FileSize(*dir);
FArchive* reader = IFileManager::Get().CreateFileReader(*dir);
if (!reader) {
UE_LOG(LogTemp, Error, TEXT("decryptFileWithAES: File could not be loaded: %s"), *dir);
return false;
}
if (hasEncryptedFileSizeInFile) {
TArray<uint8> bytes;
bytes.AddZeroed(8);
reader->Serialize(bytes.GetData(), bytes.Num());
FMemory::Memcpy(&originalFileLength, bytes.GetData(), 8);
bytes.Empty();
fileSize = fileSize-8;
}
if (fileSize < originalFileLength) {
originalFileLength = fileSize;
UE_LOG(LogTemp, Warning, TEXT("decryptFileWithAES: The encrypted file size is smaller than the original file size. File may be corrupted: %s"), *dir);
return false;
}
TArray64<uint8> data;
data.AddUninitialized(fileSize);
reader->Serialize(data.GetData(), data.Num());
reader->Close();
delete reader;
const int64 encryptedFileSize = Align(data.Num(), FAES::AESBlockSize);
if (data.Num() < encryptedFileSize) {
UE_LOG(LogTemp, Error, TEXT("decryptFileWithAES: Wrong data length. File could not be decrypted: %s"),*dir);
return false;
}
FAES::DecryptData(data.GetData(), encryptedFileSize, TCHAR_TO_ANSI(*key));
dir = dir.Replace(*fileName, *newFileName);
if (FPaths::FileExists(*dir)) {
FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*dir);
}
FArchive* writer = IFileManager::Get().CreateFileWriter(*dir, EFileWrite::FILEWRITE_Append);
if (!writer) {
UE_LOG(LogTemp, Error, TEXT("encryptFileWithAES: File could not be saved: %s"), *dir);
data.Empty();
return false;
}
writer->Serialize(data.GetData(), originalFileLength);
writer->Close();
delete writer;
data.Empty();
return true;
}
FString UFileFunctionsSocketClient::encryptMessageWithAES(FString message, FString key) {
if (message.IsEmpty() || key.Len() != 32) {
UE_LOG(LogTemp, Error, TEXT("encryptMessageWithAES:Message empty or wrong key length. The key must consist of 32 ANSI compatible characters."));
return FString();
}
TArray<uint8> data = FStringToByteArray(message);
data.Add(0x00);// null-terminator
const int64 encryptedFileSize = Align(data.Num(), FAES::AESBlockSize);
if (data.Num() < encryptedFileSize){
data.AddUninitialized(encryptedFileSize-data.Num());
}
FAES::EncryptData(data.GetData(), encryptedFileSize, TCHAR_TO_ANSI(*key));
FString encryptedBase64String = FString();
bytesToBase64String(data, encryptedBase64String);
data.Empty();
return encryptedBase64String;
}
FString UFileFunctionsSocketClient::decryptMessageWithAES(FString message, FString key) {
if (message.IsEmpty() || key.Len() != 32) {
UE_LOG(LogTemp, Error, TEXT("decryptMessageFromAES: Message empty or wrong key length. The key must consist of 32 ANSI compatible characters."));
return FString();
}
bool success = false;
TArray<uint8> data = base64StringToBytes(message, success);
const int64 encryptedFileSize = Align(data.Num(), FAES::AESBlockSize);
if (data.Num() < encryptedFileSize) {
UE_LOG(LogTemp, Error, TEXT("decryptMessageFromAES: Wrong string length. Message could not be decrypted."));
return FString();
}
FAES::DecryptData(data.GetData(), encryptedFileSize, TCHAR_TO_ANSI(*key));
FString s = FString(UTF8_TO_TCHAR((char*)data.GetData()));
data.Empty();
return s;
}
TArray<uint8> UFileFunctionsSocketClient::FStringToByteArray(FString s) {
FTCHARToUTF8 Convert(*s);
TArray<uint8> data;
data.Append((uint8*)Convert.Get(), Convert.Length());
return data;
}
FString UFileFunctionsSocketClient::int64ToString(int64 Num) {
FString str = FString();
const TCHAR* DigitToChar = TEXT("9876543210123456789");
constexpr int64 ZeroDigitIndex = 9;
bool bIsNumberNegative = Num < 0;
const int64 TempBufferSize = 32; // 32 is big enough
TCHAR TempNum[TempBufferSize];
int64 TempAt = TempBufferSize; // fill the temp string from the top down.
// Convert to string assuming base ten.
do
{
TempNum[--TempAt] = DigitToChar[ZeroDigitIndex + (Num % 10)];
Num /= 10;
} while (Num);
if (bIsNumberNegative)
{
TempNum[--TempAt] = TEXT('-');
}
const TCHAR* CharPtr = TempNum + TempAt;
const int64 NumChars = TempBufferSize - TempAt;
str.Append(CharPtr, NumChars);
return str;
}
void UFileFunctionsSocketClient::readBytesFromFileInPartsAsync(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, int32 bufferSize, float delayBetweenReadsInSeconds) {
UFileFunctionsSocketClient::getFileFunctionsSocketClientTarget()->readBytesFromFileInPartsAsyncInternal(directoryType, filePath, bufferSize, delayBetweenReadsInSeconds);
}
void UFileFunctionsSocketClient::readBytesFromFileInPartsAsyncInternal(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, int32 bufferSize, float delayBetweenReadsInSeconds) {
FString dir = UFileFunctionsSocketClient::getCleanDirectory(directoryType, filePath);
if (readFileInPartsThreads.Find(*dir) != nullptr) {
UE_LOG(LogTemp, Warning, TEXT("ReadBytesFromFileInPartsAsync: %s is being read already. Operation canceled."), *dir);
return;
}
FReadFileInPartsSocketClientThread* readThread = new FReadFileInPartsSocketClientThread(dir, bufferSize, delayBetweenReadsInSeconds);
readFileInPartsThreads.Add(dir, readThread);
}
void UFileFunctionsSocketClient::cancelReadBytesFromFileInParts(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
UFileFunctionsSocketClient::getFileFunctionsSocketClientTarget()->cancelReadBytesFromFileInPartsInternal(directoryType, filePath);
}
void UFileFunctionsSocketClient::cancelReadBytesFromFileInPartsInternal(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath) {
FString dir = UFileFunctionsSocketClient::getCleanDirectory(directoryType, filePath);
if (readFileInPartsThreads.Find(*dir) != nullptr) {
(*readFileInPartsThreads.Find(*dir))->stopThread();
}
}
void UFileFunctionsSocketClient::cleanReadBytesFromFileInParts(FString cleanDir) {
if (readFileInPartsThreads.Find(*cleanDir) != nullptr) {
readFileInPartsThreads.Remove(*cleanDir);
}
}
//void UFileFunctionsSocketClient::changeDelayInBytesFromFileInParts(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, float delayBetweenReadsInSeconds) {
// FString dir = UFileFunctionsSocketClient::getCleanDirectory(directoryType, filePath);
//
// if (readFileInPartsThreads.Find(*dir) != nullptr) {
// (*readFileInPartsThreads.Find(*dir))->setDelayBetweenReadsInSeconds(delayBetweenReadsInSeconds);
// }
//}
@@ -0,0 +1,23 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#include "SocketClient.h"
#define LOCTEXT_NAMESPACE "FSocketClientModule"
bool FSocketClientModule::isShuttingDown;
void FSocketClientModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FSocketClientModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
isShuttingDown = true;
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FSocketClientModule, SocketClient)
@@ -0,0 +1,82 @@
// Copyright 2022 David Romanski(Socke). All Rights Reserved.
#include "SocketClientAsyncNodes.h"
/*--- TCP -------------------------------------------------------------------------------------------------------------*/
UTCPConnectAsyncNode* UTCPConnectAsyncNode::socketClientTCPConnectionAsyncNode(FString domainOrI, ESocketClientIPType ipType, int32 port,
EReceiveFilterClient receiveFilters, ESocketClientTCPSeparator messageWrapping, FString optionalCustomConnectionID, bool disableNaglesAlgorithm) {
UTCPConnectAsyncNode* instance = NewObject<UTCPConnectAsyncNode>();
instance->domainOrIP = domainOrI;
instance->ipType = ipType;
instance->port = port;
instance->receiveFilters = receiveFilters;
instance->messageWrapping = messageWrapping;
instance->optionalCustomConnectionID = optionalCustomConnectionID;
instance->disableNaglesAlgorithm = disableNaglesAlgorithm;
instance->AddToRoot();
//The node is only visible in Blueprints if the function is declared in a UBlueprintAsyncActionBase class.
//Therefore an instance of itself is created here.
return instance;
}
void UTCPConnectAsyncNode::Activate() {
USocketClientBPLibrary::getSocketClientTarget()->connectSocketClientTCPNonStatic(domainOrIP, ipType, port, receiveFilters,
messageWrapping,optionalCustomConnectionID, connectionID, this, disableNaglesAlgorithm);
}
void UTCPConnectAsyncNode::triggerConnectionEvent(bool success, FString clientConnectionID, FString serverMessage){
if (success) {
OnConnect.Broadcast(serverMessage, clientConnectionID, "", TArray<uint8>());
}
else {
OnDisconnect.Broadcast(serverMessage, clientConnectionID, "", TArray<uint8>());
}
}
void UTCPConnectAsyncNode::triggerMessageEvent(TArray<uint8> byteDataArray, FString clientConnectionID, FString serverMessage){
OnServerMessage.Broadcast("",clientConnectionID,serverMessage,byteDataArray);
}
/*--- UDP -------------------------------------------------------------------------------------------------------------*/
UUDPInitAsyncNode* UUDPInitAsyncNode::socketClientInitUDPReceiverAsyncNode(FString domainOrIP,ESocketClientIPType ipType, int32 port,
EReceiveFilterClient receiveFilter, int32 maxPacketSize) {
UUDPInitAsyncNode* instance = NewObject<UUDPInitAsyncNode>();
instance->domainOrIP = domainOrIP;
instance->ipType = ipType;
instance->port = port;
instance->receiveFilter = receiveFilter;
instance->maxPacketSize = maxPacketSize;
instance->AddToRoot();
//The node is only visible in Blueprints if the function is declared in a UBlueprintAsyncActionBase class.
//Therefore an instance of itself is created here.
return instance;
}
void UUDPInitAsyncNode::Activate() {
USocketClientBPLibrary::getSocketClientTarget()->socketClientInitUDPReceiverNonStatic(connectionID, this,domainOrIP, ipType, port,
receiveFilter, maxPacketSize);
}
void UUDPInitAsyncNode::triggerInitEvent(bool success, FString clientConnectionID, FString serverMessage) {
if (success) {
OnSuccess.Broadcast(serverMessage, clientConnectionID,"",0, "", TArray<uint8>());
}
else {
OnFail.Broadcast(serverMessage, clientConnectionID, "",0,"", TArray<uint8>());
}
}
void UUDPInitAsyncNode::triggerMessageEvent(TArray<uint8> byteDataArray, FString clientConnectionID, FString serverMessage, FString peerIP, int32 peerPort) {
OnServerMessage.Broadcast("", clientConnectionID,peerIP, peerPort, serverMessage, byteDataArray);
}
@@ -0,0 +1,734 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#include "SocketClientBPLibrary.h"
#include "SocketClient.h"
USocketClientBPLibrary* USocketClientBPLibrary::socketClientBPLibrary;
USocketClientBPLibrary::USocketClientBPLibrary(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer) {
socketClientBPLibrary = this;
if (socketClientCleanerThread == nullptr)
socketClientCleanerThread = new FSocketClientCleanerThread();
}
/*Delegate functions*/
void USocketClientBPLibrary::socketClientTCPConnectionEventDelegate(const bool success, const FString message, const FString clientConnectionIDP) {}
void USocketClientBPLibrary::receiveTCPMessageEventDelegate(const FString message, const TArray<uint8>& byteArray, const FString clientConnectionIDP) {}
void USocketClientBPLibrary::socketClientUDPConnectionEventDelegate(const bool success, const FString message, const FString clientConnectionID) {}
void USocketClientBPLibrary::receiveUDPMessageEventDelegate(const FString message, const TArray<uint8>& byteArray, const FString IP, const int32 port,const FString clientConnectionID) {}
void USocketClientBPLibrary::transferFileOverTCPProgressEventDelegate(const FString clientConnectionID, const FString filePath, const float percent, const float mbit, const int64 bytesSend, const int64 fileSize){}
void USocketClientBPLibrary::fileTransferOverTCPInfoEventDelegate(const FString message, const FString clientConnectionID, const FString filePath, const bool success){}
void USocketClientBPLibrary::readBytesFromFileInPartsEventDelegate(const int64 fileSize, const int64 position, const bool end, const TArray<uint8>& byteArray){}
USocketClientBPLibrary::~USocketClientBPLibrary() {
}
USocketClientBPLibrary* USocketClientBPLibrary::getSocketClientTarget() {
return socketClientBPLibrary;
}
FString USocketClientBPLibrary::getLocalIP() {
bool canBind = false;
TSharedRef<FInternetAddr> localIp = USocketClientBPLibrary::getSocketClientTarget()->getSocketSubSystem()->GetLocalHostAddr(*GLog, canBind);
if (localIp->IsValid()) {
FString localIP = localIp->ToString(false);
if (localIP.Equals("127.0.0.1")) {
UE_LOG(LogTemp, Error, TEXT("Could not detect the local IP."));
return "0.0.0.0";
}
return localIp->ToString(false);
}
else {
UE_LOG(LogTemp, Error, TEXT("Could not detect the local IP."));
}
return "0.0.0.0";
}
void USocketClientBPLibrary::connectSocketClientTCP(FString domainOrIP, ESocketClientIPType ipType, int32 port, EReceiveFilterClient receiveFilters,
ESocketClientTCPSeparator messageSeparator, FString optionalCustomConnectionID, FString& connectionID, bool disableNaglesAlgorithm){
USocketClientBPLibrary::getSocketClientTarget()->connectSocketClientTCPNonStatic(domainOrIP, ipType, port, receiveFilters,
messageSeparator,optionalCustomConnectionID, connectionID, nullptr,disableNaglesAlgorithm);
}
void USocketClientBPLibrary::connectSocketClientTCPNonStatic(FString domain, ESocketClientIPType ipType, int32 port, EReceiveFilterClient receiveFilter,
ESocketClientTCPSeparator messageSeparator, FString optionalCustomConnectionID, FString& connectionID, UTCPConnectAsyncNode* tcpConnectAsyncNode, bool noPacketDelay) {
USocketClientTCPClient* tcpClient = NewObject<USocketClientTCPClient>(USocketClientTCPClient::StaticClass());
if (optionalCustomConnectionID.IsEmpty()) {
connectionID = FGuid::NewGuid().ToString();
}
else {
connectionID = optionalCustomConnectionID;
if (tcpClients.Find(connectionID) != nullptr) {
FString serverMessage = "An existing connection with this ID was found. Connection establishment was aborted";
UE_LOG(LogTemp, Warning, TEXT("An existing connection with this ID was found. Connection establishment was aborted. %s"), *connectionID);
onsocketClientTCPConnectionEventDelegate.Broadcast(false, serverMessage, connectionID);
tcpClient->onsocketClientTCPConnectionEventDelegate.Broadcast(false, serverMessage, connectionID);
if (tcpConnectAsyncNode != nullptr) {
tcpConnectAsyncNode->triggerConnectionEvent(false, connectionID, serverMessage);
}
return;
}
}
tcpClients.Add(connectionID, tcpClient);
tcpClient->connect(this, domain, ipType, port, receiveFilter, messageSeparator, connectionID,tcpConnectAsyncNode, noPacketDelay, false);
}
void USocketClientBPLibrary::socketClientSendTCP(FString connectionID, FString message, TArray<uint8> byteArray, bool addLineBreak){
USocketClientBPLibrary::getSocketClientTarget()->socketClientSendTCPNonStatic(connectionID, message, byteArray, addLineBreak);
}
void USocketClientBPLibrary::socketClientSendTCPNonStatic(FString connectionID,FString message, TArray<uint8> byteArray, bool addLineBreak) {
if (connectionID.IsEmpty() || tcpClients.Find(connectionID) == nullptr) {
//don't send to many error messages. one second = 10000000 ticks
if (((FDateTime::Now().GetTicks()) - lastErrorMessageTime) >= 10000000) {
UE_LOG(LogTemp, Error, TEXT("Connection not found (socketClientSendTCPMessage). %s"), *connectionID);
lastErrorMessageTime = FDateTime::Now().GetTicks();
}
return;
}
if (message.Len() > 0) {
if (addLineBreak) {
message.Append("\r\n");
}
}
USocketClientTCPClient* tcpClient = *tcpClients.Find(connectionID);
tcpClient->sendMessage(message, byteArray);
}
void USocketClientBPLibrary::socketClientSendFileOverTCP(FString& connectionID, FString domainOrIP, ESocketClientIPType ipType, int32 port,
EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FString token, FString Aes256bitKey){
USocketClientBPLibrary::getSocketClientTarget()->socketClientSendFileOverTCPNonStatic(connectionID, domainOrIP, ipType,
port, directoryType, filePath, token, Aes256bitKey);
}
void USocketClientBPLibrary::socketClientSendFileOverTCPNonStatic(FString& connectionID, FString domainOrIP, ESocketClientIPType ipType,
int32 port, EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FString token, FString Aes256bitKey){
USocketClientTCPClient* tcpClient = NewObject<USocketClientTCPClient>(USocketClientTCPClient::StaticClass());
connectionID = FGuid::NewGuid().ToString();
tcpClients.Add(connectionID, tcpClient);
tcpClient->sendFile(this, connectionID, domainOrIP, ipType, port, directoryType, filePath, token, Aes256bitKey);
}
void USocketClientBPLibrary::socketClientRequestFileOverTCP(FString& connectionID, FString domainOrIP, ESocketClientIPType ipType, int32 port,
EFileFunctionsSocketClientDirectoryType directoryType, FString downloadDirectory, bool resume, FString token, FString Aes256bitKey){
USocketClientBPLibrary::getSocketClientTarget()->socketClientRequestFileOverTCPNonStatic(connectionID, domainOrIP, ipType, port,
directoryType, downloadDirectory, resume, token, Aes256bitKey);
}
void USocketClientBPLibrary::socketClientRequestFileOverTCPNonStatic(FString& connectionID, FString domainOrIP, ESocketClientIPType ipType, int32 port,
EFileFunctionsSocketClientDirectoryType directoryType, FString downloadDirectory, bool resume, FString token, FString Aes256bitKey) {
USocketClientTCPClient* tcpClient = NewObject<USocketClientTCPClient>(USocketClientTCPClient::StaticClass());
connectionID = FGuid::NewGuid().ToString();
tcpClients.Add(connectionID, tcpClient);
tcpClient->requestFile(this, connectionID, domainOrIP, ipType, port, directoryType, downloadDirectory, resume,token, Aes256bitKey);
}
void USocketClientBPLibrary::closeSocketClientConnectionTCP(FString connectionID){
USocketClientBPLibrary::getSocketClientTarget()->closeSocketClientConnectionTCPNonStatic(connectionID);
}
void USocketClientBPLibrary::closeSocketClientConnectionTCPNonStatic(FString connectionID){
if (connectionID.IsEmpty()) {
return;
}
if (tcpClients.Find(connectionID) == nullptr) {
UE_LOG(LogTemp, Error, TEXT("Connection not found (closeSocketClientTCPConnection). %s"), *connectionID);
return;
}
USocketClientTCPClient* tcpClient = *tcpClients.Find(connectionID);
if (tcpClient != nullptr) {
if (tcpClient->isRun()) {
tcpClient->closeConnection();
}
}
tcpClients.Remove(connectionID);
tcpClient = nullptr;
}
void USocketClientBPLibrary::closeAllSocketClientConnectionsTCP(){
USocketClientBPLibrary::getSocketClientTarget()->closeAllSocketClientConnectionsTCPNonStatic();
}
void USocketClientBPLibrary::closeAllSocketClientConnectionsTCPNonStatic(){
TArray<FString> tmpArray;
tcpClients.GetKeys(tmpArray);
for (int32 i = 0; i < tmpArray.Num(); i++){
closeSocketClientConnectionTCPNonStatic(tmpArray[i]);
}
}
void USocketClientBPLibrary::getTCPConnectionByConnectionID(FString connectionID, bool& found, USocketClientTCPClient*& connection){
USocketClientBPLibrary::getSocketClientTarget()->getTCPConnectionByConnectionIDNonStatic(connectionID, found, connection);
}
void USocketClientBPLibrary::getTCPConnectionByConnectionIDNonStatic(FString connectionID, bool& found, USocketClientTCPClient* &connection){
if (connectionID.IsEmpty() || tcpClients.Find(connectionID) == nullptr) {
found = false;
connection = nullptr;
return;
}
found = true;
connection = *tcpClients.Find(connectionID);
}
bool USocketClientBPLibrary::isTCPConnected(FString connectionID){
return USocketClientBPLibrary::getSocketClientTarget()->isTCPConnectedNonStatic(connectionID);
}
bool USocketClientBPLibrary::isTCPConnectedNonStatic(FString connectionID){
if (connectionID.IsEmpty() || tcpClients.Find(connectionID) == nullptr) {
return false;
}
USocketClientTCPClient* tcpClient = *tcpClients.Find(connectionID);
if (tcpClient->isRun() && tcpClient->isConnected()) {
return true;
}
return false;
}
void USocketClientBPLibrary::socketClientInitUDPReceiver(FString& connectionID, FString domainOrIP, ESocketClientIPType ipType, int32 port, EReceiveFilterClient receiveFilter, int32 maxPacketSize){
USocketClientBPLibrary::getSocketClientTarget()->socketClientInitUDPReceiverNonStatic(connectionID,nullptr, domainOrIP, ipType, port, receiveFilter, maxPacketSize);
}
void USocketClientBPLibrary::socketClientInitUDPReceiverNonStatic(FString& connectionID, UUDPInitAsyncNode* udpInitAsyncNode, FString domain,
ESocketClientIPType ipType, int32 port, EReceiveFilterClient receiveFilter, int32 maxPacketSize) {
FString key = domain + FString::FromInt(port);
if (udpClients.Find(key) != nullptr) {
USocketClientUDP* udpClient = *udpClients.Find(key);
connectionID = udpClient->getConnectionID();
FString info = "Connection already present:" + domain + ":" + FString::FromInt(port);
onsocketClientUDPConnectionEventDelegate.Broadcast(true,info , connectionID);
if (udpInitAsyncNode != nullptr) {
udpInitAsyncNode->triggerInitEvent(true, connectionID, info);
}
return;
}
USocketClientUDP* udpClient = NewObject<USocketClientUDP>(USocketClientUDP::StaticClass());
connectionID = FGuid::NewGuid().ToString();
udpClients.Add(connectionID, udpClient);
udpClients.Add(key, udpClient);
udpClient->init(this, udpInitAsyncNode, domain, ipType, port, receiveFilter, connectionID, maxPacketSize);
}
void USocketClientBPLibrary::socketClientSendUDP(FString domainOrIP, ESocketClientIPType ipType, int32 port, FString message, TArray<uint8> byteArray, bool addLineBreak, FString connectionID){
USocketClientBPLibrary::getSocketClientTarget()->socketClientSendUDPNonStatic(domainOrIP, ipType, port, message, byteArray, addLineBreak, connectionID);
}
void USocketClientBPLibrary::socketClientSendUDPNonStatic(FString domain, ESocketClientIPType ipType, int32 port, FString message, TArray<uint8> byteArray, bool addLineBreak, FString clientConnectionIDP) {
if (clientConnectionIDP.IsEmpty() || udpClients.Find(clientConnectionIDP) == nullptr) {
//don't send to many error messages. one second = 10000000 ticks
if (((FDateTime::Now().GetTicks()) - lastErrorMessageTime) >= 10000000) {
UE_LOG(LogTemp, Error, TEXT("Connection not found (socketClientSendUDPMessage). %s"), *clientConnectionIDP);
lastErrorMessageTime = FDateTime::Now().GetTicks();
}
return;
}
if (message.Len() > 0 && addLineBreak) {
message.Append("\r\n");
}
USocketClientUDP* udpClient = *udpClients.Find(clientConnectionIDP);
udpClient->sendUDPMessage(domain, ipType, port, message, byteArray);
}
void USocketClientBPLibrary::closeSocketClientConnectionUDP(FString connectionID){
USocketClientBPLibrary::getSocketClientTarget()->closeSocketClientConnectionUDPNonStatic(connectionID);
}
void USocketClientBPLibrary::closeSocketClientConnectionUDPNonStatic(FString connectionID) {
if (connectionID.IsEmpty()) {
return;
}
if (udpClients.Find(connectionID) == nullptr) {
UE_LOG(LogTemp, Warning, TEXT("Connection not found (closeSocketClientUDPConnection). %s"), *connectionID);
return;
}
USocketClientUDP* udpClient = *udpClients.Find(connectionID);
udpClient->closeUDPConnection();
udpClients.Remove(connectionID);
FString key = udpClient->getDomainOrIP() + FString::FromInt(udpClient->getPort());
udpClients.Remove(key);
udpClient = nullptr;
}
void USocketClientBPLibrary::getUDPInitializationByConnectionID(FString connectionID, bool& found, USocketClientUDP*& connection){
USocketClientBPLibrary::getSocketClientTarget()->getUDPInitializationByConnectionIDNonStatic(connectionID, found, connection);
}
void USocketClientBPLibrary::getUDPInitializationByConnectionIDNonStatic(FString connectionID, bool& found, USocketClientUDP*& connection){
if (connectionID.IsEmpty() || udpClients.Find(connectionID) == nullptr) {
found = false;
connection = nullptr;
return;
}
found = true;
connection = *udpClients.Find(connectionID);
}
bool USocketClientBPLibrary::isUDPInitialized(FString connectionID){
return USocketClientBPLibrary::getSocketClientTarget()->isUDPInitializedNonStatic(connectionID);
}
bool USocketClientBPLibrary::isUDPInitializedNonStatic(FString connectionID){
if (connectionID.IsEmpty() || udpClients.Find(connectionID) == nullptr) {
return false;
}
return (*udpClients.Find(connectionID))->isRun();
}
void USocketClientBPLibrary::changeSocketPlatform(ESocketPlatformClient platform) {
USocketClientBPLibrary::getSocketClientTarget()->systemSocketPlatform = platform;
}
FString USocketClientBPLibrary::resolveDomain(FString serverDomainP, ESocketClientIPType ipType) {
FString* cachedDomainPointer = domainCache.Find(serverDomainP);
if (cachedDomainPointer != nullptr) {
return *cachedDomainPointer;
}
//is IPv4?
if (ipType == ESocketClientIPType::E_ipv4) {
TArray<FString> ipNumbers;
int32 lineCount = serverDomainP.ParseIntoArray(ipNumbers, TEXT("."), true);
if (lineCount == 4 && serverDomainP.Len() <= 15 && serverDomainP.Len() >= 7) {
domainCache.Add(serverDomainP, serverDomainP);
return serverDomainP;
}
}
//is IPv6? Just a simple check
if (ipType == ESocketClientIPType::E_ipv6) {
TArray<FString> ipNumbers;
int32 lineCount = serverDomainP.ParseIntoArray(ipNumbers, TEXT(":"), true);
if (lineCount >= 2) {
domainCache.Add(serverDomainP, serverDomainP);
return serverDomainP;
}
}
//resolve Domain
ISocketSubsystem* sSS = USocketClientBPLibrary::getSocketSubSystem();
FResolveInfo* ResolveInfo = sSS->GetHostByName(TCHAR_TO_ANSI(*serverDomainP));
while (!ResolveInfo->IsComplete());
int32 errorCode = ResolveInfo->GetErrorCode();
if (errorCode == 0) {
const FInternetAddr* Addr = &ResolveInfo->GetResolvedAddress();
uint32 OutIP = 0;
FString adr = Addr->ToString(false);
domainCache.Add(serverDomainP, adr);
return adr;
}
else {
if (dnsClient == nullptr)
dnsClient = NewObject<UDNSClientSocketClient>(UDNSClientSocketClient::StaticClass());
dnsClient->resolveDomain(sSS, serverDomainP);
int32 timeout = 1000;
while (dnsClient->isResloving() && timeout > 0) {
timeout -= 10;
FPlatformProcess::Sleep(0.01);
}
FString adr = dnsClient->getIP();
domainCache.Add(serverDomainP, adr);
return adr;
}
return serverDomainP;
}
void USocketClientBPLibrary::cleanConnection(FSocketClientPluginSession& session){
socketClientCleanerThread->addSession(session);
}
void USocketClientBPLibrary::changeCleanerThreadSettingsOnClient(bool showLogs, int32 minLiveTimeInSeconds){
if (USocketClientBPLibrary::getSocketClientTarget()->socketClientCleanerThread != nullptr) {
USocketClientBPLibrary::getSocketClientTarget()->socketClientCleanerThread->changeSettings(showLogs, minLiveTimeInSeconds);
}
}
void USocketClientBPLibrary::getSystemType(ESocketClientSystem& system) {
#if PLATFORM_ANDROID
system = ESocketClientSystem::Android;
return;
#endif
#if PLATFORM_IOS
system = ESocketClientSystem::IOS;
return;
#endif
#if PLATFORM_WINDOWS
system = ESocketClientSystem::Windows;
return;
#endif
#if PLATFORM_LINUX
system = ESocketClientSystem::Linux;
return;
#endif
#if PLATFORM_MAC
system = ESocketClientSystem::Mac;
return;
#endif
}
TArray<uint8> USocketClientBPLibrary::parseHexToBytes(FString hex) {
TArray<uint8> bytes;
if (hex.Contains(" ")) {
hex = hex.Replace(TEXT(" "), TEXT(""));
}
if (hex.Len() % 2 != 0) {
UE_LOG(LogTemp, Error, TEXT("This is not a valid hex string: %s"), *hex);
return bytes;
}
TArray<TCHAR> charArray = hex.GetCharArray();
for (int32 i = 0; i < (charArray.Num() - 1); i++) {
if (CheckTCharIsHex(charArray[i]) == false) {
UE_LOG(LogTemp, Error, TEXT("This is not a valid hex string: %s"), *hex);
return bytes;
}
}
bytes.AddZeroed(hex.Len() / 2);
HexToBytes(hex, bytes.GetData());
return bytes;
}
FString USocketClientBPLibrary::parseHexToString(FString hex) {
TArray<uint8> bytes = parseHexToBytes(hex);
bytes.Add(0x00);// null-terminator
char* Data = (char*)bytes.GetData();
return FString(UTF8_TO_TCHAR(Data));
}
FString USocketClientBPLibrary::parseBytesToHex(TArray<uint8> bytes) {
FString hex;
hex = BytesToHex(bytes.GetData(), bytes.Num());
return hex;
}
TArray<uint8> USocketClientBPLibrary::parseHexToBytesPure(FString hex) {
return parseHexToBytes(hex);
}
FString USocketClientBPLibrary::parseHexToStringPure(FString hex) {
return parseHexToString(hex);
}
FString USocketClientBPLibrary::parseBytesToHexPure(TArray<uint8> bytes) {
return parseBytesToHex(bytes);
}
void USocketClientBPLibrary::parseBytesToFloat(TArray<uint8> bytes, float& value) {
if (bytes.Num() != 4) {
UE_LOG(LogTemp, Error, TEXT("ParseBytesToFloat: Cannot convert bytes to float. Array must contain 4 bytes but has %i bytes."), bytes.Num());
return;
}
FMemory::Memcpy(&value, bytes.GetData(), 4);
}
void USocketClientBPLibrary::parseBytesToInteger(TArray<uint8> bytes, int32& value) {
if (bytes.Num() != 4) {
UE_LOG(LogTemp, Error, TEXT("ParseBytesToInteger: Cannot convert bytes to integer. Array must contain 4 bytes but has %i bytes."), bytes.Num());
return;
}
FMemory::Memcpy(&value, bytes.GetData(), 4);
}
void USocketClientBPLibrary::parseBytesToInteger64(TArray<uint8> bytes, int64& value) {
if (bytes.Num() != 8) {
UE_LOG(LogTemp, Error, TEXT("ParseBytesToInteger64: Cannot convert bytes to integer. Array must contain 8 bytes but has %i bytes."), bytes.Num());
return;
}
FMemory::Memcpy(&value, bytes.GetData(), 8);
}
void USocketClientBPLibrary::parseBytesToFloatPure(TArray<uint8> bytes, float& value){
parseBytesToFloat(bytes, value);
}
void USocketClientBPLibrary::parseBytesToIntegerPure(TArray<uint8> bytes, int32& value){
parseBytesToInteger(bytes, value);
}
void USocketClientBPLibrary::parseBytesToInteger64Pure(TArray<uint8> bytes, int64& value){
parseBytesToInteger64(bytes, value);
}
void USocketClientBPLibrary::parseBytesToFloatEndian(TArray<uint8> bytes, float& littleEndian, float& bigEndian) {
littleEndian = 0.f;
bigEndian = 0.f;
if (bytes.Num() != 4) {
UE_LOG(LogTemp, Error, TEXT("ParseBytesToFloat: Cannot convert bytes to float. Array must contain 4 bytes but has %i bytes."), bytes.Num());
return;
}
uint8 littleEndianChar[] = { bytes[0], bytes[1], bytes[2], bytes[3] };
FMemory::Memcpy(&littleEndian, &littleEndianChar, sizeof(littleEndian));
uint8 bigEndianChar[] = { bytes[3], bytes[2], bytes[1], bytes[0] };
FMemory::Memcpy(&bigEndian, &bigEndianChar, sizeof(bigEndian));
}
void USocketClientBPLibrary::parseBytesToIntegerEndian(TArray<uint8> bytes, int32& littleEndian, int32& bigEndian){
littleEndian = 0;
bigEndian = 0;
if (bytes.Num() != 4){
UE_LOG(LogTemp, Error, TEXT("ParseBytesToInteger: Cannot convert bytes to integer. Array must contain 4 bytes but has %i bytes."), bytes.Num());
return;
}
uint8 littleEndianChar[] = { bytes[0], bytes[1], bytes[2], bytes[3] };
FMemory::Memcpy(&littleEndian, &littleEndianChar, sizeof(littleEndian));
uint8 bigEndianChar[] = { bytes[3], bytes[2], bytes[1], bytes[0] };
FMemory::Memcpy(&bigEndian, &bigEndianChar, sizeof(bigEndian));
}
void USocketClientBPLibrary::parseBytesToInteger64Endian(TArray<uint8> bytes, int64& littleEndian, int64& bigEndian) {
littleEndian = 0;
bigEndian = 0;
if (bytes.Num() != 8) {
UE_LOG(LogTemp, Error, TEXT("ParseBytesToInteger64: Cannot convert bytes to integer. Array must contain 8 bytes but has %i bytes."), bytes.Num());
return;
}return;
uint8 littleEndianChar[] = { bytes[0], bytes[1], bytes[2], bytes[3],bytes[4], bytes[5], bytes[6], bytes[7] };
FMemory::Memcpy(&littleEndian, &littleEndianChar, sizeof(littleEndian));
uint8 bigEndianChar[] = { bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0] };
FMemory::Memcpy(&bigEndian, &bigEndianChar, sizeof(bigEndian));
}
void USocketClientBPLibrary::parseFloatToBytes(TArray<uint8>& byteArray, float value, bool switchByteOrder){
union {
float tmpVal;
uint8 tmpArray[4];
} u;
u.tmpVal = value;
byteArray.AddUninitialized(4);
FMemory::Memcpy(byteArray.GetData(), u.tmpArray, 4);
if (switchByteOrder) {
byteArray.SwapMemory(3, 0);
byteArray.SwapMemory(2, 1);
}
}
void USocketClientBPLibrary::parseIntegerToBytes(TArray<uint8>& byteArray, int32 value, bool switchByteOrder){
union {
int32 tmpVal;
uint8 tmpArray[4];
} u;
u.tmpVal = value;
byteArray.AddUninitialized(4);
FMemory::Memcpy(byteArray.GetData(), u.tmpArray, 4);
if (switchByteOrder) {
byteArray.SwapMemory(3, 0);
byteArray.SwapMemory(2, 1);
}
}
void USocketClientBPLibrary::parseInteger64ToBytes(TArray<uint8>& byteArray, int64 value, bool switchByteOrder){
union {
int64 tmpVal;
uint8 tmpArray[8];
} u;
u.tmpVal = value;
byteArray.AddUninitialized(8);
FMemory::Memcpy(byteArray.GetData(), u.tmpArray, 8);
if (switchByteOrder) {
byteArray.SwapMemory(7, 0);
byteArray.SwapMemory(6, 1);
byteArray.SwapMemory(5, 2);
byteArray.SwapMemory(4, 3);
}
}
void USocketClientBPLibrary::parseFloatToBytesPure(TArray<uint8>& byteArray, float value, bool switchByteOrder){
parseFloatToBytes(byteArray, value, switchByteOrder);
}
void USocketClientBPLibrary::parseIntegerToBytesPure(TArray<uint8>& byteArray, int32 value, bool switchByteOrder){
parseIntegerToBytes(byteArray, value, switchByteOrder);
}
void USocketClientBPLibrary::parseInteger64ToBytesPure(TArray<uint8>& byteArray, int64 value, bool switchByteOrder){
parseInteger64ToBytes(byteArray, value, switchByteOrder);
}
void USocketClientBPLibrary::parseBytesToFloatArrayPure(TArray<float>& value, TArray<uint8> bytes) {
if (bytes.Num() == 0 && bytes.Num() % 4 != 0)
return;
value.Empty();
value.AddUninitialized(bytes.Num() / 4);
FMemory::Memcpy(value.GetData(), bytes.GetData(), bytes.Num());
}
void USocketClientBPLibrary::parseBytesToIntegerArrayPure(TArray<int32>& value, TArray<uint8> bytes) {
if (bytes.Num() == 0 && bytes.Num() % 4 != 0)
return;
value.Empty();
value.AddUninitialized(bytes.Num() / 4);
FMemory::Memcpy(value.GetData(), bytes.GetData(), bytes.Num());
}
void USocketClientBPLibrary::parseBytesToInteger64ArrayPure(TArray<int64>& value, TArray<uint8> bytes) {
if (bytes.Num() == 0 && bytes.Num() % 8 != 0)
return;
value.Empty();
value.AddUninitialized(bytes.Num() / 8);
FMemory::Memcpy(value.GetData(), bytes.GetData(), bytes.Num());
}
void USocketClientBPLibrary::parseFloatArrayToBytesPure(TArray<uint8>& byteArray, TArray<float> value) {
byteArray.Empty();
byteArray.AddUninitialized(value.Num() * 4);
FMemory::Memcpy(byteArray.GetData(), value.GetData(), value.Num() * 4);
}
void USocketClientBPLibrary::parseIntegerArrayToBytesPure(TArray<uint8>& byteArray, TArray<int32> value) {
byteArray.Empty();
byteArray.AddUninitialized(value.Num() * 4);
FMemory::Memcpy(byteArray.GetData(), value.GetData(), value.Num() * 4);
}
void USocketClientBPLibrary::parseInteger64ArrayToBytesPure(TArray<uint8>& byteArray, TArray<int64> value) {
byteArray.Empty();
byteArray.AddUninitialized(value.Num() * 8);
FMemory::Memcpy(byteArray.GetData(), value.GetData(), value.Num() * 8);
}
void USocketClientBPLibrary::changeTCPSeparatorStringOnClient(FString separator) {
USocketClientBPLibrary::getSocketClientTarget()->changeTCPSeparatorStringOnClientNonStatic(separator);
}
void USocketClientBPLibrary::changeTCPSeparatorStringOnClientNonStatic(FString separator) {
tcpStringSeparator = separator;
}
void USocketClientBPLibrary::changeTCPSeparatorByteOnClient(uint8 separator) {
USocketClientBPLibrary::getSocketClientTarget()->changeTCPSeparatorByteOnClientNonStatic(separator);
}
void USocketClientBPLibrary::changeTCPSeparatorByteOnClientNonStatic(uint8 separator) {
tcpByteSeparator = separator;
}
int32 USocketClientBPLibrary::getUniquePlayerID(APlayerController* playerController) {
if (playerController == nullptr || playerController->GetLocalPlayer() == nullptr)
return 0;
return playerController->GetLocalPlayer()->GetUniqueID();
}
FString USocketClientBPLibrary::getRandomID(){
return FGuid::NewGuid().ToString();
}
void USocketClientBPLibrary::getTcpSeparator(uint8& byteSeparator, FString& stringSeparator){
stringSeparator = tcpStringSeparator;
byteSeparator = tcpByteSeparator;
}
ISocketSubsystem* USocketClientBPLibrary::getSocketSubSystem() {
switch (USocketClientBPLibrary::getSocketClientTarget()->systemSocketPlatform)
{
case ESocketPlatformClient::E_SSC_SYSTEM:
return ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
case ESocketPlatformClient::E_SSC_WINDOWS:
return ISocketSubsystem::Get(FName(TEXT("WINDOWS")));
case ESocketPlatformClient::E_SSC_MAC:
return ISocketSubsystem::Get(FName(TEXT("MAC")));
case ESocketPlatformClient::E_SSC_IOS:
return ISocketSubsystem::Get(FName(TEXT("IOS")));
case ESocketPlatformClient::E_SSC_UNIX:
return ISocketSubsystem::Get(FName(TEXT("UNIX")));
case ESocketPlatformClient::E_SSC_ANDROID:
return ISocketSubsystem::Get(FName(TEXT("ANDROID")));
case ESocketPlatformClient::E_SSC_PS4:
return ISocketSubsystem::Get(FName(TEXT("PS4")));
case ESocketPlatformClient::E_SSC_XBOXONE:
return ISocketSubsystem::Get(FName(TEXT("XBOXONE")));
case ESocketPlatformClient::E_SSC_HTML5:
return ISocketSubsystem::Get(FName(TEXT("HTML5")));
case ESocketPlatformClient::E_SSC_SWITCH:
return ISocketSubsystem::Get(FName(TEXT("SWITCH")));
case ESocketPlatformClient::E_SSC_DEFAULT:
return ISocketSubsystem::Get();
default:
return ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
}
}
@@ -0,0 +1,67 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#include "SocketClientCleanerThread.h"
FSocketClientCleanerThread::FSocketClientCleanerThread() {
FString threadName = "FSocketClientPluginCleanerThread_" + FGuid::NewGuid().ToString();
thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Lowest);
}
void FSocketClientCleanerThread::addSession(FSocketClientPluginSession& session) {
session.addToCleanerTime = FDateTime::Now().GetTicks();
sessionQueue.Enqueue(session);
}
void FSocketClientCleanerThread::changeSettings(bool showLogsP, int32 minLiveTimeInSecondsP) {
showLogs = showLogsP;
minLiveTimeInSeconds = minLiveTimeInSecondsP;
}
uint32 FSocketClientCleanerThread::Run() {
while (true) {
TArray<FSocketClientPluginSession> tryItAgain;
if (showLogs && sessionQueue.IsEmpty()) {
UE_LOG(LogTemp, Display, TEXT("SocketClient: Cleaner: No connections available to clean up."));
}
while (sessionQueue.IsEmpty() == false) {
FSocketClientPluginSession session;
sessionQueue.Dequeue(session);
//one second = 10000000 ticks
if ((FDateTime::Now().GetTicks() - session.addToCleanerTime) < (10000000 * minLiveTimeInSeconds)) {
tryItAgain.Add(session);
continue;
}
if (showLogs) {
UE_LOG(LogTemp, Display, TEXT("SocketClient: Clean connection: %s"), *session.clientID);
}
delete session.tcpSendThread;
delete session.tcpRecieverThread;
delete session.tcpFileHandlerThread;
delete session.udpSocketReceiver;
delete session.udpSendDataThead;
delete session.udpReceiveDataThread;
delete session.socket;
}
for (int32 i = 0; i < tryItAgain.Num(); i++) {
sessionQueue.Enqueue(tryItAgain[i]);
}
tryItAgain.Empty();
FPlatformProcess::Sleep(minLiveTimeInSeconds);
}
return 0;
};
@@ -0,0 +1,181 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#include "SocketClientTCP.h"
USocketClientTCPClient::USocketClientTCPClient(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {
//This prevents the garbage collector from killingand deleting the class from RAM.
this->AddToRoot();
onsocketClientTCPConnectionEventDelegate.AddDynamic(this, &USocketClientTCPClient::connectionEvent);
}
void USocketClientTCPClient::socketClientTCPConnectionEventDelegate(const bool success, const FString message, const FString clientConnectionID) {}
void USocketClientTCPClient::receiveTCPMessageEventDelegate(const FString message, const TArray<uint8>& byteArray, const FString clientConnectionID){}
void USocketClientTCPClient::transferFileOverTCPProgressEventDelegate(const FString clientConnectionID, const FString filePathP, const float percent, const float mbit, const int64 bytesTransferred, const int64 fileSize) {}
void USocketClientTCPClient::fileTransferOverTCPInfoEventDelegate(const FString message, const FString clientConnectionID, const FString filePathP, const bool success) {}
void USocketClientTCPClient::connectionEvent(bool success, FString message, FString clientConnectionID){
connected = success;
}
void USocketClientTCPClient::connect(USocketClientBPLibrary* mainLibP, FString domainOrIP, ESocketClientIPType ipType, int32 port, EReceiveFilterClient receiveFilter,
ESocketClientTCPSeparator messageWrappingP, FString connectionIDP, UTCPConnectAsyncNode* tcpConnectAsyncNodeP, bool noPacketDelay, bool noPacketBlocking){
mainLib = mainLibP;
connectionID = connectionIDP;
messageWrapping = messageWrappingP;
tcpConnectAsyncNode = tcpConnectAsyncNodeP;
USocketClientBPLibrary::socketClientBPLibrary->getTcpSeparator(tcpByteSeparator, tcpStringSeparator);
tcpReceiveDataThread = new FSocketClientTCPReceiveDataThread(mainLib, connectionID, receiveFilter, domainOrIP, ipType, port,this,noPacketDelay,noPacketBlocking);
}
void USocketClientTCPClient::sendMessage(FString message, TArray<uint8> byteArray){
if (run && tcpSendThread != nullptr) {
tcpSendThread->sendMessage(message, byteArray);
}
}
void USocketClientTCPClient::sendFile(USocketClientBPLibrary* mainLibP, FString connectionIDP, FString domainOrIP, ESocketClientIPType ipType, int32 port, EFileFunctionsSocketClientDirectoryType directoryType, FString filePathP, FString token, FString Aes256bitKey){
mainLib = mainLibP;
connectionID = connectionIDP;
aesKey = Aes256bitKey;
fileToken = token;
sendOrReceive = 0;
filePath = UFileFunctionsSocketClient::getCleanDirectory(directoryType, filePathP);
tcpFileConnectionThread = new FSocketClientTCPFileHandlerThread(mainLib, connectionID, domainOrIP, ipType, port, this);
}
void USocketClientTCPClient::requestFile(USocketClientBPLibrary* mainLibP, FString connectionIDP, FString domainOrIP, ESocketClientIPType ipType, int32 port, EFileFunctionsSocketClientDirectoryType directoryType, FString filePathP, bool resumeP, FString token, FString Aes256bitKey) {
mainLib = mainLibP;
connectionID = connectionIDP;
aesKey = Aes256bitKey;
fileToken = token;
sendOrReceive = 1;
resume = resumeP;
filePath = UFileFunctionsSocketClient::getCleanDirectory(directoryType, filePathP);
tcpFileConnectionThread = new FSocketClientTCPFileHandlerThread(mainLib, connectionID, domainOrIP, ipType, port, this);
}
void USocketClientTCPClient::closeConnection(){
setRun(false);
if (tcpFileConnectionThread != nullptr) {
tcpFileConnectionThread->triggerFileTransferOverTCPInfoEvent("Data transfer aborted.", connectionID, filePath, false, mainLib, this);
}
if (tcpSendThread != nullptr) {
tcpSendThread->pauseThread(false);
}
if (mainLib != nullptr) {
FSocketClientPluginSession connectionSession = FSocketClientPluginSession();
connectionSession.tcpRecieverThread = tcpReceiveDataThread;
connectionSession.tcpSendThread = tcpSendThread;
//connectionSession.tcpSendFileThread = fileSendThread;
connectionSession.tcpFileHandlerThread = tcpFileConnectionThread;
connectionSession.socket = socket;
connectionSession.clientID = connectionID;
mainLib->cleanConnection(connectionSession);
}
}
bool USocketClientTCPClient::isRun(){
return run;
}
void USocketClientTCPClient::setRun(bool runP) {
run = runP;
}
FString USocketClientTCPClient::getConnectionID(){
return connectionID;
}
FString USocketClientTCPClient::getAesKey(){
return aesKey;
}
FString USocketClientTCPClient::getFileToken(){
return fileToken;
}
FString USocketClientTCPClient::getFilePath() {
return filePath;
}
void USocketClientTCPClient::setSocket(FSocket* socketP){
socket = socketP;
}
FSocket* USocketClientTCPClient::getSocket(){
return socket;
}
void USocketClientTCPClient::createSendThread(){
tcpSendThread = new FSocketClientTCPSendDataThead(mainLib, this, connectionID);
}
void USocketClientTCPClient::getTcpSeparator(FString& stringSeparator, uint8& byteSeparator, ESocketClientTCPSeparator& messageWrappingP) {
messageWrappingP = messageWrapping;
stringSeparator = tcpStringSeparator;
tcpByteSeparator = byteSeparator;
}
FString USocketClientTCPClient::encryptMessage(FString message) {
return UFileFunctionsSocketClient::encryptMessageWithAES(message, aesKey);
}
FString USocketClientTCPClient::decryptMessage(FString message) {
return UFileFunctionsSocketClient::decryptMessageWithAES(message, aesKey);
}
void USocketClientTCPClient::readDataLength(TArray<uint8>& byteDataArray, int32& byteLenght) {
if (FGenericPlatformProperties::IsLittleEndian() && byteDataArray[0] == 0x00) {
FMemory::Memcpy(&byteLenght, byteDataArray.GetData() + 1, 4);
}
else {
//endian fits not. swap bytes that contains the length
byteDataArray.SwapMemory(1, 4);
byteDataArray.SwapMemory(2, 3);
FMemory::Memcpy(&byteLenght, byteDataArray.GetData() + 1, 4);
}
}
bool USocketClientTCPClient::isSendFile(){
return (sendOrReceive == 0);
}
bool USocketClientTCPClient::isReceiveFile(){
return (sendOrReceive == 1);
}
bool USocketClientTCPClient::hasResume(){
return resume;
}
bool USocketClientTCPClient::isConnected()
{
return connected;
}
void USocketClientTCPClient::deleteFile(FString filePathP){
UFileFunctionsSocketClient::deleteFileAbsolutePath(filePathP);
}
void USocketClientTCPClient::getMD5FromFileAbsolutePath(FString filePathP, bool& success, FString& MD5) {
UFileFunctionsSocketClient::getMD5FromFileAbsolutePath(filePathP, success, MD5);
}
int64 USocketClientTCPClient::fileSize(FString filePathP) {
return UFileFunctionsSocketClient::fileSizeAbsolutePath(filePathP);
}
USocketClientBPLibrary* USocketClientTCPClient::getMainLib() {
return mainLib;
}
FString USocketClientTCPClient::int64ToString(int64 num) {
return UFileFunctionsSocketClient::int64ToString(num);
}
@@ -0,0 +1,592 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#include "SocketClientTCPFileHandlerThread.h"
FSocketClientTCPFileHandlerThread::FSocketClientTCPFileHandlerThread(USocketClientBPLibrary* socketClientP, FString clientConnectionIDP, FString ipOrDomainP, ESocketClientIPType ipTypeP, int32 portP, USocketClientTCPClient* tcpClientP) :
socketClient(socketClientP),
clientConnectionID(clientConnectionIDP),
ipOrDomain(ipOrDomainP),
ipType(ipTypeP),
port(portP),
tcpClient(tcpClientP) {
FString threadName = "FServerFileConnectionThread" + FGuid::NewGuid().ToString();
thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Normal);
}
FSocketClientTCPFileHandlerThread::~FSocketClientTCPFileHandlerThread() {
delete thread;
}
uint32 FSocketClientTCPFileHandlerThread::Run() {
//UE_LOG(LogTemp, Display, TEXT("DoWork:%s"),*(FDateTime::Now()).ToString());
FString ip = socketClient->resolveDomain(ipOrDomain, ipType);
int32 portGlobal = port;
FString clientConnectionIDGlobal = clientConnectionID;
USocketClientBPLibrary* socketClientGlobal = socketClient;
USocketClientTCPClient* tcpClientGlobal = tcpClient;
//UE_LOG(LogTemp, Warning, TEXT("Tread:%s:%i"),*ip, port);
ISocketSubsystem* sSS = USocketClientBPLibrary::getSocketSubSystem();
if (sSS == nullptr) {
AsyncTask(ENamedThreads::GameThread, [ip, portGlobal, clientConnectionIDGlobal, socketClientGlobal, tcpClientGlobal]() {
if (socketClientGlobal != nullptr)
socketClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(false, "Connection failed(1). SocketSubSystem does not exist." + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal, clientConnectionIDGlobal);
if (tcpClientGlobal != nullptr)
tcpClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(false, "Connection failed(1). SocketSubSystem does not exist." + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal, clientConnectionIDGlobal);
});
return 0;
}
TSharedRef<FInternetAddr> addr = sSS->CreateInternetAddr();
bool bIsValid;
addr->SetIp(*ip, bIsValid);
addr->SetPort(port);
if (bIsValid) {
// create the socket
FSocket* socket = sSS->CreateSocket(NAME_Stream, TEXT("socketClient"), addr->GetProtocolType());
tcpClient->setSocket(socket);
// try to connect to the server
if (socket == nullptr || socket->Connect(*addr) == false) {
const TCHAR* socketErr = sSS->GetSocketError(SE_GET_LAST_ERROR_CODE);
AsyncTask(ENamedThreads::GameThread, [ip, portGlobal, clientConnectionIDGlobal, socketClientGlobal, tcpClientGlobal, socketErr]() {
if (socketClientGlobal != nullptr)
socketClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(false, "Connection failed(2):" + FString(socketErr) + "|" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal, clientConnectionIDGlobal);
if (tcpClientGlobal != nullptr)
tcpClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(false, "Connection failed(2):" + FString(socketErr) + "|" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal, clientConnectionIDGlobal);
});
}
else {
//connected
AsyncTask(ENamedThreads::GameThread, [ip, portGlobal, clientConnectionIDGlobal, tcpClientGlobal, socketClientGlobal]() {
if (socketClientGlobal != nullptr)
socketClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(true, "Connection successful:" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal, clientConnectionIDGlobal);
if (tcpClientGlobal != nullptr)
tcpClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(true, "Connection successful:" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal, clientConnectionIDGlobal);
});
tcpClient->setRun(true);
if (tcpClient->isReceiveFile()) {
doRequestFileFromServer(socket);
}
else {
if (tcpClient->isSendFile()) {
doSendFileToServer(socket);
}
}
}
AsyncTask(ENamedThreads::GameThread, [ip, portGlobal, clientConnectionIDGlobal, tcpClientGlobal, socketClientGlobal]() {
if (socketClientGlobal != nullptr) {
socketClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(false, "Connection close:" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal, clientConnectionIDGlobal);
if (tcpClientGlobal != nullptr)
tcpClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(false, "Connection close:" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal, clientConnectionIDGlobal);
}
});
USocketClientBPLibrary::getSocketClientTarget()->closeSocketClientConnectionTCPNonStatic(clientConnectionID);
tcpClient->setRun(false);
if (socket != nullptr) {
socket->Close();
}
}
else {
AsyncTask(ENamedThreads::GameThread, [ip, portGlobal, clientConnectionIDGlobal, tcpClientGlobal, socketClientGlobal]() {
if (socketClientGlobal != nullptr)
socketClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(false, "Connection failed(3). IP not valid:" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal, clientConnectionIDGlobal);
if (tcpClientGlobal != nullptr)
tcpClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(false, "Connection failed(3). IP not valid:" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal, clientConnectionIDGlobal);
});
}
return 0;
}
void FSocketClientTCPFileHandlerThread::doRequestFileFromServer(FSocket* socket){
FString filePath = tcpClient->getFilePath();
if (!FPaths::DirectoryExists(filePath)) {
triggerFileTransferOverTCPInfoEvent("Data transmission aborted. Directory not found.", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
return;
}
FString message = "REQUEST_FILE_FROM_SERVER_|_" + tcpClient->getFileToken();
sendMessageToServer(message, socket);
message = readMessageFromServer(socket);
if (message.StartsWith("REQUEST_FILE_FROM_SERVER_ACCEPTED_|_" + tcpClient->getFileToken())) {
TArray<FString> lines;
message.ParseIntoArray(lines, TEXT("_|_"), true);
if (lines.Num() == 5) {
if (lines[0].Equals("REQUEST_FILE_FROM_SERVER_ACCEPTED") && lines[1].Len() > 0 && lines[2].Len() > 0 && lines[3].Len() > 0 && lines[4].Len() > 0) {
FArchive* writer = nullptr;
FString md5Server = lines[2];
int64 fileSize = FCString::Atoi64(*lines[3]);
if (filePath.EndsWith("/")) {
filePath += lines[4];
}
else {
filePath += "/" + lines[4];
}
int64 fileSizeOnClient = 0;
if (FPaths::FileExists(filePath)) {
fileSizeOnClient = tcpClient->fileSize(filePath);
if (tcpClient->hasResume()) {
writer = IFileManager::Get().CreateFileWriter(*filePath, EFileWrite::FILEWRITE_Append);
}
else {
writer = IFileManager::Get().CreateFileWriter(*filePath);
}
}
else {
writer = IFileManager::Get().CreateFileWriter(*filePath);
}
if (writer == nullptr) {
triggerFileTransferOverTCPInfoEvent("File could not be created. ", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
return;
}
if (writer->TotalSize() >= fileSize) {
writer->Close();
delete writer;
triggerFileTransferOverTCPInfoEvent("File on the client has the same size or is larger than the file on the server.", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
return;
}
//downloadFile = true;
int64 bytesDownloaded = writer->TotalSize();
message = "REQUEST_FILE_FROM_SERVER_ACCEPTED_|_" + tcpClient->getFileToken() + "_|_" + tcpClient->int64ToString(bytesDownloaded);
sendMessageToServer(message, socket);
int64 lastTimeWithData = 0;
int64 ticks1 = 0;
int64 ticks2 = 0;
int64 ticksDownload = 0;
int64 lastByte = 0;
uint32 dataSize = 0;
TArray<uint8> buffer;
while (socket != nullptr && tcpClient->isRun()) {
ticks1 = FDateTime::Now().GetTicks();
socket->Wait(ESocketWaitConditions::WaitForRead, FTimespan::FromSeconds(waitForRead));
ticks2 = FDateTime::Now().GetTicks();
bool hasData = socket->HasPendingData(dataSize);
if (!hasData && ticks1 == ticks2) {
triggerFileTransferOverTCPInfoEvent("Connection broken.", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
break;
}
// UE_LOG(LogTemp, Display, TEXT("download %i"),dataSize);
if (hasData) {
lastTimeWithData = FDateTime::Now().GetTicks();
//downlnoad
int32 bytesRead = 0;
buffer.Empty();
buffer.SetNumUninitialized(dataSize);
if (socket->Recv(buffer.GetData(), buffer.Num(), bytesRead)) {
writer->Serialize(buffer.GetData(), buffer.Num());
//show progress each second
if ((ticksDownload + 10000000) <= FDateTime::Now().GetTicks()) {
writer->Flush();
int64 bytesTransferredLastSecond = bytesDownloaded - lastByte;
//float speed = ((float)bytesTransferredLastSecond) / 125000;
float mbit = ((float)bytesTransferredLastSecond) / 1024 / 1024 * 8;
float sent = ((float)bytesDownloaded) / 1048576;
float left = 0;
float percent = 0;
if (fileSize > 0) {
left = ((float)(fileSize - bytesDownloaded)) / 1048576;
percent = ((float)bytesDownloaded / (float)fileSize * 100);
}
triggerTransferFileEvent(clientConnectionID, filePath, socketClient, tcpClient, percent, mbit, bytesDownloaded, fileSize);
ticksDownload = FDateTime::Now().GetTicks();
lastByte = bytesDownloaded;
}
bytesDownloaded += bytesRead;
}
if (bytesDownloaded >= fileSize) {
if (writer != nullptr) {
writer->Close();
delete writer;
writer = nullptr;
}
sendEndMessage(filePath, tcpClient->getFileToken(), md5Server, clientConnectionID, socket, socketClient, tcpClient);
//transfer file finish
triggerTransferFileEvent(clientConnectionID, filePath, socketClient, tcpClient, 100, 0, bytesDownloaded, fileSize);
tcpClient->setRun(false);
}
}
//no data time out
else {
//one second = 10000000 ticks
//5 seconds timeout
// UE_LOG(LogTemp, Display, TEXT("timeout %i"),(FDateTime::Now().GetTicks() - lastTimeWithData));
if ((FDateTime::Now().GetTicks() - lastTimeWithData) >= 50000000) {
triggerFileTransferOverTCPInfoEvent("Connection timeout", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
break;
}
}
}
buffer.Empty();
if (writer != nullptr) {
writer->Close();
delete writer;
writer = nullptr;
}
}
}
}
}
void FSocketClientTCPFileHandlerThread::doSendFileToServer(FSocket* socket){
FString filePath = tcpClient->getFilePath();
if (!FPaths::FileExists(filePath)) {
triggerFileTransferOverTCPInfoEvent("Data transmission aborted. File not found.", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
return;
}
int64 fileSize = tcpClient->fileSize(filePath);
bool md5okay = false;
FString md5 = FString();
tcpClient->getMD5FromFileAbsolutePath(filePath, md5okay, md5);
FString fileAuthMessage = "SEND_FILE_TO_SERVER_|_" + tcpClient->getFileToken() + "_|_" + md5 + "_|_" + FPaths::GetCleanFilename(filePath) + "_|_" + tcpClient->int64ToString(fileSize);
sendMessageToServer(fileAuthMessage, socket);
FString message = readMessageFromServer(socket);
if (message.StartsWith("SEND_FILE_TO_SERVER_ACCEPTED_|_" + tcpClient->getFileToken())) {
if (message.RemoveFromStart("SEND_FILE_TO_SERVER_ACCEPTED_|_" + tcpClient->getFileToken() + "_|_")) {
int64 startPosition = FCString::Atoi64(*message);
// upload file
FArchive* reader = IFileManager::Get().CreateFileReader(*filePath);
if (reader == nullptr || reader->TotalSize() == 0) {
if (reader != nullptr) {
reader->Close();
}
tcpClient->setRun(false);
return;
}
fileSize = reader->TotalSize();
/*int64 ticks1 = 0;
int64 ticks2 = 0;*/
int64 readSize = 0;
int64 lastPosition = startPosition;
int64 bytesSentSinceLastTick = 0;
int32 bufferSize = 1024 * 64;
int32 dataSendBySocket = 0;
float percent = 0.f;
float mbit = 0.f;
TArray<uint8> buffer;
int64 lastTimeTicks = FDateTime::Now().GetTicks();
if (bufferSize > fileSize) {
bufferSize = fileSize;
}
if (lastPosition > 0) {
reader->Seek(lastPosition);
}
while (tcpClient->isRun() && lastPosition < fileSize) {
if ((lastPosition + bufferSize) > fileSize) {
bufferSize = fileSize - lastPosition;
}
//buffer.Reset(bufferSize);
buffer.Empty();
buffer.AddUninitialized(bufferSize);
reader->Serialize(buffer.GetData(), buffer.Num());
lastPosition += buffer.Num();
//UE_LOG(LogTemp, Warning, TEXT("Send: %i"),buffer.Num());
//ticks1 = FDateTime::Now().GetTicks();
socket->Send(buffer.GetData(), buffer.Num(), dataSendBySocket);
//socket->Wait(ESocketWaitConditions::WaitForWrite, FTimespan::FromSeconds(0.001));
//ticks2 = FDateTime::Now().GetTicks();
if (dataSendBySocket == 0 || dataSendBySocket != buffer.Num()) {
triggerFileTransferOverTCPInfoEvent("Data transmission aborted. Connection possibly broken.", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
return;
}
//slowdown for tests
//FPlatformProcess::Sleep(0.01f);
//fire event every second
//one second = 10000000 ticks
if (((FDateTime::Now().GetTicks()) - lastTimeTicks) >= 10000000) {
mbit = ((float)lastPosition - (float)bytesSentSinceLastTick) / 1024 / 1024 * 8;
lastTimeTicks = FDateTime::Now().GetTicks();
bytesSentSinceLastTick = lastPosition;
percent = ((float)lastPosition / fileSize) * 100;
triggerTransferFileEvent(clientConnectionID, filePath, socketClient, tcpClient, percent, mbit, lastPosition, fileSize);
}
}
mbit = ((float)lastPosition - (float)bytesSentSinceLastTick) / 1024 / 1024 * 8;
percent = ((float)lastPosition / fileSize) * 100;
triggerTransferFileEvent(clientConnectionID, filePath, socketClient, tcpClient, percent, mbit, lastPosition, fileSize);
buffer.Empty();
if (reader != nullptr) {
reader->Close();
reader = nullptr;
}
message = readMessageFromServer(socket);
if (message.StartsWith("SEND_FILE_TO_SERVER_END_|_" + tcpClient->getFileToken())) {
if (message.RemoveFromStart("SEND_FILE_TO_SERVER_END_|_" + tcpClient->getFileToken() + "_|_")) {
if (message.Equals("OKAY")) {
triggerFileTransferOverTCPInfoEvent("Data transmission successful. ", clientConnectionID, filePath, true, socketClient, tcpClient);
tcpClient->setRun(false);
}
else {
triggerFileTransferOverTCPInfoEvent("There was an error during data transmission. File may be corrupted or incomplete. ", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
}
}
else {
triggerFileTransferOverTCPInfoEvent("Data transmission aborted. Wrong answer from the server (3). ", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
}
}
}
else {
triggerFileTransferOverTCPInfoEvent("Data transmission aborted. Wrong answer from the server (2). ", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
return;
}
}
else {
if (message.StartsWith("SEND_FILE_TO_SERVER_END_|_" + tcpClient->getFileToken())) {
if (message.RemoveFromStart("SEND_FILE_TO_SERVER_END_|_" + tcpClient->getFileToken() + "_|_")) {
if (message.Equals("OKAY")) {
triggerFileTransferOverTCPInfoEvent("Data transmission successful. ", clientConnectionID, filePath, true, socketClient, tcpClient);
tcpClient->setRun(false);
}
else {
triggerFileTransferOverTCPInfoEvent("There was an error during data transmission. File may be corrupted or incomplete. ", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
}
}
else {
triggerFileTransferOverTCPInfoEvent("Data transmission aborted. Wrong answer from the server (3). ", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
}
}
else {
triggerFileTransferOverTCPInfoEvent("Data transmission aborted. Wrong answer from the server (1). ", clientConnectionID, filePath, false, socketClient, tcpClient);
tcpClient->setRun(false);
}
}
}
void FSocketClientTCPFileHandlerThread::sendMessageToServer(FString message, FSocket* socket){
message = tcpClient->encryptMessage(message);
TArray<uint8> byteCache;
FTCHARToUTF8 Convert(*message);
int32 sent = 0;
if (FGenericPlatformProperties::IsLittleEndian()) {
byteCache.Add(0x00);
}
else {
byteCache.Add(0x01);
}
byteCache.SetNum(5);
int32 dataLength = Convert.Length();
FMemory::Memcpy(byteCache.GetData() + 1, &dataLength, 4);
byteCache.Append((uint8*)Convert.Get(), Convert.Length());
socket->Send(byteCache.GetData(), byteCache.Num(), sent);
byteCache.Empty();
}
void FSocketClientTCPFileHandlerThread::triggerFileTransferOverTCPInfoEvent(FString messageP, FString clientConnectionIDP, FString filePathP, bool successP, USocketClientBPLibrary* socketClientP, USocketClientTCPClient* tcpClientP) {
AsyncTask(ENamedThreads::GameThread, [messageP, clientConnectionIDP, filePathP, successP, socketClientP, tcpClientP]() {
if (socketClientP != nullptr)
socketClientP->onfileTransferOverTCPInfoEventDelegate.Broadcast(messageP, clientConnectionIDP, filePathP, successP);
if (tcpClientP != nullptr)
tcpClientP->onfileTransferOverTCPInfoEventDelegate.Broadcast(messageP, clientConnectionIDP, filePathP, successP);
});
}
void FSocketClientTCPFileHandlerThread::triggerTransferFileEvent(FString clientConnectionIDP, FString filePathP, USocketClientBPLibrary* socketClientP, USocketClientTCPClient* tcpClientP, float percentP, float mbitP, int64 transferredP, int64 fileSizeP) {
AsyncTask(ENamedThreads::GameThread, [clientConnectionIDP, socketClientP, filePathP, tcpClientP, percentP, mbitP, transferredP, fileSizeP]() {
if (socketClientP != nullptr)
socketClientP->ontransferFileOverTCPProgressEventDelegate.Broadcast(clientConnectionIDP, filePathP, percentP, mbitP, transferredP, fileSizeP);
if (tcpClientP != nullptr)
tcpClientP->ontransferFileOverTCPProgressEventDelegate.Broadcast(clientConnectionIDP, filePathP, percentP, mbitP, transferredP, fileSizeP);
});
}
void FSocketClientTCPFileHandlerThread::sendEndMessage(FString fullFilePathP, FString tokenP, FString md5ServerP, FString clientConnectionIDP, FSocket* clientSocketP, USocketClientBPLibrary* socketClientP, USocketClientTCPClient* tcpClientP) {
bool md5okay = false;
FString md5Client = FString();
tcpClientP->getMD5FromFileAbsolutePath(fullFilePathP, md5okay, md5Client);
FString response = "REQUEST_FILE_FROM_SERVER_END_|_" + tokenP + "_|_";
if (md5okay && md5ServerP.Equals(md5Client)) {
triggerFileTransferOverTCPInfoEvent("File successfully received.", clientConnectionIDP, fullFilePathP, true, socketClientP, tcpClientP);
response += "OKAY";
}
else {
triggerFileTransferOverTCPInfoEvent("File received but MD5 does not match. Corrupted file will be deleted if resume is not disabled.", clientConnectionIDP, fullFilePathP, false, socketClientP, tcpClientP);
response += "MD5ERROR";
if (tcpClientP->hasResume()) {
tcpClientP->deleteFile(fullFilePathP);
}
}
sendMessageToServer(response, clientSocketP);
}
FString FSocketClientTCPFileHandlerThread::readMessageFromServer(FSocket* socket){
int64 ticks1 = 0;
int64 ticks2 = 0;
int32 lastDataLengthFromHeader = 0;
FString message = FString();
TArray<uint8> byteDataArrayCache;
TArray<uint8> byteDataArray;
uint32 dataSize = 0;
TArray<FString> lines;
while (tcpClient->isRun() && socket != nullptr) {
ticks1 = FDateTime::Now().GetTicks();
socket->Wait(ESocketWaitConditions::WaitForRead, FTimespan::FromSeconds(waitForRead));
ticks2 = FDateTime::Now().GetTicks();
bool hasData = socket->HasPendingData(dataSize);
if (!hasData && ticks1 == ticks2) {
//if (showFTPLogs) {
// UE_LOG(LogTemp, Warning, TEXT("FTP Client: TCP connection broken."));
//}
//fireConnectionEvent(false, 0, "TCP connection broken.");
return "TCP connection broken.";
}
if (!hasData) {
return message;
}
if (hasData) {
TArray<uint8> dataFromSocket;
dataFromSocket.SetNumUninitialized(dataSize);
int32 BytesRead = 0;
if (socket->Recv(dataFromSocket.GetData(), dataFromSocket.Num(), BytesRead)) {
if (lastDataLengthFromHeader == 0 && dataFromSocket.Num() >= 5) {
tcpClient->readDataLength(dataFromSocket, lastDataLengthFromHeader);
if (dataFromSocket.Num() == 5) {
dataFromSocket.Empty();
continue;
}
byteDataArrayCache.Append(dataFromSocket.GetData() + 5, dataFromSocket.Num() - 5);
dataFromSocket.Empty();
}
else {
byteDataArrayCache.Append(dataFromSocket.GetData(), dataFromSocket.Num());
}
int32 maxLoops = 1000;//to prevent endless loop
while (byteDataArrayCache.Num() > 0 && byteDataArrayCache.Num() >= lastDataLengthFromHeader && maxLoops > 0) {
maxLoops--;
byteDataArray.Append(byteDataArrayCache.GetData(), lastDataLengthFromHeader);
byteDataArrayCache.RemoveAt(0, lastDataLengthFromHeader, true);
if (byteDataArrayCache.Num() == 0) {
lastDataLengthFromHeader = 0;
break;
}
if (byteDataArrayCache.Num() > 5) {
tcpClient->readDataLength(byteDataArrayCache, lastDataLengthFromHeader);
byteDataArrayCache.RemoveAt(0, 5, true);
}
}
byteDataArray.Add(0x00);// null-terminator
message = FString(UTF8_TO_TCHAR((char*)byteDataArray.GetData()));
byteDataArray.Empty();
if (message.IsEmpty() == false) {
message = tcpClient->decryptMessage(message);
}
return message;
}
}
}
if (message.IsEmpty() == false) {
message = tcpClient->decryptMessage(message);
}
return message;
}
@@ -0,0 +1,288 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#include "SocketClientTCPReceiveDataThread.h"
FSocketClientTCPReceiveDataThread::FSocketClientTCPReceiveDataThread(USocketClientBPLibrary* socketClientBPLibraryP, FString clientConnectionIDP,
EReceiveFilterClient receiveFilterP, FString ipOrDomainP, ESocketClientIPType ipTypeP, int32 portP,
USocketClientTCPClient* tcpClientP, bool noPacketDelayP, bool noPacketBlockingP) :
socketClientBPLibrary(socketClientBPLibraryP),
clientConnectionID(clientConnectionIDP),
receiveFilter(receiveFilterP),
ipOrDomain(ipOrDomainP),
ipType(ipTypeP),
port(portP),
tcpClient(tcpClientP),
noPacketDelay(noPacketDelayP),
noPacketBlocking(noPacketBlockingP) {
FString threadName = "FServerConnectionThread" + FGuid::NewGuid().ToString();
thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Normal);
}
FSocketClientTCPReceiveDataThread::~FSocketClientTCPReceiveDataThread() {
if (tcpClient != nullptr && tcpClient->tcpConnectAsyncNode != nullptr) {
tcpClient->tcpConnectAsyncNode = nullptr;
}
delete thread;
}
uint32 FSocketClientTCPReceiveDataThread::Run() {
//UE_LOG(LogTemp, Display, TEXT("DoWork:%s"),*(FDateTime::Now()).ToString());
FString ip = socketClientBPLibrary->resolveDomain(ipOrDomain, ipType);
int32 portGlobal = port;
FString clientConnectionIDGlobal = clientConnectionID;
USocketClientBPLibrary* socketClientGlobal = socketClientBPLibrary;
USocketClientTCPClient* tcpClientGlobal = tcpClient;
//message wrapping
FString stringSeparator = FString();
uint8 byteSeparator = 0x00;
ESocketClientTCPSeparator messageWrapping = ESocketClientTCPSeparator::E_None;
tcpClient->getTcpSeparator(stringSeparator, byteSeparator, messageWrapping);
TArray<TCHAR> stringSeparatorArray = stringSeparator.GetCharArray();
if (stringSeparatorArray.Num() > 0 && stringSeparatorArray.Last() == 0x00) {
stringSeparatorArray.RemoveAt(stringSeparatorArray.Num() - 1, 1, true);
}
if (messageWrapping == ESocketClientTCPSeparator::E_LengthSeparator && stringSeparatorArray.Num() == 0) {
messageWrapping = ESocketClientTCPSeparator::E_None;
UE_LOG(LogTemp, Warning, TEXT("Socket Client Plugin: Separator mode is set to String but there is no String Separator. Mode changed to none."));
}
//UE_LOG(LogTemp, Warning, TEXT("Tread:%s:%i"),*ip, port);
ISocketSubsystem* sSS = USocketClientBPLibrary::getSocketSubSystem();
if (sSS == nullptr) {
FString info = "Connection failed(1). SocketSubSystem does not exist.:" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal;
triggerConnectionEvent(false, clientConnectionID, info, tcpClientGlobal, socketClientGlobal);
return 0;
}
TSharedRef<FInternetAddr> addr = sSS->CreateInternetAddr();
bool bIsValid;
addr->SetIp(*ip, bIsValid);
addr->SetPort(port);
if (bIsValid) {
// create the socket
FSocket* socket = sSS->CreateSocket(NAME_Stream, TEXT("socketClient"), addr->GetProtocolType());
tcpClient->setSocket(socket);
//socket options
if (socket != nullptr) {
socket->SetNoDelay(noPacketDelay);
socket->SetNonBlocking(noPacketBlocking);
tcpClient->setRun(true);
}
// try to connect to the server
if (socket == nullptr || socket->Connect(*addr) == false) {
const TCHAR* socketErr = sSS->GetSocketError(SE_GET_LAST_ERROR_CODE);
FString info = "Connection failed(2):" + FString(socketErr) + "|" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal;
triggerConnectionEvent(false, clientConnectionID, info, tcpClientGlobal, socketClientGlobal);
}
else {
FString info = "Connection successful:" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal;
triggerConnectionEvent(true, clientConnectionID, info, tcpClientGlobal, socketClientGlobal);
tcpClient->createSendThread();
int64 ticks1;
int64 ticks2;
TArray<uint8> byteDataArray;
TArray<uint8> byteDataArrayCache;
FString mainMessage;
bool inCollectMessageStatus = false;
int32 lastDataLengthFromHeader = 0;
uint32 dataSize;
while (socket != nullptr && tcpClient->isRun()) {
//ESocketConnectionState::SCS_Connected does not work https://issues.unrealengine.com/issue/UE-27542
//Compare ticks is a workaround to get a disconnect. clientSocket->Wait() stop working after disconnect. (Another bug?)
//If it doesn't wait any longer, ticks1 and ticks2 should be the same == disconnect.
ticks1 = FDateTime::Now().GetTicks();
socket->Wait(ESocketWaitConditions::WaitForRead, FTimespan::FromSeconds(1));
ticks2 = FDateTime::Now().GetTicks();
bool hasData = socket->HasPendingData(dataSize);
if (!hasData && ticks1 == ticks2) {
UE_LOG(LogTemp, Display, TEXT("Socket Client: Connection aborted or closed by the server: %s"),*clientConnectionID);
break;
}
if (hasData) {
TArray<uint8> dataFromSocket;
dataFromSocket.SetNumUninitialized(dataSize);
int32 BytesRead = 0;
if (socket->Recv(dataFromSocket.GetData(), dataFromSocket.Num(), BytesRead)) {
switch (messageWrapping)
{
case ESocketClientTCPSeparator::E_None:
triggerMessageEvent(dataFromSocket, clientConnectionIDGlobal, tcpClientGlobal, socketClientGlobal);
break;
case ESocketClientTCPSeparator::E_ByteSeparator:
for (int32 i = 0; i < dataFromSocket.Num(); i++) {
byteDataArrayCache.Add(dataFromSocket[i]);
if (dataFromSocket[i] == byteSeparator) {
triggerMessageEvent(byteDataArrayCache, clientConnectionIDGlobal, tcpClientGlobal, socketClientGlobal, false);
byteDataArrayCache.Empty();
}
}
break;
case ESocketClientTCPSeparator::E_StringSeparator:
for (int32 i = 0; i < dataFromSocket.Num(); i++) {
;
if ((TCHAR)dataFromSocket[i] == stringSeparatorArray[0]) {
bool found = true;
for (int32 j = 1; j < stringSeparatorArray.Num(); j++) {
i++;
if (dataFromSocket.Num() <= (i)) {
found = false;
break;
}
if ((TCHAR)dataFromSocket[i] != stringSeparatorArray[j]) {
found = false;
break;
}
}
if (found) {
triggerMessageEvent(byteDataArrayCache, clientConnectionIDGlobal, tcpClientGlobal, socketClientGlobal, true);
byteDataArrayCache.Empty();
}
}
else {
byteDataArrayCache.Add(dataFromSocket[i]);
}
}
break;
case ESocketClientTCPSeparator::E_LengthSeparator:
if (lastDataLengthFromHeader == 0 && dataFromSocket.Num() >= 5) {
tcpClient->readDataLength(dataFromSocket, lastDataLengthFromHeader);
if (dataFromSocket.Num() == 5) {
dataFromSocket.Empty();
continue;
}
byteDataArrayCache.Append(dataFromSocket.GetData() + 5, dataFromSocket.Num() - 5);
dataFromSocket.Empty();
}
else {
byteDataArrayCache.Append(dataFromSocket.GetData(), dataFromSocket.Num());
}
int32 maxLoops = 1000;//to prevent endless loop
while (byteDataArrayCache.Num() > 0 && byteDataArrayCache.Num() >= lastDataLengthFromHeader && maxLoops > 0) {
maxLoops--;
byteDataArray.Append(byteDataArrayCache.GetData(), lastDataLengthFromHeader);
byteDataArrayCache.RemoveAt(0, lastDataLengthFromHeader, true);
triggerMessageEvent(byteDataArray, clientConnectionIDGlobal, tcpClientGlobal, socketClientGlobal);
//UE_LOG(LogTemp, Display, TEXT("%s"), *mainMessage);
byteDataArray.Empty();
if (byteDataArrayCache.Num() == 0) {
lastDataLengthFromHeader = 0;
break;
}
if (byteDataArrayCache.Num() > 5) {
tcpClient->readDataLength(byteDataArrayCache, lastDataLengthFromHeader);
byteDataArrayCache.RemoveAt(0, 5, true);
}
}
break;
}
}
mainMessage.Empty();
byteDataArray.Empty();
dataFromSocket.Empty();
}
}
info = "Connection close:" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal;
triggerConnectionEvent(false, clientConnectionID, info, tcpClientGlobal, socketClientGlobal);
}
if (socket != nullptr) {
socket->Close();
}
if (tcpClient->isRun()) {
socketClientBPLibrary->closeSocketClientConnectionTCPNonStatic(clientConnectionID);
//tcpClient->closeConnection();
}
}
else {
FString info = "Connection failed(3). IP not valid:" + ip + ":" + FString::FromInt(portGlobal) + "|" + clientConnectionIDGlobal;
triggerConnectionEvent(false, clientConnectionID,info,tcpClientGlobal, socketClientGlobal);
}
return 0;
}
void FSocketClientTCPReceiveDataThread::triggerConnectionEvent(bool succsess, FString clientConnectionIDGlobal, FString serverMessage, USocketClientTCPClient* tcpClientGlobal, USocketClientBPLibrary* socketClientGlobal){
AsyncTask(ENamedThreads::GameThread, [succsess, serverMessage, clientConnectionIDGlobal, tcpClientGlobal, socketClientGlobal]() {
if (socketClientGlobal != nullptr) {
socketClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(succsess, serverMessage, clientConnectionIDGlobal);
}
if (tcpClientGlobal != nullptr) {
tcpClientGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(succsess, serverMessage, clientConnectionIDGlobal);
if (tcpClientGlobal->tcpConnectAsyncNode != nullptr) {
tcpClientGlobal->tcpConnectAsyncNode->triggerConnectionEvent(succsess, clientConnectionIDGlobal, serverMessage);
}
}
});
}
void FSocketClientTCPReceiveDataThread::triggerMessageEvent(TArray<uint8>& byteDataArray, FString& clientConnectionIDGlobal, USocketClientTCPClient* tcpClientGlobal,
USocketClientBPLibrary* socketClientGlobal, bool addNullTerminator) {
FString mainMessage = FString();
if (receiveFilter == EReceiveFilterClient::E_SAB || receiveFilter == EReceiveFilterClient::E_S) {
if (addNullTerminator)
byteDataArray.Add(0x00);// null-terminator
mainMessage = FString(UTF8_TO_TCHAR((char*)byteDataArray.GetData()));
if (receiveFilter == EReceiveFilterClient::E_S) {
byteDataArray.Empty();
}
}
//switch to gamethread
AsyncTask(ENamedThreads::GameThread, [mainMessage, byteDataArray, clientConnectionIDGlobal, tcpClientGlobal, socketClientGlobal]() {
if (socketClientGlobal != nullptr) {
socketClientGlobal->onreceiveTCPMessageEventDelegate.Broadcast(mainMessage, byteDataArray, clientConnectionIDGlobal);
}
if (tcpClientGlobal != nullptr) {
tcpClientGlobal->onreceiveTCPMessageEventDelegate.Broadcast(mainMessage, byteDataArray, clientConnectionIDGlobal);
if (tcpClientGlobal->tcpConnectAsyncNode != nullptr) {
tcpClientGlobal->tcpConnectAsyncNode->triggerMessageEvent(byteDataArray, clientConnectionIDGlobal,mainMessage);
}
}
});
mainMessage.Empty();
}
@@ -0,0 +1,167 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#include "SocketClientTCPSendDataThead.h"
FSocketClientTCPSendDataThead::FSocketClientTCPSendDataThead(USocketClientBPLibrary* socketClientLibP, USocketClientTCPClient* tcpClientP, FString clientConnectionIDP) :
socketClientLib(socketClientLibP),
tcpClient(tcpClientP),
clientConnectionID(clientConnectionIDP) {
FString threadName = "FSendDataToServerThread" + FGuid::NewGuid().ToString();
thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Normal);
}
FSocketClientTCPSendDataThead::~FSocketClientTCPSendDataThead() {
delete thread;
}
uint32 FSocketClientTCPSendDataThead::Run(){
if (tcpClient == nullptr) {
UE_LOG(LogTemp, Error, TEXT("Class is not initialized."));
return 0;
}
/*if (GEngine)
GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Green, TEXT("tcp socket 1"));*/
FString clientConnectionIDGlobal = clientConnectionID;
USocketClientBPLibrary* socketClientLibGlobal = socketClientLib;
//message wrapping
FString stringSeparator = FString();
uint8 byteSeparator = 0x00;
ESocketClientTCPSeparator messageWrapping = ESocketClientTCPSeparator::E_None;
tcpClient->getTcpSeparator(stringSeparator, byteSeparator, messageWrapping);
TArray<TCHAR> stringSeparatorArray = stringSeparator.GetCharArray();
if (stringSeparatorArray.Num() > 0 && stringSeparatorArray.Last() == 0x00) {
stringSeparatorArray.RemoveAt(stringSeparatorArray.Num() - 1, 1, true);
}
if (messageWrapping == ESocketClientTCPSeparator::E_LengthSeparator && stringSeparatorArray.Num() == 0) {
messageWrapping = ESocketClientTCPSeparator::E_None;
UE_LOG(LogTemp, Warning, TEXT("Socket Client Plugin: Separator mode is set to String but there is no String Separator. Mode changed to none."));
}
// get the socket
FSocket* socket = tcpClient->getSocket();
int32 sent = 0;
while (tcpClient->isRun()) {
// try to connect to the server
if (socket == NULL || socket == nullptr) {
UE_LOG(LogTemp, Error, TEXT("Connection not exist."));
//AsyncTask(ENamedThreads::GameThread, [clientConnectionIDGlobal, socketClientLibGlobal]() {
// if (socketClientLibGlobal != nullptr) {
// socketClientLibGlobal->onsocketClientTCPConnectionEventDelegate.Broadcast(false, "Connection not exist:" + clientConnectionIDGlobal, clientConnectionIDGlobal);
// socketClientLibGlobal->closeSocketClientConnection();
// }
//});
break;
}
if (socket != nullptr && tcpClient->isRun()) {
while (messageQueue.IsEmpty() == false) {
FString m;
messageQueue.Dequeue(m);
FTCHARToUTF8 Convert(*m);
sent = 0;
TArray<uint8> byteCache;
switch (messageWrapping)
{
case ESocketClientTCPSeparator::E_None:
byteCache.Append((uint8*)Convert.Get(), Convert.Length());
break;
case ESocketClientTCPSeparator::E_ByteSeparator:
byteCache.Append((uint8*)Convert.Get(), Convert.Length());
byteCache.Add(byteSeparator);
break;
case ESocketClientTCPSeparator::E_StringSeparator:
{
m += stringSeparator;
FTCHARToUTF8 ConvertWithSeparator(*m);
byteCache.Append((uint8*)ConvertWithSeparator.Get(), ConvertWithSeparator.Length());
}
break;
case ESocketClientTCPSeparator::E_LengthSeparator:
if (FGenericPlatformProperties::IsLittleEndian()) {
byteCache.Add(0x00);
}
else {
byteCache.Add(0x01);
}
byteCache.SetNum(5);
int32 dataLength = Convert.Length();
FMemory::Memcpy(byteCache.GetData() + 1, &dataLength, 4);
byteCache.Append((uint8*)Convert.Get(), Convert.Length());
break;
}
socket->Send(byteCache.GetData(), byteCache.Num(), sent);
}
while (byteArrayQueue.IsEmpty() == false) {
TArray<uint8> byteCache;
byteArrayQueue.Dequeue(byteCache);
sent = 0;
switch (messageWrapping)
{
case ESocketClientTCPSeparator::E_ByteSeparator:
byteCache.Add(byteSeparator);
break;
case ESocketClientTCPSeparator::E_StringSeparator:
{
FTCHARToUTF8 ConvertWithSeparator(*stringSeparator);
byteCache.Append((uint8*)ConvertWithSeparator.Get(), ConvertWithSeparator.Length());
}
break;
case ESocketClientTCPSeparator::E_LengthSeparator:
byteCache.InsertZeroed(0, 5);
if (FGenericPlatformProperties::IsLittleEndian() == false) {
uint8 a = 0x01;
FMemory::Memcpy(byteCache.GetData(), &a, 1);
}
int32 dataLength = byteCache.Num() - 5;
FMemory::Memcpy(byteCache.GetData() + 1, &dataLength, 4);
break;
}
socket->Send(byteCache.GetData(), byteCache.Num(), sent);
}
}
if (tcpClient->isRun()) {
pauseThread(true);
//workaround. suspend do not work on all platforms. lets sleep
while (paused && tcpClient->isRun()) {
FPlatformProcess::Sleep(0.01);
}
}
}
return 0;
}
void FSocketClientTCPSendDataThead::sendMessage(FString messageP, TArray<uint8> byteArrayP) {
if (messageP.Len() > 0)
messageQueue.Enqueue(messageP);
if (byteArrayP.Num() > 0)
byteArrayQueue.Enqueue(byteArrayP);
pauseThread(false);
}
void FSocketClientTCPSendDataThead::pauseThread(bool pause) {
paused = pause;
if (thread != nullptr)
thread->Suspend(pause);
}
@@ -0,0 +1,148 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#include "SocketClientUDP.h"
USocketClientUDP::USocketClientUDP(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {
this->AddToRoot();
}
void USocketClientUDP::socketClientUDPConnectionEventDelegate(const bool success, const FString message, const FString clientConnectionIDP){}
void USocketClientUDP::receiveUDPMessageEventDelegate(const FString message, const TArray<uint8>& byteArray, const FString IPP, const int32 portP, const FString clientConnectionIDP) {}
void USocketClientUDP::init(USocketClientBPLibrary* socketClientLibP, UUDPInitAsyncNode* udpInitAsyncNodeP, FString domainOrIPP,
ESocketClientIPType ipType, int32 portP, EReceiveFilterClient receiveFilterP, FString connectionIDP,
int32 maxPacketSizeP) {
socketClientBPLibrary = socketClientLibP;
receiveFilter = receiveFilterP;
connectionID = connectionIDP;
domainOrIP = domainOrIPP;
port = portP;
udpInitAsyncNode = udpInitAsyncNodeP;
maxPacketSize = maxPacketSizeP;
if (maxPacketSize < 1 || maxPacketSize > 65507)
maxPacketSize = 65507;
UDPThread = new FSocketClientUDPReceiveDataThread(this, socketClientLibP, domainOrIPP, portP, ipType);
}
void USocketClientUDP::sendUDPMessage(FString domainOrIPP, ESocketClientIPType ipType, int32 portP, FString message, TArray<uint8> byteArray){
if (UDPSendThread != nullptr) {
UDPSendThread->addData(message, byteArray, domainOrIPP, portP, ipType);
}
}
void USocketClientUDP::closeUDPConnection() {
if (udpSocketReceiver != nullptr) {
udpSocketReceiver->Stop();
}
setRun(false);
if (UDPSendThread != nullptr) {
UDPSendThread->pauseThread(false);
}
if (socketClientBPLibrary != nullptr) {
FSocketClientPluginSession connectionSession = FSocketClientPluginSession();
connectionSession.udpSocketReceiver = udpSocketReceiver;
connectionSession.udpSendDataThead = UDPSendThread;
connectionSession.udpReceiveDataThread = UDPThread;
connectionSession.socket = socket;
connectionSession.clientID = connectionID;
socketClientBPLibrary->cleanConnection(connectionSession);
}
//udpInitAsyncNode = nullptr;
}
void USocketClientUDP::UDPReceiver(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt) {
if (FSocketClientModule::isShuttingDown)
return;
TSharedPtr<FInternetAddr> peerAddr = EndPt.ToInternetAddr();
FString ipGlobal = peerAddr->ToString(false);
int32 portGlobal = peerAddr->GetPort();
TArray<uint8> byteArray;
if (receiveFilter == EReceiveFilterClient::E_SAB || receiveFilter == EReceiveFilterClient::E_B) {
byteArray.Append(ArrayReaderPtr->GetData(), ArrayReaderPtr->Num());
}
FString recvMessage;
if (receiveFilter == EReceiveFilterClient::E_SAB || receiveFilter == EReceiveFilterClient::E_S) {
ArrayReaderPtr->Add(0x00);// null-terminator
char* Data = (char*)ArrayReaderPtr->GetData();
recvMessage = FString(UTF8_TO_TCHAR(Data));
}
//switch to gamethread
USocketClientBPLibrary* socketClientBPLibraryGlobal = socketClientBPLibrary;
USocketClientUDP* udpClientGlobal = this;
FString clientConnectionIDGlobal = connectionID;
UUDPInitAsyncNode* udpInitAsyncNodeGlobal = udpInitAsyncNode;
AsyncTask(ENamedThreads::GameThread, [udpInitAsyncNodeGlobal, udpClientGlobal, recvMessage, byteArray, ipGlobal, portGlobal, socketClientBPLibraryGlobal, clientConnectionIDGlobal]() {
if (FSocketClientModule::isShuttingDown)
return;
socketClientBPLibraryGlobal->onreceiveUDPMessageEventDelegate.Broadcast(recvMessage, byteArray, ipGlobal, portGlobal, clientConnectionIDGlobal);
udpClientGlobal->onreceiveUDPMessageEventDelegate.Broadcast(recvMessage, byteArray, ipGlobal, portGlobal, clientConnectionIDGlobal);
if (udpInitAsyncNodeGlobal != nullptr) {
udpInitAsyncNodeGlobal->triggerMessageEvent(byteArray, clientConnectionIDGlobal, recvMessage, ipGlobal, portGlobal);
}
});
}
bool USocketClientUDP::isRun(){
return run;
}
void USocketClientUDP::setRun(bool runP){
run = runP;
}
FSocket* USocketClientUDP::getSocket(){
return socket;
}
void USocketClientUDP::setSocket(FSocket* socketP){
socket = socketP;
}
void USocketClientUDP::setUDPSocketReceiver(FUdpSocketReceiver* udpSocketReceiverP){
udpSocketReceiver = udpSocketReceiverP;
}
FString USocketClientUDP::getIP(){
return domainOrIP;
}
void USocketClientUDP::setIP(FString ipP){
domainOrIP = ipP;
}
int32 USocketClientUDP::getPort(){
return port;
}
FString USocketClientUDP::getDomainOrIP(){
return domainOrIP;
}
FString USocketClientUDP::getConnectionID(){
return connectionID;
}
void USocketClientUDP::setUDPSendThread(FSocketClientUDPSendDataThead* udpSendThreadP){
UDPSendThread = udpSendThreadP;
}
int32 USocketClientUDP::getMaxPacketSize()
{
return maxPacketSize;
}
@@ -0,0 +1,117 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#include "SocketClientUDPReceiveDataThread.h"
FSocketClientUDPReceiveDataThread::FSocketClientUDPReceiveDataThread(USocketClientUDP* udpClientP, USocketClientBPLibrary* socketClientP, FString ipP, int32 portP, ESocketClientIPType ipTypeP) :
udpClient(udpClientP),
socketClient(socketClientP),
ipGlobal(ipP),
portGlobal(portP),
ipType(ipTypeP) {
FString threadName = "FServerUDPConnectionThread_" + FGuid::NewGuid().ToString();
thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Normal);
}
FSocketClientUDPReceiveDataThread::~FSocketClientUDPReceiveDataThread() {
if (udpClient != nullptr && udpClient->udpInitAsyncNode != nullptr) {
udpClient->udpInitAsyncNode = nullptr;
}
delete thread;
}
void FSocketClientUDPReceiveDataThread::triggerInitEvent(bool success, USocketClientUDP* udpClientP, USocketClientBPLibrary* socketClientP,
FString serverMessage, FString connectionID){
AsyncTask(ENamedThreads::GameThread, [success, udpClientP, socketClientP, serverMessage, connectionID]() {
if (socketClientP != nullptr)
socketClientP->onsocketClientUDPConnectionEventDelegate.Broadcast(success, serverMessage, connectionID);
if (udpClientP != nullptr) {
udpClientP->onsocketClientUDPConnectionEventDelegate.Broadcast(success, serverMessage, connectionID);
if (udpClientP->udpInitAsyncNode != nullptr) {
udpClientP->udpInitAsyncNode->triggerInitEvent(success, connectionID, serverMessage);
}
}
});
}
uint32 FSocketClientUDPReceiveDataThread::Run() {
USocketClientUDP* udpClientGlobal = udpClient;
FString ip = socketClient->resolveDomain(ipGlobal, ipType);
udpClient->setIP(ip);
int32 port = portGlobal;
FString connectionID = udpClient->getConnectionID();
if (socket == nullptr || socket == NULL) {
FString endpointAdress = ip + ":" + FString::FromInt(port);
FIPv4Endpoint Endpoint;
// create the socket
FString socketName;
ISocketSubsystem* socketSubsystem = USocketClientBPLibrary::getSocketSubSystem();
TSharedPtr<class FInternetAddr> addr = socketSubsystem->CreateInternetAddr();
bool validIP = true;
addr->SetPort(port);
addr->SetIp(*ip, validIP);
if (!validIP) {
UE_LOG(LogTemp, Error, TEXT("SocketClient UDP. Can't set ip"));
triggerInitEvent(false, udpClient, socketClient, "SocketClient UDP. Can't set ip", connectionID);
return 0;
}
socket = socketSubsystem->CreateSocket(NAME_DGram, *socketName, addr->GetProtocolType());
if (socket == nullptr || socket == NULL) {
const TCHAR* SocketErr = socketSubsystem->GetSocketError(SE_GET_LAST_ERROR_CODE);
UE_LOG(LogTemp, Error, TEXT("UE could not init a UDP socket. You can only create listening connections on local IPs. An external IP must be redirected to a local IP in your router. %s:%i. Error: %s"), *ip, port, SocketErr);
triggerInitEvent(false, udpClient, socketClient, "(Error 0) UE could not init a UDP socket. You can only create listening connections on local IPs. An external IP must be redirected to a local IP in your router." + addr->ToString(true) + " Error:" + SocketErr, connectionID);
return 0;
}
if (!socket->SetRecvErr()) {
UE_LOG(LogTemp, Error, TEXT("SocketClient UDP. Can't set recverr"));
}
if (socket == nullptr || socket == NULL || !validIP) {
const TCHAR* SocketErr = socketSubsystem->GetSocketError(SE_GET_LAST_ERROR_CODE);
UE_LOG(LogTemp, Error, TEXT("UE could not init a UDP socket. You can only create listening connections on local IPs. An external IP must be redirected to a local IP in your router. %s:%i. Error: %s"), *ip, port, SocketErr);
triggerInitEvent(false, udpClient, socketClient, "(Error 1) UE could not init a UDP socket. You can only create listening connections on local IPs. An external IP must be redirected to a local IP in your router." + addr->ToString(true) + " Error:" + SocketErr, connectionID);
return 0;
}
socket->SetReuseAddr(true);
socket->SetNonBlocking(true);
socket->SetBroadcast(true);
if (!socket->Bind(*addr)) {
const TCHAR* SocketErr = socketSubsystem->GetSocketError(SE_GET_LAST_ERROR_CODE);
UE_LOG(LogTemp, Error, TEXT("UE could not init a UDP socket. You can only create listening connections on local IPs. An external IP must be redirected to a local IP in your router. %s:%i. Error: %s"), *ip, port, SocketErr);
triggerInitEvent(false, udpClient, socketClient, "(Error 2) UE could not init a UDP socket. You can only create listening connections on local IPs. An external IP must be redirected to a local IP in your router." + addr->ToString(true) + " Error:" + SocketErr, connectionID);
return 0;
}
FTimespan ThreadWaitTime = FTimespan::FromMilliseconds(100);
FUdpSocketReceiver* udpSocketReceiver = new FUdpSocketReceiver(socket, ThreadWaitTime, TEXT("SocketClientBPLibUDPReceiverThread"));
udpSocketReceiver->OnDataReceived().BindUObject(udpClient, &USocketClientUDP::UDPReceiver);
udpSocketReceiver->Start();
udpClient->setUDPSocketReceiver(udpSocketReceiver);
udpClient->setSocket(socket);
udpClient->setRun(true);
udpClient->setUDPSendThread(new FSocketClientUDPSendDataThead(udpClient, socketClient, ip, port));
triggerInitEvent(true, udpClient, socketClient, "Init UDP Connection OK. " + addr->ToString(true), connectionID);
}
thread = nullptr;
return 0;
}
@@ -0,0 +1,146 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#include "SocketClientUDPSendDataThead.h"
FSocketClientUDPSendDataThead::FSocketClientUDPSendDataThead(USocketClientUDP* udpClientP, USocketClientBPLibrary* socketClientP, FString mySocketipP, int32 mySocketportP) :
udpClient(udpClientP),
socketClient(socketClientP),
mySocketip(mySocketipP),
mySocketport(mySocketportP) {
FString threadName = "FServerUDPSendMessageThread_" + FGuid::NewGuid().ToString();
thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Normal);
}
FSocketClientUDPSendDataThead::~FSocketClientUDPSendDataThead() {
delete thread;
}
uint32 FSocketClientUDPSendDataThead::Run() {
FSocket* socket = udpClient->getSocket();
FString connectionID = udpClient->getConnectionID();
maxPacketSize = udpClient->getMaxPacketSize();
while (udpClient->isRun() && socket != nullptr) {
if (udpClient->isRun() && (messageQueue.IsEmpty() == false || byteArrayQueue.IsEmpty() == false)) {
int32 sent = 0;
TArray<uint8> byteArray;
if (validInternetAdress) {
while (messageQueue.IsEmpty() == false) {
FString m;
messageQueue.Dequeue(m);
FTCHARToUTF8 Convert(*m);
byteArray.Append((uint8*)Convert.Get(), Convert.Length());
sendBytes(socket, byteArray, sent, internetAdress);
}
while (byteArrayQueue.IsEmpty() == false) {
byteArrayQueue.Dequeue(byteArray);
sendBytes(socket, byteArray, sent, internetAdress);
}
}
else {
UE_LOG(LogTemp, Error, TEXT("Can't send to %s:%i . Adress not valid."), *sendToip, sendToport);
}
}
if (udpClient->isRun()) {
pauseThread(true);
//workaround. suspend do not work on all platforms. lets sleep
while (paused && udpClient->isRun()) {
FPlatformProcess::Sleep(0.01);
}
}
}
if (socket != nullptr) {
socket->Close();
socket = nullptr;
udpClient->setSocket(nullptr);
}
USocketClientBPLibrary* socketClientTMP = socketClient;
USocketClientUDP* udpClientGlobal = udpClient;
FString ipGlobal = mySocketip;
int32 portGlobal = mySocketport;
AsyncTask(ENamedThreads::GameThread, [socketClientTMP, udpClientGlobal, ipGlobal, portGlobal, connectionID]() {
if (socketClientTMP != nullptr)
socketClientTMP->onsocketClientUDPConnectionEventDelegate.Broadcast(false, "UDP connection closed. " + ipGlobal + ":" + FString::FromInt(portGlobal), connectionID);
if (udpClientGlobal != nullptr)
udpClientGlobal->onsocketClientUDPConnectionEventDelegate.Broadcast(false, "UDP connection closed. " + ipGlobal + ":" + FString::FromInt(portGlobal), connectionID);
});
thread = nullptr;
return 0;
}
void FSocketClientUDPSendDataThead::addData(FString messageP, TArray<uint8> byteArrayP, FString domainOrIP, int32 port, ESocketClientIPType ipType) {
//new adress?
if (sendToport != port || sendToDomainOrIP.Equals(domainOrIP) == false) {
sendToDomainOrIP = domainOrIP;
sendToip = socketClient->resolveDomain(domainOrIP, ipType);
sendToport = port;
internetAdress->SetIp(*sendToip, validInternetAdress);
internetAdress->SetPort(sendToport);
if (!validInternetAdress) {
//don't send to many error messages. one second = 10000000 ticks
if (((FDateTime::Now().GetTicks()) - lastErrorMessageTime) >= 10000000) {
UE_LOG(LogTemp, Error, TEXT("Can't create Adress %s:%i"), *sendToip, sendToport);
lastErrorMessageTime = FDateTime::Now().GetTicks();
}
return;
}
}
if (!validInternetAdress) {
return;
}
if (messageP.Len() > 0)
messageQueue.Enqueue(messageP);
if (byteArrayP.Num() > 0)
byteArrayQueue.Enqueue(byteArrayP);
pauseThread(false);
}
void FSocketClientUDPSendDataThead::pauseThread(bool pause) {
paused = pause;
if (thread != nullptr)
thread->Suspend(pause);
}
void FSocketClientUDPSendDataThead::sendBytes(FSocket*& socketP, TArray<uint8>& byteArray, int32& sent, TSharedRef<FInternetAddr>& addr) {
if (byteArray.Num() > maxPacketSize) {
TArray<uint8> byteArrayTemp;
for (int32 i = 0; i < byteArray.Num(); i++) {
byteArrayTemp.Add(byteArray[i]);
if (byteArrayTemp.Num() == maxPacketSize) {
sent = 0;
socketP->SendTo(byteArrayTemp.GetData(), byteArrayTemp.Num(), sent, *addr);
byteArrayTemp.Empty();
}
}
if (byteArrayTemp.Num() > 0) {
sent = 0;
socketP->SendTo(byteArrayTemp.GetData(), byteArrayTemp.Num(), sent, *addr);
byteArrayTemp.Empty();
}
}
else {
sent = 0;
socketP->SendTo(byteArray.GetData(), byteArray.Num(), sent, *addr);
}
byteArray.Empty();
}
@@ -0,0 +1,28 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
#include "DNSClientSocketClient.generated.h"
UCLASS()
class SOCKETCLIENT_API UDNSClientSocketClient : public UObject
{
GENERATED_UCLASS_BODY()
public:
void resolveDomain(ISocketSubsystem* socketSubSystem, FString domain, bool useDNSCache = true, FString dnsIP = FString("8.8.8.8"));
void UDPReceiver(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt);
FSocket* socket = nullptr;
bool isResloving();
FString getIP();
private:
bool resolving;
FString ip;
FString domain;
TMap<FString, FString> dnsCache;
};
@@ -0,0 +1,271 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
#include "SocketClientBPLibrary.h"
#include "FileFunctionsSocketClient.generated.h"
class FReadFileInPartsSocketClientThread;
UCLASS(Blueprintable, BlueprintType)
class SOCKETCLIENT_API UFileFunctionsSocketClient : public UObject
{
GENERATED_UCLASS_BODY()
public:
UFUNCTION()
static UFileFunctionsSocketClient* getFileFunctionsSocketClientTarget();
static UFileFunctionsSocketClient* fileFunctionsSocketClient;
static FString getCleanDirectory(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void writeBytesToFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, TArray<uint8> bytes, bool& success);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void addBytesToFileAndCloseIt(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, TArray<uint8> bytes, bool& success);
//UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
// static void splittFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, int32 parts, bool& success);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static TArray<uint8> readBytesFromFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, bool &success);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void readStringFromFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, bool& success, FString& data);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void writeStringToFile(EFileFunctionsSocketClientDirectoryType directoryType, FString data, FString filePath, EFileFunctionsSocketClientEncodingOptions fileEncoding, bool& success);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void getMD5FromFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, bool& success, FString& MD5);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void getMD5FromFileAbsolutePath(FString filePath, bool& success, FString& MD5);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void stringToBase64String(FString string, FString& base64String);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void base64StringToString(FString& string, FString base64String);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void bytesToBase64String(TArray<uint8> bytes, FString& base64String);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static TArray<uint8> base64StringToBytes(FString base64String, bool& success);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void fileToBase64String(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, bool& success, FString& base64String, FString& fileName);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static bool fileExists(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static bool fileExistsAbsolutePath(FString filePath);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static bool directoryExists(EFileFunctionsSocketClientDirectoryType directoryType, FString path);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static int64 fileSize(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static int64 fileSizeAbsolutePath(FString filePath);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static bool deleteFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File")
static bool deleteFileAbsolutePath(FString filePath);
/** Delete a directory and return true if the directory was deleted or otherwise does not exist. **/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static bool deleteDirectory(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
/** Return true if the file is read only. **/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static bool isReadOnly(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
/** Attempt to move a file. Return true if successful. Will not overwrite existing files. **/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static bool moveFile(EFileFunctionsSocketClientDirectoryType directoryTypeTo, FString filePathTo, EFileFunctionsSocketClientDirectoryType directoryTypeFrom, FString filePathFrom);
/** Attempt to change the read only status of a file. Return true if successful. **/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static bool setReadOnly(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, bool bNewReadOnlyValue);
/** Return the modification time of a file. Returns FDateTime::MinValue() on failure **/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static FDateTime getTimeStamp(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
/** Sets the modification time of a file **/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void setTimeStamp(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FDateTime DateTime);
/** Return the last access time of a file. Returns FDateTime::MinValue() on failure **/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static FDateTime getAccessTimeStamp(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
/** For case insensitive filesystems, returns the full path of the file with the same case as in the filesystem */
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static FString getFilenameOnDisk(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
/** Create a directory and return true if the directory was created or already existed. **/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static bool createDirectory(EFileFunctionsSocketClientDirectoryType directoryType, FString path);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void getAllFilesFromDirectory(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, int32& count, TArray<FString>& files, TArray<FString>& filePaths, FString fileType ="*.*");
/**
* Encrypts an file with AES in 256bit
* @param filePath The path must contain the file at the end.
* @param newFileName New name for the encrypted file without path. It can also take the same name of the unencrypted file. But the file will be overwritten. Possible data loss.
* @param keyIn256Bit The key must be a string with 32 characters. Please use ANSI characters only!
* @param writeEncryptedFileSizeToFile To decrypt the file correctly the size of the original file is needed. With true the size is written as int64 (8 byte) at the beginning of the file.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|AES")
static bool encryptFileWithAES(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FString newFileName, FString keyIn256Bit, bool writeEncryptedFileSizeToFile = true);
/**
* Decrypts an file that has been encrypted in AES with 256bit
* @param filePath The path must contain the file at the end.
* @param newFileName New name for the decrypted file without path. It can also take the same name of the dencrypted file. But the file will be overwritten. Possible data loss.
* @param keyIn256Bit The key must be a string with 32 characters. Please use ANSI characters only!
* @param hasEncryptedFileSizeInFile To decrypt the file correctly the size of the original file is needed. With true the size is read from the first 8 bytes in the file.
* @param originalFileSize To decrypt the file correctly the size of the original file is needed. If the size of the original file is not in the first bytes of the file, you must specify it here.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|AES")
static bool decryptFileWithAES(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FString newFileName, FString keyIn256Bit, bool hasEncryptedFileSizeInFile = true, int64 originalFileSize = 0);
/**
* Encrypts a string with AES in 256bit and returns the encrypted string as Base64 string.
* @param message The string to be encrypted
* @param keyIn256Bit The key must be a string with 32 characters. Please use ANSI characters only!
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|AES")
static FString encryptMessageWithAES(FString message, FString keyIn256Bit);
/**
* Decrypts a Base64 string that has been encrypted in AES with 256bit and returns the string.
* @param message The string to be decrypted
* @param keyIn256Bit The key must be a string with 32 characters. Please use ANSI characters only!
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|AES")
static FString decryptMessageWithAES(FString encryptedBase64Message, FString keyIn256Bit);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|String")
static FString int64ToString(int64 num);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static struct FFileFunctionsSocketClientOpenFile openFile(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static int64 addBytesToFile(struct FFileFunctionsSocketClientOpenFile openFile, TArray<uint8>bytes);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void closeFile(struct FFileFunctionsSocketClientOpenFile openFile);
/**
* With this function you can read a file piece by piece. This reduces the RAM consumption to almost zero and files can be read in infinite size.
*@param bufferSize In bytes. This is the size of the file pieces that are being read.
*@param delayBetweenReadsInSeconds Specified in seconds. The higher the value, the slower the file is read (0.0001 minimum). When sending data over the network/internet, please make sure not to send data too fast. SSDs can read data much faster than you can send it over a network. This means that the data ends up in some buffers (RAM) and can also cause them to overflow.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File", meta = (AdvancedDisplay = 2))
static void readBytesFromFileInPartsAsync(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, int32 bufferSize = 65536, float delayBetweenReadsInSeconds = 0.01f);
void readBytesFromFileInPartsAsyncInternal(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, int32 bufferSize = 65536, float delayBetweenReadsInSeconds = 0.01f);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
static void cancelReadBytesFromFileInParts(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
void cancelReadBytesFromFileInPartsInternal(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath);
void cleanReadBytesFromFileInParts(FString cleanDir);
//UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|File")
// static void changeDelayInBytesFromFileInParts(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, float delayBetweenReadsInSeconds = 0.1f);
//void changeDelayInBytesFromFileInPartsInternal(EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, float delayBetweenReadsInSeconds = 0.1f);
TMap<FString, FReadFileInPartsSocketClientThread*> readFileInPartsThreads;
private:
static TArray<uint8> FStringToByteArray(FString s);
};
/* asynchronous Thread*/
class SOCKETCLIENT_API FReadFileInPartsSocketClientThread : public FRunnable {
public:
FReadFileInPartsSocketClientThread(FString cleanDirP, int32 bufferSizeP, float delayBetweenReadsInSecondsP) :
cleanDir(cleanDirP),
bufferSize(bufferSizeP),
delayBetweenReadsInSeconds(delayBetweenReadsInSecondsP)
{
FString threadName = "FReadFileInPartsSocketClientThread" + FGuid::NewGuid().ToString();
thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Normal);
}
virtual uint32 Run() override {
FArchive* reader = IFileManager::Get().CreateFileReader(*cleanDir);
if (reader == nullptr || reader->TotalSize() == 0) {
AsyncTask(ENamedThreads::GameThread, []() {
USocketClientBPLibrary::getSocketClientTarget()->onreadBytesFromFileInPartsEventDelegate.Broadcast(0, 0, true, TArray<uint8>());
});
if (reader != nullptr) {
reader->Close();
}
delete reader;
return 0;
}
if (delayBetweenReadsInSeconds <= 0) {
delayBetweenReadsInSeconds = 0.0001f;
}
int64 fileSize = reader->TotalSize();
int64 readSize = 0;
int64 lastPosition = 0;
TArray<uint8> buffer;
if (bufferSize > fileSize) {
bufferSize = fileSize;
}
while (run && lastPosition < fileSize) {
if ((lastPosition + bufferSize) > fileSize) {
bufferSize = fileSize - lastPosition;
}
//buffer.Reset(bufferSize);
buffer.Empty();
buffer.AddUninitialized(bufferSize);
reader->Serialize(buffer.GetData(), buffer.Num());
lastPosition += buffer.Num();
//UE_LOG(LogTemp, Warning, TEXT("xxxxx READ: %i"), buffer.Num());
AsyncTask(ENamedThreads::GameThread, [fileSize, lastPosition, buffer]() {
USocketClientBPLibrary::getSocketClientTarget()->onreadBytesFromFileInPartsEventDelegate.Broadcast(fileSize, lastPosition, false, buffer);
});
FPlatformProcess::Sleep(delayBetweenReadsInSeconds);
}
AsyncTask(ENamedThreads::GameThread, [fileSize, lastPosition]() {
USocketClientBPLibrary::getSocketClientTarget()->onreadBytesFromFileInPartsEventDelegate.Broadcast(fileSize, lastPosition, true, TArray<uint8>());
});
UFileFunctionsSocketClient::getFileFunctionsSocketClientTarget()->cleanReadBytesFromFileInParts(cleanDir);
//buffer.Empty();
if (reader != nullptr) {
reader->Close();
}
delete reader;
thread = nullptr;
return 0;
}
void stopThread() {
run = false;
}
void setDelayBetweenReadsInSeconds(float d) {
delayBetweenReadsInSeconds = d;
if (delayBetweenReadsInSeconds <= 0) {
delayBetweenReadsInSeconds = 0.001f;
}
}
protected:
bool run = true;
FString cleanDir;
int32 bufferSize;
float delayBetweenReadsInSeconds;
//USocketClientBPLibrary* mainLib = USocketClientBPLibrary::getSocketClientTarget();
FRunnableThread* thread = nullptr;
};
@@ -0,0 +1,179 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Sockets.h"
#include "SocketSubsystem.h"
#include "Interfaces/IPv4/IPv4Endpoint.h"
#include "Common/UdpSocketReceiver.h"
#include "Common/UdpSocketBuilder.h"
#include "GameFramework/PlayerController.h"
#include "Engine/LocalPlayer.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "Async/Async.h"
#include "HAL/PlatformFileManager.h"
#include "HAL/FileManager.h"
#include "Containers/Queue.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Misc/Base64.h"
#include "Misc/SecureHash.h"
#include "Misc/AES.h"
#include "Modules/ModuleManager.h"
#include "Runtime/Launch/Resources/Version.h"
#if ENGINE_MAJOR_VERSION == 5 & ENGINE_MINOR_VERSION >= 2
#include "IPAddressAsyncResolve.h"
#endif
#include "SocketClient.generated.h"
class FSocketClientTCPReceiveDataThread;
class FSocketClientTCPSendDataThead;
class FSocketClientTCPFileHandlerThread;
class FUdpSocketReceiver;
class FSocketClientUDPReceiveDataThread;
class FSocketClientUDPSendDataThead;
class UTCPConnectAsyncNode;
class UUDPInitAsyncNode;
USTRUCT(BlueprintType)
struct FFileFunctionsSocketClientOpenFile
{
GENERATED_USTRUCT_BODY()
FArchive* writer = nullptr;
};
USTRUCT()
struct FSocketClientPluginSession
{
GENERATED_USTRUCT_BODY()
int64 addToCleanerTime = 0;
FString clientID = FString();
FSocket* socket = nullptr;
FSocketClientTCPSendDataThead* tcpSendThread = nullptr;
FSocketClientTCPReceiveDataThread* tcpRecieverThread = nullptr;
FSocketClientTCPFileHandlerThread* tcpFileHandlerThread = nullptr;
FUdpSocketReceiver* udpSocketReceiver = nullptr;
FSocketClientUDPSendDataThead* udpSendDataThead = nullptr;
FSocketClientUDPReceiveDataThread* udpReceiveDataThread = nullptr;
};
UENUM(BlueprintType)
enum class EFileFunctionsSocketClientDirectoryType : uint8
{
E_gd UMETA(DisplayName = "Game directory"),
E_ad UMETA(DisplayName = "Absolute directory")
};
UENUM(BlueprintType)
enum class EFileFunctionsSocketClientEncodingOptions : uint8
{
E_AutoDetect UMETA(DisplayName = "AutoDetect"),
E_ForceAnsi UMETA(DisplayName = "ForceAnsi"),
E_ForceUnicode UMETA(DisplayName = "ForceUnicode"),
E_ForceUTF8 UMETA(DisplayName = "ForceUTF8"),
E_ForceUTF8WithoutBOM UMETA(DisplayName = "ForceUTF8WithoutBOM")
};
UENUM(BlueprintType)
enum class EReceiveFilterClient : uint8
{
E_SAB UMETA(DisplayName = "Message And Bytes"),
E_S UMETA(DisplayName = "Message"),
E_B UMETA(DisplayName = "Bytes")
};
UENUM(BlueprintType)
enum class ESocketPlatformClient : uint8
{
E_SSC_SYSTEM UMETA(DisplayName = "System"),
E_SSC_DEFAULT UMETA(DisplayName = "Auto"),
E_SSC_WINDOWS UMETA(DisplayName = "WINDOWS"),
E_SSC_MAC UMETA(DisplayName = "MAC"),
E_SSC_IOS UMETA(DisplayName = "IOS"),
E_SSC_UNIX UMETA(DisplayName = "UNIX"),
E_SSC_ANDROID UMETA(DisplayName = "ANDROID"),
E_SSC_PS4 UMETA(DisplayName = "PS4"),
E_SSC_XBOXONE UMETA(DisplayName = "XBOXONE"),
E_SSC_HTML5 UMETA(DisplayName = "HTML5"),
E_SSC_SWITCH UMETA(DisplayName = "SWITCH")
};
UENUM(BlueprintType)
enum class ESocketClientIPType : uint8
{
E_ipv4 UMETA(DisplayName = "IPv4"),
E_ipv6 UMETA(DisplayName = "IPv6")
};
UENUM(BlueprintType)
enum class ESocketClientTCPSeparator : uint8
{
E_None UMETA(DisplayName = "None"),
E_ByteSeparator UMETA(DisplayName = "Separate via one Byte"),
E_StringSeparator UMETA(DisplayName = "Separate via String"),
E_LengthSeparator UMETA(DisplayName = "Separate by Length")
};
#ifndef __FileFunctionsSocketClient
#define __FileFunctionsSocketClient
#include "FileFunctionsSocketClient.h"
#endif
#ifndef __SocketClientCleanerThread
#define __SocketClientCleanerThread
#include "SocketClientCleanerThread.h"
#endif
#ifndef __SocketClientAsyncNodes
#define __SocketClientAsyncNodes
#include "SocketClientAsyncNodes.h"
#endif
#ifndef __SocketClientBPLibrary
#define __SocketClientBPLibrary
#include "SocketClientBPLibrary.h"
#endif
#ifndef __SocketClientTCP
#define __SocketClientTCP
#include "SocketClientTCP.h"
#endif
#ifndef __SocketClientUDP
#define __SocketClientUDP
#include "SocketClientUDP.h"
#endif
class FSocketClientModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
static bool isShuttingDown;
};
@@ -0,0 +1,119 @@
// Copyright 2022 David Romanski(Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
#ifndef __SocketClientBPLibrary
#define __SocketClientBPLibrary
#include "SocketClientBPLibrary.h"
#endif
#include "SocketClientAsyncNodes.generated.h"
/*--- TCP -------------------------------------------------------------------------------------------------------------*/
UCLASS()
class SOCKETCLIENT_API UTCPConnectAsyncNode : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FTCPConnectDelegate, const FString, connenctionInfo, const FString, clientConnectionID,
const FString, messageFromServer, const TArray<uint8>&, byteArrayFromServer);
UPROPERTY(BlueprintAssignable)
FTCPConnectDelegate OnConnect;
UPROPERTY(BlueprintAssignable)
FTCPConnectDelegate OnDisconnect;
UPROPERTY(BlueprintAssignable)
FTCPConnectDelegate OnServerMessage;
/**
* Connect to a TCP Server
* @param domainOrIP IP or Domain of your server
* @param ipType
* @param port
* @param receiveFilter This allows you to decide which data type you want to receive. If you receive files it makes no sense to convert them into a string.
* @param messageWrapping It may be that data packets are split or merged when transmitted over TCP in order to optimize the transmission. For example, if you send "Hello" two times in a row very quickly, it can happen that "HelloHa" and "llo" arrive. To counteract this circumstance there are options to separate the data packets.
* @param optionalCustomConnectionID Instead of an automatically generated ConnectionID you can use your own ID with this parameter.
* @param disableNaglesAlgorithm Don't change it if you don't know what this option is for! With this you can disable the TCP Nagle's algorithm to send (in LAN) very small data packets faster.
**/
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP", meta = (BlueprintInternalUseOnly = "true", AdvancedDisplay = 6))
static UTCPConnectAsyncNode* socketClientTCPConnectionAsyncNode(FString domainOrIP, ESocketClientIPType ipType, int32 port, EReceiveFilterClient receiveFilters,
ESocketClientTCPSeparator messageWrapping, FString optionalCustomConnectionID, bool disableNaglesAlgorithm = false);
virtual void Activate() override;
void triggerConnectionEvent(bool success, FString clientConnectionID, FString serverMessage);
void triggerMessageEvent(TArray<uint8> byteDataArray, FString clientConnectionID, FString serverMessage);
private:
UTCPConnectAsyncNode* instance = nullptr;
FString domainOrIP = "0.0.0.0";
ESocketClientIPType ipType = ESocketClientIPType::E_ipv4;
int32 port = 9999;
EReceiveFilterClient receiveFilters = EReceiveFilterClient::E_SAB;
ESocketClientTCPSeparator messageWrapping = ESocketClientTCPSeparator::E_None;
FString optionalCustomConnectionID = FString();
FString connectionID = FString();
bool disableNaglesAlgorithm = false;
};
/*--- UDP -------------------------------------------------------------------------------------------------------------*/
UCLASS()
class SOCKETCLIENT_API UUDPInitAsyncNode : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FUDPInitDelegate, const FString, initializationInfo, const FString, clientConnectionID,
const FString, peerIP,const int32, peerPort,
const FString, messageFromServer, const TArray<uint8>&, byteArrayFromServer);
UPROPERTY(BlueprintAssignable)
FUDPInitDelegate OnSuccess;
UPROPERTY(BlueprintAssignable)
FUDPInitDelegate OnFail;
UPROPERTY(BlueprintAssignable)
FUDPInitDelegate OnServerMessage;
/**
*Opens a connection on specific ip and port and listen on it.
*@param DomainOrIP IP or Domain to listen on. 0.0.0.0 means that data can be received on all local IPs.
*@param port port to listen on
*@param receiveFilter This allows you to decide which data type you want to receive. If you receive files it makes no sense to convert them into a string.
*@param maxPacketSize sets the maximum UDP packet size. More than 65507 is not possible.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|UDP", meta = (BlueprintInternalUseOnly = "true", AdvancedDisplay = 5))
static UUDPInitAsyncNode* socketClientInitUDPReceiverAsyncNode(FString domainOrIP = "0.0.0.0",
ESocketClientIPType ipType = ESocketClientIPType::E_ipv4, int32 port = 8888,
EReceiveFilterClient receiveFilter = EReceiveFilterClient::E_SAB, int32 maxPacketSize = 65507);
virtual void Activate() override;
void triggerInitEvent(bool success, FString clientConnectionID, FString serverMessage);
void triggerMessageEvent(TArray<uint8> byteDataArray, FString clientConnectionID, FString serverMessage, FString peerIP, int32 peerPort);
private:
UUDPInitAsyncNode* instance = nullptr;
FString domainOrIP = "0.0.0.0";
ESocketClientIPType ipType = ESocketClientIPType::E_ipv4;
int32 port = 8888;
EReceiveFilterClient receiveFilter = EReceiveFilterClient::E_SAB;
int32 maxPacketSize = 65507;
FString connectionID = FString();
};
@@ -0,0 +1,374 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
#include "SocketClientBPLibrary.generated.h"
class FSocketClientCleanerThread;
UENUM(BlueprintType)
enum class ESocketClientSystem : uint8
{
Android,
IOS,
Windows,
Linux,
Mac
};
UENUM(BlueprintType)
enum class ESocketClientDirectoryType : uint8
{
E_gd UMETA(DisplayName = "Game directory"),
E_ad UMETA(DisplayName = "Absolute directory")
};
class USocketClientTCPClient;
class USocketClientUDP;
class FSocketClientUDPReceiveDataThread;
//class FReadFileInPartsThread;
UCLASS()
class SOCKETCLIENT_API USocketClientBPLibrary : public UObject
{
GENERATED_UCLASS_BODY()
public:
~USocketClientBPLibrary();
//Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FsocketClientTCPConnectionEventDelegate, bool, success, FString, message, FString, clientConnectionID);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FreceiveTCPMessageEventDelegate, FString, message, const TArray<uint8>&, byteArray, FString, clientConnectionID);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FsocketClientUDPConnectionEventDelegate, bool, success, FString, message, FString, clientConnectionID);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FreceiveUDPMessageEventDelegate, FString, message, const TArray<uint8>&, byteArray, FString, IP_FromSender, int32, portFromSender, FString, clientConnectionID);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FreadBytesFromFileInPartsEventDelegate, int64, fileSize, int64, position,bool, end, const TArray<uint8>&, byteArray);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FtransferFileOverTCPProgressEventDelegate, FString, clientConnectionID, FString, filePath, float, percent, float, mbit, int64, bytesTransferred, int64, fileSize);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FfileTransferOverTCPInfoEventDelegate, FString, message, FString, clientConnectionID, FString, filePath, bool, success);
UFUNCTION()
void socketClientTCPConnectionEventDelegate(const bool success, const FString message, const FString clientConnectionID);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|TCP|Events|ConnectionInfo")
FsocketClientTCPConnectionEventDelegate onsocketClientTCPConnectionEventDelegate;
UFUNCTION()
void receiveTCPMessageEventDelegate(const FString message, const TArray<uint8>& byteArray, const FString clientConnectionID);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|TCP|Events|ReceiveMessage")
FreceiveTCPMessageEventDelegate onreceiveTCPMessageEventDelegate;
UFUNCTION()
void socketClientUDPConnectionEventDelegate(const bool success, const FString message, const FString clientConnectionID);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|UDP|Events|ConnectionInfo")
FsocketClientUDPConnectionEventDelegate onsocketClientUDPConnectionEventDelegate;
UFUNCTION()
void receiveUDPMessageEventDelegate(const FString message, const TArray<uint8>& byteArray, const FString IP, const int32 port,const FString clientConnectionID);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|UDP|Events|ReceiveMessage")
FreceiveUDPMessageEventDelegate onreceiveUDPMessageEventDelegate;
UFUNCTION()
void readBytesFromFileInPartsEventDelegate(const int64 fileSize, const int64 position,const bool end, const TArray<uint8>& byteArray);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|SpecialFunctions|File|Events|ReadBytesFromFileInPartsAsync")
FreadBytesFromFileInPartsEventDelegate onreadBytesFromFileInPartsEventDelegate;
UFUNCTION()
void transferFileOverTCPProgressEventDelegate(const FString clientConnectionID,const FString filePath, const float percent, const float mbit, const int64 bytesTransferred, const int64 fileSize);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|TCP|Events|File|transferFileOverTCPProgress")
FtransferFileOverTCPProgressEventDelegate ontransferFileOverTCPProgressEventDelegate;
UFUNCTION()
void fileTransferOverTCPInfoEventDelegate(const FString message, const FString clientConnectionID, const FString filePath, const bool success);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|TCP|Events|File|FileTransferOverTCPInfo")
FfileTransferOverTCPInfoEventDelegate onfileTransferOverTCPInfoEventDelegate;
/**
* Get an instance of this library. This allows non-static functions to be called.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient")
static USocketClientBPLibrary* getSocketClientTarget();
static USocketClientBPLibrary* socketClientBPLibrary;
/**
* Connect to a TCP Server
* @param domainOrIP IP or Domain of your server
* @param ipType
* @param port
* @param receiveFilter This allows you to decide which data type you want to receive. If you receive files it makes no sense to convert them into a string.
* @param messageSeparator It may be that data packets are split or merged when transmitted over TCP in order to optimize the transmission. For example, if you send "Hello" two times in a row very quickly, it can happen that "HelloHa" and "llo" arrive. To counteract this circumstance there are options to separate the data packets.
* @param optionalCustomConnectionID Instead of an automatically generated ConnectionID you can use your own ID with this parameter.
* @param disableNaglesAlgorithm Don't change it if you don't know what this option is for! With this you can disable the TCP Nagle's algorithm to send (in LAN) very small data packets faster.
**/
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP", meta = (AdvancedDisplay = 6))
static void connectSocketClientTCP(FString domainOrIP, ESocketClientIPType ipType, int32 port, EReceiveFilterClient receiveFilters,
ESocketClientTCPSeparator messageSeparator, FString optionalCustomConnectionID, FString& connectionID, bool disableNaglesAlgorithm = false);
void connectSocketClientTCPNonStatic(FString domainOrIP, ESocketClientIPType ipType, int32 port, EReceiveFilterClient receiveFilters,
ESocketClientTCPSeparator messageSeparator,FString optionalCustomConnectionID, FString &connectionID, UTCPConnectAsyncNode* tcpConnectAsyncNode, bool disableNaglesAlgorithm = false);
/**
* Sends a string or byte array to the server.
*@param connectionID The ID to an existing connection.
*@param message String to send
*@param byteArray bytes to send
*@param addLineBreak add a line break at the end
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP", meta = (AutoCreateRefTerm = "byteArray"))
static void socketClientSendTCP(FString connectionID, FString message, TArray<uint8> byteArray, bool addLineBreak = true);
void socketClientSendTCPNonStatic(FString connectionID, FString message, TArray<uint8> byteArray, bool addLineBreak = true);
/**
* Sends files as a kind of stream. This allows extremely large files to be sent since they do not have to be loaded into RAM beforehand. At the end of the transfer a MD5 checksum is created and compared between client and server to exclude errors during the transfer.
*@param connectionID The ID to an existing connection.
*@param domainOrIP IP or Domain of your server
*@param directoryType Absolute or relative directory. Absolute directory starts at the disk (e.g. Windows C:\). A relative directory starts one level higher than the Content directory in the project or game.
*@param filePath Directory including the file to be sent.
*@param token The token is a unique ID that the client and server must know. The server knows from the token in which directory the file should be saved.
*@param Aes256bitKey The AES key must consist of 32 ASCII characters. The communication between client and server is encrypted via AES in 256bit. Therefore a key must be entered.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP")
static void socketClientSendFileOverTCP(FString& connectionID, FString domainOrIP, ESocketClientIPType ipType, int32 port, EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FString token, FString Aes256bitKey);
void socketClientSendFileOverTCPNonStatic(FString& connectionID, FString domainOrIP, ESocketClientIPType ipType, int32 port, EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FString token, FString Aes256bitKey);
/**
* Lets the server send a file to this client as a kind of stream. This allows extremely large files to be sent since they do not have to be loaded into RAM beforehand. At the end of the transfer a MD5 checksum is created and compared between client and server to exclude errors during the transfer.
*@param connectionID The ID to an existing connection.
*@param domainOrIP IP or Domain of your server
*@param directoryType Absolute or relative directory. Absolute directory starts at the disk (e.g. Windows C:\). A relative directory starts one level higher than the Content directory in the project or game.
*@param filePath Directory including the file to be sent.
*@param token The token is a unique ID that the client and server must know. The server recognizes by the toke what kind of file should be sent.
*@param Aes256bitKey The AES key must consist of 32 ASCII characters. The communication between client and server is encrypted via AES in 256bit. Therefore a key must be entered.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP")
static void socketClientRequestFileOverTCP(FString& connectionID, FString domainOrIP, ESocketClientIPType ipType, int32 port, EFileFunctionsSocketClientDirectoryType directoryType, FString downloadDirectory, bool resume, FString token, FString Aes256bitKey);
void socketClientRequestFileOverTCPNonStatic(FString& connectionID, FString domainOrIP, ESocketClientIPType ipType, int32 port, EFileFunctionsSocketClientDirectoryType directoryType, FString downloadDirectory, bool resume, FString token, FString Aes256bitKey);
/**
* Terminates an existing connection.
*@param connectionID The ID to an existing connection.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP")
static void closeSocketClientConnectionTCP(FString connectionID);
void closeSocketClientConnectionTCPNonStatic(FString connectionID);
/**
* Terminates all connections.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP")
static void closeAllSocketClientConnectionsTCP();
void closeAllSocketClientConnectionsTCPNonStatic();
/**
* Useful if you want to attach events to a certain connection.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP")
static void getTCPConnectionByConnectionID(FString connectionID, bool &found, USocketClientTCPClient* &connection);
void getTCPConnectionByConnectionIDNonStatic(FString connectionID, bool& found, USocketClientTCPClient*& connection);
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP")
static bool isTCPConnected(FString connectionID);
bool isTCPConnectedNonStatic(FString connectionID);
/**
*Opens a connection on specific ip and port and listen on it.
*@param DomainOrIP IP or Domain to listen on. 0.0.0.0 means that data can be received on all local IPs.
*@param port port to listen on
*@param receiveFilter This allows you to decide which data type you want to receive. If you receive files it makes no sense to convert them into a string.
*@param maxPacketSize sets the maximum UDP packet size. More than 65507 is not possible.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|UDP")
static void socketClientInitUDPReceiver(FString& connectionID, FString domainOrIP = "0.0.0.0", ESocketClientIPType ipType = ESocketClientIPType::E_ipv4, int32 port = 8888, EReceiveFilterClient receiveFilter = EReceiveFilterClient::E_SAB, int32 maxPacketSize = 65507);
void socketClientInitUDPReceiverNonStatic(FString& connectionID, UUDPInitAsyncNode* udpInitAsyncNode, FString domainOrIP = "0.0.0.0", ESocketClientIPType ipType = ESocketClientIPType::E_ipv4, int32 port = 8888, EReceiveFilterClient receiveFilter = EReceiveFilterClient::E_SAB, int32 maxPacketSize = 65507);
/**
* A ConnectionID must be created first with "socketClientInitUDPReceiver". Messages and bytes can be sent to different hosts with the same ConnectionID.
*@param DomainOrIP target IP or Domain
*@param port target port
*@param message String to send
*@param addLineBreak add a line break at the end
*@param uniqueID is optional and required when multiple connections to the same server (same ip and port) shall be established. You can use getUniquePlayerID
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|UDP", meta = (AutoCreateRefTerm = "byteArray"))
static void socketClientSendUDP(FString domainOrIP, ESocketClientIPType ipType, int32 port, FString message, TArray<uint8> byteArray, bool addLineBreak = true, FString connectionID = "");
void socketClientSendUDPNonStatic(FString domainOrIP, ESocketClientIPType ipType, int32 port, FString message, TArray<uint8> byteArray, bool addLineBreak = true, FString connectionID = "");
/**
* Terminates an existing connection.
*@param connectionID The ID to an existing connection.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|UDP")
static void closeSocketClientConnectionUDP(FString connectionID);
void closeSocketClientConnectionUDPNonStatic(FString connectionID);
/**
* Useful if you want to attach events to a certain connection.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|UDP")
static void getUDPInitializationByConnectionID(FString connectionID, bool& found, USocketClientUDP*& connection);
void getUDPInitializationByConnectionIDNonStatic(FString connectionID, bool& found, USocketClientUDP*& connection);
UFUNCTION(BlueprintCallable, Category = "SocketClient|UDP")
static bool isUDPInitialized(FString connectionID);
bool isUDPInitializedNonStatic(FString connectionID);
/**
*Trying to determine the local IP. It uses a function in the engine that does not work on all devices. On Windows and Linux it seems to work very well. Very bad on Android. 0.0.0.0 will be returned if it doesn't work.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions")
static FString getLocalIP();
/**
*UE4 uses different socket connections. When Steam is active, Steam Sockets are used for all connections. This leads to problems if you want to use Steam but not Steam Sockets. Therefore you can change the sockets to "System".
*@param ESocketPlatformServer System = Windows on Windows, Mac = Mac on Mac ect.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions")
static void changeSocketPlatform(ESocketPlatformClient platform);
/**
*The cleaner thread is a thread that runs endlessly and deletes data remnants from closed/broken connections from RAM.
*@param showLogs Writes to the logs when data remnants are deleted.
*@param minLiveTimeInSeconds When a connection is closed it is passed to the cleaner thread. The thread ignores the connection for "minLiveTimeInSeconds" until it clears the data. This is necessary because sometimes connections need some time to be closed completely.
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions")
static void changeCleanerThreadSettingsOnClient(bool showLogs, int32 minLiveTimeInSeconds = 10);
/**
* Returns which system you are currently use. (Windows, OSX, IOS ...)
*/
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions", Meta = (ExpandEnumAsExecs = "system"))
static void getSystemType(ESocketClientSystem& system);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions")
static int32 getUniquePlayerID(APlayerController* playerController = nullptr);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions")
static FString getRandomID();
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Hex")
static TArray<uint8> parseHexToBytes(FString hex);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Hex")
static FString parseHexToString(FString hex);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Hex")
static FString parseBytesToHex(TArray<uint8> bytes);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Hex")
static TArray<uint8> parseHexToBytesPure(FString hex);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Hex")
static FString parseHexToStringPure(FString hex);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Hex")
static FString parseBytesToHexPure(TArray<uint8> bytes);
//number stuff
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Number")
static void parseBytesToFloat(TArray<uint8> bytes, float& value);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Number")
static void parseBytesToInteger(TArray<uint8> bytes, int32& value);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Number")
static void parseBytesToInteger64(TArray<uint8> bytes, int64& value);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number")
static void parseBytesToFloatPure(TArray<uint8> bytes, float& value);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number")
static void parseBytesToIntegerPure(TArray<uint8> bytes, int32& value);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number")
static void parseBytesToInteger64Pure(TArray<uint8> bytes, int64& value);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Number")
static void parseBytesToFloatEndian(TArray<uint8> bytes, float& littleEndian, float& bigEndian);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Number")
static void parseBytesToIntegerEndian(TArray<uint8> bytes, int32& littleEndian, int32& bigEndian);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Number")
static void parseBytesToInteger64Endian(TArray<uint8> bytes, int64& littleEndian, int64& bigEndian);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray"))
static void parseFloatToBytes(TArray<uint8>& byteArray, float value, bool switchByteOrder = false);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray"))
static void parseIntegerToBytes(TArray<uint8>& byteArray, int32 value, bool switchByteOrder = false);
UFUNCTION(BlueprintCallable, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray"))
static void parseInteger64ToBytes(TArray<uint8>& byteArray, int64 value, bool switchByteOrder = false);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray"))
static void parseFloatToBytesPure(TArray<uint8>& byteArray, float value, bool switchByteOrder = false);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray"))
static void parseIntegerToBytesPure(TArray<uint8>& byteArray, int32 value, bool switchByteOrder = false);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray"))
static void parseInteger64ToBytesPure(TArray<uint8>& byteArray, int64 value, bool switchByteOrder = false);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "value"))
static void parseBytesToFloatArrayPure(TArray<float>& value, TArray<uint8> bytes);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "value"))
static void parseBytesToIntegerArrayPure(TArray<int32>& value, TArray<uint8> bytes);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "value"))
static void parseBytesToInteger64ArrayPure(TArray<int64>& value, TArray<uint8> bytes);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray"))
static void parseFloatArrayToBytesPure(TArray<uint8>& byteArray, TArray<float> value);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray"))
static void parseIntegerArrayToBytesPure(TArray<uint8>& byteArray, TArray<int32> value);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketClient|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray"))
static void parseInteger64ArrayToBytesPure(TArray<uint8>& byteArray, TArray<int64> value);
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP")
static void changeTCPSeparatorStringOnClient(FString separator = "(~{");
void changeTCPSeparatorStringOnClientNonStatic(FString separator);
UFUNCTION(BlueprintCallable, Category = "SocketClient|TCP")
static void changeTCPSeparatorByteOnClient(uint8 separator = 0x00);
void changeTCPSeparatorByteOnClientNonStatic(uint8 separator);
void getTcpSeparator(uint8& byteSeparator, FString& stringSeparator);
static ISocketSubsystem* getSocketSubSystem();
FString resolveDomain(FString domain, ESocketClientIPType ipType);
//ue4 domain resolve does not work with steam. this is my own dns client
class UDNSClientSocketClient* dnsClient = nullptr;
TMap<FString, FString> domainCache;
void cleanConnection(FSocketClientPluginSession& session);
private:
ESocketPlatformClient systemSocketPlatform;
TMap<FString, USocketClientTCPClient*> tcpClients;
TMap<FString, USocketClientUDP*> udpClients;
//TMap<FString, FReadFileInPartsThread*> readFileInPartsThreads;
int64 lastErrorMessageTime = 0;
FString tcpStringSeparator = "(~{";
uint8 tcpByteSeparator = 0x00;
FSocketClientCleanerThread* socketClientCleanerThread = nullptr;
};
@@ -0,0 +1,26 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
class SOCKETCLIENT_API FSocketClientCleanerThread : public FRunnable {
public:
FSocketClientCleanerThread();
virtual uint32 Run() override;
void addSession(FSocketClientPluginSession& session);
void changeSettings(bool showLogs, int32 minLiveTimeInSeconds);
private:
bool showLogs = false;
int32 minLiveTimeInSeconds = 10;
FRunnableThread* thread = nullptr;
TQueue<FSocketClientPluginSession> sessionQueue;
};
@@ -0,0 +1,106 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
#include "SocketClientTCPReceiveDataThread.h"
#include "SocketClientTCPSendDataThead.h"
#include "SocketClientTCPFileHandlerThread.h"
#include "SocketClientTCP.generated.h"
class USocketServerBPLibrary;
UCLASS(Blueprintable, BlueprintType)
class SOCKETCLIENT_API USocketClientTCPClient : public UObject
{
GENERATED_UCLASS_BODY()
public:
//Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FsocketClientTCPConnectionEventDelegate, bool, success, FString, message, FString, clientConnectionID);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FreceiveTCPMessageEventDelegate, FString, message, const TArray<uint8>&, byteArray, FString, clientConnectionID);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FtransferFileOverTCPProgressEventDelegate, FString, clientConnectionID, FString, filePath, float, percent, float, mbit, int64, bytesTransferred, int64, fileSize);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FfileTransferOverTCPInfoEventDelegate, FString, message, FString, clientConnectionID, FString, filePath, bool, success);
UFUNCTION()
void socketClientTCPConnectionEventDelegate(const bool success, const FString message, const FString clientConnectionID);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|TCP|Events|ConnectionInfo")
FsocketClientTCPConnectionEventDelegate onsocketClientTCPConnectionEventDelegate;
UFUNCTION()
void receiveTCPMessageEventDelegate(const FString message, const TArray<uint8>& byteArray, const FString clientConnectionID);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|TCP|Events|ReceiveMessage")
FreceiveTCPMessageEventDelegate onreceiveTCPMessageEventDelegate;
UFUNCTION()
void transferFileOverTCPProgressEventDelegate(const FString clientConnectionID, const FString filePath, const float percent, const float mbit, const int64 bytesTransferred, const int64 fileSize);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|TCP|Events|File|transferFileOverTCPProgress")
FtransferFileOverTCPProgressEventDelegate ontransferFileOverTCPProgressEventDelegate;
UFUNCTION()
void fileTransferOverTCPInfoEventDelegate(const FString message, const FString clientConnectionID, const FString filePath, const bool success);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|TCP|Events|File|FileTransferOverTCPInfo")
FfileTransferOverTCPInfoEventDelegate onfileTransferOverTCPInfoEventDelegate;
UFUNCTION()
void connectionEvent(bool success, FString message, FString clientConnectionID);
void connect(USocketClientBPLibrary* mainLib, FString domainOrIP, ESocketClientIPType ipType, int32 port, EReceiveFilterClient receiveFilter,
ESocketClientTCPSeparator messageWrapping, FString connectionID, UTCPConnectAsyncNode* tcpConnectAsyncNode, bool noPacketDelay = false, bool noPacketBlocking = false);
void sendMessage(FString message, TArray<uint8> byteArray);
void sendFile(USocketClientBPLibrary* mainLib, FString connectionID, FString domainOrIP, ESocketClientIPType ipType, int32 port, EFileFunctionsSocketClientDirectoryType directoryType, FString filePath, FString token, FString Aes256bitKey);
void requestFile(USocketClientBPLibrary* mainLib, FString connectionID, FString domainOrIP, ESocketClientIPType ipType, int32 port, EFileFunctionsSocketClientDirectoryType directoryType, FString filePath,bool resume, FString token, FString Aes256bitKey);
void closeConnection();
bool isRun();
void setRun(bool runP);
FString getConnectionID();
FString getAesKey();
FString getFileToken();
FString getFilePath();
void setSocket(FSocket* socket);
FSocket* getSocket();
USocketClientBPLibrary* getMainLib();
void createSendThread();
//void createFileSendThread(int64 startPosition);
FString encryptMessage(FString message);
FString decryptMessage(FString message);
void readDataLength(TArray<uint8>& byteDataArray, int32& byteLenght);
bool isSendFile();
bool isReceiveFile();
bool hasResume();
bool isConnected();
void deleteFile(FString filePathP);
void getMD5FromFileAbsolutePath(FString filePath, bool& success, FString& MD5);
int64 fileSize(FString filePath);
FString int64ToString(int64 num);
void getTcpSeparator(FString& stringSeparator, uint8& byteSeparator, ESocketClientTCPSeparator& messageWrapping);
UTCPConnectAsyncNode* tcpConnectAsyncNode = nullptr;
private:
bool run = false;
bool connected = false;
bool resume = false;
int32 sendOrReceive = -1; //0 == send, 1 == receive;
FString connectionID = FString();
FString aesKey = FString();
FString fileToken = FString();
FString filePath = FString();
FSocket* socket = nullptr;
FSocketClientTCPReceiveDataThread* tcpReceiveDataThread = nullptr;
FSocketClientTCPSendDataThead* tcpSendThread = nullptr;
FSocketClientTCPFileHandlerThread* tcpFileConnectionThread = nullptr;
//FSocketClientTCPSendFileThread* fileSendThread = nullptr;
USocketClientBPLibrary* mainLib = nullptr;
ESocketClientTCPSeparator messageWrapping;
FString tcpStringSeparator = "(~{";
uint8 tcpByteSeparator = 0x00;
};
@@ -0,0 +1,46 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
class SOCKETCLIENT_API FSocketClientTCPFileHandlerThread : public FRunnable {
public:
FSocketClientTCPFileHandlerThread(USocketClientBPLibrary* socketClientP, FString clientConnectionIDP, FString ipOrDomainP, ESocketClientIPType ipTypeP,
int32 portP, USocketClientTCPClient* tcpClientP);
~FSocketClientTCPFileHandlerThread();
virtual uint32 Run() override;
void doRequestFileFromServer(FSocket* socket);
void doSendFileToServer(FSocket* socket);
void sendMessageToServer(FString message, FSocket* socket);
void triggerFileTransferOverTCPInfoEvent(FString messageP, FString clientConnectionIDP, FString filePathP, bool successP,
USocketClientBPLibrary* socketClientP, USocketClientTCPClient* tcpClientP);
void triggerTransferFileEvent(FString clientConnectionIDP, FString filePathP, USocketClientBPLibrary* socketClientP,
USocketClientTCPClient* tcpClientP, float percentP, float mbitP, int64 transferredP, int64 fileSizeP);
void sendEndMessage(FString fullFilePathP, FString tokenP, FString md5ServerP, FString clientConnectionIDP, FSocket* clientSocketP,
USocketClientBPLibrary* socketClientP, USocketClientTCPClient* tcpClientP);
FString readMessageFromServer(FSocket* socket);
private:
USocketClientBPLibrary* socketClient = nullptr;
//USocketClientBPLibrary* oldClient;
FString clientConnectionID;
FString originalIP;
FString ipOrDomain;
ESocketClientIPType ipType;
int32 port;
USocketClientTCPClient* tcpClient = nullptr;
FRunnableThread* thread = nullptr;
double waitForRead = 30;
};
@@ -0,0 +1,41 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
class SOCKETCLIENT_API FSocketClientTCPReceiveDataThread : public FRunnable {
public:
FSocketClientTCPReceiveDataThread(USocketClientBPLibrary* socketClientBPLibraryP, FString clientConnectionIDP, EReceiveFilterClient receiveFilterP,
FString ipOrDomainP, ESocketClientIPType ipTypeP,int32 portP, USocketClientTCPClient* tcpClientP, bool noPacketDelayP, bool noPacketBlockingP);
~FSocketClientTCPReceiveDataThread();
virtual uint32 Run() override;
void triggerConnectionEvent(bool succsess, FString clientConnectionIDGlobal, FString serverMessage, USocketClientTCPClient* tcpClientGlobal,
USocketClientBPLibrary* socketClientGlobal);
void triggerMessageEvent(TArray<uint8>& byteDataArray, FString& clientConnectionIDGlobal, USocketClientTCPClient* tcpClientGlobal,
USocketClientBPLibrary* socketClientGlobal, bool addNullTerminator = true);
private:
USocketClientBPLibrary* socketClientBPLibrary = nullptr;
//USocketClientBPLibrary* oldClient;
FString clientConnectionID;
FString originalIP;
EReceiveFilterClient receiveFilter;
FString ipOrDomain;
ESocketClientIPType ipType;
int32 port;
USocketClientTCPClient* tcpClient = nullptr;
bool noPacketDelay = false;
bool noPacketBlocking = false;
FRunnableThread* thread = nullptr;
};
@@ -0,0 +1,32 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
class SOCKETCLIENT_API FSocketClientTCPSendDataThead : public FRunnable {
public:
FSocketClientTCPSendDataThead(USocketClientBPLibrary* socketClientLibP, USocketClientTCPClient* tcpClientP, FString clientConnectionIDP);
~FSocketClientTCPSendDataThead();
virtual uint32 Run() override;
void sendMessage(FString messageP, TArray<uint8> byteArrayP);
void pauseThread(bool pause);
private:
TQueue<FString> messageQueue;
TQueue<TArray<uint8>> byteArrayQueue;
USocketClientBPLibrary* socketClientLib;
USocketClientTCPClient* tcpClient = nullptr;
FString clientConnectionID;
FRunnableThread* thread = nullptr;
bool run = true;
bool paused = false;
bool blah = true;
};
@@ -0,0 +1,73 @@
// Copyright 2017-2019 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
#include "SocketClientUDPSendDataThead.h"
#include "SocketClientUDPReceiveDataThread.h"
#include "SocketClientUDP.generated.h"
class USocketClientBPLibrary;
UCLASS(Blueprintable, BlueprintType)
class SOCKETCLIENT_API USocketClientUDP : public UObject
{
GENERATED_UCLASS_BODY()
public:
//Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FsocketClientUDPConnectionEventDelegate, bool, success, FString, message, FString, clientConnectionID);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FreceiveUDPMessageEventDelegate, FString, message, const TArray<uint8>&, byteArray, FString, IP_FromSender, int32, portFromSender, FString, clientConnectionID);
UFUNCTION()
void socketClientUDPConnectionEventDelegate(const bool success, const FString message, const FString clientConnectionID);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|UDP|Events|ConnectionInfo")
FsocketClientUDPConnectionEventDelegate onsocketClientUDPConnectionEventDelegate;
UFUNCTION()
void receiveUDPMessageEventDelegate(const FString message, const TArray<uint8>& byteArray, const FString IP, const int32 port, const FString clientConnectionID);
UPROPERTY(BlueprintAssignable, Category = "SocketClient|UDP|Events|ReceiveMessage")
FreceiveUDPMessageEventDelegate onreceiveUDPMessageEventDelegate;
void init(USocketClientBPLibrary* socketClientLibP, UUDPInitAsyncNode* udpInitAsyncNode, FString domain, ESocketClientIPType ipType, int32 port, EReceiveFilterClient receiveFilter, FString clientConnectionID, int32 maxPacketSize = 65507);
void sendUDPMessage(FString domainOrIP, ESocketClientIPType ipType, int32 port, FString message, TArray<uint8> byteArray);
void closeUDPConnection();
void UDPReceiver(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt);
bool isRun();
void setRun(bool runP);
FSocket* getSocket();
void setSocket(FSocket* socketP);
void setUDPSocketReceiver(FUdpSocketReceiver* udpSocketReceiver);
FString getIP();
void setIP(FString ipP);
int32 getPort();
FString getDomainOrIP();
FString getConnectionID();
void setUDPSendThread(FSocketClientUDPSendDataThead* udpSendThreadP);
int32 getMaxPacketSize();
UUDPInitAsyncNode* udpInitAsyncNode = nullptr;
private:
bool run = false;
EReceiveFilterClient receiveFilter;
FString connectionID;
FString domainOrIP;
int32 port = 0;
int32 maxPacketSize = 65507;
USocketClientBPLibrary* socketClientBPLibrary = nullptr;
FUdpSocketReceiver* udpSocketReceiver = nullptr;
FSocket* socket = nullptr;
FSocketClientUDPReceiveDataThread* UDPThread = nullptr;
FSocketClientUDPSendDataThead* UDPSendThread = nullptr;
};
@@ -0,0 +1,30 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
class SOCKETCLIENT_API FSocketClientUDPReceiveDataThread : public FRunnable {
public:
FSocketClientUDPReceiveDataThread(USocketClientUDP* udpClientP, USocketClientBPLibrary* socketClientP, FString ipP, int32 portP, ESocketClientIPType ipTypeP);
~FSocketClientUDPReceiveDataThread();
void triggerInitEvent(bool success, USocketClientUDP* udpClientP, USocketClientBPLibrary* socketClientP, FString serverMessage, FString connectionID);
virtual uint32 Run() override;
private :
USocketClientUDP* udpClient = nullptr;
USocketClientBPLibrary* socketClient = nullptr;
FRunnableThread* thread = nullptr;
FString ipGlobal;
int32 portGlobal;
ESocketClientIPType ipType = ESocketClientIPType::E_ipv4;
FSocket* socket = nullptr;
bool reuseSocket = false;
};
@@ -0,0 +1,37 @@
// Copyright 2022 David Romanski (Socke). All Rights Reserved.
#pragma once
#include "SocketClient.h"
class SOCKETCLIENT_API FSocketClientUDPSendDataThead : public FRunnable {
public:
FSocketClientUDPSendDataThead(USocketClientUDP* udpClientP, USocketClientBPLibrary* socketClientP, FString mySocketipP, int32 mySocketportP);
~FSocketClientUDPSendDataThead();
virtual uint32 Run() override;
void addData(FString messageP, TArray<uint8> byteArrayP, FString domainOrIP, int32 port, ESocketClientIPType ipType);
void pauseThread(bool pause);
void sendBytes(FSocket*& socketP, TArray<uint8>& byteArray, int32& sent, TSharedRef<FInternetAddr>& addr);
private:
USocketClientUDP* udpClient = nullptr;
USocketClientBPLibrary* socketClient = nullptr;
FString mySocketip;
int32 mySocketport = 0;
FString sendToip;
int32 sendToport = 0;
FString sendToDomainOrIP = FString();
FRunnableThread* thread = nullptr;
bool paused;
TQueue<FString> messageQueue;
TQueue<TArray<uint8>> byteArrayQueue;
int32 maxPacketSize = 65507;
bool validInternetAdress = false;
TSharedRef<FInternetAddr> internetAdress = USocketClientBPLibrary::getSocketSubSystem()->CreateInternetAddr();
int64 lastErrorMessageTime = 0;
};
@@ -0,0 +1,57 @@
// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved.
using UnrealBuildTool;
public class SocketClient : ModuleRules
{
public SocketClient(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
}
);
PrivateIncludePaths.AddRange(
new string[] {
"SocketClient/Private",
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"Networking",
"Sockets"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Networking",
"Sockets",
"Slate",
"SlateCore"
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}
+75
View File
@@ -0,0 +1,75 @@
# ---> UnrealEngine
# Visual Studio 2015 user specific files
.vs/
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
# Executables
*.exe
*.out
*.app
*.ipa
# These project files can be generated by the engine
*.xcodeproj
*.xcworkspace
*.sln
*.suo
*.opensdf
*.sdf
*.VC.db
*.VC.opendb
# Precompiled Assets
SourceArt/**/*.png
SourceArt/**/*.tga
# Binary Files
Binaries/*
Plugins/**/Binaries/*
# Builds
Build/*
# Whitelist PakBlacklist-<BuildConfiguration>.txt files
!Build/*/
Build/*/**
!Build/*/PakBlacklist*.txt
# Don't ignore icon files in Build
!Build/**/*.ico
# Built data for maps
*_BuiltData.uasset
# Configuration files generated by the Editor
Saved/*
# Compiled source files for the engine to use
Intermediate/*
Plugins/**/Intermediate/*
# Cache files for the editor to use
DerivedDataCache/*
+2 -1
View File
@@ -1,7 +1,8 @@
[/Script/EngineSettings.GameMapsSettings] [/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Engine/Maps/Templates/OpenWorld GameDefaultMap=/Game/Main.Main
EditorStartupMap=/Game/Main.Main
[/Script/WindowsTargetPlatform.WindowsTargetSettings] [/Script/WindowsTargetPlatform.WindowsTargetSettings]
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
+90
View File
@@ -2,3 +2,93 @@
[/Script/EngineSettings.GeneralProjectSettings] [/Script/EngineSettings.GeneralProjectSettings]
ProjectID=C5CBDA014AD60DA47558CEB13DA4AD3C ProjectID=C5CBDA014AD60DA47558CEB13DA4AD3C
[/Script/UnrealEd.ProjectPackagingSettings]
Build=IfProjectHasCode
BuildConfiguration=PPBC_Development
BuildTarget=
FullRebuild=False
ForDistribution=False
IncludeDebugFiles=False
BlueprintNativizationMethod=Disabled
bIncludeNativizedAssetsInProjectGeneration=False
bExcludeMonolithicEngineHeadersInNativizedCode=False
UsePakFile=False
bUseIoStore=False
bUseZenStore=False
bMakeBinaryConfig=False
bGenerateChunks=False
bGenerateNoChunks=False
bChunkHardReferencesOnly=False
bForceOneChunkPerFile=False
MaxChunkSize=0
bBuildHttpChunkInstallData=False
HttpChunkInstallDataDirectory=(Path="")
WriteBackMetadataToAssetRegistry=Disabled
bCompressed=True
PackageCompressionFormat=Oodle
bForceUseProjectCompressionFormatIgnoreHardwareOverride=False
PackageAdditionalCompressionOptions=
PackageCompressionMethod=Kraken
PackageCompressionLevel_DebugDevelopment=4
PackageCompressionLevel_TestShipping=5
PackageCompressionLevel_Distribution=7
PackageCompressionMinBytesSaved=1024
PackageCompressionMinPercentSaved=5
bPackageCompressionEnableDDC=False
PackageCompressionMinSizeToConsiderDDC=0
HttpChunkInstallDataVersion=
IncludePrerequisites=True
IncludeAppLocalPrerequisites=False
bShareMaterialShaderCode=True
bDeterministicShaderCodeOrder=False
bSharedMaterialNativeLibraries=True
ApplocalPrerequisitesDirectory=(Path="")
IncludeCrashReporter=False
InternationalizationPreset=English
-CulturesToStage=en
+CulturesToStage=en
LocalizationTargetCatchAllChunkId=0
bCookAll=False
bCookMapsOnly=False
bSkipEditorContent=False
bSkipMovies=False
-IniKeyDenylist=KeyStorePassword
-IniKeyDenylist=KeyPassword
-IniKeyDenylist=rsa.privateexp
-IniKeyDenylist=rsa.modulus
-IniKeyDenylist=rsa.publicexp
-IniKeyDenylist=aes.key
-IniKeyDenylist=SigningPublicExponent
-IniKeyDenylist=SigningModulus
-IniKeyDenylist=SigningPrivateExponent
-IniKeyDenylist=EncryptionKey
-IniKeyDenylist=DevCenterUsername
-IniKeyDenylist=DevCenterPassword
-IniKeyDenylist=IOSTeamID
-IniKeyDenylist=SigningCertificate
-IniKeyDenylist=MobileProvision
-IniKeyDenylist=IniKeyDenylist
-IniKeyDenylist=IniSectionDenylist
+IniKeyDenylist=KeyStorePassword
+IniKeyDenylist=KeyPassword
+IniKeyDenylist=rsa.privateexp
+IniKeyDenylist=rsa.modulus
+IniKeyDenylist=rsa.publicexp
+IniKeyDenylist=aes.key
+IniKeyDenylist=SigningPublicExponent
+IniKeyDenylist=SigningModulus
+IniKeyDenylist=SigningPrivateExponent
+IniKeyDenylist=EncryptionKey
+IniKeyDenylist=DevCenterUsername
+IniKeyDenylist=DevCenterPassword
+IniKeyDenylist=IOSTeamID
+IniKeyDenylist=SigningCertificate
+IniKeyDenylist=MobileProvision
+IniKeyDenylist=IniKeyDenylist
+IniKeyDenylist=IniSectionDenylist
-IniSectionDenylist=HordeStorageServers
-IniSectionDenylist=StorageServers
+IniSectionDenylist=HordeStorageServers
+IniSectionDenylist=StorageServers
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,28 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "MultiWindow",
"Description": "",
"Category": "Other",
"CreatedBy": "YWT20",
"CreatedByURL": "https://www.unrealengine.com/marketplace/en-US/profile/YWT20",
"DocsURL": "",
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/1056b6c5d31c49809416547f44545453",
"SupportURL": "https://www.youtube.com/channel/UCWYg44Cjom34vYb1Zedt__A?view_as=subscriber",
"EngineVersion": "5.3.0",
"CanContainContent": true,
"Installed": true,
"Modules": [
{
"Name": "MultiWindow",
"Type": "Runtime",
"LoadingPhase": "PreLoadingScreen",
"PlatformAllowList": [
"Win64",
"Mac",
"Linux"
]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,59 @@
/************************************************************************/
/* Author: YWT20 */
/* Expected release year : 2024 */
/************************************************************************/
using UnrealBuildTool;
public class MultiWindow : ModuleRules
{
public MultiWindow(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"UMG",
"InputCore",
"ApplicationCore",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}
@@ -0,0 +1,25 @@
/************************************************************************/
/* Author: YWT20 */
/* Expected release year : 2024 */
/************************************************************************/
#include "MultiWindow.h"
#define LOCTEXT_NAMESPACE "FMultiWindowModule"
void FMultiWindowModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FMultiWindowModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FMultiWindowModule, MultiWindow)
@@ -0,0 +1,242 @@
/************************************************************************/
/* Author: YWT20 */
/* Expected release year : 2024 */
/************************************************************************/
#include "MultiWindowActor.h"
#include "Components/Widget.h"
#include "Widgets/SWindow.h"
#include "Widgets/Layout/SConstraintCanvas.h"
#include "Engine.h"
#include <SMWindow.h>
AMultiWindowActor::AMultiWindowActor()
{
PrimaryActorTick.bCanEverTick = true;
}
void AMultiWindowActor::BeginPlay()
{
Super::BeginPlay();
viewPort = GEngine->GameViewport->Viewport;
}
void AMultiWindowActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
SetWindowClose();
}
void AMultiWindowActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
UpdateContentDPI();
if (WindowPos != GetWindowPositoin())
{
WindowPos = GetWindowPositoin();
OnWindowMoved.Broadcast(WindowPos);
}
if (ForceFocusToGameViewport && !viewPort->HasFocus())
{
if (!timerHandle.IsValid())
{
GetWorldTimerManager().SetTimer(timerHandle, this, &AMultiWindowActor::CheckFocus, focusTime, false);
}
}
}
void AMultiWindowActor::InitWindow(TSharedPtr<SWindow> InWindow, UUserWidget* InContentWidget)
{
if (IsPendingKillPending()) return;
window = InWindow;
contentWidget = InContentWidget;
window->GetOnWindowClosedEvent().AddUObject(this, &AMultiWindowActor::EventOnWindowClose);
WindowPos = GetWindowPositoin();
window->GetOnWindowActivatedEvent().AddLambda([&]() {
OnWindowActivated.Broadcast();
});
window->GetOnWindowDeactivatedEvent().AddLambda([&]() {
OnWindowDeactivated.Broadcast();
});
}
void AMultiWindowActor::UpdateContentDPI()
{
if (bIsAutoContentDPIScale && WindowOriginSize != GetWindowSize())
{
FVector2D TempWindowDPI = GetWindowSize() / WindowOriginSize;
if (TempWindowDPI != WindowDPI)
{
WindowDPI = TempWindowDPI;
contentWidget->SetRenderScale(WindowDPI);
}
}
}
void AMultiWindowActor::CheckFocus()
{
if (!viewPort->HasFocus())
{
FSlateApplication::Get().SetAllUserFocusToGameViewport();
}
GetWorldTimerManager().ClearTimer(timerHandle);
}
void AMultiWindowActor::SetWindowShow(bool bIsShow)
{
if (window.IsValid())
{
if (bIsShow)
{
window->ShowWindow();
}
else
{
window->HideWindow();
}
}
}
void AMultiWindowActor::SetWindowContentWidget(UUserWidget* ContentWidget)
{
if (ContentWidget && window.IsValid())
{
window->SetContent(ContentWidget->TakeWidget());
contentWidget = ContentWidget;
}
}
UUserWidget* AMultiWindowActor::GetWindowContentWidget()
{
return contentWidget;
}
void AMultiWindowActor::SetWindowSize(FVector2D WindowSize)
{
if (window)
{
window->Resize(WindowSize);
WindowOriginSize = WindowSize;
}
}
void AMultiWindowActor::SetWindowPosition(FVector2D WindowPosition)
{
if (window)
{
window->MoveWindowTo(WindowPosition);
}
}
FVector2D AMultiWindowActor::GetWindowSize()
{
if (window)
{
FVector2D tempSize = window->GetSizeInScreen();
FMargin tempBorderSize = window->GetWindowBorderSize(false);
tempSize.X -= tempBorderSize.GetDesiredSize().X;
tempSize.Y -= tempBorderSize.GetDesiredSize().Y + window->GetTitleBarSize().Get();
return tempSize;
}
return FVector2D();
}
FVector2D AMultiWindowActor::GetWindowPositoin()
{
if (window)
{
FVector2D tempSize = window->GetPositionInScreen();
return tempSize;
}
return FVector2D();
}
//FVector2D AMultiWindowActor::GetWindowDPI()
//{
// return WindowDPI;
//}
void AMultiWindowActor::SetWindowTitle(const FString& WindowTitle)
{
if (window)
{
window->SetTitle(FText::FromString(WindowTitle));
}
}
void AMultiWindowActor::SetWindowMaximize()
{
if (window)
{
window->Maximize();
}
}
void AMultiWindowActor::SetWindowRestore()
{
if (window)
{
window->Restore();
}
}
void AMultiWindowActor::SetWindowMinimize()
{
if (window)
{
window->Minimize();
}
}
void AMultiWindowActor::SetWindowClose()
{
if (window)
{
if (OnWindowClose.IsBound())
{
OnWindowClose.Broadcast();
}
OnWindowClose.Clear();
window->GetOnWindowClosedEvent().RemoveAll(this);
window->GetOnWindowActivatedEvent().RemoveAll(this);
window->GetOnWindowDeactivatedEvent().RemoveAll(this);
//window->DestroyWindowImmediately();
window->RequestDestroyWindow();
window.Reset();
Destroy();
}
}
FText AMultiWindowActor::GetWindowTitle()
{
if (window)
{
return window->GetTitle();
}
return FText();
}
void AMultiWindowActor::SetDPIScaleFactor(const float Factor)
{
if (window)
{
window->SetDPIScaleFactor(Factor);
}
}
float AMultiWindowActor::GetDPIScaleFactor() const
{
if (window)
{
return window->GetDPIScaleFactor();
}
return 0.0f;
}
@@ -0,0 +1,171 @@
/************************************************************************/
/* Author: YWT20 */
/* Expected release year : 2024 */
/************************************************************************/
#include "MultiWindowBPLibrary.h"
#include "Engine.h"
#include "Blueprint/UserWidget.h"
#include "SMWindow.h"
#include "Components/Widget.h"
#include "Widgets/SWidget.h"
#include "Widgets/Layout/SConstraintCanvas.h"
#include "Framework/Application/SlateApplication.h"
#include "Engine/GameViewportClient.h"
#include "GenericPlatform/GenericApplication.h"
#include "GameFramework/GameUserSettings.h"
#include "MultiWindow.h"
UMultiWindowBPLibrary::UMultiWindowBPLibrary(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
bool UMultiWindowBPLibrary::CreateMultiWindow(
UObject* WorldContextObject,
UUserWidget* ContentWidget,
AMultiWindowActor*& MultiWindowActor,
FVector2D WindowPositon,
FVector2D WindowSize,
bool bIsAutoCenter,
bool bIsCreateTitle,
FString WindowTitle,
float WindowBorderMarginLeft,
float WindowBorderMarginTop,
float WindowBorderMarginRight,
float WindowBorderMarginBottom,
EWindowSizingRule WindowSizingRule,
bool bIsAutoContentDPIScale,
bool IsTopmostWindow,
bool ForceFocusToGameViewport
)
{
TSharedPtr<SMWindow> window = SNew(SMWindow)
.Title(FText::FromString(WindowTitle))
.ScreenPosition(WindowPositon)
.ClientSize(WindowSize)
.FocusWhenFirstShown(true)
.HasCloseButton(true)
.IsTopmostWindow(IsTopmostWindow)
.SupportsMaximize(true)
.LayoutBorder(FMargin(WindowBorderMarginLeft, WindowBorderMarginTop, WindowBorderMarginRight, WindowBorderMarginBottom))
.SupportsMinimize(true)
.CreateTitleBar(bIsCreateTitle)
.AutoCenter(bIsAutoCenter ? (EAutoCenter::PrimaryWorkArea) : (EAutoCenter::None))
.SizingRule((ESizingRule)WindowSizingRule);
if (!window.IsValid()) { return false; }
window->SetViewportSizeDrivenByWindow(true);
FSlateApplication& slateApp = FSlateApplication::Get();
TArray<TSharedRef <SWindow>> OutWindows;
slateApp.GetAllVisibleWindowsOrdered(OutWindows);
if (TSharedPtr <SWindow> MainMenu = OutWindows[0])
{
slateApp.AddWindowAsNativeChild(window.ToSharedRef(), MainMenu.ToSharedRef());
}
else
{
slateApp.AddWindow(window.ToSharedRef(), true);
}
if (ContentWidget)
{
window->SetContent(ContentWidget->TakeWidget());
}
window->SetWindowMode(EWindowMode::Windowed);
if (UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject)) {
MultiWindowActor = World->SpawnActor<AMultiWindowActor>(FVector::ZeroVector, FRotator::ZeroRotator);
MultiWindowActor->InitWindow(window, ContentWidget);
MultiWindowActor->WindowOriginSize = WindowSize;
MultiWindowActor->bIsAutoContentDPIScale = bIsAutoContentDPIScale;
MultiWindowActor->ForceFocusToGameViewport = ForceFocusToGameViewport;
window->MultiWindowActor = MultiWindowActor;
}
return true;
}
bool UMultiWindowBPLibrary::CreateRelativeMultiWindow(UObject* WorldContextObject, UUserWidget* ContentWidget, AMultiWindowActor*& MultiWindowActor, int32 MonitorIndex, FVector2D WindowPositon, FVector2D WindowSize, bool bIsAutoCenter, bool bIsCreateTitle, FString WindowTitle, float WindowBorderMarginLeft, float WindowBorderMarginTop, float WindowBorderMarginRight, float WindowBorderMarginBottom, EWindowSizingRule WindowSizingRule, bool bIsAutoContentDPIScale, bool IsTopmostWindow, bool ForceFocusToGameViewport)
{
CreateMultiWindow(WorldContextObject, ContentWidget, MultiWindowActor, WindowPositon, WindowSize, bIsAutoCenter, bIsCreateTitle, WindowTitle, WindowBorderMarginLeft, WindowBorderMarginTop, WindowBorderMarginRight, WindowBorderMarginBottom, WindowSizingRule, bIsAutoContentDPIScale, IsTopmostWindow);
if (MonitorIndex < 0) return false;
FDisplayMetrics displayMetrics;
displayMetrics.RebuildDisplayMetrics(displayMetrics);
if (!displayMetrics.MonitorInfo.IsValidIndex(MonitorIndex)) return false;
FMonitorInfo monitorInfo = displayMetrics.MonitorInfo[MonitorIndex];
if (!GEngine) return false;
if (bIsAutoCenter)
{
WindowPositon = FVector2D(monitorInfo.WorkArea.Left, monitorInfo.WorkArea.Top);
WindowPositon += FVector2D(monitorInfo.NativeWidth * 0.5f, monitorInfo.NativeHeight * 0.5f);
WindowPositon -= WindowSize / 2;
}
else
{
WindowPositon += FVector2D(monitorInfo.WorkArea.Left, monitorInfo.WorkArea.Top);
}
MultiWindowActor->ForceFocusToGameViewport = ForceFocusToGameViewport;
MultiWindowActor->SetWindowPosition(WindowPositon);
return true;
}
bool UMultiWindowBPLibrary::CreateMultiWindowByMonitor(UObject* WorldContextObject, UUserWidget* ContentWidget, AMultiWindowActor*& MultiWindowActor, int32 MonitorIndex, bool bIsCreateTitle, FString WindowTitle, bool ForceFocusToGameViewport)
{
CreateMultiWindow(WorldContextObject, ContentWidget, MultiWindowActor, FVector2D(), FVector2D(800.0f, 640.0f), false, bIsCreateTitle, WindowTitle, 0, 0, 0, 0, EWindowSizingRule::FixedSize, false, true);
if (MonitorIndex < 0) return false;
FDisplayMetrics displayMetrics;
displayMetrics.RebuildDisplayMetrics(displayMetrics);
if (!displayMetrics.MonitorInfo.IsValidIndex(MonitorIndex)) return false;
FMonitorInfo monitorInfo = displayMetrics.MonitorInfo[MonitorIndex];
if (!GEngine) return false;
MultiWindowActor->SetWindowPosition(FVector2D(monitorInfo.WorkArea.Left, monitorInfo.WorkArea.Top));
MultiWindowActor->ForceFocusToGameViewport = ForceFocusToGameViewport;
MultiWindowActor->SetWindowMaximize();
return true;
}
int32 UMultiWindowBPLibrary::GetMonitorCount()
{
FDisplayMetrics displayMetrics;
displayMetrics.RebuildDisplayMetrics(displayMetrics);
return displayMetrics.MonitorInfo.Num();
}
int32 UMultiWindowBPLibrary::GetPrimaryMonitorIndex()
{
FDisplayMetrics displayMetrics;
displayMetrics.RebuildDisplayMetrics(displayMetrics);
for (int32 i = 0; i < displayMetrics.MonitorInfo.Num(); i++)
{
if (displayMetrics.MonitorInfo[i].bIsPrimary) return i;
}
return -1;
}
void UMultiWindowBPLibrary::MW_PrintToLog()
{
FDisplayMetrics displayMetrics;
displayMetrics.RebuildDisplayMetrics(displayMetrics);
displayMetrics.PrintToLog();
}
@@ -0,0 +1,44 @@
/************************************************************************/
/* Author: YWT20 */
/* Expected release year : 2024 */
/************************************************************************/
#include "SMWindow.h"
#include "MultiWindowActor.h"
FReply SMWindow::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& InCharacterEvent)
{
if (MultiWindowActor)
{
return MultiWindowActor->contentWidget->OnKeyChar(MyGeometry, InCharacterEvent).NativeReply;
}
return FReply::Unhandled();
}
FReply SMWindow::OnPreviewKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
if (MultiWindowActor)
{
return MultiWindowActor->contentWidget->OnPreviewKeyDown(MyGeometry, InKeyEvent).NativeReply;
}
return FReply::Unhandled();
}
FReply SMWindow::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
if (MultiWindowActor)
{
return MultiWindowActor->contentWidget->OnKeyDown(MyGeometry, InKeyEvent).NativeReply;
}
return FReply::Unhandled();
}
FReply SMWindow::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
if (MultiWindowActor)
{
return MultiWindowActor->contentWidget->OnKeyUp(MyGeometry, InKeyEvent).NativeReply;
}
return FReply::Unhandled();
}
@@ -0,0 +1,17 @@
/************************************************************************/
/* Author: YWT20 */
/* Expected release year : 2024 */
/************************************************************************/
#pragma once
#include "Modules/ModuleManager.h"
class FMultiWindowModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
@@ -0,0 +1,147 @@
/************************************************************************/
/* Author: YWT20 */
/* Expected release year : 2024 */
/************************************************************************/
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "GameFramework/Actor.h"
#include "Framework/Application/SlateApplication.h"
#include "TimerManager.h"
#include "MultiWindowActor.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWindowEvent);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWindowMovedEvent, FVector2D, pos);
UCLASS()
class MULTIWINDOW_API AMultiWindowActor : public AActor
{
GENERATED_BODY()
public:
AMultiWindowActor();
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
virtual void Tick(float DeltaTime) override;
TSharedPtr<SWindow> window = nullptr;
UUserWidget* contentWidget;
FViewport* viewPort;
FTimerHandle timerHandle;
public:
FVector2D WindowOriginSize;
FVector2D WindowPos;
FVector2D WindowDPI;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiWindow")
float focusTime = 0.25f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiWindow")
bool ForceFocusToGameViewport;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiWindow")
bool bIsAutoContentDPIScale;
// Invoked when the window is about to be closed.
UPROPERTY(BlueprintAssignable, Category = "MultiWindow|Event")
FOnWindowEvent OnWindowClose;
// Invoked when the window has been activated.
UPROPERTY(BlueprintAssignable, Category = "MultiWindow|Event")
FOnWindowEvent OnWindowActivated;
// Invoked when the window has been deactivated.
UPROPERTY(BlueprintAssignable, Category = "MultiWindow|Event")
FOnWindowEvent OnWindowDeactivated;
UPROPERTY(BlueprintAssignable, Category = "MultiWindow|Event")
FOnWindowMovedEvent OnWindowMoved;
public:
void EventOnWindowClose(const TSharedRef<SWindow>& InSWindow)
{
if (IsPendingKillPending()) return;
if (OnWindowClose.IsBound())
{
OnWindowClose.Broadcast();
}
};
public:
// Initialize multi-window data
void InitWindow(TSharedPtr<SWindow> InWindow, UUserWidget* InContentWidget);
void UpdateContentDPI();
void CheckFocus();
// Set the window visible
UFUNCTION(BlueprintCallable, Category = "MultiWindow")
void SetWindowShow(bool bIsShow);
// Set the widget content for this window
UFUNCTION(BlueprintCallable, Category = "MultiWindow")
void SetWindowContentWidget(UUserWidget* ContentWidget);
// Set the widget content for this window
UFUNCTION(BlueprintPure, Category = "MultiWindow")
UUserWidget* GetWindowContentWidget();
// Resize the window to be dpi scaled NewClientSize immediately
UFUNCTION(BlueprintCallable, Category = "MultiWindow")
void SetWindowSize(FVector2D WindowSize);
UFUNCTION(BlueprintPure, Category = "MultiWindow")
FVector2D GetWindowSize();
UFUNCTION(BlueprintCallable, Category = "MultiWindow")
void SetWindowPosition(FVector2D WindowPosition);
UFUNCTION(BlueprintPure, Category = "MultiWindow")
FVector2D GetWindowPositoin();
//UFUNCTION(BlueprintPure, Category = "MultiWindow")
// FVector2D GetWindowDPI();
// Sets the current window title
UFUNCTION(BlueprintCallable, Category = "MultiWindow")
void SetWindowTitle(const FString& WindowTitle);
// Maximize window size
UFUNCTION(BlueprintCallable, Category = "MultiWindow")
void SetWindowMaximize();
// Restore window size
UFUNCTION(BlueprintCallable, Category = "MultiWindow")
void SetWindowRestore();
// Minimize window size
UFUNCTION(BlueprintCallable, Category = "MultiWindow")
void SetWindowMinimize();
// Close the window
UFUNCTION(BlueprintCallable, Category = "MultiWindow")
void SetWindowClose();
// Grabs the current window title
UFUNCTION(BlueprintPure, Category = "MultiWindow | V2")
FText GetWindowTitle();
// Overrides the DPI scale factor of the native window
UFUNCTION(BlueprintCallable, Category = "MultiWindow | V2")
void SetDPIScaleFactor(const float Factor);
// Returns the DPI scale factor of the native window
UFUNCTION(BlueprintPure, Category = "MultiWindow | V2")
float GetDPIScaleFactor() const;
};
@@ -0,0 +1,96 @@
/************************************************************************/
/* Author: YWT20 */
/* Expected release year : 2024 */
/************************************************************************/
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MultiWindowActor.h"
#include "MultiWindowBPLibrary.generated.h"
UENUM(BlueprintType)
enum class EWindowSizingRule : uint8
{
/* The windows size fixed and cannot be resized **/
FixedSize,
/** The window size is computed from its content and cannot be resized by users */
Autosized,
/** The window can be resized by users */
UserSized,
};
UCLASS()
class UMultiWindowBPLibrary : public UBlueprintFunctionLibrary
{
GENERATED_UCLASS_BODY()
public:
// Create new window
UFUNCTION(BlueprintCallable, Category = "MultiWindow | V1", meta = (WorldContext = "WorldContextObject", AdvancedDisplay = "5"))
static bool CreateMultiWindow(
UObject* WorldContextObject,
UUserWidget* ContentWidget,
AMultiWindowActor*& MultiWindowActor,
FVector2D WindowPositon = FVector2D(0, 0),
FVector2D WindowSize = FVector2D(500, 500),
bool bIsAutoCenter = false,
bool bIsCreateTitle = true,
FString WindowTitle = FString(TEXT("MultiWindow")),
float WindowBorderMarginLeft = 5.0f,
float WindowBorderMarginTop = 5.0f,
float WindowBorderMarginRight = 5.0f,
float WindowBorderMarginBottom = 5.0f,
EWindowSizingRule WindowSizingRule = EWindowSizingRule::UserSized,
bool bIsAutoContentDPIScale = false,
bool IsTopmostWindow = false,
bool ForceFocusToGameViewport = false
);
// Create new relative window
UFUNCTION(BlueprintCallable, Category = "MultiWindow | V2", meta = (WorldContext = "WorldContextObject", AdvancedDisplay = "5"))
static bool CreateRelativeMultiWindow(
UObject* WorldContextObject,
UUserWidget* ContentWidget,
AMultiWindowActor*& MultiWindowActor,
int32 MonitorIndex,
FVector2D WindowPositon = FVector2D(0, 0),
FVector2D WindowSize = FVector2D(500, 500),
bool bIsAutoCenter = false,
bool bIsCreateTitle = true,
FString WindowTitle = FString(TEXT("MultiWindow")),
float WindowBorderMarginLeft = 5.0f,
float WindowBorderMarginTop = 5.0f,
float WindowBorderMarginRight = 5.0f,
float WindowBorderMarginBottom = 5.0f,
EWindowSizingRule WindowSizingRule = EWindowSizingRule::UserSized,
bool bIsAutoContentDPIScale = false,
bool IsTopmostWindow = false,
bool ForceFocusToGameViewport = false
);
// Create new window by Monitor
UFUNCTION(BlueprintCallable, Category = "MultiWindow | V2", meta = (WorldContext = "WorldContextObject"))
static bool CreateMultiWindowByMonitor(
UObject* WorldContextObject,
UUserWidget* ContentWidget,
AMultiWindowActor*& MultiWindowActor,
int32 MonitorIndex,
bool bIsCreateTitle = false,
FString WindowTitle = FString(TEXT("MultiWindow")),
bool ForceFocusToGameViewport = false
);
// Get monitor count
UFUNCTION(BlueprintPure, Category = "MultiWindow | V2")
static int32 GetMonitorCount();
// Get primary monitor index
UFUNCTION(BlueprintPure, Category = "MultiWindow | V2")
static int32 GetPrimaryMonitorIndex();
// Logs out display metrics
UFUNCTION(BlueprintCallable, Category = "MultiWindow | V2")
static void MW_PrintToLog();
};
@@ -0,0 +1,27 @@
/************************************************************************/
/* Author: YWT20 */
/* Expected release year : 2024 */
/************************************************************************/
#pragma once
#include "Framework/Application/SlateApplication.h"
#include "Widgets/SWindow.h"
/**
*
*/
class MULTIWINDOW_API SMWindow : public SWindow
{
public:
class AMultiWindowActor* MultiWindowActor;
public:
virtual FReply OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& InCharacterEvent) override;
virtual FReply OnPreviewKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
virtual FReply OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
};
@@ -0,0 +1,26 @@
{
"FileVersion": 3,
"Version": 2,
"VersionName": "1.2",
"FriendlyName": "NeoKinect",
"Description": "Allow Blueprints to use the Kinect v2 sensor capabilities.",
"Category": "Input Devices",
"CreatedBy": "Rodrigo Villani",
"CreatedByURL": "http://rvillani.com",
"DocsURL": "http://files.rvillani.com/neokinect/NeoKinect-QuickStart.pdf",
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/130cfe979a6e4159b634d4b1d23e60da",
"SupportURL": "mailto:contact@rvillani.com",
"EngineVersion": "5.3.0",
"CanContainContent": false,
"Installed": true,
"Modules": [
{
"Name": "NeoKinectUnreal",
"Type": "Runtime",
"LoadingPhase": "Default",
"PlatformAllowList": [
"Win64"
]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

@@ -0,0 +1,68 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
using System.IO;
using UnrealBuildTool;
public class NeoKinectUnreal : ModuleRules
{
public NeoKinectUnreal(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PrivateIncludePaths.AddRange(new[]
{
"NeoKinectUnreal/Private"
// ... add other private include paths required here ...
});
PublicDependencyModuleNames.AddRange(new[]
{
"Core",
"CoreUObject",
// ... add other public dependencies that you statically link with here ...
});
PrivateDependencyModuleNames.AddRange(new[]
{
"Engine",
"RenderCore",
"RHI",
// ... add private dependencies that you statically link with here ...
});
string thirdPartyPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "../ThirdParty/"));
string neoKinectPath = Path.Combine(thirdPartyPath, "NeoKinect");
string kinectPath = Path.Combine(thirdPartyPath, "Kinect");
PublicSystemLibraryPaths.Add(Path.Combine(neoKinectPath, "Lib"));
PublicIncludePaths.Add(Path.Combine(neoKinectPath, "Inc"));
PublicSystemLibraryPaths.Add(Path.Combine(kinectPath, "Lib"));
PublicIncludePaths.Add(Path.Combine(kinectPath, "Inc"));
const string FaceLibraryName = "Kinect20.Face";
// Add static libraries
PublicSystemLibraries.AddRange(new[]
{
"NeoKinect.lib",
"Kinect20.lib",
FaceLibraryName + ".lib"
});
// Add dynamic libraries
const string KinectFaceDllName = FaceLibraryName + ".dll";
PublicDelayLoadDLLs.AddRange(new[]
{
KinectFaceDllName
});
// Define library name for code to be able to check for its presence
PrivateDefinitions.Add("KINECT_FACE_DLL_NAME=\"" + KinectFaceDllName + "\"");
// Stage face library files when packaging
const string ProjectDLLDir = "$(ProjectDir)/Binaries/Win64";
RuntimeDependencies.Add(Path.Combine(ProjectDLLDir, KinectFaceDllName));
RuntimeDependencies.Add(Path.Combine(ProjectDLLDir, "NuiDatabase/..."), StagedFileType.NonUFS);
}
}
@@ -0,0 +1,329 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#include "KinectThread.h"
#include "HAL/RunnableThread.h"
/*-----------------------------------------------------------------------------
ThreadFrameBuffer
-----------------------------------------------------------------------------*/
using namespace NeoKinect;
FThreadFrameBuffer::~FThreadFrameBuffer()
{
DeallocateBuffers();
}
void FThreadFrameBuffer::Configure(
KinectFrameType Frame,
KinectPixelFormat Format,
KinectCoordinateSpace Space,
bool IsRemapped)
{
this->FrameType = Frame;
this->PixelFormat = Format;
this->TargetSpace = Space;
this->bRemapped = IsRemapped;
}
bool FThreadFrameBuffer::AllocateBuffers(uint32 Size)
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("Kinect_AllocateBuffers_%d"), FrameType));
if (IsAllocated())
{
return true;
}
Buffer = new uint8[Size];
BackBuffer = new uint8[Size];
return Buffer && BackBuffer;
}
void FThreadFrameBuffer::DeallocateBuffers()
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("Kinect_DeallocateBuffers_%d"), FrameType));
bIsNew = false;
if (Buffer)
{
Lock();
delete[] Buffer;
Buffer = nullptr;
Unlock();
}
if (BackBuffer)
{
LockWrite();
delete[] BackBuffer;
BackBuffer = nullptr;
UnlockWrite();
}
}
bool FThreadFrameBuffer::IsAllocated() const
{
return Buffer && BackBuffer;
}
const uint8* FThreadFrameBuffer::GetReadBuffer(bool bConsume /*= true*/)
{
if (bConsume)
{
if (Buffer && bIsNew)
{
bIsNew = false;
return Buffer;
}
return nullptr;
}
return Buffer;
}
uint8* FThreadFrameBuffer::GetWriteBuffer() const
{
return BackBuffer;
}
void FThreadFrameBuffer::SetDirty()
{
SwapBuffers();
bIsNew = true;
}
void FThreadFrameBuffer::SwapBuffers()
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("Kinect_SwapBuffers_%d"), FrameType));
Lock();
uint8* Temp = Buffer;
Buffer = BackBuffer;
BackBuffer = Temp;
Unlock();
}
bool FThreadFrameBuffer::TryLock()
{
return ReadMutex.TryLock();
}
void FThreadFrameBuffer::Lock()
{
ReadMutex.Lock();
}
void FThreadFrameBuffer::Unlock()
{
ReadMutex.Unlock();
}
void FThreadFrameBuffer::LockWrite()
{
WriteMutex.Lock();
}
void FThreadFrameBuffer::UnlockWrite()
{
WriteMutex.Unlock();
}
/*-----------------------------------------------------------------------------
KinectThread
-----------------------------------------------------------------------------*/
FKinectThread* FKinectThread::Runnable = nullptr;
FCriticalSection FKinectThread::BodiesFacesCritSec;
FThreadFrameBuffer FKinectThread::FrameBuffers[static_cast<uint64>(EKinectFrame::Count)];
bool FKinectThread::bUseBodyTracking = false;
bool FKinectThread::bUseFaceTracking = false;
FKinectThread::FKinectThread(KinectSensor *pSensor) :
pKinect(pSensor),
bBodiesDirty(false),
bFacesDirty(false)
{
Thread = FRunnableThread::Create(this, TEXT("FKinectThread")); // Windows give 8MB memory by default
if (!Thread)
UE_LOG(KinectThreadLog, Error, TEXT("Failed to create Kinect thread!"));
}
FKinectThread::~FKinectThread()
{
if (Thread) delete Thread;
Thread = nullptr;
}
FKinectThread* FKinectThread::InitKinectPolling(KinectSensor *pSensor)
{
// Create new instance of thread if it does not exist and the platform
// supports multi threading.
if (!Runnable && FPlatformProcess::SupportsMultithreading())
{
Runnable = new FKinectThread(pSensor);
}
return Runnable;
}
void FKinectThread::EnsureCompletion()
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_EnsureThreadCompletion)
Stop();
if (Thread)
Thread->WaitForCompletion();
}
void FKinectThread::Shutdown()
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_ShutdownThread)
if (Runnable)
{
Runnable->EnsureCompletion();
delete Runnable;
Runnable = nullptr;
}
}
const KinectBody* FKinectThread::GetBodiesData()
{
if (bBodiesDirty)
{
bBodiesDirty = false;
return Bodies;
}
return nullptr;
}
const KinectFace* FKinectThread::GetFacesData()
{
if (bFacesDirty)
{
bFacesDirty = false;
return Faces;
}
return nullptr;
}
bool FKinectThread::Init()
{
UE_LOG(KinectThreadLog, Log, TEXT("Kinect thread initialized."));
return true;
}
uint32 FKinectThread::Run()
{
constexpr float UpdateInterval = 1.f / cDesiredFPS;
UE_LOG(KinectThreadLog, Log, TEXT("Kinect thread running."));
// while not told to stop this thread
while (StopTaskCounter.GetValue() == 0)
{
// try locking any frames necessary
constexpr int FrameTypesCount = static_cast<int>(KinectFrameType::Count);
TRACE_CPUPROFILER_EVENT_SCOPE(KinectThread_Run)
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_LockFrames)
for (int i = 0; i < FrameTypesCount; ++i)
{
const KinectFrameType FrameType = static_cast<KinectFrameType>(i);
if (pKinect->GetIsUsingFrame(FrameType))
{
pKinect->LockLatestFrame(FrameType);
}
}
}
if (bUseBodyTracking)
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_GetBodyData)
// process bodies before face tracking - face tracking depends on bodies data
BodiesFacesCritSec.Lock();
if (pKinect->GetLatestFrameData(Bodies))
{
bBodiesDirty = true;
}
BodiesFacesCritSec.Unlock();
}
if (bUseFaceTracking)
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_GetFaceData)
// process face tracking
BodiesFacesCritSec.Lock();
if (pKinect->GetLatestFrameData(Faces))
{
bFacesDirty = true;
}
BodiesFacesCritSec.Unlock();
}
// update any necessary buffers
for (FThreadFrameBuffer& FrameBuffer : FrameBuffers)
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("Kinect_UpdateBuffer_%d"), FrameBuffer.FrameType));
if (FrameBuffer.IsAllocated() && pKinect->GetIsFrameLocked(FrameBuffer.FrameType))
{
FrameBuffer.LockWrite();
// If this is a remapped type, use the correct function.
// If the update works, marks buffer as new
if (FrameBuffer.bRemapped)
{
if (pKinect->GetRemappedFrameData(
FrameBuffer.FrameType, FrameBuffer.TargetSpace,
FrameBuffer.GetWriteBuffer(), FrameBuffer.PixelFormat))
{
FrameBuffer.SetDirty();
}
}
else
{
uint8* Buffer = FrameBuffer.GetWriteBuffer();
if (pKinect->GetLatestFrameData(
FrameBuffer.FrameType, Buffer,
FrameBuffer.PixelFormat))
{
FrameBuffer.SetDirty();
}
}
FrameBuffer.UnlockWrite();
}
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_UnlockFrames)
// unlock frames
for (int i = 0; i < FrameTypesCount; ++i)
{
pKinect->UnlockLatestFrame(static_cast<KinectFrameType>(i));
}
}
// give time for Kinect to update its frames.
FPlatformProcess::Sleep(UpdateInterval);
}
// ended successfully!
return 0;
}
void FKinectThread::Stop()
{
StopTaskCounter.Increment();
}
void FKinectThread::Exit()
{
UE_LOG(KinectThreadLog, Log, TEXT("Kinect thread ended."));
}
@@ -0,0 +1,547 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#include "NeoKinectBody.h"
#include "Helpers/GuardedNeoKinect.h"
#include "NeoKinectManager.h"
UNeoKinectBody::UNeoKinectBody()
: BodyIndex(0),
TrackingId(0),
bIsTracked(false),
bIsRestricted(false),
HandLeftState(EKinectHandState::NotTracked),
HandLeftTrackingState(EKinectTrackingState::NotTracked),
HandRightState(EKinectHandState::NotTracked),
HandRightTrackingState(EKinectTrackingState::NotTracked),
LeanTrackingState(EKinectTrackingState::NotTracked),
Lean(FVector2D(0.f, 0.f))
{
using namespace NeoKinect;
Joints.SetNum(KinectJointCount);
for (int32 JointIdx = 0; JointIdx < KinectJointCount; ++JointIdx)
{
Joints[JointIdx].Type = static_cast<EKinectJointType>(JointIdx);
}
}
UNeoKinectBody::~UNeoKinectBody()
{
Joints.Empty();
}
void UNeoKinectBody::GetHandStateAsExec(EKinectBodySide Hand, EKinectHandState& States)
{
if (Hand == EKinectBodySide::Left)
{
States = HandLeftState;
}
else
{
States = HandRightState;
}
}
EKinectHandState UNeoKinectBody::GetHandState(EKinectBodySide Hand) const
{
if (Hand == EKinectBodySide::Left)
return HandLeftState;
else
return HandRightState;
}
void UNeoKinectBody::GetHandConfidenceAsExec(EKinectBodySide Hand, EKinectTrackingState& States)
{
if (Hand == EKinectBodySide::Left)
{
States = HandLeftTrackingState;
}
else
{
States = HandRightTrackingState;
}
}
EKinectTrackingState UNeoKinectBody::GetHandConfidence(EKinectBodySide Hand) const
{
if (Hand == EKinectBodySide::Left)
return HandLeftTrackingState;
else
return HandRightTrackingState;
}
void UNeoKinectBody::GetLeanConfidenceAsExec(EKinectTrackingState& States)
{
States = LeanTrackingState;
}
TArray<FKinectJoint> UNeoKinectBody::GetJoints()
{
return Joints;
}
FVector UNeoKinectBody::GetJointLocation(EKinectJointType Joint)
{
return Joints[static_cast<int32>(Joint)].Location;
}
FRotator UNeoKinectBody::GetJointOrientation(EKinectJointType Joint)
{
return Joints[static_cast<int32>(Joint)].Orientation;
}
FVector UNeoKinectBody::GetJointLocationColor(EKinectJointType Joint)
{
return Joints[static_cast<int32>(Joint)].ColorLocation;
}
FRotator UNeoKinectBody::GetJointOrientationColor(EKinectJointType Joint)
{
return Joints[static_cast<int32>(Joint)].ColorOrientation;
}
void UNeoKinectBody::GetJointConfidenceAsExec(EKinectJointType Joint, EKinectTrackingState& States)
{
States = Joints[static_cast<int32>(Joint)].Confidence;
}
EKinectTrackingState UNeoKinectBody::GetJointConfidence(EKinectJointType Joint)
{
return Joints[static_cast<int32>(Joint)].Confidence;
}
float UNeoKinectBody::GetJointsDistance(EKinectJointType JointA, EKinectJointType JointB)
{
return FVector::Dist(Joints[static_cast<int32>(JointB)].Location, Joints[static_cast<int32>(JointA)].Location);
}
float UNeoKinectBody::GetBoneLength(EKinectJointType Joint)
{
const FVector p1 = Joints[static_cast<int32>(Joint)].Location;
switch (Joint)
{
case EKinectJointType::SpineBase:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineMid)].Location);
case EKinectJointType::SpineMid:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineBase)].Location);
case EKinectJointType::Neck:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineShoulder)].Location);
case EKinectJointType::Head:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::Neck)].Location);
case EKinectJointType::ShoulderLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineShoulder)].Location);
case EKinectJointType::ElbowLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::ShoulderLeft)].Location);
case EKinectJointType::WristLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::ElbowLeft)].Location);
case EKinectJointType::HandLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::WristLeft)].Location);
case EKinectJointType::ShoulderRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineShoulder)].Location);
case EKinectJointType::ElbowRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::ShoulderRight)].Location);
case EKinectJointType::WristRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::ElbowRight)].Location);
case EKinectJointType::HandRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::WristRight)].Location);
case EKinectJointType::HipLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineBase)].Location);
case EKinectJointType::KneeLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::HipLeft)].Location);
case EKinectJointType::AnkleLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::KneeLeft)].Location);
case EKinectJointType::FootLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::AnkleLeft)].Location);
case EKinectJointType::HipRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineBase)].Location);
case EKinectJointType::KneeRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::HipRight)].Location);
case EKinectJointType::AnkleRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::KneeRight)].Location);
case EKinectJointType::FootRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::AnkleRight)].Location);
case EKinectJointType::SpineShoulder:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineMid)].Location);
case EKinectJointType::HandTipLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::WristLeft)].Location);
case EKinectJointType::ThumbLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::HandLeft)].Location);
case EKinectJointType::HandTipRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::WristRight)].Location);
case EKinectJointType::ThumbRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::HandRight)].Location);
default:
return 0.f;
}
}
float UNeoKinectBody::JointDotVector(EKinectJointType Joint, FVector Vector)
{
Vector.Normalize();
return FVector::DotProduct(
Vector,
FRotationMatrix(Joints[static_cast<int32>(Joint)].Orientation).GetScaledAxis(EAxis::Z));
}
void UNeoKinectBody::UpdateFromKinectBody(const NeoKinect::KinectBody& kBody, const NeoKinect::KinectSensor& Sensor)
{
using namespace NeoKinect;
// For throwing BeginTrack/Lost events after update
bool bWasTracked = bIsTracked;
ClippedEdges.Left = kBody.clippedEdges.left;
ClippedEdges.Right = kBody.clippedEdges.right;
ClippedEdges.Top = kBody.clippedEdges.top;
ClippedEdges.Bottom = kBody.clippedEdges.bottom;
bIsTracked = kBody.isTracked;
bIsRestricted = kBody.isRestricted;
TrackingId = kBody.trackingId;
HandLeftState = static_cast<EKinectHandState>(kBody.handLeftState);
HandLeftTrackingState = static_cast<EKinectTrackingState>(kBody.handLeftTrackingState);
HandRightState = static_cast<EKinectHandState>(kBody.handRightState);
HandRightTrackingState = static_cast<EKinectTrackingState>(kBody.handRightTrackingState);
LeanTrackingState = static_cast<EKinectTrackingState>(kBody.leanTrackingState);
Lean.Set(kBody.lean.x, kBody.lean.y);
// update joints only if being tracked
if (bIsTracked)
{
// camera space joints transforms
for (int32 j = 0; j < KinectJointCount; ++j)
{
FKinectJoint& uJoint = Joints[j];
KinectJoint joint = kBody.joints[j];
uJoint.Confidence = static_cast<EKinectTrackingState>(joint.trackingState);
uJoint.Location.Set(
joint.position.Z * 100.f,
-joint.position.X * 100.f,
joint.position.Y * 100.f);
// Quaternion conversion from Kinect camera's coord system to Unreal's
FQuat Quat = FQuat(
-joint.orientation.z,
joint.orientation.x,
-joint.orientation.y,
joint.orientation.w
);
// In order to match the joints orientations to Unreal Mannequin, we
// still have some problems:
// - Kinect's bones extend along its UP axis, which is Z in Unreal.
// But Unreal's Mannequin skeleton's bones extend along X (as it is
// in Maya). So we re-create the orientation swapping the axes.
//
// - Some bones extend towards X, some against it. That's good for doing
// mirrored rotations in the animation software, so we'll keep it here
// by flipping some axes.
//
// - The pelvis and spine bones front axis is the same as the limbs side axis.
// In the Mannequin these match up, so we'll have to invert some here.
#define JNT_ID(Name) static_cast<uint8>(EKinectJointType::Name)
uint8 JointIndex = static_cast<uint8>(uJoint.Type);
if (JointIndex <= JNT_ID(Head) || JointIndex == JNT_ID(SpineShoulder))
{ // for spine bones
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
Quat.GetAxisZ(),
Quat.GetAxisY()).Rotator();
}
else if (JointIndex <= JNT_ID(WristLeft)
|| JointIndex >= JNT_ID(HipRight) && JointIndex <= JNT_ID(FootRight))
{ // for left arm and right leg
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
Quat.GetAxisZ(),
Quat.GetAxisX()).Rotator();
}
else if ( (JointIndex >= JNT_ID(ShoulderRight) && JointIndex <= JNT_ID(WristRight))
|| (JointIndex >= JNT_ID(HipLeft) && JointIndex <= JNT_ID(FootLeft))
|| JointIndex == JNT_ID(HandTipRight) || JointIndex == JNT_ID(ThumbRight)
)
{ // for right arm and left leg
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
-Quat.GetAxisZ(),
-Quat.GetAxisX()).Rotator();
}
else if (JointIndex == JNT_ID(HandTipLeft) || JointIndex == JNT_ID(ThumbLeft))
{ // Left hand
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
Quat.GetAxisZ(),
Quat.GetAxisX()).Rotator();
}
else if (JointIndex == JNT_ID(HandLeft))
{ // left hand
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
Quat.GetAxisZ(),
-Quat.GetAxisX()).Rotator();
}
else if (JointIndex == JNT_ID(HandRight))
{ // right hand
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
-Quat.GetAxisZ(),
Quat.GetAxisX()).Rotator();
}
#undef JNT_ID
} // camera space joints transforms
if (UNeoKinectManager::IsUsingJointsColorSpaceTransforms())
{
// color space with depth joints transforms
for (int32 j = 0; j < KinectJointCount; ++j)
{
FKinectJoint& uJoint = Joints[j];
KinectJoint joint = kBody.joints[j];
// Convert camera space location to color space with depth
Vector3 ColorDepthLocation = UNeoKinectManager::CameraLocationToColorWithDepth(Vector3(joint.position));
if (ColorDepthLocation.IsZero())
{
// fallback for when the joint can not be seen in color camera
if (uJoint.Type == EKinectJointType::SpineBase) // oh shit! Our reference is gone!
{
uJoint.ColorLocation = uJoint.Location;
continue;
}
FKinectJoint &SpineBase = Joints[static_cast<int32>(EKinectJointType::SpineBase)];
uJoint.ColorLocation = SpineBase.ColorLocation + uJoint.Location - SpineBase.Location;
continue;
}
uJoint.ColorLocation.Set(
ColorDepthLocation.z * 100.0f,
-ColorDepthLocation.x * 100.0f,
ColorDepthLocation.y * 100.0f);
} // color space with depth joints transforms
// orientations in color space with depth
#define JOINT(Name) Joints[static_cast<int32>(EKinectJointType::Name)]
#define J_DIR(FromJoint, ToJoint) JOINT(ToJoint).ColorLocation - JOINT(FromJoint).ColorLocation
#define J_ROT(JointName, Twist) JOINT(JointName).ColorOrientation = FRotationMatrix::MakeFromX##Twist(XDir, Twist##Dir).Rotator()
FVector XDir, YDir, ZDir, Dir1, Dir2;
FQuat Rot1, Rot2, RLerp;
float JointsDot;
// Spine Base
XDir = J_DIR(SpineBase, SpineMid);
ZDir = J_DIR(HipLeft, HipRight);
J_ROT(SpineBase, Z);
// Spine Middle
// same XDir
ZDir = (ZDir + J_DIR(ShoulderLeft, ShoulderRight)) / 2.0f; // intermediary between hip and shoulders twist
J_ROT(SpineMid, Z);
// Spine Shoulder
XDir = J_DIR(SpineMid, SpineShoulder);
ZDir = J_DIR(ShoulderLeft, ShoulderRight);
J_ROT(SpineShoulder, Z);
// Neck
XDir = J_DIR(SpineShoulder, Neck);
// same ZDir
J_ROT(Neck, Z);
// Head
XDir = J_DIR(Neck, Head);
// same ZDir
J_ROT(Head, Z);
// shoulder left
XDir = J_DIR(SpineShoulder, ShoulderLeft);
ZDir = J_DIR(SpineMid, SpineShoulder);
J_ROT(ShoulderLeft, Z);
// elbow left
XDir = J_DIR(ShoulderLeft, ElbowLeft);
// interpolate between a rot for fully stretched arm and one for bent
Dir1 = FVector::CrossProduct(J_DIR(ElbowLeft, WristLeft), J_DIR(ElbowLeft, ShoulderLeft)); // bent
Rot1 = FRotationMatrix::MakeFromXZ(XDir, Dir1).ToQuat(); // bent
Dir2 = FRotationMatrix(JOINT(ShoulderLeft).ColorOrientation).GetScaledAxis(EAxis::Y); // stretched
Rot2 = FRotationMatrix::MakeFromXY(XDir, Dir2).ToQuat(); // stretched
Dir1 = J_DIR(ShoulderLeft, ElbowLeft); Dir2 = J_DIR(ElbowLeft, WristLeft);
Dir1.Normalize(); Dir2.Normalize();
JointsDot = FVector::DotProduct(Dir1, Dir2);
RLerp = FQuat::Slerp(Rot2, Rot1,
FMath::Clamp(FMath::Acos(JointsDot) / 0.349f /*~20°*/, 0.f, 1.f)
);
RLerp.Normalize();
JOINT(ElbowLeft).ColorOrientation = RLerp.Rotator();
// wrist left
XDir = J_DIR(ElbowLeft, WristLeft);
YDir = J_DIR(HandLeft, ThumbLeft);
J_ROT(WristLeft, Y);
// hand left
XDir = J_DIR(WristLeft, HandTipLeft);
ZDir = J_DIR(ThumbLeft, HandLeft);
J_ROT(HandLeft, Z);
// hand tip Left
JOINT(HandTipLeft).ColorOrientation = JOINT(HandLeft).ColorOrientation;
// thumb left
XDir = J_DIR(HandLeft, ThumbLeft);
YDir = FRotationMatrix(JOINT(HandLeft).ColorOrientation).GetScaledAxis(EAxis::Y);
J_ROT(ThumbLeft, Y);
// shoulder right
XDir = J_DIR(ShoulderRight, SpineShoulder);
ZDir = J_DIR(SpineShoulder, SpineMid);
J_ROT(ShoulderRight, Z);
// elbow right
XDir = J_DIR(ElbowRight, ShoulderRight);
// interpolate between a rot for fully stretched arm and one for bent
Dir1 = FVector::CrossProduct(J_DIR(ElbowRight, WristRight), J_DIR(ElbowRight, ShoulderRight)); // bent
Rot1 = FRotationMatrix::MakeFromXZ(XDir, Dir1).ToQuat(); // bent
Dir2 = FRotationMatrix(JOINT(ShoulderRight).ColorOrientation).GetScaledAxis(EAxis::Y); // stretched
Rot2 = FRotationMatrix::MakeFromXY(XDir, Dir2).ToQuat(); // stretched
Dir1 = J_DIR(ShoulderRight, ElbowRight); Dir2 = J_DIR(ElbowRight, WristRight);
Dir1.Normalize(); Dir2.Normalize();
JointsDot = FVector::DotProduct(Dir1, Dir2);
RLerp = FQuat::Slerp(Rot2, Rot1,
FMath::Clamp(FMath::Acos(JointsDot) / 0.349f /*~20°*/, 0.f, 1.f)
);
RLerp.Normalize();
JOINT(ElbowRight).ColorOrientation = RLerp.Rotator();
// wrist right
XDir = J_DIR(WristRight, ElbowRight);
YDir = J_DIR(ThumbRight, HandRight);
J_ROT(WristRight, Y);
// hand right
XDir = J_DIR(HandTipRight, WristRight);
ZDir = -YDir;
J_ROT(HandRight, Z);
// hand tip right
JOINT(HandTipRight).ColorOrientation = JOINT(HandRight).ColorOrientation;
// thumb right
XDir = J_DIR(ThumbRight, HandRight);
YDir = FRotationMatrix(JOINT(HandRight).ColorOrientation).GetScaledAxis(EAxis::Y);
J_ROT(ThumbRight, Y);
// Hip Left
XDir = J_DIR(HipLeft, SpineBase);
ZDir = J_DIR(SpineMid, SpineBase);
J_ROT(HipLeft, Z);
// Knee left
XDir = J_DIR(KneeLeft, HipLeft);
Dir1 = FVector::CrossProduct(J_DIR(KneeLeft, AnkleLeft), J_DIR(KneeLeft, HipLeft)); // bent
Rot1 = FRotationMatrix::MakeFromXZ(XDir, Dir1).ToQuat(); // bent
Dir2 = FRotationMatrix(JOINT(SpineMid).ColorOrientation).GetScaledAxis(EAxis::Y); // stretched
Rot2 = FRotationMatrix::MakeFromXY(XDir, Dir2).ToQuat(); // stretched
Dir1 = J_DIR(HipLeft, KneeLeft); Dir2 = J_DIR(KneeLeft, AnkleLeft);
Dir1.Normalize(); Dir2.Normalize();
JointsDot = FVector::DotProduct(Dir1, Dir2);
RLerp = FQuat::Slerp(Rot2, Rot1, FMath::Clamp((FMath::Acos(JointsDot) - 0.2618f) / 0.5236f, 0.f, 1.f));
RLerp.Normalize();
JOINT(KneeLeft).ColorOrientation = RLerp.Rotator();
// ankle left
XDir = J_DIR(AnkleLeft, KneeLeft);
ZDir = FRotationMatrix(JOINT(KneeLeft).ColorOrientation).GetScaledAxis(EAxis::Z);
J_ROT(AnkleLeft, Z);
// foot left
// same X and Z dirs
J_ROT(FootLeft, Z);
// hip right
XDir = J_DIR(SpineBase, HipRight);
ZDir = J_DIR(SpineBase, SpineMid);
J_ROT(HipRight, Z);
// Knee Right
XDir = J_DIR(HipRight, KneeRight);
Dir1 = FVector::CrossProduct(J_DIR(KneeRight, AnkleRight), J_DIR(KneeRight, HipRight)); // bent
Rot1 = FRotationMatrix::MakeFromXZ(XDir, Dir1).ToQuat(); // bent
Dir2 = -FRotationMatrix(JOINT(SpineMid).ColorOrientation).GetScaledAxis(EAxis::Y); // stretched
Rot2 = FRotationMatrix::MakeFromXY(XDir, Dir2).ToQuat(); // stretched
Dir1 = J_DIR(HipRight, KneeRight); Dir2 = J_DIR(KneeRight, AnkleRight);
Dir1.Normalize(); Dir2.Normalize();
JointsDot = FVector::DotProduct(Dir1, Dir2);
RLerp = FQuat::Slerp(Rot2, Rot1, FMath::Clamp((FMath::Acos(JointsDot) - 0.2618f) / 0.5236f, 0.f, 1.f));
RLerp.Normalize();
JOINT(KneeRight).ColorOrientation = RLerp.Rotator();
// Ankle right
XDir = J_DIR(KneeRight, AnkleRight);
ZDir = FRotationMatrix(JOINT(KneeRight).ColorOrientation).GetScaledAxis(EAxis::Z);
J_ROT(AnkleRight, Z);
// foot right
// same X and Z dirs
J_ROT(FootRight, Z);
#undef JOINT
#undef J_DIR
#undef J_ROT
} // if (bUsingJointsColorSpaceTransforms)
} // if (bIsTracked)
if (bWasTracked != bIsTracked)
{
if (bIsTracked)
OnBodyBeginTrack.Broadcast(BodyIndex);
else
OnBodyLost.Broadcast(BodyIndex);
}
}
void UNeoKinectBody::Reset()
{
using namespace NeoKinect;
ClippedEdges.Left =
ClippedEdges.Right =
ClippedEdges.Top =
ClippedEdges.Bottom = false;
bIsTracked = false;
bIsRestricted = false;
TrackingId = 0Ui64;
HandLeftState = EKinectHandState::NotTracked;
HandLeftTrackingState = EKinectTrackingState::NotTracked;
HandRightState = EKinectHandState::NotTracked;
HandRightTrackingState = EKinectTrackingState::NotTracked;
LeanTrackingState = EKinectTrackingState::NotTracked;
Lean.Set(0.f, 0.f);
if (Joints.Num() != KinectJointCount)
return;
for (int32 j = 0; j < KinectJointCount; ++j)
{
if (!Joints.IsValidIndex(j))
continue;
FKinectJoint& Joint = Joints[j];
Joint.Confidence = EKinectTrackingState::NotTracked;
Joint.Location.Set(0.f, 0.f, 0.f);
Joint.ColorLocation.Set(0.f, 0.f, 0.f);
Joint.Orientation.Roll =
Joint.Orientation.Pitch =
Joint.Orientation.Yaw = 0.f;
Joint.ColorOrientation.Roll =
Joint.ColorOrientation.Pitch =
Joint.ColorOrientation.Yaw = 0.f;
}
}
@@ -0,0 +1,216 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#include "NeoKinectFace.h"
#include "NeoKinectBody.h"
#include "NeoKinectManager.h"
void FIntBoundingBox::FromRectI(const RectI& Rect)
{
Left = Rect.Left;
Top = Rect.Top;
Right = Rect.Right;
Bottom = Rect.Bottom;
}
void FFacePoints2D::FromKinectFacePoints(const NeoKinect::KinectFacePoints& Points)
{
LeftEye.X = Points.leftEye.x;
LeftEye.Y = Points.leftEye.y;
RightEye.X = Points.rightEye.x;
RightEye.Y = Points.rightEye.y;
Nose.X = Points.nose.x;
Nose.Y = Points.nose.y;
LeftMouthCorner.X = Points.leftMouthCorner.x;
LeftMouthCorner.Y = Points.leftMouthCorner.y;
RightMouthCorner.X = Points.rightMouthCorner.x;
RightMouthCorner.Y = Points.rightMouthCorner.y;
}
void FFacePoints3D::FromKinectFacePoints(const NeoKinect::KinectFacePoints& ColorPoints)
{
NeoKinect::Vector3 Location = UNeoKinectManager::ColorWithDepthFromColorOnly(ColorPoints.leftEye) * 100.f;
LeftEye.Set(-Location.z, Location.x, Location.y);
Location = UNeoKinectManager::ColorWithDepthFromColorOnly(ColorPoints.rightEye) * 100.f;
RightEye.Set(-Location.z, Location.x, Location.y);
Location = UNeoKinectManager::ColorWithDepthFromColorOnly(ColorPoints.nose) * 100.f;
Nose.Set(-Location.z, Location.x, Location.y);
Location = UNeoKinectManager::ColorWithDepthFromColorOnly(ColorPoints.leftMouthCorner) * 100.f;
LeftMouthCorner.Set(-Location.z, Location.x, Location.y);
Location = UNeoKinectManager::ColorWithDepthFromColorOnly(ColorPoints.rightMouthCorner) * 100.f;
RightMouthCorner.Set(-Location.z, Location.x, Location.y);
}
UNeoKinectFace::UNeoKinectFace()
: Index(INDEX_NONE)
, bIsTracked(false)
, TrackingId(INDEX_NONE)
, IsEngaged(EDetectionResult::Unknown)
, IsHappy(EDetectionResult::Unknown)
, IsLookingAway(EDetectionResult::Unknown)
, IsMouthMoved(EDetectionResult::Unknown)
, IsMouthOpen(EDetectionResult::Unknown)
, IsLeftEyeClosed(EDetectionResult::Unknown)
, IsRightEyeClosed(EDetectionResult::Unknown)
, IsWearingGlasses(EDetectionResult::Unknown)
{}
UNeoKinectFace::~UNeoKinectFace()
{}
void UNeoKinectFace::GetEyeClosedAsExec(EKinectBodySide Eye, EDetectionResult& Result)
{
if (Eye == EKinectBodySide::Left)
{
Result = IsLeftEyeClosed;
}
else
{
Result = IsRightEyeClosed;
}
}
EDetectionResult UNeoKinectFace::GetEyeClosed(EKinectBodySide Eye) const
{
if (Eye == EKinectBodySide::Left)
return IsLeftEyeClosed;
return IsRightEyeClosed;
}
void UNeoKinectFace::GetIsEngagedAsExec(EDetectionResult& Result)
{
Result = IsEngaged;
}
void UNeoKinectFace::GetIsHappyAsExec(EDetectionResult& Result)
{
Result = IsHappy;
}
void UNeoKinectFace::GetIsLookingAwayAsExec(EDetectionResult& Result)
{
Result = IsLookingAway;
}
void UNeoKinectFace::GetIsMouthMovedAsExec(EDetectionResult& Result)
{
Result = IsMouthMoved;
}
void UNeoKinectFace::GetIsMouthOpenAsExec(EDetectionResult& Result)
{
Result = IsMouthOpen;
}
void UNeoKinectFace::GetIsWearingGlassesAsExec(EDetectionResult& Result)
{
Result = IsWearingGlasses;
}
bool UNeoKinectFace::IsFromBody(const UNeoKinectBody* Body) const
{
return Body->TrackingId == TrackingId;
}
void UNeoKinectFace::UpdateFromKinectFace(const NeoKinect::KinectFace& kFace)
{
const bool bWasTracked = bIsTracked;
bIsTracked = kFace.bIsTracked;
TrackingId = kFace.trackingId;
ColorBoundingBox.FromRectI(kFace.boxColor);
InfraredBoundingBox.FromRectI(kFace.boxInfrared);
ColorAlignmentPoints.FromKinectFacePoints(kFace.pointsColor);
InfraredAlignmentPoints.FromKinectFacePoints(kFace.pointsInfrared);
ColorWithDepthAlignmentPoints.FromKinectFacePoints(kFace.pointsColor);
/* Camera space face transform */
Location.Set(
kFace.pivot.Z * 100.f,
-kFace.pivot.X * 100.f,
kFace.pivot.Y * 100.f);
const FQuat Quat = FQuat(
-kFace.rotation.z,
kFace.rotation.x,
-kFace.rotation.y,
kFace.rotation.w
);
Orientation = FRotationMatrix::MakeFromXZ(
Quat.GetAxisZ(),
-Quat.GetAxisY()).Rotator();
/* Color space face location */
if (UNeoKinectManager::IsUsingJointsColorSpaceTransforms())
{
using namespace NeoKinect;
// Convert camera space location to color space with depth
const Vector3 ColorDepthLocation = UNeoKinectManager::CameraLocationToColorWithDepth(Vector3(kFace.pivot));
if (ColorDepthLocation.IsZero())
{
ColorLocation = Location;
}
else
{
ColorLocation.Set(
ColorDepthLocation.z * 100.0f,
-ColorDepthLocation.x * 100.0f,
ColorDepthLocation.y * 100.0f);
}
}
IsEngaged = static_cast<EDetectionResult>(kFace.isEngaged);
IsHappy = static_cast<EDetectionResult>(kFace.isHappy);
IsLookingAway = static_cast<EDetectionResult>(kFace.isLookingAway);
IsMouthMoved = static_cast<EDetectionResult>(kFace.isMouthMoved);
IsMouthOpen = static_cast<EDetectionResult>(kFace.isMouthOpen);
IsLeftEyeClosed = static_cast<EDetectionResult>(kFace.isLeftEyeClosed);
IsRightEyeClosed = static_cast<EDetectionResult>(kFace.isRightEyeClosed);
IsWearingGlasses = static_cast<EDetectionResult>(kFace.isWearingGlasses);
if (bWasTracked != bIsTracked)
{
if (bIsTracked)
OnFaceBeginTrack.Broadcast(Index);
else
OnFaceLost.Broadcast(Index);
}
}
void UNeoKinectFace::Reset()
{
bIsTracked = false;
TrackingId = 0ULL;
ColorBoundingBox = FIntBoundingBox();
InfraredBoundingBox = FIntBoundingBox();
ColorAlignmentPoints = FFacePoints2D();
InfraredAlignmentPoints = FFacePoints2D();
Location.Set(0.f, 0.f, 0.f);
ColorLocation = Location;
Orientation.Yaw =
Orientation.Pitch =
Orientation.Roll = 0.f;
IsEngaged =
IsHappy =
IsLookingAway =
IsMouthMoved =
IsMouthOpen =
IsLeftEyeClosed =
IsRightEyeClosed =
IsWearingGlasses = EDetectionResult::Unknown;
}
@@ -0,0 +1,5 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#include "Modules/ModuleManager.h"
IMPLEMENT_MODULE(FDefaultModuleImpl, NeoKinectUnreal)
@@ -0,0 +1,10 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "Windows/AllowWindowsPlatformTypes.h"
#pragma warning(push)
#pragma warning(disable : 4471)
#include "NeoKinect.h"
#pragma warning(pop)
#include "Windows/HideWindowsPlatformTypes.h"
@@ -0,0 +1,127 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "Helpers/GuardedNeoKinect.h"
#include "NeoKinectEnums.h"
#include <atomic>
#include "CoreMinimal.h"
#include "HAL/Runnable.h"
DECLARE_LOG_CATEGORY_CLASS(KinectThreadLog, Log, All);
struct FThreadFrameBuffer
{
NeoKinect::KinectFrameType FrameType = NeoKinect::KinectFrameType::Count;
NeoKinect::KinectPixelFormat PixelFormat = NeoKinect::KinectPixelFormat::None;
NeoKinect::KinectCoordinateSpace TargetSpace = NeoKinect::KinectCoordinateSpace::Count;
bool bRemapped = false;
~FThreadFrameBuffer();
void Configure(
NeoKinect::KinectFrameType Frame,
NeoKinect::KinectPixelFormat PixelFormat,
NeoKinect::KinectCoordinateSpace Space,
bool IsRemapped);
bool AllocateBuffers(uint32 Size);
void DeallocateBuffers();
bool IsAllocated() const;
// Returns an updated buffer. If it was already consumed, return nullptr
const uint8* GetReadBuffer(bool bConsume = true);
uint8* GetWriteBuffer() const;
void SetDirty();
bool TryLock();
void Lock();
void Unlock();
void LockWrite();
void UnlockWrite();
private:
std::atomic<bool> bIsNew = false;
uint8 *Buffer = nullptr;
uint8 *BackBuffer = nullptr;
FCriticalSection ReadMutex;
FCriticalSection WriteMutex;
void SwapBuffers();
};
// Kinect polling thread
class FKinectThread : public FRunnable
{
private:
/*
Kinect updates its frames, at most, at 30fps.
But, as some of them will be available at slightly different times, I'll poll
it at double that rate to be sure.
*/
static constexpr float cDesiredFPS = 60.f;
// Singleton instance, can access the thread any time via static accessor, if it is active
static FKinectThread *Runnable;
// Thread to run FRunnable on
FRunnableThread *Thread;
// Stop this thread? Uses Thread Safe Counter
FThreadSafeCounter StopTaskCounter;
// Kinect sensor
NeoKinect::KinectSensor *pKinect;
// Bodies data
NeoKinect::KinectBody Bodies[NeoKinect::KinectBodyCount];
// Face tracking data
NeoKinect::KinectFace Faces[NeoKinect::KinectBodyCount];
public:
// Constructor
FKinectThread(NeoKinect::KinectSensor *pSensor);
// Destructor
~FKinectThread() override;
// Frames data. These control which frame buffers should or not be updated.
static FThreadFrameBuffer FrameBuffers[static_cast<uint64>(EKinectFrame::Count)];
// Controls whether body data should be read
static bool bUseBodyTracking;
// True if body data was updated since last reading
bool bBodiesDirty;
// Controls whether face tracking data should be read
static bool bUseFaceTracking;
// True if faces data was updated since last reading
bool bFacesDirty;
// Mutex for body/face data thread safety
static FCriticalSection BodiesFacesCritSec;
/*
Start the thread and the worker from static (easy access)
This code ensures only 1 KinectThread will be able to run at a time.
This function returns a handle to the newly started instance.
*/
static FKinectThread *InitKinectPolling(NeoKinect::KinectSensor *pSensor);
// Makes sure this thread has stopped properly
void EnsureCompletion();
// Shuts down the thread. Static so it can easily be called from outside the thread context
static void Shutdown();
// Gets last polled bodies data. Returns nullptr if no new data
const NeoKinect::KinectBody *GetBodiesData();
// Gets last polled faces data. Returns nullptr if no new data
const NeoKinect::KinectFace *GetFacesData();
// Begin FRunnable interface
virtual bool Init() override;
virtual uint32 Run() override;
virtual void Stop() override;
virtual void Exit() override;
// End FRunnable interface
};
@@ -0,0 +1,297 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "CoreMinimal.h"
#include "NeoKinectEnums.h"
#include "NeoKinectBody.generated.h"
namespace NeoKinect
{
struct KinectBody;
class KinectSensor;
}
/**
* Joint properties.
*
* The Location and Orientation of joints, by default, is given in camera space,
* which center is a point in the Kinect sensor not matching the color camera.
* So, to use joints over the color frame image, these coordinates must be reprojected.
* To get these reprojected values, use ColorLocation and ColorOrientation.
*/
USTRUCT(BlueprintType, Category = "NeoKinect|Structs")
struct FKinectJoint
{
GENERATED_BODY()
/** Joint type. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints")
EKinectJointType Type;
/** Joint tracking confidence. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints")
EKinectTrackingState Confidence;
/** Joint location relative to Kinect sensor in camera space. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "position translation transform camera"))
FVector Location;
/** Joint orientation relative to Kinect sensor in camera space. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "transform rotation angles camera"))
FRotator Orientation;
/** Joint location relative to Kinect sensor in color space with depth. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "position translation transform color"))
FVector ColorLocation;
/** Joint orientation relative to Kinect sensor in color space. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "transform rotation angles color"))
FRotator ColorOrientation;
FKinectJoint() :
Type(EKinectJointType::AnkleLeft),
Confidence(EKinectTrackingState::NotTracked),
Location(FVector::ZeroVector),
Orientation(FRotator::ZeroRotator),
ColorLocation(FVector::ZeroVector),
ColorOrientation(FRotator::ZeroRotator)
{}
};
/**
* Which edges are clipping a body on the view frustum.
*/
USTRUCT(BlueprintType, Category = "NeoKinect|Structs")
struct FKinectClippedEdges
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool Left;
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool Right;
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool Top;
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool Bottom;
FKinectClippedEdges() :
Left(false), Right(false), Top(false), Bottom(false)
{}
};
/**
* Represents a trackable body.
*/
UCLASS(BlueprintType, Category = "NeoKinect|Body")
class NEOKINECTUNREAL_API UNeoKinectBody : public UObject
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBodyTrackDelegate, int32, BodyIndex);
UNeoKinectBody();
~UNeoKinectBody() override;
/** Retrieves this body index (0~5). */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
int32 BodyIndex;
/** Body's unique Tracking ID. */
UPROPERTY()
int64 TrackingId;
/** Body clipped edges from the sensor's view. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body", Meta = (Keywords = "cut borders"))
FKinectClippedEdges ClippedEdges;
/** If this body represents a tracked user. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool bIsTracked;
/** If body's movement is restricted somehow. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool bIsRestricted;
/** Left hand pose */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Hands")
EKinectHandState HandLeftState;
/** Left hand tracking state */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Hands")
EKinectTrackingState HandLeftTrackingState;
/** Right hand pose */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Hands")
EKinectHandState HandRightState;
/** Right hand tracking state */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Hands")
EKinectTrackingState HandRightTrackingState;
/** Body lean tracking confidence. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
EKinectTrackingState LeanTrackingState;
/**
* Body lean.
* Ranges from -1 to 1 being left to right in X and back to front in Y.
* Values of -1 and 1 represent roughly 45 degrees of inclination.
*/
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body", Meta = (Keywords = "inclination"))
FVector2D Lean;
UPROPERTY()
TArray<FKinectJoint> Joints;
/** Called when this Body object starts tracking an user */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect|Body")
FBodyTrackDelegate OnBodyBeginTrack;
/** Called when this Body object loses track of its user */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect|Body")
FBodyTrackDelegate OnBodyLost;
/**
* Get Left/Right hand state as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Hand Which hand?
* @param States Current Hand state.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Body|Hands", Meta = (ExpandEnumAsExecs = "States"))
void GetHandStateAsExec(EKinectBodySide Hand, EKinectHandState& States);
/**
* Get Left/Right hand state.
*
* @param Hand Which hand?
* @return Current Hand state.
*/
UFUNCTION(BlueprintPure, Category = "SamrtKinect|Body|Hands")
EKinectHandState GetHandState(EKinectBodySide Hand) const;
/**
* Get Left/Right hand tracking state as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Hand Which hand?
* @param States Current Hand tracking state.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Body|Hands", Meta = (ExpandEnumAsExecs = "States"))
void GetHandConfidenceAsExec(EKinectBodySide Hand, EKinectTrackingState& States);
/**
* Get Left/Right hand tracking state.
*
* @param Hand Which hand?
* @return Current Hand tracking state.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Hands")
EKinectTrackingState GetHandConfidence(EKinectBodySide Hand) const;
/**
* Get body lean tracking confidence as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param States Lean tracking state.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Body", Meta = (ExpandEnumAsExecs = "States", Keywords = "inclination"))
void GetLeanConfidenceAsExec(EKinectTrackingState& States);
/**
* Contains this body's each joint tracking state and transform.
*
* @return This body's each joint tracking state and transform.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints")
TArray<FKinectJoint> GetJoints();
/**
* Get a Joint's location relative to Kinect sensor.
*
* @param Joint The joint type.
* @return Joint's location relative to Kinect sensor.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "position translation transform"))
FVector GetJointLocation(EKinectJointType Joint);
/**
* Get a Joint's orientation relative to Kinect sensor.
*
* @param Joint The joint type.
* @return Joint's orientation relative to Kinect sensor.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "rotation angles transform"))
FRotator GetJointOrientation(EKinectJointType Joint);
/**
* Get a Joint's location relative to Kinect sensor aligned with the Color frame.
* So, if a camera is created with the same FOV as the Kinect Color camera's and
* the Color frame texture is used as background, this location will align perfectly
* with the corresponding joint in the Color image.
*
* @param Joint The joint type.
* @return Joint's location relative to Kinect sensor.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "position translation transform"))
FVector GetJointLocationColor(EKinectJointType Joint);
/**
* Get a Joint's orientation relative to Kinect sensor aligned with the Color
* aligned joint location.
*
* @param Joint The joint type.
* @return Joint's orientation relative to Kinect sensor.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "rotation angles transform"))
FRotator GetJointOrientationColor(EKinectJointType Joint);
/**
* Get a Joint's tracking confidence as exec pins.
* Useful for redirecting the code path, like the Branch node.
*
* @param Joint The joint type.
* @param States Joint's tracking confidence.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Body|Joints", Meta = (ExpandEnumAsExecs = "States"))
void GetJointConfidenceAsExec(EKinectJointType Joint, EKinectTrackingState& States);
/**
* Get a Joint's tracking confidence.
*
* @param Joint The joint type.
* @return Joint's tracking confidence.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints")
EKinectTrackingState GetJointConfidence(EKinectJointType Joint);
/**
* Calculate the distance between two joints in the Kinect sensor space.
*
* @param JointA First joint.
* @param JointB Second joint.
* @return Distance between the two joints.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "size length"))
float GetJointsDistance(EKinectJointType JointA, EKinectJointType JointB);
/**
* Calculate the distance from a Joint's location to it's parent's location.
* Useful for getting bone sizes. Eg.: for Left Elbow, the result will be the
* distance from Left Elbow to Left Shoulder. Spine Base is the skeleton's root,
* so it will return 0.
*
* @param Joint The joint type.
* @return Distance from Joint to it's parent.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "size length"))
float GetBoneLength(EKinectJointType Joint);
/**
* The Dot product between a Joint direction and a Vector.
*
* @return -1 ~ 1: 1 = same direction as vector. -1 = opposite from vector.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "pointing direction lookat"))
float JointDotVector(EKinectJointType Joint, FVector Vector);
void UpdateFromKinectBody(const NeoKinect::KinectBody& kBody, const NeoKinect::KinectSensor& Sensor);
void Reset();
};
@@ -0,0 +1,134 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "CoreMinimal.h"
#include "NeoKinectEnums.generated.h"
/**
* The types of frames that can be read from the Kinect sensor.
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EKinectFrame : uint8
{
/** Normal camera texture. Standard RGBA format (alpha = 1) */
Color = 0,
/** Depth sensor texture. Red channel only. Values are in millimeters */
Depth = 1,
/** Depth sensor ranging from 0 to 1. Standard RGBA format (alpha = 1) */
NormalizedDepth = 2,
/** Body index value in Red channel. In material editor, multiply Red by 255
* to get actual index value (0~5) */
BodyIndex = 3,
/** Body indexes with a color for each index. Standard RGBA format.
* Alpha = 1 where there's a body, 0 otherwise */
BodyIndexColor = 4 UMETA(DisplayName = "Colored Body Index"),
/** Not implemented */
Color_DepthSpace = 5 UMETA(DisplayName = "Color in Depth Space"),
/** Depth sensor image remapped to fit the color frame. Red channel only.
* Values in millimeters, 0 where remapping is not possible. */
Depth_ColorSpace = 6 UMETA(DisplayName = "Depth in Color Space"),
/** Body index values remapped to fit the bodies in the color frame.
* Red channel only. In material editor, multiply Red by 255 to get actual
* index value (0~5). */
BodyIndex_ColorSpace= 7 UMETA(DisplayName = "Body Index in Color Space"),
/** Infrared sensor image. Red channel only, usually very dark. */
Infrared = 8,
/** Long exposure infrared sensor image. Red channel only. Can lower the
* sensor's FPS to get brighter image */
LongExposureInfrared= 9,
/** Cast to int to get the number of frame types */
Count = 10
};
/**
* Body sides.
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EKinectBodySide : uint8
{
Left = 0,
Right = 1
};
/**
* Possible hand states
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EKinectHandState : uint8
{
/** Pretty close to NotTracked. */
Unknown = 0,
NotTracked = 1,
Open = 2,
Closed = 3,
/** Closed hand with index and middle fingers pointing up, near each other */
Lasso = 4
};
/**
* Tracking confidence.
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EKinectTrackingState : uint8
{
NotTracked = 0,
/** Tracked by deduction. Sometimes good, many times terrible. */
Inferred = 1,
Tracked = 2
};
/**
* The joints that the Kinect sensor tracks on each body. 25 total.
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EKinectJointType : uint8
{
/** Near the pelvis */
SpineBase = 0,
/** Near the stomach */
SpineMid = 1 UMETA(DisplayName = "Spine Middle"),
Neck = 2,
Head = 3,
ShoulderLeft = 4 UMETA(DisplayName = "Left Shoulder"),
ElbowLeft = 5 UMETA(DisplayName = "Left Elbow"),
WristLeft = 6 UMETA(DisplayName = "Left Wrist"),
/** In the palm center */
HandLeft = 7 UMETA(DisplayName = "Left Hand"),
ShoulderRight = 8 UMETA(DisplayName = "Right Shoulder"),
ElbowRight = 9 UMETA(DisplayName = "Right Elbow"),
WristRight = 10 UMETA(DisplayName = "Right Wrist"),
/** In the palm center */
HandRight = 11 UMETA(DisplayName = "Right Hand"),
/** Femur/Hip joint */
HipLeft = 12 UMETA(DisplayName = "Left Hip"),
KneeLeft = 13 UMETA(DisplayName = "Left Knee"),
AnkleLeft = 14 UMETA(DisplayName = "Left Ankle"),
/** Tip of the foot */
FootLeft = 15 UMETA(DisplayName = "Left Foot"),
/** Femur/Hip joint */
HipRight = 16 UMETA(DisplayName = "Right Hip"),
KneeRight = 17 UMETA(DisplayName = "Right Knee"),
AnkleRight = 18 UMETA(DisplayName = "Right Ankle"),
/** Tip of the foot */
FootRight = 19 UMETA(DisplayName = "Right Foot"),
/** Between the collar bones */
SpineShoulder = 20,
HandTipLeft = 21 UMETA(DisplayName = "Left Hand Tip"),
ThumbLeft = 22 UMETA(DisplayName = "Left Thumb"),
HandTipRight = 23 UMETA(DisplayName = "Right Hand Tip"),
ThumbRight = 24 UMETA(DisplayName = "Right Thumb")
};
/**
* Face properties detection results
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EDetectionResult : uint8
{
Unknown = 0,
No = 1,
/** Consider it more as a "No" */
Maybe = 2,
Yes = 3
};
@@ -0,0 +1,290 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "NeoKinectEnums.h"
#include "Windows/AllowWindowsPlatformTypes.h"
#pragma warning(push)
#pragma warning(disable : 4471)
#include "Kinect.Face.h"
#pragma warning(pop)
#include "Windows/HideWindowsPlatformTypes.h"
#include "NeoKinectFace.generated.h"
class UNeoKinectBody;
namespace NeoKinect
{
struct KinectFace;
struct KinectFacePoints;
}
/**
* Describes the edges of a rectangle in a texture (int values)
*/
USTRUCT(BlueprintType, Category = "NeoKinect|Structs")
struct FIntBoundingBox
{
GENERATED_BODY()
// Left rectangle edge
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
int32 Left = 0;
// Top rectangle edge
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
int32 Top = 0;
// Right rectangle edge
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
int32 Right = 0;
// Bottom rectangle edge
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
int32 Bottom = 0;
void FromRectI(const RectI& Rect);
};
/**
* Lists the 2D of each face alignment point.
*/
USTRUCT(BlueprintType, Category = "NeoKinect|Structs")
struct FFacePoints2D
{
GENERATED_BODY()
// Left eye location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector2D LeftEye = FVector2D::Zero();
// Right eye location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector2D RightEye = FVector2D::Zero();
// Nose location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector2D Nose = FVector2D::Zero();
// Left mouth corner location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector2D LeftMouthCorner = FVector2D::Zero();
// Right mouth corner location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector2D RightMouthCorner = FVector2D::Zero();
void FromKinectFacePoints(const NeoKinect::KinectFacePoints& Points);
};
/**
* Lists the 3D locations of each face alignment point.
*/
USTRUCT(BlueprintType, Category = "NeoKinect|Structs")
struct FFacePoints3D
{
GENERATED_BODY()
// Left eye location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector LeftEye = FVector::Zero();
// Right eye location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector RightEye = FVector::Zero();
// Nose location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector Nose = FVector::Zero();
// Left mouth corner location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector LeftMouthCorner = FVector::Zero();
// Right mouth corner location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector RightMouthCorner = FVector::Zero();
void FromKinectFacePoints(const NeoKinect::KinectFacePoints& ColorPoints);
};
/**
* Represents a trackable body's face.
*/
UCLASS(BlueprintType, Category = "NeoKinect|Face")
class NEOKINECTUNREAL_API UNeoKinectFace : public UObject
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFaceTrackDelegate, int32, FaceIndex);
UNeoKinectFace();
~UNeoKinectFace() override;
// This face's index in the faces array
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
int32 Index;
// If this face is being tracked.
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
bool bIsTracked;
// Face tracking Id. Matches it's body TrackingId.
UPROPERTY()
int64 TrackingId;
// The face bounding box in Color space.
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FIntBoundingBox ColorBoundingBox;
// The face bounding box in Depth space.
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FIntBoundingBox InfraredBoundingBox;
// The face alignment points in Color space.
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FFacePoints2D ColorAlignmentPoints;
// The face alignment points in Depth space.
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FFacePoints2D InfraredAlignmentPoints;
/**
* The face alignment points aligned with the Color frame from the camera's
* point of view, at a distance taken from Depth frame.
* For this to have valid values the remapped Depth in Color Space frame must be activated.
*/
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FFacePoints3D ColorWithDepthAlignmentPoints;
// The face rotation.
UPROPERTY(BlueprintReadOnly, Meta = (Keywords = "rotation quaternion angles transform"), Category = "NeoKinect|Face")
FRotator Orientation;
// The head pivot location.
UPROPERTY(BlueprintReadOnly, Meta = (Keywords = "location position center head translation transform"), Category = "NeoKinect|Face")
FVector Location;
/** Head location aligned with the Color frame and with depth.
* Only valid if color transforms were activated using SetUseJointsColorSpaceTransforms */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector ColorLocation;
// Is the user engaged?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsEngaged;
// Does the user looks happy?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsHappy;
// Is the user looking away?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsLookingAway;
// Did the user mouth move?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsMouthMoved;
// Is the user mouth opened?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsMouthOpen;
// Is the user left eye closed?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsLeftEyeClosed;
// Is the user right eye closed?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsRightEyeClosed;
// Is the user wearing glasses?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsWearingGlasses;
/** Called when this face object starts tracking a user's face */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect|Face")
FFaceTrackDelegate OnFaceBeginTrack;
/** Called when this face object loses its user's face */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect|Face")
FFaceTrackDelegate OnFaceLost;
/**
* Checks if left/right eye is closed and use the result as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Eye Which eye?
* @param Result Is selected eye closed? Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetEyeClosedAsExec(EKinectBodySide Eye, EDetectionResult& Result);
/**
* Checks if left/right eye is closed.
*
* @param Eye Which eye?
* @return Is selected eye closed? Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face")
EDetectionResult GetEyeClosed(EKinectBodySide Eye) const;
/**
* Get IsEngaged as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsEngagedAsExec(EDetectionResult& Result);
/**
* Gets IsHappy as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsHappyAsExec(EDetectionResult& Result);
/**
* Gets IsLookingAway as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsLookingAwayAsExec(EDetectionResult& Result);
/**
* Gets IsMouthMoved as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsMouthMovedAsExec(EDetectionResult& Result);
/**
* Gets IsMouthOpen as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsMouthOpenAsExec(EDetectionResult& Result);
/**
* Gets IsWearingGlasses as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsWearingGlassesAsExec(EDetectionResult& Result);
/**
* Checks if this face belongs to the passed Body instance.
*
* @param Body The body to test from.
* @return True if this face belongs to the passed body.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face")
bool IsFromBody(const UNeoKinectBody* Body) const;
void UpdateFromKinectFace(const NeoKinect::KinectFace& kFace);
void Reset();
};
@@ -0,0 +1,612 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "Helpers/GuardedNeoKinect.h"
#include "NeoKinectEnums.h"
#include "PixelFormat.h"
#include "Containers/Ticker.h"
#include "NeoKinectManager.generated.h"
class FKinectThread;
class UNeoKinectBody;
class UNeoKinectFace;
class UTextureRenderTarget2D;
DECLARE_LOG_CATEGORY_CLASS(NeoKinectLog, Log, All);
/** Structure to manage Kinect frame types lifetime and updating */
struct FFrameTextureData
{
UTextureRenderTarget2D *Texture = nullptr;
uint32 Width = 0;
uint32 Height = 0;
uint32 PixelSize = 0;
uint32 BufferSize = 0;
EPixelFormat UPixelFormat = EPixelFormat::PF_Unknown;
NeoKinect::KinectPixelFormat KPixelFormat = NeoKinect::KinectPixelFormat::None;
EKinectFrame UFrameType = EKinectFrame::Count;
NeoKinect::KinectFrameType KFrameType = NeoKinect::KinectFrameType::Count;
NeoKinect::KinectCoordinateSpace TargetSpace = NeoKinect::KinectCoordinateSpace::Count;
bool bIsSRGB = false;
bool bRemapped = false;
~FFrameTextureData();
inline bool IsTextureValid() const;
void Release();
NeoKinect::FrameDescription GetFrameDescription(const NeoKinect::KinectSensor *Kinect) const;
};
/**
* Access the Kinect v2 sensor capabilities.
*
* When using any Remapped type of frame or any Coordinate Remapping methods,
* Depth frame use will automatically be switched on, since those functionalities
* depend on it. But don't worry about performance. The depth frame will only
* get written and updated to a texture on the GPU if the user explicitly
* asks for it using SetUseFrame().
*
* When using Face Tracking, Body Tracking will also be automatically switched on.
* Face Tracking depends on Body Tracking for the head pivot position and for
* fixing issues when looking for a Face.
*/
UCLASS(Category = "NeoKinect", BlueprintType)
class NEOKINECTUNREAL_API UNeoKinectManager : public UObject
{
GENERATED_BODY()
public:
/** Stores the bodies that Kinect can track. The array is created only once
* and lasts until the end of the program. */
static TArray<UNeoKinectBody*> Bodies;
/** Stores the faces that Kinect can track. The array is created only once
* and lasts until the end of the program. */
static TArray<UNeoKinectFace*> Faces;
/** Stores all the possible frame types Kinect or the program can create.
* The array is created only once and lasts until the end of the program. */
static FFrameTextureData FramesData[static_cast<int32>(EKinectFrame::Count)];
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBodyTrackDelegate, UNeoKinectBody*, Body);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFaceTrackDelegate, UNeoKinectFace*, Face);
/** Constructor: used only to bind the static dispatcher to the bodies/faces
* tracking events. */
UNeoKinectManager();
/** Destructor: unbind the static dispatcher events and makes sure to
* un-initialize the Kinect sensor before the program exits. */
~UNeoKinectManager() override;
/*-------------------------------------------------------------------------
EVENTS
-------------------------------------------------------------------------*/
private:
/** Class singleton used to give Blueprints access to the tracking events. */
static UNeoKinectManager* EventsDispatcher;
public:
/** Called when the Kinect sensor has just detected a new user's body. */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect")
FBodyTrackDelegate OnBodyBeginTrack;
/** Called when the Kinect sensor has just lost track of a user's body. */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect")
FBodyTrackDelegate OnBodyLost;
/** Called when the Kinect sensor has just detected a new user's face. */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect")
FFaceTrackDelegate OnFaceBeginTrack;
/** Called when the Kinect sensor has just lost track of a user's face. */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect")
FFaceTrackDelegate OnFaceLost;
/**
* Use the object result of this function to bind to the Kinect body/face
* tracking events. You can also bind to individual bodies/faces using their
* respective objects.
*
* @return Kinect events dispatcher object.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect")
static UNeoKinectManager* GetKinectEventsDispatcher();
/*////////////////////////////////////////////////////////////////////////////
BLUEPRINT FUNCTIONS
////////////////////////////////////////////////////////////////////////////*/
/*
* BASICS
*/
/**
* Initializes the Kinect sensor.
*
* Even though this function always works without errors, check if the sensor
* has really started by calling IsKinectAvailable. It can take a while, so
* check in intervals until it does.
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Init Kinect Sensor", Keywords = "turn on initialize enable start begin"), Category = "NeoKinect")
static void InitSensor();
/**
* Returns if sensor is already on.
*
* It may take a while for the sensor to initialize, so keep checking. If it
* takes too long, either there's no connected sensor or it's malfunctioning.
*
* @return true if Sensor is available and operational.
*/
UFUNCTION(BlueprintPure, meta = (Keywords = "on working works usable"), Category = "NeoKinect")
static bool IsKinectAvailable();
/**
* Use very carefully! Will shutdown the sensor and also invalidate any of its
* frame textures (color, depth etc). Make sure you call this when none of them
* are in use (preferably when quitting the game). Remember: with great power,
* comes great responsibility!
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Uninit Kinect Sensor", Keywords = "turn off uninitialize disable end"), Category = "NeoKinect")
static void UninitSensor();
/**
* Retrieves the Kinect sensor or NULL if not initialized.
*
* @return The Kinect sensor or NULL if not initialized.
*/
static NeoKinect::KinectSensor* GetSensor();
/**
* Gets sensor tilt in degrees. It represents the pitch of the sensor in Unreal
* coordinates (X pointing the same direction as the Kinect camera). Negative
* values mean it's pointing down.
*
* @return Kinect sensor tilt in degrees. Negative values mean it's pointing down.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect", meta = (DisplayName = "Get Kinect Tilt"))
static float GetSensorTilt();
/**
* Kinect height from the ground and ground normal. Only works if the sensor
* can see the ground.
* Bodies reading must be in use, as this data is read from the Body frames.
*
* @param Height Kinect distance from the ground
* @return Ground normal
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect", meta = (DisplayName = "Get Kinect Ground Plane", Keywords = "height normal location floor"))
static FVector GetGroundPlane(float &Height);
/*
* EXPOSURE CONTROL
*/
/*
* Fully automatic exposure setting with exposure compensation. Only affects the Color frame.
* @param exposureCompensation Negative value gives an underexposed image,
* positive gives an overexposed image.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|ColorExposure", meta = (DisplayName = "Set Kinect to Auto Exposure", Keywords = "image frame color brightness"))
static void SetAutoExposure(float exposureCompensation = 0.0f);
/*
* Sets a pseudo-exposure time in ms. Only affects the Color frame.
*
* The actual frame integration time is set to a multiple of fluorescent light period
* that is shorter than the requested time e.g. requesting 16 ms will set 10 ms
* in Australia (100Hz), 8.33 ms in USA (120Hz).
* The gain is automatically set to compensate for the reduced integration time.
*
* Requesting less than a single fluorescent light period will set the integration time
* to the requested value.
*
* To set the shortest non-flickering integration period for any country, simply set
* a pseudo-exposure time of between (10.0, 16.667) ms, which will automatically drop
* the integration time to 10 or 8.3 ms depending on country, while setting the analog
* gain control to a brighter value.
*
* @param pseudoExposureTime Pseudo-exposure time in ms. [0.0, 640]ms
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|ColorExposure", meta = (DisplayName = "Set Kinect to Semi Auto Exposure", Keywords = "image frame color brightness"))
static void SetSemiAutoExposure(float pseudoExposureTime);
/*
* Manually set frame exposure time in ms and analog gain. Only affects the Color frame.
* @param frameExposureTime Time of exposure for each frame, in ms. [0.0, 640]ms
* @param analogGain Exposure analog gain.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|ColorExposure", meta = (DisplayName = "Set Kinect to Manual Exposure", Keywords = "image frame color brightness"))
static void SetManualExposure(float frameExposureTime, float analogGain);
/*
* COORDINATE REMAPPING
*/
/**
* Converts a 3D location in Camera space to Color space with depth, so the new
* location matches the Color frame position from camera's point of view
* without turning into 2D.
*
* @param CameraLocation The 3D Camera space coordinate to be converted (in Unreal units)
* @return Realigned 3D coordinate that matches the Color frame from the camera's
* point of view (in Unreal units)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector CameraLocationToColorWithDepth(const FVector& CameraLocation);
/** Native NeoKinect implementation of the above */
static NeoKinect::Vector3 CameraLocationToColorWithDepth(NeoKinect::Vector3 CameraLocation);
/**
* Remaps a 3D camera location to a 2D point in the Color frame.
*
* @param CameraLocation The 3D Camera space coordinate to be remapped (in Unreal units)
* @return The point location in the Color frame that matches the Camera
* location input (in pixels)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector2D CameraLocationToColorPoint(const FVector& CameraLocation);
/**
* Remaps a 3D camera location to a point in the Depth frame.
*
* @param CameraLocation The 3D Camera space coordinate to be remapped (in Unreal units)
* @return The point location in the Depth frame that matches the Camera
* location input (in pixels)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector2D CameraLocationToDepthPoint(const FVector& CameraLocation);
/**
* Creates a Color with depth point from a Color point only. Viewed from the
* camera perspective, this point aligns with the Color frame without losing
* it's depth.
*
* This method uses the remapped Color buffer in Depth space so, for it to
* work, the Depth in Color Space frame must be in use.
*
* @param ColorLocation A point in the Color frame to be remapped (in pixels)
* @return The 3D version of the Color point (in Unreal units)
*/
static NeoKinect::Vector3 ColorWithDepthFromColorOnly(const NeoKinect::Vector2& ColorLocation);
/**
* Remaps a point location from the Depth frame to the Color frame.
*
* @param DepthPoint A point in the Depth frame (in pixels)
* @return Remapped Depth frame point to a Color frame point (in pixels)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector2D DepthPointToColorPoint(FVector2D DepthPoint);
/**
* Remaps a point location from the Depth frame to the 3D camera space.
*
* @param DepthPoint A point in the Depth frame (in pixels)
* @return Remapped Depth frame point to a 3D Camera location (in Unreal units)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector DepthPointToCameraLocation(FVector2D DepthPoint);
/**
* Remaps a point from the Color frame to the Depth frame.
*
* @param ColorPoint A point in the Color frame (in pixels)
* @return Color point remapped to its related location in the Depth frame (in pixels)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector2D ColorPointToDepthPoint(FVector2D ColorPoint);
/*
* TEXTURES (Color, Depth etc)
*/
/**
* Get a frame horizontal and vertical field of view.
*
* @param FrameType The frame type to request the FOV from.
* @return x = horizontal FOV, y = vertical FOV
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "width height FOV texture angle field of view", DisplayName = "Get Kinect FOV"))
static FVector2D GetFrameFieldOfView(EKinectFrame FrameType);
/**
* Get a frame texture width and height in pixels.
*
* @param FrameType The frame type to request the size from.
* @return x = width, y = height
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "width height texture resolution", DisplayName = "Get Kinect Frame Size"))
static FIntPoint GetFrameSize(EKinectFrame FrameType);
/**
* Enables or disables updating of a Kinect frame type.
*
* @param FrameType The frame type to enable/disable
* @param bEnable If the selected frame type should be enabled
* @return The Frame texture. Null if Enable = false.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Frames", Meta = (Keywords = "texture sensor camera enable disable toggle", DisplayName = "Set Use Kinect Frame"))
static UTextureRenderTarget2D* SetUseFrame(EKinectFrame FrameType, bool bEnable);
/**
* Returns if a Kinect frame type is enabled. If it is, it means you can get
* it's texture with GetFrame.
*
* @param FrameType The frame type to ask for.
* @return If FrameType is enabled or not.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "texture sensor camera enabled", DisplayName = "Get Is Using Kinect Frame"))
static bool GetIsUsingFrame(EKinectFrame FrameType);
/**
* Get the texture that represents the requested frame type.
*
* @param FrameType The frame type to get the texture from.
* @return The texture that represents the requested frame type. Null if it has
* not been enabled.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "texture sensor camera", DisplayName = "Get Kinect Frame"))
static UTextureRenderTarget2D* GetFrame(EKinectFrame FrameType);
/**
* Gets the depth value from a specific pixel on the Depth frame.
*
* @param Coordinate A point in the Depth frame (in pixels)
* @return The Depth value (distance from the Kinect sensor) in the input point
* (in centimeters)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "sample texture sensor camera"))
static float GetDepthFramePixel(FVector2D Coordinate);
/**
* Gets the depth value from a specific pixel on the Color frame
*
* @param Coordinate A point in the Color frame (in pixels)
* @return The Depth value (distance from the Kinect sensor) in the input point
* (in centimeters)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "sample texture remap"))
static float GetDepthFromColorFramePixel(FVector2D Coordinate);
/**
* Gets the color value from a specific pixel on the Color frame.
*
* @param Coordinate A point in the Color frame (in pixels)
* @return The Color value in the input point
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "sample texture sensor camera"))
static FLinearColor GetColorFramePixel(FVector2D Coordinate);
/*
* BODIES / JOINTS
*/
/**
* Array of all bodies (tracked and not tracked).
* The bodies in the array are updated automatically, so you don't need to
* request the array more than once.
*
* @return Bodies array.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body", meta = (DisplayName = "Get Kinect Bodies"))
static TArray<UNeoKinectBody*> GetBodies();
/**
* Array with only the currently tracked bodies.
*
* @return The currently tracked bodies.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body", meta = (DisplayName = "Get Kinect Tracked Bodies"))
static TArray<UNeoKinectBody*> GetTrackedBodies();
/**
* Gets tracked body whose Spine Base joint is nearest to the Kinect sensor.
*
* @param HasBody If there's at least one tracked body.
* @return The nearest body from the sensor. Null if no bodies are tracked.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body", meta = (DisplayName = "Get Kinect Nearest Body"))
static UNeoKinectBody* GetNearestBody(bool& HasBody);
/**
* Toggle the use of Color texture aligned joint locations and orientations.
*
* Calculating these remapped transforms introduce a small overhead, so it's
* disabled by default and reading the Color values from a Joint will return
* Zero values until this is enabled.
*
* @param bUse Enable usage of Joints' Color space transforms?
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Body", meta = (Keywords = "joints remap"))
static void SetUseJointsColorSpaceTransforms(const bool bUse);
/**
* Return if Joints' Color space transforms usage is enabled
*
* @return True if Joints' Color space transforms usage is enabled
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body")
static bool IsUsingJointsColorSpaceTransforms();
/**
* Cast a Body in the bodies array to its index number.
*
* @param Body The body instance.
* @return Body index.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body", Meta = (CompactNodeTitle = "->", Keywords = "convert cast"))
static int32 BodyToIndex(const UNeoKinectBody* Body);
/**
* Cast an index to a body from the bodies array.
*
* @param Index Index.
* @return Body instance. If index > 5 or index < 0, it's the body at index 0.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body", Meta = (CompactNodeTitle = "->", Keywords = "convert cast"))
static UNeoKinectBody* IndexToBody(const int32 Index);
/**
* Cast the Joint type to its joint index number.
*
* @param Joint Joint type.
* @return Index.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (CompactNodeTitle = "->", Keywords = "convert cast", BlueprintThreadSafe))
static int32 JointToIndex(const EKinectJointType Joint);
/**
* Cast a joint index to its Joint type.
*
* @param Index Index.
* @return Joint type. If index >= joint count or index < 0, it's Spine Base joint.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (CompactNodeTitle = "->", Keywords = "convert cast"))
static EKinectJointType IndexToJoint(const int32 Index);
/*
* FACE TRACKING
*/
/**
* Array of faces (tracked and not tracked). The faces instances are updated
* automatically, so you don't need to request the array more than once.
*
* @return Faces array.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face", meta = (DisplayName = "Get Kinect Faces"))
static TArray<UNeoKinectFace*> GetFaces();
/**
* Array with only the currently tracked faces.
*
* @return The currently tracked faces.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face", meta = (DisplayName = "Get Kinect Tracked Faces"))
static TArray<UNeoKinectFace*> GetTrackedFaces();
/**
* Gets tracked face whose Head joint is nearest from the sensor.
*
* @param bHasAnyFace If there's at least one tracked face.
* @return The nearest face from the sensor. Null if no faces are tracked.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face", meta = (DisplayName = "Get Kinect Nearest Face"))
static UNeoKinectFace* GetNearestFace(bool& bHasAnyFace);
/**
* Cast a Face to its index number.
*
* @param Face The face instance.
* @return Face index.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face", Meta = (CompactNodeTitle = "->", Keywords = "convert cast"))
static int32 FaceToIndex(const UNeoKinectFace* Face);
/**
* Cast a face index to a Face object.
*
* @param Index Index.
* @return Face instance. If index > 5 or index < 0, it's the face at index 0.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face", Meta = (CompactNodeTitle = "->", Keywords = "convert cast"))
static UNeoKinectFace* IndexToFace(const int32 Index);
/*
* CONSTANTS
*/
/**
* The number of bodies the Kinect device can track simultaneously.
*
* @return The number of bodies the Kinect device can track simultaneously.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Constants", meta = (Keywords = "length number size capability"))
static int32 GetTrackableBodyCount();
/**
* The number of faces the Kinect device can track simultaneously.
*
* @return The number of faces the Kinect device can track simultaneously.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Constants", meta = (Keywords = "length number size capability"))
static int32 GetTrackableFaceCount();
/**
* The number of joints the Kinect device can track in each tracked body.
*
* @return The number of joints the Kinect device can track in each tracked body.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Constants", meta = (Keywords = "length number size capability"))
static int32 GetJointCount();
/*////////////////////////////////////////////////////////////////////////////
BLUEPRINT FUNCTIONS END
////////////////////////////////////////////////////////////////////////////*/
private:
/** NeoKinect API sensor */
static NeoKinect::KinectSensor* Kinect;
/** Does the sensor polling */
static FKinectThread* KinectThread;
/** FTicker handle so we can stop ticking after disconnecting from the sensor */
static FTSTicker::FDelegateHandle TickerHandle;
//TODO: figure out a way to know when a level is unloaded
//static bool bAutoShutdown;
/** Should we read and update the bodies data each frame? */
static bool bUsingBodyFrame;
/** Should we remap the bodies transforms into 3D Color space each frame? */
static bool bUsingJointsColorSpaceTransforms;
/** Should we read and update the faces data each frame? */
static bool bUsingFaceFrame;
/** Initialize the bodies, faces and frames arrays */
static void InitStatics();
/** Updates bodies, faces and frames each frame */
static bool OnTick(float DeltaTime);
static bool IsUsingDepth();
static bool IsUsingBodyIndex();
static bool IsUsingColor();
/** Creates a 2D dynamic texture for frames usage */
static UTextureRenderTarget2D* CreateTexture(int32 Width, int32 Height, EPixelFormat PixelFormat, bool sRGB);
/** Updates all currently enabled frames. Executed in the Render thread */
static void UpdateFrames();
/** Delete frames textures memory */
static void ReleaseFrameResources(FFrameTextureData&);
/** Enable/disable bodies frame reading */
static void SetUseBodyFrame(bool bUse = true);
/** Converts bodies joints transforms to Unreal coordinate space */
static void ProcessBodies(const NeoKinect::KinectBody* KBodies);
/** Zero all bodies values. Used when disconnecting from the sensor */
static void ResetBodies();
/** Enable/disable faces frame reading */
static void SetUseFaceFrame(bool bUse = true);
/** Converts faces points transforms to Unreal coordinate space */
static void ProcessFaces(const NeoKinect::KinectFace* KFaces);
/** Zero all faces values. Used when disconnecting from the sensor */
static void ResetFaces();
UFUNCTION()
void BodyBeginTrackBroadcast(int32 BodyIndex);
UFUNCTION()
void BodyLostBroadcast(int32 BodyIndex);
UFUNCTION()
void FaceBeginTrackBroadcast(int32 FaceIndex);
UFUNCTION()
void FaceLostBroadcast(int32 FaceIndex);
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,298 @@
/* this ALWAYS GENERATED file contains the definitions for the interfaces */
/* File created by MIDL compiler version 8.00.0595 */
/* at Sun Oct 19 12:54:09 2014
*/
/* Compiler settings for ..\..\idl\Kinect.INPC.idl:
Oicf, W1, Zp8, env=Win64 (32b run), target_arch=AMD64 8.00.0595
protocol : dce , ms_ext, c_ext, robust
error checks: allocation ref bounds_check enum stub_data
VC __declspec() decoration level:
__declspec(uuid()), __declspec(selectany), __declspec(novtable)
DECLSPEC_UUID(), MIDL_INTERFACE()
*/
/* @@MIDL_FILE_HEADING( ) */
#pragma warning( disable: 4049 ) /* more than 64k source lines */
/* verify that the <rpcndr.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCNDR_H_VERSION__
#define __REQUIRED_RPCNDR_H_VERSION__ 475
#endif
/* verify that the <rpcsal.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCSAL_H_VERSION__
#define __REQUIRED_RPCSAL_H_VERSION__ 100
#endif
#include "rpc.h"
#include "rpcndr.h"
#ifndef __RPCNDR_H_VERSION__
#error this stub requires an updated version of <rpcndr.h>
#endif // __RPCNDR_H_VERSION__
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/
#ifndef __Kinect2EINPC_h__
#define __Kinect2EINPC_h__
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
/* Forward Declarations */
#ifndef __INotifyPropertyChanged_FWD_DEFINED__
#define __INotifyPropertyChanged_FWD_DEFINED__
typedef interface INotifyPropertyChanged INotifyPropertyChanged;
#endif /* __INotifyPropertyChanged_FWD_DEFINED__ */
#ifndef __IPropertyChangedEventArgs_FWD_DEFINED__
#define __IPropertyChangedEventArgs_FWD_DEFINED__
typedef interface IPropertyChangedEventArgs IPropertyChangedEventArgs;
#endif /* __IPropertyChangedEventArgs_FWD_DEFINED__ */
/* header files for imported files */
#include "oaidl.h"
#include "ocidl.h"
#include "mmreg.h"
#ifdef __cplusplus
extern "C"{
#endif
/* interface __MIDL_itf_Kinect2EINPC_0000_0000 */
/* [local] */
typedef INT_PTR WAITABLE_HANDLE;
typedef INT64 TIMESPAN;
extern RPC_IF_HANDLE __MIDL_itf_Kinect2EINPC_0000_0000_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_Kinect2EINPC_0000_0000_v0_0_s_ifspec;
#ifndef __INotifyPropertyChanged_INTERFACE_DEFINED__
#define __INotifyPropertyChanged_INTERFACE_DEFINED__
/* interface INotifyPropertyChanged */
/* [object][local][uuid] */
EXTERN_C const IID IID_INotifyPropertyChanged;
#if defined(__cplusplus) && !defined(CINTERFACE)
MIDL_INTERFACE("D27A5C77-32E9-4283-A046-9D693E29E3E7")
INotifyPropertyChanged : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE SubscribePropertyChanged(
/* [annotation][out][retval] */
_Out_ WAITABLE_HANDLE *waitableHandle) = 0;
virtual HRESULT STDMETHODCALLTYPE UnsubscribePropertyChanged(
/* [annotation][in] */
_In_ WAITABLE_HANDLE waitableHandle) = 0;
virtual HRESULT STDMETHODCALLTYPE GetPropertyChangedEventData(
/* [annotation][in] */
_In_ WAITABLE_HANDLE waitableHandle,
UINT bufferSize,
/* [annotation][out][retval] */
_Out_writes_z_(bufferSize) WCHAR *propertyName) = 0;
};
#else /* C style interface */
typedef struct INotifyPropertyChangedVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
INotifyPropertyChanged * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
INotifyPropertyChanged * This);
ULONG ( STDMETHODCALLTYPE *Release )(
INotifyPropertyChanged * This);
HRESULT ( STDMETHODCALLTYPE *SubscribePropertyChanged )(
INotifyPropertyChanged * This,
/* [annotation][out][retval] */
_Out_ WAITABLE_HANDLE *waitableHandle);
HRESULT ( STDMETHODCALLTYPE *UnsubscribePropertyChanged )(
INotifyPropertyChanged * This,
/* [annotation][in] */
_In_ WAITABLE_HANDLE waitableHandle);
HRESULT ( STDMETHODCALLTYPE *GetPropertyChangedEventData )(
INotifyPropertyChanged * This,
/* [annotation][in] */
_In_ WAITABLE_HANDLE waitableHandle,
UINT bufferSize,
/* [annotation][out][retval] */
_Out_writes_z_(bufferSize) WCHAR *propertyName);
END_INTERFACE
} INotifyPropertyChangedVtbl;
interface INotifyPropertyChanged
{
CONST_VTBL struct INotifyPropertyChangedVtbl *lpVtbl;
};
#ifdef COBJMACROS
#define INotifyPropertyChanged_QueryInterface(This,riid,ppvObject) \
( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )
#define INotifyPropertyChanged_AddRef(This) \
( (This)->lpVtbl -> AddRef(This) )
#define INotifyPropertyChanged_Release(This) \
( (This)->lpVtbl -> Release(This) )
#define INotifyPropertyChanged_SubscribePropertyChanged(This,waitableHandle) \
( (This)->lpVtbl -> SubscribePropertyChanged(This,waitableHandle) )
#define INotifyPropertyChanged_UnsubscribePropertyChanged(This,waitableHandle) \
( (This)->lpVtbl -> UnsubscribePropertyChanged(This,waitableHandle) )
#define INotifyPropertyChanged_GetPropertyChangedEventData(This,waitableHandle,bufferSize,propertyName) \
( (This)->lpVtbl -> GetPropertyChangedEventData(This,waitableHandle,bufferSize,propertyName) )
#endif /* COBJMACROS */
#endif /* C style interface */
#endif /* __INotifyPropertyChanged_INTERFACE_DEFINED__ */
#ifndef __IPropertyChangedEventArgs_INTERFACE_DEFINED__
#define __IPropertyChangedEventArgs_INTERFACE_DEFINED__
/* interface IPropertyChangedEventArgs */
/* [object][local][uuid] */
EXTERN_C const IID IID_IPropertyChangedEventArgs;
#if defined(__cplusplus) && !defined(CINTERFACE)
MIDL_INTERFACE("574E9321-9DD9-41C8-BC51-C11F3F38D6B5")
IPropertyChangedEventArgs : public IUnknown
{
public:
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_PropertyName(
UINT bufferSize,
/* [annotation][out][retval] */
_Out_writes_z_(bufferSize) WCHAR *propertyName) = 0;
};
#else /* C style interface */
typedef struct IPropertyChangedEventArgsVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
IPropertyChangedEventArgs * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
IPropertyChangedEventArgs * This);
ULONG ( STDMETHODCALLTYPE *Release )(
IPropertyChangedEventArgs * This);
/* [propget] */ HRESULT ( STDMETHODCALLTYPE *get_PropertyName )(
IPropertyChangedEventArgs * This,
UINT bufferSize,
/* [annotation][out][retval] */
_Out_writes_z_(bufferSize) WCHAR *propertyName);
END_INTERFACE
} IPropertyChangedEventArgsVtbl;
interface IPropertyChangedEventArgs
{
CONST_VTBL struct IPropertyChangedEventArgsVtbl *lpVtbl;
};
#ifdef COBJMACROS
#define IPropertyChangedEventArgs_QueryInterface(This,riid,ppvObject) \
( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )
#define IPropertyChangedEventArgs_AddRef(This) \
( (This)->lpVtbl -> AddRef(This) )
#define IPropertyChangedEventArgs_Release(This) \
( (This)->lpVtbl -> Release(This) )
#define IPropertyChangedEventArgs_get_PropertyName(This,bufferSize,propertyName) \
( (This)->lpVtbl -> get_PropertyName(This,bufferSize,propertyName) )
#endif /* COBJMACROS */
#endif /* C style interface */
#endif /* __IPropertyChangedEventArgs_INTERFACE_DEFINED__ */
/* Additional Prototypes for ALL interfaces */
/* end of Additional Prototypes */
#ifdef __cplusplus
}
#endif
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,68 @@
#pragma once
#include <memory>
#include "NuiSensor.h"
#include "Lockable.h"
namespace Xbox { namespace Kinect { class NuiSensor; } }
class KinectExposure
{
public:
KinectExposure() = default;
// Fully automatic exposure setting.
// Exposure compensation: negative value gives an underexposed image,
// positive gives an overexposed image.
void SetAutoExposure(float exposure_compensation = 0);
// Sets a pseudo-exposure time in ms, value in range [0.0, 640] ms.
//
// The actual frame integration time is set to a multiple of fluorescent light period
// that is shorter than the requested time e.g. requesting 16 ms will set 10 ms
// in Australia (100Hz), 8.33 ms in USA (120Hz).
// The gain is automatically set to compensate for the reduced integration time.
//
// Requesting less than a single fluorescent light period will set the integration time
// to the requested value.
//
// To set the shortest non-flickering integration period for any country, simply set
// a pseudo-exposure time of between (10.0, 16.667) ms, which will automatically drop
// the integration time to 10 or 8.3 ms depending on country, while setting the analog
// gain control to a brighter value.
void SetSemiAutoExposure(float pseudo_exposure_time_ms);
// Manually set true exposure time and analog gain.
void SetManualExposure(float integration_time_ms, float analog_gain);
// Use one of the SET commands.
void SetFloat(Xbox::Kinect::NUISENSOR_RGB_COMMAND_TYPE command, float value);
void SetUint(Xbox::Kinect::NUISENSOR_RGB_COMMAND_TYPE command, uint32_t value);
// Use one of the GET commands.
uint32_t GetCommand(Xbox::Kinect::NUISENSOR_RGB_COMMAND_TYPE command);
float GetCommandFloat(Xbox::Kinect::NUISENSOR_RGB_COMMAND_TYPE command);
private:
LockableSharedPtr<Xbox::Kinect::NuiSensor> _sensor;
void Connect();
//private Dictionary<NUISENSOR_RGB_COMMAND_TYPE, uint> GetCommands(NUISENSOR_RGB_COMMAND_TYPE[] commands)
//{
// NUISENSOR_RGB_CHANGE_STREAM_SETTING setting;
// NUISENSOR_RGB_CHANGE_STREAM_SETTING_REPLY reply;
// setting.SequenceId = 0;
// setting.Commands = commands.Select(command = > new NUISENSOR_RGB_CHANGE_SETTING_CMD{ Cmd = (uint)command, Arg = 0 }).ToArray();
// lock(_sensor)
// {
// _sensor.ColorChangeCameraSettings(ref setting, out reply);
// }
// return commands.Zip(reply.Status, (c, r) = > { return Tuple.Create(c, r.Data); }).ToDictionary(t = > t.Item1, t = > t.Item2);
//}
uint32_t SetCommand(Xbox::Kinect::NUISENSOR_RGB_COMMAND_TYPE command, uint32_t argument);
void SetCommand(Xbox::Kinect::NUISENSOR_RGB_COMMAND_TYPE command, float argument);
};
@@ -0,0 +1,108 @@
#pragma once
#ifndef LOCKABLE_H_
#define LOCKABLE_H_
#include <memory>
#include <mutex>
// Acts like a pointer to the locked object, but holds a lock for the given variable.
template<typename T>
class Locked
{
public:
Locked(T& obj, std::mutex& mutex) : _obj(obj), _mutex(mutex) { _mutex.lock(); }
Locked& operator=(const Locked&) = delete;
~Locked() { _mutex.unlock(); }
// Access members of the locked value.
const T* operator->() const noexcept { return &_obj; }
// Access members of the locked value.
T* operator->() noexcept { return &_obj; }
// Access the locked value.
const T& operator*() const noexcept { return _obj; }
// Access the locked value.
T& operator*() noexcept { return _obj; }
private:
T& _obj;
std::mutex& _mutex;
};
// Lockable value type.
template<typename T>
class Lockable
{
public:
// Only exists if T() exists.
Lockable() = default;
explicit Lockable(T obj) : _obj(std::move(obj)) {}
// Lock-guards the value for the lifetime of the returned object.
auto lock() const noexcept { return Locked<const T>(_obj, _mutex); }
// Lock-guards the value for the lifetime of the returned object.
auto lock() noexcept { return Locked<T>(_obj, _mutex); }
private:
T _obj;
mutable std::mutex _mutex;
};
// Acts like a shared_ptr, but holds a lock for the given pointer.
template<typename TPtr>
class LockedPtr
{
public:
LockedPtr(TPtr& obj, std::mutex& mutex) : _obj(obj), _mutex(mutex) { _mutex.lock(); }
LockedPtr& operator=(const LockedPtr&) = delete;
~LockedPtr() { _mutex.unlock(); }
// Access members of the locked shared_ptr.
auto* operator->() const noexcept { return _obj.get(); }
// Dereference the locked shared_ptr.
auto& operator*() const noexcept { return *_obj; }
// Test if the locked shared_ptr is null.
explicit operator bool() const noexcept { return bool(_obj); }
// Assign to the locked shared_ptr (assigns the shared_ptr, not the inner T value).
void operator=(TPtr obj) { _obj = std::move(obj); }
// Reset the locked shared_ptr.
void reset() noexcept { _obj.reset(); }
// Get the pointer type.
// WARNING: this is potentially unsafe since the data can be passed around unlocked.
TPtr value() const noexcept { return _obj; }
private:
TPtr& _obj;
std::mutex& _mutex;
};
// Lockable templated pointer type.
template<typename TPtr>
class LockablePtr
{
public:
LockablePtr()
: _obj()
, _mutex(std::make_shared<std::mutex>())
{
}
explicit LockablePtr(TPtr obj)
: _obj(std::move(obj))
, _mutex(std::make_shared<std::mutex>())
{
}
// Lock-guards the pointer for the lifetime of the returned object.
auto lock() const noexcept { return LockedPtr<const TPtr>(_obj, *_mutex); }
// Lock-guards the pointer for the lifetime of the returned object.
auto lock() noexcept { return LockedPtr<TPtr>(_obj, *_mutex); }
private:
TPtr _obj;
mutable std::shared_ptr<std::mutex> _mutex; // Shared so object is copyable e.g. TPtr is shared_ptr<T>
};
template<typename T>
using LockableSharedPtr = LockablePtr<std::shared_ptr<T>>;
template<typename T>
using LockableUniquePtr = LockablePtr<std::unique_ptr<T>>;
#endif // LOCKABLE_H_
@@ -0,0 +1,479 @@
#pragma once
#include <Windows.h>
#include <Ole2.h>
#include <stdio.h>
#include <Kinect.h>
#include <Kinect.Face.h>
#include <KinectExposure.h>
namespace NeoKinect
{
constexpr unsigned int KinectColorWidth = 1920;
constexpr unsigned int KinectColorHeight = 1080;
constexpr unsigned int KinectDepthWidth = 512;
constexpr unsigned int KinectDepthHeight = 424;
constexpr unsigned int KinectBodyCount = BODY_COUNT;
constexpr unsigned int KinectJointCount = JointType_Count;
struct Vector2
{
float x, y;
Vector2() : x(0.f), y(0.f) {}
Vector2(float xy) : x(xy), y(xy) {}
Vector2(float x, float y) : x(x), y(y) {}
explicit Vector2(const DepthSpacePoint& dsp) : x(dsp.X), y(dsp.Y) {}
explicit Vector2(const ColorSpacePoint& csp) : x(csp.X), y(csp.Y) {}
explicit Vector2(const PointF& pf) : x(pf.X), y(pf.Y) {}
template<typename T>
inline Vector2& operator=(const T& b);
inline DepthSpacePoint toDepthSpacePoint() const;
inline ColorSpacePoint toColorSpacePoint() const;
};
struct Vector3
{
float x, y, z;
Vector3() : x(0.f), y(0.f), z(0.f) {}
Vector3(float xyz) : x(xyz), y(xyz), z(xyz) {}
Vector3(float x, float y, float z) : x(x), y(y), z(z) {}
explicit Vector3(const CameraSpacePoint& csp) : x(csp.X), y(csp.Y), z(csp.Z) {}
inline Vector3& operator=(const CameraSpacePoint& b);
inline Vector3& operator/=(const float& b);
inline const Vector3 operator/(const float& b) const;
inline Vector3& operator*=(const float& b);
inline const Vector3 operator*(const float& b) const;
inline bool IsZero() const;
float magnitude() const; // not inline as it uses math header and I didn't want to polute the headers
inline Vector3 normalized() const;
inline CameraSpacePoint toCameraSpacePoint() const;
};
enum class KinectFrameType : BYTE
{
Color = 0,
Depth = 1,
Body = 2,
BodyIndex = 3,
Infrared = 4,
LongExposureInfrared = 5,
Face = 6,
Count = 7
};
enum class KinectCoordinateSpace : BYTE
{
Camera = 0,
Depth = 1,
Color = 2,
Count = 3
};
enum class KinectPixelFormat : BYTE
{
B8G8R8A8 = 0,
G8 = 1,
G16 = 2,
Bayer = 3,
None = 4,
Count = 5
};
struct KinectJoint
{
Vector4 orientation;
CameraSpacePoint position;
TrackingState trackingState;
JointType type;
};
struct KinectBodyEdges
{
bool left, right, top, bottom;
};
struct KinectBody
{
UINT64 trackingId;
bool isTracked, isRestricted;
KinectBodyEdges clippedEdges;
HandState handLeftState;
TrackingState handLeftTrackingState;
HandState handRightState;
TrackingState handRightTrackingState;
TrackingState leanTrackingState;
Vector2 lean;
KinectJoint joints[JointType_Count];
};
struct KinectFacePoints
{
Vector2 leftEye;
Vector2 rightEye;
Vector2 nose;
Vector2 leftMouthCorner;
Vector2 rightMouthCorner;
};
struct KinectFace
{
UINT64 trackingId;
// face bounding box in color space
RectI boxColor;
// face bounding box in infrared space
RectI boxInfrared;
// face rotation in quaternion
Vector4 rotation;
// face pivot point. The same as the skeleton head joint. It's here for conveninence.
CameraSpacePoint pivot;
// face alignment points in color space
KinectFacePoints pointsColor;
// face alignment points in infrared space
KinectFacePoints pointsInfrared;
DetectionResult isEngaged;
DetectionResult isHappy;
DetectionResult isLookingAway;
DetectionResult isMouthMoved;
DetectionResult isMouthOpen;
DetectionResult isLeftEyeClosed;
DetectionResult isRightEyeClosed;
DetectionResult isWearingGlasses;
bool bIsTracked;
};
struct FrameDescription
{
bool valid;
float diagonalFieldOfView,
horizontalFieldOfView,
verticalFieldOfView;
UINT bytesPerPixel;
UINT lengthInPixels;
int width, height;
FrameDescription() : valid(false) {}
};
class KinectSensor
{
public:
KinectSensor();
~KinectSensor();
/*
Opens the Microsoft API to the sensor.
@return Is the sensor API working?
*/
bool InitSensor();
/*
Is the sensor on already?
It may take some time for the sensor to effectivelly turn on. Keep trying.
If it's taking too long, there's probably no connected sensor or it's not working.
@return True if the sensor is on.
*/
bool IsSensorAvailable();
/*
Is the Microsoft Kinect API responding?
@return True if the sensor API is working.
*/
bool IsSensorOpen();
/*
Shuts the sensor down and closes the Microsoft API to it.
@return True if it worked.
*/
bool UninitSensor();
/*
A Vector4 containing the floor normal (xyz) and height (w).
Valid only if frequently locking and unlocking Body frame. That's when
it gets updated.
@return A Vector4 containing the floor normal (xyz) and height (w).
*/
Vector4 GetFloorClipPlane();
/*
Uses the floor clip plane to calculate how much the sensor is tilted.
@return The sensor tilt in degrees. Looking down gives negative values.
*/
float GetSensorTilt();
FrameDescription GetFrameDescription(const KinectFrameType frameType) const;
bool SetUseFrame(const KinectFrameType frameType, const bool use = true);
bool GetIsUsingFrame(const KinectFrameType frameType) const;
bool LockLatestFrame(KinectFrameType frameType);
void UnlockLatestFrame(KinectFrameType frameType);
bool GetIsFrameLocked(KinectFrameType frameType) const;
bool GetLatestFrameData(const KinectFrameType frameType, BYTE *& buffer, const KinectPixelFormat pixelFormat = KinectPixelFormat::None);
bool GetLatestFrameData(KinectBody bodies[BODY_COUNT]);
bool GetLatestFrameData(KinectFace faces[BODY_COUNT]);
/*
Gets a frame remapped to another coordinate system.
All the remapping methods depend on the Depth frame. So make sure you're
frequently locking and unlocking it.
@return True if it worked.
*/
bool GetRemappedFrameData(
const KinectFrameType frameType, const KinectCoordinateSpace targetSpace,
BYTE *const buffer, const KinectPixelFormat pixelFormat);
/*
Gets a point remapped to another coordinate system.
All the remapping methods depend on the Depth frame. So make sure you're
frequently locking and unlocking it.
@return True if it worked.
*/
bool GetRemappedPoint(
const KinectCoordinateSpace fromSpace, const KinectCoordinateSpace toSpace,
const Vector2& srcPoint, Vector2& resultPoint);
bool GetRemappedPoint(
const KinectCoordinateSpace fromSpace, const KinectCoordinateSpace toSpace,
const Vector2& srcPoint, Vector3& resultPoint) const;
bool GetRemappedPoint(
const KinectCoordinateSpace fromSpace, const KinectCoordinateSpace toSpace,
const Vector3& srcPoint, Vector2& resultPoint) const;
/*
Samples distance from the Depth frame.
The pixel used will be the round result of X and Y.
Note: this only works if the Depth frame is used and is being locked and
unlocked frequently.
*/
UINT16 SampleDepthFrame(const Vector2& point);
/*
Samples distance from the Depth frame using a coordinate from the Color frame.
The pixel used will be the round result of X and Y after remapped to the Depth frame.
Note: this only works if the 'Depth' and 'Depth remapped to Color' frames are both
used and are being locked and unlocked frequently.
*/
UINT16 SampleDepthFromColorCoords(const Vector2& colorPoint);
/*
Fully automatic exposure setting.
@param exposureCompensation Negative value gives an underexposed image,
positive gives an overexposed image.
*/
void SetAutoExposure(float exposureCompensation = 0);
/*
Sets a pseudo-exposure time in ms.
The actual frame integration time is set to a multiple of fluorescent light period
that is shorter than the requested time e.g. requesting 16 ms will set 10 ms
in Australia (100Hz), 8.33 ms in USA (120Hz).
The gain is automatically set to compensate for the reduced integration time.
Requesting less than a single fluorescent light period will set the integration time
to the requested value.
To set the shortest non-flickering integration period for any country, simply set
a pseudo-exposure time of between (10.0, 16.667) ms, which will automatically drop
the integration time to 10 or 8.3 ms depending on country, while setting the analog
gain control to a brighter value.
@param pseudoExposureTime Pseudo-exposure time in ms. [0.0, 640]ms
*/
void SetSemiAutoExposure(float pseudoExposureTime);
/*
Manually set true exposure time and analog gain.
@param frameExposureTime Time of exposure for each frame, in ms. [0.0, 640]ms
@param analogGain Exposure analog gain.
*/
void SetManualExposure(float frameExposureTime, float analogGain);
private:
IKinectSensor *pSensor; // Kinect sensor
//IMultiSourceFrameReader *pMultiFrameReader;
//IMultiSourceFrame *pMultiFrame;
KinectExposure exposureControl;
// Frame types
#define FRAME_VAR(name) I ##name## Frame *p ##name## Frame; I ##name## FrameReader *p ##name## FrameReader;
FRAME_VAR(Body);
FRAME_VAR(BodyIndex);
FRAME_VAR(Color);
FRAME_VAR(Depth);
FRAME_VAR(Infrared);
FRAME_VAR(LongExposureInfrared);
#undef FRAME_VAR
// Face frame resources
/* Features we'll use from face frames */
static const DWORD cFaceFrameFeatures =
FaceFrameFeatures_FaceEngagement
| FaceFrameFeatures_Happy
| FaceFrameFeatures_LookingAway
| FaceFrameFeatures_Glasses
| FaceFrameFeatures_LeftEyeClosed
| FaceFrameFeatures_RightEyeClosed
| FaceFrameFeatures_MouthMoved
| FaceFrameFeatures_MouthOpen
| FaceFrameFeatures_RotationOrientation
| FaceFrameFeatures_PointsInColorSpace
| FaceFrameFeatures_PointsInInfraredSpace
| FaceFrameFeatures_BoundingBoxInColorSpace
| FaceFrameFeatures_BoundingBoxInInfraredSpace;
IFaceFrameSource *pFaceFrameSources[BODY_COUNT];
IFaceFrameReader *pFaceFrameReaders[BODY_COUNT];
IFaceFrame *pFaceFrames[BODY_COUNT];
// Converts between depth, color and 3d coords
ICoordinateMapper *pMapper;
DepthSpacePoint *pColorMappedToDepth;
bool bIsColorMappedToDepthDirty;
/* We keep a copy of the latest depth frame so the user can convert points
between spaces anytime, not only when the depth frame is locked.
This copy is updated everytime the frame is locked. Because of it, some
coordinate space convertions may not work if the depth frame is not in use
nor locked frequently. */
UINT16 *pDepthRawCopy;
IBody *bodies[BODY_COUNT];
Joint joints[JointType_Count]; // keep joints array in memory to avoid frequent allocations
JointOrientation jointOrientations[JointType_Count];
Vector4 floorClipPlane;
inline bool InitMapper();
inline void UpdateColorMappedToDepth();
inline int IndexFromXY(int pitch, float x, float y) const;
};
/* Print formatted string to the Output window */
void Printf(const char *szFormat, ...);
void LogFailedHResult(const HRESULT hr, const char *functionName, const int line, const char *action = "");
template <typename T>
inline void SafeRelease(T*& pointer);
template <typename T>
inline void SafeReleaseArray(T*& pointer);
/*-------------------------------------------------------------------------
Templates and inlines implementation
-------------------------------------------------------------------------*/
template<typename T>
Vector2& Vector2::operator=(const T& b)
{
if (this != &b)
{
this->x = b.X;
this->y = b.Y;
}
return *this;
}
DepthSpacePoint Vector2::toDepthSpacePoint() const
{
DepthSpacePoint b;
b.X = this->x;
b.Y = this->y;
return b;
}
ColorSpacePoint Vector2::toColorSpacePoint() const
{
ColorSpacePoint b;
b.X = this->x;
b.Y = this->y;
return b;
}
Vector3& Vector3::operator=(const CameraSpacePoint& b)
{
this->x = b.X;
this->y = b.Y;
this->z = b.Z;
return *this;
}
Vector3& Vector3::operator/=(const float& other)
{
x /= other;
y /= other;
z /= other;
return *this;
}
const Vector3 Vector3::operator/(const float& other) const
{
Vector3 result(*this);
return result /= other;
}
Vector3& Vector3::operator*=(const float& other)
{
x *= other;
y *= other;
z *= other;
return *this;
}
const Vector3 Vector3::operator*(const float& other) const
{
Vector3 result(*this);
return result *= other;
}
bool Vector3::IsZero() const
{
return x == 0.f && y == 0.f && z == 0.f;
}
Vector3 Vector3::normalized() const
{
if (IsZero())
return Vector3();
return (*this / magnitude());
}
CameraSpacePoint Vector3::toCameraSpacePoint() const
{
CameraSpacePoint b;
b.X = this->x;
b.Y = this->y;
b.Z = this->z;
return b;
}
template<typename T>
inline void SafeRelease(T*& pointer)
{
if (pointer)
{
pointer->Release();
}
pointer = nullptr;
}
template<typename T>
inline void SafeReleaseArray(T*& pointer)
{
if (pointer)
{
delete[] pointer;
}
pointer = nullptr;
}
}

Some files were not shown because too many files have changed in this diff Show More