// Copyright Epic Games, Inc. All Rights Reserved. #include "IntegrationGameModeBase.h" #include "lpx-drvutil.hpp" #include "lpx-paths.hpp" #include "DebugPrint.h" #include "Tangible.h" #include "TangibleManager.h" #include "CommonTypes.h" #include "AnimQueue.h" #include #include using namespace DebugPrint; using namespace CommonTypes; AIntegrationGameModeBase::AIntegrationGameModeBase() { TangibleManager = NewObject(); PlayerId = 0; EngineSeconds = 0.0; //PrimaryActorTick.bCanEverTick = true; // Probably wrong //PrimaryActorTick.bTickEvenWhenPaused = true; // Probably wrong //PrimaryActorTick.TickGroup = TG_PrePhysics; // Probably wrong SetActorTickEnabled(true); SetActorTickInterval(0.0f); DebugPrintControl::EnableCollection(); ResetToInitialState(); OnWorldPreActorTickHandle = FWorldDelegates::OnWorldPreActorTick.AddUObject(this, &AIntegrationGameModeBase::OnWorldPreActorTick); OnWorldPostActorTickHandle = FWorldDelegates::OnWorldPostActorTick.AddUObject(this, &AIntegrationGameModeBase::OnWorldPostActorTick); } AIntegrationGameModeBase::~AIntegrationGameModeBase() { ResetToInitialState(); FWorldDelegates::OnWorldPreActorTick.Remove(OnWorldPreActorTickHandle); FWorldDelegates::OnWorldPostActorTick.Remove(OnWorldPostActorTickHandle); } // This method runs in the background thread, // at the moment we trigger it. // uint32 AIntegrationGameModeBase::Run() { FlxLockedWrapper lockedwrap(LockableWrapper); Sockets->Update(lockedwrap); lockedwrap->play_invoke_event_update(lockedwrap.Get(), EngineSeconds); Sockets->Update(lockedwrap); return 0; } void AIntegrationGameModeBase::ResetToInitialState() { Playing = false; if (TangibleManager != nullptr) { TangibleManager->ConditionalBeginDestroy(); TangibleManager = nullptr; } // Shut down the thread LuprexUpdateTask.Shutdown(); // Now that the thread's gone, we should be able to // just claim and hold the lock on the wrapper. FlxLockedWrapper w(LockableWrapper); // Release and close all sockets. if (Sockets != nullptr) { Sockets->ForceCloseEverything(w); Sockets.Reset(); } // Delete the engine. if (w->release != nullptr) { w->release(w.Get()); } // Reset the clocks. EngineSeconds = 0; NextRotateCube = 1.0; } void AIntegrationGameModeBase::UpdateConsoleOutput() { // Copy Luprex Stdout into the console. FlxLockedWrapper lockedwrap(LockableWrapper); if (Playing) { ConsoleOutput.Append(lockedwrap.FetchStdout()); } // Copy Debugging Prints into the console. TArray prints = DebugPrintControl::GetStored(); for (const FString& fs : prints) { ConsoleOutput.AppendLine(fs); } // If the Console text has changed, update the widget. if (ConsoleOutput.IsDirty()) { ConsoleSetOutput(ConsoleOutput.Get()); ConsoleOutput.ClearDirty(); } } #pragma optimize("", off) void AIntegrationGameModeBase::UpdateTangibles() { double radius = 1000.0; // Hardwired for now. using TanArray = UlxTangibleManager::TanArray; if (!Playing) return; TanArray alltans; { FlxLockedWrapper w(LockableWrapper); PlayerId = w.GetActor(); IdView nearids = w.GetNear(PlayerId, radius, radius, radius); TangibleManager->UpdateNearAccordingToLuprex(nearids); alltans = TangibleManager->GetAllTangibles(); IdArray allids = TangibleManager->GetIds(alltans); StringViewVec allqueues = w.GetAnimationQueues(allids); for (int i = 0; i < alltans.Num(); i++) { alltans[i]->UpdateAnimationQueue(allqueues[i]); } // Maybe call 'BecomePossessed' on some tangible. UlxTangible *poss = TangibleManager->SetPossessedTangible(PlayerId); if (poss != nullptr) { IlxTangibleInterface::Execute_BecomePossessed(poss->GetActor()); } } // This is where we run the blueprint code that updates animation // states. Be aware that the blueprint code could call back // into the gamemode, which could in turn access the FlxLockedWrapper. // This is why we've released the FlxLockedWrapper before doing this. for (int i = 0; i < alltans.Num(); i++) { alltans[i]->MaybeExecuteAnimStateChanged(); } TangibleManager->RecalcNearAccordingToUnreal(PlayerId, radius); TangibleManager->DeleteFarawayTangibles(); } void AIntegrationGameModeBase::InvokeEngioMove(const FString &action, const FVector &xyz, double facing) { FTCHARToUTF8 utf8action(action); std::string uaction(utf8action.Get(), utf8action.Length()); FlxStreamBuffer sb; sb.write_string("move"); // Function name within engio sb.write_simple_dynamic_tag(SimpleDynamicTag::STRING); sb.write_string(uaction); sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR); sb.write_fvector(xyz); sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); sb.write_double(facing); std::string_view datapk = sb.view(); FlxLockedWrapper w(LockableWrapper); int64 player = w.GetActor(); w->play_invoke_engio(w.Get(), player, datapk.size(), datapk.data()); } void AIntegrationGameModeBase::ExecuteDebuggingCommand(FlxLockedWrapper &w, const FString &fs) { if (fs == "\\invokeplayer") { DPrint(TEXT("Trying to invoke 'myfunction' in lua")); FlxStreamBuffer sb; sb.write_string("myfunction"); sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); sb.write_double(3.0); sb.write_simple_dynamic_tag(SimpleDynamicTag::STRING); sb.write_string("Howdy"); sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR); sb.write_fvector(FVector(2,3,4)); std::string_view datapk = sb.view(); int64 player = w.GetActor(); w->play_invoke_engio(w.Get(), player, datapk.size(), datapk.data()); } else { ConsoleOutput.AppendLine(TEXT("Unknown Command")); } } void AIntegrationGameModeBase::ConsoleSendInput(const FString& fs) { if (fs.IsEmpty()) { return; } FlxLockedWrapper w(LockableWrapper); if (w->engine != nullptr) { ConsoleOutput.AppendLine(FString("> ") + fs); // This is a bad way to do this. The problem is that if some // lua code contains '\\', we'll catch it instead of passing it // through. There's no simple solution, though. if (fs[0] == '\\') { ExecuteDebuggingCommand(w, fs); } else { FTCHARToUTF8 utf8fs(fs); std::string ufs(utf8fs.Get(), utf8fs.Length()); ufs = ufs + "\n"; w->play_recv_incoming(w.Get(), 0, ufs.size(), ufs.c_str()); } } } void AIntegrationGameModeBase::OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds) { if(Playing && (GetWorld() == InWorld) && (InLevelTick == LEVELTICK_All)) { LuprexUpdateTask.Wait(); EngineSeconds += deltaseconds; UpdateConsoleOutput(); UpdateTangibles(); } } void AIntegrationGameModeBase::OnWorldPostActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds) { if(Playing && (GetWorld() == InWorld) && (InLevelTick == LEVELTICK_All)) { LuprexUpdateTask.Trigger(); } } void AIntegrationGameModeBase::Tick(float deltaseconds) { Super::Tick(deltaseconds); UpdateConsoleOutput(); UpdateTangibles(); } void AIntegrationGameModeBase::BeginPlay() { Super::BeginPlay(); // Make sure we're starting from a clean slate. // Note: this claims the wrapper lock, so don't claim // the lock before calling this. ResetToInitialState(); // Now we're just going to claim the wrapper // lock for the remainder. When we create the thread, // the thread will hang until we release this lock. FlxLockedWrapper w(LockableWrapper); // Sanity checks. Make sure everything is clean. checkf(!LuprexUpdateTask.IsRunning(), TEXT("There should be no thread here.")); checkf(w->engine == nullptr, TEXT("There should be no engine here.")); // Try to initialize the wrapper. w.InitWrapper(); // If we failed to initialize the wrapper, print an error message. if (w->play_initialize == nullptr) { DPrint("Luprex wrapper initialization failed"); } // If wrapper is initialized, try to initialize the luprex engine. if (w->play_initialize != nullptr) { drvutil::ostringstream srcpak; std::string srcpakerr = drvutil::package_lua_source(LUPREX_ROOT_PATH, &srcpak); if (!srcpakerr.empty()) { DPrint(srcpakerr.c_str()); } else { std::string_view srcpakv = srcpak.view(); char* argv[1]; argv[0] = const_cast("lpxclient"); w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), ""); if (w->error[0]) { DPrint(w->error); } if (w->engine != nullptr) { DPrint("Luprex initialize success"); Playing = true; } } } // If we successfully created a luprex engine, create a socket system and a worker thread. if (Playing) { Sockets.Reset(FlxSockets::Create(w)); std::string error = Sockets->GetError(); check(error.empty()); LuprexUpdateTask.Startup(this); } // Initialize the tangible manager. TangibleManager = NewObject(); TangibleManager->Init(GetWorld(), this); } void AIntegrationGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason) { ResetToInitialState(); } int64 AIntegrationGameModeBase::GetPlayerId() { return PlayerId; }