#if UNITY_EDITOR
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using UnityEditor;
////TODO: ensure that GUIDs in the asset are unique
namespace UnityEngine.InputSystem.Editor
{
///
/// Keeps a reference to the asset being edited and maintains a copy of the asset object
/// around for editing.
///
[Serializable]
internal class InputActionAssetManager : IDisposable
{
[SerializeField] private InputActionAsset m_AssetObjectForEditing;
[SerializeField] private InputActionAsset m_ImportedAssetObject;
[SerializeField] private string m_AssetGUID;
[SerializeField] private string m_ImportedAssetJson;
[SerializeField] private bool m_IsDirty;
private SerializedObject m_SerializedObject;
///
/// Returns the Asset GUID uniquely identifying the associated imported asset.
///
public string guid => m_AssetGUID;
///
/// Returns the current Asset Path for the associated imported asset.
/// If the asset have been deleted this will be null.
///
public string path
{
get
{
Debug.Assert(!string.IsNullOrEmpty(m_AssetGUID), "Asset GUID is empty");
return AssetDatabase.GUIDToAssetPath(m_AssetGUID);
}
}
///
/// Returns the name of the associated imported asset.
///
public string name
{
get
{
var asset = importedAsset;
if (asset != null)
return asset.name;
if (!string.IsNullOrEmpty(path))
return Path.GetFileNameWithoutExtension(path);
return string.Empty;
}
}
private InputActionAsset importedAsset
{
get
{
// Note that this may be null after deserialization from domain reload
if (m_ImportedAssetObject == null)
LoadImportedObjectFromGuid();
return m_ImportedAssetObject;
}
}
public InputActionAsset editedAsset => m_AssetObjectForEditing; // TODO Remove if redundant
public Action onDirtyChanged { get; set; }
public InputActionAssetManager(InputActionAsset inputActionAsset)
{
if (inputActionAsset == null)
throw new NullReferenceException(nameof(inputActionAsset));
m_AssetGUID = EditorHelpers.GetAssetGUID(inputActionAsset);
if (m_AssetGUID == null)
throw new Exception($"Failed to get asset {inputActionAsset.name} GUID");
m_ImportedAssetObject = inputActionAsset;
Initialize();
}
public SerializedObject serializedObject => m_SerializedObject;
public bool dirty => m_IsDirty;
public bool Initialize()
{
if (m_AssetObjectForEditing == null)
{
if (importedAsset == null)
{
// The asset we want to edit no longer exists.
return false;
}
CreateWorkingCopyAsset();
}
else
{
m_SerializedObject = new SerializedObject(m_AssetObjectForEditing);
}
return true;
}
public void Dispose()
{
if (m_SerializedObject == null)
return;
m_SerializedObject?.Dispose();
m_SerializedObject = null;
}
public bool ReInitializeIfAssetHasChanged()
{
var json = importedAsset.ToJson();
if (m_ImportedAssetJson == json)
return false;
CreateWorkingCopyAsset();
return true;
}
public static InputActionAsset CreateWorkingCopy(InputActionAsset source)
{
var copy = Object.Instantiate(source);
copy.hideFlags = HideFlags.HideAndDontSave;
copy.name = source.name;
return copy;
}
public static void CreateWorkingCopyAsset(ref InputActionAsset copy, InputActionAsset source)
{
if (copy != null)
Cleanup(ref copy);
copy = CreateWorkingCopy(source);
}
private void CreateWorkingCopyAsset() // TODO Can likely be removed if combined with Initialize
{
if (m_AssetObjectForEditing != null)
Cleanup();
// Duplicate the asset along 1:1. Unlike calling Clone(), this will also preserve GUIDs.
var asset = importedAsset;
m_AssetObjectForEditing = CreateWorkingCopy(asset);
m_ImportedAssetJson = asset.ToJson();
m_SerializedObject = new SerializedObject(m_AssetObjectForEditing);
}
public void Cleanup()
{
Cleanup(ref m_AssetObjectForEditing);
}
public static void Cleanup(ref InputActionAsset asset)
{
if (asset == null)
return;
Object.DestroyImmediate(asset);
asset = null;
}
private void LoadImportedObjectFromGuid()
{
// https://fogbugz.unity3d.com/f/cases/1313185/
// InputActionEditorWindow being an EditorWindow, it will be saved as part of the editor's
// window layout. When a project is opened that has no Library/ folder, the layout from the
// most recently opened project is used. Which means that when opening an .inputactions
// asset in project A, then closing it, and then opening project B, restoring the window layout
// also tries to restore the InputActionEditorWindow having that very same asset open -- which
// will lead nowhere except there happens to be an InputActionAsset with the very same GUID in
// the project.
var assetPath = path;
if (!string.IsNullOrEmpty(assetPath))
m_ImportedAssetObject = AssetDatabase.LoadAssetAtPath(assetPath);
}
public void ApplyChanges()
{
m_SerializedObject.ApplyModifiedProperties();
m_SerializedObject.Update();
}
internal void SaveChangesToAsset()
{
// If this is invoked after a domain reload, importAsset will resolve itself.
// However, if the asset do not exist importedAsset will be null and we cannot complete the operation.
if (importedAsset == null)
throw new Exception("Unable to save changes. Associated asset does not exist.");
SaveAsset(path, m_AssetObjectForEditing.ToJson());
SetDirty(false);
}
///
/// Saves an asset to the given assetPath with file content corresponding to assetJson
/// if the current content of the asset given by assetPath is different or the asset do not exist.
///
/// Destination asset path.
/// The JSON file content to be written to the asset.
/// true if the asset was successfully modified or created, else false.
internal static bool SaveAsset(string assetPath, string assetJson)
{
var existingJson = File.Exists(assetPath) ? File.ReadAllText(assetPath) : string.Empty;
// Return immediately if file content has not changed, i.e. touching the file would not yield a difference.
if (assetJson == existingJson)
return false;
// Attempt to write asset to disc (including checkout the file) and inform the user if this fails.
if (EditorHelpers.WriteAsset(assetPath, assetJson))
return true;
Debug.LogError($"Unable save asset to \"{assetPath}\" since the asset-path could not be checked-out as editable in the underlying version-control system.");
return false;
}
public void MarkDirty()
{
SetDirty(true);
}
public void UpdateAssetDirtyState()
{
m_SerializedObject.Update();
SetDirty(m_AssetObjectForEditing.ToJson() != importedAsset.ToJson()); // TODO Why not using cached version?
}
private void SetDirty(bool newValue)
{
m_IsDirty = newValue;
if (onDirtyChanged != null)
onDirtyChanged(newValue);
}
}
}
#endif // UNITY_EDITOR