mixedreality/com.microsoft.mixedreality..../Services/SceneSystem/MixedRealitySceneSystem.cs

1390 lines
56 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
namespace Microsoft.MixedReality.Toolkit.SceneSystem
{
/// <summary>
/// The default implementation of the <see cref="Microsoft.MixedReality.Toolkit.SceneSystem.IMixedRealitySceneSystem"/>
/// Because so much of this service's functionality is editor-only, it has been split into a partial class.
/// This part handles the runtime parts of the service.
/// </summary>
[HelpURL("https://docs.microsoft.com/windows/mixed-reality/mrtk-unity/features/scene-system/scene-system-getting-started")]
public partial class MixedRealitySceneSystem : BaseCoreSystem, IMixedRealitySceneSystem
{
/// <summary>
/// Async load operation progress amount indicating that we're ready to activate a scene.
/// https://docs.unity3d.com/ScriptReference/AsyncOperation-progress.html
/// </summary>
const float SceneActivationLoadProgress = 0.9f;
/// <summary>
/// Used by internal load methods to decide which actions to invoke.
/// </summary>
private enum SceneType
{
Manager = 0,
Content = 1,
Lighting = 2,
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="registrar">The <see cref="IMixedRealityServiceRegistrar"/> instance that loaded the service.</param>
/// <param name="profile">The configuration profile for the service.</param>
[Obsolete("This constructor is obsolete (registrar parameter is no longer required) and will be removed in a future version of the Microsoft Mixed Reality Toolkit.")]
public MixedRealitySceneSystem(
IMixedRealityServiceRegistrar registrar,
MixedRealitySceneSystemProfile profile) : this(profile)
{
Registrar = registrar;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="profile">The configuration profile for the service.</param>
public MixedRealitySceneSystem(
MixedRealitySceneSystemProfile profile) : base(profile)
{ }
private MixedRealitySceneSystemProfile Profile => ConfigurationProfile as MixedRealitySceneSystemProfile;
// Internal scene operation info
private bool managerSceneOpInProgress;
private float managerSceneOpProgress;
// Content tracker instance
private SceneContentTracker contentTracker;
// Lighting executor instance
private SceneLightingExecutor lightingExecutor;
/// <inheritdoc/>
public override string Name { get; protected set; } = "Mixed Reality Scene System";
#region Actions
/// <inheritdoc />
public Action<IEnumerable<string>> OnWillLoadContent { get; set; }
/// <inheritdoc />
public Action<IEnumerable<string>> OnContentLoaded { get; set; }
/// <inheritdoc />
public Action<IEnumerable<string>> OnWillUnloadContent { get; set; }
/// <inheritdoc />
public Action<IEnumerable<string>> OnContentUnloaded { get; set; }
/// <inheritdoc />
public Action<string> OnWillLoadLighting { get; set; }
/// <inheritdoc />
public Action<string> OnLightingLoaded { get; set; }
/// <inheritdoc />
public Action<string> OnWillUnloadLighting { get; set; }
/// <inheritdoc />
public Action<string> OnLightingUnloaded { get; set; }
/// <inheritdoc />
public Action<string> OnWillLoadScene { get; set; }
/// <inheritdoc />
public Action<string> OnSceneLoaded { get; set; }
/// <inheritdoc />
public Action<string> OnWillUnloadScene { get; set; }
/// <inheritdoc />
public Action<string> OnSceneUnloaded { get; set; }
#endregion
#region Properties
/// <inheritdoc />
public bool SceneOperationInProgress { get; private set; } = false;
/// <inheritdoc />
public float SceneOperationProgress { get; private set; } = 0;
/// <inheritdoc />
public bool LightingOperationInProgress { get; private set; } = false;
/// <inheritdoc />
public float LightingOperationProgress { get; private set; } = 0;
/// <inheritdoc />
public string ActiveLightingScene { get; private set; } = string.Empty;
/// <inheritdoc />
public bool WaitingToProceed { get; private set; } = false;
/// <inheritdoc />
public bool PrevContentExists => contentTracker.PrevContentExists;
/// <inheritdoc />
public bool NextContentExists => contentTracker.NextContentExists;
/// <inheritdoc />
public string[] ContentSceneNames => contentTracker.ContentSceneNames;
/// <inheritdoc />
public uint SourceId { get; } = 0;
/// <inheritdoc />
public string SourceName { get; } = "Mixed Reality Scene System";
#endregion
#region Service Methods
/// <inheritdoc />
public override void Initialize()
{
// Create a new instance of our content tracker
contentTracker = new SceneContentTracker(Profile);
lightingExecutor = new SceneLightingExecutor();
#if UNITY_EDITOR
EditorOnInitialize();
#endif
if (!Application.isPlaying)
{
return;
}
if (Profile.UseManagerScene)
{
SetManagerScene(Profile.ManagerScene.Name);
}
if (Profile.UseLightingScene)
{ // Set our lighting scene immediately, with no transition
SetLightingScene(Profile.DefaultLightingScene.Name, LightingSceneTransitionType.None);
}
// Call the base after initialization to ensure any early exits do not
// artificially declare the service as initialized.
base.Initialize();
}
/// <inheritdoc />
public override void Enable()
{
base.Enable();
#if UNITY_EDITOR
EditorOnDisable();
#endif
}
/// <inheritdoc />
public override void Disable()
{
#if UNITY_EDITOR
EditorOnDisable();
#endif
base.Disable();
}
/// <inheritdoc />
public override void Destroy()
{
#if UNITY_EDITOR
EditorOnDestroy();
#endif
base.Destroy();
}
private static readonly ProfilerMarker UpdatePerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.Update");
/// <inheritdoc />
public override void Update()
{
using (UpdatePerfMarker.Auto())
{
// Ensure the lighting scene is active, if we're using one.
if (Profile.UseLightingScene)
{
lightingExecutor.UpdateTransition(Time.unscaledDeltaTime);
}
}
}
#endregion
#region Scene Operations
private static readonly ProfilerMarker LoadNextContentPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.LoadNextContent");
/// <inheritdoc />
public async Task LoadNextContent(bool wrap = false, LoadSceneMode mode = LoadSceneMode.Single, SceneActivationToken activationToken = null)
{
using (LoadNextContentPerfMarker.Auto())
{
string nextContent;
if (contentTracker.GetNextContent(wrap, out nextContent))
{
await LoadContent(new string[] { nextContent }, mode, activationToken);
}
else
{
Debug.LogWarning("Attempted to load next content when no next content exists. Taking no action.");
}
}
}
private static readonly ProfilerMarker LoadPrevContentPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.LoadPrevContent");
/// <inheritdoc />
public async Task LoadPrevContent(bool wrap = false, LoadSceneMode mode = LoadSceneMode.Single, SceneActivationToken activationToken = null)
{
using (LoadPrevContentPerfMarker.Auto())
{
string prevContent;
if (contentTracker.GetPrevContent(wrap, out prevContent))
{
await LoadContent(new string[] { prevContent }, mode, activationToken);
}
else
{
Debug.LogWarning("Attempted to load prev content when no next content exists. Taking no action.");
}
}
}
/// <inheritdoc />
public async Task LoadContent(string sceneToLoad, LoadSceneMode mode = LoadSceneMode.Additive, SceneActivationToken activationToken = null)
{
await LoadContent(new string[] { sceneToLoad }, mode, activationToken);
}
/// <inheritdoc />
public async Task UnloadContent(string sceneToUnload)
{
await UnloadContent(new string[] { sceneToUnload });
}
private static readonly ProfilerMarker LoadContentByTagPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.LoadContentByTag");
/// <inheritdoc />
public async Task LoadContentByTag(string tag, LoadSceneMode mode = LoadSceneMode.Additive, SceneActivationToken activationToken = null)
{
using (LoadContentByTagPerfMarker.Auto())
{
await LoadContent(Profile.GetContentSceneNamesByTag(tag), mode, activationToken);
}
}
private static readonly ProfilerMarker UnloadContentByTagPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.UnloadContentByTag");
/// <inheritdoc />
public async Task UnloadContentByTag(string tag)
{
using (UnloadContentByTagPerfMarker.Auto())
{
try
{
await UnloadScenesInternal(Profile.GetContentSceneNamesByTag(tag), SceneType.Content);
}
catch (Exception e)
{
Debug.LogError("Error when attempting to unload content by tag " + tag);
Debug.LogException(e);
}
}
}
private static readonly ProfilerMarker LoadContentPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.LoadContent");
/// <inheritdoc />
public async Task LoadContent(IEnumerable<string> scenesToLoad, LoadSceneMode mode = LoadSceneMode.Additive, SceneActivationToken activationToken = null)
{
using (LoadContentPerfMarker.Auto())
{
if (!CanSceneOpProceed(SceneType.Content))
{
Debug.LogError("Attempting to perform a scene op when a scene op is already in progress.");
return;
}
IEnumerable<string> loadedContentScenes;
if (mode == LoadSceneMode.Single && GetLoadedContentScenes(out loadedContentScenes))
{
try
{
await UnloadScenesInternal(loadedContentScenes, SceneType.Content, 0, 0.5f, true);
}
catch (Exception e)
{
Debug.LogError("Error when attempting to unload content " + String.Join(", ", loadedContentScenes));
Debug.LogException(e);
}
try
{
await LoadScenesInternal(scenesToLoad, SceneType.Content, activationToken, 0.5f, 1f, false);
}
catch (Exception e)
{
Debug.LogError("Error when attempting to load content" + String.Join(", ", scenesToLoad));
Debug.LogException(e);
}
}
else
{
try
{
await LoadScenesInternal(scenesToLoad, SceneType.Content, activationToken);
}
catch (Exception e)
{
Debug.LogError("Error when attempting to load content" + String.Join(", ", scenesToLoad));
Debug.LogException(e);
}
}
}
}
private static readonly ProfilerMarker UnloadContentPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.UnloadContent");
/// <inheritdoc />
public async Task UnloadContent(IEnumerable<string> scenesToUnload)
{
using (UnloadContentPerfMarker.Auto())
{
if (!CanSceneOpProceed(SceneType.Content))
{
Debug.LogError("Attempting to perform a scene op when a scene op is already in progress.");
return;
}
try
{
await UnloadScenesInternal(scenesToUnload, SceneType.Content);
}
catch (Exception e)
{
Debug.LogError("Error when attempting to unload content " + String.Join(", ", scenesToUnload));
Debug.LogException(e);
}
}
}
private static readonly ProfilerMarker IsContentLoadedPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.IsContentLoaded");
/// <inheritdoc />
public bool IsContentLoaded(string sceneName)
{
using (IsContentLoadedPerfMarker.Auto())
{
Scene scene = SceneManager.GetSceneByName(sceneName);
return scene.IsValid() && scene.isLoaded;
}
}
private static readonly ProfilerMarker SetLightingScenePerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.SetLightingScene");
/// <inheritdoc />
public async void SetLightingScene(string newLightingSceneName, LightingSceneTransitionType transitionType = LightingSceneTransitionType.None, float transitionDuration = 1f)
{
using (SetLightingScenePerfMarker.Auto())
{
if (ActiveLightingScene == newLightingSceneName)
{ // Nothing to do here
return;
}
if (!CanSceneOpProceed(SceneType.Lighting))
{
Debug.LogError("Attempting to perform a scene op when a scene op is already in progress.");
return;
}
SceneInfo lightingScene;
RuntimeLightingSettings lightingSettings = default(RuntimeLightingSettings);
RuntimeRenderSettings renderSettings = default(RuntimeRenderSettings);
RuntimeSunlightSettings sunSettings = default(RuntimeSunlightSettings);
if (!string.IsNullOrEmpty(newLightingSceneName) && !Profile.GetLightingSceneSettings(
newLightingSceneName,
out lightingScene,
out lightingSettings,
out renderSettings,
out sunSettings))
{ // Make sure we don't try to load a non-existent scene
Debug.LogWarning("Couldn't find lighting scene " + newLightingSceneName + " in profile - taking no action.");
return;
}
ActiveLightingScene = newLightingSceneName;
if (!Application.isPlaying)
{ // Everything else is runtime-only
return;
}
// Start the lighting executor transition - don't bother waiting for load / unload, it can start right away
lightingExecutor.StartTransition(lightingSettings, renderSettings, sunSettings, transitionType, transitionDuration);
List<string> lightingSceneNames = new List<string>();
// Create a list of lighting scenes to unload
foreach (SceneInfo lso in Profile.LightingScenes)
{
if (lso.Name != newLightingSceneName)
{
lightingSceneNames.Add(lso.Name);
}
}
try
{
// Load the new lighting scene immediately
await LoadScenesInternal(new string[] { newLightingSceneName }, SceneType.Lighting, null, 0f, 0.5f, true);
}
catch (Exception e)
{
Debug.LogError("Exception when attempting to load lighting scene " + newLightingSceneName);
Debug.LogException(e);
}
try
{
// Unload the other lighting scenes
await UnloadScenesInternal(lightingSceneNames, SceneType.Lighting, 0.5f, 1f, false);
}
catch (Exception e)
{
Debug.LogError("Exception when attempting to unload lighting scene " + string.Join(", ", lightingSceneNames));
Debug.LogException(e);
}
}
}
private static readonly ProfilerMarker SetManagerScenePerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.SetManagerScene");
/// <summary>
/// Loads the manager scene.
/// </summary>
private async void SetManagerScene(string managerSceneName)
{
using (SetManagerScenePerfMarker.Auto())
{
Scene scene = SceneManager.GetSceneByName(managerSceneName);
if (scene.IsValid() && !scene.isLoaded)
{ // If the manager scene is already loaded, don't proceed.
return;
}
try
{
await LoadScenesInternal(new string[] { managerSceneName }, SceneType.Manager);
}
catch (Exception e)
{
Debug.LogError("Error when attempting to set manager scene " + managerSceneName);
Debug.LogException(e);
}
}
}
private static readonly ProfilerMarker LoadScenesInternalPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.LoadScenesInternal");
/// <summary>
/// Internal method to handle scene loads
/// </summary>
private async Task LoadScenesInternal(
IEnumerable<string> scenesToLoad,
SceneType sceneType,
SceneActivationToken activationToken = null,
float progressOffset = 0,
float progressTarget = 1,
bool sceneOpInProgressWhenFinished = false)
{
using (LoadScenesInternalPerfMarker.Auto())
{
// If we're using an activation token let it know that we're NOT ready to proceed
activationToken?.SetReadyToProceed(false);
SetSceneOpProgress(true, progressOffset, sceneType);
// Validate our scenes
List<string> validNames = new List<string>();
List<int> validIndexes = new List<int>();
foreach (string sceneName in scenesToLoad)
{
// See if scene exists
Scene scene;
int sceneIndex;
if (!RuntimeSceneUtils.FindScene(sceneName, out scene, out sceneIndex))
{
Debug.LogError("Can't load invalid scene " + sceneName + " - make sure the scene name is spelled correctly and that you have added the scene to your MixedRealitySceneSystem profile's content scenes array.");
}
else
{
validIndexes.Add(sceneIndex);
validNames.Add(sceneName);
}
}
int totalSceneOps = validIndexes.Count;
if (totalSceneOps < 1)
{
Debug.LogWarning("No valid scenes found to load.");
SetSceneOpProgress(sceneOpInProgressWhenFinished, progressTarget, sceneType);
return;
}
// We're about to load scenes - let everyone know
InvokeWillLoadActions(validNames, sceneType);
// Load our scenes
if (validIndexes.Count > 0)
{
List<AsyncOperation> loadSceneOps = new List<AsyncOperation>();
foreach (int sceneIndex in validIndexes)
{
Scene scene = SceneManager.GetSceneByBuildIndex(sceneIndex);
if (scene.isLoaded)
continue;
AsyncOperation sceneOp = SceneManager.LoadSceneAsync(sceneIndex, LoadSceneMode.Additive);
// Set this to true unless we have an activation token
sceneOp.allowSceneActivation = activationToken == null || activationToken.AllowSceneActivation;
loadSceneOps.Add(sceneOp);
}
// Now wait for all async operations to complete
bool completedAllSceneOps = false;
while (!completedAllSceneOps)
{
if (!Application.isPlaying)
{ // Break out of this loop if we've stopped playmode
return;
}
completedAllSceneOps = true;
bool readyToProceed = false;
bool allowSceneActivation = activationToken == null || activationToken.AllowSceneActivation;
// Go through all the load scene ops and see if we're ready to be activated
float sceneOpProgress = 0;
for (int i = 0; i < loadSceneOps.Count; i++)
{
// Set allow scene activation
// (This can be set to true by user before ReadyToProceed is set)
loadSceneOps[i].allowSceneActivation = allowSceneActivation;
if (loadSceneOps[i].isDone)
{ // Sometimes if a scene is small enough, progress will get reset to 0 before you even have a chance to check it
// This is true EVEN IF you've set allowSceneActivation to false
// So use isDone as a failsafe
sceneOpProgress += 1;
}
else
{
readyToProceed |= loadSceneOps[i].progress >= SceneActivationLoadProgress;
sceneOpProgress += loadSceneOps[i].progress;
completedAllSceneOps = false;
}
}
// Let the activation know whether we're ready
activationToken?.SetReadyToProceed(readyToProceed);
sceneOpProgress = Mathf.Clamp01(SceneOperationProgress / totalSceneOps);
SetSceneOpProgress(true, Mathf.Lerp(progressOffset, progressTarget, sceneOpProgress), sceneType);
await Task.Yield();
}
}
// Wait for all scenes to be fully loaded before proceeding
bool scenesLoadedAndActivated = false;
while (!scenesLoadedAndActivated)
{
if (!Application.isPlaying)
{ // Break out of this loop if we've stopped playmode
return;
}
scenesLoadedAndActivated = true;
foreach (int sceneIndex in validIndexes)
{
Scene scene = SceneManager.GetSceneByBuildIndex(sceneIndex);
scenesLoadedAndActivated &= (scene.IsValid() & scene.isLoaded);
}
await Task.Yield();
}
// Make sure our content tracker is refreshed
contentTracker.RefreshLoadedContent();
// We're done!
SetSceneOpProgress(sceneOpInProgressWhenFinished, progressTarget, sceneType);
InvokeLoadedActions(validNames, sceneType);
}
}
private static readonly ProfilerMarker UnloadScenesInternalPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.UnloadScenesInternal");
/// <summary>
/// Internal method to handles scene unloads
/// </summary>
private async Task UnloadScenesInternal(
IEnumerable<string> scenesToUnload,
SceneType sceneType,
float progressOffset = 0,
float progressTarget = 1,
bool sceneOpInProgressWhenFinished = false)
{
using (UnloadScenesInternalPerfMarker.Auto())
{
SetSceneOpProgress(true, progressOffset, sceneType);
List<string> validNames = new List<string>();
List<int> validIndexes = new List<int>();
foreach (string sceneName in scenesToUnload)
{
// See if scene exists
Scene scene;
int sceneIndex;
if (!RuntimeSceneUtils.FindScene(sceneName, out scene, out sceneIndex))
{
Debug.LogError("Can't unload invalid scene " + sceneName + " - make sure the scene name is spelled correctly and that you have added the scene to your MixedRealitySceneSystem profile's content scenes array.");
}
else
{
validIndexes.Add(sceneIndex);
validNames.Add(sceneName);
}
}
int totalSceneOps = validIndexes.Count;
if (totalSceneOps < 1)
{
Debug.LogWarning("No valid scenes found to unload.");
SetSceneOpProgress(sceneOpInProgressWhenFinished, progressTarget, sceneType);
return;
}
// Invoke our actions
InvokeWillUnloadActions(validNames, sceneType);
// Unload our scenes
if (validIndexes.Count > 0)
{
List<AsyncOperation> unloadSceneOps = new List<AsyncOperation>();
foreach (int sceneIndex in validIndexes)
{
Scene scene = SceneManager.GetSceneByBuildIndex(sceneIndex);
if (!scene.isLoaded)
continue;
AsyncOperation sceneOp = SceneManager.UnloadSceneAsync(sceneIndex);
unloadSceneOps.Add(sceneOp);
}
// Now wait for all async operations to complete
bool completedAllSceneOps = false;
float sceneOpProgress = 0;
while (!completedAllSceneOps)
{
if (!Application.isPlaying)
{ // Break out of this loop if we've stopped playmode
return;
}
completedAllSceneOps = true;
sceneOpProgress = 0;
for (int i = 0; i < unloadSceneOps.Count; i++)
{
sceneOpProgress += unloadSceneOps[i].progress;
completedAllSceneOps &= unloadSceneOps[i].isDone;
}
sceneOpProgress = Mathf.Clamp01(SceneOperationProgress / totalSceneOps);
SetSceneOpProgress(true, Mathf.Lerp(progressOffset, progressTarget, sceneOpProgress), sceneType);
await Task.Yield();
}
}
// Wait for all scenes to be fully unloaded before proceeding
bool scenesUnloaded = false;
while (!scenesUnloaded)
{
if (!Application.isPlaying)
{ // Break out of this loop if we've stopped playmode
return;
}
scenesUnloaded = true;
foreach (int sceneIndex in validIndexes)
{
Scene scene = SceneManager.GetSceneByBuildIndex(sceneIndex);
scenesUnloaded &= !scene.isLoaded;
}
await Task.Yield();
}
// Make sure our content tracker is refreshed
contentTracker.RefreshLoadedContent();
// We're done!
SetSceneOpProgress(sceneOpInProgressWhenFinished, progressTarget, sceneType);
// Invoke our actions
InvokeUnloadedActions(validNames, sceneType);
}
}
private void SetSceneOpProgress(bool inProgress, float progress, SceneType sceneType)
{
switch (sceneType)
{
case SceneType.Manager:
managerSceneOpInProgress = inProgress;
managerSceneOpProgress = progress;
break;
case SceneType.Content:
SceneOperationInProgress = inProgress;
SceneOperationProgress = progress;
break;
case SceneType.Lighting:
LightingOperationInProgress = inProgress;
LightingOperationProgress = progress;
break;
default:
throw new NotImplementedException();
}
}
private bool CanSceneOpProceed(SceneType sceneType)
{
switch (sceneType)
{
case SceneType.Manager:
return !managerSceneOpInProgress;
case SceneType.Content:
return !SceneOperationInProgress;
case SceneType.Lighting:
return !LightingOperationInProgress;
default:
throw new NotImplementedException();
}
}
private static readonly ProfilerMarker InvokeLoadedActionsPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.InvokeLoadedActions");
private void InvokeLoadedActions(List<string> sceneNames, SceneType sceneType)
{
using (InvokeLoadedActionsPerfMarker.Auto())
{
try
{
foreach (string sceneName in sceneNames)
{
// Announce scenes individually regardless of type
OnSceneLoaded?.Invoke(sceneName);
}
switch (sceneType)
{
case SceneType.Content:
// Announce content as a set
OnContentLoaded?.Invoke(sceneNames);
break;
case SceneType.Lighting:
// We only handle lighting scenes one at a time
Debug.Assert(sceneNames.Count == 1);
OnLightingLoaded?.Invoke(sceneNames[0]);
break;
default:
// Don't announce other types of scenes individually
break;
}
}
catch (Exception e)
{
Debug.LogError("Error when attempting to invoke loaded actions for " + string.Join(", ", sceneNames));
Debug.LogException(e);
}
}
}
private static readonly ProfilerMarker InvokeWillLoadActionsPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.InvokeWillLoadActions");
private void InvokeWillLoadActions(List<string> sceneNames, SceneType sceneType)
{
using (InvokeWillLoadActionsPerfMarker.Auto())
{
try
{
foreach (string sceneName in sceneNames)
{
// Announce scenes individually regardless of type
OnWillLoadScene?.Invoke(sceneName);
}
switch (sceneType)
{
case SceneType.Content:
// Announce content as a set
OnWillLoadContent?.Invoke(sceneNames);
break;
case SceneType.Lighting:
// We only handle lighting scenes one at a time
Debug.Assert(sceneNames.Count == 1);
OnWillLoadLighting?.Invoke(sceneNames[0]);
break;
default:
// Don't announce other types of scenes individually
break;
}
}
catch (Exception e)
{
Debug.LogError("Error when attempting to invoke will load actions for " + string.Join(", ", sceneNames));
Debug.LogException(e);
}
}
}
private static readonly ProfilerMarker InvokeWillUnloadActionsPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.InvokeWillUnloadActions");
private void InvokeWillUnloadActions(List<string> sceneNames, SceneType sceneType)
{
using (InvokeWillUnloadActionsPerfMarker.Auto())
{
try
{
foreach (string sceneName in sceneNames)
{
// Announce scenes individually regardless of type
OnWillUnloadScene?.Invoke(sceneName);
}
switch (sceneType)
{
case SceneType.Content:
// Announce content as a set
OnWillUnloadContent?.Invoke(sceneNames);
break;
case SceneType.Lighting:
// We only handle lighting scenes one at a time
Debug.Assert(sceneNames.Count == 1);
OnWillUnloadLighting?.Invoke(sceneNames[0]);
break;
default:
// Don't announce other types of scenes individually
break;
}
}
catch (Exception e)
{
Debug.LogError("Error when attempting to invoke will unload actions for " + string.Join(", ", sceneNames));
Debug.LogException(e);
}
}
}
private static readonly ProfilerMarker InvokeUnloadedActionsPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.InvokeUnloadedActions");
private void InvokeUnloadedActions(List<string> sceneNames, SceneType sceneType)
{
using (InvokeUnloadedActionsPerfMarker.Auto())
{
try
{
foreach (string sceneName in sceneNames)
{
// Announce scenes individually regardless of type
OnSceneUnloaded?.Invoke(sceneName);
}
switch (sceneType)
{
case SceneType.Content:
// Announce content as a set
OnContentUnloaded?.Invoke(sceneNames);
break;
case SceneType.Lighting:
// We only handle lighting scenes one at a time
Debug.Assert(sceneNames.Count == 1);
OnLightingUnloaded?.Invoke(sceneNames[0]);
break;
default:
// Don't announce other types of scenes individually
break;
}
}
catch (Exception e)
{
Debug.LogError("Error when attempting to invoke unloaded actions for " + string.Join(", ", sceneNames));
Debug.LogException(e);
}
}
}
#endregion
#region Utilities
/// <inheritdoc />
public IEnumerable<Scene> GetScenes(IEnumerable<string> sceneNames)
{
foreach (string sceneName in sceneNames)
{
yield return GetScene(sceneName);
}
}
private static readonly ProfilerMarker GetScenePerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.GetScene");
/// <inheritdoc />
public Scene GetScene(string sceneName)
{
using (GetScenePerfMarker.Auto())
{
Scene scene = default(Scene);
int sceneIndex;
RuntimeSceneUtils.FindScene(sceneName, out scene, out sceneIndex);
return scene;
}
}
private static readonly ProfilerMarker GetLoadedContentScenesPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem.GetLoadedContentScenes");
/// <summary>
/// Checks whether any content scenes are loaded
/// If they are, adds them to loadedContentScenes and returns true
/// </summary>
private bool GetLoadedContentScenes(out IEnumerable<string> loadedContentScenes)
{
using (GetLoadedContentScenesPerfMarker.Auto())
{
List<string> loadedContentScenesList = new List<string>();
foreach (string sceneName in ContentSceneNames)
{
if (IsContentLoaded(sceneName))
{
loadedContentScenesList.Add(sceneName);
}
}
loadedContentScenes = loadedContentScenesList;
return loadedContentScenesList.Count > 0;
}
}
#endregion
#region IEqualityComparer
/// <inheritdoc />
bool IEqualityComparer.Equals(object x, object y)
{
// There shouldn't be other Boundary Managers to compare to.
return false;
}
/// <inheritdoc />
int IEqualityComparer.GetHashCode(object obj)
{
return Mathf.Abs(SourceName.GetHashCode());
}
#endregion
#region Utility Classes
/// <summary>
/// A utility class used to track which content scenes are loaded, and which should come next / before.
/// This logic could live in the service itself, but there may be cases where devs want to change how content is tracked without changing anything else.
/// Might be worth putting this into a SystemType field in the profile.
/// </summary>
private sealed class SceneContentTracker
{
public SceneContentTracker(MixedRealitySceneSystemProfile profile)
{
this.profile = profile;
CacheSortedContent();
}
private MixedRealitySceneSystemProfile profile;
public string[] ContentSceneNames => contentSceneNames;
public SceneInfo[] SortedContentScenes => sortedContentScenes;
public SceneInfo[] SortedLightingScenes => sortedLightingScenes;
public bool PrevContentExists { get { return smalledLoadedContentIndex > 0; } }
public bool NextContentExists { get { return largestLoadedContentIndex < contentSceneNames.Length - 1; } }
private int largestLoadedContentIndex;
private int smalledLoadedContentIndex;
// Cached scene info and scene names
private string[] contentSceneNames;
private SceneInfo[] sortedContentScenes;
private SceneInfo[] sortedLightingScenes;
private static readonly ProfilerMarker CacheSortedContentPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem+SceneContentTracker.CacheSortedContent");
private void CacheSortedContent()
{
using (CacheSortedContentPerfMarker.Auto())
{
// Store a set of scenes ordered by build index
sortedContentScenes = profile.ContentScenes.OrderBy(s => s.BuildIndex).ToArray();
sortedLightingScenes = profile.LightingScenes.OrderBy(s => s.BuildIndex).ToArray();
// Cache an array of scene names in the same order
contentSceneNames = new string[sortedContentScenes.Length];
for (int i = 0; i < contentSceneNames.Length; i++)
{
contentSceneNames[i] = sortedContentScenes[i].Name;
}
}
}
private static readonly ProfilerMarker GetNextContentPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem+SceneContentTracker.GetNextContent");
public bool GetNextContent(bool wrap, out string contentSceneName)
{
using (GetNextContentPerfMarker.Auto())
{
contentSceneName = string.Empty;
int nextIndex = largestLoadedContentIndex + 1;
if (nextIndex >= contentSceneNames.Length)
{
if (wrap)
{
// If we're wrapping and we've reached the end,
// just return the first index.
contentSceneName = contentSceneNames[0];
return true;
}
else
{ // We're out of scenes!
return false;
}
}
contentSceneName = contentSceneNames[nextIndex];
return true;
}
}
private static readonly ProfilerMarker GetPrevContentPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem+SceneContentTracker.GetPrevContent");
public bool GetPrevContent(bool wrap, out string contentSceneName)
{
using (GetPrevContentPerfMarker.Auto())
{
contentSceneName = string.Empty;
int prevIndex = smalledLoadedContentIndex - 1;
if (prevIndex < 0)
{
if (wrap)
{
// If we're wrapping and we've reached the start,
// just return the last index
contentSceneName = contentSceneNames[contentSceneNames.Length - 1];
return true;
}
else
{ // We're out of scenes!
return false;
}
}
contentSceneName = contentSceneNames[prevIndex];
return true;
}
}
private static readonly ProfilerMarker RefreshLoadedContentPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem+SceneContentTracker.RefreshLoadedContent");
public void RefreshLoadedContent()
{
using (RefreshLoadedContentPerfMarker.Auto())
{
largestLoadedContentIndex = -1;
smalledLoadedContentIndex = contentSceneNames.Length;
for (int i = 0; i < contentSceneNames.Length; i++)
{
Scene scene = SceneManager.GetSceneByName(contentSceneNames[i]);
if (scene.isLoaded)
{
largestLoadedContentIndex = Mathf.Max(i, largestLoadedContentIndex);
smalledLoadedContentIndex = Mathf.Min(i, smalledLoadedContentIndex);
}
}
#if UNITY_EDITOR
CacheSortedContent();
#endif
}
}
}
/// <summary>
/// A utility class used to lerp between and apply lighting settings to the active scene.
/// </summary>
private sealed class SceneLightingExecutor
{
public void StartTransition(
RuntimeLightingSettings targetLightingSettings,
RuntimeRenderSettings targetRenderSettings,
RuntimeSunlightSettings targetSunlightSettings,
LightingSceneTransitionType transitionType = LightingSceneTransitionType.None,
float transitionDuration = 1)
{
// Update our target settings
this.transitionElapsed = 0;
this.transitionType = transitionType;
this.transitionDuration = transitionDuration;
this.targetLightingSettings = targetLightingSettings;
this.targetRenderSettings = targetRenderSettings;
this.targetSunlightSettings = targetSunlightSettings;
switch (transitionType)
{
case LightingSceneTransitionType.None:
// Just execute the transition right now
// Zap immediately to the new values
currentLightingSettings = targetLightingSettings;
currentRenderSettings = targetRenderSettings;
currentSunlightSettings = targetSunlightSettings;
transitionElapsed = transitionDuration;
ApplySettings();
return;
}
// Otherwise, copy our old settings so we have something to lerp from
prevLightingSettings = currentLightingSettings;
prevRenderSettings = currentRenderSettings;
prevSunlightSettings = currentSunlightSettings;
}
private static readonly ProfilerMarker UpdateTransitionPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem+SceneLightingExecutor.UpdateTransition");
public void UpdateTransition(float deltaTime)
{
using (UpdateTransitionPerfMarker.Auto())
{
if (transitionElapsed < transitionDuration)
{
transitionElapsed += deltaTime;
if (transitionElapsed >= transitionDuration)
{
currentLightingSettings = targetLightingSettings;
currentRenderSettings = targetRenderSettings;
currentSunlightSettings = targetSunlightSettings;
ApplySettings();
return;
}
}
float transitionProgress = Mathf.Clamp01(transitionElapsed / transitionDuration);
switch (transitionType)
{
case LightingSceneTransitionType.None:
break;
case LightingSceneTransitionType.CrossFade:
// Just do a straightforward lerp from one setting to the other
currentLightingSettings = RuntimeLightingSettings.Lerp(prevLightingSettings, targetLightingSettings, transitionProgress);
currentRenderSettings = RuntimeRenderSettings.Lerp(prevRenderSettings, targetRenderSettings, transitionProgress);
currentSunlightSettings = RuntimeSunlightSettings.Lerp(prevSunlightSettings, targetSunlightSettings, transitionProgress);
break;
case LightingSceneTransitionType.FadeToBlack:
// If we're in the first half of our transition, fade out to black
if (transitionProgress < 0.5f)
{
float fadeOutProgress = transitionProgress / 0.5f;
currentLightingSettings = RuntimeLightingSettings.Lerp(
prevLightingSettings,
RuntimeLightingSettings.Black(prevLightingSettings),
fadeOutProgress);
currentRenderSettings = RuntimeRenderSettings.Lerp(
prevRenderSettings,
RuntimeRenderSettings.Black(prevRenderSettings),
fadeOutProgress);
currentSunlightSettings = RuntimeSunlightSettings.Lerp(
prevSunlightSettings,
RuntimeSunlightSettings.Black(prevSunlightSettings),
fadeOutProgress);
}
else
{
// If we're in the second half, fade in from black
float fadeInProgress = (transitionProgress - 0.5f) / 0.5f;
currentLightingSettings = RuntimeLightingSettings.Lerp(
RuntimeLightingSettings.Black(targetLightingSettings),
targetLightingSettings,
fadeInProgress);
currentRenderSettings = RuntimeRenderSettings.Lerp(
RuntimeRenderSettings.Black(targetRenderSettings),
targetRenderSettings,
fadeInProgress);
currentSunlightSettings = RuntimeSunlightSettings.Lerp(
RuntimeSunlightSettings.Black(targetSunlightSettings),
targetSunlightSettings,
fadeInProgress);
}
break;
}
ApplySettings();
}
}
private static readonly ProfilerMarker ApplySettingsPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem+SceneLightingExecutor.ApplySettings");
public void ApplySettings()
{
using (ApplySettingsPerfMarker.Auto())
{
RenderSettings.ambientEquatorColor = currentRenderSettings.AmbientEquatorColor;
RenderSettings.ambientGroundColor = currentRenderSettings.AmbientGroundColor;
RenderSettings.ambientIntensity = currentRenderSettings.AmbientIntensity;
RenderSettings.ambientLight = currentRenderSettings.AmbientLight;
RenderSettings.ambientMode = (AmbientMode)currentRenderSettings.AmbientMode;
RenderSettings.ambientSkyColor = currentRenderSettings.AmbientSkyColor;
#if UNITY_2022_2_OR_NEWER
RenderSettings.customReflectionTexture = currentRenderSettings.CustomReflection;
#else
RenderSettings.customReflection = currentRenderSettings.CustomReflection;
#endif
RenderSettings.defaultReflectionMode = currentRenderSettings.DefaultReflectionMode;
RenderSettings.defaultReflectionResolution = currentRenderSettings.DefaultReflectionResolution;
RenderSettings.fog = currentRenderSettings.Fog;
RenderSettings.fogColor = currentRenderSettings.FogColor;
RenderSettings.fogDensity = currentRenderSettings.FogDensity;
RenderSettings.fogEndDistance = currentRenderSettings.LinearFogEnd;
RenderSettings.fogMode = currentRenderSettings.FogMode;
RenderSettings.fogStartDistance = currentRenderSettings.LinearFogStart;
RenderSettings.reflectionBounces = currentRenderSettings.ReflectionBounces;
RenderSettings.reflectionIntensity = currentRenderSettings.ReflectionIntensity;
RenderSettings.skybox = currentRenderSettings.SkyboxMaterial;
RenderSettings.subtractiveShadowColor = currentRenderSettings.SubtractiveShadowColor;
if (currentSunlightSettings.UseSunlight)
{
FindOrCreateSunlight();
Light sunLight = RenderSettings.sun;
sunLight.color = currentSunlightSettings.Color;
sunLight.intensity = currentSunlightSettings.Intensity;
sunLight.transform.rotation = Quaternion.Euler(currentSunlightSettings.XRotation, currentSunlightSettings.YRotation, currentSunlightSettings.ZRotation);
}
else
{
DisableSunlight();
}
}
}
private static readonly ProfilerMarker FindOrCreateSunlightPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem+SceneLightingExecutor.FindOrCreateSunlight");
private void FindOrCreateSunlight()
{
using (FindOrCreateSunlightPerfMarker.Auto())
{
if (RenderSettings.sun == null)
{
if (sharedSunLight == null)
{
Debug.Log("Shared sunlight is null, creating a shared sunlight");
// Create a shared sunlight
sharedSunLight = new GameObject("Shared Sunlight").AddComponent<Light>();
sharedSunLight.type = LightType.Directional;
sharedSunLight.intensity = 0;
}
RenderSettings.sun = sharedSunLight;
}
RenderSettings.sun.enabled = true;
}
}
private static readonly ProfilerMarker DisableSunlightPerfMarker = new ProfilerMarker("[MRTK] MixedRealitySceneSystem+SceneLightingExecutor.DisableSunlight");
private void DisableSunlight()
{
using (DisableSunlightPerfMarker.Auto())
{
if (RenderSettings.sun != null)
{
RenderSettings.sun.enabled = false;
}
if (sharedSunLight != null)
{
sharedSunLight.enabled = false;
}
}
}
private RuntimeLightingSettings targetLightingSettings;
private RuntimeLightingSettings currentLightingSettings;
private RuntimeLightingSettings prevLightingSettings;
private RuntimeSunlightSettings targetSunlightSettings;
private RuntimeSunlightSettings currentSunlightSettings;
private RuntimeSunlightSettings prevSunlightSettings;
private RuntimeRenderSettings targetRenderSettings;
private RuntimeRenderSettings currentRenderSettings;
private RuntimeRenderSettings prevRenderSettings;
private LightingSceneTransitionType transitionType;
private float transitionDuration;
private float transitionElapsed;
private Light sharedSunLight;
}
#endregion
}
}