// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnrealBuildTool; namespace Gauntlet { /// /// ITargetDevice extension for platforms that support configurable settings /// public interface IConfigurableDevice { /// /// Returns a configuration profile with the current device settings /// /// PlatformConfigurationBase GetCurrentConfigurationSnapshot(); /// /// Applies a configuration profile to the device. If required it will reboot the device /// /// /// false if applying the config profile fails bool ApplyConfiguration(PlatformConfigurationBase Configuration); } /// /// Base interface for a platform specific configuration reader /// public interface IPlatformConfigurationReader { bool SupportsPlatform(UnrealTargetPlatform? Platform); /// /// Returns the supported platform config file extension /// /// string ConfigFileExtension(); /// /// Reads the configuration from the passed in file location /// /// An absolute path the to a file with platform configuration /// PlatformConfigurationBase ReadConfiguration(FileReference Location); } /// /// Base class for a platform configuration profile. /// This class needs to be implemented for each configurable platform /// public abstract class PlatformConfigurationBase { /// /// Name of the config profile. /// This name doesn't need to be unique across namespaces. /// public string ProfileName { get; set; } /// /// Namespace of the profile. Profiles have to have a unique name inside a namespace. /// public string Namespace { get; set; } /// /// Platform this configuration profile is for /// public UnrealTargetPlatform Platform { get; set; } } /// /// A singleton class that encapsulates a cache of device config profiles /// public class DeviceConfigurationCache { public static DeviceConfigurationCache Instance { get; private set; } = new(); private Object LockObject = new Object(); private Dictionary ConfigurationCache = new(); protected DeviceConfigurationCache() { Instance = this; } /// /// Key by which a configuration profile is identified in the cache. /// class ConfigurationCacheKey { public UnrealTargetPlatform Platform; public string Namespace; public string ProfileName; public ConfigurationCacheKey(UnrealTargetPlatform Platform, string ProjectName, string ProfileName) { this.Platform = Platform; this.Namespace = ProjectName; this.ProfileName = ProfileName; } public ConfigurationCacheKey(PlatformConfigurationBase Configuration) { this.Platform = Configuration.Platform; this.Namespace = Configuration.Namespace; this.ProfileName = Configuration.ProfileName; } public override bool Equals(object Other) { ConfigurationCacheKey OtherKey = Other as ConfigurationCacheKey; return OtherKey != null && OtherKey.Platform == Platform && OtherKey.ProfileName == ProfileName && OtherKey.Namespace == Namespace; } public override int GetHashCode() { return (Platform, Namespace, ProfileName).GetHashCode(); } } /// /// Scans the SettingDir for configuration profiles /// /// Platform for which to look for config files /// The namespace in which to put the configuration profiles /// /// public bool DiscoverConfigurationProfiles(UnrealTargetPlatform? Platform, string Namespace, string SettingDir) { if (Platform == null || !Directory.Exists(SettingDir)) { return false; } IPlatformConfigurationReader ConfigReader = Utils.InterfaceHelpers.FindImplementations(true) .Where(D => D.SupportsPlatform(Platform)) .FirstOrDefault(); if (ConfigReader == null) { Log.Info("Couldn't find a configuration reader for {0}", Platform); return false; } foreach (string File in Directory.EnumerateFiles(SettingDir, ConfigReader.ConfigFileExtension(), SearchOption.AllDirectories)) { FileReference FileRef = new FileReference(File); PlatformConfigurationBase DeviceProfile = ConfigReader.ReadConfiguration(FileRef); if (DeviceProfile != null) { DeviceProfile.Platform = Platform.Value; DeviceProfile.Namespace = Namespace; DeviceProfile.ProfileName = FileRef.GetFileNameWithoutAnyExtensions(); lock (LockObject) { ConfigurationCacheKey Key = new ConfigurationCacheKey(Platform.Value, Namespace, DeviceProfile.ProfileName); if (ConfigurationCache.ContainsKey(Key)) { Log.Verbose("Device configuration profile {0} already exists", FileRef.FullName); continue; } ConfigurationCache.Add(Key, DeviceProfile); } } } return true; } public void CacheConfigurationSnapshot(PlatformConfigurationBase Configuration, bool Overwrite = false) { lock (LockObject) { ConfigurationCacheKey Key = new ConfigurationCacheKey(Configuration.Platform, "Snapshot", Configuration.ProfileName); if (ConfigurationCache.ContainsKey(Key)) { if(Overwrite) { ConfigurationCache[Key] = Configuration; } } else { ConfigurationCache.Add(Key, Configuration); } } } public PlatformConfigurationBase GetConfigurationSnapshot(UnrealTargetPlatform? Platform, string DeviceName) { return GetConfiguration(Platform, "Snapshot", DeviceName); } public void ClearSnapshot(PlatformConfigurationBase Snapshot) { lock (LockObject) { ConfigurationCacheKey Key = new ConfigurationCacheKey(Snapshot); ConfigurationCache.Remove(Key); } } public void RevertDeviceConfiguration(ITargetDevice Device) { if (Device is IConfigurableDevice ConfigurableDevice) { var Snapshot = GetConfigurationSnapshot(Device.Platform, Device.Name); if (Snapshot == null) { return; } // Connect temporarily to be able to revert the device's configuration // if the device was disconnected entering here disconnect it after this is over bool bNeedsDisconnect = false; if (!Device.IsConnected) { Device.Connect(); bNeedsDisconnect = true; } if (ConfigurableDevice.ApplyConfiguration(Snapshot)) { ClearSnapshot(Snapshot); } if (bNeedsDisconnect) { Device.Disconnect(); } } } public PlatformConfigurationBase GetConfiguration(UnrealTargetPlatform? Platform, string Namespace, string ProfileName) { if (Platform == null) { return null; } PlatformConfigurationBase Res = null; lock (LockObject) { ConfigurationCacheKey Key = new ConfigurationCacheKey(Platform.Value, Namespace, ProfileName); ConfigurationCache.TryGetValue(Key, out Res); } return Res; } } }