327 lines
11 KiB
C#
327 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using ModestTree;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
namespace Zenject.Internal
|
|
{
|
|
[InitializeOnLoad]
|
|
public static class SceneParentAutomaticLoader
|
|
{
|
|
static SceneParentAutomaticLoader()
|
|
{
|
|
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
|
}
|
|
|
|
static void OnPlayModeStateChanged(PlayModeStateChange state)
|
|
{
|
|
if (state == PlayModeStateChange.ExitingEditMode)
|
|
{
|
|
try
|
|
{
|
|
ValidateMultiSceneSetupAndLoadDefaultSceneParents();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
EditorApplication.isPlaying = false;
|
|
throw new ZenjectException(
|
|
"Failure occurred when attempting to load default scene parent contracts!", e);
|
|
}
|
|
}
|
|
else if (state == PlayModeStateChange.EnteredEditMode)
|
|
{
|
|
// It would be cool to restore the initial scene set up here but in order to do this
|
|
// we would have to make sure that the user saves the scene before running which
|
|
// would be too annoying, so just leave any changes we've made alone
|
|
}
|
|
}
|
|
|
|
public static void ValidateMultiSceneSetupAndLoadDefaultSceneParents()
|
|
{
|
|
var defaultContractsMap = LoadDefaultContractsMap();
|
|
|
|
// NOTE: Even if configs is empty we still want to do the below logic to validate the
|
|
// multi scene setup
|
|
|
|
var sceneInfos = GetLoadedZenjectSceneInfos();
|
|
var contractMap = GetCurrentSceneContractsMap(sceneInfos);
|
|
|
|
foreach (var sceneInfo in sceneInfos)
|
|
{
|
|
ProcessScene(sceneInfo, contractMap, defaultContractsMap);
|
|
}
|
|
}
|
|
|
|
static Dictionary<string, LoadedSceneInfo> GetCurrentSceneContractsMap(
|
|
List<LoadedSceneInfo> sceneInfos)
|
|
{
|
|
var contractMap = new Dictionary<string, LoadedSceneInfo>();
|
|
|
|
foreach (var info in sceneInfos)
|
|
{
|
|
AddToContractMap(contractMap, info);
|
|
}
|
|
|
|
return contractMap;
|
|
}
|
|
|
|
static void ProcessScene(
|
|
LoadedSceneInfo sceneInfo,
|
|
Dictionary<string, LoadedSceneInfo> contractMap,
|
|
Dictionary<string, string> defaultContractsMap)
|
|
{
|
|
if (sceneInfo.SceneContext != null)
|
|
{
|
|
Assert.IsNull(sceneInfo.DecoratorContext);
|
|
ProcessSceneParents(sceneInfo, contractMap, defaultContractsMap);
|
|
}
|
|
else
|
|
{
|
|
Assert.IsNotNull(sceneInfo.DecoratorContext);
|
|
ProcessSceneDecorators(sceneInfo, contractMap, defaultContractsMap);
|
|
}
|
|
}
|
|
|
|
static void ProcessSceneDecorators(
|
|
LoadedSceneInfo sceneInfo,
|
|
Dictionary<string, LoadedSceneInfo> contractMap,
|
|
Dictionary<string, string> defaultContractsMap)
|
|
{
|
|
var decoratedContractName = sceneInfo.DecoratorContext.DecoratedContractName;
|
|
|
|
LoadedSceneInfo decoratedSceneInfo;
|
|
|
|
if (contractMap.TryGetValue(decoratedContractName, out decoratedSceneInfo))
|
|
{
|
|
ValidateDecoratedSceneMatch(sceneInfo, decoratedSceneInfo);
|
|
return;
|
|
}
|
|
|
|
decoratedSceneInfo = LoadDefaultSceneForContract(
|
|
sceneInfo, decoratedContractName, defaultContractsMap);
|
|
|
|
EditorSceneManager.MoveSceneAfter(decoratedSceneInfo.Scene, sceneInfo.Scene);
|
|
|
|
ValidateDecoratedSceneMatch(sceneInfo, decoratedSceneInfo);
|
|
|
|
ProcessScene(decoratedSceneInfo, contractMap, defaultContractsMap);
|
|
}
|
|
|
|
static void ProcessSceneParents(
|
|
LoadedSceneInfo sceneInfo,
|
|
Dictionary<string, LoadedSceneInfo> contractMap,
|
|
Dictionary<string, string> defaultContractsMap)
|
|
{
|
|
foreach (var parentContractName in sceneInfo.SceneContext.ParentContractNames)
|
|
{
|
|
LoadedSceneInfo parentInfo;
|
|
|
|
if (contractMap.TryGetValue(parentContractName, out parentInfo))
|
|
{
|
|
ValidateParentChildMatch(parentInfo, sceneInfo);
|
|
continue;
|
|
}
|
|
|
|
parentInfo = LoadDefaultSceneForContract(sceneInfo, parentContractName, defaultContractsMap);
|
|
|
|
AddToContractMap(contractMap, parentInfo);
|
|
|
|
EditorSceneManager.MoveSceneBefore(parentInfo.Scene, sceneInfo.Scene);
|
|
|
|
ValidateParentChildMatch(parentInfo, sceneInfo);
|
|
|
|
ProcessScene(parentInfo, contractMap, defaultContractsMap);
|
|
}
|
|
}
|
|
|
|
static LoadedSceneInfo LoadDefaultSceneForContract(
|
|
LoadedSceneInfo sceneInfo, string contractName, Dictionary<string, string> defaultContractsMap)
|
|
{
|
|
string scenePath;
|
|
|
|
if (!defaultContractsMap.TryGetValue(contractName, out scenePath))
|
|
{
|
|
throw Assert.CreateException(
|
|
"Could not fill contract '{0}' for scene '{1}'. No scenes with that contract name are loaded, and could not find a match in any default scene contract configs to auto load one either."
|
|
.Fmt(contractName, sceneInfo.Scene.name));
|
|
}
|
|
|
|
Scene scene;
|
|
|
|
try
|
|
{
|
|
scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new ZenjectException(
|
|
"Error while attempting to load contracts for scene '{0}'".Fmt(sceneInfo.Scene.name), e);
|
|
}
|
|
|
|
return CreateLoadedSceneInfo(scene);
|
|
}
|
|
|
|
static void ValidateDecoratedSceneMatch(
|
|
LoadedSceneInfo decoratorInfo, LoadedSceneInfo decoratedInfo)
|
|
{
|
|
var decoratorIndex = GetSceneIndex(decoratorInfo.Scene);
|
|
var decoratedIndex = GetSceneIndex(decoratedInfo.Scene);
|
|
var activeIndex = GetSceneIndex(EditorSceneManager.GetActiveScene());
|
|
|
|
Assert.That(decoratorIndex < decoratedIndex,
|
|
"Decorator scene '{0}' must be loaded before decorated scene '{1}'. Please drag the decorator scene to be placed above the other scene in the scene hierarchy.",
|
|
decoratorInfo.Scene.name, decoratedInfo.Scene.name);
|
|
|
|
if (activeIndex > decoratorIndex)
|
|
{
|
|
EditorSceneManager.SetActiveScene(decoratorInfo.Scene);
|
|
}
|
|
}
|
|
|
|
static void ValidateParentChildMatch(
|
|
LoadedSceneInfo parentSceneInfo, LoadedSceneInfo sceneInfo)
|
|
{
|
|
var parentIndex = GetSceneIndex(parentSceneInfo.Scene);
|
|
var childIndex = GetSceneIndex(sceneInfo.Scene);
|
|
var activeIndex = GetSceneIndex(EditorSceneManager.GetActiveScene());
|
|
|
|
Assert.That(parentIndex < childIndex,
|
|
"Parent scene '{0}' must be loaded before child scene '{1}'. Please drag it to be placed above its child in the scene hierarchy.", parentSceneInfo.Scene.name, sceneInfo.Scene.name);
|
|
|
|
if (activeIndex > parentIndex)
|
|
{
|
|
EditorSceneManager.SetActiveScene(parentSceneInfo.Scene);
|
|
}
|
|
}
|
|
|
|
static int GetSceneIndex(Scene scene)
|
|
{
|
|
for (int i = 0; i < EditorSceneManager.sceneCount; i++)
|
|
{
|
|
if (EditorSceneManager.GetSceneAt(i) == scene)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
throw Assert.CreateException();
|
|
}
|
|
|
|
static Dictionary<string, string> LoadDefaultContractsMap()
|
|
{
|
|
var configs = Resources.LoadAll<DefaultSceneContractConfig>(DefaultSceneContractConfig.ResourcePath);
|
|
|
|
var map = new Dictionary<string, string>();
|
|
|
|
foreach (var config in configs)
|
|
{
|
|
foreach (var info in config.DefaultContracts)
|
|
{
|
|
if (info.ContractName.Trim().IsEmpty())
|
|
{
|
|
Log.Warn("Found empty contract name in default scene contract config at path '{0}'", AssetDatabase.GetAssetPath(config));
|
|
continue;
|
|
}
|
|
|
|
Assert.That(!map.ContainsKey(info.ContractName),
|
|
"Found duplicate contract '{0}' in default scene contract config at '{1}'! Default contract already specified", info.ContractName, AssetDatabase.GetAssetPath(config));
|
|
|
|
map.Add(info.ContractName, AssetDatabase.GetAssetPath(info.Scene));
|
|
}
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
static LoadedSceneInfo CreateLoadedSceneInfo(Scene scene)
|
|
{
|
|
var info = TryCreateLoadedSceneInfo(scene);
|
|
Assert.IsNotNull(info, "Expected scene '{0}' to be a zenject scene", scene.name);
|
|
return info;
|
|
}
|
|
|
|
static LoadedSceneInfo TryCreateLoadedSceneInfo(Scene scene)
|
|
{
|
|
var sceneContext = ZenUnityEditorUtil.TryGetSceneContextForScene(scene);
|
|
var decoratorContext = ZenUnityEditorUtil.TryGetDecoratorContextForScene(scene);
|
|
|
|
if (sceneContext == null && decoratorContext == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var info = new LoadedSceneInfo
|
|
{
|
|
Scene = scene
|
|
};
|
|
|
|
if (sceneContext != null)
|
|
{
|
|
Assert.IsNull(decoratorContext,
|
|
"Found both SceneContext and SceneDecoratorContext in scene '{0}'", scene.name);
|
|
|
|
info.SceneContext = sceneContext;
|
|
}
|
|
else
|
|
{
|
|
Assert.IsNotNull(decoratorContext);
|
|
|
|
info.DecoratorContext = decoratorContext;
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
static List<LoadedSceneInfo> GetLoadedZenjectSceneInfos()
|
|
{
|
|
var result = new List<LoadedSceneInfo>();
|
|
|
|
for (int i = 0; i < EditorSceneManager.sceneCount; i++)
|
|
{
|
|
var scene = EditorSceneManager.GetSceneAt(i);
|
|
var info = TryCreateLoadedSceneInfo(scene);
|
|
|
|
if (info != null)
|
|
{
|
|
result.Add(info);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void AddToContractMap(
|
|
Dictionary<string, LoadedSceneInfo> contractMap, LoadedSceneInfo info)
|
|
{
|
|
if (info.SceneContext == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var contractName in info.SceneContext.ContractNames)
|
|
{
|
|
LoadedSceneInfo currentInfo;
|
|
|
|
if (contractMap.TryGetValue(contractName, out currentInfo))
|
|
{
|
|
throw Assert.CreateException(
|
|
"Found multiple scene contracts with name '{0}'. Scene '{1}' and scene '{2}'",
|
|
contractName, currentInfo.Scene.name, info.Scene.name);
|
|
}
|
|
|
|
contractMap.Add(contractName, info);
|
|
}
|
|
}
|
|
|
|
public class LoadedSceneInfo
|
|
{
|
|
public SceneContext SceneContext;
|
|
public SceneDecoratorContext DecoratorContext;
|
|
public Scene Scene;
|
|
}
|
|
}
|
|
}
|