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>();
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()

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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.
//

View File

@@ -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();
}
}

View File

@@ -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();