SkyPortal-plugin/Source/SkyPortal/Private/SkyPortalSubsystem.cpp

303 lines
No EOL
7.6 KiB
C++

// A lot of this code is made because of the work of https://github.com/capull0/SkyDumper and https://github.com/silicontrip/SkyReader
#include "SkyPortalSubsystem.h"
#include "Engine/Engine.h"
#include "HAL/RunnableThread.h"
#include "SkyPortalFigure.h"
#include <Async/TaskGraphInterfaces.h>
#include <Async/Async.h>
void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
FigureArray.SetNum(16);
// Create UFigureData objects dynamically
for (uint8 i = 0; i < FigureArray.Num(); i++)
{
FigureArray[i] = NewObject<UFigureData>(this);
}
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));
}
void USkyPortalSubsystem::Deinitialize()
{
// Stop the thread when the subsystem is deinitialized
if (StatusChecker)
{
StatusChecker->Stop();
StatusCheckerThread->WaitForCompletion();
delete StatusCheckerThread;
delete StatusChecker;
StatusChecker = nullptr;
StatusCheckerThread = nullptr;
}
// Disconnect portal
if (PortalHandle) {
PortalHandle->Close();
}
hid_exit();
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Deinitialized"));
Super::Deinitialize();
}
void USkyPortalSubsystem::PortalActivate(const bool bShouldActivate)
{
FWriteBlock command;
memset(command.data, 0, write_buf_size);
command.data[1] = 'A';
command.data[2] = bShouldActivate;
uint8* output;
do {
PortalHandle->Write(&command);
output = PortalHandle->Read();
} while (output[0] != 'A');
}
void USkyPortalSubsystem::PortalReady()
{
FWriteBlock command;
memset(command.data, 0, write_buf_size); //maybe not needed here
command.data[1] = 'R';
unsigned char* output;
do {
PortalHandle->Write(&command);
output = PortalHandle->Read();
} while (output[0] != 'R');
unsigned char _PortalId[2] = { output[1],output[2] };
uint16_t BE_PortalId = (_PortalId[0] << 8) | _PortalId[1]; // Big-endian format
uint32 BEU_PortalId = (_PortalId[0] << 8) | _PortalId[1]; // Big-endian format unsigned
uint16_t LE_PortalId = (_PortalId[1] << 8) | _PortalId[0]; //little endian
//uint32 LEU_PortalId = (_PortalId[1] << 8) | _PortalId[0]; //little endian unsigned
PortalId = BEU_PortalId; //Need a conversion as uint16 is not supported in BP
}
FPortalStatusData USkyPortalSubsystem::PortalStatus() {
FWriteBlock command;
memset(command.data, 0, write_buf_size); //maybe not needed here
command.data[1] = 'S';
uint8* output;
do {
PortalHandle->Write(&command);
output = PortalHandle->Read();
} while (output[0] != 'S');
return ParsePortalStatus(output);
}
SKYPORTAL_API bool USkyPortalSubsystem::RestartRunner()
{
if (StatusChecker)
{
StatusChecker->Stop();
StatusCheckerThread->WaitForCompletion();
delete StatusCheckerThread;
delete StatusChecker;
StatusChecker = nullptr;
StatusCheckerThread = nullptr;
}
StatusChecker = new FPortalStatusChecker(this, RunnerInterval); // Check every 10 milliseconds
StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));
return true;
}
#pragma region Color functions
void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color)
{
unsigned char r = FMath::Clamp(Color.R * 100, 0.0f, 255.0f);
unsigned char g = FMath::Clamp(Color.G * 100, 0.0f, 255.0f);
unsigned char b = FMath::Clamp(Color.B * 100, 0.0f, 255.0f);
FWriteBlock command;
memset(command.data, 0, write_buf_size);
command.data[1] = 'C';
command.data[2] = r; // R
command.data[3] = g; // G
command.data[4] = b; // B
// no response for this one.
PortalHandle->Write(&command);
}
void USkyPortalSubsystem::ChangePortalColorSide(const FLinearColor& Color, const EPortalSide PortalSide, const float BlendTime)
{
unsigned char r = FMath::Clamp(Color.R * 100, 0, 255);
unsigned char g = FMath::Clamp(Color.G * 100, 0, 255);
unsigned char b = FMath::Clamp(Color.B * 100, 0, 255);
EPortalSide _portalside;
FWriteBlock command;
memset(command.data, 0, write_buf_size);
if (PortalSide == EPortalSide::BOTH) {
_portalside = EPortalSide::LEFT;
}
else {
_portalside = PortalSide;
}
switch (_portalside) {
case EPortalSide::LEFT:
command.data[1] = 'J';
command.data[2] = 0x00;
case EPortalSide::RIGHT:
command.data[1] = 'J';
command.data[2] = 0x02;
case EPortalSide::TRAP:
command.data[1] = 'L';
command.data[2] = 0x01;
command.data[3] = FMath::Max3(r, g, b); // calculate brightness
PortalHandle->Write(&command); //since it's a color command for trap, only 3 bytes are needed, no response.
return;
}
command.data[3] = r; // R
command.data[4] = g; // G
command.data[5] = b; // B
//Convert the time in millisecond into two bytes
uint16_t _time = BlendTime;
uint8_t _time_low = _time & 0xFF; // Get the low byte by masking the least significant 8 bits
uint8_t _time_high = (_time >> 8) & 0xFF; // Get the high byte extracted by shifting the bits 8 positions to the right and masking the result
command.data[6] = _time_low;
command.data[7] = _time_high;
uint8* output;
do {
PortalHandle->Write(&command);
output = PortalHandle->Read();
} while (output[0] != 'J');
if (PortalSide == EPortalSide::BOTH) {
ChangePortalColorSide(Color, EPortalSide::RIGHT, BlendTime); // send a second command for the right side
}
}
#pragma endregion
void USkyPortalSubsystem::Sleep(int sleepMs) {
FPlatformProcess::Sleep(sleepMs * 0.0001);
}
bool USkyPortalSubsystem::PortalConnect()
{
if (StatusChecker)
{
StatusChecker->Stop();
StatusCheckerThread->WaitForCompletion();
delete StatusCheckerThread;
delete StatusChecker;
StatusChecker = nullptr;
StatusCheckerThread = nullptr;
}
PortalHandle = NewObject<USkyPortalIO>();
if (IsValid(PortalHandle) && PortalHandle->bPortalReady) {
UE_LOG(LogSkyportalIO, Log, TEXT("Portal connected: "));
PortalReady();
PortalActivate(1);
Sleep(500);
ChangePortalColor(FLinearColor(0.5, 0.5, 0.5));
// Start the status checker thread
StatusChecker = new FPortalStatusChecker(this, RunnerInterval);
StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);
return true;
}
return false;
}
bool USkyPortalSubsystem::IsPortalReady()
{
if (PortalHandle) {
return PortalHandle->bPortalReady;
}
//maybe should send a status request ?
return false;
}
void USkyPortalSubsystem::PortalMusic(const USoundWave* Sound)
{
return;
}
void USkyPortalSubsystem::PortalAnalyzeAsync(const uint8 Index)
{
bShouldPauseRunner = true;
UE_LOG(LogSkyportalIO, Log, TEXT("Starting async figure analysis for index %d..."), Index);
// Run the PortalAnalyze function asynchronously on a background thread
AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, Index]()
{
// Perform the blocking operation on the background thread
PortalAnalyze(Index);
// Once done, switch back to the main game thread
AsyncTask(ENamedThreads::GameThread, [this, Index]()
{
// This will be executed on the game thread after the async task is complete
bShouldPauseRunner = false;
UE_LOG(LogSkyportalIO, Log, TEXT("Figure analysis complete for index: %d"), Index);
});
});
}
void USkyPortalSubsystem::PortalAnalyze(const uint8 index)
{
UE_LOG(LogSkyportalIO, Log, TEXT("Reading figure..."));
if (FigureArray.IsValidIndex(index)) {
FigureArray[index] = PortalHandle->ReadFigureBlocks(index);
UE_LOG(LogSkyportalIO, Log, TEXT("Figure ID : %d"), FigureArray[index]->GetFigureID());
return;
}
UE_LOG(LogSkyportalIO, Error, TEXT("Error in FigureArray"));
}
void USkyPortalSubsystem::GetFigureArray() {
FigureArray;
for (int i = 0; 16; i++) {
FigureArray[i] = PortalHandle->ReadFigureBlocks(i);
}
return;
}