A game about forced loneliness, made by TACStudios
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