diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..51442de --- /dev/null +++ b/.editorconfig @@ -0,0 +1,73 @@ +# see http://editorconfig.org/ for docs on this file + +root = true + +[*] +# help with sharing files across os's (i.e. network share or through local vm) +end_of_line = lf +#charset temporarily disabled due to bug in VS2017 changing to UTF-8 with BOM (https://favro.com/card/c564ede4ed3337f7b17986b6/Uni-17877) +#charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# formattable file extensions (keep in sync with format.ini from unity-meta repo) +# +# Note: We need to split the formattable files configs into shorter duplicate entries (logically grouped) +# due to known issue in VS editorconfig extension where there is a limit of 51 characters (empirically determined). +# see: https://github.com/editorconfig/editorconfig-visualstudio/issues/21 +# +## uncrustify +[*.{c,h,cpp,hpp,m,mm,cc,cs}] +indent_style = space +indent_size = 4 + +## generic formatter (shaders) +[*.{cg,cginc,glslinc,hlsl,shader,y,ypp,yy}] +indent_style = space +indent_size = 4 + +## generic formatter (misc) +[*.{asm,s,S,pch,pchmm,java,sh,uss}] +indent_style = space +indent_size = 4 + +## perltidy +[*.{pl,pm,t,it}] +indent_style = space +indent_size = 4 + +## unity special +[*.{bindings,mem.xml}] +indent_style = space +indent_size = 4 + +# other filetypes we want to overwrite default configuration to preserve the standard +[{Makefile,makefile}] +# TAB characters are part of the Makefile format +indent_style = tab + +[*.{md,markdown}] +# trailing whitespace is significant in markdown (bad choice, bad!) +trim_trailing_whitespace = false + +# keep these and the VS stuff below in sync with .hgeol's CRLF extensions +[*.{vcproj,bat,cmd,xaml,tt,t4,ttinclude}] +end_of_line = crlf + +# this VS-specific stuff is based on experiments to see how VS will modify a file after it has been manually edited. +# the settings are meant to closely match what VS does to minimize unnecessary diffs. this duplicates some settings in * +# but let's be explicit here to be safe (in case someone wants to copy-paste this out to another .editorconfig). +[*.{vcxproj,vcxproj.filters,csproj,props,targets}] +indent_style = space +indent_size = 2 +end_of_line = crlf +charset = utf-8-bom +trim_trailing_whitespace = true +insert_final_newline = false +[*.{sln,sln.template}] +indent_style = tab +indent_size = 4 +end_of_line = crlf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = false diff --git a/.gitattribute b/.gitattribute new file mode 100644 index 0000000..91058e7 --- /dev/null +++ b/.gitattribute @@ -0,0 +1,8 @@ +* text=auto + +*.c text eol=lf +*.md text eol=lf +*.cpp text eol=lf +*.h text eol=lf +*.sh text eol=lf +*.cs text eol=lf diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..26d76bd --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,12 @@ +image: node:6.10.0 + +stages: + - push_to_packman_staging + +push_to_packman_staging: + stage: push_to_packman_staging + only: + - tags + script: + - curl -u $USERNAME:$API_KEY https://staging-packages.unity.com/auth > .npmrc + - npm publish diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..55dada3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,49 @@ +# Changelog +All notable changes to this package will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [0.1.0-preview.8] - 2018-07-30 + +* Updated UI for XR Manager to allow for adding, removing and reordering loaders. No more need for CreateAssetMenu attributes on loaders. +* Updated code to match formatting and code standards. + +## [0.1.0-preview.7] - 2018-07-25 + +* Fix issue #3: Add ASMDEFs for sample code to get it to compile. No longer need to keep copy in project. +* Fix Issue #4: Update documentation to reflect API changes and to expand information and API documentation. +* Fix Issue #5: Move boilerplate loader code to a common helper base class that can be used if an implementor wants to. + +## [0.1.0-preview.6] - 2018-07-17 + +### Added runtime tests for XRManager + +### Updated code to reflect name changes for XR Subsystem types. + +## [0.1.0-preview.5] - 2018-07-17 + +### Simplified settings for build/runtime + +Since we are 2018.3 and later only we can take advantage of the new PlayerSettings Preloaded Assets API. This API allows us to stash assets in PLayerSettings that are preloaded at runtime. Now, instead of figuring out where to write a file for which build target we just use the standard Unity engine and code access to get the settings we need when we need them. + +## [0.1.0-preview.4] - 2018-07-17 + +### Added samples and abiity to load settings + +This change adds a full fledged sample base that shows how to work with XR Management from start to finish, across run and build. This includes serializing and de-serializing the settings. + +## [0.1.0-preview.3] - 2018-07-17 + + +## [0.1.0-preview.2] - 2018-06-22 + +### Update build settings management + +Changed XRBuildData froma class to an attribute. This allows providers to use simpler SO classes for build data and not forece them to subclass anything. +Added a SettingsProvider subclass that wraps each of these attribute tagged classes. We use the display name from the attribute to populate the path in Unified Settings. The key in the attribute is used to store a single instance of the build settings SO in EditorBuildSettings as a single point to manage the instance. +Added code to auto create the first SO settings instance using a file panel since the Editor build settings container requires stored instances be backed in the Asset DB. There is no UI for creating the settings (unless added by the Provider) so this should allow us to maintain the singleton settings. Even if a user duplicates the settings instance, since it won't be in the Editor build settings container we won't honor it. + +## [0.1.0-preview.1] - 2018-06-21 + +### This is the first release of *Unity Package XR SDK Management*. diff --git a/CHANGELOG.md.meta b/CHANGELOG.md.meta new file mode 100644 index 0000000..1c06460 --- /dev/null +++ b/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1aeaeae5ea31643f89c63fb687563e05 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9f299b1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Contributing + +## If you are interested in contributing, here are some ground rules: +* ... Define guidelines & rules for what contributors need to know to successfully make Pull requests against your repo ... + +## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement) +By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions. + +## Once you have a change ready following these ground rules. Simply make a pull request diff --git a/CONTRIBUTING.md.meta b/CONTRIBUTING.md.meta new file mode 100644 index 0000000..61b88da --- /dev/null +++ b/CONTRIBUTING.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 964c6af36500043c28c2777b0b4a4ff2 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/com.unity.xr.management.md b/Documentation~/com.unity.xr.management.md new file mode 100644 index 0000000..5c68bca --- /dev/null +++ b/Documentation~/com.unity.xr.management.md @@ -0,0 +1,74 @@ +# About *XR SDK Management* package + +Use the **XR SDK Management** package to help streamline **XR SDK** lifecycle management and potentially provide users with build time UI through the Unity **Unified Settings** system. + +# End Users + +## Add an **XRManager** instance to your scene + +1) Create a new empty Game Object in your scene. +2) Add an instance of the **XRManager** component to this new Game Object. +3) Use the **XRManager** component Inspector UI to add/create, remove and reorder the loaders you widh to use. + +## Customize build and runtime settings + +Any package that needs build or runtime settings should provide a settings datatype for use. This will be surfaces in the **Unified Settings** UI window underneath a top leve **XR** node. By default a custom settings data instance will not be created. If you wish to modify build or runtime settings for the package you must go to the package authors entry in **Unified Settings** and select **Create**. This will create an instance of the settings that you can then modify inside of **Unified Settings**. + +# Package Authors + +## Lifecycle Management + +This package provides for management of **XR SDK** subsystem lifecycles without the need for boilerplate code. The **XRManager** class provides a component that can be added to a game object in the scene that will manage initialization, start, stop and denitialization of a set of subsystems defined by an **XRLoader** instance. The **XRManager** instance can handle all of the lifecycle management, init/deinit only, subsystem start/stop only or can leave all of it up to the user. + +A provider will create a subclass of **XRLoader** to provide a loader for their particular runtime scheme. An **XRLoader** is simply a **ScriptableObject** and as such, the user is able to create and instance (or more if they want) of the loader. Each **XRLoader** subclass defines the subsystems and their load order and is responsible for managing the set of subsystems they require. A user will add all the **XRLoaders** instances they created to the Loaders property on the **XRManager**, arranging them in the load order that they desire. + +**_NOTE_**: _At this time there is no way for a provider to ship an instance of their loader in their package that the user can find. To get around this, you will need to provide a means of allowing the user to create the necessary instance of your loader. The recommendation for this is to add a CreateAssetMenu attribute to your loader class and set the menu location to "XR/Loaders/[your loader name]"._ + +When asked to initialize, **XRManager** will call each **XRLoader** instance it has a reference to in the order it has and attempt to initialize each one. The first loader that succeeds initialization becomes the active loader and all further attempts to initialize are stopped. From this point the user can ask for the static **XRManager.ActiveLoader** instance to get access to the active loader. If initialization fails for all loaders, **ActiveLoader** is set to null. + +Automatic lifecycle management hooks into the following **MonoBehaviour** callback points: + +|Callback|Lifecycle Step| +|---|---| +|OnEnable|Find the first loader that succeeds intiialization and set ActiveLoader.| +|Start|Start all subsystems| +|OnDisable|Stop all subsystems| +|OnDestroy|Deintialize all subsystems and remove the ActiveLoader instance.| + + +## Build and Runtime settings through *Unified Settings* + +A provider may need optionl settings to help manage build issues or runtime configuration. They can do this by adding the **XRConfigurationData** attribute to a ScriptableObject and providing the set of properties they want to surface for users to control configuration. Configuration options will be surfaced in the **Unified Settings** window under the **XR** top level entry. We will manage the lifecycle for one instance of the class marked with the attribute through the EditorBuildSettings config object API. If no special UI is provided, the **Unified Settings** window will display the configuration settings using the standard **ScriptableObject** UI Inspector. A provider can extend the UI by creating a custom **Editor** for their configuration settings type and that will be used in the **Unified Settings** window instead. + +# Installing *XR SDK Management* + +To install this package, follow the instructions in the [Package Manager documentation](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@latest/index.html). + +# Technical details + +## Requirements + +This version of **XR SDK Management** is compatible with the following versions of the Unity Editor: + +* 2018.3 and later (recommended) + +## Known limitations + +* Still in preview. + +## Package contents + +This version of **XR SDK Management** includes: + +* **XRManager** - This is a **MonoBehaviour** that can be added to a **GameObject** in a scene and provides for management of **XRLoader** instances and their lifecycle. +* **XRLoader** - This is the base class all loaders should derive from. It provides a basic the **XRManager** can use to manage lifecycle and a simple API to allow users to request specific subsystems from the loader as and when needed. +* **XRConfigurationData** - This is an attribute that allows for build and runtime settings to be hosted within the **Unified Settings** window. All instances will be hosted under the top level **XR** entry within the **Unified Settings** window under the name supplied as part of the attribute. The management package will maintain and manage the lifecycle for one instance of the build settings using **EditorBuildSettings** config object API, stored with the key provided in the attribute. At any time, the provider or the user is free access the configuration settings instance by asking **EditorBuildSettings** for the instance associated with the chosen key (as set in the attribute). +* **Samples** - There is a samples folder in the package that contains an implementation of all parts of XR Management. Copy that folder to a location in your project/package to get started with implementing XR Management for your needs. + +## Document revision history + +|Date|Reason| +|---|---| +|July 25, 2018|Update docs to reflect API changes.| +|June 22, 2018|Added updated information about the XRBuildData Attribute.| +|June 21, 2018|Document created.| diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..b014220 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dcfbcd3f5f6514657924b8e122d97765 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/TypeLoaderExtensions.cs b/Editor/TypeLoaderExtensions.cs new file mode 100644 index 0000000..0505c2f --- /dev/null +++ b/Editor/TypeLoaderExtensions.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +using UnityEditor; + +using UnityEngine; +using UnityEngine.XR.Management; + +namespace UnityEditor.XR.Management +{ + internal static class TypeLoaderExtensions + { + public static IEnumerable GetLoadableTypes(this Assembly assembly) + { + if (assembly == null) throw new ArgumentNullException("assembly"); + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException e) + { + return e.Types.Where(t => t != null); + } + } + + public static IEnumerable GetTypesWithInterface(this Assembly asm) + { + var it = typeof(T); + return from lt in asm.GetLoadableTypes() + where it.IsAssignableFrom(lt) && !lt.IsAbstract && !lt.IsInterface + select lt; + } + + public static IEnumerable GetAllTypesWithInterface() + { + IEnumerable ret = new List(); + + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + var vendorTypes = asm.GetTypesWithInterface().ToList(); + ret = ret.Concat(vendorTypes); + } + + return ret; + } + + public static IEnumerable GetTypesWithAttribute(this Assembly asm) + { + var it = typeof(T); + return from lt in asm.GetLoadableTypes() + where (lt.GetCustomAttributes(it, true).Length > 0) && !lt.IsAbstract && !lt.IsInterface + select lt; + } + + public static IEnumerable GetAllTypesWithAttribute() + { + IEnumerable ret = new List(); + + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + var vendorTypes = asm.GetTypesWithAttribute().ToList(); + ret = ret.Concat(vendorTypes); + } + + return ret; + } + } +} diff --git a/Editor/TypeLoaderExtensions.cs.meta b/Editor/TypeLoaderExtensions.cs.meta new file mode 100644 index 0000000..848ca3a --- /dev/null +++ b/Editor/TypeLoaderExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 28c8363a46afc475bb1a7901a0dd2ad2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Unity.XR.Management.Editor.asmdef b/Editor/Unity.XR.Management.Editor.asmdef new file mode 100644 index 0000000..66d996b --- /dev/null +++ b/Editor/Unity.XR.Management.Editor.asmdef @@ -0,0 +1,16 @@ +{ + "name": "Unity.XR.Management.Editor", + "references": [ + "Unity.XR.Management" + ], + "optionalUnityReferences": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Editor/Unity.XR.Management.Editor.asmdef.meta b/Editor/Unity.XR.Management.Editor.asmdef.meta new file mode 100644 index 0000000..da0f170 --- /dev/null +++ b/Editor/Unity.XR.Management.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f9fe0089ec81f4079af78eb2287a6163 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/XRConfigurationProvider.cs b/Editor/XRConfigurationProvider.cs new file mode 100644 index 0000000..5295860 --- /dev/null +++ b/Editor/XRConfigurationProvider.cs @@ -0,0 +1,104 @@ +using System; + +using UnityEngine; +using UnityEngine.Experimental.UIElements; + +using UnityEditor; + +namespace UnityEditor.XR.Management +{ + internal class XRConfigurationProvider : SettingsProvider + { + static readonly GUIContent s_WarningToCreateSettings = EditorGUIUtility.TrTextContent("You must create a serialized instance of the settings data in order to modify the settings in this UI. Until then only default settings set by the provider will be available."); + + Type m_BuildDataType = null; + string m_DisplayName; + string m_BuildSettingsKey; + Editor m_CachedEditor; + SerializedObject m_SettingsWrapper; + + public XRConfigurationProvider(string path, string displayName, string buildSettingsKey, Type buildDataType, SettingsScopes scopes = SettingsScopes.Any) : base(path, scopes) + { + m_DisplayName = displayName; + m_BuildDataType = buildDataType; + m_BuildSettingsKey = buildSettingsKey; + } + + ScriptableObject currentSettings + { + get + { + ScriptableObject settings = null; + EditorBuildSettings.TryGetConfigObject(m_BuildSettingsKey, out settings); + if (settings == null) + { + string searchText = String.Format("t:{0}", m_BuildDataType.Name); + string[] assets = AssetDatabase.FindAssets(searchText); + if (assets.Length > 0) + { + string path = AssetDatabase.GUIDToAssetPath(assets[0]); + settings = AssetDatabase.LoadAssetAtPath(path, m_BuildDataType) as ScriptableObject; + EditorBuildSettings.AddConfigObject(m_BuildSettingsKey, settings, true); + } + } + return settings; + } + } + + void InitEditorData(ScriptableObject settings) + { + if (settings != null) + { + m_SettingsWrapper = new SerializedObject(settings); + Editor.CreateCachedEditor(settings, null, ref m_CachedEditor); + } + } + + public override void OnActivate(string searchContext, VisualElement rootElement) + { + InitEditorData(currentSettings); + } + + public override void OnDeactivate() + { + m_CachedEditor = null; + m_SettingsWrapper = null; + } + + public override void OnGUI(string searchContext) + { + if (m_SettingsWrapper == null || m_SettingsWrapper.targetObject == null) + { + EditorGUILayout.HelpBox(s_WarningToCreateSettings); + if (GUILayout.Button(EditorGUIUtility.TrTextContent("Create"))) + { + ScriptableObject settings = Create(); + InitEditorData(settings); + } + } + + if (m_SettingsWrapper != null && m_SettingsWrapper.targetObject != null && m_CachedEditor != null) + { + m_SettingsWrapper.Update(); + m_CachedEditor.OnInspectorGUI(); + m_SettingsWrapper.ApplyModifiedProperties(); + } + } + + ScriptableObject Create() + { + ScriptableObject settings = Activator.CreateInstance(m_BuildDataType) as ScriptableObject; + if (settings != null) + { + var path = EditorUtility.SaveFilePanelInProject(String.Format("Save {0} Settings", m_DisplayName), m_DisplayName, "asset", "Please enter a filename to save the settings to."); + if (!string.IsNullOrEmpty(path)) + { + AssetDatabase.CreateAsset(settings, path); + EditorBuildSettings.AddConfigObject(m_BuildSettingsKey, settings, true); + return settings; + } + } + return null; + } + } +} diff --git a/Editor/XRConfigurationProvider.cs.meta b/Editor/XRConfigurationProvider.cs.meta new file mode 100644 index 0000000..f6ec387 --- /dev/null +++ b/Editor/XRConfigurationProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44ea4f699730f4b1a93b7ea24a2319c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/XRManagerEditor.cs b/Editor/XRManagerEditor.cs new file mode 100644 index 0000000..b2514e0 --- /dev/null +++ b/Editor/XRManagerEditor.cs @@ -0,0 +1,415 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using JetBrains.Annotations; +using UnityEditorInternal; +using UnityEngine; +using UnityEngine.XR.Management; + +namespace UnityEditor.XR.Management +{ + class LoaderInfo : IEquatable + { + public Type loaderType; + public string assetName; + public XRLoader instance; + public bool isUsed; + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is LoaderInfo && Equals((LoaderInfo)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (loaderType != null ? loaderType.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (instance != null ? instance.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ isUsed.GetHashCode(); + return hashCode; + } + } + + public bool Equals(LoaderInfo other) + { + return other != null && Equals(loaderType, other.loaderType) && Equals(instance, other.instance) && isUsed == other.isUsed; + } + } + + class LoaderOrderUI + { + const string k_ErrorGettingProviderSubsystem = "Error attempting to get object data for xr manager loaders."; + const string k_AtLeastOneLoaderInstance = "Must add at least one XRLoader instance."; + + ReorderableList m_OrderedList = null; + List m_LoadersInUse = new List(); + List m_LoadersNotInUse = new List(); + SerializedProperty m_LoaderProperty; + bool m_ShouldReload = false; + + public LoaderOrderUI(List loaderInfos, SerializedProperty loaderProperty) + { + m_LoaderProperty = loaderProperty; + + foreach (var info in loaderInfos) + { + if (info.isUsed) + { + m_LoadersInUse.Add(info); + } + else + { + m_LoadersNotInUse.Add(info); + } + } + } + + void DrawElementCallback(Rect rect, int index, bool isActive, bool isFocused) + { + LoaderInfo info = m_LoadersInUse[index]; + var label = (info.instance == null) ? EditorGUIUtility.TrTextContent("Missing (XRLoader)") : EditorGUIUtility.TrTextContent(info.assetName); + EditorGUI.LabelField(rect, label); + } + + float GetElementHeight(int index) + { + return m_OrderedList.elementHeight; + } + + void UpdateSerializedProperty() + { + if (m_LoaderProperty != null && m_LoaderProperty.isArray) + { + m_LoaderProperty.ClearArray(); + + int index = 0; + foreach (LoaderInfo info in m_LoadersInUse) + { + m_LoaderProperty.InsertArrayElementAtIndex(index); + var prop = m_LoaderProperty.GetArrayElementAtIndex(index); + prop.objectReferenceValue = info.instance; + index++; + } + + m_LoaderProperty.serializedObject.ApplyModifiedProperties(); + } + } + + void ReorderLoaderList(ReorderableList list) + { + UpdateSerializedProperty(); + } + + string TypeNameToString(Type type) + { + string[] words = Regex.Matches(type.Name, "(^[a-z]+|[A-Z]+(?![a-z])|[A-Z][a-z]+)") + .OfType() + .Select(m => m.Value) + .ToArray(); + return string.Join(" ", words); + } + + void DrawAddDropdown(Rect rect, ReorderableList list) + { + List names = new List(); + List enabled = new List(); + + GenericMenu menu = new GenericMenu(); + + int index = 0; + foreach (var info in m_LoadersNotInUse) + { + string name = info.assetName; + if (String.IsNullOrEmpty(name) && info.loaderType != null) + { + name = TypeNameToString(info.loaderType); + } + + if (info.instance == null) + name = name + " (create new)"; + + menu.AddItem(new GUIContent(name), false, AddLoaderMenuSelected, index); + index++; + } + + menu.ShowAsContext(); + } + + void AddLoaderMenuSelected(object data) + { + int selected = (int)data; + LoaderInfo info = m_LoadersNotInUse[selected]; + + if (info.instance == null) + { + string newAssetName = String.Format("New {0}.asset", TypeNameToString(info.loaderType)); + XRLoader loader = ScriptableObject.CreateInstance(info.loaderType) as XRLoader; + string assetPath = EditorUtility.SaveFilePanelInProject("Create new XR Loader asset", + newAssetName, + "asset", + "Please enter a name and location where the loader asset can be created at."); + + if (string.IsNullOrEmpty(assetPath)) + { + return; + } + + info.instance = loader; + info.assetName = Path.GetFileNameWithoutExtension(assetPath); + AssetDatabase.CreateAsset(loader, assetPath); + m_ShouldReload = true; + } + + m_LoadersNotInUse.Remove(info); + info.isUsed = true; + m_LoadersInUse.Add(info); + UpdateSerializedProperty(); + } + + void RemoveInstanceFromList(ReorderableList list) + { + LoaderInfo info = m_LoadersInUse[list.index]; + m_LoadersInUse.Remove(info); + + if (info.loaderType != null) + { + info.isUsed = false; + m_LoadersNotInUse.Add(info); + } + UpdateSerializedProperty(); + } + + public bool OnGUI() + { + if (m_LoaderProperty == null) + return false; + + m_ShouldReload = false; + if (m_OrderedList == null) + { + m_OrderedList = new ReorderableList(m_LoadersInUse, typeof(XRLoader), true, true, true, true); + m_OrderedList.drawHeaderCallback = (rect) => GUI.Label(rect, EditorGUIUtility.TrTextContent("Loaders"), EditorStyles.label); + m_OrderedList.drawElementCallback = (rect, index, isActive, isFocused) => DrawElementCallback(rect, index, isActive, isFocused); + m_OrderedList.elementHeightCallback = (index) => GetElementHeight(index); + m_OrderedList.onReorderCallback = (list) => ReorderLoaderList(list); + m_OrderedList.onAddDropdownCallback = (rect, list) => DrawAddDropdown(rect, list); + m_OrderedList.onRemoveCallback = (list) => RemoveInstanceFromList(list); + } + + m_OrderedList.DoLayoutList(); + + if (!m_LoadersInUse.Any() && !m_LoadersNotInUse.Any()) + { + EditorGUILayout.HelpBox(k_AtLeastOneLoaderInstance, MessageType.Warning); + } + + return m_ShouldReload; + } + } + + + [CustomEditor(typeof(XRManager))] + public class XRManagerEditor : Editor + { + // Simple class to give us updates when the asset database changes. + class AssetCallbacks : AssetPostprocessor + { + static bool s_EditorUpdatable = false; + public static System.Action Callback { get; set; } + + static AssetCallbacks() + { + if (!s_EditorUpdatable) + { + EditorApplication.update += EditorUpdatable; + } + EditorApplication.projectChanged += EditorApplicationOnProjectChanged; + } + + static void EditorApplicationOnProjectChanged() + { + if (Callback != null) + Callback.Invoke(); + } + + static void EditorUpdatable() + { + s_EditorUpdatable = true; + EditorApplication.update -= EditorUpdatable; + if (Callback != null) + Callback.Invoke(); + } + } + + SerializedProperty m_AutomaticLoading = null; + SerializedProperty m_AutomaticRunning = null; + SerializedProperty m_LoaderList = null; + + static GUIContent k_AutoLoadLabel = EditorGUIUtility.TrTextContent("Automatic Loading"); + static GUIContent k_AutoRunLabel = EditorGUIUtility.TrTextContent("Automatic Running"); + + List m_AllLoaderInfos = new List(); + + LoaderOrderUI m_LoadOrderUI = null; + bool m_MustReloadData = true; + + void AssetProcessorCallback() + { + m_MustReloadData = true; + } + + void OnEnable() + { + AssetCallbacks.Callback = AssetProcessorCallback; + ReloadData(); + } + + public void OnDisable() + { + AssetCallbacks.Callback = null; + } + + void ReloadData() + { + if (m_LoaderList == null || m_LoaderList.serializedObject == null) + return; + + m_LoadOrderUI = null; + + m_AllLoaderInfos.Clear(); + + PopulateLoaderInfosFromCurrentAssignedLoaders(); + + PopulateLoaderInfosFromUnassignedLoaders(); + + m_MustReloadData = false; + } + + void PopulateLoaderInfosFromUnassignedLoaders() + { + List newInfos = new List(); + + GetAllKnownLoaderInfos(newInfos); + MergeLoaderInfos(newInfos); + } + + void MergeLoaderInfos(List newInfos) + { + foreach (var info in newInfos) + { + bool addNew = true; + if (info.instance != null) + { + foreach (var li in m_AllLoaderInfos) + { + if (li.instance == info.instance) + { + if (!String.IsNullOrEmpty(info.assetName)) + li.assetName = info.assetName; + addNew = false; + break; + } + } + } + + if (addNew) + { + m_AllLoaderInfos.Add(info); + } + } + } + + static void GetAllKnownLoaderInfos(List newInfos) + { + var loaderTypes = TypeLoaderExtensions.GetAllTypesWithInterface(); + foreach (Type loaderType in loaderTypes) + { + // HACK: No need for people to see these loaders + if (String.Compare("DummyLoader", loaderType.Name, StringComparison.OrdinalIgnoreCase) == 0 || + String.Compare("SampleLoader", loaderType.Name, StringComparison.OrdinalIgnoreCase) == 0) + continue; + + var assets = AssetDatabase.FindAssets(String.Format("t:{0}", loaderType)); + if (!assets.Any()) + { + LoaderInfo info = new LoaderInfo(); + info.loaderType = loaderType; + newInfos.Add(info); + } + else + { + foreach (var asset in assets) + { + string path = AssetDatabase.GUIDToAssetPath(asset); + + LoaderInfo info = new LoaderInfo(); + info.loaderType = loaderType; + info.instance = AssetDatabase.LoadAssetAtPath(path, loaderType) as XRLoader; + info.assetName = Path.GetFileNameWithoutExtension(path); + newInfos.Add(info); + } + } + } + } + + string AssetNameFromInstance(UnityEngine.Object asset) + { + if (asset == null) + return ""; + + string assetPath = AssetDatabase.GetAssetPath(asset); + return Path.GetFileNameWithoutExtension(assetPath); + } + + void PopulateLoaderInfosFromCurrentAssignedLoaders() + { + for (int i = 0; i < m_LoaderList.arraySize; i++) + { + var prop = m_LoaderList.GetArrayElementAtIndex(i); + + LoaderInfo info = new LoaderInfo(); + info.loaderType = (prop.objectReferenceValue == null) ? null : prop.objectReferenceValue.GetType(); + info.assetName = AssetNameFromInstance(prop.objectReferenceValue); + info.instance = prop.objectReferenceValue as XRLoader; + info.isUsed = true; + m_AllLoaderInfos.Add(info); + } + } + + void PopulateProperty(string propertyPath, ref SerializedProperty prop) + { + if (prop == null) prop = serializedObject.FindProperty(propertyPath); + } + + public override void OnInspectorGUI() + { + if (serializedObject == null || serializedObject.targetObject == null) + return; + + PopulateProperty("m_AutomaticLoading", ref m_AutomaticLoading); + PopulateProperty("m_AutomaticRunning", ref m_AutomaticRunning); + PopulateProperty("m_Loaders", ref m_LoaderList); + + serializedObject.Update(); + + if (m_MustReloadData) + ReloadData(); + + EditorGUILayout.PropertyField(m_AutomaticLoading, k_AutoLoadLabel); + if (m_AutomaticLoading.boolValue) + EditorGUILayout.PropertyField(m_AutomaticRunning, k_AutoRunLabel); + + if (m_LoadOrderUI == null) m_LoadOrderUI = new LoaderOrderUI(m_AllLoaderInfos, m_LoaderList); + + m_MustReloadData = m_LoadOrderUI.OnGUI(); + + serializedObject.ApplyModifiedProperties(); + } + } +} diff --git a/Editor/XRManagerEditor.cs.meta b/Editor/XRManagerEditor.cs.meta new file mode 100644 index 0000000..96c320d --- /dev/null +++ b/Editor/XRManagerEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d622ec83e4f54b6596923435a4bba8b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/XRSettingsManager.cs b/Editor/XRSettingsManager.cs new file mode 100644 index 0000000..86703c1 --- /dev/null +++ b/Editor/XRSettingsManager.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +using UnityEditor; + +using UnityEngine; +using UnityEngine.XR.Management; + +namespace UnityEditor.XR.Management +{ + class XRSettingsManager : SettingsProvider + { + static XRSettingsManager s_SettingsManager = null; + + [UnityEngine.Internal.ExcludeFromDocs] + XRSettingsManager(string path, SettingsScopes scopes = SettingsScopes.Any) : base(path, scopes) + { + } + + [SettingsProvider] + [UnityEngine.Internal.ExcludeFromDocs] + static SettingsProvider Create() + { + if (s_SettingsManager == null) + { + s_SettingsManager = new XRSettingsManager("XR"); + } + + return s_SettingsManager; + } + + [SettingsProviderGroup] + [UnityEngine.Internal.ExcludeFromDocs] + static SettingsProvider[] CreateAllChildSettingsProviders() + { + List ret = new List(); + if (s_SettingsManager != null) + { + var ats = TypeLoaderExtensions.GetAllTypesWithAttribute(); + foreach (var at in ats) + { + XRConfigurationDataAttribute xrbda = at.GetCustomAttributes(typeof(XRConfigurationDataAttribute), true)[0] as XRConfigurationDataAttribute; + string settingsPath = String.Format("XR/{0}", xrbda.displayName); + var resProv = new XRConfigurationProvider(settingsPath, xrbda.displayName, xrbda.buildSettingsKey, at); + ret.Add(resProv); + } + } + + return ret.ToArray(); + } + } +} diff --git a/Editor/XRSettingsManager.cs.meta b/Editor/XRSettingsManager.cs.meta new file mode 100644 index 0000000..414bbbf --- /dev/null +++ b/Editor/XRSettingsManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7d676d740a0644c0870d6d689136546 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..714d708 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,5 @@ +XR Management copyright © [YEAR] Unity Technologies ApS + +Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). + +Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. diff --git a/LICENSE.md.meta b/LICENSE.md.meta new file mode 100644 index 0000000..0e8537a --- /dev/null +++ b/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: abd74e060b6444ffe880023f0bc02e10 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/QAReport.md b/QAReport.md new file mode 100644 index 0000000..26c5ba3 --- /dev/null +++ b/QAReport.md @@ -0,0 +1,26 @@ +# Quality Report +Use this file to outline the test strategy for this package. + +## Version tested: [*package version*] + +## QA Owner: [*Add Name*] +## UX Owner: [*Add Name*] + +## Test strategy +*Use this section to describe how this feature was tested.* +* A link to the Test Plan (Test Rails, other) +* Results from the package's editor and runtime test suite. +* Link to automated test results (if any) +* Manual test Results, [here's an example](https://docs.google.com/spreadsheets/d/12A76U5Gf969w10KL4Ik0wC1oFIBDUoRrqIvQgD18TFo/edit#gid=0) +* Scenario test week outcome +* etc. + +## Package Status +Use this section to describe: +* UX status/evaluation results +* package stability +* known bugs, issues +* performance metrics, +* etc + +In other words, a general feeling on the health of this package. diff --git a/QAReport.md.meta b/QAReport.md.meta new file mode 100644 index 0000000..4f048e5 --- /dev/null +++ b/QAReport.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 63edc1fe73944441b9cf45b44298f4ca +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md new file mode 100644 index 0000000..bcae8c4 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# XR Management + +This package provides for management of **XR SDK** subsystem lifecycles without the need for boilerplate code as well as support for provider specific build settings. + +This package is primarily intented for XR SDK Package authors to provide management tools and touchpoints for their package users. As such, most end users will have thispackage installed transitievly through install and end user XR SDK pacakge using it. + +## Installing XR Management + +To install this package, follow the instructions in the [Package Manager documentation](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@latest/index.html). + +This package is `com.unity.xr.management` + +## Documentation + +* [Editor Script API](Editor/) +* [Runtime Script API](Runtime/) +* [Manual](Documentation/com.unity.xr.management.md) diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..eab073e --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 88d2132b5a4df4c659790dc2260104b3 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..edbfbac --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4fb10c2185e1347c4bef41feca4a8845 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Unity.XR.Management.asmdef b/Runtime/Unity.XR.Management.asmdef new file mode 100644 index 0000000..6c561e1 --- /dev/null +++ b/Runtime/Unity.XR.Management.asmdef @@ -0,0 +1,12 @@ +{ + "name": "Unity.XR.Management", + "references": [], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Runtime/Unity.XR.Management.asmdef.meta b/Runtime/Unity.XR.Management.asmdef.meta new file mode 100644 index 0000000..8103f5d --- /dev/null +++ b/Runtime/Unity.XR.Management.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e40ba710768534012815d3193fa296cb +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/XRConfigurationData.cs b/Runtime/XRConfigurationData.cs new file mode 100644 index 0000000..51f5b5b --- /dev/null +++ b/Runtime/XRConfigurationData.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +using UnityEngine; + + +namespace UnityEngine.XR.Management +{ + /// + /// This attribute is used to tag classes as providing build settings support for an XR SDK provider. The unified setting system + /// will present the settings as an inspectable object in the Unified Settings window using the built-in inspector UI. + /// + /// The implementor of the settings is able to create their own custom UI and the Unified Settings system will use that UI in + /// place of the build in inspector. See the Extending the Editor + /// portion of the Unity documentation for information and instructions on doing this. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class XRConfigurationDataAttribute : Attribute + { + /// + /// The display name to be presented to the user in the Unified Settings window. + /// + public string displayName { get; set; } + + /// + /// The key that will be used to store the singleton instance of these settings within EditorBuildSettings. + /// + /// See EditorBuildSettings scripting + /// API documentation on how this is beign done. + /// + public string buildSettingsKey { get; set; } + + private XRConfigurationDataAttribute() {} + + public XRConfigurationDataAttribute(string displayName, string buildSettingsKey) + { + this.displayName = displayName; + this.buildSettingsKey = buildSettingsKey; + } + } +} diff --git a/Runtime/XRConfigurationData.cs.meta b/Runtime/XRConfigurationData.cs.meta new file mode 100644 index 0000000..7d9fb1d --- /dev/null +++ b/Runtime/XRConfigurationData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f9b2b17cfd02430b99d104616f45273 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/XRLoader.cs b/Runtime/XRLoader.cs new file mode 100644 index 0000000..dadc471 --- /dev/null +++ b/Runtime/XRLoader.cs @@ -0,0 +1,55 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Experimental.XR; +using UnityEngine.Experimental; + +namespace UnityEngine.XR.Management +{ + /// + /// XR Loader abstract class used as a base class for specific provider implementations. Providers should implement + /// subclasses of this to provide specific initialization and management implementations that make sense for their supported + /// scenarios and needs. + /// + public abstract class XRLoader : ScriptableObject + { + /// + /// Initialize the loader. This should initialize all subsystems to support the desired runtime setup this + /// loader represents. + /// + /// + /// < Whether or not initialization succeeded > + public virtual bool Initialize() { return false; } + + /// + /// Ask loader to start all initialized subsystems. + /// + /// + /// < Whether or not all subsystems were successfully started. > + public virtual bool Start() { return false; } + + /// + /// Ask loader to stop all initialized subsystems. + /// + /// + /// < Whether or not all subsystems were successfully stopped > + public virtual bool Stop() { return false; } + + /// + /// Ask loader to deinitialize all initialized subsystems. + /// + /// + /// < Whether or not deinitialization succeeded > + public virtual bool Deinitialize() { return false; } + + /// + /// Gets the loaded subsystem of the specified type. Implementation dependent as only implemetnations + /// know what they have loaded and how best to get it.. + /// + /// + /// < Type of the subsystem to get > + /// + /// The loaded subsystem or null if not found. + public abstract T GetLoadedSubsystem() where T : IntegratedSubsystem; + } +} diff --git a/Runtime/XRLoader.cs.meta b/Runtime/XRLoader.cs.meta new file mode 100644 index 0000000..b5feac6 --- /dev/null +++ b/Runtime/XRLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61ff693bbf35149d684ae1e0be75e26f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 0eda7b875264842c7ac58d1267518728, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/XRLoaderHelper.cs b/Runtime/XRLoaderHelper.cs new file mode 100644 index 0000000..a412c42 --- /dev/null +++ b/Runtime/XRLoaderHelper.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Experimental.XR; +using UnityEngine.Experimental; + +namespace UnityEngine.XR.Management +{ + /// + /// XR Loader abstract subclass used as a base class for specific provider implementations. Class provides some + /// helper logic that can be used to handle subsystem handling in a typesafe manner, reducing potential boilerplate + /// code. + /// + public abstract class XRLoaderHelper : XRLoader + { + /// + /// Map of loaded susbsystems. Used so we don't always have to fo to XRSubsystemManger and do a manual + /// search to find the instance we loaded. + /// + protected Dictionary m_SubsystemInstanceMap = new Dictionary(); + + /// + /// Gets the loaded subsystem of the specified type. Implementation dependent as only implemetnations + /// know what they have loaded and how best to get it.. + /// + /// + /// Type of the subsystem to get. + /// + /// The loaded subsystem or null if not found. + public override T GetLoadedSubsystem() + { + Type subsystemType = typeof(T); + IntegratedSubsystem subsystem; + m_SubsystemInstanceMap.TryGetValue(subsystemType, out subsystem); + return subsystem as T; + } + + /// + /// Start a subsystem instance of a given type. Subsystem assumed to already be loaded from + /// a previous call to CreateSubsystem + /// + /// + /// A subclass of IntegratedSubsystem + protected void StartSubsystem() where T : IntegratedSubsystem + { + T subsystem = GetLoadedSubsystem(); + if (subsystem != null) + subsystem.Start(); + } + + /// + /// Stop a subsystem instance of a given type. Subsystem assumed to already be loaded from + /// a previous call to CreateSubsystem + /// + /// + /// A subclass of IntegratedSubsystem + protected void StopSubsystem() where T : IntegratedSubsystem + { + T subsystem = GetLoadedSubsystem(); + if (subsystem != null) + subsystem.Stop(); + } + + /// + /// Destroy a subsystem instance of a given type. Subsystem assumed to already be loaded from + /// a previous call to CreateSubsystem + /// + /// + /// A subclass of IntegratedSubsystem + protected void DestroySubsystem() where T : IntegratedSubsystem + { + T subsystem = GetLoadedSubsystem(); + if (subsystem != null) + subsystem.Destroy(); + } + + /// + /// Creates a subsystem given a list of descriptors and a specific subsystem id. + /// + /// + /// The descriptor type being passed in. + /// The subsystem type being requested + /// List of TDescriptor instances to use for subsystem matching. + /// The identifier key of the particualr subsystem implementation being requested. + protected void CreateSubsystem(List descriptors, string id) + where TDescriptor : IntegratedSubsystemDescriptor + where TSubsystem : IntegratedSubsystem + { + if (descriptors == null) + throw new ArgumentNullException("descriptors"); + + SubsystemManager.GetSubsystemDescriptors(descriptors); + + if (descriptors.Count > 0) + { + foreach (var descriptor in descriptors) + { + if (descriptor.id == id) + { + IntegratedSubsystem s = descriptor.Create(); + m_SubsystemInstanceMap[typeof(TSubsystem)] = s; + } + } + } + } + } +} diff --git a/Runtime/XRLoaderHelper.cs.meta b/Runtime/XRLoaderHelper.cs.meta new file mode 100644 index 0000000..4821ad2 --- /dev/null +++ b/Runtime/XRLoaderHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d28a628911c34c58a40cb9ef2ea4b31 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/XRManager.cs b/Runtime/XRManager.cs new file mode 100644 index 0000000..5690800 --- /dev/null +++ b/Runtime/XRManager.cs @@ -0,0 +1,267 @@ +using System.Collections; +using System.Collections.Generic; + +using UnityEditor; + +using UnityEngine; +using UnityEngine.Experimental.UIElements; +using UnityEngine.Serialization; +using UnityEngine.XR.Management; + +namespace UnityEngine.XR.Management +{ + /// + /// Class to handle active loader and subsystem management for XR SDK. This class is to be added as a + /// component on a GameObject in your scene. Given a list of loaders, it will attempt to load each loader in + /// the given order. The first loader that is successful wins and all remaining loaders are ignored. The loader + /// that succeeds is accessible through the property on the manager. + /// + /// Depending on configuration the XRManager component will automatically manage the active loader at correct points in the scene lifecycle. + /// The user can override certain points in the active loader lifecycle and manually manage them by toggling the *Automatic Loading* and *Automatic Running* + /// properties through the inspector UI. Disabling *Automatic Loading* implies the the user is responsibile for the full lifecycle + /// of the manager. Toggling this to false also toggles automatic running to false. + /// + /// Disabling *Automatic Running* implies that the user is responsible for starting and stopping + /// the through the and APIs. + /// + /// Automatic lifecycle management is executed as follows + /// + /// * OnEnable -> . The loader list will be iterated over and the first successful loader will be set as the active loader. + /// * Start -> . Ask the active loader to start all subsystems. + /// * OnDisable -> . Ask the active loader to stop all subsystems. + /// * OnDestroy -> . Deinitialize and remove the active loader. + /// + public sealed class XRManager : MonoBehaviour + { + [HideInInspector] + bool m_InitializationComplete = false; + + [SerializeField] + [Tooltip("Determines if the XR Manager instance is responsible for creating and destroying the appropriate loader instance.")] + [FormerlySerializedAs("AutomaticLoading")] + bool m_AutomaticLoading = true; + + /// + /// Get and set Automatic Loading state for this manager. When this is true, the manager will automatically call + /// and for you. When false + /// is also set to false and remains that way. This means that disabling automatic loading disables all automatic behavior + /// for the manager. + /// + public bool automaticLoading + { + get { return m_AutomaticLoading; } + set { m_AutomaticLoading = value; } + } + + [SerializeField] + [Tooltip("Determines if the XR Manager instance is responsible for starting and stopping subsystems for the active loader instance.")] + [FormerlySerializedAs("AutomaticRunning")] + bool m_AutomaticRunning = true; + + /// + /// Get and set automatic running state for this manager. When set to true the manager will call + /// and APIs at appropriate times. When set to false, or when is false + /// then it is up to the user of the manager to handle that same functionality. + /// + public bool automaticRunning + { + get { return m_AutomaticRunning; } + set { m_AutomaticRunning = value; } + } + + + [SerializeField] + [Tooltip("List of XR Loader instances arranged in desired load order.")] + [FormerlySerializedAs("Loaders")] + List m_Loaders = new List(); + + /// + /// List of loaders currently managed by this XR Manager instance. + /// + public List loaders + { + get { return m_Loaders; } + } + + + /// + /// Read only boolean letting us know if initialization is completed. Because initialization is + /// handled as a Coroutine, people taking advantage of the auto-lifecycle management of XRManager + /// will need to wait for init to complete before checking for an ActiveLoader and calling StartSubsystems. + /// + public bool isInitializationComplete + { + get { return m_InitializationComplete; } + } + + [HideInInspector] + static XRLoader s_ActiveLoader = null; + + /// + /// Return the current singleton active loader instance. + /// + /// + [HideInInspector] + public static XRLoader activeLoader { get { return s_ActiveLoader; } private set { s_ActiveLoader = value; }} + + /// + /// Return the current active loader, cast to the requested type. Useful shortcut when you need + /// to get the active loader as something less generic than XRLoader. + /// + /// + /// < Requested type of the loader> + /// + /// < The active loader as requested type, or null.> + public static T ActiveLoaderAs() where T : XRLoader + { + return activeLoader as T; + } + + /// + /// Iterate over the configured list of loaders and attempt to initialize each one. The first one + /// that succeeds is set as the active loader and initiialization imediately terminates. + /// + /// When complete will be set to true. This will mark that it is safe to + /// call other parts of the API. This does not guarantee that init successfully create a loader. For that + /// you need to check that ActiveLoader is not null. + /// + /// Note that there can only be one active loader. Any attempt to initialize a new active loader with one + /// already set will cause a warning to be logged and immediate exit of this function. + /// + /// + /// Enumerator marking the next spot to continue execution at. + public IEnumerator InitializeLoader() + { + if (activeLoader != null) + { + Debug.LogWarning( + "XR Management has already initialized an active loader in this scene." + + "Please make sure to stop all subsystems and deinitialize the active loader before initializing a new one."); + yield break; + } + + foreach (var loader in loaders) + { + if (loader != null) + { + if (loader.Initialize()) + { + activeLoader = loader; + m_InitializationComplete = true; + yield break; + } + } + + yield return null; + } + + activeLoader = null; + } + + /// + /// If there is an active loader, this will request the loader to start all the subsystems that it + /// is managing. + /// + /// You must wait for to be set to true prior to calling this API. + /// + public void StartSubsystems() + { + if (!m_InitializationComplete) + { + Debug.LogWarning( + "Call to StartSubsystems without an initialized manager." + + "Please make sure wait for initialization to complete before calling this API."); + return; + } + + if (activeLoader != null) + { + activeLoader.Start(); + } + } + + /// + /// If there is an active loader, this will request the loader to stop all the subsystems that it + /// is managing. + /// + /// You must wait for to be set to tru prior to calling this API. + /// + public void StopSubsystems() + { + if (!m_InitializationComplete) + { + Debug.LogWarning( + "Call to StopSubsystems without an initialized manager." + + "Please make sure wait for initialization to complete before calling this API."); + return; + } + + if (activeLoader != null) + { + activeLoader.Stop(); + } + } + + /// + /// If there is an active loader, this function will deinitialize it and remove the active loader instance from + /// management. We will automatically call prior to deinitialization to make sure + /// that things are cleaned up appropriately. + /// + /// You must wait for to be set to tru prior to calling this API. + /// + /// Upon return will be rest to false; + /// + public void DeinitializeLoader() + { + if (!m_InitializationComplete) + { + Debug.LogWarning( + "Call to DeinitializeLoader without an initialized manager." + + "Please make sure wait for initialization to complete before calling this API."); + return; + } + + StopSubsystems(); + if (activeLoader != null) + { + activeLoader.Deinitialize(); + activeLoader = null; + } + + m_InitializationComplete = false; + } + + void OnEnable() + { + if (automaticLoading) + { + StartCoroutine(InitializeLoader()); + } + } + + // Use this for initialization + void Start() + { + if (automaticLoading && automaticRunning) + { + StartSubsystems(); + } + } + + void OnDisable() + { + if (automaticLoading && automaticRunning) + { + StopSubsystems(); + } + } + + void OnDestroy() + { + if (automaticLoading) + { + DeinitializeLoader(); + } + } + } +} diff --git a/Runtime/XRManager.cs.meta b/Runtime/XRManager.cs.meta new file mode 100644 index 0000000..674f530 --- /dev/null +++ b/Runtime/XRManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4c3631f5e58749a59194e0cf6baf6d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples.meta b/Samples.meta new file mode 100644 index 0000000..7376459 --- /dev/null +++ b/Samples.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ddb5bc5b15a4d4febbaaed29d547ea40 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples/Editor.meta b/Samples/Editor.meta new file mode 100644 index 0000000..ec7d595 --- /dev/null +++ b/Samples/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 45285ff0f0a22470a9b0709b0dfba7e9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples/Editor/SampleBuildProcessor.cs b/Samples/Editor/SampleBuildProcessor.cs new file mode 100644 index 0000000..a64eae5 --- /dev/null +++ b/Samples/Editor/SampleBuildProcessor.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Linq; + +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; + +using UnityEngine; + +namespace Samples +{ + /// + /// Simple build processor that makes sure that any custom configuration that the user creates is + /// correctly passed on to the provider implementation at runtime. + /// + /// Custom configuration instances that are stored in EditorBuildSettings are not copied to the target build + /// as they are considered unreferenced assets. In order to get them to the runtime side of things, they need + /// to be serialized to the build app and deserialized at runtime. Previously this would be a manual process + /// requiring the implementor to manually serialize to some location that can then be read from to deserialize + /// at runtime. With thenew PlayerSettings Prelaoded Assets API we can now just add our asset to the prelaoded + /// list and have it be instantiated at app launch. + /// + /// Note that the preloaded assets are only notified with Awake, so anything you want or need to do with the + /// asset after launch needs to be handled there. + /// + /// More info on APIs used here: + /// * EditorBuildSettings + /// * PlayerSettings.GetPrelaodedAssets + /// * PlayerSettings.SetPrelaodedAssets + /// + public class SampleBuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport + { + public int callbackOrder + { + get { return 0; } + } + + void CleanOldSettings() + { + UnityEngine.Object[] preloadedAssets = PlayerSettings.GetPreloadedAssets(); + if (preloadedAssets == null) + return; + + var oldSettings = from s in preloadedAssets + where s.GetType() == typeof(SampleSettings) + select s; + + if (oldSettings.Any()) + { + var assets = preloadedAssets.ToList(); + foreach (var s in oldSettings) + { + assets.Remove(s); + } + + PlayerSettings.SetPreloadedAssets(assets.ToArray()); + } + } + + public void OnPreprocessBuild(BuildReport report) + { + // Always remember to cleanup preloaded assets after build to make sure we don't + // dirty later builds with assets that may not be needed or are out of date. + CleanOldSettings(); + + SampleSettings settings = null; + EditorBuildSettings.TryGetConfigObject(SampleConstants.k_SettingsKey, out settings); + if (settings == null) + return; + + UnityEngine.Object[] preloadedAssets = PlayerSettings.GetPreloadedAssets(); + + if (!preloadedAssets.Contains(settings)) + { + var assets = preloadedAssets.ToList(); + assets.Add(settings); + PlayerSettings.SetPreloadedAssets(assets.ToArray()); + } + } + + public void OnPostprocessBuild(BuildReport report) + { + // Always remember to cleanup preloaded assets after build to make sure we don't + // dirty later builds with assets that may not be needed or are out of date. + CleanOldSettings(); + } + } +} diff --git a/Samples/Editor/SampleBuildProcessor.cs.meta b/Samples/Editor/SampleBuildProcessor.cs.meta new file mode 100644 index 0000000..2205758 --- /dev/null +++ b/Samples/Editor/SampleBuildProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9216d372fe0de4086af31dce05526406 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples/Editor/SampleSettingsEditor.cs b/Samples/Editor/SampleSettingsEditor.cs new file mode 100644 index 0000000..f6c9d4b --- /dev/null +++ b/Samples/Editor/SampleSettingsEditor.cs @@ -0,0 +1,60 @@ +using UnityEditor; +using UnityEditor.XR.Management; + +using UnityEngine; + +namespace Samples +{ + /// + /// Simple custom editor used to show how to enable custom UI for XR Management + /// configuraton data. + /// + [CustomEditor(typeof(SampleSettings))] + public class SampleSettingsEditor : Editor + { + static string k_RequiresProperty = "m_RequiresItem"; + static string k_RuntimeToggleProperty = "m_RuntimeToggle"; + + static GUIContent k_ShowBuildSettingsLabel = new GUIContent("Build Settings"); + static GUIContent k_RequiresLabel = new GUIContent("Item Requirement"); + + static GUIContent k_ShowRuntimeSettingsLabel = new GUIContent("Runtime Settings"); + static GUIContent k_RuntimeToggleLabel = new GUIContent("Should I stay or should I go?"); + + bool m_ShowBuildSettings = true; + bool m_ShowRuntimeSettings = true; + + SerializedProperty m_RequiesItemProperty; + SerializedProperty m_RuntimeToggleProperty; + + public override void OnInspectorGUI() + { + if (serializedObject == null || serializedObject.targetObject == null) + return; + + if (m_RequiesItemProperty == null) m_RequiesItemProperty = serializedObject.FindProperty(k_RequiresProperty); + if (m_RuntimeToggleProperty == null) m_RuntimeToggleProperty = serializedObject.FindProperty(k_RuntimeToggleProperty); + + serializedObject.Update(); + m_ShowBuildSettings = EditorGUILayout.Foldout(m_ShowBuildSettings, k_ShowBuildSettingsLabel); + if (m_ShowBuildSettings) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(m_RequiesItemProperty, k_RequiresLabel); + EditorGUI.indentLevel--; + } + + EditorGUILayout.Space(); + + m_ShowRuntimeSettings = EditorGUILayout.Foldout(m_ShowRuntimeSettings, k_ShowRuntimeSettingsLabel); + if (m_ShowRuntimeSettings) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(m_RuntimeToggleProperty, k_RuntimeToggleLabel); + EditorGUI.indentLevel--; + } + + serializedObject.ApplyModifiedProperties(); + } + } +} diff --git a/Samples/Editor/SampleSettingsEditor.cs.meta b/Samples/Editor/SampleSettingsEditor.cs.meta new file mode 100644 index 0000000..61076bc --- /dev/null +++ b/Samples/Editor/SampleSettingsEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 81f90f56b259f4aba826980c947f4140 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples/Editor/Unity.XR.Management.Samples.Editor.asmdef b/Samples/Editor/Unity.XR.Management.Samples.Editor.asmdef new file mode 100644 index 0000000..79955ef --- /dev/null +++ b/Samples/Editor/Unity.XR.Management.Samples.Editor.asmdef @@ -0,0 +1,14 @@ +{ + "name": "Unity.XR.Management.Samples.Editor", + "references": [ + "Unity.XR.Management.Editor", + "Unity.XR.Management.Samples", + "Unity.XR.Management" + ], + "optionalUnityReferences": [ + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [] +} diff --git a/Samples/Editor/Unity.XR.Management.Samples.Editor.asmdef.meta b/Samples/Editor/Unity.XR.Management.Samples.Editor.asmdef.meta new file mode 100644 index 0000000..789af79 --- /dev/null +++ b/Samples/Editor/Unity.XR.Management.Samples.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 20d89efd2cc1043e5ac2c8f870d4e8fa +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples/SampleConstants.cs b/Samples/SampleConstants.cs new file mode 100644 index 0000000..c01c4f4 --- /dev/null +++ b/Samples/SampleConstants.cs @@ -0,0 +1,15 @@ +using System; + +namespace Samples +{ + /// + /// Static constants + /// + public static class SampleConstants + { + /// + /// Key we use to store and retrieve custom configuration settings from EditorBuildSettings + /// + public const string k_SettingsKey = "com.unity.xr.management.sample_settings"; + } +} diff --git a/Samples/SampleConstants.cs.meta b/Samples/SampleConstants.cs.meta new file mode 100644 index 0000000..1a45ff9 --- /dev/null +++ b/Samples/SampleConstants.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9fcb2c0388adf4dae9b9d876af26062c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples/SampleLoader.cs b/Samples/SampleLoader.cs new file mode 100644 index 0000000..7a9c6d3 --- /dev/null +++ b/Samples/SampleLoader.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +using UnityEngine; +using UnityEngine.XR.Management; + +using UnityEngine.Experimental; +using UnityEngine.Experimental.XR; + +namespace Samples +{ + /// + /// Sample loader implentation showing how to create simple loader. + /// NOTE: You have to rename this class to make it appear in the loader list for + /// XRManager. + /// + public class SampleLoader : XRLoaderHelper + { + static List s_InputSubsystemDescriptors = + new List(); + + public XRInputSubsystem inputSubsystem + { + get { return GetLoadedSubsystem(); } + } + + SampleSettings GetSettings() + { + SampleSettings settings = null; + // When running in the Unit Editor, we can a users customization of configuration data directly form + // EditorBuildSettings. At runtime, we need to grab it from the static instance field instead. + #if UNITY_EDITOR + UnityEditor.EditorBuildSettings.TryGetConfigObject(SampleConstants.k_SettingsKey, out settings); + #else + settings = SampleSettings.s_RuntimeInstance; + #endif + return settings; + } + + #region XRLoader API Implementation + + public override bool Initialize() + { + SampleSettings settings = GetSettings(); + if (settings != null) + { + // TODO: Pass settings off to plugin prior to subsystem init. + } + + CreateSubsystem(s_InputSubsystemDescriptors, "InputSubsystemDescriptor"); + + return false; + } + + public override bool Start() + { + StartSubsystem(); + return true; + } + + public override bool Stop() + { + StopSubsystem(); + return true; + } + + public override bool Deinitialize() + { + DestroySubsystem(); + return true; + } + + #endregion + } +} diff --git a/Samples/SampleLoader.cs.meta b/Samples/SampleLoader.cs.meta new file mode 100644 index 0000000..9dc8ef9 --- /dev/null +++ b/Samples/SampleLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7bc0125267fb14445a858e84633a46b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples/SampleSettings.cs b/Samples/SampleSettings.cs new file mode 100644 index 0000000..b15f65b --- /dev/null +++ b/Samples/SampleSettings.cs @@ -0,0 +1,53 @@ +using UnityEngine; +using UnityEngine.XR.Management; + +namespace Samples +{ + /// + /// Simple sample settings showing how to create custom configuration data for your package. + /// + // Uncomment below line to have the settings appear in unified settings. + //[XRConfigurationData("Sample Settings", SampleConstants.k_SettingsKey)] + [System.Serializable] + public class SampleSettings : ScriptableObject + { + /// Static instance that will hold the runtime asset instance we created in our build process. + /// + /// + #if !UNITY_EDITOR + public static SampleSettings s_RuntimeInstance = null; + #endif + + public enum Requirement + { + Required, + Optional, + None + } + + [SerializeField, Tooltip("Changes item requirement.")] + Requirement m_RequiresItem; + + public Requirement requiresItem + { + get { return m_RequiresItem; } + set { m_RequiresItem = value; } + } + + [SerializeField, Tooltip("Some toggle for runtime.")] + bool m_RuntimeToggle = true; + + public bool runtimeToggle + { + get { return m_RuntimeToggle; } + set { m_RuntimeToggle = value; } + } + + public void Awake() + { + #if !UNITY_EDITOR + s_RuntimeInstance = this; + #endif + } + } +} diff --git a/Samples/SampleSettings.cs.meta b/Samples/SampleSettings.cs.meta new file mode 100644 index 0000000..e8b486e --- /dev/null +++ b/Samples/SampleSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c76bebe14424446cca62c6e29db5054f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples/Unity.XR.Management.Samples.asmdef b/Samples/Unity.XR.Management.Samples.asmdef new file mode 100644 index 0000000..dae7db5 --- /dev/null +++ b/Samples/Unity.XR.Management.Samples.asmdef @@ -0,0 +1,13 @@ +{ + "name": "Unity.XR.Management.Samples", + "references": [ + "Unity.XR.Management.Editor", + "Unity.XR.Management" + ], + "optionalUnityReferences": [ + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [] +} diff --git a/Samples/Unity.XR.Management.Samples.asmdef.meta b/Samples/Unity.XR.Management.Samples.asmdef.meta new file mode 100644 index 0000000..2fc6774 --- /dev/null +++ b/Samples/Unity.XR.Management.Samples.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4b8d623d49974443c835ab669d1771ed +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests.meta b/Tests.meta new file mode 100644 index 0000000..339bf02 --- /dev/null +++ b/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4fadb9d2c47be4b2587eec5da859310c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor.meta b/Tests/Editor.meta new file mode 100644 index 0000000..471c32c --- /dev/null +++ b/Tests/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3642aed4b4ade4a759cfa78a5a493012 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/EditorTests.cs b/Tests/Editor/EditorTests.cs new file mode 100644 index 0000000..0910a17 --- /dev/null +++ b/Tests/Editor/EditorTests.cs @@ -0,0 +1,27 @@ +using UnityEngine; +using UnityEditor; +using UnityEngine.TestTools; +using NUnit.Framework; +using System.Collections; + +namespace ManagmentTests.Editor +{ + class EditorTests + { + [Test] + public void EditorSampleTestSimplePasses() + { + // Use the Assert class to test conditions. + } + + // A UnityTest behaves like a coroutine in PlayMode + // and allows you to yield null to skip a frame in EditMode + [UnityTest] + public IEnumerator EditorSampleTestWithEnumeratorPasses() + { + // Use the Assert class to test conditions. + // yield to skip a frame + yield return null; + } + } +} diff --git a/Tests/Editor/EditorTests.cs.meta b/Tests/Editor/EditorTests.cs.meta new file mode 100644 index 0000000..ab2e918 --- /dev/null +++ b/Tests/Editor/EditorTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 731806369f8c24ec3bfee6ac9f820f6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/Unity.XR.Management.EditorTests.asmdef b/Tests/Editor/Unity.XR.Management.EditorTests.asmdef new file mode 100644 index 0000000..b084258 --- /dev/null +++ b/Tests/Editor/Unity.XR.Management.EditorTests.asmdef @@ -0,0 +1,14 @@ +{ + "name": "Unity.XR.Management.EditorTests", + "references": [ + "Unity.XR.Management.Editor", + "Unity.XR.Management" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [] +} diff --git a/Tests/Editor/Unity.XR.Management.EditorTests.asmdef.meta b/Tests/Editor/Unity.XR.Management.EditorTests.asmdef.meta new file mode 100644 index 0000000..46bb499 --- /dev/null +++ b/Tests/Editor/Unity.XR.Management.EditorTests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f46fc3751c3e84133af5527f7c1abae5 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime.meta b/Tests/Runtime.meta new file mode 100644 index 0000000..2ff6e58 --- /dev/null +++ b/Tests/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 05bccdb13ab63410bb91245419b1b4e8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/DummyLoader.cs b/Tests/Runtime/DummyLoader.cs new file mode 100644 index 0000000..535d0e6 --- /dev/null +++ b/Tests/Runtime/DummyLoader.cs @@ -0,0 +1,49 @@ +using UnityEngine; +using UnityEngine.XR.Management; + +namespace ManagementTests.Runtime +{ + internal class DummyLoader : XRLoader + { + public bool shouldFail = false; + public int id; + + public DummyLoader() + { + } + + public override bool Initialize() + { + return !shouldFail; + } + + public override T GetLoadedSubsystem() + { + return default(T); + } + + protected bool Equals(DummyLoader other) + { + return base.Equals(other) && shouldFail == other.shouldFail && id == other.id; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DummyLoader)obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ shouldFail.GetHashCode(); + hashCode = (hashCode * 397) ^ id; + return hashCode; + } + } + } +} diff --git a/Tests/Runtime/DummyLoader.cs.meta b/Tests/Runtime/DummyLoader.cs.meta new file mode 100644 index 0000000..85ff08c --- /dev/null +++ b/Tests/Runtime/DummyLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53934dfb266064225a96fe928fdcd3b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/RuntimeTests.cs b/Tests/Runtime/RuntimeTests.cs new file mode 100644 index 0000000..9e6773f --- /dev/null +++ b/Tests/Runtime/RuntimeTests.cs @@ -0,0 +1,85 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using UnityEngine.XR.Management; + +using UnityEditor; +using UnityEngine.SceneManagement; + +namespace ManagementTests.Runtime +{ + [TestFixture(0, -1)] // No loaders, should never have any results + [TestFixture(1, -1)] // 1 loader, fails so no active loaders + [TestFixture(1, 0)] // All others, make sure the active loader is expected loader. + [TestFixture(2, 0)] + [TestFixture(2, 1)] + [TestFixture(3, 2)] + class ManualLifetimeTests + { + GameObject m_GameManager = null; + List m_Loaders = new List(); + int m_LoaderCount; + int m_LoaderIndexToWin; + + public ManualLifetimeTests(int loaderCount, int loaderIndexToWin) + { + m_LoaderCount = loaderCount; + m_LoaderIndexToWin = loaderIndexToWin; + } + + [SetUp] + public void SetupXRManagerTest() + { + m_GameManager = new GameObject(); + XRManager manager = m_GameManager.AddComponent() as XRManager; + manager.automaticLoading = false; + + m_Loaders = new List(); + + for (int i = 0; i < m_LoaderCount; i++) + { + DummyLoader dl = ScriptableObject.CreateInstance(typeof(DummyLoader)) as DummyLoader; + dl.id = i; + dl.shouldFail = (i != m_LoaderIndexToWin); + m_Loaders.Add(dl); + manager.loaders.Add(dl); + } + } + + [TearDown] + public void TeardownXRManagerTest() + { + Object.Destroy(m_GameManager); + m_GameManager = null; + } + + [UnityTest] + public IEnumerator CheckActivatedLoader() + { + Assert.IsNotNull(m_GameManager); + + XRManager manager = m_GameManager.GetComponent() as XRManager; + Assert.IsNotNull(manager); + + yield return manager.InitializeLoader(); + + if (m_LoaderIndexToWin < 0 || m_LoaderIndexToWin >= m_Loaders.Count) + { + Assert.IsNull(XRManager.activeLoader); + } + else + { + Assert.IsNotNull(XRManager.activeLoader); + Assert.AreEqual(m_Loaders[m_LoaderIndexToWin], XRManager.activeLoader); + } + + manager.DeinitializeLoader(); + + Assert.IsNull(XRManager.activeLoader); + + manager.loaders.Clear(); + } + } +} diff --git a/Tests/Runtime/RuntimeTests.cs.meta b/Tests/Runtime/RuntimeTests.cs.meta new file mode 100644 index 0000000..14add29 --- /dev/null +++ b/Tests/Runtime/RuntimeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4125b72fb8d964ddd9e2d704725a7cbc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Unity.XR.Management.Tests.asmdef b/Tests/Runtime/Unity.XR.Management.Tests.asmdef new file mode 100644 index 0000000..fc42dfa --- /dev/null +++ b/Tests/Runtime/Unity.XR.Management.Tests.asmdef @@ -0,0 +1,11 @@ +{ + "name": "Unity.XR.Management.Tests", + "references": [ + "Unity.XR.Management" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [], + "excludePlatforms": [] +} diff --git a/Tests/Runtime/Unity.XR.Management.Tests.asmdef.meta b/Tests/Runtime/Unity.XR.Management.Tests.asmdef.meta new file mode 100644 index 0000000..666aa6f --- /dev/null +++ b/Tests/Runtime/Unity.XR.Management.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0a731f4d44d04439bb291bdbaa8cb9fc +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Third Party Notices.md b/Third Party Notices.md new file mode 100644 index 0000000..3d57bbe --- /dev/null +++ b/Third Party Notices.md @@ -0,0 +1,15 @@ +This package contains third-party software components governed by the license(s) indicated below: +--------- + +Component Name: [provide component name] + +License Type: [Provide license type, i.e. "MIT", "Apache 2.0"] + +[Provide License Details] + +--------- +Component Name: [provide component name] + +License Type: [Provide license type, i.e. "MIT", "Apache 2.0"] + +[Provide License Details] diff --git a/Third Party Notices.md.meta b/Third Party Notices.md.meta new file mode 100644 index 0000000..c6c0f94 --- /dev/null +++ b/Third Party Notices.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 14a7ede6ecbb549e590a028c94057e8f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..929da64 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "com.unity.xr.management", + "displayName":"XR SDK Management", + "version": "0.1.0-preview.8", + "unity": "2018.3", + "description": "Package to provide for simple management of XR SDK loading, unloading and configuration.", + "keywords":["xr","sdk","ar","vr","management","build"], + "dependencies": { + "com.unity.modules.xr": "1.0.0" + } +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..baac281 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a8437ae6df3ab4de78c661f8e1148bbb +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: