Skip to content

Commit

Permalink
Merge pull request #7 from brunomikoski/feature/saving-between-editor…
Browse files Browse the repository at this point in the history
…-kill-start

fix: added to store / restore between editor instances
  • Loading branch information
brunomikoski authored Dec 8, 2020
2 parents a91af90 + 71762cf commit 41c9c9b
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 88 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.1]
### Added
- Automatically Loading and Saving the hierarchy when opening and closing unity as well [suggested here](https://github.com/brunomikoski/SceneKeeper/issues/6).

### Changed
- Moved all unity scene hierarchy to a specific class `UnityHierarchyTools`

## [0.1.0]
### Added
Expand Down Expand Up @@ -32,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### [Unreleased]


[0.1.1]: https://github.com/brunomikoski/SceneKeeper/releases/tag/v0.1.1
[0.1.0]: https://github.com/brunomikoski/SceneKeeper/releases/tag/v0.1.0
[0.0.5]: https://github.com/brunomikoski/SceneKeeper/releases/tag/v0.0.5
[0.0.4]: https://github.com/brunomikoski/SceneKeeper/releases/tag/v0.0.4
Expand Down
151 changes: 64 additions & 87 deletions Scripts/Editor/SceneStateKeeper.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
Expand All @@ -13,38 +11,9 @@ namespace BrunoMikoski.SceneHierarchyKeeper
[InitializeOnLoad]
public static class SceneStateKeeper
{
private const string HierarchyDataStorageKey = "_cachedHierarchyData_storage_key";
private const string UnityEditorSceneHierarchyWindowTypeName = "UnityEditor.SceneHierarchyWindow";
private const string ExpandTreeViewItemMethodName = "ExpandTreeViewItem";
private const string GetExpandedIDsMethodName = "GetExpandedIDs";
private const string SceneHierarchyPropertyName = "sceneHierarchy";
private const string HIERARCHY_DATA_STORAGE_KEY = "_cachedHierarchyData_storage_key";
private const int CHILD_LIST_CAPACITY = 512;

private static Type cachedSceneHierarchyWindowType;
private static Type SceneHierarchyWindowType
{
get
{
if (cachedSceneHierarchyWindowType == null)
{
//https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/SceneHierarchyWindow.cs
cachedSceneHierarchyWindowType = typeof(EditorWindow).Assembly.GetType(UnityEditorSceneHierarchyWindowTypeName);
}
return cachedSceneHierarchyWindowType;
}
}

private static EditorWindow cachedHierarchyWindow;
private static EditorWindow HierarchyWindow
{
get
{
if (cachedHierarchyWindow == null)
cachedHierarchyWindow = EditorWindow.GetWindow(SceneHierarchyWindowType);
return cachedHierarchyWindow;
}
}

private static SceneData cachedSceneData;
private static SceneData SceneData
{
Expand All @@ -59,31 +28,78 @@ private static SceneData SceneData
private static Dictionary<Transform, string> sceneItemsCache = new Dictionary<Transform, string>();
private static Dictionary<string, GameObject> pathToGameObjectsCache = new Dictionary<string, GameObject>();
private static List<Transform> childListTransform = new List<Transform>(CHILD_LIST_CAPACITY);


private static Dictionary<Scene, List<GameObject>> selectionHistory = new Dictionary<Scene, List<GameObject>>();
private static HashSet<Scene> clearedSceneChangeLists = new HashSet<Scene>();

static SceneStateKeeper()
{
EditorSceneManager.sceneOpened += OnSceneOpened;
SceneManager.sceneLoaded += OnSceneLoaded;
EditorSceneManager.sceneClosing += OnSceneClosing;
SceneManager.sceneUnloaded += OnSceneUnloaded;
Selection.selectionChanged += OnSelectionChanged;
EditorApplication.quitting += OnEditorApplicationQuitting;
EditorApplication.delayCall += RestoreFromAllOpenScenes;
}

private static void OnEditorApplicationQuitting()
{
SaveFromAllOpenScenes();
}

private static void OnSceneUnloaded(Scene scene)
{
StoreScenedData(scene);
}

private static void OnSceneLoaded(Scene scene, LoadSceneMode arg1)
{
RestoreSceneData(scene);
}

private static void OnSceneClosing(Scene scene, bool removingscene)
{
StoreScenedData(scene);
}

private static void OnSceneOpened(Scene scene, OpenSceneMode mode)
{
RestoreSceneData(scene);
}

private static void SaveFromAllOpenScenes()
{
if (!UnityHierarchyTools.IsHierarchyWindowOpen())
return;

for (int i = 0; i < SceneManager.sceneCount; i++)
{
StoreScenedData(SceneManager.GetSceneAt(i));
}
}

private static void RestoreFromAllOpenScenes()
{
if (!UnityHierarchyTools.IsHierarchyWindowOpen())
return;

for (int i = 0; i < SceneManager.sceneCount; i++)
{
RestoreSceneData(SceneManager.GetSceneAt(i));
}
}

private static void OnSelectionChanged()
{
if (!SceneKeeperTools.IsSelectionKeeperActive())
return;

if (HierarchyWindow == null)
if (!UnityHierarchyTools.IsHierarchyWindowOpen())
return;

if (Selection.objects.Length == 0)
{
if (EditorWindow.focusedWindow == HierarchyWindow)
if (EditorWindow.focusedWindow == UnityHierarchyTools.HierarchyWindow)
{
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Expand Down Expand Up @@ -123,29 +139,9 @@ private static void OnSelectionChanged()
}
}

private static void OnSceneUnloaded(Scene scene)
{
StoreScenedData(scene);
}

private static void OnSceneLoaded(Scene scene, LoadSceneMode arg1)
{
RestoreSceneData(scene);
}

private static void OnSceneClosing(Scene scene, bool removingscene)
{
StoreScenedData(scene);
}

private static void OnSceneOpened(Scene scene, OpenSceneMode mode)
{
RestoreSceneData(scene);
}

private static void RestoreSceneData(Scene scene)
{
if (HierarchyWindow == null)
if (!UnityHierarchyTools.IsHierarchyWindowOpen())
return;

HashSet<string> alreadySelectedGameObjectPaths = new HashSet<string>();
Expand All @@ -159,31 +155,20 @@ private static void RestoreHierarchyData(Scene scene, ref HashSet<string> alread
if (!SceneKeeperTools.IsHierarchyKeeperActive())
return;

object sceneHierarchy = SceneHierarchyWindowType.GetProperty(SceneHierarchyPropertyName)
.GetValue(HierarchyWindow);
MethodInfo setExpandedMethod =
sceneHierarchy.GetType().GetMethod(ExpandTreeViewItemMethodName,
BindingFlags.Instance | BindingFlags.NonPublic);

if (setExpandedMethod == null)
{
throw new Exception(
$"Could not find a method with name {ExpandTreeViewItemMethodName} on type {UnityEditorSceneHierarchyWindowTypeName}, maybe unity changed it? ");
}

for (int i = 0; i < SceneData.alwaysExpanded.Count; i++)
{
string alwaysExpandedItemPath = SceneData.alwaysExpanded[i];
if (TryToFindInAllOpenScenes(alwaysExpandedItemPath, out GameObject targetGameObject))
{
setExpandedMethod.Invoke(sceneHierarchy, new object[] {targetGameObject.GetInstanceID(), true});
alreadySelectedGameObjectPaths.Add(alwaysExpandedItemPath);
UnityHierarchyTools.SetExpanded(targetGameObject.GetInstanceID(), true);
}
}

if (!SceneData.TryGetSceneData(scene.path, out HierarchyData sceneHierarchyData))
return;

setExpandedMethod.Invoke(sceneHierarchy, new object[] {scene.handle, true});
UnityHierarchyTools.SetExpanded(scene.handle, true);

for (int i = 0; i < sceneHierarchyData.itemsPath.Count; i++)
{
Expand All @@ -195,7 +180,7 @@ private static void RestoreHierarchyData(Scene scene, ref HashSet<string> alread
if (!TryToFindBySceneRootObjects(scene, expandedItemPath, out GameObject gameObjectAtPath))
continue;

setExpandedMethod.Invoke(sceneHierarchy, new object[] {gameObjectAtPath.GetInstanceID(), true});
UnityHierarchyTools.SetExpanded(gameObjectAtPath.GetInstanceID(), true);
}
}

Expand Down Expand Up @@ -224,7 +209,7 @@ private static void RestoreSelectionData(Scene scene, ref HashSet<string> alread

private static void StoreScenedData(Scene targetScene)
{
if (HierarchyWindow == null)
if (!UnityHierarchyTools.IsHierarchyWindowOpen())
return;

HashSet<GameObject> alreadySelectedGameObjects = new HashSet<GameObject>();
Expand All @@ -241,23 +226,15 @@ private static void StoreHierarchyData(Scene targetScene, ref HashSet<GameObject

if (!SceneKeeperTools.IsHierarchyKeeperActive())
return;

MethodInfo getExpandedIDsMethod = SceneHierarchyWindowType.GetMethod(GetExpandedIDsMethodName,
BindingFlags.NonPublic | BindingFlags.Instance);
if (getExpandedIDsMethod == null)
{
throw new Exception(
$"Could not find a method with name {GetExpandedIDsMethodName} on type {UnityEditorSceneHierarchyWindowTypeName}, maybe unity changed it? ");
}

int[] result = (int[]) getExpandedIDsMethod.Invoke(HierarchyWindow, null);
int[] expandedItemIDs = UnityHierarchyTools.GetExpandedItems();

HierarchyData data = SceneData.GetOrAddSceneData(targetScene.path);
data.itemsPath.Clear();

for (int i = 0; i < result.Length; i++)
for (int i = 0; i < expandedItemIDs.Length; i++)
{
int instanceID = result[i];
int instanceID = expandedItemIDs[i];
Object targetObj = EditorUtility.InstanceIDToObject(instanceID);

if (targetObj == null)
Expand Down Expand Up @@ -375,15 +352,15 @@ private static string GetPath(this Transform transform)
private static void SaveData()
{
string json = EditorJsonUtility.ToJson(SceneData);
EditorPrefs.SetString($"{Application.productName}{HierarchyDataStorageKey}", json);
EditorPrefs.SetString($"{Application.productName}{HIERARCHY_DATA_STORAGE_KEY}", json);
}

private static SceneData LoadOrCreateData()
{
SceneData instance = new SceneData();

string hierarchyDataJson =
EditorPrefs.GetString($"{Application.productName}{HierarchyDataStorageKey}", "");
EditorPrefs.GetString($"{Application.productName}{HIERARCHY_DATA_STORAGE_KEY}", "");
if (!string.IsNullOrEmpty(hierarchyDataJson))
{
instance = JsonUtility.FromJson<SceneData>(hierarchyDataJson);
Expand Down
104 changes: 104 additions & 0 deletions Scripts/Editor/UnityHierarchyTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System;
using System.Reflection;
using UnityEditor;

namespace BrunoMikoski.SceneHierarchyKeeper
{
public static class UnityHierarchyTools
{
private const string UNITY_EDITOR_SCENE_HIERARCHY_WINDOW_TYPE_NAME = "UnityEditor.SceneHierarchyWindow";
private const string EXPAND_TREE_VIEW_ITEM_METHOD_NAME = "ExpandTreeViewItem";
private const string GET_EXPANDED_I_DS_METHOD_NAME = "GetExpandedIDs";
private const string SCENE_HIERARCHY_PROPERTY_NAME = "sceneHierarchy";

private static Type cachedSceneHierarchyWindowType;
private static Type SceneHierarchyWindowType
{
get
{
if (cachedSceneHierarchyWindowType == null)
{
//https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/SceneHierarchyWindow.cs
cachedSceneHierarchyWindowType =
typeof(EditorWindow).Assembly.GetType(UNITY_EDITOR_SCENE_HIERARCHY_WINDOW_TYPE_NAME);
}

return cachedSceneHierarchyWindowType;
}
}

private static EditorWindow cachedHierarchyWindow;
internal static EditorWindow HierarchyWindow
{
get
{
if (cachedHierarchyWindow == null)
{
cachedHierarchyWindow = EditorWindow.GetWindow(SceneHierarchyWindowType);
}
return cachedHierarchyWindow;
}
}

private static MethodInfo cachedSetExpandedMethodInfo;
private static MethodInfo SetExpandedMethodInfo
{
get
{
if (cachedSetExpandedMethodInfo == null)
{
cachedSetExpandedMethodInfo = SceneHierarchyProperty.GetType().GetMethod(
EXPAND_TREE_VIEW_ITEM_METHOD_NAME, BindingFlags.Instance | BindingFlags.NonPublic);
}

return cachedSetExpandedMethodInfo;
}
}

private static object cachedSceneHierarchyProperty;
private static object SceneHierarchyProperty
{
get
{
if (cachedSceneHierarchyProperty == null)
{
cachedSceneHierarchyProperty = SceneHierarchyWindowType.GetProperty(SCENE_HIERARCHY_PROPERTY_NAME)
.GetValue(HierarchyWindow);
}

return cachedSceneHierarchyProperty;
}
}

private static MethodInfo cachedGetExpandedIDsMethodInfo;
private static MethodInfo GetExpandedIDsMethodInfo
{
get
{
if (cachedGetExpandedIDsMethodInfo == null)
{
cachedGetExpandedIDsMethodInfo = SceneHierarchyWindowType.GetMethod(GET_EXPANDED_I_DS_METHOD_NAME,
BindingFlags.NonPublic | BindingFlags.Instance);
}
return cachedGetExpandedIDsMethodInfo;
}
}



internal static void SetExpanded(int id, bool isExpanded)
{
SetExpandedMethodInfo.Invoke(SceneHierarchyProperty, new object[] {id, isExpanded});
}

public static bool IsHierarchyWindowOpen()
{
return HierarchyWindow != null;
}

public static int[] GetExpandedItems()
{
return (int[]) GetExpandedIDsMethodInfo.Invoke(HierarchyWindow, null);
}
}
}
3 changes: 3 additions & 0 deletions Scripts/Editor/UnityHierarchyTools.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 41c9c9b

Please sign in to comment.