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