From 79a2f15dbca6b281e5bb3956ef2d2c5fbad44962 Mon Sep 17 00:00:00 2001 From: sator-imaging <16752340+sator-imaging@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:48:19 +0900 Subject: [PATCH] feat: AssemblyDefinitionSettingsProvider --- .../AssemblyDefinitionSettingsProvider.meta | 8 + ...ider.ImplicitAssemblyReferenceProcessor.cs | 207 ++++++++ ...ImplicitAssemblyReferenceProcessor.cs.meta | 11 + ...ssemblyDefinitionSettingsProvider.Prefs.cs | 72 +++ ...lyDefinitionSettingsProvider.Prefs.cs.meta | 11 + .../AssemblyDefinitionSettingsProvider.cs | 484 ++++++++++++++++++ ...AssemblyDefinitionSettingsProvider.cs.meta | 11 + 7 files changed, 804 insertions(+) create mode 100644 Editor/AssemblyDefinitionSettingsProvider.meta create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs.meta create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs.meta create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs.meta diff --git a/Editor/AssemblyDefinitionSettingsProvider.meta b/Editor/AssemblyDefinitionSettingsProvider.meta new file mode 100644 index 0000000..6eef396 --- /dev/null +++ b/Editor/AssemblyDefinitionSettingsProvider.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7d70236340c3ef24d88290e9ef6ac59c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs new file mode 100644 index 0000000..3724a27 --- /dev/null +++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs @@ -0,0 +1,207 @@ +/** Assembly Definitions Manager for Project Settings Panel + ** (c) 2024 https://github.com/sator-imaging + ** Licensed under the MIT License + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +#nullable enable + +namespace SatorImaging.UnityFundamentals.Editor +{ + partial class AssemblyDefinitionSettingsProvider + { + public sealed class ImplicitAssemblyReferenceProcessor : AssetPostprocessor + { + const string LOG_PREFIX = "[" + nameof(ImplicitAssemblyReferenceProcessor) + "] "; + + + /// Get or set processing order. Smaller value to be invoked earlier. + public int PostprocessOrder { get; set; } = 0; + + public override int GetPostprocessOrder() => PostprocessOrder; + + + /* preprocess ================================================================ */ + + // be static. not sure whether processing is done by one instance or multiple instances + readonly static HashSet _processedFilePathSet = new(); + + public void OnPreprocessAsset() + { + if (assetImporter is not AssemblyDefinitionImporter) + return; + + if (!Prefs.Instance.EnableImplicitRefsOnChanges) + return; + + // avoid unexpected loop + if (!_processedFilePathSet.Add(assetPath)) + { + //Debug.Log(LOG_PREFIX + "already processed: " + assetPath); + return; + } + + ResetIntegratedInspector(); + LockIntegratedInspector(true); + if (!TryAddImplicitReferences(assetPath)) + { + // don't require. domain reloading will happen by asset import --> LockIntegratedInspector(false); + } + } + + + /* internals ================================================================ */ + + static StringBuilder? cache_sb; + + public static bool TryAddImplicitReferences(string filePath) + { + if (!File.Exists(filePath)) + return false; + + if (!filePath.StartsWith(ASSETS_DIR_SLASH)) + return false; + + var jsonText = File.ReadAllText(filePath, Encoding.UTF8); + var jsonObj = JsonUtility.FromJson(jsonText); + var name = jsonObj?.name + ?? throw new NotSupportedException("no assembly name: " + filePath); + var prefs = Prefs.Instance; + + // don't modify implicit refs source + if (!(prefs.ImplicitReferenceNames?.Length > 0) || prefs.ImplicitReferenceNames.Contains(name, StringComparer.OrdinalIgnoreCase)) + return false; + + bool isGuidMode = true == jsonObj.references?.FirstOrDefault()?.StartsWith(GUID_MODE_PREFIX, StringComparison.OrdinalIgnoreCase); + var implicitRefs = (isGuidMode ? prefs.ImplicitReferenceGUIDs : prefs.ImplicitReferenceNames).AsEnumerable(); + + if (jsonObj.references != null) + { + implicitRefs = implicitRefs + .Where(x => !jsonObj.references.Contains(x, StringComparer.OrdinalIgnoreCase)) // ignore case for safe + ; + } + + if (!implicitRefs.Any()) + { + return false; + } + + SPECIAL_CASE_FOR_NEWLY_CREATED_FILE: + var pos_references = jsonText.AsSpan().IndexOf(JSON_REFS_INSERT_POSITION_FINDER, StringComparison.Ordinal); + if (pos_references < 0) + { + // NOTE: newly created .asmdef has only "name" property. ie. has no ',' + if (jsonText.Contains(',')) + { + Debug.LogWarning(LOG_PREFIX + "no references property: " + jsonText); + return false; + } + + jsonText = jsonText.AsSpan().TrimEnd()[..^1].TrimEnd().ToString().Replace("\t", JSON_INDENT) + + "," + Environment.NewLine + JSON_REFS_INSERT_POSITION_FINDER + "]" + Environment.NewLine + '}'; + goto SPECIAL_CASE_FOR_NEWLY_CREATED_FILE; + } + + pos_references += JSON_REFS_INSERT_POSITION_FINDER.Length; + + var sb = (cache_sb ??= new()); + sb.Append(jsonText.AsSpan(0, pos_references)); + sb.Append(JSON_REFS_ARRAY_ITEM_OPEN.AsSpan()); + sb.AppendJoin(JSON_REFS_ARRAY_ITEM_CLOSE, implicitRefs); + sb.Append('"'); // don't forget closing quote. opening quote is included in ITEM OPEN/CLOSE + if (jsonText[pos_references] != ']') + { + if (jsonObj.references?.Length > 0) + sb.Append(','); + } + else + { + sb.AppendLine(); + sb.Append(JSON_INDENT.AsSpan()); + } + sb.Append(jsonText.AsSpan(pos_references)); + + var result = sb.ToString(); + sb.Length = 0; // don't clear to keep allocated buffer! + + File.WriteAllText(filePath, result, Encoding.UTF8); + //AssetDatabase.ImportAsset(filePath); + + Debug.Log(LOG_PREFIX + filePath + "...\n" + result); + + return true; + } + + + readonly static Regex re_emptyRef = new(@"^\s+"""",?[\r\n]+", RegexOptions.Compiled | RegexOptions.Multiline); + + public static bool TryRemoveImplicitReferences(string filePath) + { + if (!File.Exists(filePath)) + return false; + + if (!filePath.StartsWith(ASSETS_DIR_SLASH)) + return false; + + // try remove both guid and name + var implicitRefs = Prefs.Instance.ImplicitReferenceGUIDs?.Concat(Prefs.Instance.ImplicitReferenceNames); + if (implicitRefs == null) + return false; + + var contentSpan = File.ReadAllText(filePath, Encoding.UTF8).ToCharArray().AsSpan(); + var originalLength = contentSpan.Length; + + // NOTE: remove only references entries + int pos_references = ((ReadOnlySpan)contentSpan).IndexOf(JSON_REFS_INSERT_POSITION_FINDER, StringComparison.Ordinal); + if (pos_references < 0) + return false; + + int len_references = contentSpan.Slice(pos_references).IndexOf(']'); + if (len_references < 0) + return false; + + var consumed = contentSpan.Length; + int pos; + foreach (var item in implicitRefs) + { + // not sure why implicit cast operator doesn't work + while ((pos = ((ReadOnlySpan)contentSpan).Slice(pos_references, len_references).IndexOf(item, StringComparison.Ordinal)) >= 0) + { + pos += pos_references; + contentSpan.Slice(pos + item.Length).CopyTo(contentSpan.Slice(pos)); + + consumed -= item.Length; + len_references -= item.Length; + } + } + + if (originalLength == consumed) + { + return false; + } + + var result = contentSpan.Slice(0, consumed).ToString(); + result = re_emptyRef.Replace(result, string.Empty); + + File.WriteAllText(filePath, result, Encoding.UTF8); + //AssetDatabase.ImportAsset(filePath); + + Debug.Log(LOG_PREFIX + filePath + "...\n" + result); + + return true; + } + + } + + } +} diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs.meta b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs.meta new file mode 100644 index 0000000..842a8d1 --- /dev/null +++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba90ea4ba17e8fd458fa605554740f23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs new file mode 100644 index 0000000..9d0a6f9 --- /dev/null +++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs @@ -0,0 +1,72 @@ +/** Assembly Definitions Manager for Project Settings Panel + ** (c) 2024 https://github.com/sator-imaging + ** Licensed under the MIT License + */ + +using System; +using System.IO; +using System.Text; +using System.Threading; +using UnityEngine; + +#nullable enable + +namespace SatorImaging.UnityFundamentals.Editor +{ + partial class AssemblyDefinitionSettingsProvider + { + [Serializable] + public sealed class Prefs + { + // json fields + [SerializeField] bool enableImplicitRefsOnChanges = false; + [SerializeField] bool turnOnImplicitRefsOnChangesAfterDomainReloading = false; + [SerializeField] string[]? implicitReferenceGUIDs; + [SerializeField] string[]? implicitReferenceNames; + + //properties + /// + /// + public string[]? ImplicitReferenceNames + { + get { return implicitReferenceNames; } + set { implicitReferenceNames = value; } + } + + public string[]? ImplicitReferenceGUIDs + { + get { return implicitReferenceGUIDs; } + set { implicitReferenceGUIDs = value; } + } + + public bool EnableImplicitRefsOnChanges + { + get { return enableImplicitRefsOnChanges; } + set { enableImplicitRefsOnChanges = value; } + } + + public bool TurnOnImplicitRefsOnChangesAfterDomainReloading + { + get { return turnOnImplicitRefsOnChangesAfterDomainReloading; } + set { turnOnImplicitRefsOnChangesAfterDomainReloading = value; } + } + + + // export path + readonly static string OUTPUT_PATH = Application.dataPath + + "/../ProjectSettings/" + nameof(AssemblyDefinitionSettingsProvider) + ".json"; + + private Prefs() + { + if (File.Exists(OUTPUT_PATH)) + JsonUtility.FromJsonOverwrite(File.ReadAllText(OUTPUT_PATH, Encoding.UTF8), this); + } + + volatile static Prefs? _instance; + public static Prefs Instance => _instance ?? Interlocked.CompareExchange(ref _instance, new(), null) ?? _instance; + + public void Save() => File.WriteAllText(OUTPUT_PATH, JsonUtility.ToJson(this, true), Encoding.UTF8); + } + + } +} diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs.meta b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs.meta new file mode 100644 index 0000000..96ef25e --- /dev/null +++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a6149383e9b74d4984d7c58b24537c9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs new file mode 100644 index 0000000..86f958f --- /dev/null +++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs @@ -0,0 +1,484 @@ +/** Assembly Definitions Manager for Project Settings Panel + ** (c) 2024 https://github.com/sator-imaging + ** Licensed under the MIT License + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEditor.AssetImporters; +using UnityEditor.Compilation; +using UnityEditorInternal; +using UnityEngine; +using UnityEngine.UIElements; + +#nullable enable + +namespace SatorImaging.UnityFundamentals.Editor +{ + public sealed partial class AssemblyDefinitionSettingsProvider : SettingsProvider + { + const string LOG_PREFIX = "[" + nameof(AssemblyDefinitionSettingsProvider) + "] "; + + readonly static string DISPLAY_NAME = "Assembly Definitions"; + + readonly static string GUID_MODE_PREFIX = "GUID:"; + readonly static string ASSETS_DIR_SLASH = "Assets/"; + //readonly static string PACKAGES_DIR_SLASH = "Packages/"; + readonly static string JSON_INDENT = @" "; + readonly static string JSON_REFS_INSERT_POSITION_FINDER = JSON_INDENT + @"""references"": ["; + readonly static string JSON_REFS_ARRAY_ITEM_OPEN = Environment.NewLine + JSON_INDENT + JSON_INDENT + "\""; + readonly static string JSON_REFS_ARRAY_ITEM_CLOSE = "\"," + JSON_REFS_ARRAY_ITEM_OPEN; + + // json representation + [Serializable] sealed class AssemDef_JsonLoader { public string? name; public string[]? references; } + + + public AssemblyDefinitionSettingsProvider(string path, SettingsScope scopes, IEnumerable? keywords = null) + : base(path, scopes, keywords) { } + + [SettingsProvider] + public static SettingsProvider CreateProvider() + { + return new AssemblyDefinitionSettingsProvider("Project/" + DISPLAY_NAME, SettingsScope.Project, null); + } + + + /* activate/deactivate ================================================================ */ + + readonly List _implicitADefList = new(); + + [DescriptionAttribute("tuple: (assetPath, fileNameNoExt)")] + readonly List<(string assetPath, string fileNameNoExt)> _assetsADefInfoList = new(); + readonly List _assetsADefLabelList = new(); + + public override void OnDeactivate() + { + base.OnDeactivate(); + + //Debug.Log(LOG_PREFIX + nameof(OnDeactivate)); + Prefs.Instance.Save(); + } + + + public override void OnActivate(string searchContext, VisualElement rootElement) + { + base.OnActivate(searchContext, rootElement); + + if (_implicitADefList.Count == 0) + { + var prefs = Prefs.Instance; // to load data + + var implicitRefAssets = prefs.ImplicitReferenceGUIDs + .Select(static x => AssetDatabase.GUIDToAssetPath(x.Substring(GUID_MODE_PREFIX.Length))) + .Select(static x => AssetDatabase.LoadAssetAtPath(x)) + .Where(static x => x != null) + ; + + _implicitADefList.AddRange(implicitRefAssets); + } + + RefreshAssetsADefCaches(); + } + + + void RefreshAssetsADefCaches() + { + var assetsInAssetsFolder = AssetDatabase.FindAssets("t:" + nameof(AssemblyDefinitionAsset)) + .Select(static guid => AssetDatabase.GUIDToAssetPath(guid)) + .Where(static path => path.StartsWith(ASSETS_DIR_SLASH)) + .Select(static path => + { + var fileNameNoExt = Path.GetFileNameWithoutExtension(path); + return (path, fileNameNoExt); + }) + ; + + _assetsADefInfoList.Clear(); + _assetsADefInfoList.AddRange(assetsInAssetsFolder); + + _assetsADefLabelList.Clear(); + _assetsADefLabelList.AddRange(assetsInAssetsFolder.Select(static x => new GUIContent(x.fileNameNoExt, x.path))); + } + + + /* OnGUI ================================================================ */ + + readonly GUIContent gui_implicitRefsLabel = new("Implicit Assembly References"); + readonly GUIContent gui_enableImplicitRefsLabel = new("Auto Update .asmdef Files on Import Event"); + readonly GUIContent gui_updateBtnLabel = new("Update All .asmdef *", "Only files in `Assets/` folder"); + readonly GUIContent gui_removeBtnLabel = new("Remove from All"); + readonly GUIContent gui_assetsADefListLabel + = new("Assembly Definitions in Assets *", "Click on selected list item will unveil .asmdef file in Project panel"); + readonly GUIContent gui_updateADefListLabel = new("Reload List"); + readonly GUIContent gui_dialogHelpText = new("[Integrated Inspector] *experimental"); + //readonly Texture gui_searchIcon = EditorGUIUtility.IconContent("d_Search Icon").image; + ReorderableList? gui_implicitRefsReorderableList; + GUIStyle? style_largeBtn; + GUIStyle? style_linkBtn; + GUIStyle? style_activeLinkBtn; + GUIStyle? style_sectionHeaderLabel; + Vector2 gui_assetsADefScrollPos; + Vector2 gui_miniInspectorScrollPos; + + // fields must be reset if selection changed + static UnityEditor.Editor? gui_cachedEditor; + static string? _activeADefAssetPath; + static bool _asmdefHasModified_last = false; + static bool _asmdefHasModified_changed = false; + + static bool _modifingAssetFiles = false; + + + public override void OnGUI(string searchContext) + { + const int MAX_PANEL_WIDTH = 400; + + base.OnGUI(searchContext); + + if (style_largeBtn == null) + { + style_largeBtn = new(EditorStyles.miniButton); + style_largeBtn.fontSize = 14; + style_largeBtn.richText = true; + style_largeBtn.fixedHeight = 28; + style_largeBtn.padding = new(16, 16, 0, 0); + } + + if (style_linkBtn == null) + { + style_linkBtn = new(EditorStyles.linkLabel); + style_linkBtn.stretchWidth = true; + style_linkBtn.fontSize = 13; + style_linkBtn.fixedHeight = 22; + } + + if (style_activeLinkBtn == null) + { + style_activeLinkBtn = new(EditorStyles.selectionRect); + style_activeLinkBtn.stretchWidth = style_linkBtn.stretchWidth; + style_activeLinkBtn.fontSize = style_linkBtn.fontSize; + style_activeLinkBtn.fixedHeight = style_linkBtn.fixedHeight; + style_activeLinkBtn.margin = new(0, 0, 0, 0); + style_activeLinkBtn.padding.left = 6; + } + + if (style_sectionHeaderLabel == null) + { + style_sectionHeaderLabel = new(EditorStyles.largeLabel); + style_sectionHeaderLabel.fontSize = 15; + style_sectionHeaderLabel.fixedHeight = 32; + style_sectionHeaderLabel.stretchWidth = true; + } + + if (gui_implicitRefsReorderableList == null) + { + gui_implicitRefsReorderableList = new(_implicitADefList, typeof(AssemblyDefinitionAsset), true, false, true, true); + gui_implicitRefsReorderableList.drawElementCallback += (rect, index, isActive, isFocused) => + { + _implicitADefList[index] = EditorGUI.ObjectField(rect, _implicitADefList[index], typeof(AssemblyDefinitionAsset), false) + as AssemblyDefinitionAsset; + }; + gui_implicitRefsReorderableList.onAddCallback = self => + { + self.list.Add(null); + }; + } + + + // start!! + var leftPanelWidth = GUILayout.Width(Math.Min(MAX_PANEL_WIDTH, EditorGUIUtility.currentViewWidth * 0.333f)); + + using var rootLayout = new EditorGUILayout.HorizontalScope(); + + // left side panel + using (new EditorGUILayout.VerticalScope(leftPanelWidth)) + { + EditorGUILayout.Space(); + + EditorGUILayout.LabelField(gui_implicitRefsLabel, style_sectionHeaderLabel); + + // TODO: add "apply all changes" button to allow changing multiple .asmdef files at once. + bool disallowChangeSelection = false; + if (gui_cachedEditor is AssetImporterEditor currentEditor) + { + var hasModified = currentEditor.HasModified(); + _asmdefHasModified_changed = hasModified != _asmdefHasModified_last; + _asmdefHasModified_last = hasModified; + + disallowChangeSelection = hasModified; + } + + using (new EditorGUI.DisabledScope(disallowChangeSelection || _modifingAssetFiles)) + { + // implicit refs list + using (var cc = new EditorGUI.ChangeCheckScope()) + { + gui_implicitRefsReorderableList.DoLayoutList(); + + if (cc.changed) + { + var snapshot = _implicitADefList + .Where(static x => x != null) + .Select(static x => + { + (string name, string guid) result = (string.Empty, string.Empty); + + if (x != null && !string.IsNullOrWhiteSpace(x.text)) + { + if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(x, out var guid, out long id)) + { + // NOTE: x.name is FILE NAME, reference requires JSON's NAME + var jsonObj = JsonUtility.FromJson(x.text); + + result.name = jsonObj?.name ?? string.Empty; + result.guid = GUID_MODE_PREFIX + guid; + + //Debug.Log(LOG_PREFIX + result.guid + " / " + result.name); + } + } + + return result; + }) + .Where(static x => x.name.Length > 0 && x.guid.Length > 0) + .Distinct() + ; + + Prefs.Instance.ImplicitReferenceGUIDs = snapshot.Select(static x => x.guid).ToArray(); + Prefs.Instance.ImplicitReferenceNames = snapshot.Select(static x => x.name).ToArray(); + } + } + + + EditorGUILayout.Space(-EditorGUIUtility.singleLineHeight * 1.0f); + + using (new GUILayout.HorizontalScope()) + { + Prefs.Instance.EnableImplicitRefsOnChanges + = EditorGUILayout.ToggleLeft(gui_enableImplicitRefsLabel, Prefs.Instance.EnableImplicitRefsOnChanges, EditorStyles.largeLabel); + + EditorGUILayout.Space(); + } + + EditorGUILayout.Space(); + + + /* = add/remove buttons = */ + using (new EditorGUILayout.HorizontalScope()) + { + if (GUILayout.Button(gui_updateBtnLabel, style_largeBtn)) + { + LockAndResetIntegratedInspectorThenUpdateAssets(ImplicitAssemblyReferenceProcessor.TryAddImplicitReferences); + } + + if (GUILayout.Button(gui_removeBtnLabel, style_largeBtn)) + { + LockAndResetIntegratedInspectorThenUpdateAssets(ImplicitAssemblyReferenceProcessor.TryRemoveImplicitReferences); + } + + GUILayout.FlexibleSpace(); + } + + + /* = assets ADef list = */ + + EditorGUILayout.Space(); + + using (new GUILayout.HorizontalScope()) + { + EditorGUILayout.LabelField(gui_assetsADefListLabel, style_sectionHeaderLabel); + + if (GUILayout.Button(gui_updateADefListLabel)) + { + RefreshAssetsADefCaches(); + } + } + + using (var sv_assetsADef = new GUILayout.ScrollViewScope(gui_assetsADefScrollPos)) + { + gui_assetsADefScrollPos = sv_assetsADef.scrollPosition; + + for (int i = 0; i < _assetsADefLabelList.Count; i++) + { + var guiLabel = _assetsADefLabelList[i]; + var fileNameNoExt = guiLabel.text; + var assetPath = guiLabel.tooltip; + + var btnStyle = assetPath == _activeADefAssetPath ? style_activeLinkBtn : style_linkBtn; + if (GUILayout.Button(guiLabel, btnStyle)) + { + if (_activeADefAssetPath == assetPath) + { + var asset = AssetDatabase.LoadAssetAtPath(assetPath); + EditorGUIUtility.PingObject(asset); + } + else + { + var importer = AssetImporter.GetAtPath(assetPath) as AssemblyDefinitionImporter; + if (importer != null) + { + ResetIntegratedInspector(); + _activeADefAssetPath = assetPath; // must be set after Reset + + UnityEditor.Editor.CreateCachedEditor(importer, null, ref gui_cachedEditor); + + if (gui_cachedEditor is AssetImporterEditor) + { + // required to show Apply/Revert button, when not, any changes will be automatically applied and cause domain reloading + //https://github.com/Unity-Technologies/UnityCsReference/blob/master/Modules/AssetPipelineEditor/ImportSettings/AssetImporterEditor.cs#L173 + MethodInfo InternalSetAssetImporterTargetEditor; + InternalSetAssetImporterTargetEditor = gui_cachedEditor.GetType() + .GetMethod(nameof(InternalSetAssetImporterTargetEditor), BindingFlags.NonPublic | BindingFlags.Instance); + + InternalSetAssetImporterTargetEditor.Invoke(gui_cachedEditor, new object[] { gui_cachedEditor }); + } + } + } + } + } + } + } + } + + + /* = right side panel = */ + + //https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs + if (gui_cachedEditor != null) + { + using (var sv_miniInspector = new EditorGUILayout.ScrollViewScope(gui_miniInspectorScrollPos)) + { + gui_miniInspectorScrollPos = sv_miniInspector.scrollPosition; + + if (_asmdefHasModified_changed) + Prefs.Instance.Save(); + + try + { + // TODO: don't ignore exception...! + // NOTE: when 'Apply' button is pressed, stack overflow will happen ...!! + // ... + // ... + // at UnityEditor.AssetImporters.AssetImporterEditor.get_preview + // at UnityEditor.Editor.ReloadPreviewInstances + // at UnityEditor.Editor.ReloadPreviewInstances + // at UnityEditor.Editor.ReloadPreviewInstances... + // ... + // ... + gui_cachedEditor.OnInspectorGUI(); + } + catch + { + EditorGUIUtility.ExitGUI(); + + ResetIntegratedInspector(); + + EditorGUIUtility.ExitGUI(); + } + } + } + } + + + /* + public override void OnTitleBarGUI() + { + if (_activeADefAssetPath != null) + { + EditorGUILayout.HelpBox(gui_dialogHelpText.text, MessageType.Info, true); + } + } + */ + + + /* integrated inspector ================================================================ */ + + [InitializeOnLoadMethod] + static void UnityEditor_Initialize() + { + // NOTE: don't remove this!! + // reset is required before compilation and selection change!! + CompilationPipeline.compilationStarted -= OnCompilationStarted; + CompilationPipeline.compilationStarted += OnCompilationStarted; + + var prefs = Prefs.Instance; + if (prefs.TurnOnImplicitRefsOnChangesAfterDomainReloading) + { + prefs.EnableImplicitRefsOnChanges = true; + prefs.TurnOnImplicitRefsOnChangesAfterDomainReloading = false; + prefs.Save(); + } + } + + + public static void LockIntegratedInspector(bool isLocked) => _modifingAssetFiles = isLocked; + + + readonly static Action OnCompilationStarted = _ => ResetIntegratedInspector(); + + public static void ResetIntegratedInspector() + { + // need to clear cached editor to update inspector correctly + if (gui_cachedEditor != null) + { + var so = gui_cachedEditor.serializedObject; + UnityEditor.Editor.DestroyImmediate(gui_cachedEditor); + so.Dispose(); // <-- this is important...!? + so = null; + + gui_cachedEditor = null; + _activeADefAssetPath = null; + + _asmdefHasModified_last = false; + _asmdefHasModified_changed = false; + } + } + + + public static void LockAndResetIntegratedInspectorThenUpdateAssets(Func updateFunc) + { + ResetIntegratedInspector(); + LockIntegratedInspector(true); + // delay is not required but make UX better + EditorApplication.delayCall += () => + { + bool isUpdated = false; + + try + { + var assetPaths = AssetDatabase.FindAssets("t:" + nameof(AssemblyDefinitionAsset)) + .Select(static x => AssetDatabase.GUIDToAssetPath(x)); + + foreach (var path in assetPaths) + { + isUpdated |= updateFunc(path); + } + + if (isUpdated) + { + var prefs = Prefs.Instance; + prefs.TurnOnImplicitRefsOnChangesAfterDomainReloading = prefs.EnableImplicitRefsOnChanges; + prefs.EnableImplicitRefsOnChanges = false; + prefs.Save(); // prepare for domain reloading + + var wnd = EditorWindow.focusedWindow; + wnd.ShowNotification(new GUIContent("Reloading Assets...")); + + AssetDatabase.Refresh(); + } + } + finally + { + // domain reloading will happen if updated + if (!isUpdated) + LockIntegratedInspector(false); + } + }; + } + + } +} diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs.meta b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs.meta new file mode 100644 index 0000000..611a8a1 --- /dev/null +++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4763635df43c3484bb438ee8d187ecba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: