#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