unity/EditorPlugin/Protocol/UnsavedChangesModelHelper.cs (97 lines of code) (raw):
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using JetBrains.Diagnostics;
using JetBrains.Lifetimes;
using JetBrains.Rd.Tasks;
using JetBrains.Rider.Model.Unity.BackendUnity;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace JetBrains.Rider.Unity.Editor
{
internal static class UnsavedChangesModelHelper
{
private static readonly ILog ourLogger = Log.GetLog("Initialization");
public static void Advise(Lifetime modelLifetime, BackendUnityModel model)
{
AdviseOnHasUnsavedChanges(model);
}
private static void AdviseOnHasUnsavedChanges(BackendUnityModel model)
{
model.HasUnsavedState.Set(rdVoid =>
{
var count = SceneManager.sceneCount;
for (var i = 0; i < count; i++)
{
if (SceneManager.GetSceneAt(i).isDirty)
return true;
}
//Example of ScriptableObject which has its state, independent from the scenes
// Add this script to Assets
// Create an instance by `Assets > Create > ScriptableObjects > SpawnManagerScriptableObject`
// Change SerializableFields in the UnityEditor
/*
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnManagerScriptableObject", order = 1)]
public class SpawnManagerScriptableObject : ScriptableObject
{
public string prefabName;
public int numberOfPrefabsToCreate;
public Vector3[] spawnPoints;
}
*/
var hasDirtyUserAssets = Resources.FindObjectsOfTypeAll<ScriptableObject>()
.Any(a =>
{
if (a.hideFlags.HasFlag(HideFlags.DontSaveInEditor))
return false;
if (!IsDirty(a))
return false;
// I don't expect too many of those unsaved user Assets with attached ScriptableObject,
// so it feels safer to check them for having a real file on the disk
// to avoid false positives
var assetPath = AssetDatabase.GetAssetPath(a);
if (string.IsNullOrEmpty(assetPath))
return false;
return File.Exists(Path.GetFullPath(assetPath));
});
if (hasDirtyUserAssets)
return true;
return IsPrefabDirty();
});
}
private static bool IsPrefabDirty()
{
/* For testing:
1. create a prefab
2. open it for editing
3. uncheck "Auto Save"
4. make any change to the prefab
*/
// from 2018.3
// return UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage().scene.isDirty;
// from 2021.2
// return UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage().scene.isDirty;
// todo: test with Unity 7+
try
{
var T = Type.GetType("UnityEditor.SceneManagement.PrefabStageUtility,UnityEditor")
?? Type.GetType("UnityEditor.Experimental.SceneManagement.PrefabStageUtility,UnityEditor");
if (T == null)
{
ourLogger.Error(
"Types \"UnityEditor.SceneManagement.PrefabStageUtility,UnityEditor\" and \"UnityEditor.Experimental.SceneManagement.PrefabStageUtility,UnityEditor\" were not found.");
return false;
}
var getCurrentPrefabStageMethodInfo =
T.GetMethod("GetCurrentPrefabStage", BindingFlags.Public | BindingFlags.Static);
if (getCurrentPrefabStageMethodInfo == null)
{
ourLogger.Error("getCurrentPrefabStageMethodInfo method not found of type='{0}'", T);
return false;
}
var currentPrefabStage = getCurrentPrefabStageMethodInfo.Invoke(null, null);
if (currentPrefabStage == null) // there is no active prefab editing
return false;
var sceneProperty = currentPrefabStage.GetType().GetProperty("scene");
if (sceneProperty == null)
{
ourLogger.Error("'scene' prop not found in type '{0}'.", currentPrefabStage.GetType());
return false;
}
var sceneObject = sceneProperty.GetValue(currentPrefabStage, new object[] { });
var isDirtyProperty = sceneObject.GetType().GetProperty("isDirty");
if (isDirtyProperty == null)
{
ourLogger.Error("isDirty prop not found in type '{0}'.", sceneProperty.GetType());
return false;
}
var isDirty = (bool)isDirtyProperty.GetValue(sceneObject, new object[] { });
return isDirty;
}
catch (Exception e)
{
ourLogger.Error(e);
}
return false;
}
private static bool IsDirty(UnityEngine.Object unityObject)
{
return EditorUtility.IsDirty(unityObject);
}
}
}