// Copyright Epic Games, Inc. All Rights Reserved. #include "StepExecutor.h" #include "IStepExecutor.h" #include "IAutomationDriver.h" #include "AutomatedApplication.h" #include "DriverConfiguration.h" #include "Misc/ScopeLock.h" #include "Containers/Ticker.h" #include "Async/Async.h" class FStepExecutor : public IStepExecutor , public TSharedFromThis { public: virtual ~FStepExecutor() { if (Promise.IsValid()) { Promise->SetValue(false); } // As we use guards to access to the Steps array we have to clear it explicitly to sync access to it FScopeLock StateLock(&StepsCS); Steps.Empty(); } virtual void Add(const TSharedRef& Step) override { check(!Promise.IsValid()); FScopeLock StateLock(&StepsCS); Steps.Add(Step); } virtual void Add(const TFunction& StepFunction) override { check(StepFunction); TSharedRef Step = MakeShared( FExecuteStepDelegate::CreateLambda(StepFunction)); Add(Step); } virtual void InsertNext(const TSharedRef& Step) override { check(Promise.IsValid()); FScopeLock StateLock(&StepsCS); Steps.Insert(Step, CurrentStepIndex + 1); } virtual void InsertNext(const TFunction& StepFunction) override { check(StepFunction); TSharedRef Step = MakeShared( FExecuteStepDelegate::CreateLambda(StepFunction)); InsertNext(Step); } virtual TAsyncResult Execute() override { check(!Promise.IsValid()); CurrentStepIndex = 0; // Queue result back on main thread TWeakPtr LocalWeakThis(SharedThis(this)); AsyncTask( ENamedThreads::GameThread, [LocalWeakThis]() { TSharedPtr Executor = LocalWeakThis.Pin(); if (Executor.IsValid()) { const int32 StepIndex = 0; FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateThreadSafeSP(Executor.ToSharedRef(), &FStepExecutor::ExecuteStep, StepIndex), 0); } } ); Promise = MakeShareable(new TPromise()); return TAsyncResult(Promise->GetFuture(), nullptr, nullptr); } virtual bool IsExecuting() const override { return Promise.IsValid(); } virtual void OnCompleted(TFunction Callback) override { OnCompletedCallback = MoveTemp(Callback); } private: FStepExecutor( const TSharedRef& InConfiguration, const TSharedRef& InApplication) : Configuration(InConfiguration) , Application(InApplication) , Steps() , CurrentStepIndex(0) , Promise() , StepTotalProcessTime(FTimespan::Zero()) , LastDelay(0) { } bool ExecuteStep(float Delta, int32 StepIndex) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FStepExecutor_ExecuteStep); check(IsInGameThread()); FStepResult Result = FStepResult(FStepResult::EState::FAILED, 0); { FScopeLock StateLock(&StepsCS); if (0 == StepIndex) { Application->SetOverrideRealCursorCoordinates(true); } // If we've encountered an invalid step that's greater then zero then we were just waiting // a little bit after the last step completed before signaling completion. if ((StepIndex > 0 && !Steps.IsValidIndex(StepIndex)) || !Application->IsHandlingMessages()) { Application->SetOverrideRealCursorCoordinates(false); Promise->SetValue(true); Promise.Reset(); StepTotalProcessTime = FTimespan::Zero(); if (OnCompletedCallback) { OnCompletedCallback(); } return false; } check(Steps.IsValidIndex(StepIndex)); Result = Steps[StepIndex]->Execute(StepTotalProcessTime); } if (Result.State == FStepResult::EState::FAILED) { Application->SetOverrideRealCursorCoordinates(false); Promise->SetValue(false); Promise.Reset(); StepTotalProcessTime = FTimespan::Zero(); if (OnCompletedCallback) { OnCompletedCallback(); } return false; } if (Result.State == FStepResult::EState::DONE) { StepTotalProcessTime = FTimespan::Zero(); ++StepIndex; } CurrentStepIndex = StepIndex; float Milliseconds = (float)(Result.NextWait.GetTicks()) / ETimespan::TicksPerMillisecond; float Delay = FMath::Max(SMALL_NUMBER, (Milliseconds / 1000) * Configuration->ExecutionSpeedMultiplier); if (LastDelay < KINDA_SMALL_NUMBER) { StepTotalProcessTime += FTimespan::FromSeconds(Delta); } StepTotalProcessTime += FTimespan::FromSeconds(Delay); LastDelay = Delay; FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateThreadSafeSP(this, &FStepExecutor::ExecuteStep, StepIndex), Delay); return false; } private: const TSharedRef Configuration; const TSharedRef& Application; TArray> Steps; int32 CurrentStepIndex; TSharedPtr> Promise; FTimespan StepTotalProcessTime; float LastDelay; TFunction OnCompletedCallback; mutable FCriticalSection StepsCS; friend FStepExecutorFactory; }; TSharedRef FStepExecutorFactory::Create( const TSharedRef& Configuration, const TSharedRef& Application) { return MakeShareable(new FStepExecutor(Configuration, Application)); }