#if ENABLE_CLOUD_SERVICES_ANALYTICS || UNITY_2023_2_OR_NEWER using System; using System.Collections.Generic; using System.Linq; using Unity.XR.CoreUtils; using UnityEditor.Compilation; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Pool; using UnityEngine.SceneManagement; using UnityEngine.XR.Interaction.Toolkit.Inputs; using UnityEngine.XR.Interaction.Toolkit.Interactables; using UnityEngine.XR.Interaction.Toolkit.Interactors; using UnityEngine.XR.Interaction.Toolkit.Locomotion; using Assembly = System.Reflection.Assembly; namespace UnityEditor.XR.Interaction.Toolkit.Analytics.Hooks { /// /// Analyzes the scenes during the build process to collect static data about the XRI components in the scenes. /// This is for getting components in the scenes grouped by type, and distinguishing between built-in XRI components, /// components defined in other Unity packages, and custom components. /// /// /// There is no requirement that it has to be done during a build as this class could be used /// to analyze a scene passed in from the Editor when not playing, but we currently only use it during the build. /// /// class XRISceneAnalyzer { /// /// The interactors in the most recent scene processed. /// List sceneInteractors { get; } = new List(); /// /// The interactables in the most recent scene processed. /// List sceneInteractables { get; } = new List(); /// /// The locomotion providers in the most recent scene processed. /// List sceneLocomotionProviders { get; } = new List(); /// /// The UI input modules in the most recent scene processed. /// List sceneInputModules { get; } = new List(); /// /// The UI raycasters in the most recent scene processed. /// List sceneRaycasters { get; } = new List(); /// /// The XR Input Modality Manager components in the most recent scene processed. /// List modalityManagers { get; } = new List(); /// /// All interactors across all scenes in the build. /// List allInteractors { get; } = new List(); /// /// All interactables across all scenes in the build. /// List allInteractables { get; } = new List(); /// /// All interactables across all scenes in the build. /// List allLocomotionProviders { get; } = new List(); /// /// All UI input modules across all scenes in the build. /// List allInputModules { get; } = new List(); /// /// The UI raycasters across all scenes in the build. /// List allRaycasters { get; } = new List(); /// /// The XR Input Modality Manager components across all scenes in the build. /// List allModalityManagers { get; } = new List(); ModalityComponentData m_ModalityData = new ModalityComponentData(); static List s_RootGameObjects = new List(); static List s_XRIAssemblies; /// /// Reset the analyzer to its initial state. /// Call this after generating the analytics payload to avoid accumulating data across /// multiple builds. /// public void Reset() { sceneInteractors.Clear(); sceneInteractables.Clear(); sceneLocomotionProviders.Clear(); sceneInputModules.Clear(); sceneRaycasters.Clear(); modalityManagers.Clear(); allInteractors.Clear(); allInteractables.Clear(); allLocomotionProviders.Clear(); allInputModules.Clear(); allRaycasters.Clear(); allModalityManagers.Clear(); m_ModalityData = default; } /// /// Get all the relevant components in the scene. /// /// The scene to process. /// Returns a struct containing overview info about the scene. public StaticSceneData CaptureComponentsInScene(Scene scene) { sceneInteractors.Clear(); sceneInteractables.Clear(); sceneLocomotionProviders.Clear(); sceneInputModules.Clear(); sceneRaycasters.Clear(); allInteractors.Clear(); using (ListPool.Get(out var scratchInteractors)) using (ListPool.Get(out var scratchInteractables)) using (ListPool.Get(out var scratchLocomotionProviders)) using (ListPool.Get(out var scratchInputModules)) using (ListPool.Get(out var scratchRaycasters)) using (ListPool.Get(out var scratchModalityManagers)) { s_RootGameObjects.EnsureCapacity(scene.rootCount); scene.GetRootGameObjects(s_RootGameObjects); foreach (var go in s_RootGameObjects) { go.GetComponentsInChildren(true, scratchInteractors); go.GetComponentsInChildren(true, scratchInteractables); go.GetComponentsInChildren(true, scratchLocomotionProviders); go.GetComponentsInChildren(true, scratchInputModules); go.GetComponentsInChildren(true, scratchRaycasters); go.GetComponentsInChildren(true, scratchModalityManagers); sceneInteractors.AddRange(scratchInteractors); sceneInteractables.AddRange(scratchInteractables); sceneLocomotionProviders.AddRange(scratchLocomotionProviders); sceneInputModules.AddRange(scratchInputModules); sceneRaycasters.AddRange(scratchRaycasters); modalityManagers.AddRange(scratchModalityManagers); } allInteractors.AddRange(sceneInteractors); allInteractables.AddRange(sceneInteractables); allLocomotionProviders.AddRange(sceneLocomotionProviders); allInputModules.AddRange(sceneInputModules); allRaycasters.AddRange(sceneRaycasters); allModalityManagers.AddRange(modalityManagers); s_RootGameObjects.Clear(); // Process scene data here that needs the scene to be loaded. m_ModalityData.componentExists |= modalityManagers.Count > 0; m_ModalityData.leftHandAssigned |= modalityManagers.Any(manager => manager.leftHand != null); m_ModalityData.rightHandAssigned |= modalityManagers.Any(manager => manager.rightHand != null); m_ModalityData.leftControllerAssigned |= modalityManagers.Any(manager => manager.leftController != null); m_ModalityData.rightControllerAssigned |= modalityManagers.Any(manager => manager.rightController != null); } return new StaticSceneData { buildIndex = scene.buildIndex, sceneGuid = AssetDatabase.GUIDFromAssetPath(scene.path).ToString(), interactorsCount = sceneInteractors.Count, interactablesCount = sceneInteractables.Count, locomotionProvidersCount = sceneLocomotionProviders.Count, uiInputModulesCount = sceneInputModules.Count, uiRaycastersCount = sceneRaycasters.Count, modalityManagersCount = modalityManagers.Count, }; } /// /// Get the component summary data for a list of components, grouping the components by type and the count of each. /// /// The base type of component, for example . /// The list of components to analyze. /// Returns a new component summary struct. public static ComponentSummaryData GetComponentSummaryData(List components) { CalculateTypeCounts(components, out var builtInCount, out var unityCount, out var customCount, out var builtInTypes, out var unityTypes, out var customTypes); return new ComponentSummaryData { totalCount = components.Count, builtInCount = builtInCount, unityCount = unityCount, customCount = customCount, builtInTypes = builtInTypes, unityTypes = unityTypes, customTypes = customTypes, }; } static void CalculateTypeCounts(List components, out int builtInCount, out int unityCount, out int customCount, out NameCountData[] builtInTypes, out NameCountData[] unityTypes, out NameCountData[] customTypes) { builtInCount = 0; unityCount = 0; customCount = 0; using (ListPool.Get(out var builtInTypesUnsorted)) using (ListPool.Get(out var unityTypesUnsorted)) using (ListPool.Get(out var customTypesUnsorted)) using (ListPool.Get(out var customDerivedComponents)) { foreach (var group in components.GroupBy(i => i.GetType())) { if (IsXRIRuntimeAssembly(group.Key)) { var count = group.Count(); builtInCount += count; builtInTypesUnsorted.Add(new NameCountData { typeName = group.Key.Name, count = count, }); } else if (XRIAnalyticsUtility.IsUnityAssembly(group.Key)) { var count = group.Count(); unityCount += count; unityTypesUnsorted.Add(new NameCountData { typeName = group.Key.FullName, count = count, }); } else { customDerivedComponents.AddRange(group); } } if (customDerivedComponents.Count > 0) { // Find the Unity type these custom components are derived from. // For those component types that just implement interfaces rather than derive from one of our built-in classes, // fallback to the interface type. foreach (var group in customDerivedComponents.GroupBy(i => GetBestBaseType(i.GetType()) ?? typeof(T))) { var count = group.Count(); customCount += count; var typeName = IsXRIRuntimeAssembly(group.Key) ? group.Key.Name : group.Key.FullName; customTypesUnsorted.Add(new NameCountData { typeName = typeName, count = count, }); } } builtInTypes = builtInTypesUnsorted.OrderByDescending(data => data.count).ThenBy(data => data.typeName).ToArray(); unityTypes = unityTypesUnsorted.OrderByDescending(data => data.count).ThenBy(data => data.typeName).ToArray(); customTypes = customTypesUnsorted.OrderByDescending(data => data.count).ThenBy(data => data.typeName).ToArray(); } } /// /// Update the analytics payload struct with the data from this analyzer. /// /// The analytics payload to write into. public void UpdateEventPayload(ref XRIBuildEvent.Payload payload) { payload.interactors = GetComponentSummaryData(allInteractors); payload.interactables = GetComponentSummaryData(allInteractables); payload.locomotionProviders = GetComponentSummaryData(allLocomotionProviders); payload.uiInputModules = GetComponentSummaryData(allInputModules); payload.uiRaycasters = GetComponentSummaryData(allRaycasters); payload.modalityManagers = GetComponentSummaryData(allModalityManagers); payload.modalityInfo = m_ModalityData; } /// /// Determine if the type is from one of the XR Interaction Toolkit runtime assemblies, including samples. /// /// The type to check. /// Returns if the type is defined in a XRI runtime assembly, including samples. static bool IsXRIRuntimeAssembly(Type type) { if (s_XRIAssemblies == null || s_XRIAssemblies.Count == 0) { s_XRIAssemblies ??= new List(); foreach (var assembly in CompilationPipeline.GetAssemblies(AssembliesType.PlayerWithoutTestAssemblies)) { if (assembly.name.StartsWith("Unity.XR.Interaction.Toolkit")) { try { var reflectionAssembly = Assembly.LoadFrom(assembly.outputPath); s_XRIAssemblies.Add(reflectionAssembly); } catch { // Ignore any exceptions loading the assembly } } } // Fallback in case the assembly loading code above fails // to at least ensure the core Unity.XR.Interaction.Toolkit assembly is included if (s_XRIAssemblies.Count == 0) s_XRIAssemblies.Add(typeof(IXRInteractable).Assembly); } return s_XRIAssemblies.Any(a => type.Assembly == a); } static Type GetBestBaseType(Type type) { while (type != null && !XRIAnalyticsUtility.IsUnityAssembly(type)) { type = type.BaseType; } return type; } } } #endif