using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine;
using UnityEngine.XR.OpenXR;
[assembly: InternalsVisibleTo("Unity.XR.OpenXR.Tests.Editor")]
[assembly: InternalsVisibleTo("Unity.XR.OpenXR.Tests")]
namespace UnityEditor.XR.OpenXR.Features
{
///
/// Editor OpenXR Feature helpers.
///
public static class FeatureHelpers
{
///
/// Discovers all features in project and ensures that OpenXRSettings.Instance.features is up to date
/// for selected build target group.
///
/// build target group to refresh
public static void RefreshFeatures(BuildTargetGroup group)
{
FeatureHelpersInternal.GetAllFeatureInfo(group);
}
///
/// Given a feature id, returns the first instance of associated with that id.
///
/// The unique id identifying the feature
/// The instance of the feature matching thd id, or null.
public static OpenXRFeature GetFeatureWithIdForActiveBuildTarget(string featureId)
{
return GetFeatureWithIdForBuildTarget(BuildPipeline.GetBuildTargetGroup(UnityEditor.EditorUserBuildSettings.activeBuildTarget), featureId);
}
///
/// Given an array of feature ids, returns an array of matching instances.
///
/// Array of feature ids to match against.
/// An array of all matching features.
public static OpenXRFeature[] GetFeaturesWithIdsForActiveBuildTarget(string[] featureIds)
{
return GetFeaturesWithIdsForBuildTarget(BuildPipeline.GetBuildTargetGroup(UnityEditor.EditorUserBuildSettings.activeBuildTarget), featureIds);
}
///
/// Given a feature id, returns the first associated with that id.
///
/// The build target group to get the feature from.
/// The unique id identifying the feature
/// The instance of the feature matching thd id, or null.
public static OpenXRFeature GetFeatureWithIdForBuildTarget(BuildTargetGroup buildTargetGroup, string featureId)
{
if (String.IsNullOrEmpty(featureId))
return null;
var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(buildTargetGroup);
if (settings == null)
return null;
foreach (var feature in settings.features)
{
if (String.Compare(featureId, feature.featureIdInternal, true) == 0)
return feature;
}
return null;
}
///
/// Given an array of feature ids, returns an array of matching instances that match.
///
/// The build target group to get the feature from.
/// Array of feature ids to match against.
/// An array of all matching features.
public static OpenXRFeature[] GetFeaturesWithIdsForBuildTarget(BuildTargetGroup buildTargetGroup, string[] featureIds)
{
List ret = new List();
if (featureIds == null || featureIds.Length == 0)
return ret.ToArray();
foreach (var featureId in featureIds)
{
var feature = GetFeatureWithIdForBuildTarget(buildTargetGroup, featureId);
if (feature != null)
ret.Add(feature);
}
return ret.ToArray();
}
}
internal static class FeatureHelpersInternal
{
public class AllFeatureInfo
{
public List Features;
public BuildTarget[] CustomLoaderBuildTargets;
}
public enum FeatureInfoCategory
{
Feature,
Interaction
}
public struct FeatureInfo
{
public string PluginPath;
public OpenXRFeatureAttribute Attribute;
public OpenXRFeature Feature;
public FeatureInfoCategory Category;
}
private static FeatureInfoCategory DetermineExtensionCategory(string extensionCategoryString)
{
if (String.Compare(extensionCategoryString, FeatureCategory.Interaction) == 0)
{
return FeatureInfoCategory.Interaction;
}
return FeatureInfoCategory.Feature;
}
///
/// Gets all features for group. If serialized feature instances do not exist, creates them.
///
/// BuildTargetGroup to get feature information for.
/// feature info
public static AllFeatureInfo GetAllFeatureInfo(BuildTargetGroup group)
{
AllFeatureInfo ret = new AllFeatureInfo { Features = new List() };
var openXrPackageSettings = OpenXRPackageSettings.GetOrCreateInstance();
var openXrSettings = openXrPackageSettings.GetSettingsForBuildTargetGroup(group);
if (openXrSettings == null)
{
return ret;
}
var assetPath = Path.Combine(OpenXRPackageSettings.GetAssetPathForComponents(OpenXRPackageSettings.s_PackageSettingsDefaultSettingsPath), openXrPackageSettings.name + ".asset");
var openXrExtensionAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
// Find any current extensions that are already serialized
var currentExts = new Dictionary();
var buildGroupName = group.ToString();
foreach (var ext in openXrExtensionAssets)
{
if (ext == null || !ext.name.Contains(buildGroupName))
continue;
foreach (Attribute attr in Attribute.GetCustomAttributes(ext.GetType()))
{
if (attr is OpenXRFeatureAttribute)
{
var extAttr = (OpenXRFeatureAttribute)attr;
currentExts[extAttr] = (OpenXRFeature)ext;
break;
}
}
}
// only one custom loader is allowed per platform.
string customLoaderExtName = "";
// Find any extensions that haven't yet been added to the feature list and create instances of them
List all = new List();
foreach (var extType in TypeCache.GetTypesWithAttribute())
{
foreach (Attribute attr in Attribute.GetCustomAttributes(extType))
{
if (attr is OpenXRFeatureAttribute)
{
var extAttr = (OpenXRFeatureAttribute)attr;
if (extAttr.BuildTargetGroups != null && !((IList)extAttr.BuildTargetGroups).Contains(group))
continue;
if (!currentExts.TryGetValue(extAttr, out var extObj))
{
// Create a new one
extObj = (OpenXRFeature)ScriptableObject.CreateInstance(extType);
extObj.name = extType.Name + " " + group;
AssetDatabase.AddObjectToAsset(extObj, openXrSettings);
AssetDatabase.SaveAssets();
}
else
{
extObj.name = extType.Name + " " + group;
}
if (extObj == null)
continue;
bool enabled = (extObj.enabled);
var ms = MonoScript.FromScriptableObject(extObj);
var path = AssetDatabase.GetAssetPath(ms);
var dir = "";
if (!String.IsNullOrEmpty(path))
dir = Path.GetDirectoryName(path);
ret.Features.Add(new FeatureInfo()
{
PluginPath = dir,
Attribute = extAttr,
Feature = extObj,
Category = DetermineExtensionCategory(extAttr.Category)
});
if (enabled && extAttr.CustomRuntimeLoaderBuildTargets?.Length > 0)
{
if (ret.CustomLoaderBuildTargets != null && (bool)extAttr.CustomRuntimeLoaderBuildTargets?.Intersect(ret.CustomLoaderBuildTargets).Any())
{
Debug.LogError($"Only one OpenXR feature may have a custom runtime loader per platform. Disable {customLoaderExtName} or {extAttr.UiName}.");
}
ret.CustomLoaderBuildTargets = extAttr.CustomRuntimeLoaderBuildTargets?.Union(ret?.CustomLoaderBuildTargets ?? new BuildTarget[] { }).ToArray();
customLoaderExtName = extAttr.UiName;
}
all.Add(extObj);
break;
}
}
}
// Update the feature list
var originalFeatures = openXrSettings.features;
var newFeatures = all
.Where(f => f != null)
.OrderByDescending(f => f.priority)
.ThenBy(f => f.nameUi)
.ToArray();
// Populate the internal feature variables for all features
bool fieldChanged = false;
foreach (var feature in newFeatures)
{
if (feature.internalFieldsUpdated)
continue;
feature.internalFieldsUpdated = true;
foreach (var attr in feature.GetType().GetCustomAttributes())
{
foreach (var sourceField in attr.GetType().GetFields())
{
var copyField = sourceField.GetCustomAttribute();
if (copyField == null)
continue;
var targetField = feature.GetType().GetField(copyField.FieldName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
if (targetField == null)
continue;
// Only set value if value is different
if ((targetField.GetValue(feature) == null && sourceField.GetValue(attr) != null) ||
targetField.GetValue(feature) == null || targetField.GetValue(feature).Equals(sourceField.GetValue(attr)) == false)
{
targetField.SetValue(feature, sourceField.GetValue(attr));
fieldChanged = true;
}
}
}
}
// Ensure the settings are saved after the features are populated
if (fieldChanged || originalFeatures == null || originalFeatures.SequenceEqual(newFeatures) == false)
{
openXrSettings.features = newFeatures;
#if UNITY_EDITOR
EditorUtility.SetDirty(openXrSettings);
#endif
}
return ret;
}
}
}