More work on tangibleCharacter, especially thread synch stuff

This commit is contained in:
2024-02-13 16:49:58 -05:00
parent d11fbca815
commit abb967f20b
7 changed files with 121 additions and 69 deletions

Binary file not shown.

View File

@@ -19,7 +19,6 @@ AIntegrationGameModeBase::AIntegrationGameModeBase()
TangibleManager = NewObject<UlxTangibleManager>(); TangibleManager = NewObject<UlxTangibleManager>();
PlayerId = 0; PlayerId = 0;
EngineSeconds = 0.0; EngineSeconds = 0.0;
NextThreadTrigger = 1.0;
//PrimaryActorTick.bCanEverTick = true; // Probably wrong //PrimaryActorTick.bCanEverTick = true; // Probably wrong
//PrimaryActorTick.bTickEvenWhenPaused = true; // Probably wrong //PrimaryActorTick.bTickEvenWhenPaused = true; // Probably wrong
//PrimaryActorTick.TickGroup = TG_PrePhysics; // Probably wrong //PrimaryActorTick.TickGroup = TG_PrePhysics; // Probably wrong
@@ -27,11 +26,15 @@ AIntegrationGameModeBase::AIntegrationGameModeBase()
SetActorTickInterval(0.0f); SetActorTickInterval(0.0f);
DebugPrintControl::EnableCollection(); DebugPrintControl::EnableCollection();
ResetToInitialState(); ResetToInitialState();
OnWorldPreActorTickHandle = FWorldDelegates::OnWorldPreActorTick.AddUObject(this, &AIntegrationGameModeBase::OnWorldPreActorTick);
OnWorldPostActorTickHandle = FWorldDelegates::OnWorldPostActorTick.AddUObject(this, &AIntegrationGameModeBase::OnWorldPostActorTick);
} }
AIntegrationGameModeBase::~AIntegrationGameModeBase() AIntegrationGameModeBase::~AIntegrationGameModeBase()
{ {
ResetToInitialState(); ResetToInitialState();
FWorldDelegates::OnWorldPreActorTick.Remove(OnWorldPreActorTickHandle);
FWorldDelegates::OnWorldPostActorTick.Remove(OnWorldPostActorTickHandle);
} }
// This method runs in the background thread, // This method runs in the background thread,
@@ -76,7 +79,6 @@ void AIntegrationGameModeBase::ResetToInitialState()
// Reset the clocks. // Reset the clocks.
EngineSeconds = 0; EngineSeconds = 0;
NextThreadTrigger = 1.0;
NextRotateCube = 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) #pragma optimize("", off)
void AIntegrationGameModeBase::UpdateTangibles() { void AIntegrationGameModeBase::UpdateTangibles() {
double radius = 1000.0; // Hardwired for now. double radius = 1000.0; // Hardwired for now.
using TanArray = UlxTangibleManager::TanArray; using TanArray = UlxTangibleManager::TanArray;
if (!Playing) return; if (!Playing) return;
FlxLockedWrapper w(LockableWrapper); TanArray alltans;
PlayerId = w.GetActor(); {
IdView nearids = w.GetNear(PlayerId, radius, radius, radius); FlxLockedWrapper w(LockableWrapper);
TangibleManager->UpdateNearAccordingToLuprex(nearids); PlayerId = w.GetActor();
TanArray alltans = TangibleManager->GetAllTangibles(); IdView nearids = w.GetNear(PlayerId, radius, radius, radius);
IdArray allids = TangibleManager->GetIds(alltans); TangibleManager->UpdateNearAccordingToLuprex(nearids);
StringViewVec allqueues = w.GetAnimationQueues(allids); 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++) { for (int i = 0; i < alltans.Num(); i++) {
alltans[i]->UpdateAnimationQueue(allqueues[i]); alltans[i]->MaybeExecuteAnimStateChanged();
} }
TangibleManager->RecalcNearAccordingToUnreal(PlayerId, radius); TangibleManager->RecalcNearAccordingToUnreal(PlayerId, radius);
TangibleManager->DeleteFarawayTangibles(); 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") { if (fs == "\\invokeplayer") {
DPrint(TEXT("Trying to invoke 'myfunction' in lua")); DPrint(TEXT("Trying to invoke 'myfunction' in lua"));
FlxLockedWrapper w(LockableWrapper);
FlxStreamBuffer sb; FlxStreamBuffer sb;
sb.write_string("myfunction"); sb.write_string("myfunction");
sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); 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 // lua code contains '\\', we'll catch it instead of passing it
// through. There's no simple solution, though. // through. There's no simple solution, though.
if (fs[0] == '\\') { if (fs[0] == '\\') {
ExecuteDebuggingCommand(fs); ExecuteDebuggingCommand(w, fs);
} else { } else {
const TCHAR* fstchar = *fs; FTCHARToUTF8 utf8fs(fs);
if (sizeof(TCHAR) == 2) std::string ufs(utf8fs.Get(), utf8fs.Length());
{ ufs = ufs + "\n";
std::u16string_view fsview((const char16_t*)fstchar, fs.Len()); w->play_recv_incoming(w.Get(), 0, ufs.size(), ufs.c_str());
std::string utf8 = drvutil::utf16_to_utf8(fsview);
utf8 = utf8 + "\n";
w->play_recv_incoming(w.Get(), 0, utf8.size(), utf8.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) void AIntegrationGameModeBase::Tick(float deltaseconds)
{ {
Super::Tick(deltaseconds); Super::Tick(deltaseconds);
if (Playing)
{
EngineSeconds += deltaseconds;
}
UpdateConsoleOutput(); UpdateConsoleOutput();
UpdateTangibles(); UpdateTangibles();
MaybeTriggerUpdateTask(deltaseconds);
} }
void AIntegrationGameModeBase::BeginPlay() void AIntegrationGameModeBase::BeginPlay()

View File

@@ -42,8 +42,11 @@ public:
UFUNCTION(BlueprintCallable, Category = "Luprex") UFUNCTION(BlueprintCallable, Category = "Luprex")
int64 GetPlayerId(); int64 GetPlayerId();
UFUNCTION(BlueprintCallable, Category = "Luprex")
void InvokeEngioMove(const FString &action, const FVector &xyz);
// Execute a debugging command, typed on the GUI. // 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. // Transfer console output from the Luprex engine to unreal.
void UpdateConsoleOutput(); void UpdateConsoleOutput();
@@ -51,8 +54,9 @@ public:
// Update the tangibles according to what Luprex tells us. // Update the tangibles according to what Luprex tells us.
void UpdateTangibles(); void UpdateTangibles();
// Trigger the update task, if enough time has passed. // Pre-tick and post-tick functions.
void MaybeTriggerUpdateTask(float deltaseconds); 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 // The run function is called by a background thread
// to update luprex sockets and update luprex itself. // to update luprex sockets and update luprex itself.
@@ -83,9 +87,10 @@ public:
// Amount of elapsed time since BeginPlay. // Amount of elapsed time since BeginPlay.
float EngineSeconds; float EngineSeconds;
// When do we next trigger the thread event.
float NextThreadTrigger;
// When do we next rotate the cube. // When do we next rotate the cube.
float NextRotateCube; float NextRotateCube;
// These allow us to pre-tick and post-tick.
FDelegateHandle OnWorldPreActorTickHandle;
FDelegateHandle OnWorldPostActorTickHandle;
}; };

View File

@@ -96,6 +96,9 @@ void UlxTangible::SetActorBlueprint(const FString &name) {
void UlxTangible::UpdateAnimationQueue(std::string_view aq) { void UlxTangible::UpdateAnimationQueue(std::string_view aq) {
AnimTracker.Update(aq); AnimTracker.Update(aq);
}
void UlxTangible::MaybeExecuteAnimStateChanged() {
int limit = 3; int limit = 3;
while (AnimTracker.IsChanged()) { while (AnimTracker.IsChanged()) {
if (limit == 0) break; if (limit == 0) break;

View File

@@ -130,13 +130,15 @@ public:
// Update the animation queue from Luprex. // Update the animation queue from Luprex.
// //
// This reads the animation queue, and then based on // This reads the animation queue, and copies it
// what is new in the animation queue, it calls into // into the animtracker.
// the Actor's TangibleInterface, calling methods such
// as 'StartAnimation' and 'AbortAnimation' as necessary.
// //
void UpdateAnimationQueue(std::string_view aq); void UpdateAnimationQueue(std::string_view aq);
// Execute AnimStateChanged, if the 'changed' bit is set.
//
void MaybeExecuteAnimStateChanged();
private: private:
// Set the actor's blueprint, and recreate the actor if necessary. // Set the actor's blueprint, and recreate the actor if necessary.
// //

View File

@@ -7,23 +7,21 @@ FTriggeredTask::FTriggeredTask() {
Client = nullptr; Client = nullptr;
Thread = nullptr; Thread = nullptr;
ThreadStopRequested = false; ThreadStopRequested = false;
ThreadEvent = nullptr; CallEvent = nullptr;
ReturnEvent = nullptr;
} }
uint32 FTriggeredTask::Run() { uint32 FTriggeredTask::Run() {
while (true) while (true)
{ {
bool triggered = ThreadEvent->Wait(3000); CallEvent->Wait();
if (ThreadStopRequested) { if (ThreadStopRequested) {
DPrint("Thread stopping as requested"); DPrint("Thread stopping as requested");
break; break;
} }
if (!triggered) {
DPrint("Thread waiting a long time...");
continue;
}
// The payload. // The payload.
Client->Run(); Client->Run();
ReturnEvent->Trigger();
} }
return 0; return 0;
} }
@@ -32,7 +30,9 @@ void FTriggeredTask::Startup(FRunnable *client) {
FScopeLock lock(&Mutex); FScopeLock lock(&Mutex);
if (Thread == nullptr) { if (Thread == nullptr) {
Client = client; Client = client;
ThreadEvent = FPlatformProcess::GetSynchEventFromPool(false); CallEvent = FPlatformProcess::GetSynchEventFromPool(true);
ReturnEvent = FPlatformProcess::GetSynchEventFromPool(false);
ReturnEvent->Trigger();
Thread = FRunnableThread::Create(this, TEXT("Worker Thread")); Thread = FRunnableThread::Create(this, TEXT("Worker Thread"));
} }
} }
@@ -40,12 +40,15 @@ void FTriggeredTask::Startup(FRunnable *client) {
void FTriggeredTask::Shutdown() { void FTriggeredTask::Shutdown() {
FScopeLock lock(&Mutex); FScopeLock lock(&Mutex);
if (Thread != nullptr) { if (Thread != nullptr) {
ReturnEvent->Wait();
ThreadStopRequested = true; ThreadStopRequested = true;
ThreadEvent->Trigger(); CallEvent->Trigger();
delete Thread; // This waits for the thread to complete. delete Thread; // This waits for the thread to complete.
Thread = nullptr; Thread = nullptr;
FPlatformProcess::ReturnSynchEventToPool(ThreadEvent); FPlatformProcess::ReturnSynchEventToPool(CallEvent);
ThreadEvent = nullptr; FPlatformProcess::ReturnSynchEventToPool(ReturnEvent);
CallEvent = nullptr;
ReturnEvent = nullptr;
} }
ThreadStopRequested = false; ThreadStopRequested = false;
} }
@@ -53,7 +56,15 @@ void FTriggeredTask::Shutdown() {
void FTriggeredTask::Trigger() { void FTriggeredTask::Trigger() {
FScopeLock lock(&Mutex); FScopeLock lock(&Mutex);
if (Thread != nullptr) { if (Thread != nullptr) {
ThreadEvent->Trigger(); ReturnEvent->Reset();
CallEvent->Trigger();
}
}
void FTriggeredTask::Wait() {
FScopeLock lock(&Mutex);
if (Thread != nullptr) {
ReturnEvent->Wait();
} }
} }

View File

@@ -11,15 +11,13 @@
// * You have a function to run in the background. // * You have a function to run in the background.
// * It should start the instant a foreground thread says "NOW". // * It should start the instant a foreground thread says "NOW".
// * It should be run exactly once for each "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, // To use this class, construct an FTriggeredTask,
// and provide a runnable object. The runnable // and provide a runnable object. The runnable
// object's "Run" method will get executed in // object's "Run" method will get executed in
// each time you call 'Trigger' on the FTriggeredTask. // 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 class FTriggeredTask : public FRunnable
@@ -43,7 +41,12 @@ private:
// once. But if ThreadStopRequested is true, it means we // once. But if ThreadStopRequested is true, it means we
// want the thread to exit. // 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. // The client whose task we're triggering.
// //
@@ -91,12 +94,18 @@ public:
// Trigger // Trigger
// //
// Run the client's 'RUN' method once, in the // Run the client's 'RUN' method once, in the
// background. Trigger is a no-op if the system // background.
// is shut down. If you trigger when the task
// is already running, a trigger may get dropped.
// //
void Trigger(); 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 // IsRunning
// //
bool IsRunning(); bool IsRunning();