diff --git a/Content/Tangibles/TangibleCharacter.uasset b/Content/Tangibles/TangibleCharacter.uasset index 1ad5223e..e00ec4d2 100644 --- a/Content/Tangibles/TangibleCharacter.uasset +++ b/Content/Tangibles/TangibleCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bef016f4e5e11071d5279b7ef36fb40a5fba609573a884b1232916fc7afe12e2 -size 491301 +oid sha256:ca12fdbfc628a39a87b1253ed7a7b2f2b3b262a8754d6b9c14c724544445a2de +size 534389 diff --git a/Source/Integration/IntegrationGameModeBase.cpp b/Source/Integration/IntegrationGameModeBase.cpp index 0ce0af61..843fefb7 100644 --- a/Source/Integration/IntegrationGameModeBase.cpp +++ b/Source/Integration/IntegrationGameModeBase.cpp @@ -19,7 +19,6 @@ AIntegrationGameModeBase::AIntegrationGameModeBase() TangibleManager = NewObject(); PlayerId = 0; EngineSeconds = 0.0; - NextThreadTrigger = 1.0; //PrimaryActorTick.bCanEverTick = true; // Probably wrong //PrimaryActorTick.bTickEvenWhenPaused = true; // Probably wrong //PrimaryActorTick.TickGroup = TG_PrePhysics; // Probably wrong @@ -27,11 +26,15 @@ AIntegrationGameModeBase::AIntegrationGameModeBase() 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, @@ -76,7 +79,6 @@ void AIntegrationGameModeBase::ResetToInitialState() // Reset the clocks. EngineSeconds = 0; - NextThreadTrigger = 1.0; NextRotateCube = 1.0; } @@ -101,41 +103,53 @@ void AIntegrationGameModeBase::UpdateConsoleOutput() { } } -void AIntegrationGameModeBase::MaybeTriggerUpdateTask(float deltaseconds) { - if (!Playing) return; - FlxLockedWrapper lockedwrap(LockableWrapper); - if (EngineSeconds >= NextThreadTrigger) - { - LuprexUpdateTask.Trigger(); - NextThreadTrigger += 0.05; - } -} - - - #pragma optimize("", off) void AIntegrationGameModeBase::UpdateTangibles() { double radius = 1000.0; // Hardwired for now. using TanArray = UlxTangibleManager::TanArray; if (!Playing) return; - FlxLockedWrapper w(LockableWrapper); - PlayerId = w.GetActor(); - IdView nearids = w.GetNear(PlayerId, radius, radius, radius); - TangibleManager->UpdateNearAccordingToLuprex(nearids); - TanArray alltans = TangibleManager->GetAllTangibles(); - IdArray allids = TangibleManager->GetIds(alltans); - StringViewVec allqueues = w.GetAnimationQueues(allids); + 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]); + } + } + // 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]->UpdateAnimationQueue(allqueues[i]); + alltans[i]->MaybeExecuteAnimStateChanged(); } TangibleManager->RecalcNearAccordingToUnreal(PlayerId, radius); TangibleManager->DeleteFarawayTangibles(); } -void AIntegrationGameModeBase::ExecuteDebuggingCommand(const FString &fs) { +void AIntegrationGameModeBase::InvokeEngioMove(const FString &action, const FVector &xyz) { + 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); + 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")); - FlxLockedWrapper w(LockableWrapper); FlxStreamBuffer sb; sb.write_string("myfunction"); sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); @@ -167,31 +181,39 @@ void AIntegrationGameModeBase::ConsoleSendInput(const FString& fs) // lua code contains '\\', we'll catch it instead of passing it // through. There's no simple solution, though. if (fs[0] == '\\') { - ExecuteDebuggingCommand(fs); + ExecuteDebuggingCommand(w, fs); } else { - const TCHAR* fstchar = *fs; - if (sizeof(TCHAR) == 2) - { - std::u16string_view fsview((const char16_t*)fstchar, fs.Len()); - std::string utf8 = drvutil::utf16_to_utf8(fsview); - utf8 = utf8 + "\n"; - w->play_recv_incoming(w.Get(), 0, utf8.size(), utf8.c_str()); - } + 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; + } +} + +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); - if (Playing) - { - EngineSeconds += deltaseconds; - } - UpdateConsoleOutput(); UpdateTangibles(); - MaybeTriggerUpdateTask(deltaseconds); } void AIntegrationGameModeBase::BeginPlay() diff --git a/Source/Integration/IntegrationGameModeBase.h b/Source/Integration/IntegrationGameModeBase.h index cb5b3643..1849cae0 100644 --- a/Source/Integration/IntegrationGameModeBase.h +++ b/Source/Integration/IntegrationGameModeBase.h @@ -42,8 +42,11 @@ public: UFUNCTION(BlueprintCallable, Category = "Luprex") int64 GetPlayerId(); + UFUNCTION(BlueprintCallable, Category = "Luprex") + void InvokeEngioMove(const FString &action, const FVector &xyz); + // Execute a debugging command, typed on the GUI. - void ExecuteDebuggingCommand(const FString &fs); + void ExecuteDebuggingCommand(FlxLockedWrapper &w, const FString &fs); // Transfer console output from the Luprex engine to unreal. void UpdateConsoleOutput(); @@ -51,8 +54,9 @@ public: // Update the tangibles according to what Luprex tells us. void UpdateTangibles(); - // Trigger the update task, if enough time has passed. - void MaybeTriggerUpdateTask(float deltaseconds); + // Pre-tick and post-tick functions. + void OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float InDeltaSeconds); + void OnWorldPostActorTick(UWorld* InWorld, ELevelTick InLevelTick, float InDeltaSeconds); // The run function is called by a background thread // to update luprex sockets and update luprex itself. @@ -83,9 +87,10 @@ public: // Amount of elapsed time since BeginPlay. float EngineSeconds; - // When do we next trigger the thread event. - float NextThreadTrigger; - // When do we next rotate the cube. float NextRotateCube; + + // These allow us to pre-tick and post-tick. + FDelegateHandle OnWorldPreActorTickHandle; + FDelegateHandle OnWorldPostActorTickHandle; }; diff --git a/Source/Integration/Tangible.cpp b/Source/Integration/Tangible.cpp index 73bd6e27..bcb9e7bf 100644 --- a/Source/Integration/Tangible.cpp +++ b/Source/Integration/Tangible.cpp @@ -96,6 +96,9 @@ void UlxTangible::SetActorBlueprint(const FString &name) { void UlxTangible::UpdateAnimationQueue(std::string_view aq) { AnimTracker.Update(aq); +} + +void UlxTangible::MaybeExecuteAnimStateChanged() { int limit = 3; while (AnimTracker.IsChanged()) { if (limit == 0) break; diff --git a/Source/Integration/Tangible.h b/Source/Integration/Tangible.h index 09c96dab..baf69d68 100644 --- a/Source/Integration/Tangible.h +++ b/Source/Integration/Tangible.h @@ -130,13 +130,15 @@ public: // Update the animation queue from Luprex. // - // This reads the animation queue, and then based on - // what is new in the animation queue, it calls into - // the Actor's TangibleInterface, calling methods such - // as 'StartAnimation' and 'AbortAnimation' as necessary. + // This reads the animation queue, and copies it + // into the animtracker. // void UpdateAnimationQueue(std::string_view aq); + // Execute AnimStateChanged, if the 'changed' bit is set. + // + void MaybeExecuteAnimStateChanged(); + private: // Set the actor's blueprint, and recreate the actor if necessary. // diff --git a/Source/Integration/TriggeredTask.cpp b/Source/Integration/TriggeredTask.cpp index af411a3c..84b16155 100644 --- a/Source/Integration/TriggeredTask.cpp +++ b/Source/Integration/TriggeredTask.cpp @@ -7,23 +7,21 @@ FTriggeredTask::FTriggeredTask() { Client = nullptr; Thread = nullptr; ThreadStopRequested = false; - ThreadEvent = nullptr; + CallEvent = nullptr; + ReturnEvent = nullptr; } uint32 FTriggeredTask::Run() { while (true) { - bool triggered = ThreadEvent->Wait(3000); + CallEvent->Wait(); if (ThreadStopRequested) { DPrint("Thread stopping as requested"); break; } - if (!triggered) { - DPrint("Thread waiting a long time..."); - continue; - } // The payload. Client->Run(); + ReturnEvent->Trigger(); } return 0; } @@ -32,7 +30,9 @@ void FTriggeredTask::Startup(FRunnable *client) { FScopeLock lock(&Mutex); if (Thread == nullptr) { Client = client; - ThreadEvent = FPlatformProcess::GetSynchEventFromPool(false); + CallEvent = FPlatformProcess::GetSynchEventFromPool(true); + ReturnEvent = FPlatformProcess::GetSynchEventFromPool(false); + ReturnEvent->Trigger(); Thread = FRunnableThread::Create(this, TEXT("Worker Thread")); } } @@ -40,12 +40,15 @@ void FTriggeredTask::Startup(FRunnable *client) { void FTriggeredTask::Shutdown() { FScopeLock lock(&Mutex); if (Thread != nullptr) { + ReturnEvent->Wait(); ThreadStopRequested = true; - ThreadEvent->Trigger(); + CallEvent->Trigger(); delete Thread; // This waits for the thread to complete. Thread = nullptr; - FPlatformProcess::ReturnSynchEventToPool(ThreadEvent); - ThreadEvent = nullptr; + FPlatformProcess::ReturnSynchEventToPool(CallEvent); + FPlatformProcess::ReturnSynchEventToPool(ReturnEvent); + CallEvent = nullptr; + ReturnEvent = nullptr; } ThreadStopRequested = false; } @@ -53,7 +56,15 @@ void FTriggeredTask::Shutdown() { void FTriggeredTask::Trigger() { FScopeLock lock(&Mutex); if (Thread != nullptr) { - ThreadEvent->Trigger(); + ReturnEvent->Reset(); + CallEvent->Trigger(); + } +} + +void FTriggeredTask::Wait() { + FScopeLock lock(&Mutex); + if (Thread != nullptr) { + ReturnEvent->Wait(); } } diff --git a/Source/Integration/TriggeredTask.h b/Source/Integration/TriggeredTask.h index 88308959..7a9fc39e 100644 --- a/Source/Integration/TriggeredTask.h +++ b/Source/Integration/TriggeredTask.h @@ -11,15 +11,13 @@ // * You have a function to run in the background. // * It should start the instant a foreground thread says "NOW". // * It should be run exactly once for each "NOW". +// * The foreground thread eventually waits for the background thread to finish. // // To use this class, construct an FTriggeredTask, // and provide a runnable object. The runnable // object's "Run" method will get executed in // each time you call 'Trigger' on the FTriggeredTask. // -// The run method will never get called reentrantly, -// even if you call 'Trigger' rapidly in succession. -// /////////////////////////////////////////////// class FTriggeredTask : public FRunnable @@ -43,7 +41,12 @@ private: // once. But if ThreadStopRequested is true, it means we // want the thread to exit. // - FEvent* ThreadEvent; + FEvent* CallEvent; + + // This event is used when the thread is done + // with its work. + // + FEvent* ReturnEvent; // The client whose task we're triggering. // @@ -91,12 +94,18 @@ public: // Trigger // // Run the client's 'RUN' method once, in the - // background. Trigger is a no-op if the system - // is shut down. If you trigger when the task - // is already running, a trigger may get dropped. + // background. // void Trigger(); + // Wait + // + // Wait for the background task to finish its work. + // If the background thread isn't running, then this + // returns immediately. + // + void Wait(); + // IsRunning // bool IsRunning();