remove junk engine src (only need core comp)

This commit is contained in:
ApfelTeeSaft
2024-07-15 08:38:15 +02:00
parent f5c993da93
commit 15ccd031c2
79085 changed files with 0 additions and 26190638 deletions
@@ -1,50 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
namespace UnrealBuildTool.Rules
{
public class AITestSuite : ModuleRules
{
public AITestSuite(ReadOnlyTargetRules Target) : base(Target)
{
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
"Developer/AITestSuite/Private",
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
"GameplayTasks",
"AIModule",
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
PrecompileForTargets = PrecompileTargetsType.Any;
}
}
}
@@ -1,28 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Actions/TestPawnAction_Log.h"
#include "TestPawnAction_CallFunction.generated.h"
class UPawnActionsComponent;
template<typename ValueType> struct FTestLogger;
UCLASS()
class UTestPawnAction_CallFunction : public UTestPawnAction_Log
{
GENERATED_UCLASS_BODY()
typedef void(*FFunctionToCall)(UPawnActionsComponent& ActionsComponent, UTestPawnAction_CallFunction& Caller, ETestPawnActionMessage::Type);
FFunctionToCall FunctionToCall;
static UTestPawnAction_CallFunction* CreateAction(UWorld& World, FTestLogger<int32>& InLogger, FFunctionToCall InFunctionToCall);
virtual bool Start() override;
virtual bool Pause(const UPawnAction* PausedBy) override;
virtual bool Resume() override;
virtual void OnFinished(EPawnActionResult::Type WithResult) override;
virtual void OnChildFinished(UPawnAction& Action, EPawnActionResult::Type WithResult) override;
};
@@ -1,44 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Actions/PawnAction.h"
#include "TestPawnAction_Log.generated.h"
template<typename ValueType> struct FTestLogger;
namespace ETestPawnActionMessage
{
enum Type
{
Started,
Paused,
Resumed,
Finished,
ChildFinished,
};
}
UCLASS()
class UTestPawnAction_Log : public UPawnAction
{
GENERATED_UCLASS_BODY()
FTestLogger<int32>* Logger;
static UTestPawnAction_Log* CreateAction(UWorld& World, FTestLogger<int32>& InLogger);
virtual bool Start() override;
virtual bool Pause(const UPawnAction* PausedBy) override;
virtual bool Resume() override;
virtual void OnFinished(EPawnActionResult::Type WithResult) override;
virtual void OnChildFinished(UPawnAction& Action, EPawnActionResult::Type WithResult) override;
FAIResourcesSet& GetRequiredResourcesSet() { return RequiredResources; }
void SetRequiredResourcesSet(const FAIResourcesSet& InRequiredResources) { RequiredResources = InRequiredResources; }
/** Testing-hack to call protected function outside of protected environment */
void Terminate(EPawnActionResult::Type Result) { Finish(Result); }
};
@@ -1,33 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/Decorators/BTDecorator_Blackboard.h"
#include "TestBTDecorator_Blackboard.generated.h"
UCLASS(meta = (HiddenNode))
class UTestBTDecorator_Blackboard : public UBTDecorator_Blackboard
{
GENERATED_UCLASS_BODY()
public:
UPROPERTY()
int32 LogIndexBecomeRelevant;
UPROPERTY()
int32 LogIndexCeaseRelevant;
UPROPERTY()
int32 LogIndexCalculate;
protected:
virtual void OnBecomeRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual void OnCeaseRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
private:
void LogExecution(int32 LogNumber) const;
};
@@ -1,16 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTDecorator.h"
#include "TestBTDecorator_CantExecute.generated.h"
UCLASS(meta=(HiddenNode))
class UTestBTDecorator_CantExecute : public UBTDecorator
{
GENERATED_UCLASS_BODY()
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
};
@@ -1,29 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTDecorator.h"
#include "TestBTDecorator_DelayedAbort.generated.h"
struct FBTDelayedAbortMemory
{
uint64 EndFrameIdx;
};
UCLASS(meta = (HiddenNode))
class UTestBTDecorator_DelayedAbort : public UBTDecorator
{
GENERATED_UCLASS_BODY()
UPROPERTY()
int32 DelayTicks;
UPROPERTY()
bool bOnlyOnce;
virtual void OnBecomeRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
virtual uint16 GetInstanceMemorySize() const override;
};
@@ -1,31 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTService.h"
#include "TestBTService_Log.generated.h"
UCLASS(meta = (HiddenNode))
class UTestBTService_Log : public UBTService
{
GENERATED_UCLASS_BODY()
UPROPERTY()
int32 LogActivation;
UPROPERTY()
int32 LogDeactivation;
UPROPERTY()
FName KeyNameTick;
UPROPERTY()
int32 LogTick;
virtual void OnBecomeRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual void OnCeaseRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
void SetFlagOnTick(FName InKeyNameTick, bool bInCallTickOnSearchStart = false);
};
@@ -1,56 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTTaskNode.h"
#include "TestBTTask_LatentWithFlags.generated.h"
struct FBTLatentTaskMemory
{
uint64 FlagFrameIdx;
uint64 EndFrameIdx;
uint8 bFlagSet : 1;
uint8 bIsAborting : 1;
};
UCLASS(meta = (HiddenNode))
class UTestBTTask_LatentWithFlags : public UBTTaskNode
{
GENERATED_UCLASS_BODY()
UPROPERTY()
int32 LogIndexExecuteStart;
UPROPERTY()
int32 LogIndexExecuteFinish;
UPROPERTY()
int32 LogIndexAbortStart;
UPROPERTY()
int32 LogIndexAbortFinish;
UPROPERTY()
int32 ExecuteTicks;
UPROPERTY()
int32 AbortTicks;
UPROPERTY()
FName KeyNameExecute;
UPROPERTY()
FName KeyNameAbort;
UPROPERTY()
TEnumAsByte<EBTNodeResult::Type> LogResult;
virtual EBTNodeResult::Type ExecuteTask(class UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual uint16 GetInstanceMemorySize() const override;
void LogExecution(class UBehaviorTreeComponent& OwnerComp, int32 LogNumber);
protected:
virtual void TickTask(class UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
@@ -1,38 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTTaskNode.h"
#include "TestBTTask_Log.generated.h"
struct FBTLogTaskMemory
{
uint64 EndFrameIdx;
};
UCLASS(meta=(HiddenNode))
class UTestBTTask_Log : public UBTTaskNode
{
GENERATED_UCLASS_BODY()
UPROPERTY()
int32 LogIndex;
UPROPERTY()
int32 LogFinished;
UPROPERTY()
int32 ExecutionTicks;
UPROPERTY()
TEnumAsByte<EBTNodeResult::Type> LogResult;
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual uint16 GetInstanceMemorySize() const override;
void LogExecution(UBehaviorTreeComponent& OwnerComp, int32 LogNumber);
protected:
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
@@ -1,24 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTTaskNode.h"
#include "TestBTTask_SetFlag.generated.h"
UCLASS(meta=(HiddenNode))
class UTestBTTask_SetFlag : public UBTTaskNode
{
GENERATED_UCLASS_BODY()
UPROPERTY()
FName KeyName;
UPROPERTY()
bool bValue;
UPROPERTY()
TEnumAsByte<EBTNodeResult::Type> TaskResult;
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
@@ -1,24 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTTaskNode.h"
#include "TestBTTask_SetValue.generated.h"
UCLASS(meta=(HiddenNode))
class UTestBTTask_SetValue : public UBTTaskNode
{
GENERATED_UCLASS_BODY()
UPROPERTY()
FName KeyName;
UPROPERTY()
int32 Value;
UPROPERTY()
TEnumAsByte<EBTNodeResult::Type> TaskResult;
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
@@ -1,65 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Stats/Stats.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Object.h"
#include "AITestsCommon.h"
#include "Tickable.h"
#include "MockAI.generated.h"
class UAIPerceptionComponent;
class UBlackboardComponent;
class UPawnActionsComponent;
class UBrainComponent;
class UMockAI;
struct FTestTickHelper : FTickableGameObject
{
TWeakObjectPtr<class UMockAI> Owner;
FTestTickHelper() : Owner(NULL) {}
virtual void Tick(float DeltaTime);
virtual bool IsTickable() const { return Owner.IsValid(); }
virtual bool IsTickableInEditor() const { return true; }
virtual TStatId GetStatId() const;
};
UCLASS()
class UMockAI : public UObject
{
GENERATED_UCLASS_BODY()
virtual ~UMockAI();
FTestTickHelper TickHelper;
UPROPERTY()
UBlackboardComponent* BBComp;
UPROPERTY()
UBrainComponent* BrainComp;
UPROPERTY()
UAIPerceptionComponent* PerceptionComp;
UPROPERTY()
UPawnActionsComponent* PawnActionComp;
template<typename TBrainClass>
void UseBrainComponent()
{
BrainComp = NewObject<TBrainClass>(FAITestHelpers::GetWorld());
}
void UseBlackboardComponent();
void UsePerceptionComponent();
void UsePawnActionsComponent();
void SetEnableTicking(bool bShouldTick);
virtual void TickMe(float DeltaTime);
};
@@ -1,26 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BehaviorTreeTypes.h"
#include "MockAI.h"
#include "MockAI_BT.generated.h"
class UBehaviorTree;
class UBehaviorTreeComponent;
UCLASS()
class UMockAI_BT : public UMockAI
{
GENERATED_UCLASS_BODY()
UPROPERTY()
UBehaviorTreeComponent* BTComp;
static TArray<int32> ExecutionLog;
TArray<int32> ExpectedResult;
bool IsRunning() const;
void RunBT(UBehaviorTree& BTAsset, EBTExecutionMode::Type RunType = EBTExecutionMode::SingleRun);
};
@@ -1,80 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Object.h"
#include "GameplayTaskOwnerInterface.h"
#include "GameplayTask.h"
#include "GameplayTasksComponent.h"
#include "MockGameplayTasks.generated.h"
class AActor;
template<typename ValueType> struct FTestLogger;
namespace ETestTaskMessage
{
enum Type
{
Activate,
Tick,
ExternalConfirm,
ExternalCancel,
Ended
};
}
UCLASS()
class UMockTask_Log : public UGameplayTask
{
GENERATED_BODY()
protected:
FTestLogger<int32>* Logger;
bool bShoudEndAsPartOfActivation;
public:
UMockTask_Log(const FObjectInitializer& ObjectInitializer);
static UMockTask_Log* CreateTask(IGameplayTaskOwnerInterface& TaskOwner, FTestLogger<int32>& InLogger, const FGameplayResourceSet& Resources = FGameplayResourceSet(), uint8 Priority = FGameplayTasks::DefaultPriority);
protected:
virtual void Activate() override;
virtual void OnDestroy(bool bOwnerFinished) override;
public:
virtual void TickTask(float DeltaTime) override;
virtual void ExternalConfirm(bool bEndTask) override;
virtual void ExternalCancel() override;
// testing only hack-functions
void EnableTick() { bTickingTask = true; }
void SetInstaEnd(bool bNewValue) { bShoudEndAsPartOfActivation = bNewValue; }
};
//
// a Testing-time component that is a way to access UGameplayTasksComponent's protected properties
//
UCLASS()
class UMockGameplayTasksComponent : public UGameplayTasksComponent
{
GENERATED_BODY()
public:
int32 GetTaskPriorityQueueSize() const { return TaskPriorityQueue.Num(); }
};
UCLASS()
class UMockGameplayTaskOwner : public UObject, public IGameplayTaskOwnerInterface
{
GENERATED_BODY()
public:
UPROPERTY()
UGameplayTasksComponent* GTComponent;
virtual UGameplayTasksComponent* GetGameplayTasksComponent(const UGameplayTask& Task) const override { return GTComponent; }
virtual AActor* GetGameplayTaskOwner(const UGameplayTask* Task) const { return nullptr; }
};
@@ -1,31 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AITestSuite.h"
#include "AITestsCommon.h"
DEFINE_LOG_CATEGORY(LogAITestSuite);
DEFINE_LOG_CATEGORY(LogBehaviorTreeTest);
class FAITestSuite : public IAITestSuite
{
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
IMPLEMENT_MODULE(FAITestSuite, AITestSuite)
void FAITestSuite::StartupModule()
{
// This code will execute after your module is loaded into memory (but after global variables are initialized, of course.)
}
void FAITestSuite::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
@@ -1,65 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Actions/TestPawnAction_CallFunction.h"
#include "Engine/World.h"
UTestPawnAction_CallFunction::UTestPawnAction_CallFunction(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, FunctionToCall(nullptr)
{
}
UTestPawnAction_CallFunction* UTestPawnAction_CallFunction::CreateAction(UWorld& World, FTestLogger<int32>& InLogger, FFunctionToCall InFunctionToCall)
{
UTestPawnAction_CallFunction* Action = UPawnAction::CreateActionInstance<UTestPawnAction_CallFunction>(World);
if (Action)
{
Action->Logger = &InLogger;
Action->FunctionToCall = InFunctionToCall;
}
return Action;
}
bool UTestPawnAction_CallFunction::Start()
{
if (Super::Start())
{
(*FunctionToCall)(*GetOwnerComponent(), *this, ETestPawnActionMessage::Started);
return true;
}
return false;
}
bool UTestPawnAction_CallFunction::Pause(const UPawnAction* PausedBy)
{
if (Super::Pause(PausedBy))
{
(*FunctionToCall)(*GetOwnerComponent(), *this, ETestPawnActionMessage::Paused);
return true;
}
return false;
}
bool UTestPawnAction_CallFunction::Resume()
{
if (Super::Resume())
{
(*FunctionToCall)(*GetOwnerComponent(), *this, ETestPawnActionMessage::Resumed);
return true;
}
return false;
}
void UTestPawnAction_CallFunction::OnFinished(EPawnActionResult::Type WithResult)
{
Super::OnFinished(WithResult);
(*FunctionToCall)(*GetOwnerComponent(), *this, ETestPawnActionMessage::Finished);
}
void UTestPawnAction_CallFunction::OnChildFinished(UPawnAction& Action, EPawnActionResult::Type WithResult)
{
Super::OnChildFinished(Action, WithResult);
(*FunctionToCall)(*GetOwnerComponent(), *this, ETestPawnActionMessage::ChildFinished);
}
@@ -1,56 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Actions/TestPawnAction_Log.h"
#include "TestLogger.h"
#include "Engine/World.h"
UTestPawnAction_Log::UTestPawnAction_Log(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, Logger(nullptr)
{
}
UTestPawnAction_Log* UTestPawnAction_Log::CreateAction(UWorld& World, FTestLogger<int32>& InLogger)
{
UTestPawnAction_Log* Action = UPawnAction::CreateActionInstance<UTestPawnAction_Log>(World);
if (Action)
{
Action->Logger = &InLogger;
}
return Action;
}
bool UTestPawnAction_Log::Start()
{
Logger->Log(ETestPawnActionMessage::Started);
return Super::Start();
}
bool UTestPawnAction_Log::Pause(const UPawnAction* PausedBy)
{
Logger->Log(ETestPawnActionMessage::Paused);
return Super::Pause(PausedBy);
}
bool UTestPawnAction_Log::Resume()
{
Logger->Log(ETestPawnActionMessage::Resumed);
return Super::Resume();
}
void UTestPawnAction_Log::OnFinished(EPawnActionResult::Type WithResult)
{
Super::OnFinished(WithResult);
Logger->Log(ETestPawnActionMessage::Finished);
}
void UTestPawnAction_Log::OnChildFinished(UPawnAction& Action, EPawnActionResult::Type WithResult)
{
Super::OnChildFinished(Action, WithResult);
Logger->Log(ETestPawnActionMessage::ChildFinished);
}
@@ -1,37 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BehaviorTree/TestBTDecorator_Blackboard.h"
#include "MockAI_BT.h"
UTestBTDecorator_Blackboard::UTestBTDecorator_Blackboard(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
, LogIndexBecomeRelevant(-1)
, LogIndexCeaseRelevant(-1)
, LogIndexCalculate(-1)
{
}
void UTestBTDecorator_Blackboard::OnBecomeRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
LogExecution(LogIndexBecomeRelevant);
Super::OnBecomeRelevant(OwnerComp, NodeMemory);
}
void UTestBTDecorator_Blackboard::OnCeaseRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
LogExecution(LogIndexCeaseRelevant);
Super::OnCeaseRelevant(OwnerComp, NodeMemory);
}
bool UTestBTDecorator_Blackboard::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
LogExecution(LogIndexCalculate);
return Super::CalculateRawConditionValue(OwnerComp, NodeMemory);
}
void UTestBTDecorator_Blackboard::LogExecution(int32 LogNumber) const
{
if (LogNumber >= 0)
{
UMockAI_BT::ExecutionLog.Add(LogNumber);
}
}
@@ -1,17 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BehaviorTree/TestBTDecorator_CantExecute.h"
UTestBTDecorator_CantExecute::UTestBTDecorator_CantExecute(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
NodeName = TEXT("Can't Exexcute");
bAllowAbortNone = false;
bAllowAbortLowerPri = false;
bAllowAbortChildNodes = false;
}
bool UTestBTDecorator_CantExecute::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
return false;
}
@@ -1,41 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BehaviorTree/TestBTDecorator_DelayedAbort.h"
#include "AITestsCommon.h"
UTestBTDecorator_DelayedAbort::UTestBTDecorator_DelayedAbort(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
NodeName = "Delayed Abort";
DelayTicks = 5;
bOnlyOnce = true;
bNotifyTick = true;
bNotifyBecomeRelevant = true;
bAllowAbortNone = false;
bAllowAbortLowerPri = false;
bAllowAbortChildNodes = true;
FlowAbortMode = EBTFlowAbortMode::Self;
}
void UTestBTDecorator_DelayedAbort::OnBecomeRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
FBTDelayedAbortMemory* MyMemory = (FBTDelayedAbortMemory*)NodeMemory;
MyMemory->EndFrameIdx = FAITestHelpers::FramesCounter() + DelayTicks;
}
void UTestBTDecorator_DelayedAbort::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
FBTDelayedAbortMemory* MyMemory = (FBTDelayedAbortMemory*)NodeMemory;
if (FAITestHelpers::FramesCounter() >= MyMemory->EndFrameIdx)
{
OwnerComp.RequestExecution(this);
MyMemory->EndFrameIdx = bOnlyOnce ? MAX_uint64 : 0;
}
}
uint16 UTestBTDecorator_DelayedAbort::GetInstanceMemorySize() const
{
return sizeof(FBTDelayedAbortMemory);
}
@@ -1,59 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BehaviorTree/TestBTService_Log.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "MockAI_BT.h"
UTestBTService_Log::UTestBTService_Log(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
NodeName = "LogService";
bNotifyBecomeRelevant = true;
bNotifyCeaseRelevant = true;
LogActivation = INDEX_NONE;
LogDeactivation = INDEX_NONE;
KeyNameTick = NAME_None;
LogTick = INDEX_NONE;
}
void UTestBTService_Log::OnBecomeRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
Super::OnBecomeRelevant(OwnerComp, NodeMemory);
if (LogActivation >= 0)
{
UMockAI_BT::ExecutionLog.Add(LogActivation);
}
}
void UTestBTService_Log::OnCeaseRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
Super::OnCeaseRelevant(OwnerComp, NodeMemory);
if (LogDeactivation >= 0)
{
UMockAI_BT::ExecutionLog.Add(LogDeactivation);
}
}
void UTestBTService_Log::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
if (KeyNameTick != NAME_None)
{
OwnerComp.GetBlackboardComponent()->SetValueAsBool(KeyNameTick, true);
}
if (LogTick >= 0)
{
UMockAI_BT::ExecutionLog.Add(LogTick);
}
}
void UTestBTService_Log::SetFlagOnTick(FName InKeyNameTick, bool bInCallTickOnSearchStart /* = false */)
{
KeyNameTick = InKeyNameTick;
bCallTickOnSearchStart = bInCallTickOnSearchStart;
}
@@ -1,103 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BehaviorTree/TestBTTask_LatentWithFlags.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "MockAI_BT.h"
UTestBTTask_LatentWithFlags::UTestBTTask_LatentWithFlags(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
NodeName = "LatentTest";
LogIndexExecuteStart = 0;
LogIndexExecuteFinish = 0;
LogIndexAbortStart = 0;
LogIndexAbortFinish = 0;
ExecuteTicks = 2;
AbortTicks = 2;
KeyNameExecute = TEXT("Bool1");
KeyNameAbort = TEXT("Bool2");
LogResult = EBTNodeResult::Succeeded;
bNotifyTick = true;
}
EBTNodeResult::Type UTestBTTask_LatentWithFlags::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
FBTLatentTaskMemory* MyMemory = (FBTLatentTaskMemory*)NodeMemory;
MyMemory->FlagFrameIdx = ExecuteTicks + FAITestHelpers::FramesCounter();
MyMemory->EndFrameIdx = MyMemory->FlagFrameIdx + ExecuteTicks;
MyMemory->bFlagSet = false;
MyMemory->bIsAborting = false;
LogExecution(OwnerComp, LogIndexExecuteStart);
if (ExecuteTicks == 0)
{
OwnerComp.GetBlackboardComponent()->SetValueAsBool(KeyNameExecute, true);
MyMemory->bFlagSet = true;
LogExecution(OwnerComp, LogIndexExecuteFinish);
return LogResult;
}
return EBTNodeResult::InProgress;
}
EBTNodeResult::Type UTestBTTask_LatentWithFlags::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
FBTLatentTaskMemory* MyMemory = (FBTLatentTaskMemory*)NodeMemory;
MyMemory->FlagFrameIdx = AbortTicks + FAITestHelpers::FramesCounter();
MyMemory->EndFrameIdx = MyMemory->FlagFrameIdx + AbortTicks;
MyMemory->bFlagSet = false;
MyMemory->bIsAborting = true;
LogExecution(OwnerComp, LogIndexAbortStart);
if (AbortTicks == 0)
{
OwnerComp.GetBlackboardComponent()->SetValueAsBool(KeyNameAbort, true);
MyMemory->bFlagSet = true;
LogExecution(OwnerComp, LogIndexAbortFinish);
return EBTNodeResult::Aborted;
}
return EBTNodeResult::InProgress;
}
void UTestBTTask_LatentWithFlags::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
FBTLatentTaskMemory* MyMemory = (FBTLatentTaskMemory*)NodeMemory;
if (!MyMemory->bFlagSet && FAITestHelpers::FramesCounter() >= MyMemory->FlagFrameIdx)
{
MyMemory->bFlagSet = true;
OwnerComp.GetBlackboardComponent()->SetValueAsBool(
MyMemory->bIsAborting ? KeyNameAbort : KeyNameExecute,
true);
}
if (FAITestHelpers::FramesCounter() >= MyMemory->EndFrameIdx)
{
if (MyMemory->bIsAborting)
{
LogExecution(OwnerComp, LogIndexAbortFinish);
FinishLatentAbort(OwnerComp);
}
else
{
LogExecution(OwnerComp, LogIndexExecuteFinish);
FinishLatentTask(OwnerComp, LogResult);
}
}
}
uint16 UTestBTTask_LatentWithFlags::GetInstanceMemorySize() const
{
return sizeof(FBTLatentTaskMemory);
}
void UTestBTTask_LatentWithFlags::LogExecution(UBehaviorTreeComponent& OwnerComp, int32 LogNumber)
{
if (LogNumber >= 0)
{
UMockAI_BT::ExecutionLog.Add(LogNumber);
}
}
@@ -1,53 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BehaviorTree/TestBTTask_Log.h"
#include "MockAI_BT.h"
UTestBTTask_Log::UTestBTTask_Log(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
NodeName = "Log";
ExecutionTicks = 0;
LogIndex = 0;
LogFinished = -1;
LogResult = EBTNodeResult::Succeeded;
bNotifyTick = true;
}
EBTNodeResult::Type UTestBTTask_Log::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
FBTLogTaskMemory* MyMemory = (FBTLogTaskMemory*)NodeMemory;
MyMemory->EndFrameIdx = ExecutionTicks + FAITestHelpers::FramesCounter();
LogExecution(OwnerComp, LogIndex);
if (ExecutionTicks == 0)
{
return LogResult;
}
return EBTNodeResult::InProgress;
}
void UTestBTTask_Log::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
FBTLogTaskMemory* MyMemory = (FBTLogTaskMemory*)NodeMemory;
if (FAITestHelpers::FramesCounter() >= MyMemory->EndFrameIdx)
{
LogExecution(OwnerComp, LogFinished);
FinishLatentTask(OwnerComp, LogResult);
}
}
uint16 UTestBTTask_Log::GetInstanceMemorySize() const
{
return sizeof(FBTLogTaskMemory);
}
void UTestBTTask_Log::LogExecution(UBehaviorTreeComponent& OwnerComp, int32 LogNumber)
{
if (LogNumber >= 0)
{
UMockAI_BT::ExecutionLog.Add(LogNumber);
}
}
@@ -1,19 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BehaviorTree/TestBTTask_SetFlag.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Bool.h"
#include "BehaviorTree/BlackboardComponent.h"
UTestBTTask_SetFlag::UTestBTTask_SetFlag(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
NodeName = "Log";
TaskResult = EBTNodeResult::Succeeded;
KeyName = TEXT("Bool1");
bValue = true;
}
EBTNodeResult::Type UTestBTTask_SetFlag::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
OwnerComp.GetBlackboardComponent()->SetValue<UBlackboardKeyType_Bool>(KeyName, bValue);
return TaskResult;
}
@@ -1,19 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BehaviorTree/TestBTTask_SetValue.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Int.h"
#include "BehaviorTree/BlackboardComponent.h"
UTestBTTask_SetValue::UTestBTTask_SetValue(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
NodeName = "SetValue";
TaskResult = EBTNodeResult::Succeeded;
KeyName = TEXT("Int");
Value = 1;
}
EBTNodeResult::Type UTestBTTask_SetValue::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
OwnerComp.GetBlackboardComponent()->SetValue<UBlackboardKeyType_Int>(KeyName, Value);
return TaskResult;
}
@@ -1,87 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "MockAI.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Perception/AIPerceptionComponent.h"
#include "Actions/PawnActionsComponent.h"
#include "BrainComponent.h"
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
void FTestTickHelper::Tick(float DeltaTime)
{
if (Owner.IsValid())
{
Owner->TickMe(DeltaTime);
}
}
TStatId FTestTickHelper::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FTestTickHelper, STATGROUP_Tickables);
}
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
UMockAI::UMockAI(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
UMockAI::~UMockAI()
{
TickHelper.Owner.Reset();
}
void UMockAI::SetEnableTicking(bool bShouldTick)
{
if (bShouldTick)
{
TickHelper.Owner = this;
}
else
{
TickHelper.Owner = NULL;
}
}
void UMockAI::UseBlackboardComponent()
{
BBComp = NewObject<UBlackboardComponent>(FAITestHelpers::GetWorld());
}
void UMockAI::UsePerceptionComponent()
{
PerceptionComp = NewObject<UAIPerceptionComponent>(FAITestHelpers::GetWorld());
}
void UMockAI::UsePawnActionsComponent()
{
PawnActionComp = NewObject<UPawnActionsComponent>(FAITestHelpers::GetWorld());
}
void UMockAI::TickMe(float DeltaTime)
{
if (BBComp)
{
BBComp->TickComponent(DeltaTime, ELevelTick::LEVELTICK_All, NULL);
}
if (PerceptionComp)
{
PerceptionComp->TickComponent(DeltaTime, ELevelTick::LEVELTICK_All, NULL);
}
if (BrainComp)
{
BrainComp->TickComponent(DeltaTime, ELevelTick::LEVELTICK_All, NULL);
}
if (PawnActionComp)
{
PawnActionComp->TickComponent(DeltaTime, ELevelTick::LEVELTICK_All, NULL);
}
}
@@ -1,45 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "MockAI_BT.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BehaviorTree.h"
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
TArray<int32> UMockAI_BT::ExecutionLog;
UMockAI_BT::UMockAI_BT(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
UseBlackboardComponent();
UseBrainComponent<UBehaviorTreeComponent>();
BTComp = Cast<UBehaviorTreeComponent>(BrainComp);
}
}
bool UMockAI_BT::IsRunning() const
{
return BTComp && BTComp->IsRunning() && BTComp->GetRootTree();
}
void UMockAI_BT::RunBT(UBehaviorTree& BTAsset, EBTExecutionMode::Type RunType)
{
if (BTAsset.BlackboardAsset)
{
BBComp->InitializeBlackboard(*BTAsset.BlackboardAsset);
}
BBComp->CacheBrainComponent(*BTComp);
BTComp->CacheBlackboardComponent(BBComp);
UWorld* World = FAITestHelpers::GetWorld();
BBComp->RegisterComponentWithWorld(World);
BTComp->RegisterComponentWithWorld(World);
BTComp->StartTree(BTAsset, RunType);
}
@@ -1,81 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "MockGameplayTasks.h"
#include "TestLogger.h"
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
UMockTask_Log::UMockTask_Log(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, Logger(nullptr)
, bShoudEndAsPartOfActivation(false)
{
}
UMockTask_Log* UMockTask_Log::CreateTask(IGameplayTaskOwnerInterface& TaskOwner, FTestLogger<int32>& InLogger, const FGameplayResourceSet& Resources, uint8 Priority)
{
UMockTask_Log* Task = NewTask<UMockTask_Log>(TaskOwner);
if (Task)
{
Task->Logger = &InLogger;
Task->RequiredResources = Resources;
Task->ClaimedResources = Resources;
Task->Priority = Priority;
}
return Task;
}
void UMockTask_Log::Activate()
{
if (Logger)
{
Logger->Log(ETestTaskMessage::Activate);
}
Super::Activate();
if (bShoudEndAsPartOfActivation)
{
EndTask();
}
}
void UMockTask_Log::OnDestroy(bool bInOwnerFinished)
{
if (Logger)
{
Logger->Log(ETestTaskMessage::Ended);
}
Super::OnDestroy(bInOwnerFinished);
}
void UMockTask_Log::TickTask(float DeltaTime)
{
if (Logger)
{
Logger->Log(ETestTaskMessage::Tick);
}
Super::TickTask(DeltaTime);
}
void UMockTask_Log::ExternalConfirm(bool bEndTask)
{
if (Logger)
{
Logger->Log(ETestTaskMessage::ExternalConfirm);
}
Super::ExternalConfirm(bEndTask);
}
void UMockTask_Log::ExternalCancel()
{
if (Logger)
{
Logger->Log(ETestTaskMessage::ExternalCancel);
}
Super::ExternalCancel();
}
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
@@ -1,3 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "TestLogger.h"
@@ -1,112 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AITestsCommon.h"
#include "EngineGlobals.h"
#include "Engine/Engine.h"
namespace FAITestHelpers
{
uint64 UpdatesCounter = 0;
void UpdateFrameCounter()
{
static uint64 PreviousFramesCounter = GFrameCounter;
if (PreviousFramesCounter != GFrameCounter)
{
++UpdatesCounter;
PreviousFramesCounter = GFrameCounter;
}
}
uint64 FramesCounter()
{
return UpdatesCounter;
}
}
bool FAITestCommand_WaitSeconds::Update()
{
float NewTime = FPlatformTime::Seconds();
if (NewTime - StartTime >= Duration)
{
return true;
}
return false;
}
bool FAITestCommand_WaitOneTick::Update()
{
if (bAlreadyRun == false)
{
bAlreadyRun = true;
return true;
}
return false;
}
bool FAITestCommand_SetUpTest::Update()
{
return AITest && AITest->SetUp();
}
bool FAITestCommand_PerformTest::Update()
{
return AITest == nullptr || AITest->Update();
}
bool FAITestCommand_TearDownTest::Update()
{
if (AITest)
{
AITest->TearDown();
delete AITest;
AITest = nullptr;
}
return true;
}
namespace FAITestHelpers
{
UWorld* GetWorld()
{
#if WITH_EDITOR
if (GIsEditor)
{
return GWorld;
}
#endif // WITH_EDITOR
return GEngine->GetWorldContexts()[0].World();
}
}
//----------------------------------------------------------------------//
// FAITestBase
//----------------------------------------------------------------------//
FAITestBase::~FAITestBase()
{
check(bTearedDown && "Super implementation of TearDown not called!");
}
void FAITestBase::AddAutoDestroyObject(UObject& ObjectRef)
{
ObjectRef.AddToRoot();
SpawnedObjects.Add(&ObjectRef);
}
UWorld& FAITestBase::GetWorld() const
{
UWorld* World = FAITestHelpers::GetWorld();
check(World);
return *World;
}
void FAITestBase::TearDown()
{
bTearedDown = true;
for (auto AutoDestroyedObject : SpawnedObjects)
{
AutoDestroyedObject->RemoveFromRoot();
}
SpawnedObjects.Reset();
}
@@ -1,3 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
File diff suppressed because it is too large Load Diff
@@ -1,497 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "GameplayTask.h"
#include "AITestsCommon.h"
#include "MockGameplayTasks.h"
#define LOCTEXT_NAMESPACE "AITestSuite_GameplayTasksTest"
typedef FAITest_SimpleComponentBasedTest<UMockGameplayTasksComponent> FAITest_GameplayTasksTest;
static const FGameplayResourceSet::FResourceID ResourceMovement = 0;
static const FGameplayResourceSet::FResourceID ResourceLogic = 1;
static const FGameplayResourceSet::FResourceID ResourceAnimation = 2;
static const FGameplayResourceSet MovementResourceSet = FGameplayResourceSet().AddID(ResourceMovement);
static const FGameplayResourceSet LogicResourceSet = FGameplayResourceSet().AddID(ResourceLogic);
static const FGameplayResourceSet AnimationResourceSet = FGameplayResourceSet().AddID(ResourceAnimation);
static const FGameplayResourceSet MoveAndAnimResourceSet = FGameplayResourceSet().AddSet(MovementResourceSet).AddSet(AnimationResourceSet);
static const FGameplayResourceSet MoveAndLogicResourceSet = FGameplayResourceSet((1 << ResourceMovement) | (1 << ResourceLogic));
static const FGameplayResourceSet MoveAnimLogicResourceSet = FGameplayResourceSet((1 << ResourceMovement) | (1 << ResourceLogic) | (1 << ResourceAnimation));
static const uint8 LowPriority = 1;
static const uint8 HighPriority = 255;
struct FAITest_GameplayTask_ComponentState : public FAITest_GameplayTasksTest
{
virtual bool InstantTest() override
{
AITEST_FALSE("UGameplayTasksComponent\'s default behavior is not to tick initially", Component->GetShouldTick());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_ComponentState, "System.AI.Gameplay Tasks.Component\'s basic behavior")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_ExternalCancelWithTick : public FAITest_GameplayTasksTest
{
UMockTask_Log* Task;
virtual bool SetUp() override
{
FAITest_GameplayTasksTest::SetUp();
Logger.ExpectedValues.Add(ETestTaskMessage::Activate);
Logger.ExpectedValues.Add(ETestTaskMessage::Tick);
Logger.ExpectedValues.Add(ETestTaskMessage::ExternalCancel);
Logger.ExpectedValues.Add(ETestTaskMessage::Ended);
UWorld& World = GetWorld();
Task = UMockTask_Log::CreateTask(*Component, Logger);
Task->EnableTick();
AITEST_TRUE("Task should be \'uninitialized\' before Activate is called on it", Task->GetState() == EGameplayTaskState::AwaitingActivation);
Task->ReadyForActivation();
AITEST_TRUE("Task should be \'Active\' after basic call to ReadyForActivation", Task->GetState() == EGameplayTaskState::Active);
AITEST_TRUE("Component should want to tick in this scenario", Component->GetShouldTick());
return true;
}
virtual bool InstantTest() override
{
TickComponent();
Task->ExternalCancel();
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_ExternalCancelWithTick, "System.AI.Gameplay Tasks.External Cancel with Tick")
//----------------------------------------------------------------------//
// In this test the task should get properly created, acticated and end
// during update without any ticking
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_SelfEnd : public FAITest_GameplayTasksTest
{
UMockTask_Log* Task;
virtual bool SetUp() override
{
FAITest_GameplayTasksTest::SetUp();
Logger.ExpectedValues.Add(ETestTaskMessage::Activate);
Logger.ExpectedValues.Add(ETestTaskMessage::Ended);
UWorld& World = GetWorld();
Task = UMockTask_Log::CreateTask(*Component, Logger);
Task->EnableTick();
Task->ReadyForActivation();
return true;
}
virtual bool InstantTest() override
{
Task->EndTask();
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_SelfEnd, "System.AI.Gameplay Tasks.Self End")
//----------------------------------------------------------------------//
// Testing multiple simultaneously ticking tasks
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_SimulatanousTick : public FAITest_GameplayTasksTest
{
UMockTask_Log* Tasks[3];
virtual bool SetUp() override
{
FAITest_GameplayTasksTest::SetUp();
Logger.ExpectedValues.Add(ETestTaskMessage::Activate);
Logger.ExpectedValues.Add(ETestTaskMessage::Activate);
Logger.ExpectedValues.Add(ETestTaskMessage::Activate);
Logger.ExpectedValues.Add(ETestTaskMessage::Tick);
Logger.ExpectedValues.Add(ETestTaskMessage::Tick);
Logger.ExpectedValues.Add(ETestTaskMessage::Tick);
Logger.ExpectedValues.Add(ETestTaskMessage::ExternalCancel);
Logger.ExpectedValues.Add(ETestTaskMessage::Ended);
Logger.ExpectedValues.Add(ETestTaskMessage::ExternalCancel);
Logger.ExpectedValues.Add(ETestTaskMessage::Ended);
Logger.ExpectedValues.Add(ETestTaskMessage::ExternalCancel);
Logger.ExpectedValues.Add(ETestTaskMessage::Ended);
for (int32 Index = 0; Index < sizeof(Tasks) / sizeof(UMockTask_Log*); ++Index)
{
Tasks[Index] = UMockTask_Log::CreateTask(*Component, Logger);
Tasks[Index]->EnableTick();
Tasks[Index]->ReadyForActivation();
}
AITEST_TRUE("Component should want to tick in this scenario", Component->GetShouldTick());
return true;
}
virtual bool InstantTest() override
{
TickComponent();
for (int32 Index = 0; Index < sizeof(Tasks) / sizeof(UMockTask_Log*); ++Index)
{
Tasks[Index]->ExternalCancel();
}
AITEST_FALSE("Component should want to tick in this scenario", Component->GetShouldTick());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_SimulatanousTick, "System.AI.Gameplay Tasks.Simultanously ticking tasks")
//----------------------------------------------------------------------//
// Testing multiple simultaneously ticking tasks UGameplayTaskResource
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_ResourceSet : public FAITestBase
{
virtual bool InstantTest() override
{
FGameplayResourceSet ResourcesSet;
AITEST_TRUE("New FGameplayResourceSet should be empty initialy", ResourcesSet.IsEmpty());
ResourcesSet.AddID(ResourceLogic);
AITEST_FALSE("Added one ID, ResourcesSet should not be perceived as empty now", ResourcesSet.IsEmpty());
ResourcesSet.RemoveID(ResourceAnimation);
AITEST_FALSE("Removed ID not previously added, ResourcesSet should not be perceived as empty now", ResourcesSet.IsEmpty());
ResourcesSet.RemoveID(ResourceLogic);
AITEST_TRUE("Removed ID previously added, ResourcesSet should be empty now", ResourcesSet.IsEmpty());
AITEST_FALSE("Single ID checking, not present ID", MoveAndAnimResourceSet.HasAnyID(LogicResourceSet));
AITEST_TRUE("Single ID checking", MoveAndAnimResourceSet.HasAnyID(MovementResourceSet));
AITEST_TRUE("Single ID checking", MoveAndAnimResourceSet.HasAnyID(AnimationResourceSet));
AITEST_TRUE("Multiple ID checking - has all, self test", MoveAndAnimResourceSet.HasAllIDs(MoveAndAnimResourceSet));
AITEST_TRUE("Multiple ID checking - has all, other identical", MoveAndAnimResourceSet.HasAllIDs(FGameplayResourceSet((1 << ResourceMovement) | (1 << ResourceAnimation))));
AITEST_FALSE("Multiple ID checking - has all, other different", MoveAndAnimResourceSet.HasAllIDs(MoveAndLogicResourceSet));
AITEST_TRUE("Multiple ID checking - overlap", MoveAndAnimResourceSet.GetOverlap(MoveAndLogicResourceSet) == MovementResourceSet);
AITEST_TRUE("Multiple ID checking - substraction", MoveAndAnimResourceSet.GetDifference(MoveAndLogicResourceSet) == AnimationResourceSet);
AITEST_FALSE("FGameplayResourceSet containing 0-th ID is not empty", MovementResourceSet.IsEmpty());
AITEST_TRUE("FGameplayResourceSet has 0-th ID", MovementResourceSet.HasID(ResourceMovement));
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_ResourceSet, "System.AI.Gameplay Tasks.Resource Set")
//----------------------------------------------------------------------//
// Running tasks requiring non-overlapping resources
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_NonOverlappingResources : public FAITest_GameplayTasksTest
{
UMockTask_Log* Tasks[2];
virtual bool InstantTest() override
{
UWorld& World = GetWorld();
Tasks[0] = UMockTask_Log::CreateTask(*Component, Logger, MoveAndAnimResourceSet);
Tasks[1] = UMockTask_Log::CreateTask(*Component, Logger, LogicResourceSet);
Tasks[0]->ReadyForActivation();
AITEST_TRUE("TasksComponent should claim it's using 0th task's resources", Component->GetCurrentlyUsedResources() == Tasks[0]->GetClaimedResources());
Tasks[1]->ReadyForActivation();
AITEST_TRUE("Both tasks should be \'Active\' since their resources do not overlap", Tasks[0]->GetState() == EGameplayTaskState::Active && Tasks[1]->GetState() == EGameplayTaskState::Active);
AITEST_TRUE("TasksComponent should claim it's using only latter task's resources", Component->GetCurrentlyUsedResources() == MoveAnimLogicResourceSet);
Tasks[0]->ExternalCancel();
AITEST_TRUE("Only index 1 task's resources should be relevant now", Component->GetCurrentlyUsedResources() == Tasks[1]->GetClaimedResources());
Tasks[1]->ExternalCancel();
AITEST_TRUE("No resources should be occupied now", Component->GetCurrentlyUsedResources().IsEmpty());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_NonOverlappingResources, "System.AI.Gameplay Tasks.Non-overlapping resources")
//----------------------------------------------------------------------//
// Running tasks requiring overlapping resources
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_OverlappingResources : public FAITest_GameplayTasksTest
{
UMockTask_Log* Tasks[2];
virtual bool InstantTest() override
{
UWorld& World = GetWorld();
Tasks[0] = UMockTask_Log::CreateTask(*Component, Logger, MoveAndAnimResourceSet);
Tasks[1] = UMockTask_Log::CreateTask(*Component, Logger, MoveAndLogicResourceSet);
Tasks[0]->ReadyForActivation();
Tasks[1]->ReadyForActivation();
AITEST_TRUE("Only the latter task should be active since it shadows the other one in terms of required resources", Tasks[1]->GetState() == EGameplayTaskState::Active);
AITEST_TRUE("The first task should be paused at this moment", Tasks[0]->GetState() == EGameplayTaskState::Paused);
AITEST_TRUE("TasksComponent should claim it's using only latter task's resources", Component->GetCurrentlyUsedResources() == Tasks[1]->GetClaimedResources());
Tasks[1]->ExternalCancel();
AITEST_TRUE("Now the latter task should be marked as Finished", Tasks[1]->GetState() == EGameplayTaskState::Finished);
AITEST_TRUE("And the first task should be resumed", Tasks[0]->GetState() == EGameplayTaskState::Active);
AITEST_TRUE("TasksComponent should claim it's using only first task's resources", Component->GetCurrentlyUsedResources() == Tasks[0]->GetClaimedResources());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_OverlappingResources, "System.AI.Gameplay Tasks.Overlapping resources")
//----------------------------------------------------------------------//
// Pausing a task overlapping a lower priority task should not resume the low priority task
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_PausingTasksBlockingOtherTasks : public FAITest_GameplayTasksTest
{
UMockTask_Log* Tasks[3];
virtual bool InstantTest() override
{
UWorld& World = GetWorld();
Tasks[0] = UMockTask_Log::CreateTask(*Component, Logger, MovementResourceSet);
Tasks[1] = UMockTask_Log::CreateTask(*Component, Logger, MoveAndLogicResourceSet);
Tasks[2] = UMockTask_Log::CreateTask(*Component, Logger, LogicResourceSet);
Tasks[0]->ReadyForActivation();
Tasks[1]->ReadyForActivation();
AITEST_FALSE("First task should be paused since it's resources get overlapped", Tasks[0]->IsActive());
AITEST_TRUE("Second task should on top and active", Tasks[1]->IsActive());
Tasks[2]->ReadyForActivation();
AITEST_FALSE("Second task should get paused since its resources got overlapped", Tasks[1]->IsActive());
AITEST_FALSE("First task should remain paused since it's resources get overlapped by the paused task", Tasks[0]->IsActive());
Tasks[2]->ExternalCancel();
AITEST_FALSE("Nothing shoud change for the first task", Tasks[0]->IsActive());
AITEST_TRUE("Second task should be active again", Tasks[1]->IsActive());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_PausingTasksBlockingOtherTasks, "System.AI.Gameplay Tasks.Pausing tasks blocking other tasks")
//----------------------------------------------------------------------//
// Priority handling
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_Priorities : public FAITest_GameplayTasksTest
{
UMockTask_Log* Tasks[3];
virtual bool InstantTest() override
{
UWorld& World = GetWorld();
// all tasks use same resources, have different priorities
// let's do some tick testing as well
Tasks[0] = UMockTask_Log::CreateTask(*Component, Logger, MoveAndAnimResourceSet, LowPriority);
Tasks[0]->EnableTick();
Tasks[1] = UMockTask_Log::CreateTask(*Component, Logger, MoveAndAnimResourceSet);
Tasks[1]->EnableTick();
Tasks[2] = UMockTask_Log::CreateTask(*Component, Logger, MoveAndAnimResourceSet, HighPriority);
Tasks[1]->ReadyForActivation();
Tasks[0]->ReadyForActivation();
AITEST_TRUE("Task at index 1 should be active at this point since it's higher priority", Tasks[1]->IsActive() && !Tasks[0]->IsActive());
AITEST_TRUE("TasksComponent should claim it's using only resources of task 1", Component->GetCurrentlyUsedResources() == Tasks[1]->GetClaimedResources());
AITEST_TRUE("Current top action wants to tick so Component should want that as well", Component->GetShouldTick());
Tasks[2]->ReadyForActivation();
AITEST_TRUE("Now the last pushed, highest priority task should be active", Tasks[2]->IsActive() && !Tasks[0]->IsActive() && !Tasks[1]->IsActive());
AITEST_FALSE("No ticking task is active so Component should not want to tick", Component->GetShouldTick());
Tasks[1]->ExternalCancel();
AITEST_TRUE("Canceling mid-priority inactive task should not influence what's active", Tasks[2]->IsActive() && !Tasks[0]->IsActive() && !Tasks[1]->IsActive());
AITEST_FALSE("Current top action still doesn't want to tick, so neither should the Component", Component->GetShouldTick());
Tasks[2]->ExternalCancel();
AITEST_TRUE("After canceling the top-priority task the lowest priority task remains to be active", !Tasks[2]->IsActive() && Tasks[0]->IsActive() && !Tasks[1]->IsActive());
AITEST_TRUE("New top action wants tick, so should Component", Component->GetShouldTick());
Tasks[0]->ExternalCancel();
AITEST_FALSE("Task-less component should not want to tick", Component->GetShouldTick());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_Priorities, "System.AI.Gameplay Tasks.Priorities")
//----------------------------------------------------------------------//
// Internal ending, by task ending itself or owner finishing
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_InternalEnding : public FAITest_GameplayTasksTest
{
static const int32 TasksCount = 4;
UMockTask_Log* Task_SelfEndNoResources;
UMockTask_Log* Task_OwnerEndNoResources;
UMockTask_Log* Task_SelfEndWithResources;
UMockTask_Log* Task_OwnerEndWithResources;
UMockTask_Log* Tasks[TasksCount];
virtual bool InstantTest() override
{
UWorld& World = GetWorld();
// all tasks use same resources, have different priorities
Tasks[0] = Task_SelfEndNoResources = UMockTask_Log::CreateTask(*Component, Logger);
Tasks[1] = Task_OwnerEndNoResources = UMockTask_Log::CreateTask(*Component, Logger);
// not using overlapping resources set on purpose - want to test them independently
Tasks[2] = Task_SelfEndWithResources = UMockTask_Log::CreateTask(*Component, Logger, MovementResourceSet);
Tasks[3] = Task_OwnerEndWithResources = UMockTask_Log::CreateTask(*Component, Logger, LogicResourceSet);
for (int32 Index = 0; Index < TasksCount; ++Index)
{
Tasks[Index]->ReadyForActivation();
AITEST_TRUE("Trivial activation should succeed", Tasks[Index]->IsActive());
}
AITEST_TRUE("Resources should sum up", Component->GetCurrentlyUsedResources() == MoveAndLogicResourceSet);
Task_SelfEndNoResources->EndTask();
AITEST_FALSE("Task_SelfEndNoResources should be \'done\' now", Task_SelfEndNoResources->IsActive());
Task_OwnerEndNoResources->TaskOwnerEnded();
AITEST_FALSE("Task_SelfEndNoResources should be \'done\' now", Task_OwnerEndNoResources->IsActive());
Task_SelfEndWithResources->EndTask();
AITEST_FALSE("Task_SelfEndWithResources should be \'done\' now", Task_SelfEndWithResources->IsActive());
AITEST_TRUE("Only the other task's resources should matter now", Component->GetCurrentlyUsedResources() == LogicResourceSet);
AITEST_TRUE("There should be only one active task in the priority queue", Component->GetTaskPriorityQueueSize() == 1);
Task_OwnerEndWithResources->TaskOwnerEnded();
AITEST_FALSE("Task_SelfEndWithResources should be \'done\' now", Task_OwnerEndWithResources->IsActive());
AITEST_TRUE("No resources should be locked at this moment", Component->GetCurrentlyUsedResources().IsEmpty());
AITEST_TRUE("Priority Task Queue should be empty", Component->GetTaskPriorityQueueSize() == 0);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_InternalEnding, "System.AI.Gameplay Tasks.Self and Owner ending")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_MultipleOwners : public FAITest_GameplayTasksTest
{
static const int32 TasksCount = 3;
UMockTask_Log* Tasks[TasksCount];
UMockTask_Log* LowPriorityTask;
UMockGameplayTaskOwner* OtherOwner;
virtual bool InstantTest() override
{
OtherOwner = NewObject<UMockGameplayTaskOwner>();
OtherOwner->GTComponent = Component;
Tasks[0] = UMockTask_Log::CreateTask(*Component, Logger, MovementResourceSet);
Tasks[1] = UMockTask_Log::CreateTask(*OtherOwner, Logger, MovementResourceSet);
Tasks[2] = UMockTask_Log::CreateTask(*Component, Logger, MovementResourceSet);
for (int32 Index = 0; Index < TasksCount; ++Index)
{
Tasks[Index]->ReadyForActivation();
}
// This part tests what happens if "other owner" task is in the middle of the queue and
// not active
AITEST_TRUE("Last pushed task should be active now", Tasks[2]->IsActive());
Component->EndAllResourceConsumingTasksOwnedBy(*Component);
AITEST_TRUE("There should be only one task in the queue now", Component->GetTaskPriorityQueueSize() == 1);
AITEST_TRUE("The last remaining task should be active now", Tasks[1]->IsActive());
// this part tests what happens during pruning if the "other owner" task is active at the
// moment of performing the action
LowPriorityTask = UMockTask_Log::CreateTask(*Component, Logger, MovementResourceSet, LowPriority);
LowPriorityTask->ReadyForActivation();
AITEST_TRUE("There should be 2 tasks in the queue now", Component->GetTaskPriorityQueueSize() == 2);
Component->EndAllResourceConsumingTasksOwnedBy(*Component);
AITEST_TRUE("There should be only one task in the queue after second pruning", Component->GetTaskPriorityQueueSize() == 1);
AITEST_TRUE("The last remaining task should be still active", Tasks[1]->IsActive());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_MultipleOwners, "System.AI.Gameplay Tasks.Handling multiple task owners")
//----------------------------------------------------------------------//
// Claimed vs Required resources test
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_ClaimedResources : public FAITest_GameplayTasksTest
{
static const int32 TasksCount = 4;
UMockTask_Log* Tasks[TasksCount];
virtual bool InstantTest() override
{
UWorld& World = GetWorld();
// create three tasks
// first one has a resource we're going to overlap with the extra-claimed resource of the next task
Tasks[0] = UMockTask_Log::CreateTask(*Component, Logger, MovementResourceSet);
Tasks[0]->ReadyForActivation();
// second task requires non-overlapping resources to first task
Tasks[1] = UMockTask_Log::CreateTask(*Component, Logger, AnimationResourceSet);
// but declared an overlapping resource as "claimed"
Tasks[1]->AddClaimedResourceSet(MovementResourceSet);
Tasks[1]->ReadyForActivation();
// at this point first task should get paused since it's required resource is claimed, or shadowed, by the newer task
AITEST_FALSE("The first task should get paused since its required resource is claimed, or shadowed, by the newer task", Tasks[0]->IsActive());
AITEST_TRUE("The second task should be running, nothing obstructing it", Tasks[1]->IsActive());
// a new low-priority task should not be allowed to run neither
Tasks[2] = UMockTask_Log::CreateTask(*Component, Logger, MovementResourceSet, LowPriority);
Tasks[2]->ReadyForActivation();
AITEST_FALSE("The new low-priority task should not be allowed to run neither", Tasks[2]->IsActive());
AITEST_TRUE("The second task should be still running", Tasks[1]->IsActive());
// however, a new task, that's using the overlapped claimed resource
// should run without any issues
// note, this doesn't have to be "high priority" task - new tasks with same priority as "current"
// are treated like higher priority anyway
Tasks[3] = UMockTask_Log::CreateTask(*Component, Logger, MovementResourceSet, HighPriority);
Tasks[3]->ReadyForActivation();
AITEST_TRUE("The new high-priority task should be allowed to run", Tasks[3]->IsActive());
// but active task, that declared the active resources should not get paused neither
AITEST_TRUE("The second task should be still running, it's required resources are not being overlapped", Tasks[1]->IsActive());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_ClaimedResources, "System.AI.Gameplay Tasks.Claimed resources")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_GameplayTask_ClaimedResourcesAndInstantFinish : public FAITest_GameplayTasksTest
{
static const int32 TasksCount = 4;
UMockTask_Log* Task;
virtual bool InstantTest() override
{
UWorld& World = GetWorld();
Task = UMockTask_Log::CreateTask(*Component, Logger, MovementResourceSet);
Task->SetInstaEnd(true);
Task->ReadyForActivation();
AITEST_TRUE("No claimed resources should be left behind", Component->GetCurrentlyUsedResources().IsEmpty());
AITEST_TRUE("There should no active tasks when task auto-insta-ended", Component->GetTaskPriorityQueueSize() == 0);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_GameplayTask_ClaimedResourcesAndInstantFinish, "System.AI.Gameplay Tasks.Claimed resources vs Insta-finish tasks")
// add tests if component wants ticking at while aborting/reactivating tasks
// add test for re-adding/re-activating a finished task
#undef LOCTEXT_NAMESPACE
@@ -1,378 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "AITestsCommon.h"
#include "Actions/PawnActionsComponent.h"
#include "Actions/TestPawnAction_Log.h"
#include "Actions/TestPawnAction_CallFunction.h"
#define LOCTEXT_NAMESPACE "AITestSuite_PawnActionTest"
typedef FAITest_SimpleComponentBasedTest<UPawnActionsComponent> FAITest_SimpleActionsTest;
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_Push : public FAITest_SimpleActionsTest
{
UTestPawnAction_Log* Action;
virtual bool SetUp() override
{
FAITest_SimpleActionsTest::SetUp();
UWorld& World = GetWorld();
Action = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*Action, EAIRequestPriority::Logic);
AITEST_NULL("No action should be active at this point", Component->GetCurrentAction());
return true;
}
virtual bool InstantTest() override
{
TickComponent();
AITEST_TRUE("After one tick created action should be the active one", Component->GetCurrentAction() == Action);
AITEST_TRUE("After one tick created action should have been started", Logger.LoggedValues.Top() == ETestPawnActionMessage::Started);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_Push, "System.AI.Pawn Actions.Pushing Single Action")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_PushingSameActionWithDelay : public FAITest_SimpleActionsTest
{
UTestPawnAction_Log* Action;
virtual bool SetUp() override
{
FAITest_SimpleActionsTest::SetUp();
UWorld& World = GetWorld();
Action = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*Action, EAIRequestPriority::Logic);
return true;
}
virtual bool InstantTest() override
{
TickComponent();
AITEST_FALSE("Addind an action for a second time should fail", Component->PushAction(*Action, EAIRequestPriority::Logic));
AITEST_FALSE("Addind an action for a second time, but with different priority, should fail", Component->PushAction(*Action, EAIRequestPriority::Ultimate));
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_PushingSameActionWithDelay, "System.AI.Pawn Actions.Pusihng action that has already been pushed should fail")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_Pause : public FAITest_SimpleActionsTest
{
UTestPawnAction_Log* Action;
virtual bool SetUp() override
{
FAITest_SimpleActionsTest::SetUp();
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Paused);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
UWorld& World = GetWorld();
Action = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*Action, EAIRequestPriority::Logic);
return true;
}
virtual bool InstantTest() override
{
UWorld& World = GetWorld();
TickComponent();
UTestPawnAction_Log* AnotherAction = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*AnotherAction, EAIRequestPriority::Logic);
TickComponent();
AITEST_TRUE("Second pushed action should be the active one", Component->GetCurrentAction() == AnotherAction);
AITEST_TRUE("First actionshould be paused", Action->IsPaused());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_Pause, "System.AI.Pawn Actions.Pausing Action by younger Action of same priority")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_SamePriorityOrder : public FAITest_SimpleActionsTest
{
UTestPawnAction_Log* FirstAction;
virtual bool SetUp() override
{
FAITest_SimpleActionsTest::SetUp();
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
UWorld& World = GetWorld();
FirstAction = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*FirstAction, EAIRequestPriority::Logic);
UTestPawnAction_Log* SecondAction = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*SecondAction, EAIRequestPriority::Logic);
return true;
}
virtual bool InstantTest() override
{
TickComponent();
AITEST_TRUE("Second pushed action should be the active one", Component->GetCurrentAction() != FirstAction);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_SamePriorityOrder, "System.AI.Pawn Actions.Respecting push order")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_DoublePushingAction : public FAITest_SimpleActionsTest
{
virtual bool InstantTest() override
{
UWorld& World = GetWorld();
UTestPawnAction_Log* Action = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*Action, EAIRequestPriority::Logic);
AITEST_FALSE("Pushing same action for the second time should fail", Component->PushAction(*Action, EAIRequestPriority::Logic));
AITEST_TRUE("There should be exactly one ActionEvent awaiting processing", Component->GetActionEventsQueueSize() == 1);
TickComponent();
AITEST_TRUE("There should be only one action on stack now.", Component->GetActionStackSize(EAIRequestPriority::Logic) == 1);
AITEST_TRUE("Action queue should be empty.", Component->GetActionEventsQueueSize() == 0);
AITEST_FALSE("Pushing already active action should", Component->PushAction(*Action, EAIRequestPriority::Logic));
AITEST_TRUE("Action queue should be empty.", Component->GetActionEventsQueueSize() == 0);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_DoublePushingAction, "System.AI.Pawn Actions.Pushing same action twice")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_SimplePriority : public FAITest_SimpleActionsTest
{
virtual bool InstantTest() override
{
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Paused);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
UWorld& World = GetWorld();
UTestPawnAction_Log* LowPriorityAction = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*LowPriorityAction, EAIRequestPriority::Logic);
TickComponent();
UTestPawnAction_Log* HighPriorityAction = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*HighPriorityAction, EAIRequestPriority::Reaction);
TickComponent();
AITEST_TRUE("There should be exactly one action on Logic stack now.", Component->GetActionStackSize(EAIRequestPriority::Logic) == 1);
AITEST_TRUE("There should be exactly one action on Reaction stack now.", Component->GetActionStackSize(EAIRequestPriority::Reaction) == 1);
AITEST_TRUE("The higher priority action should be the active one", Component->GetCurrentAction() == HighPriorityAction);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_SimplePriority, "System.AI.Pawn Actions.Pushing different priority actions")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_HighPriorityKeepRunning : public FAITest_SimpleActionsTest
{
virtual bool InstantTest() override
{
// only this one event should get logged
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
UWorld& World = GetWorld();
UTestPawnAction_Log* HighPriorityAction = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*HighPriorityAction, EAIRequestPriority::Reaction);
TickComponent();
UTestPawnAction_Log* LowPriorityAction = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*LowPriorityAction, EAIRequestPriority::Logic);
TickComponent();
AITEST_TRUE("There should be exactly one action on Logic stack now.", Component->GetActionStackSize(EAIRequestPriority::Logic) == 1);
AITEST_TRUE("There should be exactly one action on Reaction stack now.", Component->GetActionStackSize(EAIRequestPriority::Reaction) == 1);
AITEST_TRUE("The higher priority action should still be the active", Component->GetCurrentAction() == HighPriorityAction);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_HighPriorityKeepRunning, "System.AI.Pawn Actions.High priority action still running after pushing lower priority action")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_SamePriorityActionsPushing : public FAITest_SimpleActionsTest
{
virtual bool InstantTest() override
{
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
UWorld& World = GetWorld();
Component->PushAction(*UTestPawnAction_Log::CreateAction(World, Logger), EAIRequestPriority::Logic);
Component->PushAction(*UTestPawnAction_Log::CreateAction(World, Logger), EAIRequestPriority::Logic);
Component->PushAction(*UTestPawnAction_Log::CreateAction(World, Logger), EAIRequestPriority::Logic);
UPawnAction* LastAction = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*LastAction, EAIRequestPriority::Logic);
AITEST_TRUE("Action queue should be empty.", Component->GetActionEventsQueueSize() == 4);
TickComponent();
AITEST_TRUE("Last action pushed should the one active", Component->GetCurrentAction() == LastAction);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_SamePriorityActionsPushing, "System.AI.Pawn Actions.Pushing multiple actions of same priority")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_Aborting : public FAITest_SimpleActionsTest
{
virtual bool InstantTest() override
{
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Finished);
UWorld& World = GetWorld();
UTestPawnAction_Log* Action = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*Action, EAIRequestPriority::Logic);
TickComponent();
Component->AbortAction(*Action);
TickComponent();
AITEST_TRUE("There should be no actions on the stack.", Component->GetActionStackSize(EAIRequestPriority::Logic) == 0);
AITEST_NULL("There should be no current action", Component->GetCurrentAction());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_Aborting, "System.AI.Pawn Actions.Basic aborting mechanics")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_PushAndAbort : public FAITest_SimpleActionsTest
{
virtual bool InstantTest() override
{
UWorld& World = GetWorld();
UTestPawnAction_Log* Action = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*Action, EAIRequestPriority::Logic);
Component->AbortAction(*Action);
TickComponent();
AITEST_TRUE("There should be no actions on the stack.", Component->GetActionStackSize(EAIRequestPriority::Logic) == 0);
AITEST_NULL("There should be no current action", Component->GetCurrentAction());
AITEST_TRUE("No actuall work should have been done", Logger.LoggedValues.Num() == 0);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_PushAndAbort, "System.AI.Pawn Actions.Push and Abort same frame")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_AbortAfterPushingNewAction : public FAITest_SimpleActionsTest
{
virtual bool InstantTest() override
{
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Finished);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
UWorld& World = GetWorld();
UTestPawnAction_Log* FirstAction = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*FirstAction, EAIRequestPriority::Logic);
TickComponent();
UTestPawnAction_Log* SecondAction = UTestPawnAction_Log::CreateAction(World, Logger);
Component->PushAction(*SecondAction, EAIRequestPriority::Logic);
Component->AbortAction(*FirstAction);
TickComponent();
AITEST_TRUE("There should be exactly one action on stack.", Component->GetActionStackSize(EAIRequestPriority::Logic) == 1);
AITEST_TRUE("Last pushed action should be the active one", Component->GetCurrentAction() == SecondAction);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_AbortAfterPushingNewAction, "System.AI.Pawn Actions.Abort action after a newer action has been pushed")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_ActionPushingActions : public FAITest_SimpleActionsTest
{
static void CreateNewAction(UPawnActionsComponent& ActionsComponent, UTestPawnAction_CallFunction& Caller, ETestPawnActionMessage::Type Message)
{
if (Message == ETestPawnActionMessage::Started)
{
UTestPawnAction_Log* NextAction = UTestPawnAction_Log::CreateAction(*Caller.GetWorld(), *Caller.Logger);
ActionsComponent.PushAction(*NextAction, Caller.GetPriority());
}
}
virtual bool InstantTest() override
{
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Paused);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
UWorld& World = GetWorld();
UTestPawnAction_CallFunction* RootAction = UTestPawnAction_CallFunction::CreateAction(World, Logger, &FAITest_PawnActions_ActionPushingActions::CreateNewAction);
Component->PushAction(*RootAction, EAIRequestPriority::Logic);
TickComponent();
AITEST_TRUE("There should be exactly one action on stack.", Component->GetActionStackSize(EAIRequestPriority::Logic) == 1);
AITEST_TRUE("Root action action be the active one", Component->GetCurrentAction() == RootAction);
AITEST_TRUE("There should be exactly one action event pending", Component->GetActionEventsQueueSize() == 1);
TickComponent();
AITEST_TRUE("There should be exactly one action on stack.", Component->GetActionStackSize(EAIRequestPriority::Logic) == 2);
AITEST_TRUE("Root action action be the active one", Component->GetCurrentAction() != RootAction);
AITEST_TRUE("There should be exactly one action event pending", Component->GetActionEventsQueueSize() == 0);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_ActionPushingActions, "System.AI.Pawn Actions.Action pushing Actions")
// scenarios to test
// * Push, wait for Start and Abort instantly. Implement a special action that will finish in X tick with result Y
#undef LOCTEXT_NAMESPACE
@@ -1,196 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "AITypes.h"
#include "AITestsCommon.h"
#include "Actions/TestPawnAction_Log.h"
#include "Actions/PawnActionsComponent.h"
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_ResourceIDBasic : public FAITestBase
{
virtual bool InstantTest() override
{
AITEST_TRUE("There are always some resources as long as AIModule is present", FAIResources::GetResourcesCount() > 0);
const FAIResourceID& MovementID = FAIResources::GetResource(FAIResources::Movement);
AITEST_TRUE("Resource ID's indexes are broken!", FAIResources::Movement == MovementID);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_ResourceIDBasic, "System.AI.Resource ID.Basic operations")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_ResourceLock : public FAITestBase
{
virtual bool InstantTest() override
{
FAIResourceLock MockLock;
// basic locking
MockLock.SetLock(EAIRequestPriority::HardScript);
AITEST_TRUE("Resource should be locked", MockLock.IsLocked());
AITEST_TRUE("Resource should be locked with specified priority", MockLock.IsLockedBy(EAIRequestPriority::HardScript));
AITEST_FALSE("Resource should not be available for lower priorities", MockLock.IsAvailableFor(EAIRequestPriority::Logic));
AITEST_TRUE("Resource should be available for higher priorities", MockLock.IsAvailableFor(EAIRequestPriority::Reaction));
// clearing lock:
// try clearing with lower priority
MockLock.ClearLock(EAIRequestPriority::Logic);
AITEST_TRUE("Resource should be still locked", MockLock.IsLocked());
AITEST_FALSE("Resource should still not be available for lower priorities", MockLock.IsAvailableFor(EAIRequestPriority::Logic));
AITEST_TRUE("Resource should still be available for higher priorities", MockLock.IsAvailableFor(EAIRequestPriority::Reaction));
// releasing the actual lock
MockLock.ClearLock(EAIRequestPriority::HardScript);
AITEST_FALSE("Resource should be available now", MockLock.IsLocked());
// clearing all locks at one go
MockLock.SetLock(EAIRequestPriority::HardScript);
MockLock.SetLock(EAIRequestPriority::Logic);
MockLock.SetLock(EAIRequestPriority::Reaction);
bool bWasLocked = MockLock.IsLocked();
MockLock.ForceClearAllLocks();
AITEST_TRUE("Resource should no longer be locked", bWasLocked == true && MockLock.IsLocked() == false);
// merging
FAIResourceLock MockLock2;
MockLock.SetLock(EAIRequestPriority::HardScript);
MockLock2.SetLock(EAIRequestPriority::Logic);
// merge
MockLock2 += MockLock;
AITEST_TRUE("Resource should be locked on both priorities", MockLock2.IsLockedBy(EAIRequestPriority::Logic) && MockLock2.IsLockedBy(EAIRequestPriority::HardScript));
MockLock2.ClearLock(EAIRequestPriority::Logic);
AITEST_TRUE("At this point both locks should be identical", MockLock == MockLock2);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_ResourceLock, "System.AI.Resource ID.Resource locking")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_ResourceSet : public FAITestBase
{
virtual bool InstantTest() override
{
{
FAIResourcesSet ResourceSet;
AITEST_TRUE("Resource Set should be empty by default", ResourceSet.IsEmpty());
for (uint8 FlagIndex = 0; FlagIndex < FAIResourcesSet::MaxFlags; ++FlagIndex)
{
AITEST_FALSE("Resource Set should not contain any resources when empty", ResourceSet.ContainsResourceIndex(FlagIndex));
}
}
{
FAIResourcesSet ResourceSet(FAIResourcesSet::AllResources);
AITEST_FALSE("Resource Set should be empty by default", ResourceSet.IsEmpty());
for (uint8 FlagIndex = 0; FlagIndex < FAIResourcesSet::MaxFlags; ++FlagIndex)
{
AITEST_TRUE("Full Resource Set should contain every resource", ResourceSet.ContainsResourceIndex(FlagIndex) == true);
}
}
{
const FAIResourceID& MovementResource = FAIResources::GetResource(FAIResources::Movement);
const FAIResourceID& PerceptionResource = FAIResources::GetResource(FAIResources::Perception);
FAIResourcesSet ResourceSet;
ResourceSet.AddResource(PerceptionResource);
AITEST_TRUE("Resource Set should contain added resource", ResourceSet.ContainsResource(PerceptionResource));
AITEST_TRUE("Resource Set should contain added resource given by Index", ResourceSet.ContainsResourceIndex(PerceptionResource.Index));
for (uint8 FlagIndex = 0; FlagIndex < FAIResourcesSet::MaxFlags; ++FlagIndex)
{
if (FlagIndex != PerceptionResource.Index)
{
AITEST_FALSE("Resource Set should not contain any other resources", ResourceSet.ContainsResourceIndex(FlagIndex));
}
}
AITEST_FALSE("Resource Set should not be empty after adding a resource", ResourceSet.IsEmpty());
ResourceSet.AddResourceIndex(MovementResource.Index);
AITEST_TRUE("Resource Set should contain second added resource", ResourceSet.ContainsResource(MovementResource));
AITEST_TRUE("Resource Set should contain second added resource given by Index", ResourceSet.ContainsResourceIndex(MovementResource.Index));
ResourceSet.RemoveResource(MovementResource);
AITEST_FALSE("Resource Set should no longer contain second added resource", ResourceSet.ContainsResource(MovementResource));
AITEST_FALSE("Resource Set should still be not empty after removing one resource", ResourceSet.IsEmpty());
ResourceSet.RemoveResourceIndex(PerceptionResource.Index);
AITEST_TRUE("Resource Set should be empty after removing last resource", ResourceSet.IsEmpty() == true);
}
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_ResourceSet, "System.AI.Resource ID.Resource locking")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_PausingActionsOfSameResource : public FAITest_SimpleComponentBasedTest<UPawnActionsComponent>
{
virtual bool InstantTest() override
{
/*Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Paused);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);*/
UWorld& World = GetWorld();
UTestPawnAction_Log* MoveAction = UTestPawnAction_Log::CreateAction(World, Logger);
MoveAction->GetRequiredResourcesSet() = FAIResourcesSet(FAIResources::Movement);
Component->PushAction(*MoveAction, EAIRequestPriority::Logic);
Component->TickComponent(FAITestHelpers::TickInterval, ELevelTick::LEVELTICK_All, nullptr);
UTestPawnAction_Log* AnotherMoveAction = UTestPawnAction_Log::CreateAction(World, Logger);
AnotherMoveAction->GetRequiredResourcesSet() = FAIResourcesSet(FAIResources::Movement);
Component->PushAction(*AnotherMoveAction, EAIRequestPriority::Logic);
Component->TickComponent(FAITestHelpers::TickInterval, ELevelTick::LEVELTICK_All, nullptr);
AITEST_TRUE("First MoveAction should get paused", MoveAction->IsPaused() == true);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_PausingActionsOfSameResource, "System.AI.Pawn Actions.Pausing actions of same resource")
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
struct FAITest_PawnActions_NotPausingActionsOfDifferentResources : public FAITest_SimpleComponentBasedTest<UPawnActionsComponent>
{
virtual bool InstantTest() override
{
/*Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Paused);
Logger.ExpectedValues.Add(ETestPawnActionMessage::Started);*/
UWorld& World = GetWorld();
UTestPawnAction_Log* MoveAction = UTestPawnAction_Log::CreateAction(World, Logger);
MoveAction->SetRequiredResourcesSet(FAIResourcesSet(FAIResources::Movement));
Component->PushAction(*MoveAction, EAIRequestPriority::Logic);
Component->TickComponent(FAITestHelpers::TickInterval, ELevelTick::LEVELTICK_All, nullptr);
UTestPawnAction_Log* PerceptionAction = UTestPawnAction_Log::CreateAction(World, Logger);
PerceptionAction->SetRequiredResourcesSet(FAIResourcesSet(FAIResources::Perception));
Component->PushAction(*PerceptionAction, EAIRequestPriority::Logic);
Component->TickComponent(FAITestHelpers::TickInterval, ELevelTick::LEVELTICK_All, nullptr);
// @todo test temporarily disabled
//AITEST_TRUE("First MoveAction should get paused", MoveAction->IsPaused() == false && PerceptionAction->IsPaused() == false);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FAITest_PawnActions_NotPausingActionsOfDifferentResources, "System.AI.Pawn Actions.Not pausing actions of different resources")
@@ -1,39 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
/**
* The public interface to this module
*/
class IAITestSuite : public IModuleInterface
{
public:
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static inline IAITestSuite& Get()
{
return FModuleManager::LoadModuleChecked< IAITestSuite >("AITestSuite");
}
/**
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
*
* @return True if the module is loaded and ready to use
*/
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded( "AITestSuite" );
}
};
@@ -1,262 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/UObjectGlobals.h"
#include "Misc/AutomationTest.h"
#include "TestLogger.h"
#include "Engine/EngineBaseTypes.h"
DECLARE_LOG_CATEGORY_EXTERN(LogAITestSuite, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogBehaviorTreeTest, Log, All);
DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FAITestCommand_WaitSeconds, float, Duration);
class FAITestCommand_WaitOneTick : public IAutomationLatentCommand
{
public:
FAITestCommand_WaitOneTick()
: bAlreadyRun(false)
{}
virtual bool Update() override;
private:
bool bAlreadyRun;
};
namespace FAITestHelpers
{
UWorld* GetWorld();
static const float TickInterval = 1.f / 30;
void UpdateFrameCounter();
uint64 FramesCounter();
}
struct AITESTSUITE_API FAITestBase
{
private:
// internals
TArray<UObject*> SpawnedObjects;
uint32 bTearedDown : 1;
protected:
FAutomationTestBase* TestRunner;
FAITestBase() : bTearedDown(false), TestRunner(nullptr)
{}
template<typename ClassToSpawn>
ClassToSpawn* NewAutoDestroyObject()
{
ClassToSpawn* ObjectInstance = NewObject<ClassToSpawn>();
ObjectInstance->AddToRoot();
SpawnedObjects.Add(ObjectInstance);
return ObjectInstance;
}
void AddAutoDestroyObject(UObject& ObjectRef);
virtual UWorld& GetWorld() const;
FAutomationTestBase& GetTestRunner() const { check(TestRunner); return *TestRunner; }
public:
virtual void SetTestRunner(FAutomationTestBase& AutomationTestInstance) { TestRunner = &AutomationTestInstance; }
// interface
virtual ~FAITestBase();
/** @return true if setup was completed successfully, false otherwise (which will result in failing the test instance). */
virtual bool SetUp() { return true; }
/** @return true to indicate that the test is done. */
virtual bool Update() { return false; }
/** @return false to indicate an issue with test execution. Will signal to automation framework this test instance failed. */
virtual bool InstantTest() { return false;}
// it's essential that overriding functions call the super-implementation. Otherwise the check in ~FAITestBase will fail.
virtual void TearDown();
};
DEFINE_EXPORTED_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(AITESTSUITE_API, FAITestCommand_SetUpTest, FAITestBase*, AITest);
DEFINE_EXPORTED_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(AITESTSUITE_API, FAITestCommand_PerformTest, FAITestBase*, AITest);
DEFINE_EXPORTED_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(AITESTSUITE_API, FAITestCommand_TearDownTest, FAITestBase*, AITest);
// @note that TestClass needs to derive from FAITestBase
#define IMPLEMENT_AI_LATENT_TEST(TestClass, PrettyName) \
IMPLEMENT_SIMPLE_AUTOMATION_TEST(TestClass##_Runner, PrettyName, (EAutomationTestFlags::ClientContext | EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)) \
bool TestClass##_Runner::RunTest(const FString& Parameters) \
{ \
/* spawn test instance. Setup should be done in test's constructor */ \
TestClass* TestInstance = new TestClass(); \
TestInstance->SetTestRunner(*this); \
/* set up */ \
ADD_LATENT_AUTOMATION_COMMAND(FAITestCommand_SetUpTest(TestInstance)); \
/* run latent command to update */ \
ADD_LATENT_AUTOMATION_COMMAND(FAITestCommand_PerformTest(TestInstance)); \
/* run latent command to tear down */ \
ADD_LATENT_AUTOMATION_COMMAND(FAITestCommand_TearDownTest(TestInstance)); \
return true; \
}
#define IMPLEMENT_AI_INSTANT_TEST(TestClass, PrettyName) \
IMPLEMENT_SIMPLE_AUTOMATION_TEST(TestClass##Runner, PrettyName, (EAutomationTestFlags::ClientContext | EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)) \
bool TestClass##Runner::RunTest(const FString& Parameters) \
{ \
bool bSuccess = false; \
/* spawn test instance. */ \
TestClass* TestInstance = new TestClass(); \
TestInstance->SetTestRunner(*this); \
/* set up */ \
if (TestInstance->SetUp()) \
{ \
/* call the instant-test code */ \
bSuccess = TestInstance->InstantTest(); \
/* tear down */ \
TestInstance->TearDown(); \
}\
delete TestInstance; \
return bSuccess; \
}
/**
* This macro allows one to implement a whole set of simple tests that share common setups. To use it first implement
* a struct that builds the said common setup. Like so:
*
* struct FMyCommonSetup : public FAITestBase
* {
* virtual bool SetUp() override
* {
* // your test common setup build code here
*
* // return false if setup fails and the test needs to be aborted
* return true;
* }
* };
*
* Once that's done you can implement a specific test using this setup class like so:
*
* IMPLEMENT_INSTANT_TEST_WITH_FIXTURE(FMyCommonSetup, "System.Engine.AI.MyTestGroup", ThisSpecificTestName)
* {
* // your test code here
*
* // return false to indicate the whole test instance failed for some reason
* return true;
* }
*/
#define IMPLEMENT_INSTANT_TEST_WITH_FIXTURE(Fixture, PrettyGroupNameString, TestExperiment) \
struct Fixture##_##TestExperiment : public Fixture \
{ \
virtual bool InstantTest() override; \
}; \
IMPLEMENT_AI_INSTANT_TEST(Fixture##_##TestExperiment, PrettyGroupNameString "." # TestExperiment) \
bool Fixture##_##TestExperiment::InstantTest()
//----------------------------------------------------------------------//
// Specific test types
//----------------------------------------------------------------------//
template<class TComponent>
struct FAITest_SimpleComponentBasedTest : public FAITestBase
{
FTestLogger<int32> Logger;
TComponent* Component;
FAITest_SimpleComponentBasedTest()
{
Component = NewAutoDestroyObject<TComponent>();
}
virtual void SetTestRunner(FAutomationTestBase& AutomationTestInstance) override
{
FAITestBase::SetTestRunner(AutomationTestInstance);
Logger.TestRunner = TestRunner;
}
virtual ~FAITest_SimpleComponentBasedTest()
{
GetTestRunner().TestTrue(TEXT("Not all expected values has been logged"), Logger.ExpectedValues.Num() == 0 || Logger.ExpectedValues.Num() == Logger.LoggedValues.Num());
}
virtual bool SetUp() override
{
UWorld* World = FAITestHelpers::GetWorld();
Component->RegisterComponentWithWorld(World);
return World != nullptr;
}
void TickComponent()
{
Component->TickComponent(FAITestHelpers::TickInterval, ELevelTick::LEVELTICK_All, nullptr);
}
};
//----------------------------------------------------------------------//
// state testing macros, valid in FTestAIBase (and subclasses') methods
// Using these macros makes sure the test function fails if the assertion
// fails making sure the rest of the test relying on given condition being
// true doesn't crash
//----------------------------------------------------------------------//
#define AITEST_TRUE(What, Value)\
if (!GetTestRunner().TestTrue(What, Value))\
{\
return false;\
}
#define AITEST_FALSE(What, Value)\
if (!GetTestRunner().TestFalse(What, Value))\
{\
return false;\
}
#define AITEST_NULL(What, Pointer)\
if (!GetTestRunner().TestNull(What, Pointer))\
{\
return false;\
}
#define AITEST_NOT_NULL(What, Pointer)\
if (!GetTestRunner().TestNotNull(What, Pointer))\
{\
return false;\
}
namespace FTestHelpers
{
template<typename T1, typename T2>
inline bool TestEqual(const FString& Description, T1 Expression, T2 Expected, FAutomationTestBase& This)
{
This.TestEqual(*Description, Expression, Expected);
return Expression == Expected;
}
template<typename T1, typename T2>
inline bool TestEqual(const FString& Description, T1* Expression, T2* Expected, FAutomationTestBase& This)
{
This.TestEqual(*Description, reinterpret_cast<uint64>(Expression), reinterpret_cast<uint64>(Expected));
return Expression == Expected;
}
template<typename T1, typename T2>
inline bool TestNotEqual(const FString& Description, T1 Expression, T2 Expected, FAutomationTestBase& This)
{
This.TestNotEqual(*Description, Expression, Expected);
return Expression != Expected;
}
template<typename T1, typename T2>
inline bool TestNotEqual(const FString& Description, T1* Expression, T2* Expected, FAutomationTestBase& This)
{
This.TestNotEqual(*Description, reinterpret_cast<uint64>(Expression), reinterpret_cast<uint64>(Expected));
return Expression != Expected;
}
}
#define AITEST_EQUAL(What, Actual, Expected)\
if (!FTestHelpers::TestEqual(What, Actual, Expected, GetTestRunner()))\
{\
return false;\
}
#define AITEST_NOT_EQUAL(What, Actual, Expected)\
if (!FTestHelpers::TestNotEqual(What, Actual, Expected, GetTestRunner()))\
{\
return false;\
}
@@ -1,315 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/UnrealType.h"
#include "BehaviorTree/BehaviorTreeTypes.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Bool.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Int.h"
#include "BehaviorTree/Composites/BTComposite_Selector.h"
#include "BehaviorTree/Composites/BTComposite_Sequence.h"
#include "BehaviorTree/Composites/BTComposite_SimpleParallel.h"
#include "BehaviorTree/Decorators/BTDecorator_Loop.h"
#include "BehaviorTree/Tasks/BTTask_RunBehavior.h"
#include "BehaviorTree/TestBTTask_Log.h"
#include "BehaviorTree/TestBTTask_SetFlag.h"
#include "BehaviorTree/TestBTTask_SetValue.h"
#include "BehaviorTree/TestBTTask_LatentWithFlags.h"
#include "BehaviorTree/TestBTDecorator_Blackboard.h"
#include "BehaviorTree/TestBTDecorator_DelayedAbort.h"
#include "BehaviorTree/TestBTService_Log.h"
struct FBTBuilder
{
static UBehaviorTree& CreateBehaviorTree()
{
UBlackboardData* BB = NewObject<UBlackboardData>();
FBlackboardEntry KeyData;
KeyData.EntryName = TEXT("Bool1");
KeyData.KeyType = NewObject<UBlackboardKeyType_Bool>();
BB->Keys.Add(KeyData);
KeyData.EntryName = TEXT("Bool2");
KeyData.KeyType = NewObject<UBlackboardKeyType_Bool>();
BB->Keys.Add(KeyData);
KeyData.EntryName = TEXT("Bool3");
KeyData.KeyType = NewObject<UBlackboardKeyType_Bool>();
BB->Keys.Add(KeyData);
KeyData.EntryName = TEXT("Bool4");
KeyData.KeyType = NewObject<UBlackboardKeyType_Bool>();
BB->Keys.Add(KeyData);
KeyData.EntryName = TEXT("Int");
KeyData.KeyType = NewObject<UBlackboardKeyType_Int>();
BB->Keys.Add(KeyData);
BB->UpdateParentKeys();
UBehaviorTree* TreeOb = NewObject<UBehaviorTree>();
TreeOb->BlackboardAsset = BB;
return *TreeOb;
}
static UBehaviorTree& CreateBehaviorTree(UBehaviorTree& ParentTree)
{
UBehaviorTree* TreeOb = NewObject<UBehaviorTree>();
TreeOb->BlackboardAsset = ParentTree.BlackboardAsset;
return *TreeOb;
}
static UBTComposite_Selector& AddSelector(UBehaviorTree& TreeOb)
{
UBTComposite_Selector* NodeOb = NewObject<UBTComposite_Selector>(&TreeOb);
NodeOb->InitializeFromAsset(TreeOb);
TreeOb.RootNode = NodeOb;
return *NodeOb;
}
static UBTComposite_Selector& AddSelector(UBTCompositeNode& ParentNode)
{
UBTComposite_Selector* NodeOb = NewObject<UBTComposite_Selector>(ParentNode.GetTreeAsset());
NodeOb->InitializeFromAsset(*ParentNode.GetTreeAsset());
const int32 ChildIdx = ParentNode.Children.AddZeroed(1);
ParentNode.Children[ChildIdx].ChildComposite = NodeOb;
return *NodeOb;
}
static UBTComposite_Sequence& AddSequence(UBehaviorTree& TreeOb)
{
UBTComposite_Sequence* NodeOb = NewObject<UBTComposite_Sequence>(&TreeOb);
NodeOb->InitializeFromAsset(TreeOb);
TreeOb.RootNode = NodeOb;
return *NodeOb;
}
static UBTComposite_Sequence& AddSequence(UBTCompositeNode& ParentNode)
{
UBTComposite_Sequence* NodeOb = NewObject<UBTComposite_Sequence>(ParentNode.GetTreeAsset());
NodeOb->InitializeFromAsset(*ParentNode.GetTreeAsset());
const int32 ChildIdx = ParentNode.Children.AddZeroed(1);
ParentNode.Children[ChildIdx].ChildComposite = NodeOb;
return *NodeOb;
}
static UBTComposite_SimpleParallel& AddParallel(UBehaviorTree& TreeOb, EBTParallelMode::Type Mode)
{
UBTComposite_SimpleParallel* NodeOb = NewObject<UBTComposite_SimpleParallel>(&TreeOb);
NodeOb->FinishMode = Mode;
NodeOb->InitializeFromAsset(TreeOb);
TreeOb.RootNode = NodeOb;
return *NodeOb;
}
static UBTComposite_SimpleParallel& AddParallel(UBTCompositeNode& ParentNode, EBTParallelMode::Type Mode)
{
UBTComposite_SimpleParallel* NodeOb = NewObject<UBTComposite_SimpleParallel>(ParentNode.GetTreeAsset());
NodeOb->FinishMode = Mode;
NodeOb->InitializeFromAsset(*ParentNode.GetTreeAsset());
const int32 ChildIdx = ParentNode.Children.AddZeroed(1);
ParentNode.Children[ChildIdx].ChildComposite = NodeOb;
return *NodeOb;
}
static void AddTask(UBTCompositeNode& ParentNode, int32 LogIndex, EBTNodeResult::Type NodeResult, int32 ExecutionTicks = 0)
{
UTestBTTask_Log* TaskNode = NewObject<UTestBTTask_Log>(ParentNode.GetTreeAsset());
TaskNode->LogIndex = LogIndex;
TaskNode->LogResult = NodeResult;
TaskNode->ExecutionTicks = ExecutionTicks;
const int32 ChildIdx = ParentNode.Children.AddZeroed(1);
ParentNode.Children[ChildIdx].ChildTask = TaskNode;
}
static void AddTaskLogFinish(UBTCompositeNode& ParentNode, int32 LogIndex, int32 FinishIndex, EBTNodeResult::Type NodeResult, int32 ExecutionTicks = 0)
{
UTestBTTask_Log* TaskNode = NewObject<UTestBTTask_Log>(ParentNode.GetTreeAsset());
TaskNode->LogIndex = LogIndex;
TaskNode->LogFinished = FinishIndex;
TaskNode->LogResult = NodeResult;
TaskNode->ExecutionTicks = ExecutionTicks;
const int32 ChildIdx = ParentNode.Children.AddZeroed(1);
ParentNode.Children[ChildIdx].ChildTask = TaskNode;
}
static void AddTaskFlagChange(UBTCompositeNode& ParentNode, bool bValue, EBTNodeResult::Type NodeResult, FName BoolKeyName = TEXT("Bool1"))
{
UTestBTTask_SetFlag* TaskNode = NewObject<UTestBTTask_SetFlag>(ParentNode.GetTreeAsset());
TaskNode->bValue = bValue;
TaskNode->TaskResult = NodeResult;
TaskNode->KeyName = BoolKeyName;
const int32 ChildIdx = ParentNode.Children.AddZeroed(1);
ParentNode.Children[ChildIdx].ChildTask = TaskNode;
}
static void AddTaskValueChange(UBTCompositeNode& ParentNode, int32 Value, EBTNodeResult::Type NodeResult, FName IntKeyName = TEXT("Int"))
{
UTestBTTask_SetValue* TaskNode = NewObject<UTestBTTask_SetValue>(ParentNode.GetTreeAsset());
TaskNode->Value = Value;
TaskNode->TaskResult = NodeResult;
TaskNode->KeyName = IntKeyName;
const int32 ChildIdx = ParentNode.Children.AddZeroed(1);
ParentNode.Children[ChildIdx].ChildTask = TaskNode;
}
static void AddTaskSubtree(UBTCompositeNode& ParentNode, UBehaviorTree* TreeAsset)
{
UBTTask_RunBehavior* TaskNode = NewObject<UBTTask_RunBehavior>(ParentNode.GetTreeAsset());
FObjectProperty* SubtreeProp = FindFProperty<FObjectProperty>(UBTTask_RunBehavior::StaticClass(), TEXT("BehaviorAsset"));
uint8* SubtreePropData = SubtreeProp->ContainerPtrToValuePtr<uint8>(TaskNode);
SubtreeProp->SetObjectPropertyValue(SubtreePropData, TreeAsset);
const int32 ChildIdx = ParentNode.Children.AddZeroed(1);
ParentNode.Children[ChildIdx].ChildTask = TaskNode;
}
static void AddTaskLatentFlags(UBTCompositeNode& ParentNode, EBTNodeResult::Type NodeResult,
int32 ExecuteHalfTicks, FName ExecuteKeyName, int32 ExecuteLogStart, int32 ExecuteLogFinish,
int32 AbortHalfTicks = 0, FName AbortKeyName = NAME_None, int32 AbortLogStart = 0, int32 AbortLogFinish = 0)
{
UTestBTTask_LatentWithFlags* TaskNode = NewObject<UTestBTTask_LatentWithFlags>(ParentNode.GetTreeAsset());
TaskNode->ExecuteTicks = ExecuteHalfTicks;
TaskNode->KeyNameExecute = ExecuteKeyName;
TaskNode->LogIndexExecuteStart = ExecuteLogStart;
TaskNode->LogIndexExecuteFinish = ExecuteLogFinish;
TaskNode->AbortTicks = AbortHalfTicks;
TaskNode->KeyNameAbort = AbortKeyName;
TaskNode->LogIndexAbortStart = AbortLogStart;
TaskNode->LogIndexAbortFinish = AbortLogFinish;
const int32 ChildIdx = ParentNode.Children.AddZeroed(1);
ParentNode.Children[ChildIdx].ChildTask = TaskNode;
}
template<class T>
static T& WithDecorator(UBTCompositeNode& ParentNode, UClass* DecoratorClass = T::StaticClass())
{
T* DecoratorOb = NewObject<T>(ParentNode.GetTreeAsset());
ParentNode.Children.Last().Decorators.Add(DecoratorOb);
return *DecoratorOb;
}
static void WithDecoratorBlackboard(UBTCompositeNode& ParentNode, EBasicKeyOperation::Type Condition,
EBTFlowAbortMode::Type Observer, FName BoolKeyName = TEXT("Bool1"),
int32 LogIndexBecomeRelevant = -1, int32 LogIndexCeaseRelevant = -1, int32 LogIndexCalculate = -1)
{
UTestBTDecorator_Blackboard& BBDecorator = WithDecorator<UTestBTDecorator_Blackboard>(ParentNode);
BBDecorator.LogIndexBecomeRelevant= LogIndexBecomeRelevant;
BBDecorator.LogIndexCeaseRelevant= LogIndexCeaseRelevant;
BBDecorator.LogIndexCalculate = LogIndexCalculate;
FByteProperty* ConditionProp = FindFProperty<FByteProperty>(UBTDecorator_Blackboard::StaticClass(), TEXT("OperationType"));
uint8* ConditionPropData = ConditionProp->ContainerPtrToValuePtr<uint8>(&BBDecorator);
ConditionProp->SetIntPropertyValue(ConditionPropData, (uint64)Condition);
FByteProperty* ObserverProp = FindFProperty<FByteProperty>(UBTDecorator_Blackboard::StaticClass(), TEXT("FlowAbortMode"));
uint8* ObserverPropData = ObserverProp->ContainerPtrToValuePtr<uint8>(&BBDecorator);
ObserverProp->SetIntPropertyValue(ObserverPropData, (uint64)Observer);
FStructProperty* KeyProp = FindFProperty<FStructProperty>(UBTDecorator_Blackboard::StaticClass(), TEXT("BlackboardKey"));
FBlackboardKeySelector* KeyPropData = KeyProp->ContainerPtrToValuePtr<FBlackboardKeySelector>(&BBDecorator);
KeyPropData->SelectedKeyName = BoolKeyName;
}
static void WithDecoratorBlackboard(UBTCompositeNode& ParentNode, EArithmeticKeyOperation::Type Condition, int32 Value,
EBTFlowAbortMode::Type Observer, EBTBlackboardRestart::Type NotifyMode, FName IntKeyName = TEXT("Int"),
int32 LogIndexBecomeRelevant = -1, int32 LogIndexCeaseRelevant = -1, int32 LogIndexCalculate = -1)
{
UTestBTDecorator_Blackboard& BBDecorator = WithDecorator<UTestBTDecorator_Blackboard>(ParentNode);
BBDecorator.LogIndexBecomeRelevant = LogIndexBecomeRelevant;
BBDecorator.LogIndexCeaseRelevant = LogIndexCeaseRelevant;
BBDecorator.LogIndexCalculate = LogIndexCalculate;
FByteProperty* ConditionProp = FindFProperty<FByteProperty>(UBTDecorator_Blackboard::StaticClass(), TEXT("OperationType"));
uint8* ConditionPropData = ConditionProp->ContainerPtrToValuePtr<uint8>(&BBDecorator);
ConditionProp->SetIntPropertyValue(ConditionPropData, (uint64)Condition);
FByteProperty* ObserverProp = FindFProperty<FByteProperty>(UBTDecorator_Blackboard::StaticClass(), TEXT("FlowAbortMode"));
uint8* ObserverPropData = ObserverProp->ContainerPtrToValuePtr<uint8>(&BBDecorator);
ObserverProp->SetIntPropertyValue(ObserverPropData, (uint64)Observer);
FByteProperty* NotifyModeProp = FindFProperty<FByteProperty>(UBTDecorator_Blackboard::StaticClass(), TEXT("NotifyObserver"));
uint8* NotifyModePropData = NotifyModeProp->ContainerPtrToValuePtr<uint8>(&BBDecorator);
NotifyModeProp->SetIntPropertyValue(NotifyModePropData, (uint64)NotifyMode);
FIntProperty* ConditionValueProp = FindFProperty<FIntProperty>(UBTDecorator_Blackboard::StaticClass(), TEXT("IntValue"));
uint8* ConditionValuePropData = ConditionValueProp->ContainerPtrToValuePtr<uint8>(&BBDecorator);
ConditionValueProp->SetIntPropertyValue(ConditionValuePropData, (uint64)Value);
FStructProperty* KeyProp = FindFProperty<FStructProperty>(UBTDecorator_Blackboard::StaticClass(), TEXT("BlackboardKey"));
FBlackboardKeySelector* KeyPropData = KeyProp->ContainerPtrToValuePtr<FBlackboardKeySelector>(&BBDecorator);
KeyPropData->SelectedKeyName = IntKeyName;
}
static void WithDecoratorDelayedAbort(UBTCompositeNode& ParentNode, int32 NumTicks, bool bAbortOnlyOnce = true)
{
UTestBTDecorator_DelayedAbort& AbortDecorator = WithDecorator<UTestBTDecorator_DelayedAbort>(ParentNode);
AbortDecorator.DelayTicks = NumTicks;
AbortDecorator.bOnlyOnce = bAbortOnlyOnce;
}
static void WithDecoratorLoop(UBTCompositeNode& ParentNode, int32 NumLoops = 2)
{
UBTDecorator_Loop& LoopDecorator = WithDecorator<UBTDecorator_Loop>(ParentNode);
LoopDecorator.NumLoops = NumLoops;
}
template<class T>
static T& WithService(UBTCompositeNode& ParentNode, UClass* ServiceClass = T::StaticClass())
{
T* ServiceOb = NewObject<T>(ParentNode.GetTreeAsset());
ParentNode.Services.Add(ServiceOb);
return *ServiceOb;
}
static void WithServiceLog(UBTCompositeNode& ParentNode, int32 ActivationIndex, int32 DeactivationIndex, int32 TickIndex = INDEX_NONE, FName BoolKeyName = NAME_None, bool bCallTickOnSearchStart = false)
{
UTestBTService_Log& LogService = WithService<UTestBTService_Log>(ParentNode);
LogService.LogActivation = ActivationIndex;
LogService.LogDeactivation = DeactivationIndex;
LogService.LogTick = TickIndex;
LogService.SetFlagOnTick(BoolKeyName, bCallTickOnSearchStart);
}
template<class T>
static T& WithTaskService(UBTCompositeNode& ParentNode, UClass* ServiceClass = T::StaticClass())
{
UBTTaskNode* TaskNode = ParentNode.Children.Last().ChildTask;
check(TaskNode);
T* ServiceOb = NewObject<T>(ParentNode.GetTreeAsset());
TaskNode->Services.Add(ServiceOb);
return *ServiceOb;
}
static void WithTaskServiceLog(UBTCompositeNode& ParentNode, int32 ActivationIndex, int32 DeactivationIndex, int32 TickIndex = INDEX_NONE, FName BoolKeyName = NAME_None, bool bCallTickOnSearchStart = false)
{
UTestBTService_Log& LogService = WithTaskService<UTestBTService_Log>(ParentNode);
LogService.LogActivation = ActivationIndex;
LogService.LogDeactivation = DeactivationIndex;
LogService.LogTick = TickIndex;
LogService.SetFlagOnTick(BoolKeyName, bCallTickOnSearchStart);
}
};
@@ -1,41 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Misc/AutomationTest.h"
template<typename ValueType>
struct FTestLogger
{
TArray<ValueType> ExpectedValues;
TArray<ValueType> LoggedValues;
FAutomationTestBase* TestRunner;
FTestLogger(FAutomationTestBase* InTestRunner = nullptr) : TestRunner(InTestRunner)
{
}
~FTestLogger()
{
TestRunner->TestTrue("Not all values expected values have been logged!", ExpectedValues.Num() == 0 || LoggedValues.Num() == ExpectedValues.Num());
}
void Log(const ValueType& Value)
{
LoggedValues.Add(Value);
if (ExpectedValues.Num() > 0 && TestRunner != nullptr)
{
if (LoggedValues.Num() <= ExpectedValues.Num())
{
TestRunner->TestEqual("Logged value different then expected!", LoggedValues.Top(), ExpectedValues[LoggedValues.Num() - 1]);
}
else
{
TestRunner->TestTrue("Logged more values than expected!", false);
}
}
}
};
@@ -1,33 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class AllDesktopTargetPlatform : ModuleRules
{
public AllDesktopTargetPlatform(ReadOnlyTargetRules Target) : base(Target)
{
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"TargetPlatform",
"DesktopPlatform",
"LaunchDaemonMessages",
"Projects"
}
);
PrivateIncludePathModuleNames.AddRange(
new string[] {
"Messaging",
"TargetDeviceServices",
}
);
if (Target.bCompileAgainstEngine)
{
PrivateDependencyModuleNames.Add("Engine");
}
}
}
@@ -1,104 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AllDesktopTargetPlatform.cpp: Implements the FDesktopTargetPlatform class.
=============================================================================*/
#include "AllDesktopTargetPlatform.h"
#if WITH_ENGINE
#include "Sound/SoundWave.h"
#endif
/* FAllDesktopTargetPlatform structors
*****************************************************************************/
FAllDesktopTargetPlatform::FAllDesktopTargetPlatform()
{
#if WITH_ENGINE
// use non-platform specific settings
FConfigCacheIni::LoadLocalIniFile(EngineSettings, TEXT("Engine"), true, NULL);
StaticMeshLODSettings.Initialize(EngineSettings);
#endif // #if WITH_ENGINE
}
FAllDesktopTargetPlatform::~FAllDesktopTargetPlatform()
{
}
/* ITargetPlatform interface
*****************************************************************************/
#if WITH_ENGINE
void FAllDesktopTargetPlatform::GetAllPossibleShaderFormats( TArray<FName>& OutFormats ) const
{
static FName NAME_PCD3D_SM5(TEXT("PCD3D_SM5"));
#if PLATFORM_WINDOWS
// right now, only windows can properly compile D3D shaders
OutFormats.AddUnique(NAME_PCD3D_SM5);
#endif
}
void FAllDesktopTargetPlatform::GetAllTargetedShaderFormats( TArray<FName>& OutFormats ) const
{
GetAllPossibleShaderFormats(OutFormats);
}
void FAllDesktopTargetPlatform::GetTextureFormats( const UTexture* Texture, TArray< TArray<FName> >& OutFormats) const
{
// just use the standard texture format name for this texture (without DX11 texture support)
GetDefaultTextureFormatNamePerLayer(OutFormats.AddDefaulted_GetRef(), this, Texture, EngineSettings, false);
}
void FAllDesktopTargetPlatform::GetAllTextureFormats(TArray<FName>& OutFormats) const
{
GetAllDefaultTextureFormats(this, OutFormats, false);
}
FName FAllDesktopTargetPlatform::GetWaveFormat( const class USoundWave* Wave ) const
{
static FName NAME_OGG(TEXT("OGG"));
static FName NAME_OPUS(TEXT("OPUS"));
static FName NAME_ADPCM(TEXT("ADPCM"));
// Seekable streams need to pick a codec which allows fixed-sized frames so we can compute stream chunk index to load
if (Wave->IsSeekableStreaming())
{
return NAME_ADPCM;
}
// there is no one platform to check for Streaming status here
else if (Wave->IsStreaming(TEXT("Windows")))
{
#if !USE_VORBIS_FOR_STREAMING
return NAME_OPUS;
#endif
}
return NAME_OGG;
}
void FAllDesktopTargetPlatform::GetAllWaveFormats(TArray<FName>& OutFormats) const
{
static FName NAME_ADPCM(TEXT("ADPCM"));
static FName NAME_OGG(TEXT("OGG"));
static FName NAME_OPUS(TEXT("OPUS"));
OutFormats.Add(NAME_ADPCM);
OutFormats.Add(NAME_OGG);
OutFormats.Add(NAME_OPUS);
}
#endif // WITH_ENGINE
@@ -1,187 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AllDesktopTargetPlatform.h: Declares the FDesktopTargetPlatform class.
=============================================================================*/
#pragma once
#include "CoreMinimal.h"
#include "Misc/ConfigCacheIni.h"
#include "Interfaces/ITargetPlatform.h"
#include "Common/TargetPlatformBase.h"
#if WITH_ENGINE
#include "AudioCompressionSettings.h"
#include "StaticMeshResources.h"
#endif // WITH_ENGINE
class UTextureLODSettings;
class FAllDesktopPlatformProperties : public FGenericPlatformProperties
{
public:
static FORCEINLINE const char* PlatformName()
{
return "AllDesktop";
}
static FORCEINLINE const char* IniPlatformName()
{
// this uses generic, non-platform-specific .ini files
return "";
}
static FORCEINLINE const TCHAR* GetRuntimeSettingsClassName()
{
return TEXT("");
}
static FORCEINLINE bool HasEditorOnlyData()
{
return false;
}
static FORCEINLINE bool RequiresCookedData()
{
return true;
}
};
/**
* FDesktopTargetPlatform, abstraction for cooking iOS platforms
*/
class FAllDesktopTargetPlatform
: public TTargetPlatformBase<FAllDesktopPlatformProperties>
{
public:
/**
* Default constructor.
*/
FAllDesktopTargetPlatform();
/**
* Destructor.
*/
~FAllDesktopTargetPlatform();
public:
//~ Begin TTargetPlatformBase Interface
virtual bool IsServerOnly( ) const override
{
return false;
}
//~ End TTargetPlatformBase Interface
public:
//~ Begin ITargetPlatform Interface
virtual void EnableDeviceCheck(bool OnOff) override
{
}
virtual void GetAllDevices( TArray<ITargetDevicePtr>& OutDevices ) const override
{
}
virtual bool GenerateStreamingInstallManifest(const TMultiMap<FString, int32>& PakchunkMap, const TSet<int32>& PakchunkIndicesInUse) const override
{
return true;
}
virtual ITargetDevicePtr GetDefaultDevice( ) const override
{
return NULL;
}
virtual ITargetDevicePtr GetDevice( const FTargetDeviceId& DeviceId ) override
{
return NULL;
}
virtual bool IsRunningPlatform( ) const override
{
return false;
}
virtual bool SupportsFeature( ETargetPlatformFeatures Feature ) const override
{
return TTargetPlatformBase<FAllDesktopPlatformProperties>::SupportsFeature(Feature);
}
virtual bool IsSdkInstalled(bool bProjectHasCode, FString& OutTutorialPath) const override
{
return true;
}
virtual int32 CheckRequirements(bool bProjectHasCode, EBuildConfiguration Configuration, bool bRequiresAssetNativization, FString& OutTutorialPath, FString& OutDocumentationPath, FText& CustomizedLogMessage) const override
{
return ETargetPlatformReadyStatus::Ready;
}
#if WITH_ENGINE
virtual void GetAllPossibleShaderFormats( TArray<FName>& OutFormats ) const override;
virtual void GetAllTargetedShaderFormats( TArray<FName>& OutFormats ) const override;
virtual const class FStaticMeshLODSettings& GetStaticMeshLODSettings( ) const override
{
return StaticMeshLODSettings;
}
virtual void GetTextureFormats(const UTexture* Texture, TArray< TArray<FName> >& OutFormats) const override;
virtual void GetAllTextureFormats(TArray<FName>& OutFormats) const override;
virtual const UTextureLODSettings& GetTextureLODSettings() const override
{
return *TextureLODSettings;
}
virtual void RegisterTextureLODSettings(const UTextureLODSettings* InTextureLODSettings) override
{
TextureLODSettings = InTextureLODSettings;
}
virtual FName GetWaveFormat( const class USoundWave* Wave ) const override;
virtual void GetAllWaveFormats(TArray<FName>& OutFormats) const override;
#endif // WITH_ENGINE
DECLARE_DERIVED_EVENT(FAllDesktopTargetPlatform, ITargetPlatform::FOnTargetDeviceDiscovered, FOnTargetDeviceDiscovered);
virtual FOnTargetDeviceDiscovered& OnDeviceDiscovered( ) override
{
return DeviceDiscoveredEvent;
}
DECLARE_DERIVED_EVENT(FAllDesktopTargetPlatform, ITargetPlatform::FOnTargetDeviceLost, FOnTargetDeviceLost);
virtual FOnTargetDeviceLost& OnDeviceLost( ) override
{
return DeviceLostEvent;
}
private:
#if WITH_ENGINE
// Holds the Engine INI settings, for quick use.
FConfigFile EngineSettings;
// Holds the cache of the target LOD settings.
const UTextureLODSettings* TextureLODSettings;
// Holds the static mesh LOD settings.
FStaticMeshLODSettings StaticMeshLODSettings;
#endif // WITH_ENGINE
// Holds an event delegate that is executed when a new target device has been discovered.
FOnTargetDeviceDiscovered DeviceDiscoveredEvent;
// Holds an event delegate that is executed when a target device has been lost, i.e. disconnected or timed out.
FOnTargetDeviceLost DeviceLostEvent;
};
@@ -1,60 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/ITargetPlatformModule.h"
#include "AllDesktopTargetPlatform.h"
/**
* Holds the target platform singleton.
*/
static ITargetPlatform* Singleton = NULL;
/**
* Module for a generic target platform for desktop platforms
*/
class FAllDesktopTargetPlatformModule : public ITargetPlatformModule
{
public:
/**
* Destructor.
*/
FAllDesktopTargetPlatformModule()
{
Singleton = NULL;
}
public:
// Begin ITargetPlatformModule interface
virtual ITargetPlatform* GetTargetPlatform() override
{
if (Singleton == NULL && FAllDesktopTargetPlatform::IsUsable())
{
Singleton = new FAllDesktopTargetPlatform();
}
return Singleton;
}
// End ITargetPlatformModule interface
public:
// Begin IModuleInterface interface
virtual void StartupModule() override
{
}
virtual void ShutdownModule() override
{
}
// End IModuleInterface interface
};
IMPLEMENT_MODULE( FAllDesktopTargetPlatformModule, AllDesktopTargetPlatform);
@@ -1,47 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class AndroidDeviceDetection : ModuleRules
{
public AndroidDeviceDetection( ReadOnlyTargetRules Target ) : base(Target)
{
BinariesSubFolder = "Android";
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Core",
"Json",
"JsonUtilities",
"PIEPreviewDeviceSpecification"
}
);
PrivateIncludePathModuleNames.AddRange(
new string[]
{
"TcpMessaging",
}
);
PublicIncludePaths.AddRange(
new string[]
{
"Runtime/Core/Public/Android"
}
);
if (Target.bCompileAgainstEngine)
{
PrivateDependencyModuleNames.Add("Engine");
}
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
"TcpMessaging"
}
);
}
}
@@ -1,784 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AndroidDeviceDetectionModule.cpp: Implements the FAndroidDeviceDetectionModule class.
=============================================================================*/
#include "CoreTypes.h"
#include "HAL/UnrealMemory.h"
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "Containers/StringConv.h"
#include "Containers/Map.h"
#include "GenericPlatform/GenericPlatformStackWalk.h"
#include "HAL/PlatformProcess.h"
#include "Logging/LogMacros.h"
#include "HAL/FileManager.h"
#include "Misc/Parse.h"
#include "Misc/Paths.h"
#include "HAL/Runnable.h"
#include "HAL/RunnableThread.h"
#include "HAL/ThreadSafeCounter.h"
#include "Misc/ScopeLock.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/IAndroidDeviceDetection.h"
#include "Interfaces/IAndroidDeviceDetectionModule.h"
#include "ITcpMessagingModule.h"
#include "PIEPreviewDeviceSpecification.h"
#include "JsonObjectConverter.h"
#include "Dom/JsonObject.h"
#include "Misc/FileHelper.h"
#include "Misc/MessageDialog.h"
#define LOCTEXT_NAMESPACE "FAndroidDeviceDetectionModule"
DEFINE_LOG_CATEGORY_STATIC(AndroidDeviceDetectionLog, Log, All);
class FAndroidDeviceDetectionRunnable : public FRunnable
{
public:
FAndroidDeviceDetectionRunnable(TMap<FString,FAndroidDeviceInfo>& InDeviceMap, FCriticalSection* InDeviceMapLock, FCriticalSection* InADBPathCheckLock) :
StopTaskCounter(0),
DeviceMap(InDeviceMap),
DeviceMapLock(InDeviceMapLock),
ADBPathCheckLock(InADBPathCheckLock),
HasADBPath(false),
ForceCheck(false)
{
TcpMessagingModule = FModuleManager::LoadModulePtr<ITcpMessagingModule>("TcpMessaging");
}
public:
// FRunnable interface.
virtual bool Init(void)
{
return true;
}
virtual void Exit(void)
{
}
virtual void Stop(void)
{
StopTaskCounter.Increment();
}
virtual uint32 Run(void)
{
int LoopCount = 10;
while (StopTaskCounter.GetValue() == 0)
{
// query every 10 seconds
if (LoopCount++ >= 10 || ForceCheck)
{
// Make sure we have an ADB path before checking
FScopeLock PathLock(ADBPathCheckLock);
if (HasADBPath)
QueryConnectedDevices();
LoopCount = 0;
ForceCheck = false;
}
FPlatformProcess::Sleep(1.0f);
}
return 0;
}
void UpdateADBPath(FString &InADBPath, FString& InGetPropCommand, bool InbGetExtensionsViaSurfaceFlinger, bool InbForLumin)
{
ADBPath = InADBPath;
GetPropCommand = InGetPropCommand;
bGetExtensionsViaSurfaceFlinger = InbGetExtensionsViaSurfaceFlinger;
bForLumin = InbForLumin;
HasADBPath = !ADBPath.IsEmpty();
// Force a check next time we go around otherwise it can take over 10sec to find devices
ForceCheck = HasADBPath;
// If we have no path then clean the existing devices out
if (!HasADBPath && DeviceMap.Num() > 0)
{
DeviceMap.Reset();
}
}
private:
bool ExecuteAdbCommand( const FString& CommandLine, FString* OutStdOut, FString* OutStdErr ) const
{
// execute the command
int32 ReturnCode;
FString DefaultError;
// make sure there's a place for error output to go if the caller specified nullptr
if (!OutStdErr)
{
OutStdErr = &DefaultError;
}
if (FPaths::FileExists(*ADBPath))
{
FPlatformProcess::ExecProcess(*ADBPath, *CommandLine, &ReturnCode, OutStdOut, OutStdErr);
if (ReturnCode != 0)
{
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("The Android SDK command '%s' failed to run. Return code: %d, Error: %s\n"), *CommandLine, ReturnCode, **OutStdErr);
return false;
}
}
return true;
}
// searches for 'DPIString' and
int32 ExtractDPI(const FString& SurfaceFlingerOutput, const FString& DPIString)
{
int32 FoundDpi = INDEX_NONE;
int32 DpiIndex = SurfaceFlingerOutput.Find(DPIString);
if (DpiIndex != INDEX_NONE)
{
int32 StartIndex = INDEX_NONE;
for (int32 i = DpiIndex; i < SurfaceFlingerOutput.Len(); ++i)
{
// if we somehow hit a line break character something went wrong and no digits were found on this line
// we don't want to search the SurfaceFlinger feed so exit now
if (FChar::IsLinebreak(SurfaceFlingerOutput[i]))
{
break;
}
// search for the first digit aka the beginning of the DPI value
if (StartIndex == INDEX_NONE && FChar::IsDigit(SurfaceFlingerOutput[i]))
{
StartIndex = i;
}
// if we hit some non-numeric character extract the number and exit
else if (StartIndex != INDEX_NONE && !FChar::IsDigit(SurfaceFlingerOutput[i]))
{
FString str = SurfaceFlingerOutput.Mid(StartIndex, i - StartIndex);
FoundDpi = FCString::Atoi(*str);
break;
}
}
}
return FoundDpi;
}
// retrieve the string between 'InOutStartIndex' and the start position of the next 'Token' substring
// the white spaces of the resulting string are trimmed out at both ends
FString ExtractNextToken(int32& InOutStartIndex, const FString& SurfaceFlingerOutput, const FString& Token)
{
FString OutString;
int32 StartIndex = InOutStartIndex;
int32 EndIndex = SurfaceFlingerOutput.Find(Token, ESearchCase::IgnoreCase, ESearchDir::FromStart, StartIndex);
if (EndIndex != INDEX_NONE)
{
InOutStartIndex = EndIndex + 1;
// the index should point to the position before the token start
--EndIndex;
for (int32 i = StartIndex; i < EndIndex; ++i)
{
if (!FChar::IsWhitespace(SurfaceFlingerOutput[i]))
{
StartIndex = i;
break;
}
}
for (int32 i = EndIndex; i > StartIndex; --i)
{
if (!FChar::IsWhitespace(SurfaceFlingerOutput[i]))
{
EndIndex = i;
break;
}
}
OutString = SurfaceFlingerOutput.Mid(StartIndex, FMath::Max(0, EndIndex - StartIndex + 1));
}
return OutString;
}
void ExtractGPUInfo(FString& outGLVersion, FString& outGPUFamily, const FString& SurfaceFlingerOutput)
{
int32 FoundDpi = INDEX_NONE;
int32 LineIndex = SurfaceFlingerOutput.Find(TEXT("GLES:"));
if (LineIndex != INDEX_NONE)
{
int32 StartIndex = SurfaceFlingerOutput.Find(TEXT(":"), ESearchCase::IgnoreCase, ESearchDir::FromStart, LineIndex);
if (StartIndex != INDEX_NONE)
{
++StartIndex;
FString GPUVendorString = ExtractNextToken(StartIndex, SurfaceFlingerOutput, TEXT(","));
outGPUFamily = ExtractNextToken(StartIndex, SurfaceFlingerOutput, TEXT(","));
outGLVersion = ExtractNextToken(StartIndex, SurfaceFlingerOutput, TEXT("\n"));
}
}
}
void QueryConnectedDevices()
{
// grab the list of devices via adb
FString StdOut;
if (!ExecuteAdbCommand(TEXT("devices -l"), &StdOut, nullptr))
{
return;
}
// separate out each line
TArray<FString> DeviceStrings;
StdOut = StdOut.Replace(TEXT("\r"), TEXT("\n"));
StdOut.ParseIntoArray(DeviceStrings, TEXT("\n"), true);
// list of any existing port forwardings, filled in when we find a device we need to add.
TArray<FString> PortForwardings;
// a list containing all devices found this time, so we can remove anything not in this list
TArray<FString> CurrentlyConnectedDevices;
for (int32 StringIndex = 0; StringIndex < DeviceStrings.Num(); ++StringIndex)
{
const FString& DeviceString = DeviceStrings[StringIndex];
// skip over non-device lines
if (DeviceString.StartsWith("* ") || DeviceString.StartsWith("List "))
{
continue;
}
// grab the device serial number
int32 TabIndex;
// use either tab or space as separator
if (!DeviceString.FindChar(TCHAR('\t'), TabIndex))
{
if (!DeviceString.FindChar(TCHAR(' '), TabIndex))
{
continue;
}
}
FAndroidDeviceInfo NewDeviceInfo;
NewDeviceInfo.SerialNumber = DeviceString.Left(TabIndex);
const FString DeviceState = DeviceString.Mid(TabIndex + 1).TrimStart();
NewDeviceInfo.bAuthorizedDevice = DeviceState != TEXT("unauthorized");
if (bForLumin)
{
// 'mldb oobestaus' is deprecated. 'mldb ps' gives us similar functionality for checking device readiness to some extent.
const FString OobeCommand = FString::Printf(TEXT("-s %s ps"), *NewDeviceInfo.SerialNumber);
FString OobeStatus;
NewDeviceInfo.bAuthorizedDevice = ExecuteAdbCommand(*OobeCommand, &OobeStatus, nullptr);
if (DeviceMap.Contains(NewDeviceInfo.SerialNumber))
{
if (DeviceMap[NewDeviceInfo.SerialNumber].bAuthorizedDevice != NewDeviceInfo.bAuthorizedDevice)
{
// if this device is already in the connected list but authorization has changed, remove it.
// it will be added in the next query which will allow UI to refresh properly.
continue;
}
}
}
// add it to our list of currently connected devices
CurrentlyConnectedDevices.Add(NewDeviceInfo.SerialNumber);
// move on to next device if this one is already a known device that has either already been authorized or the authorization
// status has not changed
if (DeviceMap.Contains(NewDeviceInfo.SerialNumber) &&
(DeviceMap[NewDeviceInfo.SerialNumber].bAuthorizedDevice == NewDeviceInfo.bAuthorizedDevice))
{
continue;
}
if (!NewDeviceInfo.bAuthorizedDevice && !bForLumin)
{
//note: AndroidTargetDevice::GetName() does not fetch this value, do not rely on this
NewDeviceInfo.DeviceName = TEXT("Unauthorized - enable USB debugging");
}
else
{
// grab the Lumin/Android version
const FString AndroidVersionCommand = bForLumin ? FString::Printf(TEXT("%s ro.build.id"), *GetPropCommand) :
FString::Printf(TEXT("-s %s %s ro.build.version.release"), *NewDeviceInfo.SerialNumber, *GetPropCommand);
if (!ExecuteAdbCommand(*AndroidVersionCommand, &NewDeviceInfo.HumanAndroidVersion, nullptr))
{
continue;
}
NewDeviceInfo.HumanAndroidVersion = NewDeviceInfo.HumanAndroidVersion.Replace(TEXT("\r"), TEXT("")).Replace(TEXT("\n"), TEXT(""));
NewDeviceInfo.HumanAndroidVersion.TrimStartAndEndInline();
// grab the Android SDK version
const FString SDKVersionCommand = FString::Printf(TEXT("-s %s %s ro.build.version.sdk"), *NewDeviceInfo.SerialNumber, *GetPropCommand);
FString SDKVersionString;
if (!ExecuteAdbCommand(*SDKVersionCommand, &SDKVersionString, nullptr))
{
continue;
}
NewDeviceInfo.SDKVersion = FCString::Atoi(*SDKVersionString);
if (NewDeviceInfo.SDKVersion <= 0)
{
NewDeviceInfo.SDKVersion = INDEX_NONE;
}
if (bGetExtensionsViaSurfaceFlinger)
{
// get the GL extensions string (and a bunch of other stuff)
const FString ExtensionsCommand = FString::Printf(TEXT("-s %s shell dumpsys SurfaceFlinger"), *NewDeviceInfo.SerialNumber);
if (!ExecuteAdbCommand(*ExtensionsCommand, &NewDeviceInfo.GLESExtensions, nullptr))
{
continue;
}
// extract DPI information
int32 XDpi = ExtractDPI(NewDeviceInfo.GLESExtensions, TEXT("x-dpi"));
int32 YDpi = ExtractDPI(NewDeviceInfo.GLESExtensions, TEXT("y-dpi"));
if (XDpi != INDEX_NONE && YDpi != INDEX_NONE)
{
NewDeviceInfo.DeviceDPI = (XDpi + YDpi) / 2;
}
// extract OpenGL version and GPU family name
ExtractGPUInfo(NewDeviceInfo.OpenGLVersionString, NewDeviceInfo.GPUFamilyString, NewDeviceInfo.GLESExtensions);
}
// grab device brand
{
FString ExecCommand = FString::Printf(TEXT("-s %s %s ro.product.brand"), *NewDeviceInfo.SerialNumber, *GetPropCommand);
FString RoProductBrand;
ExecuteAdbCommand(*ExecCommand, &RoProductBrand, nullptr);
const TCHAR* Ptr = *RoProductBrand;
FParse::Line(&Ptr, NewDeviceInfo.DeviceBrand);
}
// grab screen resolution
{
FString ResolutionString;
const FString ExecCommand = FString::Printf(TEXT("-s %s shell wm size"), *NewDeviceInfo.SerialNumber);
if (ExecuteAdbCommand(*ExecCommand, &ResolutionString, nullptr))
{
bool bFoundResX = false;
int32 StartIndex = INDEX_NONE;
for (int32 Index = 0; Index < ResolutionString.Len(); ++Index)
{
if (StartIndex == INDEX_NONE && FChar::IsDigit(ResolutionString[Index]))
{
StartIndex = Index;
}
else if (StartIndex != INDEX_NONE && !FChar::IsDigit(ResolutionString[Index]))
{
FString str = ResolutionString.Mid(StartIndex, Index - StartIndex);
if (bFoundResX)
{
NewDeviceInfo.ResolutionY = FCString::Atoi(*str);
break;
}
else
{
NewDeviceInfo.ResolutionX = FCString::Atoi(*str);
bFoundResX = true;
StartIndex = INDEX_NONE;
}
}
}
}
}
// grab the GL ES version
FString GLESVersionString;
const FString GLVersionCommand = FString::Printf(TEXT("-s %s %s ro.opengles.version"), *NewDeviceInfo.SerialNumber, *GetPropCommand);
if (!ExecuteAdbCommand(*GLVersionCommand, &GLESVersionString, nullptr))
{
continue;
}
NewDeviceInfo.GLESVersion = FCString::Atoi(*GLESVersionString);
// parse the device model
FParse::Value(*DeviceString, TEXT("model:"), NewDeviceInfo.Model);
if (NewDeviceInfo.Model.IsEmpty())
{
FString ModelCommand = FString::Printf(TEXT("-s %s %s ro.product.model"), *NewDeviceInfo.SerialNumber, *GetPropCommand);
FString RoProductModel;
ExecuteAdbCommand(*ModelCommand, &RoProductModel, nullptr);
const TCHAR* Ptr = *RoProductModel;
FParse::Line(&Ptr, NewDeviceInfo.Model);
}
// parse the device name
FParse::Value(*DeviceString, TEXT("device:"), NewDeviceInfo.DeviceName);
if (NewDeviceInfo.DeviceName.IsEmpty())
{
FString DeviceCommand = FString::Printf(TEXT("-s %s %s ro.product.device"), *NewDeviceInfo.SerialNumber, *GetPropCommand);
FString RoProductDevice;
ExecuteAdbCommand(*DeviceCommand, &RoProductDevice, nullptr);
const TCHAR* Ptr = *RoProductDevice;
FParse::Line(&Ptr, NewDeviceInfo.DeviceName);
}
// establish port forwarding if we're doing messaging
if (TcpMessagingModule != nullptr)
{
// fill in the port forwarding array if needed
if (PortForwardings.Num() == 0)
{
FString ForwardList;
if (ExecuteAdbCommand(TEXT("forward --list"), &ForwardList, nullptr))
{
ForwardList = ForwardList.Replace(TEXT("\r"), TEXT("\n"));
ForwardList.ParseIntoArray(PortForwardings, TEXT("\n"), true);
}
}
// check if this device already has port forwarding enabled for message bus, eg from another editor session
for (FString& FwdString : PortForwardings)
{
const TCHAR* Ptr = *FwdString;
FString FwdSerialNumber, FwdHostPortString, FwdDevicePortString;
uint16 FwdHostPort, FwdDevicePort;
if (FParse::Token(Ptr, FwdSerialNumber, false) && FwdSerialNumber == NewDeviceInfo.SerialNumber &&
FParse::Token(Ptr, FwdHostPortString, false) && FParse::Value(*FwdHostPortString, TEXT("tcp:"), FwdHostPort) &&
FParse::Token(Ptr, FwdDevicePortString, false) && FParse::Value(*FwdDevicePortString, TEXT("tcp:"), FwdDevicePort) && FwdDevicePort == 6666)
{
NewDeviceInfo.HostMessageBusPort = FwdHostPort;
break;
}
}
// if not, setup TCP port forwarding for message bus on first available TCP port above 6666
if (NewDeviceInfo.HostMessageBusPort == 0)
{
uint16 HostMessageBusPort = 6666;
bool bFoundPort;
do
{
bFoundPort = true;
for (auto It = DeviceMap.CreateConstIterator(); It; ++It)
{
if (HostMessageBusPort == It.Value().HostMessageBusPort)
{
bFoundPort = false;
HostMessageBusPort++;
break;
}
}
} while (!bFoundPort);
FString DeviceCommand = FString::Printf(TEXT("-s %s forward tcp:%d tcp:6666"), *NewDeviceInfo.SerialNumber, HostMessageBusPort);
ExecuteAdbCommand(*DeviceCommand, nullptr, nullptr);
NewDeviceInfo.HostMessageBusPort = HostMessageBusPort;
}
TcpMessagingModule->AddOutgoingConnection(FString::Printf(TEXT("127.0.0.1:%d"), NewDeviceInfo.HostMessageBusPort));
}
}
// add the device to the map
{
FScopeLock ScopeLock(DeviceMapLock);
FAndroidDeviceInfo& SavedDeviceInfo = DeviceMap.Add(NewDeviceInfo.SerialNumber);
SavedDeviceInfo = NewDeviceInfo;
}
}
// loop through the previously connected devices list and remove any that aren't still connected from the updated DeviceMap
TArray<FString> DevicesToRemove;
for (auto It = DeviceMap.CreateConstIterator(); It; ++It)
{
if (!CurrentlyConnectedDevices.Contains(It.Key()))
{
if (TcpMessagingModule && It.Value().HostMessageBusPort != 0)
{
TcpMessagingModule->RemoveOutgoingConnection(FString::Printf(TEXT("127.0.0.1:%d"), It.Value().HostMessageBusPort));
}
DevicesToRemove.Add(It.Key());
}
}
{
// enter the critical section and remove the devices from the map
FScopeLock ScopeLock(DeviceMapLock);
for (auto It = DevicesToRemove.CreateConstIterator(); It; ++It)
{
DeviceMap.Remove(*It);
}
}
}
private:
// path to the adb command
FString ADBPath;
FString GetPropCommand;
bool bGetExtensionsViaSurfaceFlinger;
bool bForLumin;
// > 0 if we've been asked to abort work in progress at the next opportunity
FThreadSafeCounter StopTaskCounter;
TMap<FString,FAndroidDeviceInfo>& DeviceMap;
FCriticalSection* DeviceMapLock;
FCriticalSection* ADBPathCheckLock;
bool HasADBPath;
bool ForceCheck;
ITcpMessagingModule* TcpMessagingModule;
};
class FAndroidDeviceDetection : public IAndroidDeviceDetection
{
public:
FAndroidDeviceDetection()
: DetectionThread(nullptr)
, DetectionThreadRunnable(nullptr)
{
// create and fire off our device detection thread
DetectionThreadRunnable = new FAndroidDeviceDetectionRunnable(DeviceMap, &DeviceMapLock, &ADBPathCheckLock);
DetectionThread = FRunnableThread::Create(DetectionThreadRunnable, TEXT("FAndroidDeviceDetectionRunnable"));
}
virtual ~FAndroidDeviceDetection()
{
if (DetectionThreadRunnable && DetectionThread)
{
DetectionThreadRunnable->Stop();
DetectionThread->WaitForCompletion();
}
}
virtual void Initialize(const TCHAR* InSDKDirectoryEnvVar, const TCHAR* InSDKRelativeExePath, const TCHAR* InGetPropCommand, bool InbGetExtensionsViaSurfaceFlinger, bool InbForLumin = false) override
{
SDKDirEnvVar = InSDKDirectoryEnvVar;
SDKRelativeExePath = InSDKRelativeExePath;
GetPropCommand = InGetPropCommand;
bGetExtensionsViaSurfaceFlinger = InbGetExtensionsViaSurfaceFlinger;
bForLumin = InbForLumin;
UpdateADBPath();
}
virtual const TMap<FString,FAndroidDeviceInfo>& GetDeviceMap() override
{
return DeviceMap;
}
virtual FCriticalSection* GetDeviceMapLock() override
{
return &DeviceMapLock;
}
virtual FString GetADBPath() override
{
FScopeLock PathUpdateLock(&ADBPathCheckLock);
return ADBPath;
}
virtual void UpdateADBPath() override
{
FScopeLock PathUpdateLock(&ADBPathCheckLock);
FString AndroidDirectory = FPlatformMisc::GetEnvironmentVariable(*SDKDirEnvVar);
ADBPath.Empty();
#if PLATFORM_MAC || PLATFORM_LINUX
if (AndroidDirectory.Len() == 0)
{
#if PLATFORM_LINUX
// didn't find ANDROID_HOME, so parse the .bashrc file on Linux
FArchive* FileReader = IFileManager::Get().CreateFileReader(*FString("~/.bashrc"));
#else
// didn't find ANDROID_HOME, so parse the .bash_profile file on MAC
FArchive* FileReader = IFileManager::Get().CreateFileReader(*FString([@"~/.bash_profile" stringByExpandingTildeInPath]));
#endif
if (FileReader)
{
const int64 FileSize = FileReader->TotalSize();
ANSICHAR* AnsiContents = (ANSICHAR*)FMemory::Malloc(FileSize + 1);
FileReader->Serialize(AnsiContents, FileSize);
FileReader->Close();
delete FileReader;
AnsiContents[FileSize] = 0;
TArray<FString> Lines;
FString(ANSI_TO_TCHAR(AnsiContents)).ParseIntoArrayLines(Lines);
FMemory::Free(AnsiContents);
for (int32 Index = Lines.Num()-1; Index >=0; Index--)
{
if (AndroidDirectory.Len() == 0 && Lines[Index].StartsWith(FString::Printf(TEXT("export %s="), *SDKDirEnvVar)))
{
FString Directory;
Lines[Index].Split(TEXT("="), NULL, &Directory);
Directory = Directory.Replace(TEXT("\""), TEXT(""));
AndroidDirectory = Directory;
setenv(TCHAR_TO_ANSI(*SDKDirEnvVar), TCHAR_TO_ANSI(*AndroidDirectory), 1);
}
}
}
}
#endif
if (AndroidDirectory.Len() > 0)
{
ADBPath = FPaths::Combine(*AndroidDirectory, SDKRelativeExePath);
// if it doesn't exist then just clear the path as we might set it later
if (!FPaths::FileExists(*ADBPath))
{
ADBPath.Empty();
}
}
DetectionThreadRunnable->UpdateADBPath(ADBPath, GetPropCommand, bGetExtensionsViaSurfaceFlinger, bForLumin);
}
virtual void ExportDeviceProfile(const FString& OutPath, const FString& DeviceName) override
{
// instantiate an FPIEPreviewDeviceSpecifications instance and its values
FPIEPreviewDeviceSpecifications DeviceSpecs;
bool bOpenGL3x = false;
{
FScopeLock ExportLock(GetDeviceMapLock());
const FAndroidDeviceInfo* DeviceInfo = GetDeviceMap().Find(DeviceName);
if (DeviceInfo == nullptr)
{
FText TitleMessage = LOCTEXT("loc_ExportError_Title", "File export error.");
FMessageDialog::Open(EAppMsgType::Ok, EAppReturnType::Ok, LOCTEXT("loc_ExportError_Message", "Device disconnected!"), &TitleMessage);
return;
}
// generic values
DeviceSpecs.DevicePlatform = EPIEPreviewDeviceType::Android;
DeviceSpecs.ResolutionX = DeviceInfo->ResolutionX;
DeviceSpecs.ResolutionY = DeviceInfo->ResolutionY;
DeviceSpecs.ResolutionYImmersiveMode = 0;
DeviceSpecs.PPI = DeviceInfo->DeviceDPI;
DeviceSpecs.ScaleFactors = { 0.25f, 0.5f, 0.75f, 1.0f };
// Android specific values
DeviceSpecs.AndroidProperties.AndroidVersion = DeviceInfo->HumanAndroidVersion;
DeviceSpecs.AndroidProperties.DeviceModel = DeviceInfo->Model;
DeviceSpecs.AndroidProperties.DeviceMake = DeviceInfo->DeviceBrand;
DeviceSpecs.AndroidProperties.GLVersion = DeviceInfo->OpenGLVersionString;
DeviceSpecs.AndroidProperties.GPUFamily = DeviceInfo->GPUFamilyString;
DeviceSpecs.AndroidProperties.VulkanVersion = "0.0.0";
DeviceSpecs.AndroidProperties.UsingHoudini = false;
DeviceSpecs.AndroidProperties.VulkanAvailable = false;
// OpenGL ES 3.x
bOpenGL3x = DeviceInfo->OpenGLVersionString.Contains(TEXT("OpenGL ES 3"));
if (bOpenGL3x)
{
DeviceSpecs.AndroidProperties.GLES31RHIState.MaxTextureDimensions = 4096;
DeviceSpecs.AndroidProperties.GLES31RHIState.MaxShadowDepthBufferSizeX = 2048;
DeviceSpecs.AndroidProperties.GLES31RHIState.MaxShadowDepthBufferSizeY = 2048;
DeviceSpecs.AndroidProperties.GLES31RHIState.MaxCubeTextureDimensions = 2048;
DeviceSpecs.AndroidProperties.GLES31RHIState.SupportsRenderTargetFormat_PF_G8 = true;
DeviceSpecs.AndroidProperties.GLES31RHIState.SupportsRenderTargetFormat_PF_FloatRGBA = DeviceInfo->GLESExtensions.Contains(TEXT("GL_EXT_color_buffer_half_float"));
DeviceSpecs.AndroidProperties.GLES31RHIState.SupportsMultipleRenderTargets = true;
}
// OpenGL ES 2.0
UE_CLOG(!bOpenGL3x, LogCore, Fatal, TEXT("OpenGL ES 3 Required."));
} // FScopeLock ExportLock released
// create a JSon object from the above structure
TSharedPtr<FJsonObject> JsonObject = FJsonObjectConverter::UStructToJsonObject<FPIEPreviewDeviceSpecifications>(DeviceSpecs);
// remove IOS fields
JsonObject->RemoveField("IOSProperties");
// serialize the JSon object to string
FString OutputString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
// export file to disk
FFileHelper::SaveStringToFile(OutputString, *OutPath);
} // end of virtual void ExportDeviceProfile(...)
private:
// path to the adb command (local)
FString ADBPath;
FString SDKDirEnvVar;
FString SDKRelativeExePath;
FString GetPropCommand;
bool bGetExtensionsViaSurfaceFlinger;
bool bForLumin;
FRunnableThread* DetectionThread;
FAndroidDeviceDetectionRunnable* DetectionThreadRunnable;
TMap<FString,FAndroidDeviceInfo> DeviceMap;
FCriticalSection DeviceMapLock;
FCriticalSection ADBPathCheckLock;
};
/**
* Holds the target platform singleton.
*/
static TMap<FString, FAndroidDeviceDetection*> AndroidDeviceDetectionSingletons;
/**
* Module for detecting android devices.
*/
class FAndroidDeviceDetectionModule : public IAndroidDeviceDetectionModule
{
public:
/**
* Destructor.
*/
~FAndroidDeviceDetectionModule( )
{
for (auto It : AndroidDeviceDetectionSingletons)
{
delete It.Value;
}
AndroidDeviceDetectionSingletons.Empty();
}
virtual IAndroidDeviceDetection* GetAndroidDeviceDetection(const TCHAR* OverridePlatformName) override
{
FString Key(OverridePlatformName);
FAndroidDeviceDetection* Value = AndroidDeviceDetectionSingletons.FindRef(Key);
if (Value == nullptr)
{
Value = AndroidDeviceDetectionSingletons.Add(Key, new FAndroidDeviceDetection());
}
return Value;
}
};
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE( FAndroidDeviceDetectionModule, AndroidDeviceDetection);
@@ -1,26 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AndroidDeviceDetection.h: AndroidDeviceDetection module public header file.
=============================================================================*/
#pragma once
/* Boilerplate
*****************************************************************************/
#include "Misc/MonolithicHeaderBoilerplate.h"
MONOLITHIC_HEADER_BOILERPLATE()
/* Dependencies
*****************************************************************************/
#include "Core.h"
#include "Modules/ModuleManager.h"
/* Interfaces
*****************************************************************************/
#include "Interfaces/IAndroidDeviceDetection.h"
#include "Interfaces/IAndroidDeviceDetectionModule.h"
@@ -1,88 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
IAndroidDeviceDetection.h: Declares the IAndroidDeviceDetection interface.
=============================================================================*/
#pragma once
#include "Containers/UnrealString.h"
#include "HAL/CriticalSection.h"
template<typename KeyType,typename ValueType,typename SetAllocator ,typename KeyFuncs > class TMap;
struct FAndroidDeviceInfo
{
// Device serial number, used to route ADB commands to a specific device
FString SerialNumber;
// Device model name
FString Model;
// Device name
FString DeviceName;
// User-visible version of android installed (ro.build.version.release)
FString HumanAndroidVersion;
// Android SDK version supported by the device (ro.build.version.sdk - note: deprecated in 4 according to docs, but version 4 devices return an empty string when querying the 'replacement' SDK_INT)
int32 SDKVersion;
// List of supported OpenGL extensions (retrieved via SurfaceFlinger)
FString GLESExtensions;
// Supported GLES version (ro.opengles.version)
int32 GLESVersion;
// Is the device authorized for USB communication? If not, then none of the other properties besides the serial number will be valid
bool bAuthorizedDevice;
// TCP port number on our local host forwarded over adb to the device
uint16 HostMessageBusPort;
// Holds pixel per inch value.
int32 DeviceDPI = 0;
// Holds the display resolution for the device
int32 ResolutionX = 0;
int32 ResolutionY = 0;
// Holds the reported OpenGLES version.
FString OpenGLVersionString;
// Holds the GPU family name.
FString GPUFamilyString;
// Holds the name of the manufacturer
FString DeviceBrand;
FAndroidDeviceInfo()
: SDKVersion(INDEX_NONE)
, GLESVersion(INDEX_NONE)
, bAuthorizedDevice(true)
, HostMessageBusPort(0)
{
}
};
/**
* Interface for AndroidDeviceDetection module.
*/
class IAndroidDeviceDetection
{
public:
virtual void Initialize(const TCHAR* SDKDirectoryEnvVar, const TCHAR* SDKRelativeExePath, const TCHAR* GetPropCommand, bool bGetExtensionsViaSurfaceFlinger, bool bForLumin = false) = 0;
virtual const TMap<FString,FAndroidDeviceInfo>& GetDeviceMap() = 0;
virtual FCriticalSection* GetDeviceMapLock() = 0;
virtual void UpdateADBPath() = 0;
virtual FString GetADBPath() = 0;
virtual void ExportDeviceProfile(const FString& OutPath, const FString& DeviceName) = 0;
protected:
/**
* Virtual destructor
*/
virtual ~IAndroidDeviceDetection() { }
};
@@ -1,32 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
IAndroidDeviceDetectionModule.h: Declares the IAndroidDeviceDetectionModule interface.
=============================================================================*/
#pragma once
#include "Modules/ModuleInterface.h"
class IAndroidDeviceDetection;
/**
* Interface for AndroidDeviceDetection module.
*/
class IAndroidDeviceDetectionModule
: public IModuleInterface
{
public:
/**
* Returns the android device detection singleton.
* @param AlternamePlatformName If a platform needs a separate detection instance, pass in an identifier here to create a new one
*/
virtual IAndroidDeviceDetection* GetAndroidDeviceDetection(const TCHAR* AlternamePlatformName=TEXT("")) = 0;
protected:
/**
* Virtual destructor
*/
virtual ~IAndroidDeviceDetectionModule( ) { }
};
@@ -1,42 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class AndroidPlatformEditor : ModuleRules
{
public AndroidPlatformEditor(ReadOnlyTargetRules Target) : base(Target)
{
BinariesSubFolder = "Android";
PrivateDependencyModuleNames.AddRange(
new string[] {
"Core",
"CoreUObject",
"InputCore",
"Engine",
"Slate",
"SlateCore",
"EditorStyle",
"EditorWidgets",
"DesktopWidgets",
"PropertyEditor",
"SharedSettingsWidgets",
"SourceControl",
"AndroidRuntimeSettings",
"AndroidDeviceDetection",
"TargetPlatform",
"RenderCore",
"RHI",
"MaterialShaderQualitySettings",
"MainFrame",
"AudioSettingsEditor"
}
);
PrivateIncludePathModuleNames.AddRange(
new string[] {
"Settings",
}
);
}
}
@@ -1,279 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AndroidLicenseDialog.h"
#include "Misc/Paths.h"
#include "HAL/PlatformProcess.h"
#include "Misc/FileHelper.h"
#include "Misc/EngineBuildSettings.h"
#include "Misc/EngineVersion.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Text/SRichTextBlock.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SHyperlink.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "EditorStyleSet.h"
#include "Misc/SecureHash.h"
#include "HAL/PlatformFilemanager.h"
#include "Interfaces/IAndroidDeviceDetectionModule.h"
#include "Interfaces/IAndroidDeviceDetection.h"
#include "Interfaces/IMainFrameModule.h"
#include "AndroidRuntimeSettings.h"
#define LOCTEXT_NAMESPACE "AndroidLicenseDialog"
void SAndroidLicenseDialog::Construct(const FArguments& InArgs)
{
bLicenseValid = false;
// from Android SDK Tools 26.1.1
FString LicenseFilename = FPaths::EngineDir() + TEXT("Source/ThirdParty/Android/package.xml");
FString LicenseText = "Unable to read " + LicenseFilename;
// Create file reader
TUniquePtr<FArchive> FileReader(IFileManager::Get().CreateFileReader(*LicenseFilename));
if (FileReader)
{
// Create buffer for file input
uint32 BufferSize = FileReader->TotalSize();
uint8* Buffer = (uint8*)FMemory::Malloc(BufferSize);
FileReader->Serialize(Buffer, BufferSize);
LicenseText = "Invalid license!";
uint8 StartPattern[] = "<license id=\"android-sdk-license\" type=\"text\">";
int32 StartPatternLength = strlen((char *)StartPattern);
uint8* LicenseStart = Buffer;
uint8* BufferEnd = Buffer + BufferSize - StartPatternLength;
while (LicenseStart < BufferEnd)
{
if (!memcmp(LicenseStart, StartPattern, StartPatternLength))
{
break;
}
LicenseStart++;
}
if (LicenseStart < BufferEnd)
{
LicenseStart += StartPatternLength;
uint8 EndPattern[] = "</license>";
int32 EndPatternLength = strlen((char *)EndPattern);
uint8* LicenseEnd = LicenseStart;
BufferEnd = Buffer + BufferSize - EndPatternLength;
while (LicenseEnd < BufferEnd)
{
if (!memcmp(LicenseEnd, EndPattern, EndPatternLength))
{
break;
}
LicenseEnd++;
}
if (LicenseEnd < BufferEnd)
{
int32 LicenseLength = LicenseEnd - LicenseStart;
{
const FUTF8ToTCHAR ConvertedString(reinterpret_cast<ANSICHAR*>(LicenseStart), LicenseLength);
LicenseText = FString(ConvertedString.Length(), ConvertedString.Get());
}
FSHA1::HashBuffer(LicenseStart, LicenseLength, LicenseHash.Hash);
bLicenseValid = true;
}
}
FMemory::Free(Buffer);
}
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
[
SAssignNew(ScrollBox, SScrollBox)
.Style(FEditorStyle::Get(), "ScrollBox")
+ SScrollBox::Slot()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SRichTextBlock)
.Text(FText::FromString(LicenseText))
.DecoratorStyleSet(&FEditorStyle::Get())
.AutoWrapText(true)
.Justification(ETextJustify::Left)
]
]
]
+ SVerticalBox::Slot()
.VAlign(VAlign_Bottom)
.HAlign(HAlign_Center)
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(20, 5, 20, 5)
.AutoWidth()
[
SNew(SButton)
.IsEnabled(bLicenseValid)
.OnClicked(this, &SAndroidLicenseDialog::OnAgree)
[
SNew(STextBlock)
.Text(LOCTEXT("AndroidLicenseAgreement_Agree", "Agree"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
+ SHorizontalBox::Slot()
.Padding(20, 5, 20, 5)
.AutoWidth()
[
SNew(SButton)
.OnClicked(this, &SAndroidLicenseDialog::OnCancel)
[
SNew(STextBlock)
.Text(LOCTEXT("AndroidLicenseAgreement_Cancel", "Cancel"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
]
];
}
static FString GetLicensePath()
{
auto &AndroidDeviceDetection = FModuleManager::LoadModuleChecked<IAndroidDeviceDetectionModule>("AndroidDeviceDetection");
IAndroidDeviceDetection* DeviceDetection = AndroidDeviceDetection.GetAndroidDeviceDetection();
FString ADBPath = DeviceDetection->GetADBPath();
if (!FPaths::FileExists(*ADBPath))
{
return TEXT("");
}
// strip off the adb.exe part
FString PlatformToolsPath;
FString Filename;
FString Extension;
FPaths::Split(ADBPath, PlatformToolsPath, Filename, Extension);
// remove the platform-tools part and point to licenses
FPaths::NormalizeDirectoryName(PlatformToolsPath);
FString LicensePath = PlatformToolsPath + "/../licenses";
FPaths::CollapseRelativeDirectories(LicensePath);
return LicensePath;
}
bool SAndroidLicenseDialog::HasLicense()
{
FString LicensePath = GetLicensePath();
if (LicensePath.IsEmpty())
{
return false;
}
// directory must exist
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (!PlatformFile.DirectoryExists(*LicensePath))
{
return false;
}
// license file must exist
FString LicenseFilename = LicensePath + "/android-sdk-license";
if (!PlatformFile.FileExists(*LicenseFilename))
{
return false;
}
// contents must match hash of license text
FString FileData = "";
FFileHelper::LoadFileToString(FileData, *LicenseFilename);
TArray<FString> lines;
int32 lineCount = FileData.ParseIntoArray(lines, TEXT("\n"), true);
FString LicenseString = LicenseHash.ToString().ToLower();
for (FString &line : lines)
{
if (line.TrimStartAndEnd().Equals(LicenseString))
{
return true;
}
}
// doesn't match
return false;
}
void SAndroidLicenseDialog::SetLicenseAcceptedCallback(const FSimpleDelegate& InOnLicenseAccepted)
{
OnLicenseAccepted = InOnLicenseAccepted;
}
FReply SAndroidLicenseDialog::OnAgree()
{
FString LicensePath = GetLicensePath();
if (!LicensePath.IsEmpty())
{
// create licenses directory if doesn't exist
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (!PlatformFile.DirectoryExists(*LicensePath))
{
PlatformFile.CreateDirectory(*LicensePath);
}
FString LicenseFilename = LicensePath + "/android-sdk-license";
IFileHandle* FileHandle = PlatformFile.OpenWrite(*LicenseFilename);
if (FileHandle)
{
FString HashText = TEXT("\015\012") + LicenseHash.ToString().ToLower();
FileHandle->Write((const uint8*)TCHAR_TO_ANSI(*HashText), HashText.Len());
delete FileHandle;
}
else
{
FText ErrorText = FText::Format(LOCTEXT("CouldntWriteLicense", "Couldn't write license file {0}. Make sure you have the permissions to modify the file and try again."), FText::FromString(LicenseFilename));
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *ErrorText.ToString(), TEXT("Error"));
}
}
OnLicenseAccepted.ExecuteIfBound();
TSharedRef<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()).ToSharedRef();
FSlateApplication::Get().RequestDestroyWindow(ParentWindow);
return FReply::Handled();
}
FReply SAndroidLicenseDialog::OnCancel()
{
// turn off Gradle checkbox
//GetMutableDefault<UAndroidRuntimeSettings>()->bEnableGradle = false;
TSharedRef<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()).ToSharedRef();
FSlateApplication::Get().RequestDestroyWindow(ParentWindow);
return FReply::Handled();
}
#undef LOCTEXT_NAMESPACE
@@ -1,45 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Input/Reply.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SCompoundWidget.h"
#include "Misc/SecureHash.h"
class SScrollBox;
/**
* Credit screen widget that displays a scrolling list contributors.
*/
class SAndroidLicenseDialog : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS( SAndroidLicenseDialog )
{}
SLATE_END_ARGS()
/**
* Constructs the credits screen widgets
*/
void Construct(const FArguments& InArgs);
bool HasLicense();
void SetLicenseAcceptedCallback(const FSimpleDelegate& InOnLicenseAccepted);
private:
bool bLicenseValid;
FReply OnAgree();
FReply OnCancel();
FSHAHash LicenseHash;
/** The widget that scrolls the license text */
TSharedPtr<SScrollBox> ScrollBox;
FSimpleDelegate OnLicenseAccepted;
};
@@ -1,162 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "GenericPlatform/GenericPlatformStackWalk.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstance.h"
#include "Interfaces/IAndroidDeviceDetectionModule.h"
#include "PropertyEditorModule.h"
#include "AndroidRuntimeSettings.h"
#include "AndroidTargetSettingsCustomization.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "AndroidSDKSettings.h"
#include "AndroidSDKSettingsCustomization.h"
#include "ISettingsModule.h"
#include "MaterialShaderQualitySettingsCustomization.h"
#include "MaterialShaderQualitySettings.h"
#include "ComponentRecreateRenderStateContext.h"
#include "ShaderPlatformQualitySettings.h"
#include "Misc/CoreDelegates.h"
#define LOCTEXT_NAMESPACE "FAndroidPlatformEditorModule"
/**
* Module for Android platform editor utilities
*/
class FAndroidPlatformEditorModule
: public IModuleInterface
{
// IModuleInterface interface
virtual void StartupModule() override
{
// register settings detail panel customization
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyModule.RegisterCustomClassLayout(
UAndroidRuntimeSettings::StaticClass()->GetFName(),
FOnGetDetailCustomizationInstance::CreateStatic(&FAndroidTargetSettingsCustomization::MakeInstance)
);
PropertyModule.RegisterCustomClassLayout(
UAndroidSDKSettings::StaticClass()->GetFName(),
FOnGetDetailCustomizationInstance::CreateStatic(&FAndroidSDKSettingsCustomization::MakeInstance)
);
FOnUpdateMaterialShaderQuality UpdateMaterials = FOnUpdateMaterialShaderQuality::CreateLambda([]()
{
FGlobalComponentRecreateRenderStateContext Recreate;
FlushRenderingCommands();
UMaterial::AllMaterialsCacheResourceShadersForRendering();
UMaterialInstance::AllMaterialsCacheResourceShadersForRendering();
});
PropertyModule.RegisterCustomClassLayout(
UShaderPlatformQualitySettings::StaticClass()->GetFName(),
FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialShaderQualitySettingsCustomization::MakeInstance, UpdateMaterials)
);
PropertyModule.NotifyCustomizationModuleChanged();
// register settings
ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
if (SettingsModule != nullptr)
{
SettingsModule->RegisterSettings("Project", "Platforms", "Android",
LOCTEXT("RuntimeSettingsName", "Android"),
LOCTEXT("RuntimeSettingsDescription", "Project settings for Android apps"),
GetMutableDefault<UAndroidRuntimeSettings>()
);
SettingsModule->RegisterSettings("Project", "Platforms", "AndroidSDK",
LOCTEXT("SDKSettingsName", "Android SDK"),
LOCTEXT("SDKSettingsDescription", "Settings for Android SDK (for all projects)"),
GetMutableDefault<UAndroidSDKSettings>()
);
{
static FName NAME_GLSL_ES3_1_ANDROID(TEXT("GLSL_ES3_1_ANDROID"));
UShaderPlatformQualitySettings* AndroidMaterialQualitySettings = UMaterialShaderQualitySettings::Get()->GetShaderPlatformQualitySettings(NAME_GLSL_ES3_1_ANDROID);
SettingsModule->RegisterSettings("Project", "Platforms", "AndroidES31Quality",
LOCTEXT("AndroidES31QualitySettingsName", "Android Material Quality - ES31"),
LOCTEXT("AndroidES31QualitySettingsDescription", "Settings for Android ES3.1 material quality"),
AndroidMaterialQualitySettings
);
}
{
static FName NAME_SF_VULKAN_ES31_ANDROID(TEXT("SF_VULKAN_ES31_ANDROID"));
UShaderPlatformQualitySettings* AndroidMaterialQualitySettings = UMaterialShaderQualitySettings::Get()->GetShaderPlatformQualitySettings(NAME_SF_VULKAN_ES31_ANDROID);
SettingsModule->RegisterSettings("Project", "Platforms", "AndroidVulkanQuality",
LOCTEXT("AndroidVulkanQualitySettingsName", "Android Material Quality - Vulkan"),
LOCTEXT("AndroidVulkanQualitySettingsDescription", "Settings for Android Vulkan material quality"),
AndroidMaterialQualitySettings
);
}
{
static FName NAME_SF_VULKAN_SM5_ANDROID(TEXT("SF_VULKAN_SM5_ANDROID"));
UShaderPlatformQualitySettings* AndroidMaterialQualitySettings = UMaterialShaderQualitySettings::Get()->GetShaderPlatformQualitySettings(NAME_SF_VULKAN_SM5_ANDROID);
SettingsModule->RegisterSettings("Project", "Platforms", "AndroidVulkanSM5Quality",
LOCTEXT("AndroidVulkanSM5QualitySettingsName", "Android SM5 Material Quality - Vulkan"),
LOCTEXT("AndroidVulkanSM5QualitySettingsDescription", "Settings for Android Vulkan SM5 material quality"),
AndroidMaterialQualitySettings
);
}
}
auto AndroidRuntimeSettings = GetMutableDefault<UAndroidRuntimeSettings>();
if (AndroidRuntimeSettings)
{
AndroidRuntimeSettings->OnPropertyChanged.AddLambda([this, AndroidRuntimeSettings](struct FPropertyChangedEvent& PropertyChangedEvent)
{
if (PropertyChangedEvent.Property != nullptr)
{
if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, bBuildForES31) &&
AndroidRuntimeSettings->bBuildForES31 == false)
{
FCoreDelegates::OnFeatureLevelDisabled.Broadcast(ERHIFeatureLevel::ES3_1, FName());
return;
}
if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, bSupportsVulkan) &&
AndroidRuntimeSettings->bSupportsVulkan == false)
{
FCoreDelegates::OnFeatureLevelDisabled.Broadcast(ERHIFeatureLevel::ES3_1, LegacyShaderPlatformToShaderFormat(SP_VULKAN_ES3_1_ANDROID));
return;
}
}
});
}
// Force the SDK settings into a sane state initially so we can make use of them
auto &TargetPlatformManagerModule = FModuleManager::LoadModuleChecked<ITargetPlatformManagerModule>("TargetPlatform");
UAndroidSDKSettings * settings = GetMutableDefault<UAndroidSDKSettings>();
settings->SetTargetModule(&TargetPlatformManagerModule);
auto &AndroidDeviceDetection = FModuleManager::LoadModuleChecked<IAndroidDeviceDetectionModule>("AndroidDeviceDetection");
settings->SetDeviceDetection(AndroidDeviceDetection.GetAndroidDeviceDetection());
settings->UpdateTargetModulePaths();
}
virtual void ShutdownModule() override
{
ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
if (SettingsModule != nullptr)
{
SettingsModule->UnregisterSettings("Project", "Platforms", "Android");
SettingsModule->UnregisterSettings("Project", "Platforms", "AndroidSDK");
SettingsModule->UnregisterSettings("Project", "Platforms", "AndroidES31Quality");
SettingsModule->UnregisterSettings("Project", "Platforms", "AndroidVulkanQuality");
}
}
};
IMPLEMENT_MODULE(FAndroidPlatformEditorModule, AndroidPlatformEditor);
#undef LOCTEXT_NAMESPACE
@@ -1,70 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AndroidSDKSettings.h"
#include "Misc/Paths.h"
#include "Interfaces/IAndroidDeviceDetection.h"
//#include "EngineTypes.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
DEFINE_LOG_CATEGORY_STATIC(AndroidSDKSettings, Log, All);
UAndroidSDKSettings::UAndroidSDKSettings(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
#if WITH_EDITOR
void UAndroidSDKSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
UpdateTargetModulePaths();
}
void UAndroidSDKSettings::SetTargetModule(ITargetPlatformManagerModule * InTargetManagerModule)
{
TargetManagerModule = InTargetManagerModule;
}
void UAndroidSDKSettings::SetDeviceDetection(IAndroidDeviceDetection * InAndroidDeviceDetection)
{
AndroidDeviceDetection = InAndroidDeviceDetection;
}
void UAndroidSDKSettings::UpdateTargetModulePaths()
{
TArray<FString> Keys;
TArray<FString> Values;
if (!SDKPath.Path.IsEmpty())
{
FPaths::NormalizeFilename(SDKPath.Path);
Keys.Add(TEXT("ANDROID_HOME"));
Values.Add(SDKPath.Path);
}
if (!NDKPath.Path.IsEmpty())
{
FPaths::NormalizeFilename(NDKPath.Path);
Keys.Add(TEXT("NDKROOT"));
Values.Add(NDKPath.Path);
}
if (!JavaPath.Path.IsEmpty())
{
FPaths::NormalizeFilename(JavaPath.Path);
Keys.Add(TEXT("JAVA_HOME"));
Values.Add(JavaPath.Path);
}
SaveConfig();
if (Keys.Num() != 0)
{
TargetManagerModule->UpdatePlatformEnvironment(TEXT("Android"), Keys, Values);
AndroidDeviceDetection->UpdateADBPath();
}
}
#endif
@@ -1,54 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Object.h"
#include "Engine/EngineTypes.h"
#include "AndroidSDKSettings.generated.h"
class IAndroidDeviceDetection;
class ITargetPlatformManagerModule;
/**
* Implements the settings for the Android SDK setup.
*/
UCLASS(config=Engine, globaluserconfig)
class ANDROIDPLATFORMEDITOR_API UAndroidSDKSettings : public UObject
{
public:
GENERATED_UCLASS_BODY()
// Location on disk of the Android SDK (falls back to ANDROID_HOME environment variable if this is left blank)
UPROPERTY(GlobalConfig, EditAnywhere, Category = SDKConfig, Meta = (DisplayName = "Location of Android SDK (the directory usually contains 'android-sdk-')"))
FDirectoryPath SDKPath;
// Location on disk of the Android NDK (falls back to NDKROOT environment variable if this is left blank)
UPROPERTY(GlobalConfig, EditAnywhere, Category = SDKConfig, Meta = (DisplayName = "Location of Android NDK (the directory usually contains 'android-ndk-')"))
FDirectoryPath NDKPath;
// Location on disk of Java (falls back to JAVA_HOME environment variable if this is left blank)
UPROPERTY(GlobalConfig, EditAnywhere, Category = SDKConfig, Meta = (DisplayName = "Location of JAVA (the directory usually contains 'jdk')"))
FDirectoryPath JavaPath;
// Which SDK to package and compile Java with (a specific version or (without quotes) 'latest' for latest version on disk, or 'matchndk' to match the NDK API Level)
UPROPERTY(GlobalConfig, EditAnywhere, Category = SDKConfig, Meta = (DisplayName = "SDK API Level (specific version, 'latest', or 'matchndk' - see tooltip)"))
FString SDKAPILevel;
// Which NDK to compile with (a specific version or (without quotes) 'latest' for latest version on disk). Note that choosing android-21 or later won't run on pre-5.0 devices.
UPROPERTY(GlobalConfig, EditAnywhere, Category = SDKConfig, Meta = (DisplayName = "NDK API Level (specific version or 'latest' - see tooltip)"))
FString NDKAPILevel;
#if WITH_EDITOR
// UObject interface
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
// End of UObject interface
void SetTargetModule(ITargetPlatformManagerModule * TargetManagerModule);
void SetDeviceDetection(IAndroidDeviceDetection * AndroidDeviceDetection);
void UpdateTargetModulePaths();
ITargetPlatformManagerModule * TargetManagerModule;
IAndroidDeviceDetection * AndroidDeviceDetection;
#endif
};
@@ -1,42 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AndroidSDKSettingsCustomization.h"
#include "Modules/ModuleManager.h"
#include "Layout/Visibility.h"
#include "UnrealClient.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "AndroidSDKSettings.h"
#include "PropertyHandle.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "IDetailPropertyRow.h"
#define LOCTEXT_NAMESPACE "AndroidSDKSettings"
//////////////////////////////////////////////////////////////////////////
// FAndroidSDKSettingsCustomization
TSharedRef<IDetailCustomization> FAndroidSDKSettingsCustomization::MakeInstance()
{
return MakeShareable(new FAndroidSDKSettingsCustomization);
}
FAndroidSDKSettingsCustomization::FAndroidSDKSettingsCustomization()
{
TargetPlatformManagerModule = &FModuleManager::LoadModuleChecked<ITargetPlatformManagerModule>("TargetPlatform");
}
void FAndroidSDKSettingsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
{
SavedLayoutBuilder = &DetailLayout;
BuildSDKPathSection(DetailLayout);
}
void FAndroidSDKSettingsCustomization::BuildSDKPathSection(IDetailLayoutBuilder& DetailLayout)
{
}
//////////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE
@@ -1,33 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "IDetailCustomization.h"
class IDetailLayoutBuilder;
class ITargetPlatformManagerModule;
//////////////////////////////////////////////////////////////////////////
// FAndroidSDKSettingsCustomization
class FAndroidSDKSettingsCustomization : public IDetailCustomization
{
public:
// Makes a new instance of this detail layout class for a specific detail view requesting it
static TSharedRef<IDetailCustomization> MakeInstance();
// IDetailCustomization interface
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailLayout) override;
// End of IDetailCustomization interface
private:
FAndroidSDKSettingsCustomization();
void BuildSDKPathSection(IDetailLayoutBuilder& DetailLayout);
private:
IDetailLayoutBuilder* SavedLayoutBuilder;
ITargetPlatformManagerModule * TargetPlatformManagerModule;
};
@@ -1,839 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AndroidTargetSettingsCustomization.h"
#include "Misc/Paths.h"
#include "Layout/Margin.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Engine/GameViewportClient.h"
#include "Widgets/SBoxPanel.h"
#include "Engine/GameEngine.h"
#include "Framework/Text/SlateHyperlinkRun.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Text/SRichTextBlock.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SButton.h"
#include "EditorStyleSet.h"
#include "AndroidRuntimeSettings.h"
#include "PropertyHandle.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "IDetailPropertyRow.h"
#include "DetailCategoryBuilder.h"
#include "SExternalImageReference.h"
#include "SHyperlinkLaunchURL.h"
#include "SPlatformSetupMessage.h"
#include "PlatformIconInfo.h"
#include "SourceControlHelpers.h"
#include "ManifestUpdateHelper.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Misc/EngineBuildSettings.h"
#include "InstalledPlatformInfo.h"
#include "AndroidLicenseDialog.h"
#include "Interfaces/IMainFrameModule.h"
#include "Framework/Application/SlateApplication.h"
#define LOCTEXT_NAMESPACE "AndroidRuntimeSettings"
//////////////////////////////////////////////////////////////////////////
// FAndroidTargetSettingsCustomization
namespace FAndroidTargetSettingsCustomizationConstants
{
const FText DisabledTip = LOCTEXT("GitHubSourceRequiredToolTip", "This requires GitHub source.");
}
TSharedRef<IDetailCustomization> FAndroidTargetSettingsCustomization::MakeInstance()
{
return MakeShareable(new FAndroidTargetSettingsCustomization);
}
FAndroidTargetSettingsCustomization::FAndroidTargetSettingsCustomization()
: LastLicenseChecktime(-1.0)
, AndroidRelativePath(TEXT(""))
, EngineAndroidPath(FPaths::EngineDir() + TEXT("Build/Android/Java"))
, GameAndroidPath(FPaths::ProjectDir() + TEXT("Build/Android"))
, EngineGooglePlayAppIDPath(EngineAndroidPath / TEXT("res") / TEXT("values") / TEXT("GooglePlayAppID.xml"))
, GameGooglePlayAppIDPath(GameAndroidPath / TEXT("res") / TEXT("values") / TEXT("GooglePlayAppID.xml"))
, EngineProguardPath(EngineAndroidPath / TEXT("proguard-project.txt"))
, GameProguardPath(GameAndroidPath / TEXT("proguard-project.txt"))
, EngineProjectPropertiesPath(EngineAndroidPath / TEXT("project.properties"))
, GameProjectPropertiesPath(GameAndroidPath / TEXT("project.properties"))
{
new (IconNames) FPlatformIconInfo(TEXT("res/drawable/icon.png"), LOCTEXT("SettingsIcon", "Icon"), FText::GetEmpty(), 48, 48, FPlatformIconInfo::Required);
new (IconNames) FPlatformIconInfo(TEXT("res/drawable-ldpi/icon.png"), LOCTEXT("SettingsIcon_LDPI", "LDPI Icon"), FText::GetEmpty(), 36, 36, FPlatformIconInfo::Required);
new (IconNames) FPlatformIconInfo(TEXT("res/drawable-mdpi/icon.png"), LOCTEXT("SettingsIcon_MDPI", "MDPI Icon"), FText::GetEmpty(), 48, 48, FPlatformIconInfo::Required);
new (IconNames) FPlatformIconInfo(TEXT("res/drawable-hdpi/icon.png"), LOCTEXT("SettingsIcon_HDPI", "HDPI Icon"), FText::GetEmpty(), 72, 72, FPlatformIconInfo::Required);
new (IconNames) FPlatformIconInfo(TEXT("res/drawable-xhdpi/icon.png"), LOCTEXT("SettingsIcon_XHDPI", "XHDPI Icon"), FText::GetEmpty(), 96, 96, FPlatformIconInfo::Required);
new (LaunchImageNames)FPlatformIconInfo(TEXT("res/drawable/downloadimagev.png"), LOCTEXT("SettingsIcon_DownloadImageV", "Download Background Vertical Image"), FText::GetEmpty(), 720, 1280, FPlatformIconInfo::Required);
new (LaunchImageNames)FPlatformIconInfo(TEXT("res/drawable/downloadimageh.png"), LOCTEXT("SettingsIcon_DownloadImageH", "Download Background Horizontal Image"), FText::GetEmpty(), 1280, 720, FPlatformIconInfo::Required);
new (LaunchImageNames)FPlatformIconInfo(TEXT("res/drawable/splashscreen_portrait.png"), LOCTEXT("LaunchImage_Portrait", "Launch Portrait"), FText::GetEmpty(), 360, 640, FPlatformIconInfo::Required);
new (LaunchImageNames)FPlatformIconInfo(TEXT("res/drawable/splashscreen_landscape.png"), LOCTEXT("LaunchImage_Landscape", "Launch Landscape"), FText::GetEmpty(), 640, 360, FPlatformIconInfo::Required);
new (DaydreamAppTileImageNames) FPlatformIconInfo(TEXT("res/drawable-nodpi/vr_icon.png"), LOCTEXT("AppTile_Icon", "App Tile Icon"), FText::GetEmpty(), 512, 512, FPlatformIconInfo::Optional);
new (DaydreamAppTileImageNames) FPlatformIconInfo(TEXT("res/drawable-nodpi/vr_icon_background.png"), LOCTEXT("AppTile_Icon_Background", "App Tile Icon Background"), FText::GetEmpty(), 512, 512, FPlatformIconInfo::Optional);
}
void FAndroidTargetSettingsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
{
SavedLayoutBuilder = &DetailLayout;
BuildAppManifestSection(DetailLayout);
BuildIconSection(DetailLayout);
BuildLaunchImageSection(DetailLayout);
BuildDaydreamAppTileImageSection(DetailLayout);
BuildGraphicsDebuggerSection(DetailLayout);
AudioPluginWidgetManager.BuildAudioCategory(DetailLayout, FString(TEXT("Android")));
}
static void OnBrowserLinkClicked(const FSlateHyperlinkRun::FMetadata& Metadata)
{
const FString* URL = Metadata.Find(TEXT("href"));
if(URL)
{
FPlatformProcess::LaunchURL(**URL, nullptr, nullptr);
}
}
void FAndroidTargetSettingsCustomization::BuildAppManifestSection(IDetailLayoutBuilder& DetailLayout)
{
// Cache some categories
IDetailCategoryBuilder& APKPackagingCategory = DetailLayout.EditCategory(TEXT("APK Packaging"));
IDetailCategoryBuilder& BuildCategory = DetailLayout.EditCategory(TEXT("Build"));
IDetailCategoryBuilder& AdvancedBuildCategory = DetailLayout.EditCategory(TEXT("AdvancedBuild"));
AdvancedBuildCategory.InitiallyCollapsed(true);
IDetailCategoryBuilder& SDKConfigCategory = DetailLayout.EditCategory(TEXT("Project SDK Override"));
SDKConfigCategory.InitiallyCollapsed(true);
IDetailCategoryBuilder& SigningCategory = DetailLayout.EditCategory(TEXT("DistributionSigning"));
TSharedRef<SPlatformSetupMessage> PlatformSetupMessage = SNew(SPlatformSetupMessage, GameProjectPropertiesPath)
.PlatformName(LOCTEXT("AndroidPlatformName", "Android"))
.OnSetupClicked(this, &FAndroidTargetSettingsCustomization::CopySetupFilesIntoProject);
SetupForPlatformAttribute = PlatformSetupMessage->GetReadyToGoAttribute();
APKPackagingCategory.AddCustomRow(LOCTEXT("Warning", "Warning"), false)
.WholeRowWidget
[
PlatformSetupMessage
];
APKPackagingCategory.AddCustomRow(LOCTEXT("UpgradeInfo", "Upgrade Info"), false)
.WholeRowWidget
[
SNew(SBorder)
.Padding(1)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(FMargin(10, 10, 10, 10))
.FillWidth(1.0f)
[
SNew(SRichTextBlock)
.Text(LOCTEXT("UpgradeInfoMessage", "<RichTextBlock.TextHighlight>Note to users from 4.6 or earlier</>: We now <RichTextBlock.TextHighlight>GENERATE</> an AndroidManifest.xml when building, so if you have customized your .xml file, you will need to put all of your changes into the below settings. Note that we don't touch your AndroidManifest.xml that is in your project directory.\nAdditionally, we no longer use SigningConfig.xml, the settings are now set in the Distribution Signing section.\n\n<RichTextBlock.TextHighlight>NOTE</>: You must accept the SDK license agreement (click on button below) to use Gradle if it isn't grayed out."))
.TextStyle(FEditorStyle::Get(), "MessageLog")
.DecoratorStyleSet(&FEditorStyle::Get())
.AutoWrapText(true)
+ SRichTextBlock::HyperlinkDecorator(TEXT("browser"), FSlateHyperlinkRun::FOnClick::CreateStatic(&OnBrowserLinkClicked))
]
]
];
APKPackagingCategory.AddCustomRow(LOCTEXT("AndroidSDKLicenses", "Android SDK Licenses"), false)
.WholeRowWidget
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(FMargin(0, 5, 5, 5))
.AutoWidth()
[
SNew(SButton)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.OnClicked(this, &FAndroidTargetSettingsCustomization::OnAcceptSDKLicenseClicked)
.IsEnabled(this, &FAndroidTargetSettingsCustomization::IsLicenseInvalid)
[
SNew(STextBlock)
.Text(LOCTEXT("AcceptSDKLicense", "Accept SDK License"))
]
]
];
APKPackagingCategory.AddCustomRow(LOCTEXT("BuildFolderLabel", "Build Folder"), false)
.IsEnabled(SetupForPlatformAttribute)
.NameContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(FMargin(0, 1, 0, 1))
.FillWidth(1.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("BuildFolderLabel", "Build Folder"))
.Font(DetailLayout.GetDetailFont())
]
]
.ValueContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.Text(LOCTEXT("OpenBuildFolderButton", "Open Build Folder"))
.ToolTipText(LOCTEXT("OpenManifestFolderButton_Tooltip", "Opens the folder containing the build files in Explorer or Finder (it's recommended you check these in to source control to share with your team)"))
.OnClicked(this, &FAndroidTargetSettingsCustomization::OpenBuildFolder)
]
];
SDKConfigCategory.AddCustomRow(LOCTEXT("SDKConfigInfo", "SDK Config Info"), false)
.WholeRowWidget
[
SNew(SBorder)
.Padding(1)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(FMargin(10, 10, 10, 10))
.FillWidth(1.0f)
[
SNew(SRichTextBlock)
.Text(LOCTEXT("SDKConfigMessage", "Leave these fields blank to use global Android SDK project settings. Changing these settings will only affect this project."))
.TextStyle(FEditorStyle::Get(), "MessageLog")
.DecoratorStyleSet(&FEditorStyle::Get())
.AutoWrapText(true)
]
]
];
TSharedRef<IPropertyHandle> SDKAPILevelOverrideProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, SDKAPILevelOverride));
SDKConfigCategory.AddProperty(SDKAPILevelOverrideProperty);
TSharedRef<IPropertyHandle> NDKAPILevelOverrideProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, NDKAPILevelOverride));
SDKConfigCategory.AddProperty(NDKAPILevelOverrideProperty);
// Signing category
SigningCategory.AddCustomRow(LOCTEXT("SigningHyperlink", "Signing Hyperlink"), false)
.WholeRowWidget
[
SNew(SBox)
.HAlign(HAlign_Center)
[
SNew(SHyperlinkLaunchURL, TEXT("http://developer.android.com/tools/publishing/app-signing.html#releasemode"))
.Text(LOCTEXT("AndroidDeveloperSigningPage", "Android Developer page on Signing for Distribution"))
.ToolTipText(LOCTEXT("AndroidDeveloperSigningPageTooltip", "Opens a page that discusses the signing using keytool"))
]
];
// Google Play category
IDetailCategoryBuilder& GooglePlayCategory = DetailLayout.EditCategory(TEXT("GooglePlayServices"));
TSharedRef<SPlatformSetupMessage> GooglePlaySetupMessage = SNew(SPlatformSetupMessage, GameGooglePlayAppIDPath)
.PlatformName(LOCTEXT("GooglePlayPlatformName", "Google Play services"))
.OnSetupClicked(this, &FAndroidTargetSettingsCustomization::CopyGooglePlayAppIDFileIntoProject);
SetupForGooglePlayAttribute = GooglePlaySetupMessage->GetReadyToGoAttribute();
GooglePlayCategory.AddCustomRow(LOCTEXT("Warning", "Warning"), false)
.WholeRowWidget
[
GooglePlaySetupMessage
];
GooglePlayCategory.AddCustomRow(LOCTEXT("AppIDHyperlink", "App ID Hyperlink"), false)
.WholeRowWidget
[
SNew(SBox)
.HAlign(HAlign_Center)
[
SNew(SHyperlinkLaunchURL, TEXT("http://developer.android.com/google/index.html"))
.Text(LOCTEXT("GooglePlayDeveloperPage", "Android Developer Page on Google Play services"))
.ToolTipText(LOCTEXT("GooglePlayDeveloperPageTooltip", "Opens a page that discusses Google Play services"))
]
];
TSharedRef<IPropertyHandle> EnabledProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, bEnableGooglePlaySupport));
GooglePlayCategory.AddProperty(EnabledProperty)
.EditCondition(SetupForGooglePlayAttribute, NULL);
TSharedRef<IPropertyHandle> AppIDProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, GamesAppID));
AppIDProperty->SetOnPropertyValueChanged(FSimpleDelegate::CreateRaw(this, &FAndroidTargetSettingsCustomization::OnAppIDModified));
GooglePlayCategory.AddProperty(AppIDProperty)
.EditCondition(SetupForGooglePlayAttribute, NULL);
TSharedRef<IPropertyHandle> SupportAdMobProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, bSupportAdMob));
GooglePlayCategory.AddProperty(SupportAdMobProperty)
.EditCondition(SetupForGooglePlayAttribute, NULL);
TSharedRef<IPropertyHandle> AdMobAdUnitIDProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, AdMobAdUnitID));
AdMobAdUnitIDProperty->MarkHiddenByCustomization();
TSharedRef<IPropertyHandle> AdMobAdUnitIDsProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, AdMobAdUnitIDs));
GooglePlayCategory.AddProperty(AdMobAdUnitIDsProperty)
.EditCondition(SetupForGooglePlayAttribute, NULL);
TSharedRef<IPropertyHandle> GooglePlayLicenseKeyProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, GooglePlayLicenseKey));
GooglePlayCategory.AddProperty(GooglePlayLicenseKeyProperty)
.EditCondition(SetupForGooglePlayAttribute, NULL);
#define SETUP_ANDROIDARCH_PROP(ArchFragment, PropName, Category, Tip) \
{ \
TSharedRef<IPropertyHandle> PropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, PropName)); \
Category.AddProperty(PropertyHandle) \
.IsEnabled(FInstalledPlatformInfo::Get().IsValidPlatformArchitecture(TEXT("Android"), ArchFragment)) \
.ToolTip(FInstalledPlatformInfo::Get().IsValidPlatformArchitecture(TEXT("Android"), ArchFragment) ? Tip : FAndroidTargetSettingsCustomizationConstants::DisabledTip); \
}
#define SETUP_SOURCEONLY_PROP(PropName, Category, Tip) \
{ \
TSharedRef<IPropertyHandle> PropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, PropName)); \
Category.AddProperty(PropertyHandle) \
.IsEnabled(FEngineBuildSettings::IsSourceDistribution()) \
.ToolTip(FEngineBuildSettings::IsSourceDistribution() ? Tip : FAndroidTargetSettingsCustomizationConstants::DisabledTip); \
}
SETUP_ANDROIDARCH_PROP(TEXT("-armv7"), bBuildForArmV7, BuildCategory, LOCTEXT("BuildForArmV7ToolTip", "Enable ArmV7 CPU architecture support? (this will be used if all CPU architecture types are unchecked)"));
SETUP_ANDROIDARCH_PROP(TEXT("-arm64"), bBuildForArm64, BuildCategory, LOCTEXT("BuildForArm64ToolTip", "Enable Arm64 CPU architecture support? (use at least NDK r11c, requires Lollipop (android-21) minimum)"));
// SETUP_ANDROIDARCH_PROP(TEXT("-x86"), bBuildForX86, BuildCategory, LOCTEXT("BuildForX86ToolTip", "Enable X86 CPU architecture support?"));
SETUP_ANDROIDARCH_PROP(TEXT("-x64"), bBuildForX8664, BuildCategory, LOCTEXT("BuildForX8664ToolTip", "Enable X86-64 CPU architecture support?"));
// @todo android fat binary: Put back in when we expose those
// SETUP_SOURCEONLY_PROP(bSplitIntoSeparateApks, BuildCategory, LOCTEXT("SplitIntoSeparateAPKsToolTip", "If checked, CPU architectures and rendering types will be split into separate .apk files"));
// check for Gradle change
TSharedRef<IPropertyHandle> EnableGradleProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, bEnableGradle));
EnableGradleProperty->MarkHiddenByCustomization();
//FSimpleDelegate EnableGradleChange = FSimpleDelegate::CreateSP(this, &FAndroidTargetSettingsCustomization::OnEnableGradleChange);
//EnableGradleProperty->SetOnPropertyValueChanged(EnableGradleChange);
// check for GoogleVR change
TSharedRef<IPropertyHandle> GoogleVRCapsProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, GoogleVRCaps));
FSimpleDelegate GoogleVRCapsChange = FSimpleDelegate::CreateSP(this, &FAndroidTargetSettingsCustomization::OnGoogleVRCapsChange);
GoogleVRCapsProperty->SetOnPropertyValueChanged(GoogleVRCapsChange);
}
bool FAndroidTargetSettingsCustomization::IsLicenseInvalid() const
{
static bool bInvalid = true;
// only check every 30 seconds after first time
double CurrentTime = FApp::GetCurrentTime();
if (LastLicenseChecktime < 0.0 || CurrentTime - LastLicenseChecktime >= 30.0)
{
const_cast<FAndroidTargetSettingsCustomization *>(this)->LastLicenseChecktime = CurrentTime;
TSharedPtr<SAndroidLicenseDialog> LicenseDialog = SNew(SAndroidLicenseDialog);
bInvalid = !LicenseDialog->HasLicense();
}
return bInvalid;
}
void FAndroidTargetSettingsCustomization::OnLicenseAccepted()
{
LastLicenseChecktime = -1.0;
}
FReply FAndroidTargetSettingsCustomization::OnAcceptSDKLicenseClicked()
{
// only show if don't have a valid license
TSharedPtr<SAndroidLicenseDialog> LicenseDialog = SNew(SAndroidLicenseDialog);
if (!LicenseDialog->HasLicense())
{
FSimpleDelegate LicenseAcceptedCallback = FSimpleDelegate::CreateSP(this, &FAndroidTargetSettingsCustomization::OnLicenseAccepted);
LicenseDialog->SetLicenseAcceptedCallback(LicenseAcceptedCallback);
const FText AndroidLicenseWindowTitle = LOCTEXT("AndroidLicenseUnrealEditor", "Android SDK License");
TSharedPtr<SWindow> AndroidLicenseWindow =
SNew(SWindow)
.Title(AndroidLicenseWindowTitle)
.ClientSize(FVector2D(600.f, 700.f))
.SupportsMaximize(false)
.SupportsMinimize(false)
.SizingRule(ESizingRule::FixedSize)
[
LicenseDialog.ToSharedRef()
];
IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
TSharedPtr<SWindow> ParentWindow = MainFrame.GetParentWindow();
if (ParentWindow.IsValid())
{
FSlateApplication::Get().AddModalWindow(AndroidLicenseWindow.ToSharedRef(), ParentWindow.ToSharedRef());
}
else
{
FSlateApplication::Get().AddWindow(AndroidLicenseWindow.ToSharedRef());
}
}
LastLicenseChecktime = -1.0;
return FReply::Handled();
}
void FAndroidTargetSettingsCustomization::OnGoogleVRCapsChange()
{
/* Doing this isn't really useful since has no effect if plugin isn't also enabled
Better to just have a warning in the log during packaging (and it isn't as expensive now)
const TArray<TEnumAsByte<EGoogleVRCaps::Type>> &GoogleCaps = GetDefault<UAndroidRuntimeSettings>()->GoogleVRCaps;
bool bIsDaydream = GoogleCaps.Contains(EGoogleVRCaps::Daydream33) || GoogleCaps.Contains(EGoogleVRCaps::Daydream63) || GoogleCaps.Contains(EGoogleVRCaps::Daydream66);
if (bIsDaydream && GetDefault<UAndroidRuntimeSettings>()->bAllowIMU)
{
// turn off IMU for Daydream (but user can turn it back on
GetMutableDefault<UAndroidRuntimeSettings>()->bAllowIMU = false;
}
*/
}
void FAndroidTargetSettingsCustomization::OnEnableGradleChange()
{
// only need to do this if enabling
if (!GetDefault<UAndroidRuntimeSettings>()->bEnableGradle)
{
return;
}
// only show if don't have a valid license
TSharedPtr<SAndroidLicenseDialog> LicenseDialog = SNew(SAndroidLicenseDialog);
if (!LicenseDialog->HasLicense())
{
FSimpleDelegate LicenseAcceptedCallback = FSimpleDelegate::CreateSP(this, &FAndroidTargetSettingsCustomization::OnLicenseAccepted);
LicenseDialog->SetLicenseAcceptedCallback(LicenseAcceptedCallback);
const FText AndroidLicenseWindowTitle = LOCTEXT("AndroidLicenseUnrealEditor", "Android SDK License");
TSharedPtr<SWindow> AndroidLicenseWindow =
SNew(SWindow)
.Title(AndroidLicenseWindowTitle)
.ClientSize(FVector2D(600.f, 700.f))
.HasCloseButton(false)
.SupportsMaximize(false)
.SupportsMinimize(false)
.SizingRule(ESizingRule::FixedSize)
[
LicenseDialog.ToSharedRef()
];
IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
TSharedPtr<SWindow> ParentWindow = MainFrame.GetParentWindow();
if (ParentWindow.IsValid())
{
FSlateApplication::Get().AddModalWindow(AndroidLicenseWindow.ToSharedRef(), ParentWindow.ToSharedRef());
}
else
{
FSlateApplication::Get().AddWindow(AndroidLicenseWindow.ToSharedRef());
}
}
}
void FAndroidTargetSettingsCustomization::BuildIconSection(IDetailLayoutBuilder& DetailLayout)
{
// Icon category
IDetailCategoryBuilder& IconCategory = DetailLayout.EditCategory(TEXT("Icons"));
IconCategory.AddCustomRow(LOCTEXT("IconsHyperlink", "Icons Hyperlink"), false)
.WholeRowWidget
[
SNew(SBox)
.HAlign(HAlign_Center)
[
SNew(SHyperlinkLaunchURL, TEXT("http://developer.android.com/design/style/iconography.html"))
.Text(LOCTEXT("AndroidDeveloperIconographyPage", "Android Developer Page on Iconography"))
.ToolTipText(LOCTEXT("AndroidDeveloperIconographyPageTooltip", "Opens a page on Android Iconography"))
]
];
for (const FPlatformIconInfo& Info : IconNames)
{
const FString AutomaticImagePath = EngineAndroidPath / Info.IconPath;
const FString TargetImagePath = GameAndroidPath / Info.IconPath;
IconCategory.AddCustomRow(Info.IconName)
.NameContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.Padding( FMargin( 0, 1, 0, 1 ) )
.FillWidth(1.0f)
[
SNew(STextBlock)
.Text(Info.IconName)
.Font(DetailLayout.GetDetailFont())
]
]
.ValueContent()
.MaxDesiredWidth(400.0f)
.MinDesiredWidth(100.0f)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
[
SNew(SExternalImageReference, AutomaticImagePath, TargetImagePath)
.FileDescription(Info.IconDescription)
.RequiredSize(Info.IconRequiredSize)
.MaxDisplaySize(FVector2D(FMath::Min(96, Info.IconRequiredSize.X), FMath::Min(96, Info.IconRequiredSize.Y)))
]
];
}
}
void FAndroidTargetSettingsCustomization::BuildLaunchImageSection(IDetailLayoutBuilder& DetailLayout)
{
// Add the launch images
IDetailCategoryBuilder& LaunchImageCategory = DetailLayout.EditCategory(TEXT("LaunchImages"));
LaunchImageCategory.AddCustomRow(LOCTEXT("LaunchImageInfo", "Launch Image Info"), false)
.WholeRowWidget
[
SNew(SBorder)
.Padding(1)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(FMargin(10, 10, 10, 10))
.FillWidth(1.0f)
[
SNew(SRichTextBlock)
.Text(LOCTEXT("LaunchImageInfoMessage", "The <RichTextBlock.TextHighlight>Download Background</> image is used as the background for OBB downloading. The <RichTextBlock.TextHighlight>Launch Portrait</> image is used as a splash screen for applications with Portrait, Reverse Portrait, Sensor Portrait, Sensor, or Full Sensor orientation. The <RichTextBlock.TextHighlight>Launch Landscape</> image is used as a splash screen for applications with Landscape, Sensor Landscape, Reverse Landscape, Sensor, or Full Sensor orientation.\n\nThe launch images will be scaled to fit the device in the active orientation. Additional optional launch images may be provided as overrides for LDPI, MDPI, HDPI, and XHDPI by placing them in the project's corresponding Build/Android/res/drawable-* directory."))
.TextStyle(FEditorStyle::Get(), "MessageLog")
.DecoratorStyleSet(&FEditorStyle::Get())
.AutoWrapText(true)
+ SRichTextBlock::HyperlinkDecorator(TEXT("browser"), FSlateHyperlinkRun::FOnClick::CreateStatic(&OnBrowserLinkClicked))
]
]
];
const FVector2D LaunchImageMaxSize(150.0f, 150.0f);
for (const FPlatformIconInfo& Info : LaunchImageNames)
{
const FString AutomaticImagePath = EngineAndroidPath / Info.IconPath;
const FString TargetImagePath = GameAndroidPath / Info.IconPath;
LaunchImageCategory.AddCustomRow(Info.IconName)
.NameContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(FMargin(0, 1, 0, 1))
.FillWidth(1.0f)
[
SNew(STextBlock)
.Text(Info.IconName)
.Font(DetailLayout.GetDetailFont())
]
]
.ValueContent()
.MaxDesiredWidth(400.0f)
.MinDesiredWidth(100.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
[
SNew(SExternalImageReference, AutomaticImagePath, TargetImagePath)
.FileDescription(Info.IconDescription)
// .RequiredSize(Info.IconRequiredSize)
.MaxDisplaySize(LaunchImageMaxSize)
]
];
}
}
void FAndroidTargetSettingsCustomization::BuildDaydreamAppTileImageSection(IDetailLayoutBuilder& DetailLayout)
{
// Daydream App Tile Category
IDetailCategoryBuilder& DaydreamAppTileCategory = DetailLayout.EditCategory(TEXT("DaydreamAppTile"));
for (const FPlatformIconInfo& Info : DaydreamAppTileImageNames)
{
const FString AutomaticImagePath = EngineAndroidPath / Info.IconPath;
const FString TargetImagePath = GameAndroidPath / Info.IconPath;
DaydreamAppTileCategory.AddCustomRow(Info.IconName)
.NameContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.Padding( FMargin( 0, 1, 0, 1 ) )
.FillWidth(1.0f)
[
SNew(STextBlock)
.Text(Info.IconName)
.Font(DetailLayout.GetDetailFont())
]
]
.ValueContent()
.MaxDesiredWidth(400.0f)
.MinDesiredWidth(100.0f)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
[
SNew(SExternalImageReference, AutomaticImagePath, TargetImagePath)
.FileDescription(Info.IconDescription)
.RequiredSize(Info.IconRequiredSize)
.MaxDisplaySize(FVector2D(FMath::Min(96, Info.IconRequiredSize.X), FMath::Min(96, Info.IconRequiredSize.Y)))
]
];
}
}
FReply FAndroidTargetSettingsCustomization::OpenBuildFolder()
{
const FString BuildFolder = FPaths::ConvertRelativePathToFull(FPaths::GetPath(GameProjectPropertiesPath));
FPlatformProcess::ExploreFolder(*BuildFolder);
return FReply::Handled();
}
void FAndroidTargetSettingsCustomization::CopySetupFilesIntoProject()
{
// First copy the manifest, it must get copied
FText ErrorMessage;
if (!SourceControlHelpers::CopyFileUnderSourceControl(GameProjectPropertiesPath, EngineProjectPropertiesPath, LOCTEXT("ProjectProperties", "Project Properties"), /*out*/ ErrorMessage))
{
FNotificationInfo Info(ErrorMessage);
Info.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(Info);
}
else
{
// Now try to copy all of the icons, etc... (these can be ignored if the file already exists)
for (const FPlatformIconInfo& Info : IconNames)
{
const FString EngineImagePath = EngineAndroidPath / Info.IconPath;
const FString ProjectImagePath = GameAndroidPath / Info.IconPath;
if (!FPaths::FileExists(ProjectImagePath))
{
SourceControlHelpers::CopyFileUnderSourceControl(ProjectImagePath, EngineImagePath, Info.IconName, /*out*/ ErrorMessage);
}
}
// Now try to copy all of the launch images... (these can be ignored if the file already exists)
for (const FPlatformIconInfo& Info : LaunchImageNames)
{
const FString EngineImagePath = EngineAndroidPath / Info.IconPath;
const FString ProjectImagePath = GameAndroidPath / Info.IconPath;
if (!FPaths::FileExists(ProjectImagePath))
{
SourceControlHelpers::CopyFileUnderSourceControl(ProjectImagePath, EngineImagePath, Info.IconName, /*out*/ ErrorMessage);
}
}
// Now try to copy all of the launch images... (these can be ignored if the file already exists)
for (const FPlatformIconInfo& Info : DaydreamAppTileImageNames)
{
const FString EngineImagePath = EngineAndroidPath / Info.IconPath;
const FString ProjectImagePath = GameAndroidPath / Info.IconPath;
if (!FPaths::FileExists(ProjectImagePath))
{
SourceControlHelpers::CopyFileUnderSourceControl(ProjectImagePath, EngineImagePath, Info.IconName, /*out*/ ErrorMessage);
}
}
// and copy the other files (aren't required)
//SourceControlHelpers::CopyFileUnderSourceControl(GameProguardPath, EngineProguardPath, LOCTEXT("Proguard", "Proguard Settings"), /*out*/ ErrorMessage);
}
SavedLayoutBuilder->ForceRefreshDetails();
}
void FAndroidTargetSettingsCustomization::CopyGooglePlayAppIDFileIntoProject()
{
FText ErrorMessage;
if (!SourceControlHelpers::CopyFileUnderSourceControl(GameGooglePlayAppIDPath, EngineGooglePlayAppIDPath, LOCTEXT("GooglePlayAppID", "GooglePlayAppID.xml"), /*out*/ ErrorMessage))
{
FNotificationInfo Info(ErrorMessage);
Info.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(Info);
}
SavedLayoutBuilder->ForceRefreshDetails();
}
void FAndroidTargetSettingsCustomization::OnAppIDModified()
{
const FString NewIDString = GetDefault<UAndroidRuntimeSettings>()->GamesAppID;
if (NewIDString.Len() > 0 && !FCString::IsNumeric(*NewIDString))
{
FNotificationInfo Info(LOCTEXT("InvalidGamesAppID", "The Games App ID you provided is invalid"));
Info.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(Info);
return;
}
if (FPaths::FileExists(GameGooglePlayAppIDPath))
{
FManifestUpdateHelper Updater(GameGooglePlayAppIDPath);
const FString AppIDTag(TEXT("name=\"app_id\">"));
const FString ClosingTag(TEXT("</string>"));
Updater.ReplaceKey(AppIDTag, ClosingTag, NewIDString);
Updater.Finalize(GameGooglePlayAppIDPath);
}
}
static EVisibility GraphicsDebuggerSettingsVisibility(EAndroidGraphicsDebugger::Type DebuggerType, TSharedPtr<IPropertyHandle> AndroidGraphicsDebuggerProperty)
{
uint8 ValueAsByte = 0;
FPropertyAccess::Result Result = AndroidGraphicsDebuggerProperty->GetValue(ValueAsByte);
if (Result == FPropertyAccess::Success && ValueAsByte == static_cast<uint8>(DebuggerType))
{
return EVisibility::Visible;
}
return EVisibility::Hidden;
}
static FText GetMaliGraphicsDebuggerHelpText()
{
const static FText InstallText(LOCTEXT("MGDInstallText", "Run the following command from a host command line from the target/unrooted directory located in the installation directory of the MGD tool, to install the MGD Daemon application on your device."));
const static FString InstallCommand(TEXT("adb install -r MGD.apk"));
const static FText RunText1(LOCTEXT("MGDIRunText1", "Run the following command from your host to establish a tunnel between your PC and the MGD Daemon. This needs to be done each time you connect your device by USB."));
const static FString RunCommand(TEXT("adb forward tcp:5002 tcp:5002"));
const static FText RunText2(LOCTEXT("MGDIRunText2", "Next, ensure you are running the daemon. Run the MGD Daemon application and switch it to the \"ON\" state"));
FFormatOrderedArguments Args;
Args.Add(InstallText);
Args.Add(FText::FromString(InstallCommand));
Args.Add(RunText1);
Args.Add(FText::FromString(RunCommand));
Args.Add(RunText2);
return FText::Format(LOCTEXT("MaliGraphicsDebuggerHelpText","<RichTextBlock.TextHighlight>Installation</>\n{0}\n{1}\n\n<RichTextBlock.TextHighlight>Run</>\n{2}\n{3}\n{4}"),
Args);
}
static FText GetAdrenoProfilerHelpText()
{
const static FText RunText(LOCTEXT("AdrenoRunText", "Before profiling, and after rebooting your Android device, you must enable debug mode by setting the following property from the command line:"));
const static FString RunCommand(TEXT("adb shell setprop debug.egl.profiler 1"));
FFormatOrderedArguments Args;
Args.Add(RunText);
Args.Add(FText::FromString(RunCommand));
return FText::Format(LOCTEXT("AdrenoHelpText","{0}\n{1}"), Args);
}
void FAndroidTargetSettingsCustomization::BuildGraphicsDebuggerSection(IDetailLayoutBuilder& DetailLayout)
{
IDetailCategoryBuilder& GraphicsDebuggerCategory = DetailLayout.EditCategory(TEXT("GraphicsDebugger"));
TSharedPtr<IPropertyHandle> AndroidGraphicsDebuggerProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, AndroidGraphicsDebugger));
GraphicsDebuggerCategory.AddProperty(AndroidGraphicsDebuggerProperty);
// Mali Graphics Debugger settings
{
TAttribute<EVisibility> MaliSettingsVisibility(
TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateStatic(GraphicsDebuggerSettingsVisibility, EAndroidGraphicsDebugger::Mali, AndroidGraphicsDebuggerProperty))
);
TSharedPtr<IPropertyHandle> MaliGraphicsDebuggerPathProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, MaliGraphicsDebuggerPath));
DetailLayout.HideProperty(MaliGraphicsDebuggerPathProperty);
GraphicsDebuggerCategory.AddProperty(MaliGraphicsDebuggerPathProperty).Visibility(MaliSettingsVisibility);
FText MGDHelpText = GetMaliGraphicsDebuggerHelpText();
GraphicsDebuggerCategory.AddCustomRow(LOCTEXT("MaliGraphicsDebuggerInfo", "Mali Graphics Debugger Info"), false)
.Visibility(MaliSettingsVisibility)
.WholeRowWidget
[
SNew(SBorder)
.Padding(1)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.Padding(FMargin(10, 10, 10, 10))
.AutoHeight()
[
SNew(SRichTextBlock)
.Text(MGDHelpText)
.TextStyle(FEditorStyle::Get(), "MessageLog")
.DecoratorStyleSet(&FEditorStyle::Get())
.AutoWrapText(true)
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(10, 10, 10, 10))
[
SNew(SBox)
.HAlign(HAlign_Left)
[
SNew(SHyperlinkLaunchURL, TEXT("http://malideveloper.arm.com/resources/tools/mali-graphics-debugger/"))
.Text(LOCTEXT("MaliGraphicsDebuggerPage", "Mali Graphics Debugger home page"))
.ToolTipText(LOCTEXT("MaliGraphicsDebuggerPageTooltip", "Opens the Mali Graphics Debugger home page on ARM's website"))
]
]
]
];
}
// Adreno Profiler settings
{
TAttribute<EVisibility> AdrenoSettingsVisibility(
TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateStatic(GraphicsDebuggerSettingsVisibility, EAndroidGraphicsDebugger::Adreno, AndroidGraphicsDebuggerProperty))
);
FText AdrenoHelpText = GetAdrenoProfilerHelpText();
GraphicsDebuggerCategory.AddCustomRow(LOCTEXT("AdrenoProfilerInfo", "Adreno Profiler Info"), false)
.Visibility(AdrenoSettingsVisibility)
.WholeRowWidget
[
SNew(SBorder)
.Padding(1)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.Padding(FMargin(10, 10, 10, 10))
.AutoHeight()
[
SNew(SRichTextBlock)
.Text(AdrenoHelpText)
.TextStyle(FEditorStyle::Get(), "MessageLog")
.DecoratorStyleSet(&FEditorStyle::Get())
.AutoWrapText(true)
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(10, 10, 10, 10))
[
SNew(SBox)
.HAlign(HAlign_Left)
[
SNew(SHyperlinkLaunchURL, TEXT("https://developer.qualcomm.com/software/adreno-gpu-profiler"))
.Text(LOCTEXT("AdrenoProfilerPage", "Adreno Profiler home page"))
.ToolTipText(LOCTEXT("AdrenoProfilerPageTooltip", "Opens the Adreno Profiler home page on the Qualcomm website"))
]
]
]
];
}
}
//////////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE
@@ -1,92 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Misc/Attribute.h"
#include "Input/Reply.h"
#include "IDetailCustomization.h"
#include "PropertyHandle.h"
#include "TargetPlatformAudioCustomization.h"
class IDetailLayoutBuilder;
//////////////////////////////////////////////////////////////////////////
// FAndroidTargetSettingsCustomization
class FAndroidTargetSettingsCustomization : public IDetailCustomization
{
public:
// Makes a new instance of this detail layout class for a specific detail view requesting it
static TSharedRef<IDetailCustomization> MakeInstance();
// IDetailCustomization interface
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailLayout) override;
// End of IDetailCustomization interface
private:
FAndroidTargetSettingsCustomization();
void BuildAppManifestSection(IDetailLayoutBuilder& DetailLayout);
void BuildIconSection(IDetailLayoutBuilder& DetailLayout);
void BuildLaunchImageSection(IDetailLayoutBuilder& DetailLayout);
void BuildDaydreamAppTileImageSection(IDetailLayoutBuilder& DetailLayout);
void BuildGraphicsDebuggerSection(IDetailLayoutBuilder& DetailLayout);
// Navigates to the build files in the explorer or finder
FReply OpenBuildFolder();
// Copies the setup files for the platform into the project
void CopySetupFilesIntoProject();
// Copies the strings.xml file for the platform into the project
void CopyGooglePlayAppIDFileIntoProject();
// Called when the app id is modified
void OnAppIDModified();
// Called when GoogleVRCaps is modified
void OnGoogleVRCapsChange();
// Called when EnableGradle is modified
void OnEnableGradleChange();
// Called when License accepted
void OnLicenseAccepted();
// returns whether Android SDK license valid
bool IsLicenseInvalid() const;
// Show license agreement for user to accept
FReply OnAcceptSDKLicenseClicked();
private:
double LastLicenseChecktime;
const FString AndroidRelativePath;
const FString EngineAndroidPath;
const FString GameAndroidPath;
const FString EngineGooglePlayAppIDPath;
const FString GameGooglePlayAppIDPath;
const FString EngineProguardPath;
const FString GameProguardPath;
const FString EngineProjectPropertiesPath;
const FString GameProjectPropertiesPath;
TArray<struct FPlatformIconInfo> IconNames;
TArray<struct FPlatformIconInfo> LaunchImageNames;
TArray<struct FPlatformIconInfo> DaydreamAppTileImageNames;
// Is the manifest writable?
TAttribute<bool> SetupForPlatformAttribute;
// Is the App ID string writable?
TAttribute<bool> SetupForGooglePlayAttribute;
FAudioPluginWidgetManager AudioPluginWidgetManager;
IDetailLayoutBuilder* SavedLayoutBuilder;
};
@@ -1,45 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class AndroidTargetPlatform : ModuleRules
{
public AndroidTargetPlatform(ReadOnlyTargetRules Target) : base(Target)
{
BinariesSubFolder = "Android";
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"TargetPlatform",
"DesktopPlatform",
"AndroidDeviceDetection",
"AudioPlatformConfiguration"
}
);
PublicIncludePaths.AddRange(
new string[]
{
"Runtime/Core/Public/Android"
}
);
if (Target.bCompileAgainstEngine)
{
PrivateDependencyModuleNames.Add("Engine");
PrivateIncludePathModuleNames.Add("TextureCompressor"); //@todo android: AndroidTargetPlatform.Build
}
PublicDefinitions.Add("WITH_OGGVORBIS=1");
PrivateIncludePaths.AddRange(
new string[]
{
}
);
}
}
@@ -1,261 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AndroidTargetDevice.h: Declares the AndroidTargetDevice class.
=============================================================================*/
#pragma once
#include "CoreTypes.h"
#include "Containers/UnrealString.h"
#include "Templates/SharedPointer.h"
#include "CoreMinimal.h"
#include "HAL/PlatformProcess.h"
class FAndroidTargetDevice;
class FTargetDeviceId;
class ITargetDevice;
class ITargetPlatform;
struct FTargetDeviceProcessInfo;
enum class ETargetDeviceFeatures;
enum class ETargetDeviceTypes;
/**
* Type definition for shared pointers to instances of FAndroidTargetDevice.
*/
typedef TSharedPtr<class FAndroidTargetDevice, ESPMode::ThreadSafe> FAndroidTargetDevicePtr;
/**
* Type definition for shared references to instances of FAndroidTargetDevice.
*/
typedef TSharedRef<class FAndroidTargetDevice, ESPMode::ThreadSafe> FAndroidTargetDeviceRef;
/**
* Type definition for shared references to instances of FAndroidTargetDevice.
*/
typedef TSharedPtr<class FAndroidTargetDeviceOutput, ESPMode::ThreadSafe> FAndroidTargetDeviceOutputPtr;
/**
* Implements a Android target device.
*/
class FAndroidTargetDevice : public ITargetDevice
{
public:
/**
* Creates and initializes a new Android target device.
*
* @param InTargetPlatform - The target platform.
* @param InSerialNumber - The ADB serial number of the target device.
* @param InAndroidVariant - The variant of the Android platform, i.e. ETC2, DXT or ASTC.
*/
FAndroidTargetDevice(const ITargetPlatform& InTargetPlatform, const FString& InSerialNumber, const FString& InAndroidVariant)
: AndroidVariant(InAndroidVariant)
, bConnected(false)
, bIsDeviceAuthorized(false)
, AndroidSDKVersion(INDEX_NONE)
, DeviceName(InSerialNumber)
, Model(InSerialNumber)
, SerialNumber(InSerialNumber)
, TargetPlatform(InTargetPlatform)
{ }
public:
/**
* Sets the device's connection state.
*
* @param bInConnected - Whether the device is connected.
*/
void SetConnected(bool bInConnected)
{
bConnected = bInConnected;
}
/**
* Sets the device's authorization state.
*
* @param bInConnected - Whether the device is authorized for USB communications.
*/
void SetAuthorized(bool bInIsAuthorized)
{
bIsDeviceAuthorized = bInIsAuthorized;
}
/**
* Sets the device's OS/SDK versions.
*
* @param InSDKVersion - Android SDK version of the device.
* @param InReleaseVersion - Android Release (human-readable) version of the device.
*/
void SetVersions(int32 InSDKVersion, const FString& InReleaseVersion)
{
AndroidSDKVersion = InSDKVersion;
AndroidVersionString = InReleaseVersion;
}
/**
* Sets the device name.
*
* @param InDeviceName - The device name to set.
*/
void SetDeviceName(const FString& InDeviceName)
{
DeviceName = InDeviceName;
}
/**
* Sets the device name.
*
* @param InDeviceName - The device name to set.
*/
void SetModel(const FString& InNodel)
{
Model = InNodel;
}
FString GetSerialNumber() const
{
return SerialNumber;
}
public:
//~ Begin ITargetDevice Interface
virtual bool Connect() override
{
return true;
}
virtual bool Deploy(const FString& SourceFolder, FString& OutAppId) override;
virtual void Disconnect() override
{
}
virtual ETargetDeviceTypes GetDeviceType() const override
{
//@TODO: How to distinguish between a Tablet and a Phone (or a TV microconsole, etc...), and is it important?
return ETargetDeviceTypes::Tablet;
}
virtual FTargetDeviceId GetId() const override
{
return FTargetDeviceId(TargetPlatform.PlatformName(), SerialNumber);
}
virtual FString GetName() const override
{
// we need a unique name for all devices, so use human usable model name and the unique id
return FString::Printf(TEXT("%s (%s)"), *Model, *SerialNumber);
}
virtual FString GetOperatingSystemName() override;
virtual int32 GetProcessSnapshot( TArray<FTargetDeviceProcessInfo>& OutProcessInfos ) override;
virtual const class ITargetPlatform& GetTargetPlatform( ) const override
{
return TargetPlatform;
}
virtual bool IsConnected() override
{
return bConnected;
}
virtual bool IsDefault() const override
{
return true;
}
virtual bool IsAuthorized() const override
{
return bIsDeviceAuthorized;
}
virtual bool PowerOff(bool Force) override;
virtual bool PowerOn() override
{
return true;
}
// Return true if the devices can be grouped in an aggregate (All_<platform>_devices_on_<host>) proxy
virtual bool IsPlatformAggregated() const override
{
return true;
}
// the name of the aggregate (All_<platform>_devices_on_<host>) proxy
virtual FString GetAllDevicesName() const override;
// the default variant (texture compression) of the aggregate (All_<platform>_devices_on_<host>) proxy
virtual FName GetAllDevicesDefaultVariant() const override
{
// The Android platform has an aggregate (All_<platform>_devices_on_<host>) entry in the Project Launcher
// Multi is the default texture format
return "Android_Multi";
}
virtual bool Launch(const FString& AppId, EBuildConfiguration BuildConfiguration, EBuildTargetType TargetType, const FString& Params, uint32* OutProcessId);
virtual bool Reboot(bool bReconnect = false) override;
virtual bool Run(const FString& ExecutablePath, const FString& Params, uint32* OutProcessId) override;
virtual bool TerminateLaunchedProcess(const FString& ProcessIdentifier) override;
virtual bool SupportsFeature(ETargetDeviceFeatures Feature) const override;
virtual bool SupportsSdkVersion(const FString& VersionString) const override;
virtual bool TerminateProcess(const int64 ProcessId) override;
virtual void SetUserCredentials(const FString& UserName, const FString& UserPassword) override;
virtual bool GetUserCredentials(FString& OutUserName, FString& OutUserPassword) override;
virtual void ExecuteConsoleCommand(const FString& ExecCommand) const override;
virtual ITargetDeviceOutputPtr CreateDeviceOutputRouter(FOutputDevice* Output) const override;
//~ End ITargetDevice Interface
/** Full filename for ADB executable. */
static bool GetAdbFullFilename(FString& OutFilename);
protected:
/**
* Executes an SDK command with the specified command line on this device only using ADB.
*
* @param Params - The command line parameters.
* @param OutStdOut - Optional pointer to a string that will hold the command's output log.
* @param OutStdErr - Optional pointer to a string that will hold the error message, if any.
*
* @return true on success, false otherwise.
*/
bool ExecuteAdbCommand( const FString& Params, FString* OutStdOut, FString* OutStdErr ) const;
protected:
// The variant of the Android platform, i.e. ETC2, DXT or ASTC.
FString AndroidVariant;
// Holds a flag indicating whether the device is currently connected.
bool bConnected;
// Holds a flag indicating whether the device is USB comms authorized (if not, most other values aren't valid but we still want to show the device as detected but unready)
bool bIsDeviceAuthorized;
// Holds the Android SDK version
int32 AndroidSDKVersion;
// Holds the Android Release version string (e.g., "2.3" or "4.2.2")
FString AndroidVersionString;
// Holds the device name.
FString DeviceName;
// Holds the device model.
FString Model;
// Holds the serial number (from ADB devices) of this target device.
FString SerialNumber;
// Holds a reference to the device's target platform.
const ITargetPlatform& TargetPlatform;
};
#include "AndroidTargetDevice.inl"
@@ -1,216 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/* ITargetDevice interface
*****************************************************************************/
#include "CoreTypes.h"
#include "Containers/UnrealString.h"
#include "Misc/Optional.h"
#include "HAL/PlatformProcess.h"
#include "AndroidTargetDeviceOutput.h"
class FAndroidTargetDevice;
struct FTargetDeviceProcessInfo;
enum class ETargetDeviceFeatures;
template<typename OptionalType> struct TOptional;
inline bool FAndroidTargetDevice::Deploy( const FString& SourceFolder, FString& OutAppId )
{
int32 ReturnCode = 0;
//void* RunningProc = NULL;
// run the packager to create and install the .apk
// @todo android: install separately?
FString RepackageCommand(FString(TEXT("AndroidPackager ")) + OutAppId + FString(TEXT(" AndroidARMv7 ")) + FString(TEXT("Debug")));
FProcHandle RunningProc = FPlatformProcess::CreateProc(TEXT("../DotNET/Android/AndroidPackager"), *RepackageCommand, true, false, false, NULL, 0, TEXT("../DotNET/"), NULL);
FPlatformProcess::WaitForProc(RunningProc);
FPlatformProcess::GetProcReturnCode(RunningProc, &ReturnCode);
FPlatformProcess::CloseProc(RunningProc);
return 0 == ReturnCode;
}
inline FString FAndroidTargetDevice::GetOperatingSystemName()
{
if (!AndroidVersionString.IsEmpty())
{
return FString::Printf(TEXT("Android %s, API level %d"), *AndroidVersionString, AndroidSDKVersion);
}
else
{
return TEXT("Android");
}
}
inline int32 FAndroidTargetDevice::GetProcessSnapshot( TArray<FTargetDeviceProcessInfo>& OutProcessInfos )
{
return 0;
}
inline bool FAndroidTargetDevice::Launch( const FString& AppId, EBuildConfiguration BuildConfiguration, EBuildTargetType TargetType, const FString& Params, uint32* OutProcessId )
{
// this isn't used, UAT handles it all
return false;
}
inline bool FAndroidTargetDevice::Reboot( bool bReconnect )
{
if (!ExecuteAdbCommand(TEXT("reboot"), NULL, NULL))
{
return false;
}
return true;
}
inline bool FAndroidTargetDevice::PowerOff( bool Force )
{
if (!ExecuteAdbCommand(TEXT("reboot --poweroff"), NULL, NULL))
{
return false;
}
return true;
}
inline FString FAndroidTargetDevice::GetAllDevicesName() const
{
return FString::Printf(TEXT("All_%s_On_%s"), *(GetTargetPlatform().IniPlatformName()), FPlatformProcess::ComputerName());
}
inline bool FAndroidTargetDevice::Run( const FString& ExecutablePath, const FString& Params, uint32* OutProcessId )
{
// @todo android: how to run from this?
return false;
}
// cancel the running application
inline bool FAndroidTargetDevice::TerminateLaunchedProcess(const FString& ProcessIdentifier)
{
FString AdbCommand = FString::Printf(TEXT("shell am force-stop '%s' -s %s"), *ProcessIdentifier, *SerialNumber);
return ExecuteAdbCommand(AdbCommand, nullptr, nullptr);
}
inline bool FAndroidTargetDevice::SupportsFeature( ETargetDeviceFeatures Feature ) const
{
switch (Feature)
{
case ETargetDeviceFeatures::PowerOff:
return true;
case ETargetDeviceFeatures::PowerOn:
return false;
case ETargetDeviceFeatures::Reboot:
return true;
default:
return false;
}
}
inline bool FAndroidTargetDevice::SupportsSdkVersion( const FString& VersionString ) const
{
return true;
}
inline bool FAndroidTargetDevice::TerminateProcess( const int64 ProcessId )
{
return false;
}
inline void FAndroidTargetDevice::SetUserCredentials( const FString& UserName, const FString& UserPassword )
{
}
inline bool FAndroidTargetDevice::GetUserCredentials( FString& OutUserName, FString& OutUserPassword )
{
return false;
}
inline void FAndroidTargetDevice::ExecuteConsoleCommand(const FString& ExecCommand) const
{
FString AdbCommand = FString::Printf(TEXT("shell \"am broadcast -a android.intent.action.RUN -e cmd '%s'\""), *ExecCommand);
ExecuteAdbCommand(AdbCommand, nullptr, nullptr);
}
inline ITargetDeviceOutputPtr FAndroidTargetDevice::CreateDeviceOutputRouter(FOutputDevice* Output) const
{
FAndroidTargetDeviceOutputPtr DeviceOutputPtr = MakeShareable(new FAndroidTargetDeviceOutput());
if (DeviceOutputPtr->Init(*this, Output))
{
return DeviceOutputPtr;
}
return nullptr;
}
/* FAndroidTargetDevice implementation
*****************************************************************************/
inline bool FAndroidTargetDevice::GetAdbFullFilename(FString& OutFilename)
{
TOptional<FString> ResultPath;
// get the SDK binaries folder
FString AndroidDirectory = FPlatformMisc::GetEnvironmentVariable(TEXT("ANDROID_HOME"));
if (AndroidDirectory.Len() == 0)
{
return false;
}
#if PLATFORM_WINDOWS
OutFilename = FString::Printf(TEXT("%s\\platform-tools\\adb.exe"), *AndroidDirectory);
#else
OutFilename = FString::Printf(TEXT("%s/platform-tools/adb"), *AndroidDirectory);
#endif
return true;
}
inline bool FAndroidTargetDevice::ExecuteAdbCommand( const FString& CommandLine, FString* OutStdOut, FString* OutStdErr ) const
{
FString Filename;
if (!GetAdbFullFilename(Filename))
{
return false;
}
// execute the command
int32 ReturnCode;
FString DefaultError;
// make sure there's a place for error output to go if the caller specified NULL
if (!OutStdErr)
{
OutStdErr = &DefaultError;
}
FString CommandLineWithDevice;
// the devices command should never include a specific device
if (CommandLine == TEXT("devices"))
{
CommandLineWithDevice = CommandLine;
}
else
{
CommandLineWithDevice = FString::Printf(TEXT("-s %s %s"), *SerialNumber, *CommandLine);
}
FPlatformProcess::ExecProcess(*Filename, *CommandLineWithDevice, &ReturnCode, OutStdOut, OutStdErr);
if (ReturnCode != 0)
{
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("The Android SDK command '%s' failed to run. Return code: %d, Error: %s\n"), *CommandLineWithDevice, ReturnCode, **OutStdErr);
return false;
}
return true;
}
@@ -1,56 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/UnrealString.h"
#include "HAL/Runnable.h"
#include "HAL/RunnableThread.h"
#include "HAL/PlatformProcess.h"
#include "Interfaces/ITargetDeviceOutput.h"
#include "Misc/ConfigCacheIni.h"
#include "HAL/ThreadSafeCounter.h"
class FAndroidTargetDevice;
class FAndroidDeviceOutputReaderRunnable : public FRunnable
{
public:
FAndroidDeviceOutputReaderRunnable(const FString& AdbFilename, const FString& DeviceSerialNumber, FOutputDevice* Output);
// FRunnable interface.
virtual bool Init(void) override;
virtual void Exit(void) override;
virtual void Stop(void) override;
virtual uint32 Run(void) override;
private:
bool StartLogcatProcess(void);
private:
// > 0 if we've been asked to abort work in progress at the next opportunity
FThreadSafeCounter StopTaskCounter;
FString AdbFilename;
FString DeviceSerialNumber;
FOutputDevice* Output;
void* LogcatReadPipe;
void* LogcatWritePipe;
FProcHandle LogcatProcHandle;
};
/**
* Implements a Android target device.
*/
class FAndroidTargetDeviceOutput : public ITargetDeviceOutput
{
public:
bool Init(const FAndroidTargetDevice& TargetDevice, FOutputDevice* Output);
private:
TUniquePtr<FRunnableThread> DeviceOutputThread;
FString DeviceSerialNumber;
FString DeviceName;
};
#include "AndroidTargetDeviceOutput.inl"
@@ -1,121 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreTypes.h"
#include "CoreFwd.h"
class FAndroidDeviceOutputReaderRunnable;
class FAndroidTargetDevice;
class FAndroidTargetDeviceOutput;
inline FAndroidDeviceOutputReaderRunnable::FAndroidDeviceOutputReaderRunnable(const FString& InAdbFilename, const FString& InDeviceSerialNumber, FOutputDevice* InOutput)
: StopTaskCounter(0)
, AdbFilename(InAdbFilename)
, DeviceSerialNumber(InDeviceSerialNumber)
, Output(InOutput)
, LogcatReadPipe(nullptr)
, LogcatWritePipe(nullptr)
{
}
inline bool FAndroidDeviceOutputReaderRunnable::StartLogcatProcess(void)
{
FString Params = FString::Printf(TEXT(" -s %s logcat UE4:V DEBUG:V *:S -v tag"), *DeviceSerialNumber);
LogcatProcHandle = FPlatformProcess::CreateProc(*AdbFilename, *Params, true, false, false, NULL, 0, NULL, LogcatWritePipe);
return LogcatProcHandle.IsValid();
}
inline bool FAndroidDeviceOutputReaderRunnable::Init(void)
{
FPlatformProcess::CreatePipe(LogcatReadPipe, LogcatWritePipe);
return StartLogcatProcess();
}
inline void FAndroidDeviceOutputReaderRunnable::Exit(void)
{
if (LogcatProcHandle.IsValid())
{
FPlatformProcess::CloseProc(LogcatProcHandle);
}
FPlatformProcess::ClosePipe(LogcatReadPipe, LogcatWritePipe);
}
inline void FAndroidDeviceOutputReaderRunnable::Stop(void)
{
StopTaskCounter.Increment();
}
inline uint32 FAndroidDeviceOutputReaderRunnable::Run(void)
{
FString LogcatOutput;
while (StopTaskCounter.GetValue() == 0 && LogcatProcHandle.IsValid())
{
if (!FPlatformProcess::IsProcRunning(LogcatProcHandle))
{
// When user plugs out USB cable adb process stops
// Keep trying to restore adb connection until code that uses this object will not kill us
Output->Serialize(TEXT("Trying to restore connection to device..."), ELogVerbosity::Log, NAME_None);
FPlatformProcess::CloseProc(LogcatProcHandle);
if (StartLogcatProcess())
{
FPlatformProcess::Sleep(1.0f);
}
else
{
Output->Serialize(TEXT("Failed to start adb proccess"), ELogVerbosity::Log, NAME_None);
Stop();
}
}
else
{
LogcatOutput.Append(FPlatformProcess::ReadPipe(LogcatReadPipe));
if (LogcatOutput.Len() > 0)
{
TArray<FString> OutputLines;
LogcatOutput.ParseIntoArray(OutputLines, TEXT("\n"), false);
if (!LogcatOutput.EndsWith(TEXT("\n")))
{
// partial line at the end, do not serialize it until we receive remainder
LogcatOutput = OutputLines.Last();
OutputLines.RemoveAt(OutputLines.Num() - 1);
}
else
{
LogcatOutput.Reset();
}
for (int32 i = 0; i < OutputLines.Num(); ++i)
{
Output->Serialize(*OutputLines[i], ELogVerbosity::Log, NAME_None);
}
}
FPlatformProcess::Sleep(0.1f);
}
}
return 0;
}
inline bool FAndroidTargetDeviceOutput::Init(const FAndroidTargetDevice& TargetDevice, FOutputDevice* Output)
{
check(Output);
// Output will be produced by background thread
check(Output->CanBeUsedOnAnyThread());
DeviceSerialNumber = TargetDevice.GetSerialNumber();
DeviceName = TargetDevice.GetName();
FString AdbFilename;
if (FAndroidTargetDevice::GetAdbFullFilename(AdbFilename))
{
auto* Runnable = new FAndroidDeviceOutputReaderRunnable(AdbFilename, DeviceSerialNumber, Output);
DeviceOutputThread = TUniquePtr<FRunnableThread>(FRunnableThread::Create(Runnable, TEXT("FAndroidDeviceOutputReaderRunnable")));
return true;
}
return false;
}
@@ -1,826 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AndroidTargetPlatform.inl: Implements the FAndroidTargetPlatform class.
=============================================================================*/
/* FAndroidTargetPlatform structors
*****************************************************************************/
#include "AndroidTargetPlatform.h"
#include "CoreTypes.h"
#include "Misc/AssertionMacros.h"
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "UObject/NameTypes.h"
#include "Logging/LogMacros.h"
#include "Stats/Stats.h"
#include "Serialization/Archive.h"
#include "Misc/FileHelper.h"
#include "Misc/SecureHash.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformFilemanager.h"
#include "HAL/IConsoleManager.h"
#include "Interfaces/IAndroidDeviceDetectionModule.h"
#include "Interfaces/IAndroidDeviceDetection.h"
#include "Modules/ModuleManager.h"
#include "Misc/SecureHash.h"
#if WITH_ENGINE
#include "AudioCompressionSettings.h"
#include "Sound/SoundWave.h"
#endif
#define LOCTEXT_NAMESPACE "FAndroidTargetPlatform"
class Error;
class FAndroidTargetDevice;
class FConfigCacheIni;
class FModuleManager;
class FScopeLock;
class FStaticMeshLODSettings;
class FTargetDeviceId;
class FTicker;
class IAndroidDeviceDetectionModule;
class UTexture;
class UTextureLODSettings;
struct FAndroidDeviceInfo;
enum class ETargetPlatformFeatures;
template<typename TPlatformProperties> class TTargetPlatformBase;
static FString GetLicensePath()
{
auto &AndroidDeviceDetection = FModuleManager::LoadModuleChecked<IAndroidDeviceDetectionModule>("AndroidDeviceDetection");
IAndroidDeviceDetection* DeviceDetection = AndroidDeviceDetection.GetAndroidDeviceDetection();
FString ADBPath = DeviceDetection->GetADBPath();
if (!FPaths::FileExists(*ADBPath))
{
return TEXT("");
}
// strip off the adb.exe part
FString PlatformToolsPath;
FString Filename;
FString Extension;
FPaths::Split(ADBPath, PlatformToolsPath, Filename, Extension);
// remove the platform-tools part and point to licenses
FPaths::NormalizeDirectoryName(PlatformToolsPath);
FString LicensePath = PlatformToolsPath + "/../licenses";
FPaths::CollapseRelativeDirectories(LicensePath);
return LicensePath;
}
#if WITH_ENGINE
static bool GetLicenseHash(FSHAHash& LicenseHash)
{
bool bLicenseValid = false;
// from Android SDK Tools 25.2.3
FString LicenseFilename = FPaths::EngineDir() + TEXT("Source/ThirdParty/Android/package.xml");
// Create file reader
TUniquePtr<FArchive> FileReader(IFileManager::Get().CreateFileReader(*LicenseFilename));
if (FileReader)
{
// Create buffer for file input
uint32 BufferSize = FileReader->TotalSize();
uint8* Buffer = (uint8*)FMemory::Malloc(BufferSize);
FileReader->Serialize(Buffer, BufferSize);
uint8 StartPattern[] = "<license id=\"android-sdk-license\" type=\"text\">";
int32 StartPatternLength = strlen((char *)StartPattern);
uint8* LicenseStart = Buffer;
uint8* BufferEnd = Buffer + BufferSize - StartPatternLength;
while (LicenseStart < BufferEnd)
{
if (!memcmp(LicenseStart, StartPattern, StartPatternLength))
{
break;
}
LicenseStart++;
}
if (LicenseStart < BufferEnd)
{
LicenseStart += StartPatternLength;
uint8 EndPattern[] = "</license>";
int32 EndPatternLength = strlen((char *)EndPattern);
uint8* LicenseEnd = LicenseStart;
BufferEnd = Buffer + BufferSize - EndPatternLength;
while (LicenseEnd < BufferEnd)
{
if (!memcmp(LicenseEnd, EndPattern, EndPatternLength))
{
break;
}
LicenseEnd++;
}
if (LicenseEnd < BufferEnd)
{
int32 LicenseLength = LicenseEnd - LicenseStart;
FSHA1::HashBuffer(LicenseStart, LicenseLength, LicenseHash.Hash);
bLicenseValid = true;
}
}
FMemory::Free(Buffer);
}
return bLicenseValid;
}
#endif
static bool HasLicense()
{
#if WITH_ENGINE
FString LicensePath = GetLicensePath();
if (LicensePath.IsEmpty())
{
return false;
}
// directory must exist
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (!PlatformFile.DirectoryExists(*LicensePath))
{
return false;
}
// license file must exist
FString LicenseFilename = LicensePath + "/android-sdk-license";
if (!PlatformFile.FileExists(*LicenseFilename))
{
return false;
}
FSHAHash LicenseHash;
if (!GetLicenseHash(LicenseHash))
{
return false;
}
// contents must match hash of license text
FString FileData = "";
FFileHelper::LoadFileToString(FileData, *LicenseFilename);
TArray<FString> lines;
int32 lineCount = FileData.ParseIntoArray(lines, TEXT("\n"), true);
FString LicenseString = LicenseHash.ToString().ToLower();
for (FString &line : lines)
{
if (line.TrimStartAndEnd().Equals(LicenseString))
{
return true;
}
}
#endif
// doesn't match
return false;
}
FAndroidTargetPlatform::FAndroidTargetPlatform(bool bInIsClient )
: bIsClient(bInIsClient)
, DeviceDetection(nullptr)
{
#if WITH_ENGINE
FConfigCacheIni::LoadLocalIniFile(EngineSettings, TEXT("Engine"), true, *IniPlatformName());
TextureLODSettings = nullptr; // These are registered by the device profile system.
StaticMeshLODSettings.Initialize(EngineSettings);
#endif
TickDelegate = FTickerDelegate::CreateRaw(this, &FAndroidTargetPlatform::HandleTicker);
TickDelegateHandle = FTicker::GetCoreTicker().AddTicker(TickDelegate, 4.0f);
}
FAndroidTargetPlatform::~FAndroidTargetPlatform()
{
FTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle);
}
FAndroidTargetDevicePtr FAndroidTargetPlatform::CreateTargetDevice(const ITargetPlatform& InTargetPlatform, const FString& InSerialNumber, const FString& InAndroidVariant) const
{
return MakeShareable(new FAndroidTargetDevice(InTargetPlatform, InSerialNumber, InAndroidVariant));
}
static bool UsesVirtualTextures()
{
static auto* CVarMobileVirtualTextures = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.VirtualTextures"));
return CVarMobileVirtualTextures->GetValueOnAnyThread() != 0;
}
bool FAndroidTargetPlatform::SupportsES31() const
{
// default no support for ES31
bool bBuildForES31 = false;
#if WITH_ENGINE
GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bBuildForES31"), bBuildForES31, GEngineIni);
#endif
return bBuildForES31;
}
bool FAndroidTargetPlatform::SupportsVulkan() const
{
// default to not supporting Vulkan
bool bSupportsVulkan = false;
#if WITH_ENGINE
GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bSupportsVulkan"), bSupportsVulkan, GEngineIni);
#endif
return bSupportsVulkan;
}
bool FAndroidTargetPlatform::SupportsVulkanSM5() const
{
// default to no support for VulkanSM5
bool bSupportsMobileVulkanSM5 = false;
#if WITH_ENGINE
GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bSupportsVulkanSM5"), bSupportsMobileVulkanSM5, GEngineIni);
#endif
return bSupportsMobileVulkanSM5;
}
bool FAndroidTargetPlatform::SupportsSoftwareOcclusion() const
{
static auto* CVarMobileAllowSoftwareOcclusion = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.AllowSoftwareOcclusion"));
return CVarMobileAllowSoftwareOcclusion->GetValueOnAnyThread() != 0;
}
bool FAndroidTargetPlatform::SupportsLandscapeMeshLODStreaming() const
{
bool bStreamLandscapeMeshLODs = false;
#if WITH_ENGINE
GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bStreamLandscapeMeshLODs"), bStreamLandscapeMeshLODs, GEngineIni);
#endif
return bStreamLandscapeMeshLODs;
}
/* ITargetPlatform overrides
*****************************************************************************/
void FAndroidTargetPlatform::GetAllDevices( TArray<ITargetDevicePtr>& OutDevices ) const
{
OutDevices.Reset();
for (auto Iter = Devices.CreateConstIterator(); Iter; ++Iter)
{
OutDevices.Add(Iter.Value());
}
}
ITargetDevicePtr FAndroidTargetPlatform::GetDefaultDevice( ) const
{
// return the first device in the list
if (Devices.Num() > 0)
{
auto Iter = Devices.CreateConstIterator();
if (Iter)
{
return Iter.Value();
}
}
return nullptr;
}
ITargetDevicePtr FAndroidTargetPlatform::GetDevice( const FTargetDeviceId& DeviceId )
{
if (DeviceId.GetPlatformName() == PlatformName())
{
return Devices.FindRef(DeviceId.GetDeviceName());
}
return nullptr;
}
bool FAndroidTargetPlatform::IsRunningPlatform( ) const
{
return false; // This platform never runs the target platform framework
}
bool FAndroidTargetPlatform::IsSdkInstalled(bool bProjectHasCode, FString& OutDocumentationPath) const
{
OutDocumentationPath = FString("Shared/Tutorials/SettingUpAndroidTutorial");
return true;
}
int32 FAndroidTargetPlatform::CheckRequirements(bool bProjectHasCode, EBuildConfiguration Configuration, bool bRequiresAssetNativization, FString& OutTutorialPath, FString& OutDocumentationPath, FText& CustomizedLogMessage) const
{
OutDocumentationPath = TEXT("Platforms/Android/GettingStarted");
int32 bReadyToBuild = ETargetPlatformReadyStatus::Ready;
if (!IsSdkInstalled(bProjectHasCode, OutTutorialPath))
{
bReadyToBuild |= ETargetPlatformReadyStatus::SDKNotFound;
}
bool bEnableGradle;
GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bEnableGradle"), bEnableGradle, GEngineIni);
if (bEnableGradle)
{
// need to check license was accepted
if (!HasLicense())
{
OutTutorialPath.Empty();
CustomizedLogMessage = LOCTEXT("AndroidLicenseNotAcceptedMessageDetail", "SDK License must be accepted in the Android project settings to deploy your app to the device.");
bReadyToBuild |= ETargetPlatformReadyStatus::LicenseNotAccepted;
}
}
return bReadyToBuild;
}
bool FAndroidTargetPlatform::SupportsFeature( ETargetPlatformFeatures Feature ) const
{
switch (Feature)
{
case ETargetPlatformFeatures::Packaging:
case ETargetPlatformFeatures::DeviceOutputLog:
return true;
case ETargetPlatformFeatures::LowQualityLightmaps:
case ETargetPlatformFeatures::MobileRendering:
return SupportsES31() || SupportsVulkan();
case ETargetPlatformFeatures::HighQualityLightmaps:
case ETargetPlatformFeatures::DeferredRendering:
return SupportsVulkanSM5();
case ETargetPlatformFeatures::Tessellation:
return false;
case ETargetPlatformFeatures::SoftwareOcclusion:
return SupportsSoftwareOcclusion();
case ETargetPlatformFeatures::VirtualTextureStreaming:
return UsesVirtualTextures();
case ETargetPlatformFeatures::LandscapeMeshLODStreaming:
return SupportsLandscapeMeshLODStreaming();
default:
break;
}
return TTargetPlatformBase<FAndroidPlatformProperties>::SupportsFeature(Feature);
}
#if WITH_ENGINE
void FAndroidTargetPlatform::GetAllPossibleShaderFormats( TArray<FName>& OutFormats ) const
{
static FName NAME_SF_VULKAN_ES31_ANDROID(TEXT("SF_VULKAN_ES31_ANDROID_NOUB"));
static FName NAME_GLSL_ES3_1_ANDROID(TEXT("GLSL_ES3_1_ANDROID"));
static FName NAME_SF_VULKAN_SM5_ANDROID(TEXT("SF_VULKAN_SM5_ANDROID"));
if (SupportsVulkan())
{
OutFormats.AddUnique(NAME_SF_VULKAN_ES31_ANDROID);
}
if (SupportsVulkanSM5())
{
OutFormats.AddUnique(NAME_SF_VULKAN_SM5_ANDROID);
}
if (SupportsES31())
{
OutFormats.AddUnique(NAME_GLSL_ES3_1_ANDROID);
}
}
void FAndroidTargetPlatform::GetAllTargetedShaderFormats( TArray<FName>& OutFormats ) const
{
GetAllPossibleShaderFormats(OutFormats);
}
const FStaticMeshLODSettings& FAndroidTargetPlatform::GetStaticMeshLODSettings( ) const
{
return StaticMeshLODSettings;
}
void FAndroidTargetPlatform::GetTextureFormats( const UTexture* InTexture, TArray< TArray<FName> >& OutFormats) const
{
#if WITH_EDITOR
const int32 NumLayers = InTexture->Source.GetNumLayers();
// Can always compress power-of-two, sometimes support non-POT compression
const bool bIsCompressionValid = InTexture->Source.IsPowerOfTwo() || SupportsCompressedNonPOT();
OutFormats.Reserve((int32)EAndroidTextureFormatCategory::Count);
for (int32 FormatIndex = 0; FormatIndex < (int32)EAndroidTextureFormatCategory::Count; ++FormatIndex)
{
const EAndroidTextureFormatCategory FormatCategory = (EAndroidTextureFormatCategory)FormatIndex;
if (!SupportsTextureFormatCategory(FormatCategory))
{
continue;
}
TArray<FName> FormatPerLayer;
FormatPerLayer.SetNum(NumLayers);
bool bValidFormat = true;
for (int32 LayerIndex = 0; LayerIndex < NumLayers; ++LayerIndex)
{
FTextureFormatSettings LayerFormatSettings;
InTexture->GetLayerFormatSettings(LayerIndex, LayerFormatSettings);
const bool bNoCompression = LayerFormatSettings.CompressionNone // Code wants the texture uncompressed.
|| (InTexture->LODGroup == TEXTUREGROUP_ColorLookupTable) // Textures in certain LOD groups should remain uncompressed.
|| (InTexture->LODGroup == TEXTUREGROUP_Bokeh)
|| (LayerFormatSettings.CompressionSettings == TC_EditorIcon)
|| (InTexture->Source.GetSizeX() < 4) // Don't compress textures smaller than the DXT block size.
|| (InTexture->Source.GetSizeY() < 4)
|| (InTexture->Source.GetSizeX() % 4 != 0)
|| (InTexture->Source.GetSizeY() % 4 != 0);
// Determine the pixel format of the compressed texture.
if (InTexture->LODGroup == TEXTUREGROUP_Shadowmap)
{
// forward rendering only needs one channel for shadow maps
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameG8;
}
else if (bNoCompression && InTexture->HasHDRSource(LayerIndex))
{
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameRGBA16F;
}
else if (bNoCompression)
{
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameBGRA8;
}
else if (LayerFormatSettings.CompressionSettings == TC_ReflectionCapture && !LayerFormatSettings.CompressionNone)
{
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameETC2_RGBA;
}
else if (LayerFormatSettings.CompressionSettings == TC_HDR || LayerFormatSettings.CompressionSettings == TC_HDR_Compressed)
{
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameRGBA16F;
}
else if (LayerFormatSettings.CompressionSettings == TC_Normalmap)
{
if(!bIsCompressionValid) FormatPerLayer[LayerIndex] = AndroidTexFormat::NamePOTERROR;
else if (FormatCategory == EAndroidTextureFormatCategory::DXT) FormatPerLayer[LayerIndex] = AndroidTexFormat::NameDXT5;
else if (FormatCategory == EAndroidTextureFormatCategory::ETC2) FormatPerLayer[LayerIndex] = AndroidTexFormat::NameETC2_RGB;
else bValidFormat = false;
}
else if (LayerFormatSettings.CompressionSettings == TC_Displacementmap)
{
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameRGBA16F;
}
else if (LayerFormatSettings.CompressionSettings == TC_VectorDisplacementmap)
{
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameBGRA8;
}
else if (LayerFormatSettings.CompressionSettings == TC_Grayscale)
{
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameG8;
}
else if (LayerFormatSettings.CompressionSettings == TC_Alpha)
{
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameG8;
}
else if (LayerFormatSettings.CompressionSettings == TC_DistanceFieldFont)
{
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameG8;
}
else if (LayerFormatSettings.CompressionSettings == TC_HalfFloat)
{
FormatPerLayer[LayerIndex] = AndroidTexFormat::NameR16F;
}
else if (LayerFormatSettings.CompressionSettings == TC_BC7)
{
if (!bIsCompressionValid) FormatPerLayer[LayerIndex] = AndroidTexFormat::NamePOTERROR;
else if (FormatCategory == EAndroidTextureFormatCategory::DXT) FormatPerLayer[LayerIndex] = AndroidTexFormat::NameDXT5;
else if (FormatCategory == EAndroidTextureFormatCategory::ETC2) FormatPerLayer[LayerIndex] = AndroidTexFormat::NameAutoETC2;
else bValidFormat = false;
}
else if (LayerFormatSettings.CompressionNoAlpha)
{
if (!bIsCompressionValid) FormatPerLayer[LayerIndex] = AndroidTexFormat::NamePOTERROR;
else if (FormatCategory == EAndroidTextureFormatCategory::DXT) FormatPerLayer[LayerIndex] = AndroidTexFormat::NameDXT1;
else if (FormatCategory == EAndroidTextureFormatCategory::ETC2) FormatPerLayer[LayerIndex] = AndroidTexFormat::NameETC2_RGB;
else bValidFormat = false;
}
else if (InTexture->bDitherMipMapAlpha)
{
if (!bIsCompressionValid) FormatPerLayer[LayerIndex] = AndroidTexFormat::NamePOTERROR;
else if (FormatCategory == EAndroidTextureFormatCategory::DXT) FormatPerLayer[LayerIndex] = AndroidTexFormat::NameDXT5;
else if (FormatCategory == EAndroidTextureFormatCategory::ETC2) FormatPerLayer[LayerIndex] = AndroidTexFormat::NameAutoETC2;
else bValidFormat = false;
}
else
{
if (!bIsCompressionValid) FormatPerLayer[LayerIndex] = AndroidTexFormat::NamePOTERROR;
else if (FormatCategory == EAndroidTextureFormatCategory::DXT) FormatPerLayer[LayerIndex] = AndroidTexFormat::NameAutoDXT;
else if (FormatCategory == EAndroidTextureFormatCategory::ETC2) FormatPerLayer[LayerIndex] = AndroidTexFormat::NameAutoETC2;
else bValidFormat = false;
}
}
if (bValidFormat)
{
OutFormats.AddUnique(FormatPerLayer);
}
}
#endif // WITH_EDITOR
}
FName FAndroidTargetPlatform::FinalizeVirtualTextureLayerFormat(FName Format) const
{
#if WITH_EDITOR
// Remap non-ETC variants to ETC
const static FName ETCRemap[][2] =
{
{ { FName(TEXT("ASTC_RGB")) }, { AndroidTexFormat::NameETC2_RGB } },
{ { FName(TEXT("ASTC_RGBA")) }, { AndroidTexFormat::NameETC2_RGBA } },
{ { FName(TEXT("ASTC_RGBAuto")) }, { AndroidTexFormat::NameAutoETC2 } },
{ { FName(TEXT("ASTC_NormalAG")) }, { AndroidTexFormat::NameETC2_RGB } },
{ { FName(TEXT("ASTC_NormalRG")) }, { AndroidTexFormat::NameETC2_RGB } },
{ { AndroidTexFormat::NameDXT1 }, { AndroidTexFormat::NameETC2_RGB } },
{ { AndroidTexFormat::NameDXT5 }, { AndroidTexFormat::NameAutoETC2 } },
{ { AndroidTexFormat::NameAutoDXT }, { AndroidTexFormat::NameAutoETC2 } }
};
for (int32 RemapIndex = 0; RemapIndex < UE_ARRAY_COUNT(ETCRemap); RemapIndex++)
{
if (ETCRemap[RemapIndex][0] == Format)
{
return ETCRemap[RemapIndex][1];
}
}
#endif
return Format;
}
void FAndroidTargetPlatform::GetAllTextureFormats(TArray<FName>& OutFormats) const
{
OutFormats.Add(AndroidTexFormat::NameG8);
OutFormats.Add(AndroidTexFormat::NameRGBA16F);
OutFormats.Add(AndroidTexFormat::NameBGRA8);
OutFormats.Add(AndroidTexFormat::NameRGBA16F);
OutFormats.Add(AndroidTexFormat::NameRGBA16F);
OutFormats.Add(AndroidTexFormat::NameBGRA8);
OutFormats.Add(AndroidTexFormat::NameG8);
OutFormats.Add(AndroidTexFormat::NameG8);
OutFormats.Add(AndroidTexFormat::NameG8);
OutFormats.Add(AndroidTexFormat::NameR16F);
auto AddAllTextureFormatIfSupports = [=, &OutFormats](bool bIsNonPOT)
{
AddTextureFormatIfSupports(AndroidTexFormat::NameAutoDXT, OutFormats, bIsNonPOT);
AddTextureFormatIfSupports(AndroidTexFormat::NameDXT1, OutFormats, bIsNonPOT);
AddTextureFormatIfSupports(AndroidTexFormat::NameDXT5, OutFormats, bIsNonPOT);
AddTextureFormatIfSupports(AndroidTexFormat::NameAutoETC2, OutFormats, bIsNonPOT);
};
AddAllTextureFormatIfSupports(true);
AddAllTextureFormatIfSupports(false);
}
void FAndroidTargetPlatform::GetReflectionCaptureFormats( TArray<FName>& OutFormats ) const
{
static auto* MobileShadingPathCvar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.ShadingPath"));
const bool bMobileDeferredShading = (MobileShadingPathCvar->GetValueOnAnyThread() == 1);
if (SupportsVulkanSM5() || (SupportsVulkan() && bMobileDeferredShading))
{
// use Full HDR with SM5 and Mobile Deferred
OutFormats.Add(FName(TEXT("FullHDR")));
}
// always emit encoded
OutFormats.Add(FName(TEXT("EncodedHDR")));
}
const UTextureLODSettings& FAndroidTargetPlatform::GetTextureLODSettings() const
{
return *TextureLODSettings;
}
FName FAndroidTargetPlatform::GetWaveFormat( const class USoundWave* Wave ) const
{
static const FName NAME_ADPCM(TEXT("ADPCM"));
static const FName NAME_OGG(TEXT("OGG"));
static bool bFormatRead = false;
static FName NAME_FORMAT;
if (!bFormatRead)
{
bFormatRead = true;
FName AudioSetting;
{
FString AudioSettingStr;
if (!GConfig->GetString(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("AndroidAudio"), AudioSettingStr, GEngineIni))
{
AudioSetting = *AudioSettingStr;
}
}
#if WITH_OGGVORBIS
if (AudioSetting == NAME_OGG || AudioSetting == NAME_None)
{
NAME_FORMAT = NAME_OGG;
}
#else
if (AudioSetting == NAME_OGG)
{
UE_LOG(LogAudio, Error, TEXT("Attemped to select Ogg Vorbis encoding when the cooker is built without Ogg Vorbis support."));
}
#endif
else
{
// Otherwise return ADPCM as it'll either be option '2' or 'default' depending on WITH_OGGVORBIS config
NAME_FORMAT = NAME_ADPCM;
}
}
if (Wave->IsSeekableStreaming())
{
return NAME_ADPCM;
}
return NAME_FORMAT;
}
void FAndroidTargetPlatform::GetAllWaveFormats(TArray<FName>& OutFormats) const
{
static FName NAME_OGG(TEXT("OGG"));
static FName NAME_ADPCM(TEXT("ADPCM"));
OutFormats.Add(NAME_OGG);
OutFormats.Add(NAME_ADPCM);
}
#endif //WITH_ENGINE
bool FAndroidTargetPlatform::SupportsVariants() const
{
return true;
}
FText FAndroidTargetPlatform::GetVariantTitle() const
{
return LOCTEXT("AndroidVariantTitle", "Texture Format");
}
/* FAndroidTargetPlatform implementation
*****************************************************************************/
void FAndroidTargetPlatform::AddTextureFormatIfSupports( FName Format, TArray<FName>& OutFormats, bool bIsCompressedNonPOT ) const
{
if (SupportsTextureFormat(Format))
{
if (bIsCompressedNonPOT && SupportsCompressedNonPOT() == false)
{
OutFormats.Add(AndroidTexFormat::NamePOTERROR);
}
else
{
OutFormats.Add(Format);
}
}
}
void FAndroidTargetPlatform::InitializeDeviceDetection()
{
DeviceDetection = FModuleManager::LoadModuleChecked<IAndroidDeviceDetectionModule>("AndroidDeviceDetection").GetAndroidDeviceDetection();
DeviceDetection->Initialize(TEXT("ANDROID_HOME"),
#if PLATFORM_WINDOWS
TEXT("platform-tools\\adb.exe"),
#else
TEXT("platform-tools/adb"),
#endif
TEXT("shell getprop"), true);
}
bool FAndroidTargetPlatform::ShouldExpandTo32Bit(const uint16* Indices, const int32 NumIndices) const
{
bool bIsMaliBugIndex = false;
const uint16 MaliBugIndexMaxDiff = 16;
uint16 LastIndex = Indices[0];
for (int32 i = 1; i < NumIndices; ++i)
{
uint16 CurrentIndex = Indices[i];
if ((FMath::Abs(LastIndex - CurrentIndex) > MaliBugIndexMaxDiff))
{
bIsMaliBugIndex = true;
break;
}
else
{
LastIndex = CurrentIndex;
}
}
return bIsMaliBugIndex;
}
/* FAndroidTargetPlatform callbacks
*****************************************************************************/
bool FAndroidTargetPlatform::HandleTicker( float DeltaTime )
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FAndroidTargetPlatform_HandleTicker);
if (DeviceDetection == nullptr)
{
InitializeDeviceDetection();
checkf(DeviceDetection != nullptr, TEXT("A target platform didn't create a device detection object in InitializeDeviceDetection()!"));
}
TArray<FString> ConnectedDeviceIds;
{
FScopeLock ScopeLock(DeviceDetection->GetDeviceMapLock());
auto DeviceIt = DeviceDetection->GetDeviceMap().CreateConstIterator();
for (; DeviceIt; ++DeviceIt)
{
ConnectedDeviceIds.Add(DeviceIt.Key());
const FAndroidDeviceInfo& DeviceInfo = DeviceIt.Value();
// see if this device is already known
if (Devices.Contains(DeviceIt.Key()))
{
FAndroidTargetDevicePtr TestDevice = Devices[DeviceIt.Key()];
// ignore if authorization didn't change
if (DeviceInfo.bAuthorizedDevice == TestDevice->IsAuthorized())
{
continue;
}
// remove it to add again
TestDevice->SetConnected(false);
Devices.Remove(DeviceIt.Key());
DeviceLostEvent.Broadcast(TestDevice.ToSharedRef());
}
// check if this platform is supported by the extensions and version
if (!SupportedByExtensionsString(DeviceInfo.GLESExtensions, DeviceInfo.GLESVersion))
{
continue;
}
// create target device
FAndroidTargetDevicePtr& Device = Devices.Add(DeviceInfo.SerialNumber);
Device = CreateTargetDevice(*this, DeviceInfo.SerialNumber, GetAndroidVariantName());
Device->SetConnected(true);
Device->SetModel(DeviceInfo.Model);
Device->SetDeviceName(DeviceInfo.DeviceName);
Device->SetAuthorized(DeviceInfo.bAuthorizedDevice);
Device->SetVersions(DeviceInfo.SDKVersion, DeviceInfo.HumanAndroidVersion);
DeviceDiscoveredEvent.Broadcast(Device.ToSharedRef());
}
}
// remove disconnected devices
for (auto Iter = Devices.CreateIterator(); Iter; ++Iter)
{
if (!ConnectedDeviceIds.Contains(Iter.Key()))
{
FAndroidTargetDevicePtr RemovedDevice = Iter.Value();
RemovedDevice->SetConnected(false);
Iter.RemoveCurrent();
DeviceLostEvent.Broadcast(RemovedDevice.ToSharedRef());
}
}
return true;
}
FAndroidTargetDeviceRef FAndroidTargetPlatform::CreateNewDevice(const FAndroidDeviceInfo &DeviceInfo)
{
return MakeShareable(new FAndroidTargetDevice(*this, DeviceInfo.SerialNumber, GetAndroidVariantName()));
}
#undef LOCTEXT_NAMESPACE
@@ -1,687 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AndroidTargetPlatform.h: Declares the FAndroidTargetPlatform class.
=============================================================================*/
#pragma once
#include "CoreTypes.h"
#include "Containers/UnrealString.h"
#include "UObject/NameTypes.h"
#include "Delegates/IDelegateInstance.h"
#include "Containers/Map.h"
#include "Delegates/Delegate.h"
#include "Containers/Ticker.h"
#include "Misc/ScopeLock.h"
#include "Android/AndroidPlatformProperties.h"
#include "Interfaces/ITargetPlatformModule.h"
#include "Common/TargetPlatformBase.h"
#include "Interfaces/IAndroidDeviceDetection.h"
#include "Interfaces/IAndroidDeviceDetectionModule.h"
#include "AndroidTargetDevice.h"
#if WITH_ENGINE
#include "Engine/TextureCube.h"
#include "Internationalization/Text.h"
#include "StaticMeshResources.h"
#endif // WITH_ENGINE
#define LOCTEXT_NAMESPACE "FAndroidTargetPlatform"
class FTargetDeviceId;
class IAndroidDeviceDetection;
class ITargetPlatform;
class UTextureLODSettings;
enum class ETargetPlatformFeatures;
template<typename TPlatformProperties> class TTargetPlatformBase;
template< typename InElementType, typename KeyFuncs , typename Allocator > class TSet;
template<typename KeyType,typename ValueType,typename SetAllocator ,typename KeyFuncs > class TMap;
template<typename KeyType,typename ValueType,typename SetAllocator ,typename KeyFuncs > class TMultiMap;
template<typename TPlatformProperties> class TTargetPlatformBase;
/**
* Defines supported texture format names.
*/
namespace AndroidTexFormat
{
// Compressed Texture Formats
static FName NameDXT1(TEXT("DXT1"));
static FName NameDXT5(TEXT("DXT5"));
static FName NameAutoDXT(TEXT("AutoDXT"));
static FName NameETC2_RGB(TEXT("ETC2_RGB"));
static FName NameETC2_RGBA(TEXT("ETC2_RGBA"));
static FName NameAutoETC2(TEXT("AutoETC2"));
static FName NameASTC_4x4(TEXT("ASTC_4x4"));
static FName NameASTC_6x6(TEXT("ASTC_6x6"));
static FName NameASTC_8x8(TEXT("ASTC_8x8"));
static FName NameASTC_10x10(TEXT("ASTC_10x10"));
static FName NameASTC_12x12(TEXT("ASTC_12x12"));
static FName NameAutoASTC(TEXT("AutoASTC"));
// Uncompressed Texture Formats
static FName NameBGRA8(TEXT("BGRA8"));
static FName NameG8(TEXT("G8"));
static FName NameVU8(TEXT("VU8"));
static FName NameRGBA16F(TEXT("RGBA16F"));
static FName NameR16F(TEXT("R16F"));
// Error "formats" (uncompressed)
static FName NamePOTERROR(TEXT("POTERROR"));
}
/** Listed in order of priority...if device supports multiple formats, first format in list will be chosen */
enum class EAndroidTextureFormatCategory
{
DXT,
ETC2,
ASTC,
Count,
};
/**
* FAndroidTargetPlatform, abstraction for cooking Android platforms
*/
class ANDROIDTARGETPLATFORM_API FAndroidTargetPlatform : public TTargetPlatformBase<FAndroidPlatformProperties>
{
public:
/**
* Default constructor.
*/
FAndroidTargetPlatform(bool bInIsClient);
/**
* Destructor
*/
virtual ~FAndroidTargetPlatform();
public:
/**
* Gets the name of the Android platform variant, i.e. ASTC, ETC2, DXT, etc.
*
* @param Variant name.
*/
virtual FString GetAndroidVariantName() const
{
return FString();
}
virtual FString IniPlatformName() const override
{
return "Android";
}
virtual FString PlatformName() const override
{
FString PlatformName = TEXT("Android");
FString Variant = GetAndroidVariantName();
if (Variant.Len() > 0)
{
PlatformName += FString(TEXT("_")) + Variant;
}
if (bIsClient)
{
PlatformName += TEXT("Client");
}
return PlatformName;
}
public:
//~ Begin ITargetPlatform Interface
virtual void EnableDeviceCheck(bool OnOff) override {}
virtual bool AddDevice( const FString& DeviceName, bool bDefault ) override
{
return false;
}
virtual void GetAllDevices( TArray<ITargetDevicePtr>& OutDevices ) const override;
virtual bool GenerateStreamingInstallManifest(const TMultiMap<FString, int32>& PakchunkMap, const TSet<int32>& PakchunkIndicesInUse) const override
{
return true;
}
virtual ITargetDevicePtr GetDefaultDevice( ) const override;
virtual ITargetDevicePtr GetDevice( const FTargetDeviceId& DeviceId ) override;
virtual bool IsRunningPlatform( ) const override;
virtual bool IsServerOnly( ) const override
{
return false;
}
virtual bool IsClientOnly() const override
{
return bIsClient;
}
virtual bool IsSdkInstalled(bool bProjectHasCode, FString& OutDocumentationPath) const override;
virtual int32 CheckRequirements(bool bProjectHasCode, EBuildConfiguration Configuration, bool bRequiresAssetNativization, FString& OutTutorialPath, FString& OutDocumentationPath, FText& CustomizedLogMessage) const override;
virtual bool SupportsFeature( ETargetPlatformFeatures Feature ) const override;
virtual bool SupportsTextureFormat( FName Format ) const
{
// By default we support all texture formats.
return true;
}
virtual bool SupportsTextureFormatCategory(EAndroidTextureFormatCategory Category) const
{
return true;
}
virtual bool SupportsCompressedNonPOT( ) const
{
// most formats do support non-POT compressed textures
return true;
}
#if WITH_ENGINE
virtual void GetReflectionCaptureFormats( TArray<FName>& OutFormats ) const override;
virtual void GetAllPossibleShaderFormats( TArray<FName>& OutFormats ) const override;
virtual void GetAllTargetedShaderFormats(TArray<FName>& OutFormats) const override;
virtual const class FStaticMeshLODSettings& GetStaticMeshLODSettings() const override;
virtual void GetTextureFormats( const UTexture* InTexture, TArray< TArray<FName> >& OutFormats) const override;
virtual FName FinalizeVirtualTextureLayerFormat(FName Format) const override;
virtual void GetAllTextureFormats(TArray<FName>& OutFormats) const override;
virtual const UTextureLODSettings& GetTextureLODSettings() const override;
virtual void RegisterTextureLODSettings(const UTextureLODSettings* InTextureLODSettings) override
{
TextureLODSettings = InTextureLODSettings;
}
virtual FName GetWaveFormat( const class USoundWave* Wave ) const override;
virtual void GetAllWaveFormats( TArray<FName>& OutFormats) const override;
#endif //WITH_ENGINE
virtual bool SupportsVariants() const override;
virtual FText GetVariantTitle() const override;
virtual void GetBuildProjectSettingKeys(FString& OutSection, TArray<FString>& InBoolKeys, TArray<FString>& InIntKeys, TArray<FString>& InStringKeys) const override
{
OutSection = TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings");
InBoolKeys.Add(TEXT("bBuildForArmV7")); InBoolKeys.Add(TEXT("bBuildForArm64")); InBoolKeys.Add(TEXT("bBuildForX86"));
InBoolKeys.Add(TEXT("bBuildForX8664")); InBoolKeys.Add(TEXT("bBuildForES31")); InBoolKeys.Add(TEXT("bBuildWithHiddenSymbolVisibility"));
InBoolKeys.Add(TEXT("bUseNEONForArmV7")); InBoolKeys.Add(TEXT("bSaveSymbols"));
InStringKeys.Add(TEXT("NDKAPILevel"));
}
DECLARE_DERIVED_EVENT(FAndroidTargetPlatform, ITargetPlatform::FOnTargetDeviceDiscovered, FOnTargetDeviceDiscovered);
virtual FOnTargetDeviceDiscovered& OnDeviceDiscovered( ) override
{
return DeviceDiscoveredEvent;
}
DECLARE_DERIVED_EVENT(FAndroidTargetPlatform, ITargetPlatform::FOnTargetDeviceLost, FOnTargetDeviceLost);
virtual FOnTargetDeviceLost& OnDeviceLost( ) override
{
return DeviceLostEvent;
}
virtual bool ShouldExpandTo32Bit(const uint16* Indices, const int32 NumIndices) const override;
//~ End ITargetPlatform Interface
virtual void InitializeDeviceDetection();
protected:
/**
* Adds the specified texture format to the OutFormats if this android target platforms supports it.
*
* @param Format - The format to add.
* @param OutFormats - The collection of formats to add to.
* @param bIsCompressedNonPOT - If this is true, the texture wants to be compressed but is not a power of 2
*/
void AddTextureFormatIfSupports( FName Format, TArray<FName>& OutFormats, bool bIsCompressedNonPOT=false ) const;
/**
* Return true if this device has a supported set of extensions for this platform.
*
* @param Extensions - The GL extensions string.
* @param GLESVersion - The GLES version reported by this device.
*/
virtual bool SupportedByExtensionsString( const FString& ExtensionsString, const int GLESVersion ) const
{
return true;
}
virtual FAndroidTargetDevicePtr CreateTargetDevice(const ITargetPlatform& InTargetPlatform, const FString& InSerialNumber, const FString& InAndroidVariant) const;
// query for rene3ring mode support
bool SupportsES31() const;
bool SupportsVulkan() const;
bool SupportsSoftwareOcclusion() const;
bool SupportsLandscapeMeshLODStreaming() const;
bool SupportsVulkanSM5() const;
#if WITH_ENGINE
// Holds the Engine INI settings (for quick access).
FConfigFile EngineSettings;
#endif //WITH_ENGINE
protected:
// Handles when the ticker fires.
bool HandleTicker( float DeltaTime );
virtual FAndroidTargetDeviceRef CreateNewDevice(const FAndroidDeviceInfo &DeviceInfo);
// true if this is a client TP
bool bIsClient;
// Holds a map of valid devices.
TMap<FString, FAndroidTargetDevicePtr> Devices;
// Holds a delegate to be invoked when the widget ticks.
FTickerDelegate TickDelegate;
// Handle to the registered TickDelegate.
FDelegateHandle TickDelegateHandle;
// Pointer to the device detection handler that grabs device ids in another thread
IAndroidDeviceDetection* DeviceDetection;
#if WITH_ENGINE
// Holds a cache of the target LOD settings.
const UTextureLODSettings* TextureLODSettings;
// Holds the static mesh LOD settings.
FStaticMeshLODSettings StaticMeshLODSettings;
ITargetDevicePtr DefaultDevice;
#endif //WITH_ENGINE
// Holds an event delegate that is executed when a new target device has been discovered.
FOnTargetDeviceDiscovered DeviceDiscoveredEvent;
// Holds an event delegate that is executed when a target device has been lost, i.e. disconnected or timed out.
FOnTargetDeviceLost DeviceLostEvent;
};
//#include "AndroidTargetPlatform.inl"
class FAndroid_DXTTargetPlatform : public FAndroidTargetPlatform
{
public:
FAndroid_DXTTargetPlatform(bool bIsClient) : FAndroidTargetPlatform(bIsClient)
{
this->PlatformInfo = PlatformInfo::FindPlatformInfo("Android_DXT");
}
virtual FString GetAndroidVariantName() const override
{
return TEXT("DXT");
}
virtual FText DisplayName() const override
{
return LOCTEXT("Android_DXT", "Android (DXT)");
}
virtual bool SupportsTextureFormat(FName Format) const override
{
if (Format == AndroidTexFormat::NameDXT1 ||
Format == AndroidTexFormat::NameDXT5 ||
Format == AndroidTexFormat::NameAutoDXT)
{
return true;
}
return false;
}
virtual bool SupportsTextureFormatCategory(EAndroidTextureFormatCategory Category) const override
{
return Category == EAndroidTextureFormatCategory::DXT;
}
virtual bool SupportedByExtensionsString(const FString& ExtensionsString, const int GLESVersion) const override
{
return (ExtensionsString.Contains(TEXT("GL_NV_texture_compression_s3tc")) || ExtensionsString.Contains(TEXT("GL_EXT_texture_compression_s3tc")));
}
virtual FText GetVariantDisplayName() const override
{
return LOCTEXT("Android_DXT_ShortName", "DXT");
}
virtual float GetVariantPriority() const override
{
float Priority;
return (GConfig->GetFloat(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("TextureFormatPriority_DXT"), Priority, GEngineIni) ?
Priority : 0.6f) * 10.0f + (IsClientOnly() ? 0.25f : 0.5f);
}
};
class FAndroid_ASTCTargetPlatform : public FAndroidTargetPlatform
{
public:
FAndroid_ASTCTargetPlatform(bool bIsClient) : FAndroidTargetPlatform(bIsClient)
{
this->PlatformInfo = PlatformInfo::FindPlatformInfo("Android_ASTC");
}
virtual FString GetAndroidVariantName() const override
{
return TEXT("ASTC");
}
virtual FText DisplayName() const override
{
return LOCTEXT("Android_ASTC", "Android (ASTC)");
}
virtual bool SupportsTextureFormat(FName Format) const override
{
if (Format == AndroidTexFormat::NameASTC_4x4 ||
Format == AndroidTexFormat::NameASTC_6x6 ||
Format == AndroidTexFormat::NameASTC_8x8 ||
Format == AndroidTexFormat::NameASTC_10x10 ||
Format == AndroidTexFormat::NameASTC_12x12 ||
Format == AndroidTexFormat::NameAutoASTC)
{
return true;
}
return false;
}
virtual bool SupportsTextureFormatCategory(EAndroidTextureFormatCategory Category) const override
{
return Category == EAndroidTextureFormatCategory::ASTC;
}
#if WITH_ENGINE
virtual void GetTextureFormats(const UTexture* Texture, TArray< TArray<FName> >& OutFormats) const
{
check(Texture);
// we remap some of the defaults (with ASTC formats)
static FName FormatRemap[][2] =
{
// Default format: ASTC format:
{ { FName(TEXT("DXT1")) }, { FName(TEXT("ASTC_RGB")) } },
{ { FName(TEXT("DXT5")) }, { FName(TEXT("ASTC_RGBA")) } },
{ { FName(TEXT("DXT5n")) }, { FName(TEXT("ASTC_NormalAG")) } },
{ { FName(TEXT("BC5")) }, { FName(TEXT("ASTC_NormalRG")) } },
{ { FName(TEXT("BC6H")) }, { FName(TEXT("ASTC_RGB")) } },
{ { FName(TEXT("BC7")) }, { FName(TEXT("ASTC_RGBAuto")) } },
{ { FName(TEXT("AutoDXT")) }, { FName(TEXT("ASTC_RGBAuto")) } },
};
GetDefaultTextureFormatNamePerLayer(OutFormats.AddDefaulted_GetRef(), this, Texture, EngineSettings, true, false, 1);
for (FName& TextureFormatName : OutFormats.Last())
{
if (Texture->LODGroup == TEXTUREGROUP_Shadowmap)
{
// forward rendering only needs one channel for shadow maps
TextureFormatName = FName(TEXT("G8"));
}
else
{
// perform any remapping away from defaults
for (int32 RemapIndex = 0; RemapIndex < UE_ARRAY_COUNT(FormatRemap); ++RemapIndex)
{
if (TextureFormatName == FormatRemap[RemapIndex][0])
{
// we found a remapping
TextureFormatName = FormatRemap[RemapIndex][1];
break;
}
}
}
if (Texture->IsA(UTextureCube::StaticClass()))
{
const UTextureCube* Cube = CastChecked<UTextureCube>(Texture);
if (Cube != nullptr)
{
FTextureFormatSettings FormatSettings;
Cube->GetDefaultFormatSettings(FormatSettings);
if (FormatSettings.CompressionSettings == TC_ReflectionCapture && !FormatSettings.CompressionNone)
{
TextureFormatName = FName(TEXT("ETC2_RGBA"));
}
}
}
}
}
virtual void GetAllTextureFormats(TArray<FName>& OutFormats) const override
{
// we remap some of the defaults (with ASTC formats)
static FName FormatRemap[][2] =
{
// Default format: ASTC format:
{ { FName(TEXT("DXT1")) }, { FName(TEXT("ASTC_RGB")) } },
{ { FName(TEXT("DXT5")) }, { FName(TEXT("ASTC_RGBA")) } },
{ { FName(TEXT("DXT5n")) }, { FName(TEXT("ASTC_NormalAG")) } },
{ { FName(TEXT("BC5")) }, { FName(TEXT("ASTC_NormalRG")) } },
{ { FName(TEXT("BC6H")) }, { FName(TEXT("ASTC_RGB")) } },
{ { FName(TEXT("BC7")) }, { FName(TEXT("ASTC_RGBAuto")) } },
{ { FName(TEXT("AutoDXT")) }, { FName(TEXT("ASTC_RGBAuto")) } },
};
GetAllDefaultTextureFormats(this, OutFormats, true);
for (int32 RemapIndex = 0; RemapIndex < UE_ARRAY_COUNT(FormatRemap); ++RemapIndex)
{
OutFormats.Remove(FormatRemap[RemapIndex][0]);
OutFormats.AddUnique(FormatRemap[RemapIndex][1]);
}
}
#endif
virtual bool SupportedByExtensionsString(const FString& ExtensionsString, const int GLESVersion) const override
{
return ExtensionsString.Contains(TEXT("GL_KHR_texture_compression_astc_ldr"));
}
virtual FText GetVariantDisplayName() const override
{
return LOCTEXT("Android_ASTC_ShortName", "ASTC");
}
virtual float GetVariantPriority() const override
{
float Priority;
return (GConfig->GetFloat(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("TextureFormatPriority_ASTC"), Priority, GEngineIni) ?
Priority : 0.9f) * 10.0f + (IsClientOnly() ? 0.25f : 0.5f);
}
};
class FAndroid_ETC2TargetPlatform : public FAndroidTargetPlatform
{
public:
FAndroid_ETC2TargetPlatform(bool bIsClient) : FAndroidTargetPlatform(bIsClient)
{
this->PlatformInfo = PlatformInfo::FindPlatformInfo("Android_ETC2");
}
virtual FText DisplayName() const override
{
return LOCTEXT("Android_ETC2", "Android (ETC2)");
}
virtual FString GetAndroidVariantName() const override
{
return TEXT("ETC2");
}
virtual bool SupportsTextureFormat(FName Format) const override
{
if (Format == AndroidTexFormat::NameETC2_RGB ||
Format == AndroidTexFormat::NameETC2_RGBA ||
Format == AndroidTexFormat::NameAutoETC2)
{
return true;
}
return false;
}
virtual bool SupportsTextureFormatCategory(EAndroidTextureFormatCategory Category) const override
{
return Category == EAndroidTextureFormatCategory::ETC2;
}
virtual bool SupportedByExtensionsString(const FString& ExtensionsString, const int GLESVersion) const override
{
return GLESVersion >= 0x30000;
}
virtual FText GetVariantDisplayName() const override
{
return LOCTEXT("Android_ETC2_ShortName", "ETC2");
}
virtual float GetVariantPriority() const override
{
float Priority;
return (GConfig->GetFloat(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("TextureFormatPriority_ETC2"), Priority, GEngineIni) ?
Priority : 0.2f) * 10.0f + (IsClientOnly() ? 0.25f : 0.5f);
}
};
class FAndroid_MultiTargetPlatform : public FAndroidTargetPlatform
{
TArray<ITargetPlatform*> FormatTargetPlatforms;
FString FormatTargetString;
public:
FAndroid_MultiTargetPlatform(bool bIsClient) : FAndroidTargetPlatform(bIsClient)
{
this->PlatformInfo = PlatformInfo::FindPlatformInfo("Android_Multi");
}
// set up all of the multiple formats together into this one
void LoadFormats(TArray<FAndroidTargetPlatform*> SingleFormatTPs)
{
// sort formats by priority so higher priority formats are packaged (and thus used by the device) first
// note that we passed this by value, not ref, so we can sort it
SingleFormatTPs.Sort([](const FAndroidTargetPlatform& A, const FAndroidTargetPlatform& B)
{
float PriorityA = 0.f;
float PriorityB = 0.f;
FString VariantA = A.GetAndroidVariantName().Replace(TEXT("Client"), TEXT(""));
FString VariantB = B.GetAndroidVariantName().Replace(TEXT("Client"), TEXT(""));
GConfig->GetFloat(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), *(FString(TEXT("TextureFormatPriority_")) + VariantA), PriorityA, GEngineIni);
GConfig->GetFloat(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), *(FString(TEXT("TextureFormatPriority_")) + VariantB), PriorityB, GEngineIni);
return PriorityA > PriorityB;
});
FormatTargetPlatforms.Empty();
FormatTargetString = TEXT("");
TSet<FString> SeenFormats;
// Load the TargetPlatform module for each format
for (FAndroidTargetPlatform* SingleFormatTP : SingleFormatTPs)
{
// only use once each
if (SeenFormats.Contains(SingleFormatTP->GetAndroidVariantName()))
{
continue;
}
SeenFormats.Add(SingleFormatTP->GetAndroidVariantName());
bool bEnabled = false;
FString SettingsName = FString(TEXT("bMultiTargetFormat_")) + *SingleFormatTP->GetAndroidVariantName();
GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), *SettingsName, bEnabled, GEngineIni);
if (bEnabled)
{
if (FormatTargetPlatforms.Num())
{
FormatTargetString += TEXT(",");
}
FormatTargetString += SingleFormatTP->GetAndroidVariantName();
FormatTargetPlatforms.Add(SingleFormatTP);
}
}
PlatformInfo::UpdatePlatformDisplayName(TEXT("Android_Multi"), DisplayName());
}
virtual FString GetAndroidVariantName() const override
{
return TEXT("Multi");
}
virtual FText DisplayName() const override
{
return FText::Format(LOCTEXT("Android_Multi", "Android (Multi:{0})"), FText::FromString(FormatTargetString));
}
#if WITH_ENGINE
virtual void GetTextureFormats(const UTexture* Texture, TArray< TArray<FName> >& OutFormats) const
{
// Ask each platform variant to choose texture formats
for (ITargetPlatform* Platform : FormatTargetPlatforms)
{
TArray< TArray<FName> > PlatformFormats;
Platform->GetTextureFormats(Texture, PlatformFormats);
for (const TArray<FName>& FormatPerLayer : PlatformFormats)
{
OutFormats.AddUnique(FormatPerLayer);
}
}
}
virtual void GetAllTextureFormats(TArray<FName>& OutFormats) const override
{
// Ask each platform variant to choose texture formats
for (ITargetPlatform* Platform : FormatTargetPlatforms)
{
TArray<FName> PlatformFormats;
Platform->GetAllTextureFormats(PlatformFormats);
for (FName Format : PlatformFormats)
{
OutFormats.AddUnique(Format);
}
}
}
#endif
virtual FText GetVariantDisplayName() const override
{
return LOCTEXT("Android_Multi_ShortName", "Multi");
}
virtual float GetVariantPriority() const override
{
// lowest priority so specific variants are chosen first
return (IsClientOnly() ? 0.25f : 0.5f);
}
};
#undef LOCTEXT_NAMESPACE
@@ -1,98 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "Android/AndroidPlatformProperties.h"
#include "Interfaces/ITargetPlatformModule.h"
#include "Common/TargetPlatformBase.h"
#include "Interfaces/IAndroidDeviceDetection.h"
#include "Interfaces/IAndroidDeviceDetectionModule.h"
#include "AndroidTargetDevice.h"
#include "AndroidTargetPlatform.h"
#include "IAndroidTargetPlatformModule.h"
#define LOCTEXT_NAMESPACE "FAndroidTargetPlatformModule"
/**
* Module for the Android target platform.
*/
class FAndroidTargetPlatformModule : public IAndroidTargetPlatformModule
{
public:
/**
* Destructor.
*/
~FAndroidTargetPlatformModule( )
{
for (ITargetPlatform* TP : TargetPlatforms)
{
delete TP;
}
TargetPlatforms.Empty();
MultiPlatforms.Empty();
}
public:
// Begin ITargetPlatformModule interface
virtual TArray<ITargetPlatform*> GetTargetPlatforms() override
{
if (TargetPlatforms.Num() == 0 && FAndroidTargetPlatform::IsUsable())
{
for (int32 Type = 0; Type < 2; Type++)
{
bool bIsClient = Type == 1;
SinglePlatforms.Add(new FAndroidTargetPlatform(bIsClient));
SinglePlatforms.Add(new FAndroid_ASTCTargetPlatform(bIsClient));
SinglePlatforms.Add(new FAndroid_DXTTargetPlatform(bIsClient));
SinglePlatforms.Add(new FAndroid_ETC2TargetPlatform(bIsClient));
// thse are used in NotifyMultiSelectedFormatsChanged, so track in another array
MultiPlatforms.Add(new FAndroid_MultiTargetPlatform(bIsClient));
}
// join the single and the multi into one
TargetPlatforms.Append(SinglePlatforms);
TargetPlatforms.Append(MultiPlatforms);
// set up the multi platforms now that we have all the other platforms ready to go
NotifyMultiSelectedFormatsChanged();
}
return TargetPlatforms;
}
virtual void NotifyMultiSelectedFormatsChanged() override
{
for (FAndroid_MultiTargetPlatform* TP : MultiPlatforms)
{
TP->LoadFormats(SinglePlatforms);
}
// @todo multi needs to be passed this event!
}
// End ITargetPlatformModule interface
public:
// Begin IModuleInterface interface
virtual void StartupModule() override { }
virtual void ShutdownModule() override { }
// End IModuleInterface interface
private:
/** Holds the target platforms. */
TArray<ITargetPlatform*> TargetPlatforms;
TArray<FAndroidTargetPlatform*> SinglePlatforms;
TArray<FAndroid_MultiTargetPlatform*> MultiPlatforms;
};
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE( FAndroidTargetPlatformModule, AndroidTargetPlatform);
@@ -1,19 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
/*------------------------------------------------------------------------------------
IAndroid_MultiTargetPlatformModule interface
------------------------------------------------------------------------------------*/
#include "CoreMinimal.h"
#include "Interfaces/ITargetPlatformModule.h"
class IAndroidTargetPlatformModule : public ITargetPlatformModule
{
public:
//
// Called by AndroidRuntimeSettings to notify us when the user changes the selected texture formats for the Multi format
//
virtual void NotifyMultiSelectedFormatsChanged() = 0;
};
@@ -1,40 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class MetalShaderFormat : ModuleRules
{
public MetalShaderFormat(ReadOnlyTargetRules Target) : base(Target)
{
PrivateIncludePathModuleNames.Add("TargetPlatform");
PublicIncludePaths.Add("Runtime/Apple/MetalRHI/Public");
PrivateIncludePaths.AddRange(
new string[] {
"Developer/DerivedDataCache/Public",
}
);
PrivateDependencyModuleNames.AddRange(
new string[] {
"Core",
"RenderCore",
"ShaderCompilerCommon",
"ShaderPreprocessor",
"FileUtilities"
}
);
if (Target.Platform == UnrealTargetPlatform.Mac || Target.Platform == UnrealTargetPlatform.Win64)
{
AddEngineThirdPartyPrivateStaticDependencies(Target, "SPIRVReflect");
}
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
"DerivedDataCache",
}
);
}
}
@@ -1,134 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include <list>
#include <map>
enum EMetalGPUSemantics
{
EMetalGPUSemanticsMobile, // Mobile shaders for TBDR GPUs
EMetalGPUSemanticsTBDRDesktop, // Desktop shaders for TBDR GPUs
EMetalGPUSemanticsImmediateDesktop // Desktop shaders for Immediate GPUs
};
enum EMetalTypeBufferMode
{
EMetalTypeBufferModeRaw = 0, // No typed buffers
EMetalTypeBufferMode2DSRV = 1, // Buffer<> SRVs are typed via 2D textures, RWBuffer<> UAVs are raw buffers
EMetalTypeBufferModeTBSRV = 2, // Buffer<> SRVs are typed via texture-buffers, RWBuffer<> UAVs are raw buffers
EMetalTypeBufferMode2D = 3, // Buffer<> SRVs & RWBuffer<> UAVs are typed via 2D textures
EMetalTypeBufferModeTB = 4, // Buffer<> SRVs & RWBuffer<> UAVs are typed via texture-buffers
};
// Metal supports 16 across all HW
static const int32 MaxMetalSamplers = 16;
struct FShaderCompilerEnvironment;
struct SDMARange
{
unsigned SourceCB;
unsigned SourceOffset;
unsigned Size;
unsigned DestCBIndex;
unsigned DestCBPrecision;
unsigned DestOffset;
bool operator <(SDMARange const & Other) const
{
if (SourceCB == Other.SourceCB)
{
return SourceOffset < Other.SourceOffset;
}
return SourceCB < Other.SourceCB;
}
};
typedef std::list<SDMARange> TDMARangeList;
typedef std::map<unsigned, TDMARangeList> TCBDMARangeMap;
static void InsertRange( TCBDMARangeMap& CBAllRanges, unsigned SourceCB, unsigned SourceOffset, unsigned Size, unsigned DestCBIndex, unsigned DestCBPrecision, unsigned DestOffset )
{
check(SourceCB < (1 << 12));
check(DestCBIndex < (1 << 12));
check(DestCBPrecision < (1 << 8));
unsigned SourceDestCBKey = (SourceCB << 20) | (DestCBIndex << 8) | DestCBPrecision;
SDMARange Range = { SourceCB, SourceOffset, Size, DestCBIndex, DestCBPrecision, DestOffset };
TDMARangeList& CBRanges = CBAllRanges[SourceDestCBKey];
//printf("* InsertRange: %08x\t%u:%u - %u:%c:%u:%u\n", SourceDestCBKey, SourceCB, SourceOffset, DestCBIndex, DestCBPrecision, DestOffset, Size);
if (CBRanges.empty())
{
CBRanges.push_back(Range);
}
else
{
TDMARangeList::iterator Prev = CBRanges.end();
bool bAdded = false;
for (auto Iter = CBRanges.begin(); Iter != CBRanges.end(); ++Iter)
{
if (SourceOffset + Size <= Iter->SourceOffset)
{
if (Prev == CBRanges.end())
{
CBRanges.push_front(Range);
}
else
{
CBRanges.insert(Iter, Range);
}
bAdded = true;
break;
}
Prev = Iter;
}
if (!bAdded)
{
CBRanges.push_back(Range);
}
if (CBRanges.size() > 1)
{
// Try to merge ranges
bool bDirty = false;
do
{
bDirty = false;
TDMARangeList NewCBRanges;
for (auto Iter = CBRanges.begin(); Iter != CBRanges.end(); ++Iter)
{
if (Iter == CBRanges.begin())
{
Prev = CBRanges.begin();
}
else
{
if (Prev->SourceOffset + Prev->Size == Iter->SourceOffset && Prev->DestOffset + Prev->Size == Iter->DestOffset)
{
SDMARange Merged = *Prev;
Merged.Size = Prev->Size + Iter->Size;
NewCBRanges.pop_back();
NewCBRanges.push_back(Merged);
++Iter;
NewCBRanges.insert(NewCBRanges.end(), Iter, CBRanges.end());
bDirty = true;
break;
}
}
NewCBRanges.push_back(*Iter);
Prev = Iter;
}
CBRanges.swap(NewCBRanges);
}
while (bDirty);
}
}
}
@@ -1,188 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Misc/SecureHash.h"
#include "DerivedDataPluginInterface.h"
#include "ShaderCompilerCommon.h"
#include "HlslccDefinitions.h"
#include "MetalBackend.h"
struct FMetalShaderDebugInfoJob
{
FName ShaderFormat;
FSHAHash Hash;
FString CompilerVersion;
FString MinOSVersion;
FString DebugInfo;
FString MathMode;
FString Standard;
uint32 SourceCRCLen;
uint32 SourceCRC;
FString MetalCode;
};
struct FMetalShaderDebugInfo
{
uint32 UncompressedSize;
TArray<uint8> CompressedData;
friend FArchive& operator<<( FArchive& Ar, FMetalShaderDebugInfo& Info )
{
Ar << Info.UncompressedSize << Info.CompressedData;
return Ar;
}
};
class FMetalShaderDebugInfoCooker : public FDerivedDataPluginInterface
{
public:
FMetalShaderDebugInfoCooker(FMetalShaderDebugInfoJob& Job);
virtual ~FMetalShaderDebugInfoCooker();
#if PLATFORM_MAC || PLATFORM_IOS
#pragma mark - FDerivedDataPluginInterface Interface -
#endif
virtual const TCHAR* GetPluginName() const override;
virtual const TCHAR* GetVersionString() const override;
virtual FString GetPluginSpecificCacheKeySuffix() const override;
virtual bool IsBuildThreadsafe() const override;
virtual bool Build(TArray<uint8>& OutData) override;
private:
FMetalShaderDebugInfoJob& Job;
FMetalShaderDebugInfo Output;
};
struct FMetalShaderBytecodeJob
{
FName ShaderFormat;
FSHAHash Hash;
FString Defines;
FString TmpFolder;
FString InputFile;
FString InputPCHFile;
FString OutputFile;
FString OutputObjectFile;
FString CompilerVersion;
FString MinOSVersion;
FString DebugInfo;
FString MathMode;
FString Standard;
FString IncludeDir;
uint32 SourceCRCLen;
uint32 SourceCRC;
bool bRetainObjectFile;
bool bCompileAsPCH;
FString Message;
FString Results;
FString Errors;
int32 ReturnCode;
};
struct FMetalShaderBytecode
{
FString NativePath;
TArray<uint8> OutputFile;
TArray<uint8> ObjectFile;
friend FArchive& operator<<( FArchive& Ar, FMetalShaderBytecode& Info )
{
Ar << Info.NativePath << Info.OutputFile << Info.ObjectFile;
return Ar;
}
};
class FMetalShaderBytecodeCooker : public FDerivedDataPluginInterface
{
public:
FMetalShaderBytecodeCooker(FMetalShaderBytecodeJob& Job);
virtual ~FMetalShaderBytecodeCooker();
#if PLATFORM_MAC || PLATFORM_IOS
#pragma mark - FDerivedDataPluginInterface Interface -
#endif
virtual const TCHAR* GetPluginName() const override;
virtual const TCHAR* GetVersionString() const override;
virtual FString GetPluginSpecificCacheKeySuffix() const override;
virtual bool IsBuildThreadsafe() const override;
virtual bool Build(TArray<uint8>& OutData) override;
private:
FMetalShaderBytecodeJob& Job;
FMetalShaderBytecode Output;
};
struct FMetalShaderPreprocessed
{
FString NativePath;
TArray<uint8> OutputFile;
TArray<uint8> ObjectFile;
friend FArchive& operator<<( FArchive& Ar, FMetalShaderPreprocessed& Info )
{
Ar << Info.NativePath << Info.OutputFile << Info.ObjectFile;
return Ar;
}
};
struct FMetalShaderOutputJob
{
const FShaderCompilerInput& Input;
FShaderCompilerOutput& Output;
const FString& WorkingDirectory;
FString PreprocessedShader;
FSHAHash GUIDHash;
uint8 VersionEnum;
uint32 CCFlags;
EHlslCompileTarget HlslCompilerTarget;
EHlslCompileTarget MetalCompilerTarget;
EMetalGPUSemantics Semantics;
EMetalTypeBufferMode TypeMode;
uint32 MaxUnrollLoops;
EHlslShaderFrequency Frequency;
bool bDumpDebugInfo;
FString Standard;
FString MinOSVersion;
};
class FMetalShaderOutputCooker : public FDerivedDataPluginInterface
{
public:
FMetalShaderOutputCooker(const FShaderCompilerInput& _Input,FShaderCompilerOutput& Output,const FString& WorkingDirectory, FString PreprocessedShader, FSHAHash GUIDHash, uint8 VersionEnum, uint32 CCFlags, EHlslCompileTarget HlslCompilerTarget, EHlslCompileTarget MetalCompilerTarget, EMetalGPUSemantics Semantics, EMetalTypeBufferMode TypeMode, uint32 MaxUnrollLoops, EHlslShaderFrequency Frequency, bool bDumpDebugInfo, FString Standard, FString MinOSVersion);
virtual ~FMetalShaderOutputCooker();
#if PLATFORM_MAC || PLATFORM_IOS
#pragma mark - FDerivedDataPluginInterface Interface -
#endif
virtual const TCHAR* GetPluginName() const override;
virtual const TCHAR* GetVersionString() const override;
virtual FString GetPluginSpecificCacheKeySuffix() const override;
virtual bool IsBuildThreadsafe() const override;
virtual bool Build(TArray<uint8>& OutData) override;
private:
const FShaderCompilerInput& Input;
FShaderCompilerOutput& Output;
const FString& WorkingDirectory;
FString PreprocessedShader;
FSHAHash GUIDHash;
uint8 VersionEnum;
uint32 CCFlags;
int32 IABTier;
EHlslCompileTarget HlslCompilerTarget;
EHlslCompileTarget MetalCompilerTarget;
EMetalGPUSemantics Semantics;
EMetalTypeBufferMode TypeMode;
uint32 MaxUnrollLoops;
EHlslShaderFrequency Frequency;
bool bDumpDebugInfo;
FString Standard;
FString MinOSVersion;
};
@@ -1,902 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetalShaderFormat.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/IShaderFormat.h"
#include "Interfaces/IShaderFormatModule.h"
#include "ShaderCore.h"
#include "ShaderCodeArchive.h"
#include "hlslcc.h"
#include "MetalShaderResources.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformFilemanager.h"
#include "Serialization/Archive.h"
#include "Misc/ConfigCacheIni.h"
#include "MetalBackend.h"
#include "Misc/FileHelper.h"
#include "FileUtilities/ZipArchiveWriter.h"
#include "MetalDerivedData.h"
DEFINE_LOG_CATEGORY(LogMetalCompilerSetup)
DEFINE_LOG_CATEGORY(LogMetalShaderCompiler)
#define WRITE_METAL_SHADER_SOURCE_ARCHIVE 0
// Set this define to get additional logging information about Metal toolchain setup.
#define CHECK_METAL_COMPILER_TOOLCHAIN_SETUP 0
extern bool StripShader_Metal(TArray<uint8>& Code, class FString const& DebugPath, bool const bNative);
extern uint64 AppendShader_Metal(class FName const& Format, class FString const& ArchivePath, const FSHAHash& Hash, TArray<uint8>& Code);
extern bool FinalizeLibrary_Metal(class FName const& Format, class FString const& ArchivePath, class FString const& LibraryPath, TSet<uint64> const& Shaders, class FString const& DebugOutputDir);
class FMetalShaderFormat : public IShaderFormat
{
public:
enum
{
HEADER_VERSION = 71,
};
struct FVersion
{
uint16 XcodeVersion;
uint16 HLSLCCMinor : 8;
uint16 Format : 8;
};
FMetalShaderFormat()
{
FMetalCompilerToolchain::CreateAndInit();
}
virtual ~FMetalShaderFormat()
{
FMetalCompilerToolchain::Destroy();
}
virtual uint32 GetVersion(FName Format) const override final
{
return GetMetalFormatVersion(Format);
}
virtual void GetSupportedFormats(TArray<FName>& OutFormats) const override final
{
OutFormats.Add(NAME_SF_METAL);
OutFormats.Add(NAME_SF_METAL_MRT);
OutFormats.Add(NAME_SF_METAL_TVOS);
OutFormats.Add(NAME_SF_METAL_MRT_TVOS);
OutFormats.Add(NAME_SF_METAL_SM5_NOTESS);
OutFormats.Add(NAME_SF_METAL_SM5);
OutFormats.Add(NAME_SF_METAL_MACES3_1);
OutFormats.Add(NAME_SF_METAL_MRT_MAC);
}
virtual void CompileShader(FName Format, const struct FShaderCompilerInput& Input, struct FShaderCompilerOutput& Output,const FString& WorkingDirectory) const override final
{
check(Format == NAME_SF_METAL || Format == NAME_SF_METAL_MRT || Format == NAME_SF_METAL_TVOS || Format == NAME_SF_METAL_MRT_TVOS || Format == NAME_SF_METAL_SM5_NOTESS || Format == NAME_SF_METAL_SM5 || Format == NAME_SF_METAL_MACES3_1 || Format == NAME_SF_METAL_MRT_MAC);
CompileShader_Metal(Input, Output, WorkingDirectory);
}
virtual bool CanStripShaderCode(bool const bNativeFormat) const override final
{
return CanCompileBinaryShaders() && bNativeFormat;
}
virtual bool StripShaderCode( TArray<uint8>& Code, FString const& DebugOutputDir, bool const bNative ) const override final
{
return StripShader_Metal(Code, DebugOutputDir, bNative);
}
virtual bool SupportsShaderArchives() const override
{
return CanCompileBinaryShaders();
}
virtual bool CreateShaderArchive(FString const& LibraryName,
FName Format,
const FString& WorkingDirectory,
const FString& OutputDir,
const FString& DebugOutputDir,
const FSerializedShaderArchive& InSerializedShaders,
const TArray<TArray<uint8>>& ShaderCode,
TArray<FString>* OutputFiles) const override final
{
const int32 NumShadersPerLibrary = 10000;
check(LibraryName.Len() > 0);
check(Format == NAME_SF_METAL || Format == NAME_SF_METAL_MRT || Format == NAME_SF_METAL_TVOS || Format == NAME_SF_METAL_MRT_TVOS || Format == NAME_SF_METAL_SM5_NOTESS || Format == NAME_SF_METAL_SM5 || Format == NAME_SF_METAL_MACES3_1 || Format == NAME_SF_METAL_MRT_MAC);
const FString ArchivePath = (WorkingDirectory / Format.GetPlainNameString());
IFileManager::Get().DeleteDirectory(*ArchivePath, false, true);
IFileManager::Get().MakeDirectory(*ArchivePath);
FSerializedShaderArchive SerializedShaders(InSerializedShaders);
check(SerializedShaders.GetNumShaders() == ShaderCode.Num());
TArray<uint8> StrippedShaderCode;
TArray<uint8> TempShaderCode;
TArray<TSet<uint64>> SubLibraries;
for (int32 ShaderIndex = 0; ShaderIndex < SerializedShaders.GetNumShaders(); ++ShaderIndex)
{
SerializedShaders.DecompressShader(ShaderIndex, ShaderCode, TempShaderCode);
StripShader_Metal(TempShaderCode, DebugOutputDir, true);
uint64 ShaderId = AppendShader_Metal(Format, ArchivePath, SerializedShaders.ShaderHashes[ShaderIndex], TempShaderCode);
uint32 LibraryIndex = ShaderIndex / NumShadersPerLibrary;
if (ShaderId)
{
if (SubLibraries.Num() <= (int32)LibraryIndex)
{
SubLibraries.Add(TSet<uint64>());
}
SubLibraries[LibraryIndex].Add(ShaderId);
}
FShaderCodeEntry& ShaderEntry = SerializedShaders.ShaderEntries[ShaderIndex];
ShaderEntry.Size = TempShaderCode.Num();
ShaderEntry.UncompressedSize = TempShaderCode.Num();
StrippedShaderCode.Append(TempShaderCode);
}
SerializedShaders.Finalize();
bool bOK = false;
FString LibraryPlatformName = FString::Printf(TEXT("%s_%s"), *LibraryName, *Format.GetPlainNameString());
volatile int32 CompiledLibraries = 0;
TArray<FGraphEventRef> Tasks;
for (uint32 Index = 0; Index < (uint32)SubLibraries.Num(); Index++)
{
TSet<uint64>& PartialShaders = SubLibraries[Index];
FString LibraryPath = (OutputDir / LibraryPlatformName) + FString::Printf(TEXT(".%d"), Index) + FMetalCompilerToolchain::MetalLibraryExtension;
if (OutputFiles)
{
OutputFiles->Add(LibraryPath);
}
// Enqueue the library compilation as a task so we can go wide
FGraphEventRef CompletionFence = FFunctionGraphTask::CreateAndDispatchWhenReady([Format, ArchivePath, LibraryPath, PartialShaders, DebugOutputDir, &CompiledLibraries]()
{
if (FinalizeLibrary_Metal(Format, ArchivePath, LibraryPath, PartialShaders, DebugOutputDir))
{
FPlatformAtomics::InterlockedIncrement(&CompiledLibraries);
}
}, TStatId(), NULL, ENamedThreads::AnyThread);
Tasks.Add(CompletionFence);
}
#if WITH_ENGINE
FGraphEventRef DebugDataCompletionFence = FFunctionGraphTask::CreateAndDispatchWhenReady([Format, OutputDir, LibraryPlatformName, DebugOutputDir]()
{
//TODO add a check in here - this will only work if we have shader archiving with debug info set.
//We want to archive all the metal shader source files so that they can be unarchived into a debug location
//This allows the debugging of optimised metal shaders within the xcode tool set
//Currently using the 'tar' system tool to create a compressed tape archive
//Place the archive in the same position as the .metallib file
FString CompressedDir = (OutputDir / TEXT("../MetaData/ShaderDebug/"));
IFileManager::Get().MakeDirectory(*CompressedDir, true);
FString CompressedPath = (CompressedDir / LibraryPlatformName) + TEXT(".zip");
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
IFileHandle* ZipFile = PlatformFile.OpenWrite(*CompressedPath);
if (ZipFile)
{
FZipArchiveWriter* ZipWriter = new FZipArchiveWriter(ZipFile);
//Find the metal source files
TArray<FString> FilesToArchive;
IFileManager::Get().FindFilesRecursive(FilesToArchive, *DebugOutputDir, TEXT("*.metal"), true, false, false);
//Write the local file names into the target file
const FString DebugDir = DebugOutputDir / *Format.GetPlainNameString();
for (FString FileName : FilesToArchive)
{
TArray<uint8> FileData;
FFileHelper::LoadFileToArray(FileData, *FileName);
FPaths::MakePathRelativeTo(FileName, *DebugDir);
ZipWriter->AddFile(FileName, FileData, FDateTime::Now());
}
delete ZipWriter;
ZipWriter = nullptr;
}
else
{
UE_LOG(LogShaders, Error, TEXT("Failed to create Metal debug .zip output file \"%s\". Debug .zip export will be disabled."), *CompressedPath);
}
}, TStatId(), NULL, ENamedThreads::AnyThread);
Tasks.Add(DebugDataCompletionFence);
#endif // WITH_ENGINE
// Wait for tasks
for (auto& Task : Tasks)
{
FTaskGraphInterface::Get().WaitUntilTaskCompletes(Task);
}
if (CompiledLibraries == SubLibraries.Num())
{
FString BinaryShaderFile = (OutputDir / LibraryPlatformName) + FMetalCompilerToolchain::MetalMapExtension;
FArchive* BinaryShaderAr = IFileManager::Get().CreateFileWriter(*BinaryShaderFile);
if (BinaryShaderAr != NULL)
{
FMetalShaderLibraryHeader Header;
Header.Format = Format.GetPlainNameString();
Header.NumLibraries = SubLibraries.Num();
Header.NumShadersPerLibrary = NumShadersPerLibrary;
*BinaryShaderAr << Header;
*BinaryShaderAr << SerializedShaders;
*BinaryShaderAr << StrippedShaderCode;
BinaryShaderAr->Flush();
delete BinaryShaderAr;
if (OutputFiles)
{
OutputFiles->Add(BinaryShaderFile);
}
bOK = true;
}
}
return bOK;
//Map.Format = Format.GetPlainNameString();
}
virtual bool CanCompileBinaryShaders() const override final
{
#if PLATFORM_MAC
return FPlatformMisc::IsSupportedXcodeVersionInstalled();
#else
return FMetalCompilerToolchain::Get()->IsCompilerAvailable();
#endif
}
virtual const TCHAR* GetPlatformIncludeDirectory() const
{
return TEXT("Metal");
}
};
uint32 GetMetalFormatVersion(FName Format)
{
static_assert(sizeof(FMetalShaderFormat::FVersion) == sizeof(uint32), "Out of bits!");
union
{
FMetalShaderFormat::FVersion Version;
uint32 Raw;
} Version;
// If there's no compiler on this machine, this is irrelevant so just return 0
if (!FMetalCompilerToolchain::Get()->IsCompilerAvailable())
{
return 0;
}
// Include the Xcode version when the .ini settings instruct us to do so.
bool bAddXcodeVersionInShaderVersion = false;
EShaderPlatform ShaderPlatform = FMetalCompilerToolchain::MetalShaderFormatToLegacyShaderPlatform(Format);
if(FMetalCompilerToolchain::IsMobile(ShaderPlatform))
{
GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("XcodeVersionInShaderVersion"), bAddXcodeVersionInShaderVersion, GEngineIni);
}
else
{
GConfig->GetBool(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("XcodeVersionInShaderVersion"), bAddXcodeVersionInShaderVersion, GEngineIni);
}
// We are going to use the LLVM target version instead of the Xcode build, since standalone metal toolchains do not require xcode
FMetalCompilerToolchain::PackedVersion TargetVersion = FMetalCompilerToolchain::Get()->GetTargetVersion(ShaderPlatform);
const FString& CompilerVersionString = FMetalCompilerToolchain::Get()->GetCompilerVersionString(ShaderPlatform);
union HashMe
{
struct
{
uint16 top;
uint16 bottom;
};
uint32 Value;
};
HashMe V;
V.Value = GetTypeHash(CompilerVersionString);
uint16 HashValue = V.top ^ V.bottom;
if (!FApp::IsEngineInstalled() && bAddXcodeVersionInShaderVersion)
{
// For local development we'll mix in the LLVM target version.
HashValue ^= TargetVersion.Major;
HashValue ^= TargetVersion.Minor;
HashValue ^= TargetVersion.Patch;
}
else
{
// In the other case (ie, shipping editor binary distributions)
// We will only mix the hash of the version string
}
Version.Version.XcodeVersion = HashValue;
Version.Version.Format = FMetalShaderFormat::HEADER_VERSION;
Version.Version.HLSLCCMinor = HLSLCC_VersionMinor;
// Check that we didn't overwrite any bits
check(Version.Version.XcodeVersion == HashValue);
check(Version.Version.Format == FMetalShaderFormat::HEADER_VERSION);
check(Version.Version.HLSLCCMinor == HLSLCC_VersionMinor);
return Version.Raw;
}
/**
* Module for Metal shaders
*/
static IShaderFormat* Singleton = nullptr;
class FMetalShaderFormatModule : public IShaderFormatModule
{
public:
virtual ~FMetalShaderFormatModule()
{
Singleton = nullptr;
}
virtual IShaderFormat* GetShaderFormat()
{
return Singleton;
}
virtual void StartupModule() override
{
Singleton = new FMetalShaderFormat();
}
virtual void ShutdownModule() override
{
delete Singleton;
Singleton = nullptr;
}
};
IMPLEMENT_MODULE( FMetalShaderFormatModule, MetalShaderFormat);
static FMetalCompilerToolchain::EMetalToolchainStatus ParseCompilerVersionAndTarget(const FString& OutputOfMetalDashV, FString& VersionString, FMetalCompilerToolchain::PackedVersion& PackedVersionNumber, FMetalCompilerToolchain::PackedVersion& PackedTargetNumber)
{
/*
Output of metal -v might look like this:
Apple LLVM version 902.11 (metalfe-902.11.1)
Target: air64-apple-darwin19.5.0
Thread model: posix
InstalledDir: C:\Program Files\Metal Developer Tools\ios\bin
*/
TArray<FString> Lines;
OutputOfMetalDashV.ParseIntoArrayLines(Lines);
{
VersionString = Lines[0];
FString& Version = Lines[0];
check(!Version.IsEmpty());
int32 Major = 0, Minor = 0, Patch = 0;
int32 NumResults = 0;
#if !PLATFORM_WINDOWS
NumResults = sscanf(TCHAR_TO_ANSI(*Version), "Apple LLVM version %d.%d.%d", &Major, &Minor, &Patch);
#else
NumResults = swscanf_s(*Version, TEXT("Apple LLVM version %d.%d.%d"), &Major, &Minor, &Patch);
#endif
PackedVersionNumber.Major = Major;
PackedVersionNumber.Minor = Minor;
PackedVersionNumber.Patch = Patch;
}
if (PackedVersionNumber.Version == 0)
{
return FMetalCompilerToolchain::EMetalToolchainStatus::CouldNotParseCompilerVersion;
}
{
FString& FormatVersion = Lines[1];
int32 Major = 0, Minor = 0, Patch = 0;
int32 NumResults = 0;
#if !PLATFORM_WINDOWS
NumResults = sscanf(TCHAR_TO_ANSI(*FormatVersion), "Target: air64-apple-darwin%d.%d.%d", &Major, &Minor, &Patch);
#else
NumResults = swscanf_s(*FormatVersion, TEXT("Target: air64-apple-darwin%d.%d.%d"), &Major, &Minor, &Patch);
#endif
PackedTargetNumber.Major = Major;
PackedTargetNumber.Minor = Minor;
PackedTargetNumber.Patch = Patch;
}
if (PackedTargetNumber.Version == 0)
{
return FMetalCompilerToolchain::EMetalToolchainStatus::CouldNotParseTargetVersion;
}
return FMetalCompilerToolchain::EMetalToolchainStatus::Success;
}
static FMetalCompilerToolchain::EMetalToolchainStatus ParseLibraryToolpath(const FString& OutputOfMetalSearchDirs, FString& LibraryPath)
{
static FString LibraryPrefix(TEXT("libraries: =%s"));
TArray<FString> Lines;
OutputOfMetalSearchDirs.ParseIntoArrayLines(Lines);
{
FString& LibraryLine = Lines[1];
LibraryPath = LibraryLine.RightChop(LibraryPrefix.Len());
if (!FPaths::DirectoryExists(LibraryPath))
{
return FMetalCompilerToolchain::EMetalToolchainStatus::CouldNotFindMetalStdLib;
}
FPaths::Combine(LibraryPath, TEXT("include"), TEXT("metal"));
if (!FPaths::DirectoryExists(LibraryPath))
{
return FMetalCompilerToolchain::EMetalToolchainStatus::CouldNotFindMetalStdLib;
}
}
return FMetalCompilerToolchain::EMetalToolchainStatus::Success;
}
FMetalCompilerToolchain* FMetalCompilerToolchain::Singleton = nullptr;
FString FMetalCompilerToolchain::MetalExtention(TEXT(".metal"));
FString FMetalCompilerToolchain::MetalLibraryExtension(TEXT(".metallib"));
FString FMetalCompilerToolchain::MetalObjectExtension(TEXT(".air"));
#if PLATFORM_WINDOWS
FString FMetalCompilerToolchain::MetalFrontendBinary(TEXT("metal.exe"));
FString FMetalCompilerToolchain::MetalArBinary(TEXT("metal-ar.exe"));
FString FMetalCompilerToolchain::MetalLibraryBinary(TEXT("metallib.exe"));
#else
FString FMetalCompilerToolchain::MetalFrontendBinary(TEXT("metal"));
FString FMetalCompilerToolchain::MetalArBinary(TEXT("metal-ar"));
FString FMetalCompilerToolchain::MetalLibraryBinary(TEXT("metallib"));
#endif
FString FMetalCompilerToolchain::MetalMapExtension(TEXT(".metalmap"));
FString FMetalCompilerToolchain::XcrunPath(TEXT("/usr/bin/xcrun"));
FString FMetalCompilerToolchain::MetalMacSDK(TEXT("macosx"));
FString FMetalCompilerToolchain::MetalMobileSDK(TEXT("iphoneos"));
FString FMetalCompilerToolchain::DefaultWindowsToolchainPath(TEXT("c:/Program Files/Metal Developer Tools"));
// Static methods
void FMetalCompilerToolchain::CreateAndInit()
{
Singleton = new FMetalCompilerToolchain;
Singleton->Init();
}
void FMetalCompilerToolchain::Destroy()
{
Singleton->Teardown();
delete Singleton;
Singleton = nullptr;
}
EShaderPlatform FMetalCompilerToolchain::MetalShaderFormatToLegacyShaderPlatform(FName ShaderFormat)
{
if (ShaderFormat == NAME_SF_METAL) return SP_METAL;
if (ShaderFormat == NAME_SF_METAL_MRT) return SP_METAL_MRT;
if (ShaderFormat == NAME_SF_METAL_TVOS) return SP_METAL_TVOS;
if (ShaderFormat == NAME_SF_METAL_MRT_TVOS) return SP_METAL_MRT_TVOS;
if (ShaderFormat == NAME_SF_METAL_MRT_MAC) return SP_METAL_MRT_MAC;
if (ShaderFormat == NAME_SF_METAL_SM5) return SP_METAL_SM5;
if (ShaderFormat == NAME_SF_METAL_SM5_NOTESS) return SP_METAL_SM5_NOTESS;
if (ShaderFormat == NAME_SF_METAL_MACES3_1) return SP_METAL_MACES3_1;
return SP_NumPlatforms;
}
// Instance methods
FMetalCompilerToolchain::PackedVersion FMetalCompilerToolchain::GetCompilerVersion(EShaderPlatform Platform) const
{
if (this->IsMobile(Platform))
{
return this->MetalCompilerVersion[AppleSDKMobile];
}
return this->MetalCompilerVersion[AppleSDKMac];
}
FMetalCompilerToolchain::PackedVersion FMetalCompilerToolchain::GetTargetVersion(EShaderPlatform Platform) const
{
if (this->IsMobile(Platform))
{
return this->MetalTargetVersion[AppleSDKMobile];
}
return this->MetalTargetVersion[AppleSDKMac];
}
const FString& FMetalCompilerToolchain::GetCompilerVersionString(EShaderPlatform Platform) const
{
if (this->IsMobile(Platform))
{
return this->MetalCompilerVersionString[AppleSDKMobile];
}
return this->MetalCompilerVersionString[AppleSDKMac];
}
void FMetalCompilerToolchain::Init()
{
bToolchainAvailable = false;
bToolchainBinariesPresent = false;
bSkipPCH = true;
#if PLATFORM_MAC
EMetalToolchainStatus Result = DoMacNativeSetup();
#else
EMetalToolchainStatus Result = DoWindowsSetup();
#endif
if (Result != EMetalToolchainStatus::Success)
{
#if CHECK_METAL_COMPILER_TOOLCHAIN_SETUP
UE_LOG(LogMetalCompilerSetup, Warning, TEXT("Metal compiler not found. Shaders will be stored as text."));
#endif
bToolchainAvailable = false;
}
else
{
Result = FetchCompilerVersion();
if (Result != EMetalToolchainStatus::Success)
{
UE_LOG(LogMetalCompilerSetup, Log, TEXT("Could not parse compiler version."));
}
Result = FetchMetalStandardLibraryPath();
if (Result != EMetalToolchainStatus::Success)
{
UE_LOG(LogMetalCompilerSetup, Warning, TEXT("Could not parse metal_stdlib path. Will not use PCH."));
bSkipPCH = true;
// This is not really an error since we can compile without the PCH just fine.
Result = EMetalToolchainStatus::Success;
}
else
{
// This is forced off for now. If we wish to re-enable it a lot of testing should be done.
//bSkipPCH = false;
}
bToolchainAvailable = true;
}
#if CHECK_METAL_COMPILER_TOOLCHAIN_SETUP
if (Result == EMetalToolchainStatus::Success)
{
check(IsCompilerAvailable());
UE_LOG(LogMetalCompilerSetup, Log, TEXT("Metal toolchain setup complete."));
#if PLATFORM_WINDOWS
UE_LOG(LogMetalCompilerSetup, Log, TEXT("Using Local Metal compiler"));
UE_LOG(LogMetalCompilerSetup, Log, TEXT("Mac metalfe found at %s"), *MetalFrontendBinaryCommand[AppleSDKMac]);
UE_LOG(LogMetalCompilerSetup, Log, TEXT("Mobile metalfe found at %s"), *MetalFrontendBinaryCommand[AppleSDKMobile]);
#else
UE_LOG(LogMetalCompilerSetup, Log, TEXT("Using Local Metal compiler"));
#endif
UE_LOG(LogMetalCompilerSetup, Log, TEXT("Mac metalfe version %s"), *MetalCompilerVersionString[AppleSDKMac]);
UE_LOG(LogMetalCompilerSetup, Log, TEXT("Mobile metalfe version %s"), *MetalCompilerVersionString[AppleSDKMobile]);
}
else
{
UE_LOG(LogMetalCompilerSetup, Warning, TEXT("Failed to set up Metal toolchain. See log above. Shaders will not be compiled offline."));
}
#endif
}
void FMetalCompilerToolchain::Teardown()
{
// remove temporaries
if (this->LocalTempFolder.IsEmpty())
{
return;
}
if (!FPaths::DirectoryExists(this->LocalTempFolder))
{
return;
}
bool bSuccess = IFileManager::Get().DeleteDirectory(*this->LocalTempFolder, false, true);
if (!bSuccess)
{
UE_LOG(LogMetalCompilerSetup, Warning, TEXT("Could not delete temporary %s"), *this->LocalTempFolder);
}
}
FMetalCompilerToolchain::EMetalToolchainStatus FMetalCompilerToolchain::FetchCompilerVersion()
{
EMetalToolchainStatus Result = EMetalToolchainStatus::Success;
{
int32 ReturnCode = 0;
FString StdOut;
// metal -v writes its output to stderr
// But the underlying (windows) implementation of CreateProc puts everything into one pipe, which is written to StdOut.
bool bResult = this->ExecMetalFrontend(AppleSDKMac, TEXT("-v"), &ReturnCode, &StdOut, &StdOut);
check(bResult);
Result = ParseCompilerVersionAndTarget(StdOut, this->MetalCompilerVersionString[AppleSDKMac], this->MetalCompilerVersion[AppleSDKMac], this->MetalTargetVersion[AppleSDKMac]);
if (Result != EMetalToolchainStatus::Success)
{
return Result;
}
}
{
int32 ReturnCode = 0;
FString StdOut;
// metal -v writes its output to stderr
bool bResult = this->ExecMetalFrontend(AppleSDKMobile, TEXT("-v"), &ReturnCode, &StdOut, &StdOut);
check(bResult);
Result = ParseCompilerVersionAndTarget(StdOut, this->MetalCompilerVersionString[AppleSDKMobile], this->MetalCompilerVersion[AppleSDKMobile], this->MetalTargetVersion[AppleSDKMobile]);
}
return Result;
}
FMetalCompilerToolchain::EMetalToolchainStatus FMetalCompilerToolchain::FetchMetalStandardLibraryPath()
{
// if we've already decided to skip compiling a PCH we don't need this path at all.
if (this->bSkipPCH)
{
return EMetalToolchainStatus::Success;
}
EMetalToolchainStatus Result = EMetalToolchainStatus::Success;
{
int32 ReturnCode = 0;
FString StdOut, StdErr;
bool bResult = this->ExecMetalFrontend(AppleSDKMac, TEXT("--print-search-dirs"), &ReturnCode, &StdOut, &StdErr);
check(bResult);
Result = ParseLibraryToolpath(StdOut, this->MetalStandardLibraryPath[AppleSDKMac]);
if (Result != EMetalToolchainStatus::Success)
{
return Result;
}
}
{
int32 ReturnCode = 0;
FString StdOut, StdErr;
bool bResult = this->ExecMetalFrontend(AppleSDKMobile, TEXT("--print-search-dirs"), &ReturnCode, &StdOut, &StdErr);
check(bResult);
Result = ParseLibraryToolpath(StdOut, this->MetalStandardLibraryPath[AppleSDKMobile]);
}
return Result;
}
#if PLATFORM_MAC
FMetalCompilerToolchain::EMetalToolchainStatus FMetalCompilerToolchain::DoMacNativeSetup()
{
int32 ReturnCode = 0;
FString StdOut, StdErr;
bool bSuccess = this->ExecGenericCommand(*XcrunPath, *FString::Printf(TEXT("-sdk %s --find %s"), *this->MetalMacSDK, *this->MetalFrontendBinary), &ReturnCode, &StdOut, &StdErr);
bSuccess |= FPaths::FileExists(StdOut);
if(!bSuccess || ReturnCode > 0)
{
return EMetalToolchainStatus::ToolchainNotFound;
}
this->bToolchainBinariesPresent = true;
return EMetalToolchainStatus::Success;
}
#endif
#if PLATFORM_WINDOWS
FMetalCompilerToolchain::EMetalToolchainStatus FMetalCompilerToolchain::DoWindowsSetup()
{
int32 Result = 0;
FString ToolchainBase;
GConfig->GetString(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("WindowsMetalToolchainOverride"), ToolchainBase, GEngineIni);
const bool bUseOverride = (!ToolchainBase.IsEmpty() && FPaths::DirectoryExists(ToolchainBase));
if (!bUseOverride)
{
ToolchainBase = DefaultWindowsToolchainPath;
}
// Look for the windows native toolchain
MetalFrontendBinaryCommand[AppleSDKMac] = ToolchainBase / TEXT("macos") / TEXT("bin") / MetalFrontendBinary;
MetalFrontendBinaryCommand[AppleSDKMobile] = ToolchainBase / TEXT("ios") / TEXT("bin") / MetalFrontendBinary;
bool bUseLocalMetalToolchain = FPaths::FileExists(*MetalFrontendBinaryCommand[AppleSDKMac]) && FPaths::FileExists(*MetalFrontendBinaryCommand[AppleSDKMobile]);
if (!bUseLocalMetalToolchain)
{
#if CHECK_METAL_COMPILER_TOOLCHAIN_SETUP
UE_LOG(LogMetalCompilerSetup, Display, TEXT("Searching for Metal toolchain, but it doesn't appear to be installed."));
UE_LOG(LogMetalCompilerSetup, Display, TEXT("Searched for %s and %s"), *MetalFrontendBinaryCommand[AppleSDKMac], *MetalFrontendBinaryCommand[AppleSDKMobile]);
#endif
return EMetalToolchainStatus::ToolchainNotFound;
}
MetalArBinaryCommand[AppleSDKMac] = ToolchainBase / TEXT("macos") / TEXT("bin") / MetalArBinary;
MetalArBinaryCommand[AppleSDKMobile] = ToolchainBase / TEXT("ios") / TEXT("bin") / MetalArBinary;
MetalLibBinaryCommand[AppleSDKMac] = ToolchainBase / TEXT("macos") / TEXT("bin") / MetalLibraryBinary;
MetalLibBinaryCommand[AppleSDKMobile] = ToolchainBase / TEXT("ios") / TEXT("bin") / MetalLibraryBinary;
if (!FPaths::FileExists(*MetalArBinaryCommand[AppleSDKMac]) ||
!FPaths::FileExists(*MetalArBinaryCommand[AppleSDKMobile]) ||
!FPaths::FileExists(*MetalLibBinaryCommand[AppleSDKMac]) ||
!FPaths::FileExists(*MetalLibBinaryCommand[AppleSDKMobile]))
{
#if CHECK_METAL_COMPILER_TOOLCHAIN_SETUP
UE_LOG(LogMetalCompilerSetup, Warning, TEXT("Missing toolchain binaries."))
#endif
return EMetalToolchainStatus::ToolchainNotFound;
}
this->bToolchainBinariesPresent = true;
return EMetalToolchainStatus::Success;
}
#endif
bool FMetalCompilerToolchain::ExecMetalFrontend(EAppleSDKType SDK, const TCHAR* Parameters, int32* OutReturnCode, FString* OutStdOut, FString* OutStdErr) const
{
check(this->bToolchainBinariesPresent);
#if PLATFORM_MAC
FString BuiltParams = FString::Printf(TEXT("-sdk %s %s %s"), *SDKToString(SDK), *this->MetalFrontendBinary, Parameters);
return ExecGenericCommand(*XcrunPath, *BuiltParams, OutReturnCode, OutStdOut, OutStdErr);
#else
return ExecGenericCommand(*this->MetalFrontendBinaryCommand[SDK], Parameters, OutReturnCode, OutStdOut, OutStdErr);
#endif
}
bool FMetalCompilerToolchain::ExecMetalLib(EAppleSDKType SDK, const TCHAR* Parameters, int32* OutReturnCode, FString* OutStdOut, FString* OutStdErr) const
{
check(this->bToolchainBinariesPresent);
#if PLATFORM_MAC
FString BuiltParams = FString::Printf(TEXT("-sdk %s %s %s"), *SDKToString(SDK), *this->MetalLibraryBinary, Parameters);
return ExecGenericCommand(*XcrunPath, *BuiltParams, OutReturnCode, OutStdOut, OutStdErr);
#else
return ExecGenericCommand(*this->MetalLibBinaryCommand[SDK], Parameters, OutReturnCode, OutStdOut, OutStdErr);
#endif
}
bool FMetalCompilerToolchain::ExecMetalAr(EAppleSDKType SDK, const TCHAR* ScriptFile, int32* OutReturnCode, FString* OutStdOut, FString* OutStdErr) const
{
check(this->bToolchainBinariesPresent);
// WARNING: This phase may be run in parallel so we must not collide our scripts
// metal-ar is really llvm-ar, which acts like the standard ar. Since we usually end up with a ton of objects we are archiving we'd like to script it
// Unfortunately ar reads its script from stdin (when the -M arg is present) instead of being provided a file
// So on windows we'll spawn cmd.exe and pipe the script file into metal-ar.exe -M
#if PLATFORM_MAC
FString Command = FString::Printf(TEXT("-c \"%s -sdk %s '%s' -M < '%s'\""), *XcrunPath, *SDKToString(SDK), *this->MetalArBinary, ScriptFile);
bool bSuccess = ExecGenericCommand(TEXT("/bin/sh"), *Command, OutReturnCode, OutStdOut, OutStdErr);
#else
FString Command = FString::Printf(TEXT("/C type \"%s\" | \"%s\" -M"), ScriptFile, *this->MetalArBinaryCommand[SDK]);
bool bSuccess = ExecGenericCommand(TEXT("cmd.exe"), *Command, OutReturnCode, OutStdOut, OutStdErr);
#endif
if (!bSuccess)
{
UE_LOG(LogMetalShaderCompiler, Error, TEXT("Error creating .metalar. %s."), **OutStdOut);
UE_LOG(LogMetalShaderCompiler, Error, TEXT("Error creating .metalar. %s."), **OutStdErr);
}
return bSuccess;
}
bool FMetalCompilerToolchain::ExecGenericCommand(const TCHAR* Command, const TCHAR* Params, int32* OutReturnCode, FString* OutStdOut, FString* OutStdErr) const
{
#if PLATFORM_WINDOWS
{
// Why do we have our own implementation here? Because metal.exe wants to create a console window.
// So if we don't specify the options to CreateProc we end up with tons and tons of windows appearing and disappearing during a cook.
void* OutputReadPipe = nullptr;
void* OutputWritePipe = nullptr;
FPlatformProcess::CreatePipe(OutputReadPipe, OutputWritePipe);
FProcHandle Proc = FPlatformProcess::CreateProc(Command, Params, false, true, true, nullptr, -1, nullptr, OutputWritePipe, nullptr);
if (!Proc.IsValid())
{
FPlatformProcess::ClosePipe(OutputReadPipe, OutputWritePipe);
return false;
}
int32 RC;
FPlatformProcess::WaitForProc(Proc);
FPlatformProcess::GetProcReturnCode(Proc, &RC);
if (OutStdOut)
{
*OutStdOut = FPlatformProcess::ReadPipe(OutputReadPipe);
}
FPlatformProcess::ClosePipe(OutputReadPipe, OutputWritePipe);
FPlatformProcess::CloseProc(Proc);
if (OutReturnCode)
{
*OutReturnCode = RC;
}
return RC == 0;
}
#else
// Otherwise use the API
return FPlatformProcess::ExecProcess(Command, Params, OutReturnCode, OutStdOut, OutStdErr);
#endif
}
bool FMetalCompilerToolchain::CompileMetalShader(FMetalShaderBytecodeJob& Job, FMetalShaderBytecode& Output) const
{
// The local files
const FString& LocalInputMetalFilePath = Job.InputFile;
const FString& LocalOutputMetalAIRFilePath = Job.OutputObjectFile;
const FString& LocalOutputMetalLibFilePath = Job.OutputFile;
EAppleSDKType SDK = FMetalCompilerToolchain::MetalFormatToSDK(Job.ShaderFormat);
// .metal -> .air
FString IncludeArgs = Job.IncludeDir.Len() ? FString::Printf(TEXT("-I %s"), *Job.IncludeDir) : TEXT("");
{
// Invoke the metal frontend.
FString MetalParams = FString::Printf(TEXT("%s %s %s %s -Wno-null-character -fbracket-depth=1024 %s %s %s %s -o %s"), *Job.MinOSVersion, *Job.DebugInfo, *Job.MathMode, TEXT("-c"), *Job.Standard, *Job.Defines, *IncludeArgs, *LocalInputMetalFilePath, *LocalOutputMetalAIRFilePath);
bool bSuccess = this->ExecMetalFrontend(SDK, *MetalParams, &Job.ReturnCode, &Job.Results, &Job.Errors);
if (!bSuccess || (Job.ReturnCode != 0))
{
Job.Message = FString::Printf(TEXT("Failed to compile %s to bytecode %s, code: %d, output: %s %s"), *LocalInputMetalFilePath, *LocalOutputMetalAIRFilePath, Job.ReturnCode, *Job.Results, *Job.Errors);
return false;
}
}
{
// If we have succeeded, now we can create a metallib out of the AIR.
// TODO do we want to do this in every case? Should be able to skip if we are using Shader Libraries at the high level
FString MetalLibParams = FString::Printf(TEXT("-o %s %s"), *LocalOutputMetalLibFilePath, *LocalOutputMetalAIRFilePath);
bool bSuccess = this->ExecMetalLib(SDK, *MetalLibParams, &Job.ReturnCode, &Job.Results, &Job.Errors);
if (!bSuccess || (Job.ReturnCode != 0))
{
Job.Message = FString::Printf(TEXT("Failed to package %s into %s, code: %d, output: %s %s"), *LocalOutputMetalAIRFilePath, *LocalOutputMetalLibFilePath, Job.ReturnCode, *Job.Results, *Job.Errors);
return false;
}
}
// At this point we have an .air file and a .metallib file
if (Job.bRetainObjectFile)
{
// Retain the .air. This usually means we are using shared native libraries.
bool bSuccess = FFileHelper::LoadFileToArray(Output.ObjectFile, *LocalOutputMetalAIRFilePath);
if (!bSuccess)
{
Job.Message = FString::Printf(TEXT("Failed to store AIR %s"), *LocalOutputMetalAIRFilePath);
return false;
}
}
{
// Retain the .metallib
Output.NativePath = LocalInputMetalFilePath;
bool bSuccess = FFileHelper::LoadFileToArray(Output.OutputFile, *LocalOutputMetalLibFilePath);
if (!bSuccess)
{
Job.Message = FString::Printf(TEXT("Failed to store metallib %s"), *LocalOutputMetalAIRFilePath);
return false;
}
}
return true;
}
@@ -1,262 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// ....
#pragma once
#include "CoreMinimal.h"
#include "RHIDefinitions.h"
#include "HAL/PlatformProcess.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
// IOS and TVOS use the mobile toolchain.
enum EAppleSDKType
{
AppleSDKMac,
AppleSDKMobile,
AppleSDKCount,
};
extern void CompileShader_Metal(const struct FShaderCompilerInput& Input, struct FShaderCompilerOutput& Output, const class FString& WorkingDirectory);
extern uint32 GetMetalFormatVersion(FName Format);
static FName NAME_SF_METAL(TEXT("SF_METAL"));
static FName NAME_SF_METAL_MRT(TEXT("SF_METAL_MRT"));
static FName NAME_SF_METAL_TVOS(TEXT("SF_METAL_TVOS"));
static FName NAME_SF_METAL_MRT_TVOS(TEXT("SF_METAL_MRT_TVOS"));
static FName NAME_SF_METAL_SM5_NOTESS(TEXT("SF_METAL_SM5_NOTESS"));
static FName NAME_SF_METAL_SM5(TEXT("SF_METAL_SM5"));
static FName NAME_SF_METAL_MACES3_1(TEXT("SF_METAL_MACES3_1"));
static FName NAME_SF_METAL_MRT_MAC(TEXT("SF_METAL_MRT_MAC"));
DECLARE_LOG_CATEGORY_EXTERN(LogMetalCompilerSetup, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogMetalShaderCompiler, Log, All);
class FMetalCompilerToolchain
{
public:
enum class EMetalToolchainStatus : int32
{
Success,
ToolchainNotFound,
CouldNotParseCompilerVersion,
CouldNotParseTargetVersion,
CouldNotFindMetalStdLib,
};
struct PackedVersion
{
union
{
struct
{
int32 Major : 16;
int32 Minor : 8;
int32 Patch : 8;
};
int32 Version;
};
};
// Initializes this toolchain.
void Init();
// Tears down the toolchain
void Teardown();
// Takes a Job and compiles a shader. Produces .air and .metallib
bool CompileMetalShader(struct FMetalShaderBytecodeJob& Job, struct FMetalShaderBytecode& Output) const;
// Executes 'Command' on the local machine
bool ExecGenericCommand(const TCHAR* Command, const TCHAR* Params, int32* OutReturnCode, FString* OutStdOut, FString* OutStdErr) const;
// Executes the metal frontend compiler for 'SDK' on the local or remote machine, depending on the current configuration
bool ExecMetalFrontend(EAppleSDKType SDK, const TCHAR* Parameters, int32* OutReturnCode, FString* OutStdOut, FString* OutStdErr) const;
// Executes metallib for 'SDK' on the local or remote machine, depending on configuration
bool ExecMetalLib(EAppleSDKType SDK, const TCHAR* Parameters, int32* OutReturnCode, FString* OutStdOut, FString* OutStdErr) const;
// Executes metal-ar for 'SDK' on the local or remote machine, depending on configuration
bool ExecMetalAr(EAppleSDKType SDK, const TCHAR* ScriptFile, int32* OutReturnCode, FString* OutStdOut, FString* OutStdErr) const;
// This toolchain is set up correctly and ready to use.
bool IsCompilerAvailable() const
{
return this->bToolchainAvailable;
}
// The version of the compiler, given by metal -v
PackedVersion GetCompilerVersion(EShaderPlatform Platform) const;
// The AIR target version, given by metal -v
PackedVersion GetTargetVersion(EShaderPlatform Platform) const;
// The first line of metal -v, which gives the version
const FString& GetCompilerVersionString(EShaderPlatform Platform) const;
// Fully qualified path to a process-specific temporary directory on the local machine.
// This directory will be destroyed when the toolchain is destroyed
const FString& GetLocalTempDir() const
{
// nothing like dispatch_once on windows?
// this should always happen on the same thread anyway
static bool bLocalTempDirCreated = false;
if (!bLocalTempDirCreated)
{
LocalTempFolder = FPaths::Combine(FPlatformProcess::UserTempDir(), TEXT("MetalShaderCompilation"), FString::Printf(TEXT("%u"), FPlatformProcess::GetCurrentProcessId()));
if (!FPaths::DirectoryExists(LocalTempFolder))
{
bool bSuccess = IFileManager::Get().MakeDirectory(*LocalTempFolder, true);
if (!bSuccess)
{
UE_LOG(LogMetalCompilerSetup, Fatal, TEXT("Attempting to create temporary directory at %s but failed."), *LocalTempFolder);
}
}
bLocalTempDirCreated = true;
}
return LocalTempFolder;
}
// The Singleton Toolchain object
static const FMetalCompilerToolchain* Get()
{
return Singleton;
}
// Creates and intializes the toolchain for this process
static void CreateAndInit();
// Cleans up and deletes the toolchain for this process
static void Destroy();
// True if Platform indicates a mobile (ios, tvos) platform
static bool IsMobile(const EShaderPlatform Platform)
{
return (Platform == SP_METAL || Platform == SP_METAL_MRT || Platform == SP_METAL_TVOS || Platform == SP_METAL_MRT_TVOS);
}
// True if Format is the FName of a mobile shader format (ios, tvos)
static bool IsMobile(const FName& Format)
{
EShaderPlatform Platform = MetalShaderFormatToLegacyShaderPlatform(Format);
return IsMobile(Platform);
}
// True if Platform is a tvos platform
static bool IsTVOS(const EShaderPlatform Platform)
{
return Platform == SP_METAL_TVOS || Platform == SP_METAL_MRT_TVOS;
}
// Converts an FName ShaderFormat to the equivalent ShaderPlatform
static EShaderPlatform MetalShaderFormatToLegacyShaderPlatform(FName ShaderFormat);
// Returns the SDK (Mac, Mobile) to use in order to compile shaders of Platform
static EAppleSDKType MetalShaderPlatformToSDK(EShaderPlatform Platform)
{
if (IsMobile(Platform))
{
return AppleSDKMobile;
}
return AppleSDKMac;
}
// Returns the SDK (Mac, Mobile) to use in order to compile shaders of ShaderFormat
static EAppleSDKType MetalFormatToSDK(FName ShaderFormat)
{
if (IsMobile(ShaderFormat))
{
return AppleSDKMobile;
}
return AppleSDKMac;
}
static const FString& SDKToString(EAppleSDKType SDK)
{
if(SDK == AppleSDKMac)
{
return MetalMacSDK;
}
return MetalMobileSDK;
}
// The extension of a metal shader - .metal
static FString MetalExtention;
// The extension of a packed metal library - .metallib
static FString MetalLibraryExtension;
// The extension of the metal IR objects - .air
static FString MetalObjectExtension;
// The name of the metal frontend compiler - metal
static FString MetalFrontendBinary;
// The name of the metal-ar archiver - metal-ar
static FString MetalArBinary;
// The name of the metal binary packager - metallib
static FString MetalLibraryBinary;
// The extension of the mapping from shader to metallib for shared material libraries - .metalmap
static FString MetalMapExtension;
// The path to xcrun
static FString XcrunPath;
// The string xcrun expects for the mac SDK - macos
static FString MetalMacSDK;
// The string xcrun expects for the mobile SDKs - iphoneos
static FString MetalMobileSDK;
// The default installation directory of the windows native metal compiler
static FString DefaultWindowsToolchainPath;
private:
// Members
bool bToolchainAvailable : 1;
bool bToolchainBinariesPresent : 1;
// In this implementation we will also skip PCH generation but this is left in case we'd like to turn it on in the future.
// Probably needs serious testing as to whether it actually works and actually gains us anything.
bool bSkipPCH : 1;
// The path to metal_stdlib
FString MetalStandardLibraryPath[AppleSDKCount];
// These are the strings to pass to Exec to invoke various utilities.
// On Mac we'll just use xcrun and the name of the utility
#if PLATFORM_WINDOWS
// On Windows we will need to figure out the full path to each
// The command string to invoke 'metal'
FString MetalFrontendBinaryCommand[AppleSDKCount];
// The command string to invoke 'metallib'
FString MetalLibBinaryCommand[AppleSDKCount];
// The command string to invoke 'metal-ar'
FString MetalArBinaryCommand[AppleSDKCount];
#endif
// The compiler version string, parsed out of metal -v
FString MetalCompilerVersionString[AppleSDKCount];
// The compiler version number, parsed out of metal -v. This is the first number that occurs - not the number that is (metalfe-###)
PackedVersion MetalCompilerVersion[AppleSDKCount];
// The compiler target version, parsed out of metal -v. This is the number from 'Target: air64-apple-darwin##.##.##'
PackedVersion MetalTargetVersion[AppleSDKCount];
// Names of temporary directories, generated when the toolchain is initialized
// mutable :(
mutable FString LocalTempFolder;
// Statics
// The one and only toolchain
static FMetalCompilerToolchain* Singleton;
#if PLATFORM_MAC
// fills out the paths and sets up the toolchain for compilation on the local mac
EMetalToolchainStatus DoMacNativeSetup();
#endif
#if PLATFORM_WINDOWS
// Sets up toolchain for compilation on windows.
EMetalToolchainStatus DoWindowsSetup();
#endif
// Parses and verifies the version of the compiler. Fetched via metal -v
EMetalToolchainStatus FetchCompilerVersion();
// Parses and verifies where metal_stdlib is located.
EMetalToolchainStatus FetchMetalStandardLibraryPath();
};
/*
*/
@@ -1,98 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class AssetTools : ModuleRules
{
public AssetTools(ReadOnlyTargetRules Target) : base(Target)
{
PrivateIncludePaths.Add("Developer/AssetTools/Private");
PublicDependencyModuleNames.AddRange(
new string[] {
"Core",
"CoreUObject",
"SlateCore",
"UnrealEd",
}
);
PrivateDependencyModuleNames.AddRange(
new string[] {
"CurveAssetEditor",
"Engine",
"InputCore",
"ApplicationCore",
"Slate",
"EditorStyle",
"SourceControl",
"TextureEditor",
"PropertyEditor",
"Kismet",
"Landscape",
"Foliage",
"Projects",
"RHI",
"MaterialEditor",
"ToolMenus",
"PhysicsCore",
"DeveloperSettings"
}
);
PrivateIncludePathModuleNames.AddRange(
new string[] {
"Analytics",
"AssetRegistry",
"ContentBrowser",
"CollectionManager",
"CurveAssetEditor",
"DesktopPlatform",
"EditorWidgets",
"GameProjectGeneration",
"PropertyEditor",
"ActorPickerMode",
"Kismet",
"MainFrame",
"MaterialEditor",
"MessageLog",
"PackagesDialog",
"Persona",
"FontEditor",
"AudioEditor",
"SourceControl",
"Landscape",
"SkeletonEditor",
"SkeletalMeshEditor",
"AnimationEditor",
"AnimationBlueprintEditor",
"AnimationModifiers"
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[] {
"AssetRegistry",
"ContentBrowser",
"CollectionManager",
"CurveTableEditor",
"DataTableEditor",
"DesktopPlatform",
"EditorWidgets",
"GameProjectGeneration",
"ActorPickerMode",
"MainFrame",
"MessageLog",
"PackagesDialog",
"Persona",
"FontEditor",
"AudioEditor",
"SkeletonEditor",
"SkeletalMeshEditor",
"AnimationEditor",
"AnimationBlueprintEditor",
"AnimationModifiers"
}
);
}
}
@@ -1,58 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AdvancedCopyCustomization.h"
#include "Containers/UnrealString.h"
#include "AssetRegistryModule.h"
#include "Interfaces/IPluginManager.h"
#include "Engine/World.h"
#include "Engine/Level.h"
#include "Engine/MapBuildDataRegistry.h"
#define LOCTEXT_NAMESPACE "AdvancedCopyCustomization"
UAdvancedCopyCustomization::UAdvancedCopyCustomization(const class FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bShouldGenerateRelativePaths(true)
{
FilterForExcludingDependencies.PackagePaths.Add(TEXT("/Engine"));
for (TSharedRef<IPlugin>& Plugin : IPluginManager::Get().GetDiscoveredPlugins())
{
if (Plugin->GetType() != EPluginType::Project)
{
FilterForExcludingDependencies.PackagePaths.Add(FName(*("/" + Plugin->GetName())));
}
}
FilterForExcludingDependencies.bRecursivePaths = true;
FilterForExcludingDependencies.bRecursiveClasses = true;
FilterForExcludingDependencies.ClassNames.Add(UWorld::StaticClass()->GetFName());
FilterForExcludingDependencies.ClassNames.Add(ULevel::StaticClass()->GetFName());
FilterForExcludingDependencies.ClassNames.Add(UMapBuildDataRegistry::StaticClass()->GetFName());
}
void UAdvancedCopyCustomization::SetPackageThatInitiatedCopy(const FString& InBasePackage)
{
FString TempPackage = InBasePackage;
FAssetRegistryModule& AssetRegistryModule = FModuleManager::Get().LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
TArray<FAssetData> DependencyAssetData;
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
AssetRegistry.GetAssetsByPackageName(FName(*InBasePackage), DependencyAssetData);
// We found a folder
if (DependencyAssetData.Num() == 0)
{
// Take off the name of the folder we copied so copied files are still nested
TempPackage.Split(TEXT("/"), &TempPackage, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
}
if (!TempPackage.EndsWith(TEXT("/")))
{
TempPackage += TEXT("/");
}
PackageThatInitiatedCopy = TempPackage;
}
#undef LOCTEXT_NAMESPACE
@@ -1,500 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AssetFixUpRedirectors.h"
#include "UObject/ObjectRedirector.h"
#include "Misc/MessageDialog.h"
#include "HAL/FileManager.h"
#include "Misc/ScopedSlowTask.h"
#include "Modules/ModuleManager.h"
#include "UObject/UObjectHash.h"
#include "UObject/MetaData.h"
#include "Misc/PackageName.h"
#include "ISourceControlOperation.h"
#include "SourceControlOperations.h"
#include "ISourceControlModule.h"
#include "FileHelpers.h"
#include "SDiscoveringAssetsDialog.h"
#include "AssetRenameManager.h"
#include "AssetRegistryModule.h"
#include "ICollectionManager.h"
#include "CollectionManagerModule.h"
#include "ObjectTools.h"
#include "Logging/MessageLog.h"
#include "AssetTools.h"
#include "Engine/Blueprint.h"
#define LOCTEXT_NAMESPACE "AssetFixUpRedirectors"
struct FRedirectorRefs
{
UObjectRedirector* Redirector;
FName RedirectorPackageName;
TArray<FName> ReferencingPackageNames;
FText FailureReason;
bool bRedirectorValidForFixup;
FRedirectorRefs(UObjectRedirector* InRedirector)
: Redirector(InRedirector)
, RedirectorPackageName(InRedirector->GetOutermost()->GetFName())
, bRedirectorValidForFixup(true)
{}
};
void FAssetFixUpRedirectors::FixupReferencers(const TArray<UObjectRedirector*>& Objects, const bool bCheckoutDialogPrompt) const
{
// Transform array into TWeakObjectPtr array
TArray<TWeakObjectPtr<UObjectRedirector>> ObjectWeakPtrs;
for (auto Object : Objects)
{
ObjectWeakPtrs.Add(Object);
}
if (ObjectWeakPtrs.Num() > 0)
{
// If the asset registry is still loading assets, we cant check for referencers, so we must open the Discovering Assets dialog until it is done
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
if (AssetRegistryModule.Get().IsLoadingAssets())
{
// Open a dialog asking the user to wait while assets are being discovered
SDiscoveringAssetsDialog::OpenDiscoveringAssetsDialog(
SDiscoveringAssetsDialog::FOnAssetsDiscovered::CreateSP(this, &FAssetFixUpRedirectors::ExecuteFixUp, ObjectWeakPtrs, bCheckoutDialogPrompt)
);
}
else
{
// No need to wait, attempt to fix references now.
ExecuteFixUp(ObjectWeakPtrs, bCheckoutDialogPrompt);
}
}
}
void FAssetFixUpRedirectors::ExecuteFixUp(TArray<TWeakObjectPtr<UObjectRedirector>> Objects, const bool bCheckoutDialogPrompt) const
{
TGuardValue<bool> Guard(bIsFixupReferencersInProgress, true);
TArray<FRedirectorRefs> RedirectorRefsList;
for (auto Object : Objects)
{
auto ObjectRedirector = Object.Get();
if (ObjectRedirector)
{
RedirectorRefsList.Emplace(ObjectRedirector);
}
}
if ( RedirectorRefsList.Num() > 0 )
{
// Gather all referencing packages for all redirectors that are being fixed.
PopulateRedirectorReferencers(RedirectorRefsList);
// Update Package Status for all selected redirectors if SCC is enabled
if ( UpdatePackageStatus(RedirectorRefsList) )
{
// Load all referencing packages.
TArray<UPackage*> ReferencingPackagesToSave;
LoadReferencingPackages(RedirectorRefsList, ReferencingPackagesToSave);
// Check out all referencing packages, leave redirectors for assets referenced by packages that are not checked out and remove those packages from the save list.
const bool bUserAcceptedCheckout = CheckOutReferencingPackages(RedirectorRefsList, ReferencingPackagesToSave, bCheckoutDialogPrompt);
if ( bUserAcceptedCheckout )
{
// If any referencing packages are left read-only, the checkout failed or SCC was not enabled. Trim them from the save list and leave redirectors.
DetectReadOnlyPackages(RedirectorRefsList, ReferencingPackagesToSave);
// Fix up referencing FSoftObjectPaths
FixUpSoftObjectPaths(RedirectorRefsList, ReferencingPackagesToSave);
// Save all packages that were referencing any of the assets that were moved without redirectors
TArray<UPackage*> FailedToSave;
SaveReferencingPackages(ReferencingPackagesToSave, FailedToSave);
// Save any collections that were referencing any of the redirectors
SaveReferencingCollections(RedirectorRefsList);
// Wait for package referencers to be updated
UpdateAssetReferencers(RedirectorRefsList);
// Delete any redirectors that are no longer referenced
DeleteRedirectors(RedirectorRefsList, FailedToSave);
// Finally, report any failures that happened during the rename
ReportFailures(RedirectorRefsList);
}
}
}
}
void FAssetFixUpRedirectors::PopulateRedirectorReferencers(TArray<FRedirectorRefs>& RedirectorsToPopulate) const
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
for ( auto RedirectorRefsIt = RedirectorsToPopulate.CreateIterator(); RedirectorRefsIt; ++RedirectorRefsIt )
{
FRedirectorRefs& RedirectorRefs = *RedirectorRefsIt;
AssetRegistryModule.Get().GetReferencers(RedirectorRefs.RedirectorPackageName, RedirectorRefs.ReferencingPackageNames);
}
}
bool FAssetFixUpRedirectors::UpdatePackageStatus(const TArray<FRedirectorRefs>& RedirectorsToFix) const
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
if ( ISourceControlModule::Get().IsEnabled() )
{
// Update the source control server availability to make sure we can do the rename operation
SourceControlProvider.Login();
if ( !SourceControlProvider.IsAvailable() )
{
// We have failed to update source control even though it is enabled. This is critical and we can not continue
FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "SourceControl_ServerUnresponsive", "Source Control is unresponsive. Please check your connection and try again.") );
return false;
}
TArray<UPackage*> PackagesToAddToSCCUpdate;
for ( auto RedirectorRefsIt = RedirectorsToFix.CreateConstIterator(); RedirectorRefsIt; ++RedirectorRefsIt )
{
const FRedirectorRefs& RedirectorRefs = *RedirectorRefsIt;
PackagesToAddToSCCUpdate.Add(RedirectorRefs.Redirector->GetOutermost());
}
SourceControlProvider.Execute(ISourceControlOperation::Create<FUpdateStatus>(), PackagesToAddToSCCUpdate);
}
return true;
}
void FAssetFixUpRedirectors::LoadReferencingPackages(TArray<FRedirectorRefs>& RedirectorsToFix, TArray<UPackage*>& OutReferencingPackagesToSave) const
{
FScopedSlowTask SlowTask( RedirectorsToFix.Num(), LOCTEXT( "LoadingReferencingPackages", "Loading Referencing Packages..." ) );
SlowTask.MakeDialog();
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
// Load all packages that reference each redirector, if possible
for ( auto RedirectorRefsIt = RedirectorsToFix.CreateIterator(); RedirectorRefsIt; ++RedirectorRefsIt )
{
SlowTask.EnterProgressFrame(1);
FRedirectorRefs& RedirectorRefs = *RedirectorRefsIt;
if ( ISourceControlModule::Get().IsEnabled() )
{
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(RedirectorRefs.Redirector->GetOutermost(), EStateCacheUsage::Use);
const bool bValidSCCState = !SourceControlState.IsValid() || SourceControlState->IsAdded() || SourceControlState->IsCheckedOut() || SourceControlState->CanCheckout() || !SourceControlState->IsSourceControlled() || SourceControlState->IsIgnored();
if ( !bValidSCCState )
{
RedirectorRefs.bRedirectorValidForFixup = false;
RedirectorRefs.FailureReason = LOCTEXT("RedirectorFixupFailed_BadSCC", "Redirector could not be checked out or marked for delete");
}
}
// Load all referencers
for ( auto PackageNameIt = RedirectorRefs.ReferencingPackageNames.CreateConstIterator(); PackageNameIt; ++PackageNameIt )
{
const FString PackageName = (*PackageNameIt).ToString();
// Find the package in memory. If it is not in memory, try to load it
UPackage* Package = FindPackage(NULL, *PackageName);
if ( !Package )
{
Package = LoadPackage(NULL, *PackageName, LOAD_None);
}
if ( Package )
{
if ( Package->HasAnyPackageFlags(PKG_CompiledIn) )
{
// This is a script reference
RedirectorRefs.bRedirectorValidForFixup = false;
RedirectorRefs.FailureReason = FText::Format(LOCTEXT("RedirectorFixupFailed_CodeReference", "Redirector is referenced by code. Package: {0}"), FText::FromString(PackageName));
}
else
{
// If we found a valid package, mark it for save
OutReferencingPackagesToSave.AddUnique(Package);
}
}
}
}
}
bool FAssetFixUpRedirectors::CheckOutReferencingPackages(TArray<FRedirectorRefs>& RedirectorsToFix, TArray<UPackage*>& InOutReferencingPackagesToSave, const bool bCheckoutDialogPrompt) const
{
// Prompt to check out all successfully loaded packages
bool bUserAcceptedCheckout = true;
if ( InOutReferencingPackagesToSave.Num() > 0 )
{
if ( ISourceControlModule::Get().IsEnabled() )
{
TArray<UPackage*> PackagesCheckedOutOrMadeWritable;
TArray<UPackage*> PackagesNotNeedingCheckout;
if (bCheckoutDialogPrompt)
{
bUserAcceptedCheckout = FEditorFileUtils::PromptToCheckoutPackages( false, InOutReferencingPackagesToSave, &PackagesCheckedOutOrMadeWritable, &PackagesNotNeedingCheckout );
}
else
{
const bool bErrorIfAlreadyCheckedOut = false;
const bool bConfirmPackageBranchCheckOutStatus = false;
FEditorFileUtils::CheckoutPackages(InOutReferencingPackagesToSave, &PackagesCheckedOutOrMadeWritable, bErrorIfAlreadyCheckedOut, bConfirmPackageBranchCheckOutStatus);
}
if ( bUserAcceptedCheckout )
{
TArray<UPackage*> PackagesThatCouldNotBeCheckedOut = InOutReferencingPackagesToSave;
for ( auto PackageIt = PackagesCheckedOutOrMadeWritable.CreateConstIterator(); PackageIt; ++PackageIt )
{
PackagesThatCouldNotBeCheckedOut.Remove(*PackageIt);
}
for ( auto PackageIt = PackagesNotNeedingCheckout.CreateConstIterator(); PackageIt; ++PackageIt )
{
PackagesThatCouldNotBeCheckedOut.Remove(*PackageIt);
}
for ( auto PackageIt = PackagesThatCouldNotBeCheckedOut.CreateConstIterator(); PackageIt; ++PackageIt )
{
const FName NonCheckedOutPackageName = (*PackageIt)->GetFName();
for ( auto RedirectorRefsIt = RedirectorsToFix.CreateIterator(); RedirectorRefsIt; ++RedirectorRefsIt )
{
FRedirectorRefs& RedirectorRefs = *RedirectorRefsIt;
if ( RedirectorRefs.ReferencingPackageNames.Contains(NonCheckedOutPackageName) )
{
// We did not check out at least one of the packages we needed to. This redirector can not be fixed up.
RedirectorRefs.FailureReason = FText::Format(LOCTEXT("RedirectorFixupFailed_NotCheckedOut", "Referencing package {0} was not checked out"), FText::FromName(NonCheckedOutPackageName));
RedirectorRefs.bRedirectorValidForFixup = false;
}
}
InOutReferencingPackagesToSave.Remove(*PackageIt);
}
}
}
}
return bUserAcceptedCheckout;
}
void FAssetFixUpRedirectors::DetectReadOnlyPackages(TArray<FRedirectorRefs>& RedirectorsToFix, TArray<UPackage*>& InOutReferencingPackagesToSave) const
{
// For each valid package...
for ( int32 PackageIdx = InOutReferencingPackagesToSave.Num() - 1; PackageIdx >= 0; --PackageIdx )
{
UPackage* Package = InOutReferencingPackagesToSave[PackageIdx];
if ( Package )
{
// Find the package filename
FString Filename;
if ( FPackageName::DoesPackageExist(Package->GetName(), NULL, &Filename) )
{
// If the file is read only
if ( IFileManager::Get().IsReadOnly(*Filename) )
{
FName PackageName = Package->GetFName();
// Find all assets that were referenced by this package to create a redirector when named
for ( auto RedirectorIt = RedirectorsToFix.CreateIterator(); RedirectorIt; ++RedirectorIt )
{
FRedirectorRefs& RedirectorRefs = *RedirectorIt;
if ( RedirectorRefs.ReferencingPackageNames.Contains(PackageName) )
{
RedirectorRefs.FailureReason = FText::Format(LOCTEXT("RedirectorFixupFailed_ReadOnly", "Referencing package {0} was read-only"), FText::FromName(PackageName));
RedirectorRefs.bRedirectorValidForFixup = false;
}
}
// Remove the package from the save list
InOutReferencingPackagesToSave.RemoveAt(PackageIdx);
}
}
}
}
}
void FAssetFixUpRedirectors::SaveReferencingPackages(const TArray<UPackage*>& ReferencingPackagesToSave, TArray<UPackage*>& OutFailedToSave) const
{
if ( ReferencingPackagesToSave.Num() > 0 )
{
const bool bCheckDirty = false;
const bool bPromptToSave = false;
FEditorFileUtils::PromptForCheckoutAndSave(ReferencingPackagesToSave, bCheckDirty, bPromptToSave, &OutFailedToSave);
ISourceControlModule::Get().QueueStatusUpdate(ReferencingPackagesToSave);
}
}
void FAssetFixUpRedirectors::SaveReferencingCollections(TArray<FRedirectorRefs>& RedirectorsToFix) const
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
// Find all collections that were referenced by any of the redirectors that are potentially going to be removed and attempt to re-save them
// The redirectors themselves will have already been fixed up, as collections do that once the asset registry has been populated,
// however collections lazily re-save redirector fix-up to avoid SCC issues, so we need to force that now
for (FRedirectorRefs& RedirectorRefs : RedirectorsToFix)
{
// Follow each link in the redirector, and notify the collections manager that it is going to be removed - this will force it to re-save any required collections
for (UObjectRedirector* Redirector = RedirectorRefs.Redirector; Redirector; Redirector = Cast<UObjectRedirector>(Redirector->DestinationObject))
{
const FName RedirectorObjectPath = *Redirector->GetPathName();
if (!CollectionManagerModule.Get().HandleRedirectorDeleted(RedirectorObjectPath))
{
RedirectorRefs.FailureReason = FText::Format(LOCTEXT("RedirectorFixupFailed_CollectionsFailedToSave", "Referencing collection(s) failed to save: {0}"), CollectionManagerModule.Get().GetLastError());
RedirectorRefs.bRedirectorValidForFixup = false;
}
}
}
}
void FAssetFixUpRedirectors::UpdateAssetReferencers(const TArray<FRedirectorRefs>& RedirectorsToFix) const
{
// Load the asset registry module
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
TArray<FString> AssetPaths;
for (const auto& Redirector : RedirectorsToFix)
{
AssetPaths.AddUnique(FPackageName::GetLongPackagePath(Redirector.RedirectorPackageName.ToString()) / TEXT("")); // Ensure trailing slash
for (const auto& Referencer : Redirector.ReferencingPackageNames)
{
AssetPaths.AddUnique(FPackageName::GetLongPackagePath(Referencer.ToString()) / TEXT("")); // Ensure trailing slash
}
}
AssetRegistryModule.Get().ScanPathsSynchronous(AssetPaths, true);
}
void FAssetFixUpRedirectors::DeleteRedirectors(TArray<FRedirectorRefs>& RedirectorsToFix, const TArray<UPackage*>& FailedToSave) const
{
TArray<UObject*> ObjectsToDelete;
for ( auto RedirectorIt = RedirectorsToFix.CreateIterator(); RedirectorIt; ++RedirectorIt )
{
FRedirectorRefs& RedirectorRefs = *RedirectorIt;
if ( RedirectorRefs.bRedirectorValidForFixup )
{
check(RedirectorRefs.Redirector);
bool bAllReferencersFixedUp = true;
for (const auto& ReferencingPackageName : RedirectorRefs.ReferencingPackageNames)
{
if (FailedToSave.ContainsByPredicate([&](UPackage* Package) { return Package->GetFName() == ReferencingPackageName; }))
{
bAllReferencersFixedUp = false;
break;
}
}
if (!bAllReferencersFixedUp)
{
continue;
}
// Add all redirectors found in this package to the redirectors to delete list.
// All redirectors in this package should be fixed up.
UPackage* RedirectorPackage = RedirectorRefs.Redirector->GetOutermost();
TArray<UObject*> AssetsInRedirectorPackage;
GetObjectsWithOuter(RedirectorPackage, AssetsInRedirectorPackage, /*bIncludeNestedObjects=*/false);
UMetaData* PackageMetaData = NULL;
bool bContainsAtLeastOneOtherAsset = false;
for ( auto ObjIt = AssetsInRedirectorPackage.CreateConstIterator(); ObjIt; ++ObjIt )
{
if ( UObjectRedirector* Redirector = Cast<UObjectRedirector>(*ObjIt) )
{
Redirector->RemoveFromRoot();
ObjectsToDelete.Add(Redirector);
}
else if ( UMetaData* MetaData = Cast<UMetaData>(*ObjIt) )
{
PackageMetaData = MetaData;
}
else
{
bContainsAtLeastOneOtherAsset = true;
}
}
if ( !bContainsAtLeastOneOtherAsset )
{
RedirectorPackage->RemoveFromRoot();
ObjectsToDelete.Add(RedirectorPackage);
// @todo we shouldnt be worrying about metadata objects here, ObjectTools::CleanUpAfterSuccessfulDelete should
if ( PackageMetaData )
{
PackageMetaData->RemoveFromRoot();
ObjectsToDelete.Add(PackageMetaData);
}
}
// This redirector will be deleted, NULL the reference here
RedirectorRefs.Redirector = NULL;
}
}
if ( ObjectsToDelete.Num() > 0 )
{
ObjectTools::DeleteObjects(ObjectsToDelete, false);
}
}
void FAssetFixUpRedirectors::ReportFailures(const TArray<FRedirectorRefs>& RedirectorsToFix) const
{
FMessageLog EditorErrors("EditorErrors");
bool bTitleOutput = false;
for ( auto RedirectorIt = RedirectorsToFix.CreateConstIterator(); RedirectorIt; ++RedirectorIt )
{
const FRedirectorRefs& RedirectorRefs = *RedirectorIt;
if ( !RedirectorRefs.bRedirectorValidForFixup )
{
if(!bTitleOutput)
{
EditorErrors.Info(LOCTEXT("RedirectorFixupFailedMessage", "The following redirectors could not be completely fixed up"));
bTitleOutput = true;
}
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("PackageName"), FText::FromName(RedirectorRefs.RedirectorPackageName));
Arguments.Add(TEXT("FailureReason"), FText::FromString(RedirectorRefs.FailureReason.ToString()));
EditorErrors.Warning(FText::Format(LOCTEXT("RedirectorFixupFailedReason", "{PackageName} - {FailureReason}"), Arguments ));
}
}
EditorErrors.Open();
}
void FAssetFixUpRedirectors::FixUpSoftObjectPaths(const TArray<FRedirectorRefs>& RedirectorsToFix, const TArray<UPackage*>& InReferencingPackagesToSave) const
{
TArray<UPackage *> PackagesToCheck(InReferencingPackagesToSave);
FEditorFileUtils::GetDirtyWorldPackages(PackagesToCheck);
FEditorFileUtils::GetDirtyContentPackages(PackagesToCheck);
TMap<FSoftObjectPath, FSoftObjectPath> RedirectorMap;
for (const FRedirectorRefs& RedirectorRef : RedirectorsToFix)
{
UObjectRedirector* Redirector = RedirectorRef.Redirector;
FSoftObjectPath OldPath = FSoftObjectPath(Redirector);
FSoftObjectPath NewPath = FSoftObjectPath(Redirector->DestinationObject);
RedirectorMap.Add(OldPath, NewPath);
if (UBlueprint* Blueprint = Cast<UBlueprint>(Redirector->DestinationObject))
{
// Add redirect for class and default as well
RedirectorMap.Add(FString::Printf(TEXT("%s_C"), *OldPath.ToString()), FString::Printf(TEXT("%s_C"), *NewPath.ToString()));
RedirectorMap.Add(FString::Printf(TEXT("%s.Default__%s_C"), *OldPath.GetLongPackageName(), *OldPath.GetAssetName()), FString::Printf(TEXT("%s.Default__%s_C"), *NewPath.GetLongPackageName(), *NewPath.GetAssetName()));
}
}
UAssetToolsImpl::Get().AssetRenameManager->RenameReferencingSoftObjectPaths(PackagesToCheck, RedirectorMap);
}
#undef LOCTEXT_NAMESPACE
@@ -1,69 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
struct FRedirectorRefs;
class FAssetFixUpRedirectors : public TSharedFromThis<FAssetFixUpRedirectors>
{
public:
/**
* Fix up references to the specified redirectors.
* @param bCheckoutDialogPrompt indicates whether to prompt the user with files checkout dialog or silently attempt to checkout all necessary files.
*/
void FixupReferencers(const TArray<UObjectRedirector*>& Objects, bool bCheckoutDialogPrompt = true) const;
/** Returns whether redirectors are being fixed up. */
bool IsFixupReferencersInProgress() const { return bIsFixupReferencersInProgress; }
private:
/** The core code of the fixup operation */
void ExecuteFixUp(TArray<TWeakObjectPtr<UObjectRedirector>> Objects, bool bCheckoutDialogPrompt) const;
/** Fills out the Referencing packages for all the redirectors described in AssetsToPopulate */
void PopulateRedirectorReferencers(TArray<FRedirectorRefs>& RedirectorsToPopulate) const;
/** Updates the source control status of the packages containing the assets to rename */
bool UpdatePackageStatus(const TArray<FRedirectorRefs>& RedirectorsToFix) const;
/**
* Loads all referencing packages to redirectors in RedirectorsToFix, finds redirectors whose references can
* not be fixed up, and returns a list of referencing packages to save.
*/
void LoadReferencingPackages(TArray<FRedirectorRefs>& RedirectorsToFix, TArray<UPackage*>& OutReferencingPackagesToSave) const;
/**
* Check out referencing packages and marks assets whose referencing packages were not checked out to not fix the redirector.
* Trims PackagesToSave when necessary.
* Returns true if the user opted to continue the operation or no dialog was required.
*/
bool CheckOutReferencingPackages(TArray<FRedirectorRefs>& RedirectorsToFix, TArray<UPackage*>& InOutReferencingPackagesToSave, bool bCheckoutDialogPrompt) const;
/** Finds any read only packages and removes them from the save list. Redirectors referenced by these packages will not be fixed up. */
void DetectReadOnlyPackages(TArray<FRedirectorRefs>& RedirectorsToFix, TArray<UPackage*>& InOutReferencingPackagesToSave) const;
/** FixUp soft object paths */
void FixUpSoftObjectPaths(const TArray<FRedirectorRefs>& RedirectorsToFix, const TArray<UPackage*>& InReferencingPackagesToSave) const;
/** Saves all the referencing packages and updates SCC state */
void SaveReferencingPackages(const TArray<UPackage*>& ReferencingPackagesToSave, TArray<UPackage*>& OutFailedToSave) const;
/** Saves any collections that were referencing any of the redirectors and updates SCC state */
void SaveReferencingCollections(TArray<FRedirectorRefs>& RedirectorsToFix) const;
/** Waits for the asset registry to update its asset referencer cache */
void UpdateAssetReferencers(const TArray<FRedirectorRefs>& RedirectorsToFix) const;
/** Deletes redirectors that are valid to delete */
void DeleteRedirectors(TArray<FRedirectorRefs>& RedirectorsToFix, const TArray<UPackage*>& FailedToSave) const;
/** Report any failures that may have happened during the rename */
void ReportFailures(const TArray<FRedirectorRefs>& RedirectorsToFix) const;
private:
mutable bool bIsFixupReferencersInProgress = false;
};
File diff suppressed because it is too large Load Diff
@@ -1,122 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "IAssetTools.h"
#include "UObject/SoftObjectPath.h"
struct FAssetRenameDataWithReferencers;
struct FCachedSoftReference
{
// Insert friendly
TMap<FSoftObjectPath, TSet<FWeakObjectPtr>> Map;
// So we can binary search for the TMap keys
TArray<FSoftObjectPath> Keys;
};
/**
* The manager to handle renaming assets.
* This manager attempts to fix up references in memory if possible and only leaves UObjectRedirectors when needed.
* Redirectors are left unless ALL of the following are true about the asset
* 1) The asset has not yet been checked into source control. This does not apply when source control is disabled.
* 2) The user is able and willing to check out all uasset files that directly reference the asset from source control. The files must be at head revision and not checked out by another user. This rule does not apply when source control is disabled.
* 3) No maps reference the asset directly.
* 4) All uasset files that directly reference the asset are writable on disk.
*/
class FAssetRenameManager : public TSharedFromThis<FAssetRenameManager>
{
public:
/** Renames assets using the specified names. */
bool RenameAssets(const TArray<FAssetRenameData>& AssetsAndNames) const;
/** Renames assets using the specified names. */
EAssetRenameResult RenameAssetsWithDialog(const TArray<FAssetRenameData>& AssetsAndNames, bool bAutoCheckout = false) const;
/** Returns list of objects that soft reference the given soft object path. This will load assets into memory to verify */
void FindSoftReferencesToObject(FSoftObjectPath TargetObject, TArray<UObject*>& ReferencingObjects) const;
/** Returns list of objects that soft reference the given soft object paths. This will load assets into memory to verify */
void FindSoftReferencesToObjects(const TArray<FSoftObjectPath>& TargetObjects, TMap<FSoftObjectPath, TArray<UObject*>>& ReferencingObjects) const;
/** Accessor for post rename event */
FAssetPostRenameEvent& OnAssetPostRenameEvent() { return AssetPostRenameEvent; }
/**
* Function that renames all FSoftObjectPath object with the old asset path to the new one.
*
* @param PackagesToCheck Packages to check for referencing FSoftObjectPath.
* @param AssetRedirectorMap Map from old asset path to new asset path
*/
void RenameReferencingSoftObjectPaths(TArray<UPackage*> PackagesToCheck, const TMap<FSoftObjectPath, FSoftObjectPath>& AssetRedirectorMap) const;
/** Filters packages list depending on if it actually has soft object paths pointing to the specific object being renamed */
bool CheckPackageForSoftObjectReferences(UPackage* Package, const TMap<FSoftObjectPath, FSoftObjectPath>& AssetRedirectorMap, TArray<UObject*>& OutReferencingObjects) const;
/** Filters packages list depending on if it actually has soft object paths pointing to the specific object being renamed */
bool CheckPackageForSoftObjectReferences(UPackage* Package, const TMap<FSoftObjectPath, FSoftObjectPath>& AssetRedirectorMap, TMap<FSoftObjectPath, TArray<UObject*>>& OutReferencingObjects) const;
private:
/** Callback used by DiscoverintAssetsDialog to call FixrefrencesAndRename */
void FixReferencesAndRenameCallback(TArray<FAssetRenameData> AssetsAndNames, bool bAutoCheckout, bool bWithDialog) const;
/** Attempts to load and fix redirector references for the supplied assets */
bool FixReferencesAndRename(const TArray<FAssetRenameData>& AssetsAndNames, bool bAutoCheckout, bool bWithDialog) const;
/** Get a list of assets referenced from CDOs */
TArray<TWeakObjectPtr<UObject>> FindCDOReferencedAssets(const TArray<FAssetRenameDataWithReferencers>& AssetsToRename) const;
/** Fills out the Referencing packages for all the assets described in AssetsToPopulate */
void PopulateAssetReferencers(TArray<FAssetRenameDataWithReferencers>& AssetsToPopulate) const;
/** Updates the source control status of the packages containing the assets to rename */
bool UpdatePackageStatus(const TArray<FAssetRenameDataWithReferencers>& AssetsToRename) const;
/**
* Loads all referencing packages to assets in AssetsToRename, finds assets whose references can
* not be fixed up to mark that a redirector should be left, and returns a list of referencing packages to save.
* If bLoadAllPackages is true, it will load all referencing packages even if they can't be checked out
* If bCheckStatus is true it will check the source control status
*/
void LoadReferencingPackages(TArray<FAssetRenameDataWithReferencers>& AssetsToRename, bool bLoadAllPackages, bool bCheckStatus, TArray<UPackage*>& OutReferencingPackagesToSave, TArray<UObject*>& OutSoftReferencingObjects) const;
/** Gather a list of referencing object for each of the asset in AssetsToRename. Will load all referencing packages */
void GatherReferencingObjects(TArray<FAssetRenameDataWithReferencers>& AssetsToRename, TMap<FSoftObjectPath, TArray<UObject*>>& OutSoftReferencingObjects) const;
/**
* Prompts to check out the source package and all referencing packages and marks assets whose referencing packages were not checked out to leave a redirector.
* Trims PackagesToSave when necessary.
* Returns true if the user opted to continue the operation or no dialog was required.
*/
bool CheckOutPackages(TArray<FAssetRenameDataWithReferencers>& AssetsToRename, TArray<UPackage*>& InOutReferencingPackagesToSave, bool bAutoCheckout) const;
/** Attempts to check out packages, returns false on any failure */
bool AutoCheckOut(TArray<UPackage*>& PackagesToCheckOut) const;
/** Finds any collections that are referencing the assets to be renamed. Assets referenced by collections will leave redirectors */
void DetectReferencingCollections(TArray<FAssetRenameDataWithReferencers>& AssetsToRename) const;
/** Finds any read only packages and removes them from the save list. Assets referenced by these packages will leave redirectors. */
void DetectReadOnlyPackages(TArray<FAssetRenameDataWithReferencers>& AssetsToRename, TArray<UPackage*>& InOutReferencingPackagesToSave) const;
/** Performs the asset rename after the user has selected to proceed */
void PerformAssetRename(TArray<FAssetRenameDataWithReferencers>& AssetsToRename) const;
/** Saves all the referencing packages and updates SCC state */
void SaveReferencingPackages(const TArray<UPackage*>& ReferencingPackagesToSave) const;
/** Report any failures that may have happened during the rename. Return the number of failures */
int32 ReportFailures(const TArray<FAssetRenameDataWithReferencers>& AssetsToRename, bool bWithDialog) const;
/** Called when a package is dirtied, clears the cache */
void OnMarkPackageDirty(UPackage* Pkg, bool bWasDirty);
/** Event issued at the end of the rename process */
FAssetPostRenameEvent AssetPostRenameEvent;
/** Cache of package->soft references, to avoid serializing the same package over and over */
mutable TMap<FName, FCachedSoftReference> CachedSoftReferences;
mutable FDelegateHandle DirtyDelegateHandle;
};
File diff suppressed because it is too large Load Diff
@@ -1,214 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "IAssetTools.h"
#include "IAssetTypeActions.h"
#include "AssetData.h"
#include "AssetRenameManager.h"
#include "Misc/BlacklistNames.h"
#include "AssetTools.generated.h"
class FAssetFixUpRedirectors;
class UToolMenu;
class IClassTypeActions;
class UAutomatedAssetImportData;
class UFactory;
class UAssetImportTask;
struct ReportPackageData;
/** Parameters for importing specific set of files */
struct FAssetImportParams
{
FAssetImportParams()
: SpecifiedFactory(nullptr)
, ImportData(nullptr)
, AssetImportTask(nullptr)
, bSyncToBrowser(true)
, bForceOverrideExisting(false)
, bAutomated(false)
{}
/** Factory to use for importing files */
UFactory* SpecifiedFactory;
/** Data used to determine rules for importing assets through the automated command line interface */
const UAutomatedAssetImportData* ImportData;
/** Script exposed rules and state for importing assets */
UAssetImportTask* AssetImportTask;
/** Whether or not to sync the content browser to the assets after import */
bool bSyncToBrowser : 1;
/** Whether or not we are forcing existing assets to be overriden without asking */
bool bForceOverrideExisting : 1;
/** Whether or not this is an automated import */
bool bAutomated : 1;
};
/** For backwards compatibility */
typedef class UAssetToolsImpl FAssetTools;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
UCLASS(transient)
class UAssetToolsImpl : public UObject, public IAssetTools
{
GENERATED_BODY()
public:
UAssetToolsImpl(const FObjectInitializer& ObjectInitializer);
// IAssetTools implementation
virtual void RegisterAssetTypeActions(const TSharedRef<IAssetTypeActions>& NewActions) override;
virtual void UnregisterAssetTypeActions(const TSharedRef<IAssetTypeActions>& ActionsToRemove) override;
virtual void GetAssetTypeActionsList( TArray<TWeakPtr<IAssetTypeActions>>& OutAssetTypeActionsList ) const override;
virtual TWeakPtr<IAssetTypeActions> GetAssetTypeActionsForClass(const UClass* Class) const override;
virtual TArray<TWeakPtr<IAssetTypeActions>> GetAssetTypeActionsListForClass(const UClass* Class) const override;
virtual EAssetTypeCategories::Type RegisterAdvancedAssetCategory(FName CategoryKey, FText CategoryDisplayName) override;
virtual EAssetTypeCategories::Type FindAdvancedAssetCategory(FName CategoryKey) const override;
virtual void GetAllAdvancedAssetCategories(TArray<FAdvancedAssetCategory>& OutCategoryList) const override;
virtual void RegisterClassTypeActions(const TSharedRef<IClassTypeActions>& NewActions) override;
virtual void UnregisterClassTypeActions(const TSharedRef<IClassTypeActions>& ActionsToRemove) override;
virtual void GetClassTypeActionsList( TArray<TWeakPtr<IClassTypeActions>>& OutClassTypeActionsList ) const override;
virtual TWeakPtr<IClassTypeActions> GetClassTypeActionsForClass( UClass* Class ) const override;
virtual UObject* CreateAsset(const FString& AssetName, const FString& PackagePath, UClass* AssetClass, UFactory* Factory, FName CallingContext = NAME_None) override;
virtual UObject* CreateAsset(UClass* AssetClass, UFactory* Factory, FName CallingContext = NAME_None) override;
virtual UObject* CreateAssetWithDialog(UClass* AssetClass, UFactory* Factory, FName CallingContext = NAME_None) override;
virtual UObject* CreateAssetWithDialog(const FString& AssetName, const FString& PackagePath, UClass* AssetClass, UFactory* Factory, FName CallingContext = NAME_None) override;
virtual UObject* DuplicateAsset(const FString& AssetName, const FString& PackagePath, UObject* OriginalObject) override;
virtual UObject* DuplicateAssetWithDialog(const FString& AssetName, const FString& PackagePath, UObject* OriginalObject) override;
virtual UObject* DuplicateAssetWithDialogAndTitle(const FString& AssetName, const FString& PackagePath, UObject* OriginalObject, FText DialogTitle) override;
virtual bool RenameAssets(const TArray<FAssetRenameData>& AssetsAndNames) override;
virtual EAssetRenameResult RenameAssetsWithDialog(const TArray<FAssetRenameData>& AssetsAndNames, bool bAutoCheckout = false) override;
virtual void FindSoftReferencesToObject(FSoftObjectPath TargetObject, TArray<UObject*>& ReferencingObjects) override;
virtual void FindSoftReferencesToObjects(const TArray<FSoftObjectPath>& TargetObjects, TMap<FSoftObjectPath, TArray<UObject*>>& ReferencingObjects) override;
virtual void RenameReferencingSoftObjectPaths(const TArray<UPackage *> PackagesToCheck, const TMap<FSoftObjectPath, FSoftObjectPath>& AssetRedirectorMap) override;
virtual TArray<UObject*> ImportAssets(const FString& DestinationPath) override;
virtual TArray<UObject*> ImportAssetsWithDialog(const FString& DestinationPath) override;
virtual TArray<UObject*> ImportAssets(const TArray<FString>& Files, const FString& DestinationPath, UFactory* ChosenFactory, bool bSyncToBrowser = true, TArray<TPair<FString, FString>>* FilesAndDestinations = nullptr) const override;
virtual TArray<UObject*> ImportAssetsAutomated(const UAutomatedAssetImportData* ImportData) override;
virtual void ImportAssetTasks(const TArray<UAssetImportTask*>& ImportTasks) override;
virtual void ExportAssets(const TArray<FString>& AssetsToExport, const FString& ExportPath) override;
virtual void ExportAssets(const TArray<UObject*>& AssetsToExport, const FString& ExportPath) const override;
virtual void ExportAssetsWithDialog(const TArray<UObject*>& AssetsToExport, bool bPromptForIndividualFilenames) override;
virtual void ExportAssetsWithDialog(const TArray<FString>& AssetsToExport, bool bPromptForIndividualFilenames) override;
virtual void CreateUniqueAssetName(const FString& InBasePackageName, const FString& InSuffix, FString& OutPackageName, FString& OutAssetName) override;
virtual bool AssetUsesGenericThumbnail( const FAssetData& AssetData ) const override;
virtual void DiffAgainstDepot(UObject* InObject, const FString& InPackagePath, const FString& InPackageName) const override;
virtual void DiffAssets(UObject* OldAsset1, UObject* NewAsset, const struct FRevisionInfo& OldRevision, const struct FRevisionInfo& NewRevision) const override;
virtual FString DumpAssetToTempFile(UObject* Asset) const override;
virtual bool CreateDiffProcess(const FString& DiffCommand, const FString& OldTextFilename, const FString& NewTextFilename, const FString& DiffArgs = FString("")) const override;
virtual void MigratePackages(const TArray<FName>& PackageNamesToMigrate) const override;
virtual void BeginAdvancedCopyPackages(const TArray<FName>& InputNamesToCopy, const FString& TargetPath) const override;
virtual void FixupReferencers(const TArray<UObjectRedirector*>& Objects, bool bCheckoutDialogPrompt = true) const override;
virtual bool IsFixupReferencersInProgress() const override;
virtual FAssetPostRenameEvent& OnAssetPostRename() override { return AssetRenameManager->OnAssetPostRenameEvent(); }
virtual void ExpandDirectories(const TArray<FString>& Files, const FString& DestinationPath, TArray<TPair<FString, FString>>& FilesAndDestinations) const override;
virtual bool AdvancedCopyPackages(const FAdvancedCopyParams& CopyParams, const TArray<TMap<FString, FString>> PackagesAndDestinations) const override;
virtual bool AdvancedCopyPackages(const TMap<FString, FString>& SourceAndDestPackages, const bool bForceAutosave, const bool bCopyOverAllDestinationOverlaps) const override;
virtual void GenerateAdvancedCopyDestinations(FAdvancedCopyParams& InParams, const TArray<FName>& InPackageNamesToCopy, const class UAdvancedCopyCustomization* CopyCustomization, TMap<FString, FString>& OutPackagesAndDestinations) const override;
virtual bool FlattenAdvancedCopyDestinations(const TArray<TMap<FString, FString>> PackagesAndDestinations, TMap<FString, FString>& FlattenedPackagesAndDestinations) const override;
virtual bool ValidateFlattenedAdvancedCopyDestinations(const TMap<FString, FString>& FlattenedPackagesAndDestinations) const override;
virtual void GetAllAdvancedCopySources(FName SelectedPackage, FAdvancedCopyParams& CopyParams, TArray<FName>& OutPackageNamesToCopy, TMap<FName, FName>& DependencyMap, const class UAdvancedCopyCustomization* CopyCustomization) const override;
virtual void InitAdvancedCopyFromCopyParams(FAdvancedCopyParams CopyParams) const override;
virtual void OpenEditorForAssets(const TArray<UObject*>& Assets) override;
virtual void ConvertVirtualTextures(const TArray<UTexture2D*>& Textures, bool bConvertBackToNonVirtual, const TArray<UMaterial*>* RelatedMaterials = nullptr) const override;
virtual bool IsAssetClassSupported(const UClass* AssetClass) const override;
virtual TArray<UFactory*> GetNewAssetFactories() const override;
virtual TSharedRef<FBlacklistNames>& GetAssetClassBlacklist() override;
virtual TSharedRef<FBlacklistPaths>& GetFolderBlacklist() override;
virtual TSharedRef<FBlacklistPaths>& GetWritableFolderBlacklist() override;
virtual bool AllPassWritableFolderFilter(const TArray<FString>& InPaths) const override;
virtual void NotifyBlockedByWritableFolderFilter() const;
public:
/** Gets the asset tools singleton as a FAssetTools for asset tools module use */
static UAssetToolsImpl& Get();
/** Syncs the primary content browser to the specified assets, whether or not it is locked. Most syncs that come from AssetTools -feel- like they came from the content browser, so this is okay. */
void SyncBrowserToAssets(const TArray<UObject*>& AssetsToSync);
void SyncBrowserToAssets(const TArray<FAssetData>& AssetsToSync);
/** The manager to handle renaming assets */
TSharedPtr<FAssetRenameManager> AssetRenameManager;
/** The manager to handle fixing up redirectors */
TSharedPtr<FAssetFixUpRedirectors> AssetFixUpRedirectors;
private:
/** Checks to see if a package is marked for delete then ask the user if he would like to check in the deleted file before he can continue. Returns true when it is safe to proceed. */
bool CheckForDeletedPackage(const UPackage* Package) const;
/** Returns true if the supplied Asset name and package are currently valid for creation. */
bool CanCreateAsset(const FString& AssetName, const FString& PackageName, const FText& OperationText) const;
/** Begins the package migration, after assets have been discovered */
void PerformMigratePackages(TArray<FName> PackageNamesToMigrate) const;
/** Begins the package advanced copy, after assets have been discovered */
void PerformAdvancedCopyPackages(TArray<FName> SelectedPackageNames, FString TargetPath) const;
/** Copies files after the final list was confirmed */
void MigratePackages_ReportConfirmed(TSharedPtr<TArray<ReportPackageData>> PackageDataToMigrate) const;
/** Copies files after the final list was confirmed */
void AdvancedCopyPackages_ReportConfirmed(FAdvancedCopyParams CopyParam, TArray<TMap<FString, FString>> DestinationMap) const;
/** Gets the dependencies of the specified package recursively */
void RecursiveGetDependencies(const FName& PackageName, TSet<FName>& AllDependencies, const FString& OriginalRoot) const;
/** Gets the dependencies of the specified package recursively while omitting things that don't pass the FARFilter passed in from FAdvancedCopyParams */
void RecursiveGetDependenciesAdvanced(const FName& PackageName, FAdvancedCopyParams& CopyParams, TArray<FName>& AllDependencies, TMap<FName, FName>& DependencyMap, const class UAdvancedCopyCustomization* CopyCustomization, TArray<FAssetData>& OptionalAssetData) const;
/** Records the time taken for an import and reports it to engine analytics, if available */
static void OnNewImportRecord(UClass* AssetType, const FString& FileExtension, bool bSucceeded, bool bWasCancelled, const FDateTime& StartTime);
/** Records what assets users are creating */
static void OnNewCreateRecord(UClass* AssetType, bool bDuplicated);
/** Internal method that performs the actual asset importing */
TArray<UObject*> ImportAssetsInternal(const TArray<FString>& Files, const FString& RootDestinationPath, TArray<TPair<FString, FString>> *FilesAndDestinationsPtr, const FAssetImportParams& ImportParams) const;
/** Internal method to export assets. If no export path is created a user will be prompted for one. if bPromptIndividualFilenames is true a user will be asked per file */
void ExportAssetsInternal(const TArray<UObject*>& ObjectsToExport, bool bPromptIndividualFilenames, const FString& ExportPath) const;
UObject* PerformDuplicateAsset(const FString& AssetName, const FString& PackagePath, UObject* OriginalObject, bool bWithDialog);
/** Internal method that performs actions when asset class blacklist filter changes */
void AssetClassBlacklistChanged();
/**
* Add sub content blacklist filter for a new mount point
* @param InMount The mount point
*/
void AddSubContentBlacklist(const FString& InMount);
/** Called when a new mount is added to add the proper sub content blacklist to it. */
void OnContentPathMounted(const FString& InAssetPath, const FString& FileSystemPath);
private:
/** The list of all registered AssetTypeActions */
TArray<TSharedRef<IAssetTypeActions>> AssetTypeActionsList;
/** The list of all registered ClassTypeActions */
TArray<TSharedRef<IClassTypeActions>> ClassTypeActionsList;
/** The categories that have been allocated already */
TMap<FName, FAdvancedAssetCategory> AllocatedCategoryBits;
/** The next user category bit to allocate (set to 0 when there are no more bits left) */
uint32 NextUserCategoryBit;
/** Blacklist of assets by class name */
TSharedRef<FBlacklistNames> AssetClassBlacklist;
/** Blacklist of folder paths */
TSharedRef<FBlacklistPaths> FolderBlacklist;
/** Blacklist of folder paths to write to */
TSharedRef<FBlacklistPaths> WritableFolderBlacklist;
/** List of sub content path blacklisted for every mount. */
TArray<FString> SubContentBlacklistPaths;
};
PRAGMA_ENABLE_DEPRECATION_WARNINGS
@@ -1,32 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#define LOCTEXT_NAMESPACE "AssetTools"
class FAssetToolsModule;
class FAssetToolsConsoleCommands
{
public:
const FAssetToolsModule& Module;
//FAutoConsoleCommand CreateCommand;
FAssetToolsConsoleCommands(const FAssetToolsModule& InModule)
: Module(InModule)
//, CreateCommand(
// TEXT( "CollectionManager.Create" ),
// *LOCTEXT("CommandText_Create", "Creates a collection of the specified name and type").ToString(),
// FConsoleCommandWithArgsDelegate::CreateRaw( this, &FCollectionManagerConsoleCommands::Create ) )
{}
//void Create(const TArray<FString>& Args)
//{
//}
};
#undef LOCTEXT_NAMESPACE
@@ -1,7 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
DECLARE_LOG_CATEGORY_EXTERN(LogAssetTools, Log, All);
@@ -1,47 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AssetToolsModule.h"
#include "AssetToolsLog.h"
#include "AssetTools.h"
#include "AssetToolsConsoleCommands.h"
#include "MessageLogInitializationOptions.h"
#include "MessageLogModule.h"
IMPLEMENT_MODULE( FAssetToolsModule, AssetTools );
DEFINE_LOG_CATEGORY(LogAssetTools);
void FAssetToolsModule::StartupModule()
{
ConsoleCommands = new FAssetToolsConsoleCommands(*this);
AssetToolsPtr = MakeWeakObjectPtr(const_cast<UAssetToolsImpl*>(GetDefault<UAssetToolsImpl>()));
// create a message log for the asset tools to use
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
FMessageLogInitializationOptions InitOptions;
InitOptions.bShowPages = true;
MessageLogModule.RegisterLogListing("AssetTools", NSLOCTEXT("AssetTools", "AssetToolsLogLabel", "Asset Tools"), InitOptions);
}
void FAssetToolsModule::ShutdownModule()
{
AssetToolsPtr = nullptr;
if (ConsoleCommands != NULL)
{
delete ConsoleCommands;
ConsoleCommands = NULL;
}
if (FModuleManager::Get().IsModuleLoaded("MessageLog"))
{
// unregister message log
FMessageLogModule& MessageLogModule = FModuleManager::GetModuleChecked<FMessageLogModule>("MessageLog");
MessageLogModule.UnregisterLogListing("AssetTools");
}
}
IAssetTools& FAssetToolsModule::Get() const
{
return *AssetToolsPtr;
}
@@ -1,18 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"
#include "GameFramework/Actor.h"
class FAssetTypeActions_Actor : public FAssetTypeActions_Base
{
public:
// IAssetTypeActions Implementation
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_Actor", "Actor"); }
virtual FColor GetTypeColor() const override { return FColor(0,232,0); }
virtual UClass* GetSupportedClass() const override { return AActor::StaticClass(); }
virtual uint32 GetCategories() override { return EAssetTypeCategories::None; }
virtual FString GetObjectDisplayName(UObject* Object) const override { return CastChecked<AActor>(Object)->GetActorLabel(); }
};
@@ -1,9 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AssetTypeActions/AssetTypeActions_ActorFoliageSettings.h"
#include "FoliageType_Actor.h"
UClass* FAssetTypeActions_ActorFoliageSettings::GetSupportedClass() const
{
return UFoliageType_Actor::StaticClass();
}
@@ -1,23 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"
class FAssetTypeActions_ActorFoliageSettings : public FAssetTypeActions_Base
{
public:
FAssetTypeActions_ActorFoliageSettings(EAssetTypeCategories::Type InAssetCategoryBit)
: AssetCategoryBit(InAssetCategoryBit)
{}
// IAssetTypeActions Implementation
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ActorFoliageSettings", "Actor Foliage"); }
virtual FColor GetTypeColor() const override { return FColor(12, 65, 12); }
virtual UClass* GetSupportedClass() const override;
virtual uint32 GetCategories() override { return AssetCategoryBit; }
private:
EAssetTypeCategories::Type AssetCategoryBit;
};
@@ -1,16 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions/AssetTypeActions_BlendSpace.h"
#include "Animation/AimOffsetBlendSpace.h"
class FAssetTypeActions_AimOffset : public FAssetTypeActions_BlendSpace
{
public:
// IAssetTypeActions Implementation
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AimOffset", "Aim Offset"); }
virtual FColor GetTypeColor() const override { return FColor(0,162,232); }
virtual UClass* GetSupportedClass() const override { return UAimOffsetBlendSpace::StaticClass(); }
};
@@ -1,16 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions/AssetTypeActions_BlendSpace1D.h"
#include "Animation/AimOffsetBlendSpace1D.h"
class FAssetTypeActions_AimOffset1D : public FAssetTypeActions_BlendSpace1D
{
public:
// IAssetTypeActions Implementation
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AimOffset1D", "Aim Offset 1D"); }
virtual FColor GetTypeColor() const override { return FColor(0,162,232); }
virtual UClass* GetSupportedClass() const override { return UAimOffsetBlendSpace1D::StaticClass(); }
};
@@ -1,279 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AssetTypeActions/AssetTypeActions_AnimBlueprint.h"
#include "ToolMenus.h"
#include "Widgets/Layout/SBorder.h"
#include "Misc/MessageDialog.h"
#include "Widgets/Images/SImage.h"
#include "EditorStyleSet.h"
#include "Animation/AnimInstance.h"
#include "Factories/AnimBlueprintFactory.h"
#include "ThumbnailRendering/SceneThumbnailInfo.h"
#include "AssetTools.h"
#include "PersonaModule.h"
#include "Framework/Notifications/NotificationManager.h"
#include "SBlueprintDiff.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "SSkeletonWidget.h"
#include "Styling/SlateIconFinder.h"
#include "IAnimationBlueprintEditorModule.h"
#include "Preferences/PersonaOptions.h"
#if WITH_EDITOR
#include "Subsystems/AssetEditorSubsystem.h"
#include "Editor.h"
#endif
#define LOCTEXT_NAMESPACE "AssetTypeActions"
void FAssetTypeActions_AnimBlueprint::GetActions(const TArray<UObject*>& InObjects, FToolMenuSection& Section)
{
FAssetTypeActions_Blueprint::GetActions(InObjects, Section);
auto AnimBlueprints = GetTypedWeakObjectPtrs<UAnimBlueprint>(InObjects);
Section.AddMenuEntry(
"AnimBlueprint_FindSkeleton",
LOCTEXT("AnimBlueprint_FindSkeleton", "Find Skeleton"),
LOCTEXT("AnimBlueprint_FindSkeletonTooltip", "Finds the skeleton used by the selected Anim Blueprints in the content browser."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.FindSkeleton"),
FUIAction(
FExecuteAction::CreateSP( this, &FAssetTypeActions_AnimBlueprint::ExecuteFindSkeleton, AnimBlueprints ),
FCanExecuteAction()
)
);
Section.AddSubMenu(
"RetargetBlueprintSubmenu",
LOCTEXT("RetargetBlueprintSubmenu", "Retarget Anim Blueprints"),
LOCTEXT("RetargetBlueprintSubmenu_ToolTip", "Opens the retarget blueprints menu"),
FNewToolMenuDelegate::CreateSP( this, &FAssetTypeActions_AnimBlueprint::FillRetargetMenu, InObjects),
false,
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.RetargetSkeleton")
);
}
void FAssetTypeActions_AnimBlueprint::FillRetargetMenu(UToolMenu* MenuBuilder, const TArray<UObject*> InObjects)
{
bool bAllSkeletonsNull = true;
for(auto Iter = InObjects.CreateConstIterator(); Iter; ++Iter)
{
if(UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(*Iter))
{
if(AnimBlueprint->TargetSkeleton)
{
bAllSkeletonsNull = false;
break;
}
}
}
FToolMenuSection& Section = MenuBuilder->AddSection("Section");
if(bAllSkeletonsNull)
{
Section.AddMenuEntry(
"AnimBlueprint_RetargetSkeletonInPlace",
LOCTEXT("AnimBlueprint_RetargetSkeletonInPlace", "Retarget skeleton on existing Anim Blueprints"),
LOCTEXT("AnimBlueprint_RetargetSkeletonInPlaceTooltip", "Retargets the selected Anim Blueprints to a new skeleton (and optionally all referenced animations too)"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.RetargetSkeleton"),
FUIAction(
FExecuteAction::CreateSP( this, &FAssetTypeActions_AnimBlueprint::RetargetAssets, InObjects, false ), // false = do not duplicate assets first
FCanExecuteAction()
)
);
}
Section.AddMenuEntry(
"AnimBlueprint_DuplicateAndRetargetSkeleton",
LOCTEXT("AnimBlueprint_DuplicateAndRetargetSkeleton", "Duplicate Anim Blueprints and Retarget"),
LOCTEXT("AnimBlueprint_DuplicateAndRetargetSkeletonTooltip", "Duplicates and then retargets the selected Anim Blueprints to a new skeleton (and optionally all referenced animations too)"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.DuplicateAndRetargetSkeleton"),
FUIAction(
FExecuteAction::CreateSP( this, &FAssetTypeActions_AnimBlueprint::RetargetAssets, InObjects, true ), // true = duplicate assets and retarget them
FCanExecuteAction()
)
);
}
UThumbnailInfo* FAssetTypeActions_AnimBlueprint::GetThumbnailInfo(UObject* Asset) const
{
UAnimBlueprint* AnimBlueprint = CastChecked<UAnimBlueprint>(Asset);
UThumbnailInfo* ThumbnailInfo = AnimBlueprint->ThumbnailInfo;
if (ThumbnailInfo == NULL)
{
ThumbnailInfo = NewObject<USceneThumbnailInfo>(AnimBlueprint, NAME_None, RF_Transactional);
AnimBlueprint->ThumbnailInfo = ThumbnailInfo;
}
return ThumbnailInfo;
}
UFactory* FAssetTypeActions_AnimBlueprint::GetFactoryForBlueprintType(UBlueprint* InBlueprint) const
{
UAnimBlueprint* AnimBlueprint = CastChecked<UAnimBlueprint>(InBlueprint);
if(InBlueprint->BlueprintType == BPTYPE_Interface)
{
return NewObject<UAnimLayerInterfaceFactory>();
}
else
{
UAnimBlueprintFactory* AnimBlueprintFactory = NewObject<UAnimBlueprintFactory>();
AnimBlueprintFactory->ParentClass = TSubclassOf<UAnimInstance>(*InBlueprint->GeneratedClass);
AnimBlueprintFactory->TargetSkeleton = AnimBlueprint->TargetSkeleton;
return AnimBlueprintFactory;
}
}
void FAssetTypeActions_AnimBlueprint::OpenAssetEditor( const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor )
{
EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;
for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt)
{
auto AnimBlueprint = Cast<UAnimBlueprint>(*ObjIt);
if (AnimBlueprint != NULL && AnimBlueprint->SkeletonGeneratedClass && AnimBlueprint->GeneratedClass)
{
if(AnimBlueprint->BlueprintType != BPTYPE_Interface && !AnimBlueprint->TargetSkeleton)
{
FText ShouldRetargetMessage = LOCTEXT("ShouldRetarget_Message", "Could not find the skeleton for Anim Blueprint '{BlueprintName}' Would you like to choose a new one?");
FFormatNamedArguments Arguments;
Arguments.Add( TEXT("BlueprintName"), FText::FromString(AnimBlueprint->GetName()));
if ( FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(ShouldRetargetMessage, Arguments)) == EAppReturnType::Yes )
{
bool bDuplicateAssets = false;
TArray<UObject*> AnimBlueprints;
AnimBlueprints.Add(AnimBlueprint);
RetargetAssets(AnimBlueprints, bDuplicateAssets);
}
}
else
{
const bool bBringToFrontIfOpen = true;
#if WITH_EDITOR
if (IAssetEditorInstance* EditorInstance = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(AnimBlueprint, bBringToFrontIfOpen))
{
EditorInstance->FocusWindow(AnimBlueprint);
}
else
#endif
{
IAnimationBlueprintEditorModule& AnimationBlueprintEditorModule = FModuleManager::LoadModuleChecked<IAnimationBlueprintEditorModule>("AnimationBlueprintEditor");
AnimationBlueprintEditorModule.CreateAnimationBlueprintEditor(Mode, EditWithinLevelEditor, AnimBlueprint);
}
}
}
else
{
FMessageDialog::Open( EAppMsgType::Ok, LOCTEXT("FailedToLoadCorruptAnimBlueprint", "The Anim Blueprint could not be loaded because it is corrupt."));
}
}
}
void FAssetTypeActions_AnimBlueprint::PerformAssetDiff(UObject* Asset1, UObject* Asset2, const struct FRevisionInfo& OldRevision, const struct FRevisionInfo& NewRevision) const
{
UBlueprint* OldBlueprint = CastChecked<UBlueprint>(Asset1);
UBlueprint* NewBlueprint = CastChecked<UBlueprint>(Asset2);
// sometimes we're comparing different revisions of one single asset (other
// times we're comparing two completely separate assets altogether)
bool bIsSingleAsset = (NewBlueprint->GetName() == OldBlueprint->GetName());
FText WindowTitle = LOCTEXT("NamelessAnimationBlueprintDiff", "Animation Blueprint Diff");
// if we're diffing one asset against itself
if (bIsSingleAsset)
{
// identify the assumed single asset in the window's title
WindowTitle = FText::Format(LOCTEXT("AnimationBlueprintDiff", "{0} - Animation Blueprint Diff"), FText::FromString(NewBlueprint->GetName()));
}
SBlueprintDiff::CreateDiffWindow(WindowTitle, OldBlueprint, NewBlueprint, OldRevision, NewRevision);
}
void FAssetTypeActions_AnimBlueprint::ExecuteFindSkeleton(TArray<TWeakObjectPtr<UAnimBlueprint>> Objects)
{
TArray<UObject*> ObjectsToSync;
for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt)
{
auto Object = (*ObjIt).Get();
if ( Object )
{
USkeleton* Skeleton = Object->TargetSkeleton;
if (Skeleton)
{
ObjectsToSync.AddUnique(Skeleton);
}
}
}
if ( ObjectsToSync.Num() > 0 )
{
FAssetTools::Get().SyncBrowserToAssets(ObjectsToSync);
}
}
void FAssetTypeActions_AnimBlueprint::RetargetAnimationHandler(USkeleton* OldSkeleton, USkeleton* NewSkeleton, bool bRemapReferencedAssets, bool bAllowRemapToExisting, bool bConvertSpaces, const EditorAnimUtils::FNameDuplicationRule* NameRule, TArray<TWeakObjectPtr<UObject>> AnimBlueprints)
{
if(!OldSkeleton || OldSkeleton->GetPreviewMesh(true))
{
FAnimationRetargetContext RetargetContext(AnimBlueprints, bRemapReferencedAssets, bConvertSpaces);
if(bAllowRemapToExisting)
{
SAnimationRemapAssets::ShowWindow(RetargetContext, NewSkeleton);
}
EditorAnimUtils::RetargetAnimations(OldSkeleton, NewSkeleton, RetargetContext, bRemapReferencedAssets, NameRule);
}
else
{
FFormatNamedArguments Args;
Args.Add(TEXT("OldSkeletonName"), FText::FromString(GetNameSafe(OldSkeleton)));
Args.Add(TEXT("NewSkeletonName"), FText::FromString(GetNameSafe(NewSkeleton)));
FNotificationInfo Info(FText::Format(LOCTEXT("Retarget Failed", "Old Skeleton {OldSkeletonName} and New Skeleton {NewSkeletonName} need to have Preview Mesh set up to convert animation"), Args));
Info.ExpireDuration = 5.0f;
Info.bUseLargeFont = false;
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
if(Notification.IsValid())
{
Notification->SetCompletionState(SNotificationItem::CS_Fail);
}
}
}
void FAssetTypeActions_AnimBlueprint::RetargetAssets(TArray<UObject*> InAnimBlueprints, bool bDuplicateAssets)
{
bool bRemapReferencedAssets = false;
USkeleton* OldSkeleton = NULL;
if ( InAnimBlueprints.Num() > 0 )
{
UAnimBlueprint * AnimBP = CastChecked<UAnimBlueprint>(InAnimBlueprints[0]);
OldSkeleton = AnimBP->TargetSkeleton;
}
const FText Message = LOCTEXT("RemapSkeleton_Warning", "Select the skeleton to remap this asset to.");
auto AnimBlueprints = GetTypedWeakObjectPtrs<UObject>(InAnimBlueprints);
SAnimationRemapSkeleton::ShowWindow(OldSkeleton, Message, bDuplicateAssets, FOnRetargetAnimation::CreateSP(this, &FAssetTypeActions_AnimBlueprint::RetargetAnimationHandler, AnimBlueprints));
}
TSharedPtr<SWidget> FAssetTypeActions_AnimBlueprint::GetThumbnailOverlay(const FAssetData& AssetData) const
{
const FSlateBrush* Icon = FSlateIconFinder::FindIconBrushForClass(UAnimBlueprint::StaticClass());
return SNew(SBorder)
.BorderImage(FEditorStyle::GetNoBrush())
.Visibility(EVisibility::HitTestInvisible)
.Padding(FMargin(0.0f, 0.0f, 0.0f, 3.0f))
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
[
SNew(SImage)
.Image(Icon)
];
}
#undef LOCTEXT_NAMESPACE
@@ -1,43 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Toolkits/IToolkitHost.h"
#include "AssetTypeActions/AssetTypeActions_Blueprint.h"
#include "EditorAnimUtils.h"
#include "Animation/AnimBlueprint.h"
class UFactory;
class FAssetTypeActions_AnimBlueprint : public FAssetTypeActions_Blueprint
{
public:
// IAssetTypeActions Implementation
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AnimBlueprint", "Animation Blueprint"); }
virtual FColor GetTypeColor() const override { return FColor(200,116,0); }
virtual UClass* GetSupportedClass() const override { return UAnimBlueprint::StaticClass(); }
virtual void GetActions(const TArray<UObject*>& InObjects, struct FToolMenuSection& Section) override;
virtual void OpenAssetEditor( const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>() ) override;
virtual uint32 GetCategories() override { return EAssetTypeCategories::Animation; }
virtual void PerformAssetDiff(UObject* Asset1, UObject* Asset2, const struct FRevisionInfo& OldRevision, const struct FRevisionInfo& NewRevision) const override;
virtual class UThumbnailInfo* GetThumbnailInfo(UObject* Asset) const override;
virtual TSharedPtr<SWidget> GetThumbnailOverlay(const FAssetData& AssetData) const override;
// FAssetTypeActions_Blueprint interface
virtual UFactory* GetFactoryForBlueprintType(UBlueprint* InBlueprint) const override;
private:
/** Handler to fill the retarget submenu */
void FillRetargetMenu(class UToolMenu* Menu, const TArray<UObject*> InObjects);
/** Handler for when FindSkeleton is selected */
void ExecuteFindSkeleton(TArray<TWeakObjectPtr<UAnimBlueprint>> Objects);
/** Context menu item handler for changing the supplied assets skeletons */
void RetargetAssets(TArray<UObject*> InAnimBlueprints, bool bDuplicateAssets);
/** Handler for retargeting */
void RetargetAnimationHandler(USkeleton* OldSkeleton, USkeleton* NewSkeleton, bool bRemapReferencedAssets, bool bAllowRemapToExisting, bool bConvertSpaces, const EditorAnimUtils::FNameDuplicationRule* NameRule, TArray<TWeakObjectPtr<UObject>> AnimBlueprints);
};
@@ -1,115 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AssetTypeActions/AssetTypeActions_AnimBoneCompressionSettings.h"
#include "Animation/AnimSequence.h"
#include "Dialogs/Dialogs.h"
#include "EditorStyleSet.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Misc/MessageDialog.h"
#include "Misc/ScopedSlowTask.h"
#include "UObject/UObjectIterator.h"
#define LOCTEXT_NAMESPACE "AssetTypeActions"
void FAssetTypeActions_AnimBoneCompressionSettings::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor)
{
TSharedRef<FSimpleAssetEditor> AssetEditor = FSimpleAssetEditor::CreateEditor(EToolkitMode::Standalone, EditWithinLevelEditor, InObjects);
auto SettingAssets = GetTypedWeakObjectPtrs<UAnimBoneCompressionSettings>(InObjects);
if (SettingAssets.Num() == 1)
{
TSharedPtr<class FUICommandList> PluginCommands = MakeShareable(new FUICommandList);
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension("Asset", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FAssetTypeActions_AnimBoneCompressionSettings::AddToolbarExtension, SettingAssets[0]));
AssetEditor->AddToolbarExtender(ToolbarExtender);
AssetEditor->RegenerateMenusAndToolbars();
}
}
void FAssetTypeActions_AnimBoneCompressionSettings::AddToolbarExtension(FToolBarBuilder& Builder, TWeakObjectPtr<UAnimBoneCompressionSettings> BoneSettings)
{
Builder.BeginSection("Compress");
Builder.AddToolBarButton(
FUIAction(
FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimBoneCompressionSettings::ExecuteCompression, BoneSettings)
),
NAME_None,
LOCTEXT("AnimBoneCompressionSettings_Compress", "Compress"),
LOCTEXT("AnimBoneCompressionSettings_CompressTooltip", "All animation sequences that use these settings will be compressed."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ApplyCompression")
);
Builder.EndSection();
}
void FAssetTypeActions_AnimBoneCompressionSettings::GetActions(const TArray<UObject*>& InObjects, FMenuBuilder& MenuBuilder)
{
auto SettingAssets = GetTypedWeakObjectPtrs<UAnimBoneCompressionSettings>(InObjects);
if (SettingAssets.Num() != 1)
{
return;
}
MenuBuilder.AddMenuEntry(
LOCTEXT("AnimBoneCompressionSettings_Compress", "Compress"),
LOCTEXT("AnimBoneCompressionSettings_CompressTooltip", "All animation sequences that use these settings will be compressed."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ApplyCompression.Small"),
FUIAction(
FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimBoneCompressionSettings::ExecuteCompression, SettingAssets[0])
)
);
}
void FAssetTypeActions_AnimBoneCompressionSettings::ExecuteCompression(TWeakObjectPtr<UAnimBoneCompressionSettings> BoneSettings)
{
if (!BoneSettings.IsValid())
{
return;
}
UAnimBoneCompressionSettings* Settings = BoneSettings.Get();
TArray<UAnimSequence*> AnimSeqsToRecompress;
for (TObjectIterator<UAnimSequence> It; It; ++It)
{
UAnimSequence* AnimSeq = *It;
if (AnimSeq->GetOutermost() == GetTransientPackage())
{
continue;
}
if (AnimSeq->BoneCompressionSettings == Settings)
{
AnimSeqsToRecompress.Add(AnimSeq);
}
}
if (AnimSeqsToRecompress.Num() == 0)
{
return;
}
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("NumAnimSequences"), FText::AsNumber(AnimSeqsToRecompress.Num()));
FText DialogText = FText::Format(LOCTEXT("AnimBoneCompressionSettings_CompressWarningText", "{NumAnimSequences} animation sequences are about to compress."), Arguments);
FText DialogTitle = LOCTEXT("AnimBoneCompressionSettings_CompressWarning", "Warning");
const EAppReturnType::Type DlgResult = FMessageDialog::Open(EAppMsgType::OkCancel, DialogText, &DialogTitle);
if (DlgResult != EAppReturnType::Ok)
{
return;
}
const FText StatusText = FText::Format(LOCTEXT("AnimBoneCompressionSettings_Compressing", "Compressing '{0}' animations"), FText::AsNumber(AnimSeqsToRecompress.Num()));
FScopedSlowTask LoadingAnimSlowTask(AnimSeqsToRecompress.Num(), StatusText);
LoadingAnimSlowTask.MakeDialog();
for (UAnimSequence* AnimSeq : AnimSeqsToRecompress)
{
LoadingAnimSlowTask.EnterProgressFrame();
AnimSeq->RequestSyncAnimRecompression(false);
}
}
#undef LOCTEXT_NAMESPACE
@@ -1,27 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"
#include "Animation/AnimBoneCompressionSettings.h"
class FAssetTypeActions_AnimBoneCompressionSettings : public FAssetTypeActions_Base
{
public:
// IAssetTypeActions Implementation
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AnimBoneCompressionSettings", "Bone Compression Settings"); }
virtual FColor GetTypeColor() const override { return FColor(255, 255, 0); }
virtual UClass* GetSupportedClass() const override { return UAnimBoneCompressionSettings::StaticClass(); }
virtual bool CanFilter() override { return true; }
virtual uint32 GetCategories() override { return EAssetTypeCategories::Animation; }
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
virtual bool HasActions(const TArray<UObject*>& InObjects) const override { return true; }
virtual void GetActions(const TArray<UObject*>& InObjects, FMenuBuilder& MenuBuilder) override;
private:
void AddToolbarExtension(FToolBarBuilder& Builder, TWeakObjectPtr<UAnimBoneCompressionSettings> BoneSettings);
void ExecuteCompression(TWeakObjectPtr<UAnimBoneCompressionSettings> BoneSettings);
};
@@ -1,17 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions/AssetTypeActions_AnimationAsset.h"
#include "Animation/AnimComposite.h"
class FAssetTypeActions_AnimComposite : public FAssetTypeActions_AnimationAsset
{
public:
// IAssetTypeActions Implementation
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AnimComposite", "Animation Composite"); }
virtual FColor GetTypeColor() const override { return FColor(181,230,29); }
virtual UClass* GetSupportedClass() const override { return UAnimComposite::StaticClass(); }
virtual bool CanFilter() override { return true; }
};
@@ -1,114 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h"
#include "Animation/AnimSequence.h"
#include "Misc/MessageDialog.h"
#include "EditorStyleSet.h"
#include "ToolMenus.h"
#include "Misc/ScopedSlowTask.h"
#include "UObject/UObjectIterator.h"
#define LOCTEXT_NAMESPACE "AssetTypeActions"
void FAssetTypeActions_AnimCurveCompressionSettings::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor)
{
TSharedRef<FSimpleAssetEditor> AssetEditor = FSimpleAssetEditor::CreateEditor(EToolkitMode::Standalone, EditWithinLevelEditor, InObjects);
auto SettingAssets = GetTypedWeakObjectPtrs<UAnimCurveCompressionSettings>(InObjects);
if (SettingAssets.Num() == 1)
{
TSharedPtr<class FUICommandList> PluginCommands = MakeShareable(new FUICommandList);
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension("Asset", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FAssetTypeActions_AnimCurveCompressionSettings::AddToolbarExtension, SettingAssets[0]));
AssetEditor->AddToolbarExtender(ToolbarExtender);
AssetEditor->RegenerateMenusAndToolbars();
}
}
void FAssetTypeActions_AnimCurveCompressionSettings::AddToolbarExtension(FToolBarBuilder& Builder, TWeakObjectPtr<UAnimCurveCompressionSettings> CurveSettings)
{
Builder.BeginSection("Compress");
Builder.AddToolBarButton(
FUIAction(
FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimCurveCompressionSettings::ExecuteCompression, CurveSettings)
),
NAME_None,
LOCTEXT("AnimCurveCompressionSettings_Compress", "Compress"),
LOCTEXT("AnimCurveCompressionSettings_CompressTooltip", "All animation sequences that use these settings will be compressed."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ApplyCompression")
);
Builder.EndSection();
}
void FAssetTypeActions_AnimCurveCompressionSettings::GetActions(const TArray<UObject*>& InObjects, FToolMenuSection& Section)
{
auto SettingAssets = GetTypedWeakObjectPtrs<UAnimCurveCompressionSettings>(InObjects);
if (SettingAssets.Num() != 1)
{
return;
}
Section.AddMenuEntry(
"AnimCurveCompressionSettings_Compress",
LOCTEXT("AnimCurveCompressionSettings_Compress", "Compress"),
LOCTEXT("AnimCurveCompressionSettings_CompressTooltip", "All animation sequences that use these settings will be compressed."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ApplyCompression.Small"),
FUIAction(
FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimCurveCompressionSettings::ExecuteCompression, SettingAssets[0])
)
);
}
void FAssetTypeActions_AnimCurveCompressionSettings::ExecuteCompression(TWeakObjectPtr<UAnimCurveCompressionSettings> CurveSettings)
{
if (!CurveSettings.IsValid())
{
return;
}
UAnimCurveCompressionSettings* Settings = CurveSettings.Get();
TArray<UAnimSequence*> AnimSeqsToRecompress;
for (TObjectIterator<UAnimSequence> It; It; ++It)
{
UAnimSequence* AnimSeq = *It;
if (AnimSeq->GetOutermost() == GetTransientPackage())
{
continue;
}
if (AnimSeq->CurveCompressionSettings == Settings)
{
AnimSeqsToRecompress.Add(AnimSeq);
}
}
if (AnimSeqsToRecompress.Num() == 0)
{
return;
}
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("NumAnimSequences"), FText::AsNumber(AnimSeqsToRecompress.Num()));
FText DialogText = FText::Format(LOCTEXT("AnimCurveCompressionSettings_CompressWarningText", "{NumAnimSequences} animation sequences are about to compress."), Arguments);
FText DialogTitle = LOCTEXT("AnimCurveCompressionSettings_CompressWarning", "Warning");
const EAppReturnType::Type DlgResult = FMessageDialog::Open(EAppMsgType::OkCancel, DialogText, &DialogTitle);
if (DlgResult != EAppReturnType::Ok)
{
return;
}
const FText StatusText = FText::Format(LOCTEXT("AnimCurveCompressionSettings_Compressing", "Compressing '{0}' animations"), FText::AsNumber(AnimSeqsToRecompress.Num()));
FScopedSlowTask LoadingAnimSlowTask(AnimSeqsToRecompress.Num(), StatusText);
LoadingAnimSlowTask.MakeDialog();
for (UAnimSequence* AnimSeq : AnimSeqsToRecompress)
{
LoadingAnimSlowTask.EnterProgressFrame();
AnimSeq->RequestSyncAnimRecompression(false);
}
}
#undef LOCTEXT_NAMESPACE
@@ -1,27 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"
#include "Animation/AnimCurveCompressionSettings.h"
class FAssetTypeActions_AnimCurveCompressionSettings : public FAssetTypeActions_Base
{
public:
// IAssetTypeActions Implementation
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AnimCurveCompressionSettings", "Curve Compression Settings"); }
virtual FColor GetTypeColor() const override { return FColor(255, 255, 0); }
virtual UClass* GetSupportedClass() const override { return UAnimCurveCompressionSettings::StaticClass(); }
virtual bool CanFilter() override { return true; }
virtual uint32 GetCategories() override { return EAssetTypeCategories::Animation; }
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
virtual bool HasActions(const TArray<UObject*>& InObjects) const override { return true; }
virtual void GetActions(const TArray<UObject*>& InObjects, struct FToolMenuSection& Section) override;
private:
void AddToolbarExtension(FToolBarBuilder& Builder, TWeakObjectPtr<UAnimCurveCompressionSettings> CurveSettings);
void ExecuteCompression(TWeakObjectPtr<UAnimCurveCompressionSettings> CurveSettings);
};
@@ -1,70 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AssetTypeActions/AssetTypeActions_AnimMontage.h"
#include "Factories/AnimMontageFactory.h"
#include "AnimationEditorUtils.h"
#include "ToolMenus.h"
#include "AssetTools.h"
#include "EditorStyleSet.h"
#define LOCTEXT_NAMESPACE "AssetTypeActions"
void FAssetTypeActions_AnimMontage::GetActions(const TArray<UObject*>& InObjects, FToolMenuSection& Section)
{
auto Montages = GetTypedWeakObjectPtrs<UAnimMontage>(InObjects);
// only show child montage if inobjects are not child montage already
bool bContainsChildMontage = false;
for (UObject* Object : InObjects)
{
bContainsChildMontage |= CastChecked<UAnimationAsset>(Object)->HasParentAsset();
if (bContainsChildMontage)
{
break;
}
}
// if no child montage is found
if (!bContainsChildMontage)
{
// create mew child anim montage
Section.AddMenuEntry(
"AnimMontage_CreateChildMontage",
LOCTEXT("AnimMontage_CreateChildMontage", "Create Child Montage"),
LOCTEXT("AnimMontage_CreateChildMontageTooltip", "Create Child Animation Montage and remap to another animation assets."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.AnimMontage"),
FUIAction(FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimMontage::CreateChildAnimMontage, Montages))
);
}
FAssetTypeActions_AnimationAsset::GetActions(InObjects, Section);
}
void FAssetTypeActions_AnimMontage::CreateChildAnimMontage(TArray<TWeakObjectPtr<UAnimMontage>> AnimMontages)
{
if (AnimMontages.Num() > 0)
{
const FString DefaultSuffix = TEXT("_Montage");
UAnimMontageFactory* Factory = NewObject<UAnimMontageFactory>();
TArray<UObject*> ObjectsToSync;
// need to know source and target
for (int32 MontageIndex = 0; MontageIndex < AnimMontages.Num(); ++MontageIndex)
{
UAnimMontage* ParentMontage = AnimMontages[MontageIndex].Get();
check(ParentMontage);
UAnimMontage* NewAsset = AnimationEditorUtils::CreateAnimationAsset<UAnimMontage>(ParentMontage->GetSkeleton(), ParentMontage->GetOutermost()->GetName(), TEXT("_Child"));
if (NewAsset)
{
NewAsset->SetParentAsset(ParentMontage);
ObjectsToSync.Add(NewAsset);
}
}
FAssetTools::Get().SyncBrowserToAssets(ObjectsToSync);
}
}
#undef LOCTEXT_NAMESPACE
@@ -1,26 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions/AssetTypeActions_AnimationAsset.h"
#include "Animation/AnimMontage.h"
class FAssetTypeActions_AnimMontage : public FAssetTypeActions_AnimationAsset
{
public:
// IAssetTypeActions Implementation
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AnimMontage", "Animation Montage"); }
virtual FColor GetTypeColor() const override { return FColor(100,100,255); }
virtual UClass* GetSupportedClass() const override { return UAnimMontage::StaticClass(); }
virtual bool CanFilter() override { return true; }
virtual void GetActions(const TArray<UObject*>& InObjects, struct FToolMenuSection& Section) override;
private:
/*
* Child Anim Montage: Child Anim Montage only can replace name of animations, and no other meaningful edits
* as it will derive every data from Parent. There might be some other data that will allow to be replaced, but for now, it is
* not.
*/
void CreateChildAnimMontage(TArray<TWeakObjectPtr<UAnimMontage>> AnimMontages);
};
@@ -1,275 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AssetTypeActions/AssetTypeActions_AnimSequence.h"
#include "Animation/AnimSequence.h"
#include "ToolMenus.h"
#include "EditorStyleSet.h"
#include "EditorReimportHandler.h"
#include "Animation/AnimMontage.h"
#include "Factories/AnimCompositeFactory.h"
#include "Factories/AnimStreamableFactory.h"
#include "Factories/AnimMontageFactory.h"
#include "Factories/PoseAssetFactory.h"
#include "EditorFramework/AssetImportData.h"
#include "Animation/AnimComposite.h"
#include "Animation/AnimStreamable.h"
#include "Animation/PoseAsset.h"
#include "AssetTools.h"
#include "IContentBrowserSingleton.h"
#include "ContentBrowserModule.h"
#include "IAnimationModifiersModule.h"
#include "Algo/Transform.h"
#define LOCTEXT_NAMESPACE "AssetTypeActions"
UClass* FAssetTypeActions_AnimSequence::GetSupportedClass() const
{
return UAnimSequence::StaticClass();
}
void FAssetTypeActions_AnimSequence::GetActions(const TArray<UObject*>& InObjects, FToolMenuSection& Section)
{
auto Sequences = GetTypedWeakObjectPtrs<UAnimSequence>(InObjects);
// create menu
Section.AddSubMenu(
"CreateAnimSubmenu",
LOCTEXT("CreateAnimSubmenu", "Create"),
LOCTEXT("CreateAnimSubmenu_ToolTip", "Create assets from this anim sequence"),
FNewMenuDelegate::CreateSP(this, &FAssetTypeActions_AnimSequence::FillCreateMenu, Sequences),
false,
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.CreateAnimAsset")
);
Section.AddMenuEntry(
"AnimSequence_ReimportWithNewSource",
LOCTEXT("AnimSequence_ReimportWithNewSource", "Reimport with New Source"),
LOCTEXT("AnimSequence_ReimportWithNewSourceTooltip", "Reimport the selected sequence(s) from a new source file."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.ReimportAnim"),
FUIAction(FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteReimportWithNewSource, Sequences))
);
Section.AddMenuEntry(
"AnimSequence_AddAnimationModifier",
LOCTEXT("AnimSequence_AddAnimationModifier", "Add Animation Modifier(s)"),
LOCTEXT("AnimSequence_AddAnimationModifierTooltip", "Apply new animation modifier(s)."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.AnimationModifier"),
FUIAction(FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteAddNewAnimationModifier, Sequences))
);
FAssetTypeActions_AnimationAsset::GetActions(InObjects, Section);
}
void FAssetTypeActions_AnimSequence::FillCreateMenu(FMenuBuilder& MenuBuilder, const TArray<TWeakObjectPtr<UAnimSequence>> Sequences) const
{
MenuBuilder.AddMenuEntry(
LOCTEXT("AnimSequence_NewAnimComposite", "Create AnimComposite"),
LOCTEXT("AnimSequence_NewAnimCompositeTooltip", "Creates an AnimComposite using the selected anim sequence."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.AnimComposite"),
FUIAction(
FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteNewAnimComposite, Sequences),
FCanExecuteAction()
)
);
MenuBuilder.AddMenuEntry(
LOCTEXT("AnimSequence_NewAnimMontage", "Create AnimMontage"),
LOCTEXT("AnimSequence_NewAnimMontageTooltip", "Creates an AnimMontage using the selected anim sequence."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.AnimMontage"),
FUIAction(
FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteNewAnimMontage, Sequences),
FCanExecuteAction()
)
);
// Not supported, streamable animation logic will be ported to UAnimSequence
/*MenuBuilder.AddMenuEntry(
LOCTEXT("AnimSequence_NewAnimStreamable", "Create AnimStreamable"),
LOCTEXT("AnimSequence_NewAnimStreamableTooltip", "Creates an AnimStreamable using the selected anim sequence."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.AnimMontage"),
FUIAction(
FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteNewAnimStreamable, Sequences),
FCanExecuteAction()
)
);*/
MenuBuilder.AddMenuEntry(
LOCTEXT("AnimSequence_NewPoseAsset", "Create PoseAsset"),
LOCTEXT("AnimSequence_NewPoseAssetTooltip", "Creates an PoseAsset using the selected anim sequence."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.PoseAsset"),
FUIAction(
FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteNewPoseAsset, Sequences),
FCanExecuteAction()
)
);
}
void FAssetTypeActions_AnimSequence::GetResolvedSourceFilePaths(const TArray<UObject*>& TypeAssets, TArray<FString>& OutSourceFilePaths) const
{
for (auto& Asset : TypeAssets)
{
const auto AnimSequence = CastChecked<UAnimSequence>(Asset);
AnimSequence->AssetImportData->ExtractFilenames(OutSourceFilePaths);
}
}
void FAssetTypeActions_AnimSequence::ExecuteReimportWithNewSource(TArray<TWeakObjectPtr<UAnimSequence>> Objects)
{
FAssetImportInfo EmptyImportInfo;
TArray<UObject*> ReimportAssets;
for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt)
{
UObject* Object = (*ObjIt).Get();
if (Object)
{
ReimportAssets.Add(Object);
}
}
const bool bShowNotification = !FApp::IsUnattended();
const bool bReimportWithNewFile = true;
const int32 SourceFileIndex = INDEX_NONE;
FReimportManager::Instance()->ValidateAllSourceFileAndReimport(ReimportAssets, bShowNotification, SourceFileIndex, bReimportWithNewFile);
}
void FAssetTypeActions_AnimSequence::ExecuteNewAnimComposite(TArray<TWeakObjectPtr<UAnimSequence>> Objects) const
{
const FString DefaultSuffix = TEXT("_Composite");
UAnimCompositeFactory* Factory = NewObject<UAnimCompositeFactory>();
CreateAnimationAssets(Objects, UAnimComposite::StaticClass(), Factory, DefaultSuffix, FOnConfigureFactory::CreateSP(this, &FAssetTypeActions_AnimSequence::ConfigureFactoryForAnimComposite));
}
void FAssetTypeActions_AnimSequence::ExecuteNewAnimMontage(TArray<TWeakObjectPtr<UAnimSequence>> Objects) const
{
const FString DefaultSuffix = TEXT("_Montage");
UAnimMontageFactory* Factory = NewObject<UAnimMontageFactory>();
CreateAnimationAssets(Objects, UAnimMontage::StaticClass(), Factory, DefaultSuffix, FOnConfigureFactory::CreateSP(this, &FAssetTypeActions_AnimSequence::ConfigureFactoryForAnimMontage));
}
void FAssetTypeActions_AnimSequence::ExecuteNewAnimStreamable(TArray<TWeakObjectPtr<UAnimSequence>> Objects) const
{
const FString DefaultSuffix = TEXT("_Streamable");
UAnimStreamableFactory* Factory = NewObject<UAnimStreamableFactory>();
auto StreamableConfigure = [](UFactory* AssetFactory, UAnimSequence* SourceAnimation) -> bool
{
UAnimStreamableFactory* StreamableAnimFactory = CastChecked<UAnimStreamableFactory>(AssetFactory);
StreamableAnimFactory->SourceAnimation = SourceAnimation;
return true;
};
CreateAnimationAssets(Objects, UAnimStreamable::StaticClass(), Factory, DefaultSuffix, FOnConfigureFactory::CreateLambda(StreamableConfigure));
}
void FAssetTypeActions_AnimSequence::ExecuteNewPoseAsset(TArray<TWeakObjectPtr<UAnimSequence>> Objects) const
{
const FString DefaultSuffix = TEXT("_PoseAsset");
UPoseAssetFactory* Factory = NewObject<UPoseAssetFactory>();
CreateAnimationAssets(Objects, UPoseAsset::StaticClass(), Factory, DefaultSuffix, FOnConfigureFactory::CreateSP(this, &FAssetTypeActions_AnimSequence::ConfigureFactoryForPoseAsset));
}
void FAssetTypeActions_AnimSequence::ExecuteAddNewAnimationModifier(TArray<TWeakObjectPtr<UAnimSequence>> Objects)
{
TArray<UAnimSequence*> AnimSequences;
Algo::TransformIf(Objects, AnimSequences,
[](const TWeakObjectPtr<UAnimSequence>& WeakAnimSequence)
{
return WeakAnimSequence.Get() && WeakAnimSequence->IsA<UAnimSequence>();
},
[](const TWeakObjectPtr<UAnimSequence>& WeakAnimSequence)
{
return WeakAnimSequence.Get();
});
if (IAnimationModifiersModule* Module = FModuleManager::Get().LoadModulePtr<IAnimationModifiersModule>("AnimationModifiers"))
{
Module->ShowAddAnimationModifierWindow(AnimSequences);
}
}
bool FAssetTypeActions_AnimSequence::ConfigureFactoryForAnimComposite(UFactory* AssetFactory, UAnimSequence* SourceAnimation) const
{
UAnimCompositeFactory* CompositeFactory = CastChecked<UAnimCompositeFactory>(AssetFactory);
CompositeFactory->SourceAnimation = SourceAnimation;
return true;
}
bool FAssetTypeActions_AnimSequence::ConfigureFactoryForAnimMontage(UFactory* AssetFactory, UAnimSequence* SourceAnimation) const
{
UAnimMontageFactory* MontageFactory = CastChecked<UAnimMontageFactory>(AssetFactory);
MontageFactory->SourceAnimation = SourceAnimation;
return true;
}
bool FAssetTypeActions_AnimSequence::ConfigureFactoryForPoseAsset(UFactory* AssetFactory, UAnimSequence* SourceAnimation) const
{
UPoseAssetFactory* CompositeFactory = CastChecked<UPoseAssetFactory>(AssetFactory);
CompositeFactory->SourceAnimation = SourceAnimation;
return CompositeFactory->ConfigureProperties();
}
void FAssetTypeActions_AnimSequence::CreateAnimationAssets(const TArray<TWeakObjectPtr<UAnimSequence>>& AnimSequences, TSubclassOf<UAnimationAsset> AssetClass, UFactory* AssetFactory, const FString& InSuffix, FOnConfigureFactory OnConfigureFactory) const
{
if ( AnimSequences.Num() == 1 )
{
auto AnimSequence = AnimSequences[0].Get();
if ( AnimSequence )
{
// Determine an appropriate name for inline-rename
FString Name;
FString PackageName;
CreateUniqueAssetName(AnimSequence->GetOutermost()->GetName(), InSuffix, PackageName, Name);
if (OnConfigureFactory.IsBound())
{
if (OnConfigureFactory.Execute(AssetFactory, AnimSequence))
{
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
ContentBrowserModule.Get().CreateNewAsset(Name, FPackageName::GetLongPackagePath(PackageName), AssetClass, AssetFactory);
}
}
}
}
else
{
TArray<UObject*> ObjectsToSync;
for (auto SeqIt = AnimSequences.CreateConstIterator(); SeqIt; ++SeqIt)
{
UAnimSequence* AnimSequence = (*SeqIt).Get();
if ( AnimSequence )
{
// Determine an appropriate name
FString Name;
FString PackageName;
CreateUniqueAssetName(AnimSequence->GetOutermost()->GetName(), InSuffix, PackageName, Name);
if (OnConfigureFactory.IsBound())
{
if (OnConfigureFactory.Execute(AssetFactory, AnimSequence))
{
// Create the asset, and assign it's skeleton
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
UAnimationAsset* NewAsset = Cast<UAnimationAsset>(AssetToolsModule.Get().CreateAsset(Name, FPackageName::GetLongPackagePath(PackageName), AssetClass, AssetFactory));
if (NewAsset)
{
NewAsset->MarkPackageDirty();
ObjectsToSync.Add(NewAsset);
}
}
}
}
}
if ( ObjectsToSync.Num() > 0 )
{
FAssetTools::Get().SyncBrowserToAssets(ObjectsToSync);
}
}
}
#undef LOCTEXT_NAMESPACE

Some files were not shown because too many files have changed in this diff Show More