No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InputActionAssetManager.cs 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. #if UNITY_EDITOR
  2. using System;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.IO;
  5. using UnityEditor;
  6. ////TODO: ensure that GUIDs in the asset are unique
  7. namespace UnityEngine.InputSystem.Editor
  8. {
  9. /// <summary>
  10. /// Keeps a reference to the asset being edited and maintains a copy of the asset object
  11. /// around for editing.
  12. /// </summary>
  13. [Serializable]
  14. internal class InputActionAssetManager : IDisposable
  15. {
  16. [SerializeField] private InputActionAsset m_AssetObjectForEditing;
  17. [SerializeField] private InputActionAsset m_ImportedAssetObject;
  18. [SerializeField] private string m_AssetGUID;
  19. [SerializeField] private string m_ImportedAssetJson;
  20. [SerializeField] private bool m_IsDirty;
  21. private SerializedObject m_SerializedObject;
  22. /// <summary>
  23. /// Returns the Asset GUID uniquely identifying the associated imported asset.
  24. /// </summary>
  25. public string guid => m_AssetGUID;
  26. /// <summary>
  27. /// Returns the current Asset Path for the associated imported asset.
  28. /// If the asset have been deleted this will be <c>null</c>.
  29. /// </summary>
  30. public string path
  31. {
  32. get
  33. {
  34. Debug.Assert(!string.IsNullOrEmpty(m_AssetGUID), "Asset GUID is empty");
  35. return AssetDatabase.GUIDToAssetPath(m_AssetGUID);
  36. }
  37. }
  38. /// <summary>
  39. /// Returns the name of the associated imported asset.
  40. /// </summary>
  41. public string name
  42. {
  43. get
  44. {
  45. var asset = importedAsset;
  46. if (asset != null)
  47. return asset.name;
  48. if (!string.IsNullOrEmpty(path))
  49. return Path.GetFileNameWithoutExtension(path);
  50. return string.Empty;
  51. }
  52. }
  53. private InputActionAsset importedAsset
  54. {
  55. get
  56. {
  57. // Note that this may be null after deserialization from domain reload
  58. if (m_ImportedAssetObject == null)
  59. LoadImportedObjectFromGuid();
  60. return m_ImportedAssetObject;
  61. }
  62. }
  63. public InputActionAsset editedAsset => m_AssetObjectForEditing; // TODO Remove if redundant
  64. public Action<bool> onDirtyChanged { get; set; }
  65. public InputActionAssetManager(InputActionAsset inputActionAsset)
  66. {
  67. if (inputActionAsset == null)
  68. throw new NullReferenceException(nameof(inputActionAsset));
  69. m_AssetGUID = EditorHelpers.GetAssetGUID(inputActionAsset);
  70. if (m_AssetGUID == null)
  71. throw new Exception($"Failed to get asset {inputActionAsset.name} GUID");
  72. m_ImportedAssetObject = inputActionAsset;
  73. Initialize();
  74. }
  75. public SerializedObject serializedObject => m_SerializedObject;
  76. public bool dirty => m_IsDirty;
  77. public bool Initialize()
  78. {
  79. if (m_AssetObjectForEditing == null)
  80. {
  81. if (importedAsset == null)
  82. {
  83. // The asset we want to edit no longer exists.
  84. return false;
  85. }
  86. CreateWorkingCopyAsset();
  87. }
  88. else
  89. {
  90. m_SerializedObject = new SerializedObject(m_AssetObjectForEditing);
  91. }
  92. return true;
  93. }
  94. public void Dispose()
  95. {
  96. if (m_SerializedObject == null)
  97. return;
  98. m_SerializedObject?.Dispose();
  99. m_SerializedObject = null;
  100. }
  101. public bool ReInitializeIfAssetHasChanged()
  102. {
  103. var json = importedAsset.ToJson();
  104. if (m_ImportedAssetJson == json)
  105. return false;
  106. CreateWorkingCopyAsset();
  107. return true;
  108. }
  109. public static InputActionAsset CreateWorkingCopy(InputActionAsset source)
  110. {
  111. var copy = Object.Instantiate(source);
  112. copy.hideFlags = HideFlags.HideAndDontSave;
  113. copy.name = source.name;
  114. return copy;
  115. }
  116. public static void CreateWorkingCopyAsset(ref InputActionAsset copy, InputActionAsset source)
  117. {
  118. if (copy != null)
  119. Cleanup(ref copy);
  120. copy = CreateWorkingCopy(source);
  121. }
  122. private void CreateWorkingCopyAsset() // TODO Can likely be removed if combined with Initialize
  123. {
  124. if (m_AssetObjectForEditing != null)
  125. Cleanup();
  126. // Duplicate the asset along 1:1. Unlike calling Clone(), this will also preserve GUIDs.
  127. var asset = importedAsset;
  128. m_AssetObjectForEditing = CreateWorkingCopy(asset);
  129. m_ImportedAssetJson = asset.ToJson();
  130. m_SerializedObject = new SerializedObject(m_AssetObjectForEditing);
  131. }
  132. public void Cleanup()
  133. {
  134. Cleanup(ref m_AssetObjectForEditing);
  135. }
  136. public static void Cleanup(ref InputActionAsset asset)
  137. {
  138. if (asset == null)
  139. return;
  140. Object.DestroyImmediate(asset);
  141. asset = null;
  142. }
  143. private void LoadImportedObjectFromGuid()
  144. {
  145. // https://fogbugz.unity3d.com/f/cases/1313185/
  146. // InputActionEditorWindow being an EditorWindow, it will be saved as part of the editor's
  147. // window layout. When a project is opened that has no Library/ folder, the layout from the
  148. // most recently opened project is used. Which means that when opening an .inputactions
  149. // asset in project A, then closing it, and then opening project B, restoring the window layout
  150. // also tries to restore the InputActionEditorWindow having that very same asset open -- which
  151. // will lead nowhere except there happens to be an InputActionAsset with the very same GUID in
  152. // the project.
  153. var assetPath = path;
  154. if (!string.IsNullOrEmpty(assetPath))
  155. m_ImportedAssetObject = AssetDatabase.LoadAssetAtPath<InputActionAsset>(assetPath);
  156. }
  157. public void ApplyChanges()
  158. {
  159. m_SerializedObject.ApplyModifiedProperties();
  160. m_SerializedObject.Update();
  161. }
  162. internal void SaveChangesToAsset()
  163. {
  164. // If this is invoked after a domain reload, importAsset will resolve itself.
  165. // However, if the asset do not exist importedAsset will be null and we cannot complete the operation.
  166. if (importedAsset == null)
  167. throw new Exception("Unable to save changes. Associated asset does not exist.");
  168. SaveAsset(path, m_AssetObjectForEditing.ToJson());
  169. SetDirty(false);
  170. }
  171. /// <summary>
  172. /// Saves an asset to the given <c>assetPath</c> with file content corresponding to <c>assetJson</c>
  173. /// if the current content of the asset given by <c>assetPath</c> is different or the asset do not exist.
  174. /// </summary>
  175. /// <param name="assetPath">Destination asset path.</param>
  176. /// <param name="assetJson">The JSON file content to be written to the asset.</param>
  177. /// <returns><c>true</c> if the asset was successfully modified or created, else <c>false</c>.</returns>
  178. internal static bool SaveAsset(string assetPath, string assetJson)
  179. {
  180. var existingJson = File.Exists(assetPath) ? File.ReadAllText(assetPath) : string.Empty;
  181. // Return immediately if file content has not changed, i.e. touching the file would not yield a difference.
  182. if (assetJson == existingJson)
  183. return false;
  184. // Attempt to write asset to disc (including checkout the file) and inform the user if this fails.
  185. if (EditorHelpers.WriteAsset(assetPath, assetJson))
  186. return true;
  187. Debug.LogError($"Unable save asset to \"{assetPath}\" since the asset-path could not be checked-out as editable in the underlying version-control system.");
  188. return false;
  189. }
  190. public void MarkDirty()
  191. {
  192. SetDirty(true);
  193. }
  194. public void UpdateAssetDirtyState()
  195. {
  196. m_SerializedObject.Update();
  197. SetDirty(m_AssetObjectForEditing.ToJson() != importedAsset.ToJson()); // TODO Why not using cached version?
  198. }
  199. private void SetDirty(bool newValue)
  200. {
  201. m_IsDirty = newValue;
  202. if (onDirtyChanged != null)
  203. onDirtyChanged(newValue);
  204. }
  205. }
  206. }
  207. #endif // UNITY_EDITOR