Files
UnrealEngine/Engine/Source/Developer/TargetDeviceServices/Private/Services/TargetDeviceService.cpp
2025-05-18 13:04:45 +08:00

519 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Services/TargetDeviceService.h"
#include "HAL/PlatformProcess.h"
#include "HAL/FileManager.h"
#include "HAL/LowLevelMemTracker.h"
#include "IMessageBus.h"
#include "Interfaces/ITargetDevice.h"
#include "Interfaces/ITargetPlatform.h"
#include "MessageEndpoint.h"
#include "MessageEndpointBuilder.h"
#include "Misc/Paths.h"
#include "Misc/ConfigCacheIni.h"
#include "PlatformInfo.h"
#include "Serialization/Archive.h"
#include "Interfaces/ITargetPlatformSettings.h"
#include "Interfaces/ITargetPlatformControls.h"
#include "TargetDeviceServiceMessages.h"
LLM_DECLARE_TAG(TargetDeviceProxyManager);
/* Local helpers
*****************************************************************************/
struct FVariantSortCallback
{
FORCEINLINE bool operator()(const ITargetDeviceWeakPtr& A, const ITargetDeviceWeakPtr& B) const
{
ITargetDevicePtr APtr = A.Pin();
ITargetDevicePtr BPtr = B.Pin();
return APtr->GetPlatformControls().GetVariantPriority() > BPtr->GetPlatformControls().GetVariantPriority();
}
};
/* FTargetDeviceService structors
*****************************************************************************/
FTargetDeviceService::FTargetDeviceService(const FString& InDeviceName, const TSharedRef<IMessageBus, ESPMode::ThreadSafe>& InMessageBus)
: DeviceName(InDeviceName)
, Running(false)
, Shared(false)
{
// initialize messaging
MessageEndpoint = FMessageEndpoint::Builder(FName(*FString::Printf(TEXT("FTargetDeviceService (%s)"), *DeviceName)), InMessageBus)
.Handling<FTargetDeviceClaimDenied>(this, &FTargetDeviceService::HandleClaimDeniedMessage)
.Handling<FTargetDeviceClaimed>(this, &FTargetDeviceService::HandleClaimedMessage)
.Handling<FTargetDeviceServiceTerminateLaunchedProcess>(this, &FTargetDeviceService::HandleTerminateLaunchedProcessMessage)
.Handling<FTargetDeviceServicePing>(this, &FTargetDeviceService::HandlePingMessage)
.Handling<FTargetDeviceServicePowerOff>(this, &FTargetDeviceService::HandlePowerOffMessage)
.Handling<FTargetDeviceServicePowerOn>(this, &FTargetDeviceService::HandlePowerOnMessage)
.Handling<FTargetDeviceServiceReboot>(this, &FTargetDeviceService::HandleRebootMessage)
.Handling<FTargetDeviceUnclaimed>(this, &FTargetDeviceService::HandleUnclaimedMessage);
if (MessageEndpoint.IsValid())
{
MessageEndpoint->Subscribe<FTargetDeviceClaimed>();
MessageEndpoint->Subscribe<FTargetDeviceUnclaimed>();
MessageEndpoint->Subscribe<FTargetDeviceServicePing>();
}
}
FTargetDeviceService::~FTargetDeviceService()
{
Stop();
FMessageEndpoint::SafeRelease(MessageEndpoint);
}
/* ITargetDeviceService interface
*****************************************************************************/
void FTargetDeviceService::AddTargetDevice(TSharedPtr<ITargetDevice, ESPMode::ThreadSafe> InDevice)
{
if (!InDevice.IsValid())
{
return;
}
FName Variant = FName(InDevice->GetPlatformControls().PlatformName().GetCharArray().GetData());
if (DevicePlatformName == NAME_None)
{
// If this seems nasty your right!
// This is just one more nastiness in this class due to the fact that we intend to refactor the target platform stuff as a separate task.
const PlatformInfo::FTargetPlatformInfo& Info = InDevice->GetPlatformControls().GetTargetPlatformInfo();
const PlatformInfo::FTargetPlatformInfo* VanillaInfo = Info.VanillaInfo;
DevicePlatformName = Info.Name;
DevicePlatformDisplayName = VanillaInfo->DisplayName.ToString();
// Sigh the hacks... Should be able to remove if platform info gets cleaned up.... Windows doesn't have a reasonable vanilla platform.
const FString VariableSplit(TEXT("("));
FString Full = VanillaInfo->DisplayName.ToString();
FString Left;
FString Right;
bool bSplit = Full.Split(VariableSplit, &Left, &Right);
DevicePlatformDisplayName = bSplit ? Left.TrimStart() : Full;
}
// double add, which due to the async nature of some device discovery can't be easily avoided.
if (!(TargetDevicePtrs.FindRef(Variant).IsValid()))
{
TargetDevicePtrs.Add(Variant, InDevice);
// sort and choose cache the default
TargetDevicePtrs.ValueSort(FVariantSortCallback());
auto DeviceIterator = TargetDevicePtrs.CreateIterator();
if (DeviceIterator)
{
DefaultDevicePtr = (*DeviceIterator).Value;
}
else
{
DefaultDevicePtr = nullptr;
}
}
}
bool FTargetDeviceService::CanStart(FName InFlavor) const
{
ITargetDevicePtr TargetDevice = GetDevice(InFlavor);
if (TargetDevice.IsValid())
{
return TargetDevice->IsConnected();
}
return false;
}
const FString& FTargetDeviceService::GetClaimHost()
{
return ClaimHost;
}
const FString& FTargetDeviceService::GetClaimUser()
{
return ClaimUser;
}
ITargetDevicePtr FTargetDeviceService::GetDevice(FName InVariant) const
{
ITargetDevicePtr TargetDevice;
if (InVariant == NAME_None)
{
TargetDevice = DefaultDevicePtr.Pin();
}
else
{
const ITargetDeviceWeakPtr * WeakTargetDevicePtr = TargetDevicePtrs.Find(InVariant);
if (WeakTargetDevicePtr != nullptr)
{
TargetDevice = WeakTargetDevicePtr->Pin();
}
}
return TargetDevice;
}
FString FTargetDeviceService::GetDeviceName() const
{
return DeviceName;
}
FName FTargetDeviceService::GetDevicePlatformName() const
{
return DevicePlatformName;
}
FString FTargetDeviceService::GetDevicePlatformDisplayName() const
{
return DevicePlatformDisplayName;
}
bool FTargetDeviceService::IsRunning() const
{
return Running;
}
bool FTargetDeviceService::IsShared() const
{
return (Running && Shared);
}
int32 FTargetDeviceService::NumTargetDevices()
{
return TargetDevicePtrs.Num();
}
void FTargetDeviceService::RemoveTargetDevice(TSharedPtr<ITargetDevice, ESPMode::ThreadSafe> InDevice)
{
if (!InDevice.IsValid())
{
return;
}
FName Variant = FName(InDevice->GetPlatformControls().PlatformName().GetCharArray().GetData());
TargetDevicePtrs.Remove(Variant);
// Cache the default
auto DeviceIterator = TargetDevicePtrs.CreateIterator();
if (DeviceIterator)
{
DefaultDevicePtr = (*DeviceIterator).Value;
}
else
{
DefaultDevicePtr = nullptr;
}
}
void FTargetDeviceService::SetShared(bool InShared)
{
Shared = InShared;
}
bool FTargetDeviceService::Start()
{
if (!Running && MessageEndpoint.IsValid())
{
// notify other services
ClaimAddress = MessageEndpoint->GetAddress();
ClaimHost = FPlatformProcess::ComputerName();
ClaimUser = FPlatformProcess::UserName(false);
MessageEndpoint->Publish(FMessageEndpoint::MakeMessage<FTargetDeviceClaimed>(DeviceName, ClaimHost, ClaimUser));
Running = true;
}
return true;
}
void FTargetDeviceService::Stop()
{
if (Running)
{
// message is going to be deleted by FMemory::Free() (see FMessageContext destructor), so allocate it with Malloc
MessageEndpoint->Publish(FMessageEndpoint::MakeMessage<FTargetDeviceUnclaimed>(DeviceName, FPlatformProcess::ComputerName(), FPlatformProcess::UserName(false)));
FPlatformProcess::SleepNoStats(0.01);
// Only stop the device if we care about device claiming
bool bDisableDeviceClaiming = false;
if(!GConfig->GetBool(TEXT("/Script/Engine.Engine"), TEXT("DisableDeviceClaiming"), bDisableDeviceClaiming, GEngineIni) || !bDisableDeviceClaiming)
{
Running = false;
}
}
}
/* FTargetDeviceService implementation
*****************************************************************************/
bool FTargetDeviceService::StoreDeployedFile(FArchive* FileReader, const FString& TargetFileName) const
{
if (FileReader == nullptr)
{
return false;
}
// create target file
FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*TargetFileName);
if (FileWriter == nullptr)
{
return false;
}
FileReader->Seek(0);
// copy file contents
int64 BytesRemaining = FileReader->TotalSize();
int64 BufferSize = 128 * 1024;
if (BytesRemaining < BufferSize)
{
BufferSize = BytesRemaining;
}
void* Buffer = FMemory::Malloc(BufferSize);
while (BytesRemaining > 0)
{
FileReader->Serialize(Buffer, BufferSize);
FileWriter->Serialize(Buffer, BufferSize);
BytesRemaining -= BufferSize;
if (BytesRemaining < BufferSize)
{
BufferSize = BytesRemaining;
}
}
// clean up
FMemory::Free(Buffer);
delete FileWriter;
return true;
}
/* FTargetDeviceService callbacks
*****************************************************************************/
void FTargetDeviceService::HandleClaimDeniedMessage(const FTargetDeviceClaimDenied& Message, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context)
{
// HACK: Disabling claim denied message. Allows the editor to always claim a device and should prevent cases where instances of the
// editor running on other machines can steal a device from us - which is undesirable on some platforms.
// Also see FTargetDeviceProxyManager::HandlePongMessage()
#if 0
if (Running && (Message.DeviceName == DeviceName))
{
Stop();
ClaimAddress = Context->GetSender();
ClaimHost = Message.HostName;
ClaimUser = Message.HostUser;
}
#endif
}
void FTargetDeviceService::HandleClaimedMessage(const FTargetDeviceClaimed& Message, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context)
{
if (Message.DeviceName != DeviceName)
{
return;
}
if (Running)
{
if (Context->GetSender() != MessageEndpoint->GetAddress())
{
MessageEndpoint->Send(FMessageEndpoint::MakeMessage<FTargetDeviceClaimDenied>(DeviceName, FPlatformProcess::ComputerName(), FPlatformProcess::UserName(false)), Context->GetSender());
}
}
else
{
ClaimAddress = Context->GetSender();
ClaimHost = Message.HostName;
ClaimUser = Message.HostUser;
}
}
void FTargetDeviceService::HandleUnclaimedMessage(const FTargetDeviceUnclaimed& Message, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context)
{
if (Message.DeviceName == DeviceName)
{
if (Context->GetSender() == ClaimAddress)
{
ClaimAddress.Invalidate();
ClaimHost.Empty();
ClaimUser.Empty();
}
}
}
void FTargetDeviceService::HandleTerminateLaunchedProcessMessage(const FTargetDeviceServiceTerminateLaunchedProcess& Message, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context)
{
if (!Running)
{
return;
}
ITargetDevicePtr TargetDevice = GetDevice(Message.Variant);
if (TargetDevice.IsValid())
{
TargetDevice->TerminateLaunchedProcess(Message.AppID);
}
}
void FTargetDeviceService::HandlePingMessage(const FTargetDeviceServicePing& InMessage, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context)
{
if (!Running)
{
return;
}
if (!Shared && (InMessage.HostUser != FPlatformProcess::UserName(false)))
{
return;
}
LLM_SCOPE_BYTAG(TargetDeviceProxyManager);
ITargetDevicePtr DefaultDevice = GetDevice(); // Default Device is needed here!
if (DefaultDevice.IsValid())
{
const FString& PlatformName = DefaultDevice->GetPlatformControls().PlatformName();
const PlatformInfo::FTargetPlatformInfo* VanillaInfo = DefaultDevice->GetPlatformControls().GetTargetPlatformInfo().VanillaInfo;
FTargetDeviceServicePong* Message = FMessageEndpoint::MakeMessage<FTargetDeviceServicePong>();
Message->Name = DefaultDevice->GetName();
Message->Type = TargetDeviceTypes::ToString(DefaultDevice->GetDeviceType());
Message->HostName = FPlatformProcess::ComputerName();
Message->HostUser = FPlatformProcess::UserName(false);
Message->Connected = DefaultDevice->IsConnected();
Message->ConnectionType = TargetDeviceConnectionTypes::ToString(DefaultDevice->GetDeviceConnectionType());
Message->Authorized = DefaultDevice->IsAuthorized();
Message->Make = TEXT("@todo");
Message->Model = DefaultDevice->GetModelId();
Message->OSVersion = DefaultDevice->GetOSVersion();
Message->Architecture = DefaultDevice->GetArchitecture();
DefaultDevice->GetUserCredentials(Message->DeviceUser, Message->DeviceUserPassword);
Message->Shared = Shared;
Message->SupportsMultiLaunch = DefaultDevice->SupportsFeature(ETargetDeviceFeatures::MultiLaunch);
Message->SupportsPowerOff = DefaultDevice->SupportsFeature(ETargetDeviceFeatures::PowerOff);
Message->SupportsPowerOn = DefaultDevice->SupportsFeature(ETargetDeviceFeatures::PowerOn);
Message->SupportsReboot = DefaultDevice->SupportsFeature(ETargetDeviceFeatures::Reboot);
Message->SupportsVariants = DefaultDevice->GetPlatformControls().SupportsVariants();
Message->DefaultVariant = FName(DefaultDevice->GetPlatformControls().PlatformName().GetCharArray().GetData());
// Check if we should also create an aggregate (All_<platform>_devices_on_<host>) proxy
Message->Aggregated = DefaultDevice->IsPlatformAggregated();
Message->AllDevicesName = DefaultDevice->GetAllDevicesName().IsEmpty() ? VanillaInfo->Name.ToString() : DefaultDevice->GetAllDevicesName();
Message->AllDevicesDefaultVariant = DefaultDevice->GetAllDevicesDefaultVariant().IsNone() ? Message->DefaultVariant : DefaultDevice->GetAllDevicesDefaultVariant();
// Add the data for all the flavors
Message->Variants.SetNumZeroed(TargetDevicePtrs.Num());
int Index = 0;
for (auto TargetDeviceIt = TargetDevicePtrs.CreateIterator(); TargetDeviceIt; ++TargetDeviceIt, ++Index)
{
const ITargetDevicePtr& TargetDevice = TargetDeviceIt.Value().Pin();
const PlatformInfo::FTargetPlatformInfo& Info = TargetDevice->GetPlatformControls().GetTargetPlatformInfo();
FTargetDeviceVariant& Variant = Message->Variants[Index];
Variant.DeviceID = TargetDevice->GetId().ToString();
Variant.VariantName = TargetDeviceIt.Key();
Variant.TargetPlatformName = TargetDevice->GetPlatformControls().PlatformName();
Variant.TargetPlatformId = Info.Name;
Variant.VanillaPlatformId = Info.VanillaInfo->Name;
Variant.PlatformDisplayName = Info.DisplayName.ToString();
}
MessageEndpoint->Send(Message, Context->GetSender());
}
}
void FTargetDeviceService::HandlePowerOffMessage( const FTargetDeviceServicePowerOff& Message, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context )
{
if (!Running)
{
return;
}
ITargetDevicePtr TargetDevice = GetDevice(); // Any Device is fine here
if (TargetDevice.IsValid())
{
TargetDevice->PowerOff(Message.Force);
}
}
void FTargetDeviceService::HandlePowerOnMessage( const FTargetDeviceServicePowerOn& Message, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context )
{
if (!Running)
{
return;
}
ITargetDevicePtr TargetDevice = GetDevice(); // Any Device is fine here
if (TargetDevice.IsValid())
{
TargetDevice->PowerOn();
}
}
void FTargetDeviceService::HandleRebootMessage( const FTargetDeviceServiceReboot& Message, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context )
{
if (!Running)
{
return;
}
ITargetDevicePtr TargetDevice = GetDevice(); // Any Device is fine here
if (TargetDevice.IsValid())
{
TargetDevice->Reboot();
}
}