using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using UnityEditor.AddressableAssets.Build.DataBuilders; using UnityEditor.AddressableAssets.Settings; using UnityEngine; using UnityEngine.Serialization; // ReSharper disable DelegateSubtraction namespace UnityEditor.AddressableAssets.HostingServices { /// /// Manages the hosting services. /// [Serializable] public class HostingServicesManager : ISerializationCallbackReceiver { internal const string KPrivateIpAddressKey = "PrivateIpAddress"; internal string GetPrivateIpAddressKey(int id = 0) { if (id == 0) return KPrivateIpAddressKey; return $"{KPrivateIpAddressKey}_{id}"; } [Serializable] internal class HostingServiceInfo { [SerializeField] internal string classRef; [SerializeField] internal KeyDataStore dataStore; } [FormerlySerializedAs("m_hostingServiceInfos")] [SerializeField] List m_HostingServiceInfos; [FormerlySerializedAs("m_settings")] [SerializeField] AddressableAssetSettings m_Settings; [FormerlySerializedAs("m_nextInstanceId")] [SerializeField] int m_NextInstanceId; [FormerlySerializedAs("m_registeredServiceTypeRefs")] [SerializeField] List m_RegisteredServiceTypeRefs; readonly Type[] m_BuiltinServiceTypes = { typeof(HttpHostingService) }; Dictionary m_HostingServiceInfoMap; ILogger m_Logger; List m_RegisteredServiceTypes; internal bool exitingEditMode = false; /// /// Key/Value pairs valid for profile variable substitution /// public Dictionary GlobalProfileVariables { get; private set; } internal static readonly string k_GlobalProfileVariablesCountKey = $"com.unity.addressables.{nameof(GlobalProfileVariables)}Count"; internal static string GetSessionStateKey(int id) { return $"com.unity.addressables.{nameof(GlobalProfileVariables)}{id}"; } /// /// Direct logging output of all managed services /// public ILogger Logger { get { return m_Logger; } set { m_Logger = value ?? Debug.unityLogger; foreach (var svc in HostingServices) svc.Logger = m_Logger; } } /// /// Static method for use in starting up the HostingServicesManager in batch mode. /// /// public static void BatchMode(AddressableAssetSettings settings) { if (settings == null) { Debug.LogError("Could not load Addressable Assets settings - aborting."); return; } var manager = settings.HostingServicesManager; if (manager == null) { Debug.LogError("Could not load HostingServicesManager - aborting."); return; } manager.StartAllServices(); } /// /// Static method for use in starting up the HostingServicesManager in batch mode. This method /// without parameters will find and use the default object. /// public static void BatchMode() { BatchMode(AddressableAssetSettingsDefaultObject.Settings); } /// /// Indicates whether or not this HostingServiceManager is initialized /// public bool IsInitialized { get { return m_Settings != null; } } /// /// Return an enumerable list of all configured objects /// public ICollection HostingServices { get { return m_HostingServiceInfoMap.Keys; } } /// /// Get an array of all types that have been used by the manager, or are known /// built-in types available for use. /// /// public Type[] RegisteredServiceTypes { get { if (m_RegisteredServiceTypes.Count == 0) m_RegisteredServiceTypes.AddRange(m_BuiltinServiceTypes); return m_RegisteredServiceTypes.ToArray(); } } /// /// The id value that will be assigned to the next add to the manager. /// public int NextInstanceId { get { return m_NextInstanceId; } } /// /// Create a new /// public HostingServicesManager() { GlobalProfileVariables = new Dictionary(); m_HostingServiceInfos = new List(); m_HostingServiceInfoMap = new Dictionary(); m_RegisteredServiceTypes = new List(); m_RegisteredServiceTypeRefs = new List(); m_Logger = Debug.unityLogger; } /// /// Initialize manager with the given object. /// /// public void Initialize(AddressableAssetSettings settings) { if (IsInitialized) return; m_Settings = settings; RefreshGlobalProfileVariables(); } /// /// Calls on all managed instances /// where is true /// public void StopAllServices() { foreach (var svc in HostingServices) { try { if (svc.IsHostingServiceRunning) svc.StopHostingService(); } catch (Exception e) { m_Logger.LogFormat(LogType.Error, e.Message); } } } /// /// Calls on all managed instances /// where is false /// public void StartAllServices() { foreach (var svc in HostingServices) { try { if (!svc.IsHostingServiceRunning) svc.StartHostingService(); } catch (Exception e) { m_Logger.LogFormat(LogType.Error, e.Message); } } } /// /// Add a new hosting service instance of the given type. The must implement the /// interface, or an is thrown. /// /// A object for the service. Must implement /// A descriptive name for the new service instance. /// public IHostingService AddHostingService(Type serviceType, string name) { var svc = Activator.CreateInstance(serviceType) as IHostingService; if (svc == null) throw new ArgumentException("Provided type does not implement IHostingService", "serviceType"); if (!m_RegisteredServiceTypes.Contains(serviceType)) m_RegisteredServiceTypes.Add(serviceType); var info = new HostingServiceInfo { classRef = TypeToClassRef(serviceType), dataStore = new KeyDataStore() }; svc.Logger = m_Logger; svc.DescriptiveName = name; svc.InstanceId = m_NextInstanceId; svc.HostingServiceContentRoots.AddRange(GetAllContentRoots()); m_Settings.profileSettings.RegisterProfileStringEvaluationFunc(svc.EvaluateProfileString); m_HostingServiceInfoMap.Add(svc, info); m_Settings.SetDirty(AddressableAssetSettings.ModificationEvent.HostingServicesManagerModified, this, true, true); AddressableAssetUtility.OpenAssetIfUsingVCIntegration(m_Settings); m_NextInstanceId++; return svc; } /// /// Stops the given , unregisters callbacks, and removes it from management. This /// function does nothing if the service is not being managed by this /// /// public void RemoveHostingService(IHostingService svc) { if (!m_HostingServiceInfoMap.ContainsKey(svc)) return; svc.StopHostingService(); m_Settings.profileSettings.UnregisterProfileStringEvaluationFunc(svc.EvaluateProfileString); m_HostingServiceInfoMap.Remove(svc); m_Settings.SetDirty(AddressableAssetSettings.ModificationEvent.HostingServicesManagerModified, this, true, true); AddressableAssetUtility.OpenAssetIfUsingVCIntegration(m_Settings); } /// /// Should be called by parent instance OnEnable method /// public void OnEnable() { Debug.Assert(IsInitialized); m_Settings.OnModification -= OnSettingsModification; m_Settings.OnModification += OnSettingsModification; m_Settings.profileSettings.RegisterProfileStringEvaluationFunc(EvaluateGlobalProfileVariableKey); bool hasAnEnabledService = false; foreach (var svc in HostingServices) { svc.Logger = m_Logger; m_Settings.profileSettings.RegisterProfileStringEvaluationFunc(svc.EvaluateProfileString); var baseSvc = svc as BaseHostingService; baseSvc?.OnEnable(); if (!hasAnEnabledService) hasAnEnabledService = svc.IsHostingServiceRunning || baseSvc != null && baseSvc.WasEnabled; } if (!exitingEditMode || exitingEditMode && hasAnEnabledService && IsUsingPackedPlayMode()) RefreshGlobalProfileVariables(); else LoadSessionStateKeys(); } bool IsUsingPackedPlayMode() { return m_Settings.ActivePlayModeDataBuilder != null && m_Settings.ActivePlayModeDataBuilder.GetType() == typeof(BuildScriptPackedPlayMode); } /// /// Should be called by parent instance OnDisable method /// public void OnDisable() { Debug.Assert(IsInitialized); // ReSharper disable once DelegateSubtraction m_Settings.OnModification -= OnSettingsModification; m_Settings.profileSettings.UnregisterProfileStringEvaluationFunc(EvaluateGlobalProfileVariableKey); foreach (var svc in HostingServices) { svc.Logger = null; m_Settings.profileSettings.UnregisterProfileStringEvaluationFunc(svc.EvaluateProfileString); (svc as BaseHostingService)?.OnDisable(); } SaveSessionStateKeys(); } internal void LoadSessionStateKeys() { int numKeys = SessionState.GetInt(k_GlobalProfileVariablesCountKey, 0); for (int i = 0; i < numKeys; i++) { string profileVar = SessionState.GetString(GetSessionStateKey(i), string.Empty); if (!string.IsNullOrEmpty(profileVar)) GlobalProfileVariables.Add(GetPrivateIpAddressKey(i), profileVar); } } internal void SaveSessionStateKeys() { int prevNumKeys = SessionState.GetInt(k_GlobalProfileVariablesCountKey, 0); SessionState.SetInt(k_GlobalProfileVariablesCountKey, GlobalProfileVariables.Count); int profileVarIdx = 0; foreach (KeyValuePair pair in GlobalProfileVariables) { SessionState.SetString(GetSessionStateKey(profileVarIdx), pair.Value); profileVarIdx++; } EraseSessionStateKeys(profileVarIdx, prevNumKeys); } internal static void EraseSessionStateKeys() { int numKeys = SessionState.GetInt(k_GlobalProfileVariablesCountKey, 0); EraseSessionStateKeys(0, numKeys); SessionState.EraseInt(k_GlobalProfileVariablesCountKey); } static void EraseSessionStateKeys(int min, int max) { for (int i = min; i < max; i++) { SessionState.EraseString(GetSessionStateKey(i)); } } /// Ensure object is ready for serialization, and calls methods /// on all managed instances /// public void OnBeforeSerialize() { // https://docs.unity3d.com/ScriptReference/EditorWindow.OnInspectorUpdate.html // Because the manager is a serialized field in the Addressables settings, this method is called // at 10 frames per second when the settings are opened in the inspector... // Be careful what you put in there... m_HostingServiceInfos.Clear(); foreach (var svc in HostingServices) { var info = m_HostingServiceInfoMap[svc]; m_HostingServiceInfos.Add(info); svc.OnBeforeSerialize(info.dataStore); } m_RegisteredServiceTypeRefs.Clear(); foreach (var type in m_RegisteredServiceTypes) m_RegisteredServiceTypeRefs.Add(TypeToClassRef(type)); } /// Ensure object is ready for serialization, and calls methods /// on all managed instances /// public void OnAfterDeserialize() { m_HostingServiceInfoMap = new Dictionary(); foreach (var svcInfo in m_HostingServiceInfos) { IHostingService svc = CreateHostingServiceInstance(svcInfo.classRef); if (svc == null) continue; svc.OnAfterDeserialize(svcInfo.dataStore); m_HostingServiceInfoMap.Add(svc, svcInfo); } m_RegisteredServiceTypes = new List(); foreach (var typeRef in m_RegisteredServiceTypeRefs) { var type = Type.GetType(typeRef, false); if (type == null) continue; m_RegisteredServiceTypes.Add(type); } } /// /// Refresh values in the global profile variables table. /// public void RefreshGlobalProfileVariables() { var vars = GlobalProfileVariables; vars.Clear(); var ipAddressList = FilterValidIPAddresses(NetworkInterface.GetAllNetworkInterfaces() .Where(n => n.NetworkInterfaceType != NetworkInterfaceType.Loopback && n.OperationalStatus == OperationalStatus.Up) .SelectMany(n => n.GetIPProperties().UnicastAddresses) .Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork) .Select(a => a.Address).ToList()); if (ipAddressList.Count > 0) { vars.Add(KPrivateIpAddressKey, ipAddressList[0].ToString()); if (ipAddressList.Count > 1) { for (var i = 1; i < ipAddressList.Count; i++) vars.Add(KPrivateIpAddressKey + "_" + i, ipAddressList[i].ToString()); } } } // Internal for unit tests internal string EvaluateGlobalProfileVariableKey(string key) { string retVal; GlobalProfileVariables.TryGetValue(key, out retVal); return retVal; } void OnSettingsModification(AddressableAssetSettings s, AddressableAssetSettings.ModificationEvent evt, object obj) { switch (evt) { case AddressableAssetSettings.ModificationEvent.GroupAdded: case AddressableAssetSettings.ModificationEvent.GroupRemoved: case AddressableAssetSettings.ModificationEvent.GroupSchemaAdded: case AddressableAssetSettings.ModificationEvent.GroupSchemaRemoved: case AddressableAssetSettings.ModificationEvent.GroupSchemaModified: case AddressableAssetSettings.ModificationEvent.ActiveProfileSet: case AddressableAssetSettings.ModificationEvent.BuildSettingsChanged: case AddressableAssetSettings.ModificationEvent.ProfileModified: var profileRemoteBuildPath = m_Settings.profileSettings.GetValueByName(m_Settings.activeProfileId, AddressableAssetSettings.kRemoteBuildPath); if (profileRemoteBuildPath != null && (profileRemoteBuildPath.Contains('[') || !CurrentContentRootsContain(profileRemoteBuildPath))) ConfigureAllHostingServices(); break; case AddressableAssetSettings.ModificationEvent.BatchModification: ConfigureAllHostingServices(); break; } } bool CurrentContentRootsContain(string root) { foreach (var svc in HostingServices) { if (!svc.HostingServiceContentRoots.Contains(root)) return false; } return true; } void ConfigureAllHostingServices() { var contentRoots = GetAllContentRoots(); foreach (var svc in HostingServices) { svc.HostingServiceContentRoots.Clear(); svc.HostingServiceContentRoots.AddRange(contentRoots); } } string[] GetAllContentRoots() { Debug.Assert(IsInitialized); var contentRoots = new List(); foreach (var group in m_Settings.groups) { if (group != null) { foreach (var schema in group.Schemas) { var configProvider = schema as IHostingServiceConfigurationProvider; if (configProvider != null) { var groupRoot = configProvider.HostingServicesContentRoot; if (groupRoot != null && !contentRoots.Contains(groupRoot)) contentRoots.Add(groupRoot); } } } } return contentRoots.ToArray(); } IHostingService CreateHostingServiceInstance(string classRef) { try { var objType = Type.GetType(classRef, true); var svc = (IHostingService)Activator.CreateInstance(objType); return svc; } catch (Exception e) { m_Logger.LogFormat(LogType.Error, "Could not create IHostingService from class ref '{0}'", classRef); m_Logger.LogFormat(LogType.Error, e.Message); } return null; } static string TypeToClassRef(Type t) { return string.Format("{0}, {1}", t.FullName, t.Assembly.GetName().Name); } // For unit tests internal AddressableAssetSettings Settings { get { return m_Settings; } } private List FilterValidIPAddresses(List ipAddresses) { List validIpList = new List(); foreach (IPAddress address in ipAddresses) { var sender = new System.Net.NetworkInformation.Ping(); var reply = sender.Send(address.ToString(), 5000); if (reply.Status == IPStatus.Success) { validIpList.Add(address); } } return validIpList; } } }