From 55c58a81a5e6a9e8800ff4e1b5b4ccfde42a86c7 Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Thu, 3 Oct 2024 10:14:08 +0900 Subject: [PATCH 01/13] Add a notification for when the layout rules are broken. --- .../LayoutRules/AddressRules/AddressRule.cs | 10 ++ .../LayoutRules/LabelRules/LabelRule.cs | 22 +++- .../Core/Models/LayoutRules/LayoutRule.cs | 71 +++++++++-- .../LayoutRuleCorruptionNotificationType.cs | 13 ++ ...youtRuleCorruptionNotificationType.cs.meta | 3 + .../LayoutRules/VersionRules/VersionRule.cs | 25 +++- .../Models/Services/ApplyLayoutRuleService.cs | 49 +++++++- .../AssetFilterImpl/AssetFilterAsset.cs | 7 ++ .../AssetFilterImpl/AssetFilterBase.cs | 11 +- .../AssetFilterImpl/CustomAssetFilter.cs | 5 + .../DependentObjectBasedAssetFilter.cs | 17 +++ .../ExtensionBasedAssetFilter.cs | 15 +++ .../FindAssetsBasedAssetFilter.cs | 6 + .../AssetFilterImpl/ObjectBasedAssetFilter.cs | 17 +++ .../AssetFilterImpl/RegexBasedAssetFilter.cs | 28 ++++- .../AssetFilterImpl/TypeBasedAssetFilter.cs | 42 +++++-- .../Models/Shared/AssetGroups/AssetGroup.cs | 29 ++++- .../AssetGroups/AssetGroupObservableList.cs | 15 +++ .../Models/Shared/AssetGroups/IAssetFilter.cs | 10 +- .../Shared/AssetGroups/TypeReference.cs | 8 +- .../LayoutRuleEditorPresenter.cs | 119 ++++++++++-------- .../LayoutRuleEditorWindow.cs | 19 ++- .../Core/Tools/CLI/SmartAddresserCLI.cs | 15 ++- .../SmartAddresserAssetPostProcessor.cs | 21 +++- .../Shared/SmartAddresserProjectSettings.cs | 48 ++++++- .../SmartAddresserProjectSettingsProvider.cs | 59 +++++++-- 26 files changed, 564 insertions(+), 120 deletions(-) create mode 100644 Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs.meta diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/AddressRules/AddressRule.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/AddressRules/AddressRule.cs index a0f5fa1..33c69d2 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/AddressRules/AddressRule.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/AddressRules/AddressRule.cs @@ -76,6 +76,16 @@ public void Setup() AddressProvider.Value.Setup(); } + public bool Validate(out string errorMessage) + { + if (_assetGroups.Validate(out errorMessage)) + return true; + + var addressableGroupName = _addressableGroup != null ? _addressableGroup.Name : "(None)"; + errorMessage = $"Address rule is corrupted: {addressableGroupName}{Environment.NewLine}{errorMessage}"; + return false; + } + /// /// Provide an address from asset information. /// diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LabelRules/LabelRule.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LabelRules/LabelRule.cs index eb3c206..559409e 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LabelRules/LabelRule.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LabelRules/LabelRule.cs @@ -17,11 +17,11 @@ public sealed class LabelRule : ISerializationCallbackReceiver [SerializeField] private ObservableProperty _name = new ObservableProperty("New Label Rule"); [SerializeField] private AssetGroupObservableList _assetGroups = new AssetGroupObservableList(); + [SerializeReference] private ILabelProvider _labelProviderInternal; + private ObservableProperty _assetGroupDescription = new ObservableProperty(); private ObservableProperty _labelProviderDescription = new ObservableProperty(); - [SerializeReference] private ILabelProvider _labelProviderInternal; - public LabelRule() { _id = IdentifierFactory.Create(); @@ -65,6 +65,15 @@ public void Setup() LabelProvider.Value.Setup(); } + public bool Validate(out string errorMessage) + { + if (_assetGroups.Validate(out errorMessage)) + return true; + + errorMessage = $"Label rule is corrupted: {_name.Value}{Environment.NewLine}{errorMessage}"; + return false; + } + /// /// Create a label from asset information. /// @@ -77,8 +86,13 @@ public void Setup() /// You can pass false if it is guaranteed to be valid. /// /// Return true if successful. - public bool TryProvideLabel(string assetPath, Type assetType, bool isFolder, out string label, - bool checkIsPathValidForEntry = true) + public bool TryProvideLabel( + string assetPath, + Type assetType, + bool isFolder, + out string label, + bool checkIsPathValidForEntry = true + ) { if (!_assetGroups.Contains(assetPath, assetType, isFolder)) { diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs index 22ea98c..dd74457 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using SmartAddresser.Editor.Core.Models.LayoutRules.AddressRules; using SmartAddresser.Editor.Core.Models.LayoutRules.LabelRules; using SmartAddresser.Editor.Core.Models.LayoutRules.Settings; @@ -35,11 +36,8 @@ public bool SyncAddressRulesWithAddressableAssetGroups(List(); foreach (var addressableGroup in addressableGroups) { @@ -76,8 +73,29 @@ public void SetupForAddress() addressRule.Setup(); } - public bool TryProvideAddressAndAddressableGroup(string assetPath, Type assetType, bool isFolder, bool doSetup, - out string address, out AddressableAssetGroup addressableGroup) + public bool ValidateForAddress(out string errorMessage) + { + var result = true; + var sb = new StringBuilder(); + foreach (var addressRule in _addressRules) + { + result &= addressRule.Validate(out var message); + if (!string.IsNullOrEmpty(message)) + sb.Append(message); + } + + errorMessage = sb.ToString(); + return result; + } + + public bool TryProvideAddressAndAddressableGroup( + string assetPath, + Type assetType, + bool isFolder, + bool doSetup, + out string address, + out AddressableAssetGroup addressableGroup + ) { foreach (var addressRule in _addressRules) { @@ -103,6 +121,21 @@ public void SetupForLabels() labelRule.Setup(); } + public bool ValidateForLabels(out string errorMessage) + { + var result = true; + var sb = new StringBuilder(); + foreach (var labelRule in _labelRules) + { + result &= labelRule.Validate(out var message); + if (!string.IsNullOrEmpty(message)) + sb.Append(message); + } + + errorMessage = sb.ToString(); + return result; + } + /// /// Provide the labels. /// @@ -115,8 +148,13 @@ public void SetupForLabels() /// You can pass false if it is guaranteed to be valid. /// /// - public IReadOnlyCollection ProvideLabels(string assetPath, Type assetType, bool isFolder, bool doSetup, - bool checkIsPathValidForEntry = true) + public IReadOnlyCollection ProvideLabels( + string assetPath, + Type assetType, + bool isFolder, + bool doSetup, + bool checkIsPathValidForEntry = true + ) { var labels = new HashSet(); for (int i = 0, count = _labelRules.Count; i < count; i++) @@ -139,6 +177,21 @@ public void SetupForVersion() versionRule.Setup(); } + public bool ValidateForVersion(out string errorMessage) + { + var result = true; + var sb = new StringBuilder(); + foreach (var versionRule in _versionRules) + { + result &= versionRule.Validate(out var message); + if (!string.IsNullOrEmpty(message)) + sb.Append(message); + } + + errorMessage = sb.ToString(); + return result; + } + public string ProvideVersion(string assetPath, Type assetType, bool isFolder, bool doSetup) { foreach (var versionRule in _versionRules) diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs new file mode 100644 index 0000000..4b3a648 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs @@ -0,0 +1,13 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +namespace SmartAddresser.Editor.Core.Models.LayoutRules +{ + public enum LayoutRuleCorruptionNotificationType + { + Ignore, + LogError, + ThrowException, + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs.meta b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs.meta new file mode 100644 index 0000000..e36114e --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 41d1ff9f6ef54c6c81019e670f5689ca +timeCreated: 1727843909 \ No newline at end of file diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/VersionRules/VersionRule.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/VersionRules/VersionRule.cs index 608b5f0..b41231c 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/VersionRules/VersionRule.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/VersionRules/VersionRule.cs @@ -16,10 +16,10 @@ public sealed class VersionRule : ISerializationCallbackReceiver [SerializeField] private string _id; [SerializeField] private ObservableProperty _name = new ObservableProperty("New Version Rule"); [SerializeField] private AssetGroupObservableList _assetGroups = new AssetGroupObservableList(); - private ObservableProperty _assetGroupDescription = new ObservableProperty(); - private ObservableProperty _versionProviderDescription = new ObservableProperty(); [SerializeReference] private IVersionProvider _versionProviderInternal; + private ObservableProperty _assetGroupDescription = new ObservableProperty(); + private ObservableProperty _versionProviderDescription = new ObservableProperty(); public VersionRule() { @@ -64,6 +64,15 @@ public void Setup() VersionProvider.Value.Setup(); } + public bool Validate(out string errorMessage) + { + if (_assetGroups.Validate(out errorMessage)) + return true; + + errorMessage = $"Version rule is corrupted: {_name.Value}{Environment.NewLine}{errorMessage}"; + return false; + } + /// /// Create a version from asset information. /// @@ -76,8 +85,13 @@ public void Setup() /// You can pass false if it is guaranteed to be valid. /// /// Return true if successful. - public bool TryProvideVersion(string assetPath, Type assetType, bool isFolder, out string version, - bool checkIsPathValidForEntry = true) + public bool TryProvideVersion( + string assetPath, + Type assetType, + bool isFolder, + out string version, + bool checkIsPathValidForEntry = true + ) { if (!_assetGroups.Contains(assetPath, assetType, isFolder)) { @@ -92,12 +106,13 @@ public bool TryProvideVersion(string assetPath, Type assetType, bool isFolder, o } version = VersionProvider.Value.Provide(assetPath, assetType, isFolder); - + if (string.IsNullOrEmpty(version)) { version = null; return false; } + return true; } diff --git a/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs b/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs index 3eb3c51..0a41b5c 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs @@ -1,10 +1,15 @@ +using System; using System.Collections.Generic; using System.Linq; +using System.Text; using SmartAddresser.Editor.Core.Models.LayoutRules; +using SmartAddresser.Editor.Core.Models.Shared; using SmartAddresser.Editor.Foundation.AddressableAdapter; using SmartAddresser.Editor.Foundation.AssetDatabaseAdapter; using SmartAddresser.Editor.Foundation.SemanticVersioning; using UnityEditor.AddressableAssets.Settings; +using UnityEngine; +using Version = SmartAddresser.Editor.Foundation.SemanticVersioning.Version; namespace SmartAddresser.Editor.Core.Models.Services { @@ -41,13 +46,55 @@ public void Setup() _layoutRule.SetupForVersion(); } + public void ValidateLayoutRules(LayoutRuleCorruptionNotificationType corruptionNotificationType) + { + if (corruptionNotificationType == LayoutRuleCorruptionNotificationType.Ignore) + return; + + if (ValidateLayoutRulesInternal(out var errorMessage)) + return; + + if (corruptionNotificationType == LayoutRuleCorruptionNotificationType.LogError) + { + Debug.LogError(errorMessage); + return; + } + + if (corruptionNotificationType == LayoutRuleCorruptionNotificationType.ThrowException) + throw new Exception(errorMessage); + } + + private bool ValidateLayoutRulesInternal(out string errorMessage) + { + var result = true; + var sb = new StringBuilder(); + + result &= _layoutRule.ValidateForAddress(out var message); + if (!string.IsNullOrEmpty(message)) + sb.AppendLine(message); + + result &= _layoutRule.ValidateForLabels(out message); + if (!string.IsNullOrEmpty(message)) + sb.AppendLine(message); + + result &= _layoutRule.ValidateForVersion(out message); + if (!string.IsNullOrEmpty(message)) + sb.AppendLine(message); + + errorMessage = sb.ToString(); + return result; + } + /// /// Apply the layout rule to the addressable settings for all assets. /// - public void ApplyAll() + public void ApplyAll(LayoutRuleCorruptionNotificationType layoutRuleCorruptionNotificationType = LayoutRuleCorruptionNotificationType.Ignore) { Setup(); + // Check Corruption + ValidateLayoutRules(layoutRuleCorruptionNotificationType); + // Add all entries to the addressable asset system. var removeTargetAssetGuids = new List(); var versionExpression = _layoutRule.Settings.VersionExpression; diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterAsset.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterAsset.cs index ecf8476..a99633a 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterAsset.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterAsset.cs @@ -10,6 +10,13 @@ public abstract class AssetFilterAsset : ScriptableObject, IAssetFilter /// public abstract void SetupForMatching(); + /// + public virtual bool Validate(out string errorMessage) + { + errorMessage = null; + return true; + } + /// public abstract bool IsMatch(string assetPath, Type assetType, bool isFolder); diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterBase.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterBase.cs index c1f6f1c..666cddd 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterBase.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterBase.cs @@ -14,14 +14,17 @@ protected AssetFilterBase() } public string Id => _id; - - /// + + /// public abstract void SetupForMatching(); - /// + /// + public abstract bool Validate(out string errorMessage); + + /// public abstract bool IsMatch(string assetPath, Type assetType, bool isFolder); - /// + /// public abstract string GetDescription(); public void OverwriteValuesFromJson(string json) diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs index e54e6ee..391da61 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs @@ -27,6 +27,11 @@ public void SetupForMatching() assetFilter.SetupForMatching(); } + public bool Validate(out string errorMessage) + { + return assetFilter.Validate(out errorMessage); + } + public bool IsMatch(string assetPath, Type assetType, bool isFolder) { return assetFilter.IsMatch(assetPath, assetType, isFolder); diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilter.cs index 04bb56c..5274a78 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilter.cs @@ -20,6 +20,7 @@ public sealed class DependentObjectBasedAssetFilter : AssetFilterBase [SerializeField] private ObjectListableProperty _object = new ObjectListableProperty(); private List _dependentAssetPaths = new List(); + private bool _hasNullObject; /// /// If true, check only direct dependencies. @@ -38,10 +39,14 @@ public bool OnlyDirectDependencies public override void SetupForMatching() { _dependentAssetPaths.Clear(); + _hasNullObject = false; foreach (var obj in _object) { if (obj == null) + { + _hasNullObject = true; continue; + } var path = AssetDatabase.GetAssetPath(obj); var dependencies = AssetDatabase.GetDependencies(path, !_onlyDirectDependencies); @@ -51,6 +56,18 @@ public override void SetupForMatching() _dependentAssetPaths = _dependentAssetPaths.Distinct().ToList(); } + public override bool Validate(out string errorMessage) + { + if (_hasNullObject) + { + errorMessage = "There are null reference objects."; + return false; + } + + errorMessage = null; + return true; + } + /// public override bool IsMatch(string assetPath, Type assetType, bool isFolder) { diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilter.cs index 412bf05..90e8e35 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilter.cs @@ -20,6 +20,7 @@ public sealed class ExtensionBasedAssetFilter : AssetFilterBase { [SerializeField] private StringListableProperty _extension = new StringListableProperty(); private List _extensions = new List(); + private bool _hasEmptyExtension; /// /// Extensions for filtering. @@ -29,10 +30,12 @@ public sealed class ExtensionBasedAssetFilter : AssetFilterBase public override void SetupForMatching() { _extensions.Clear(); + _hasEmptyExtension = false; foreach (var extension in _extension) { if (string.IsNullOrEmpty(extension)) { + _hasEmptyExtension = true; continue; } @@ -46,6 +49,18 @@ public override void SetupForMatching() } } + public override bool Validate(out string errorMessage) + { + if (_hasEmptyExtension) + { + errorMessage = "There are empty extensions."; + return false; + } + + errorMessage = string.Empty; + return true; + } + /// public override bool IsMatch(string assetPath, Type assetType, bool isFolder) { diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/FindAssetsBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/FindAssetsBasedAssetFilter.cs index 583bcdc..a0a9eb3 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/FindAssetsBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/FindAssetsBasedAssetFilter.cs @@ -48,6 +48,12 @@ public override void SetupForMatching() _foundAssetPaths.AddRange(assetPaths); } + public override bool Validate(out string errorMessage) + { + errorMessage = null; + return true; + } + /// public override bool IsMatch(string assetPath, Type assetType, bool isFolder) { diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilter.cs index 8cee2d9..ec2da7a 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilter.cs @@ -23,6 +23,7 @@ public sealed class ObjectBasedAssetFilter : AssetFilterBase [SerializeField] private ObjectListableProperty _object = new ObjectListableProperty(); private List<(string assetPath, bool isFolder)> _objectInfoList = new List<(string assetPath, bool isFolder)>(); + private bool _hasNullObject; public FolderTargetingMode FolderTargetingMode { @@ -38,16 +39,32 @@ public FolderTargetingMode FolderTargetingMode public override void SetupForMatching() { _objectInfoList.Clear(); + _hasNullObject = false; foreach (var obj in _object) { if (obj == null) + { + _hasNullObject = true; continue; + } var isFolder = obj is DefaultAsset; var path = AssetDatabase.GetAssetPath(obj); _objectInfoList.Add((path, isFolder)); } } + + public override bool Validate(out string errorMessage) + { + if (_hasNullObject) + { + errorMessage = $"[{GetType().Name}] There are null reference objects."; + return false; + } + + errorMessage = null; + return true; + } /// public override bool IsMatch(string assetPath, Type assetType, bool isFolder) diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilter.cs index d9fbe9d..f8d877a 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilter.cs @@ -22,6 +22,7 @@ public sealed class RegexBasedAssetFilter : AssetFilterBase [SerializeField] private AssetFilterCondition _condition = AssetFilterCondition.ContainsMatched; [SerializeField] private StringListableProperty _assetPathRegex = new StringListableProperty(); private List _regexes = new List(); + private List _errorRegexStrings = new List(); public bool MatchWithFolders { @@ -43,6 +44,7 @@ public AssetFilterCondition Condition public override void SetupForMatching() { _regexes.Clear(); + _errorRegexStrings.Clear(); foreach (var assetPathRegex in _assetPathRegex) { if (string.IsNullOrEmpty(assetPathRegex)) @@ -55,11 +57,35 @@ public override void SetupForMatching() } catch { - // If the regex string is invalid and an exception is thrown, continue. + // If the regex string is invalid and an exception is thrown, add to errorStrings and continue. + _errorRegexStrings.Add(assetPathRegex); } } } + public override bool Validate(out string errorMessage) + { + if (_errorRegexStrings.Count >= 1) + { + var sb = new StringBuilder(); + sb.Append("Invalid regexes: "); + foreach (var errorRegexString in _errorRegexStrings) + { + sb.Append(errorRegexString); + sb.Append(", "); + } + + // Remove the last ", ". + sb.Remove(sb.Length - 2, 2); + + errorMessage = sb.ToString(); + return false; + } + + errorMessage = null; + return true; + } + /// public override bool IsMatch(string assetPath, Type assetType, bool isFolder) { diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilter.cs index b877303..af8b75b 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilter.cs @@ -14,6 +14,7 @@ public sealed class TypeBasedAssetFilter : AssetFilterBase { [SerializeField] private TypeReferenceListableProperty _type = new TypeReferenceListableProperty(); [SerializeField] private bool _matchWithDerivedTypes = true; + private List _invalidAssemblyQualifiedNames = new List(); private Dictionary _resultCache = new Dictionary(); private object _resultCacheLocker = new object(); @@ -33,6 +34,7 @@ public bool MatchWithDerivedTypes public override void SetupForMatching() { _types.Clear(); + _invalidAssemblyQualifiedNames.Clear(); foreach (var typeRef in _type) { if (typeRef == null) @@ -41,13 +43,40 @@ public override void SetupForMatching() if (!typeRef.IsValid()) continue; - var type = System.Type.GetType(typeRef.AssemblyQualifiedName); - _types.Add(type); + var assemblyQualifiedName = typeRef.AssemblyQualifiedName; + var type = System.Type.GetType(assemblyQualifiedName); + if (type == null) + _invalidAssemblyQualifiedNames.Add(assemblyQualifiedName); + else + _types.Add(type); } _resultCache.Clear(); } + public override bool Validate(out string errorMessage) + { + if (_invalidAssemblyQualifiedNames.Count >= 1) + { + var sb = new StringBuilder(); + sb.Append($"[{GetType().Name}] Unknown types: "); + foreach (var invalidAssemblyQualifiedNames in _invalidAssemblyQualifiedNames) + { + sb.Append(invalidAssemblyQualifiedNames); + sb.Append(" / "); + } + + // Remove the last " / ". + sb.Remove(sb.Length - 3, 3); + + errorMessage = sb.ToString(); + return false; + } + + errorMessage = null; + return true; + } + /// public override bool IsMatch(string assetPath, Type assetType, bool isFolder) { @@ -57,13 +86,8 @@ public override bool IsMatch(string assetPath, Type assetType, bool isFolder) if (_resultCache.TryGetValue(assetType, out var result)) return result; - for (var i = 0; i < _types.Count; i++) + foreach (var type in _types) { - var type = _types[i]; - - if (type == null) - continue; - if (type == assetType) { result = true; @@ -112,7 +136,7 @@ public override string GetDescription() result.Insert(0, "Type: "); } - if (MatchWithDerivedTypes) + if (MatchWithDerivedTypes) result.Append(" and derived types"); return result.ToString(); diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroup.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroup.cs index a6897d9..284adae 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroup.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroup.cs @@ -16,11 +16,12 @@ namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups [Serializable] public sealed class AssetGroup { + private const string Indent = " "; + [SerializeField] private string _id; [SerializeField] private ObservableProperty _name = new ObservableProperty("New Asset Group"); - [SerializeField] - private SerializeReferenceObservableList _filters = + [SerializeField] private SerializeReferenceObservableList _filters = new SerializeReferenceObservableList(); public AssetGroup() @@ -39,10 +40,32 @@ public AssetGroup() public void Setup() { - foreach (var filter in _filters) + foreach (var filter in _filters) filter?.SetupForMatching(); } + public bool Validate(out string errorMessage) + { + var result = true; + var sb = new StringBuilder(); + foreach (var filter in _filters) + { + result &= filter.Validate(out var message); + if (!string.IsNullOrEmpty(message)) + sb.AppendLine($"{Indent}{Indent}{message}"); + } + + if (sb.Length == 0) + { + errorMessage = null; + return result; + } + + errorMessage = sb.ToString(); + errorMessage = $"{Indent}Group: {_name.Value}{Environment.NewLine}{errorMessage}"; + return result; + } + /// /// Return true if the asset belongs to this group. /// diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroupObservableList.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroupObservableList.cs index 8b1eb30..720c4c9 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroupObservableList.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroupObservableList.cs @@ -20,6 +20,21 @@ public void Setup() group.Setup(); } + public bool Validate(out string errorMessage) + { + var result = true; + var sb = new StringBuilder(); + foreach (var group in this) + { + result &= group.Validate(out var message); + if (!string.IsNullOrEmpty(message)) + sb.Append(message); + } + + errorMessage = sb.ToString(); + return result; + } + /// /// Return true if this asset group contains the asset. /// diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/IAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/IAssetFilter.cs index ad18c21..c0233df 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/IAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/IAssetFilter.cs @@ -14,12 +14,20 @@ public interface IAssetFilter /// Preprocessing of . /// /// - /// This will be called before and less often than . + /// This will be called before and less often than . /// And will be executed in the main thread, in contrast to . /// So you should write heavy processes or processes that use Unity's API here. /// void SetupForMatching(); + /// + /// Returns false if the AssetFilter is corrupted. + /// Example: When an asset referenced by the filter is deleted. + /// This method will be called after . + /// + /// + bool Validate(out string errorMessage); + /// /// Return true if the asset passes this filter. /// diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/TypeReference.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/TypeReference.cs index 4d01d67..6519a6c 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/TypeReference.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/TypeReference.cs @@ -13,19 +13,19 @@ public class TypeReference public string Name { get => _name; - set => _name = value; + internal set => _name = value; } public string FullName { get => _fullName; - set => _fullName = value; + internal set => _fullName = value; } public string AssemblyQualifiedName { get => _assemblyQualifiedName; - set => _assemblyQualifiedName = value; + internal set => _assemblyQualifiedName = value; } public static TypeReference Create(Type type) @@ -38,7 +38,7 @@ public static TypeReference Create(Type type) }; return instance; } - + public bool IsValid() { return !string.IsNullOrEmpty(_assemblyQualifiedName); diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs index 48cacc3..8470dc9 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs @@ -40,8 +40,11 @@ internal sealed class LayoutRuleEditorPresenter : IDisposable private ILayoutRuleDataRepository _dataRepository; private bool _didSetupView; - public LayoutRuleEditorPresenter(LayoutRuleEditorView view, AutoIncrementHistory history, - IAssetSaveService assetSaveService) + public LayoutRuleEditorPresenter( + LayoutRuleEditorView view, + AutoIncrementHistory history, + IAssetSaveService assetSaveService + ) { _view = view; _addressRuleEditorPresenter = @@ -122,8 +125,11 @@ private void SetupActiveView(LayoutRuleData data) _didSetupView = true; } - private void OnAddressableAssetSettingsModified(AddressableAssetSettings settings, - AddressableAssetSettings.ModificationEvent e, object obj) + private void OnAddressableAssetSettingsModified( + AddressableAssetSettings settings, + AddressableAssetSettings.ModificationEvent e, + object obj + ) { if (e == AddressableAssetSettings.ModificationEvent.GroupAdded || e == AddressableAssetSettings.ModificationEvent.GroupMoved @@ -221,12 +227,14 @@ void OnAssetSelectButtonClicked() var menuName = $"{sourceAssetNames[i]}"; var isActive = activeSourceAssetIndex == i; var idx = i; - menu.AddItem(new GUIContent(menuName), isActive, () => - { - var asset = sourceAssets[idx]; - SetupActiveView(asset); - _dataRepository.SetEditingData(asset); - }); + menu.AddItem(new GUIContent(menuName), + isActive, + () => + { + var asset = sourceAssets[idx]; + SetupActiveView(asset); + _dataRepository.SetEditingData(asset); + }); } menu.ShowAsContext(); @@ -239,46 +247,54 @@ void OnMenuButtonClicked() var menu = new GenericMenu(); - menu.AddItem(new GUIContent("Apply to Addressables"), false, () => - { - var projectSettings = SmartAddresserProjectSettings.instance; - var primaryData = projectSettings.PrimaryData; - - if (primaryData == null) + menu.AddItem(new GUIContent("Apply to Addressables"), + false, + () => { - Apply(); - return; - } - - if (primaryData == _editingData.Value) - { - Apply(); - return; - } - - // If the primary data is not the same as the editing data, ask the user to confirm. - // If the user confirms, remove the primary data and apply the editing data. - var dialogMessage = - $"The {nameof(projectSettings.PrimaryData)} of the Project Settings is not the same as the data you are applying. Do you want to remove the {nameof(projectSettings.PrimaryData)} from Project Settings and apply the editing data?"; - if (EditorUtility.DisplayDialog("Confirm", dialogMessage, "Remove & Apply", "Cancel")) - { - projectSettings.PrimaryData = null; - Apply(); - } - - void Apply() - { - // Apply the layout rules to the addressable asset system. - var layoutRule = _editingData.Value.LayoutRule; - var versionExpressionParser = new VersionExpressionParserRepository().Load(); - var assetDatabaseAdapter = new AssetDatabaseAdapter(); - var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; - var addressableSettingsAdapter = new AddressableAssetSettingsAdapter(addressableSettings); - var applyService = new ApplyLayoutRuleService(layoutRule, versionExpressionParser, - addressableSettingsAdapter, assetDatabaseAdapter); - applyService.ApplyAll(); - } - }); + var projectSettings = SmartAddresserProjectSettings.instance; + var primaryData = projectSettings.PrimaryData; + + if (primaryData == null) + { + Apply(); + return; + } + + if (primaryData == _editingData.Value) + { + Apply(); + return; + } + + // If the primary data is not the same as the editing data, ask the user to confirm. + // If the user confirms, remove the primary data and apply the editing data. + var dialogMessage = + $"The {nameof(projectSettings.PrimaryData)} of the Project Settings is not the same as the data you are applying. Do you want to remove the {nameof(projectSettings.PrimaryData)} from Project Settings and apply the editing data?"; + if (EditorUtility.DisplayDialog("Confirm", dialogMessage, "Remove & Apply", "Cancel")) + { + projectSettings.PrimaryData = null; + Apply(); + } + + void Apply() + { + // Apply the layout rules to the addressable asset system. + var layoutRule = _editingData.Value.LayoutRule; + var versionExpressionParser = new VersionExpressionParserRepository().Load(); + var assetDatabaseAdapter = new AssetDatabaseAdapter(); + var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; + var addressableSettingsAdapter = new AddressableAssetSettingsAdapter(addressableSettings); + var applyService = new ApplyLayoutRuleService(layoutRule, + versionExpressionParser, + addressableSettingsAdapter, + assetDatabaseAdapter); + + // Check Corruption + var corruptionNotificationType = + projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnApplyAll; + applyService.ApplyAll(corruptionNotificationType); + } + }); menu.AddItem(new GUIContent("Open Layout Viewer"), false, LayoutViewerWindow.Open); @@ -288,7 +304,10 @@ void Apply() void OnCreateButtonClicked() { var assetPath = EditorUtility.SaveFilePanelInProject($"Create {nameof(LayoutRuleData)}", - $"{nameof(LayoutRuleData)}", "asset", "", "Assets"); + $"{nameof(LayoutRuleData)}", + "asset", + "", + "Assets"); if (string.IsNullOrEmpty(assetPath)) return; diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs index bcb5e8f..a7b1eee 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs @@ -79,9 +79,15 @@ private void OnLostFocus() var assetDatabaseAdapter = new AssetDatabaseAdapter(); var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; var addressableSettingsAdapter = new AddressableAssetSettingsAdapter(addressableSettings); - var applyService = new ApplyLayoutRuleService(layoutRule, versionExpressionParser, - addressableSettingsAdapter, assetDatabaseAdapter); - applyService.ApplyAll(); + var applyService = new ApplyLayoutRuleService(layoutRule, + versionExpressionParser, + addressableSettingsAdapter, + assetDatabaseAdapter); + + // Check Corruption + var corruptionNotificationType = + projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnApplyAll; + applyService.ApplyAll(corruptionNotificationType); } _hasAnyDataChanged = false; @@ -119,8 +125,11 @@ private void Setup() .DisposeWith(_setupDisposables); // Set up presenter and view. - _view = new LayoutRuleEditorView(_addressTreeViewState, _labelTreeViewState, _versionTreeViewState, - _splitViewState, Repaint); + _view = new LayoutRuleEditorView(_addressTreeViewState, + _labelTreeViewState, + _versionTreeViewState, + _splitViewState, + Repaint); _presenter = new LayoutRuleEditorPresenter(_view, _history, assetSaveService); _presenter.SetupView(new LayoutRuleDataRepository()); } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs b/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs index 9b93bd6..9bda1aa 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs @@ -61,7 +61,9 @@ public static void ApplyRules() // Build and validate the Layout. var buildLayoutService = new BuildLayoutService(assetDatabaseAdapter); var layout = buildLayoutService.Execute(layoutRule); - layout.Validate(true, validationSettings.DuplicateAddresses, validationSettings.DuplicateAssetPaths, + layout.Validate(true, + validationSettings.DuplicateAddresses, + validationSettings.DuplicateAssetPaths, validationSettings.EntryHasMultipleVersions); // Export the result of the validation. @@ -70,7 +72,7 @@ public static void ApplyRules() // Exit if error occurred. if (layout.ErrorType == LayoutErrorType.Error - || (options.FailWhenWarning && layout.ErrorType == LayoutErrorType.Warning)) + || options.FailWhenWarning && layout.ErrorType == LayoutErrorType.Warning) { EditorApplication.Exit(ErrorLevelValidateFailed); return; @@ -78,9 +80,12 @@ public static void ApplyRules() } // Apply the layout rules to the addressable asset system. - var applyService = new ApplyLayoutRuleService(layoutRule, versionExpressionParser, - addressableSettingsAdapter, assetDatabaseAdapter); - applyService.ApplyAll(); + var applyService = new ApplyLayoutRuleService(layoutRule, + versionExpressionParser, + addressableSettingsAdapter, + assetDatabaseAdapter); + + applyService.ApplyAll(projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnApplyAll); EditorApplication.Exit(ErrorLevelNone); } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs b/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs index 74ee3b1..d790245 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs @@ -10,8 +10,12 @@ namespace SmartAddresser.Editor.Core.Tools.Importer { internal sealed class SmartAddresserAssetPostProcessor : AssetPostprocessor { - private static void OnPostprocessAllAssets(string[] importedAssetPaths, string[] deletedAssetPaths, - string[] movedAssetPaths, string[] movedFromAssetPaths) + private static void OnPostprocessAllAssets( + string[] importedAssetPaths, + string[] deletedAssetPaths, + string[] movedAssetPaths, + string[] movedFromAssetPaths + ) { var layoutRuleDataRepository = new LayoutRuleDataRepository(); var primaryData = layoutRuleDataRepository.PrimaryData; @@ -25,10 +29,17 @@ private static void OnPostprocessAllAssets(string[] importedAssetPaths, string[] var assetDatabaseAdapter = new AssetDatabaseAdapter(); var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; var addressableSettingsAdapter = new AddressableAssetSettingsAdapter(addressableSettings); - var applyService = new ApplyLayoutRuleService(layoutRule, versionExpressionParser, - addressableSettingsAdapter, assetDatabaseAdapter); + var applyService = new ApplyLayoutRuleService(layoutRule, + versionExpressionParser, + addressableSettingsAdapter, + assetDatabaseAdapter); applyService.Setup(); + // Check Corruption + var projectSettings = SmartAddresserProjectSettings.instance; + var corruptionNotificationType = projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnImport; + applyService.ValidateLayoutRules(corruptionNotificationType); + var versionExpression = layoutRule.Settings.VersionExpression.Value; if (string.IsNullOrEmpty(versionExpression)) versionExpression = null; @@ -44,7 +55,7 @@ private static void OnPostprocessAllAssets(string[] importedAssetPaths, string[] var guid = AssetDatabase.AssetPathToGUID(movedAssetPath); applyService.Apply(guid, false, true, versionExpression); } - + applyService.InvokeBatchModificationEvent(); } } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs index 0653afa..9c522f6 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs @@ -1,6 +1,7 @@ using System; using SmartAddresser.Editor.Core.Models.LayoutRules; using SmartAddresser.Editor.Core.Models.Layouts; +using SmartAddresser.Editor.Core.Models.Shared; using UnityEditor; using UnityEngine; @@ -12,6 +13,7 @@ public sealed class SmartAddresserProjectSettings : ScriptableSingleton layoutRuleCorruption; + set + { + if (value == layoutRuleCorruption) + return; + + layoutRuleCorruption = value; + Save(true); + } + } + [Serializable] public sealed class Validation { @@ -63,8 +78,11 @@ public Validation() { } - public Validation(EntryErrorType duplicateAddresses, EntryErrorType duplicateAssetPaths, - EntryErrorType entryHasMultipleVersions) + public Validation( + EntryErrorType duplicateAddresses, + EntryErrorType duplicateAssetPaths, + EntryErrorType entryHasMultipleVersions + ) { this.duplicateAddresses = duplicateAddresses; this.duplicateAssetPaths = duplicateAssetPaths; @@ -75,5 +93,31 @@ public Validation(EntryErrorType duplicateAddresses, EntryErrorType duplicateAss public EntryErrorType DuplicateAssetPaths => duplicateAssetPaths; public EntryErrorType EntryHasMultipleVersions => entryHasMultipleVersions; } + + [Serializable] + public sealed class LayoutRuleCorruption + { + [SerializeField] private LayoutRuleCorruptionNotificationType notificationTypeOnApplyAll = + LayoutRuleCorruptionNotificationType.ThrowException; + + [SerializeField] private LayoutRuleCorruptionNotificationType notificationTypeOnImport = + LayoutRuleCorruptionNotificationType.Ignore; + + public LayoutRuleCorruption() + { + } + + public LayoutRuleCorruption( + LayoutRuleCorruptionNotificationType notificationTypeOnApplyAll, + LayoutRuleCorruptionNotificationType notificationTypeOnImport + ) + { + this.notificationTypeOnApplyAll = notificationTypeOnApplyAll; + this.notificationTypeOnImport = notificationTypeOnImport; + } + + public LayoutRuleCorruptionNotificationType NotificationTypeOnApplyAll => notificationTypeOnApplyAll; + public LayoutRuleCorruptionNotificationType NotificationTypeOnImport => notificationTypeOnImport; + } } } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs index 08fec1c..8c77b7f 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs @@ -1,7 +1,9 @@ +using System; using System.Collections.Generic; using SmartAddresser.Editor.Core.Models.LayoutRules; using SmartAddresser.Editor.Core.Models.Layouts; using SmartAddresser.Editor.Core.Models.Services; +using SmartAddresser.Editor.Core.Models.Shared; using SmartAddresser.Editor.Foundation.AddressableAdapter; using SmartAddresser.Editor.Foundation.AssetDatabaseAdapter; using UnityEditor; @@ -12,8 +14,11 @@ namespace SmartAddresser.Editor.Core.Tools.Shared { public sealed class SmartAddresserProjectSettingsProvider : SettingsProvider { - public SmartAddresserProjectSettingsProvider(string path, SettingsScope scopes, - IEnumerable keywords = null) + public SmartAddresserProjectSettingsProvider( + string path, + SettingsScope scopes, + IEnumerable keywords = null + ) : base(path, scopes, keywords) { } @@ -22,7 +27,8 @@ public SmartAddresserProjectSettingsProvider(string path, SettingsScope scopes, public static SettingsProvider CreateProvider() { var keywords = new[] { "Smart Addresser" }; - return new SmartAddresserProjectSettingsProvider("Project/Smart Addresser", SettingsScope.Project, + return new SmartAddresserProjectSettingsProvider("Project/Smart Addresser", + SettingsScope.Project, keywords); } @@ -38,22 +44,30 @@ public override void OnGUI(string searchContext) projectSettings.PrimaryData = (LayoutRuleData)EditorGUILayout.ObjectField("Primary Data", projectSettings.PrimaryData, - typeof(LayoutRuleData), false); + typeof(LayoutRuleData), + false); if (ccs.changed && projectSettings.PrimaryData != null) { if (EditorUtility.DisplayDialog("Confirmation", - "If you change the Primary Data, it will be applied immediately. Do you want change it?", - "OK", "Cancel")) + "If you change the Primary Data, it will be applied immediately. Do you want change it?", + "OK", + "Cancel")) { var layoutRule = projectSettings.PrimaryData.LayoutRule; var versionExpressionParser = new VersionExpressionParserRepository().Load(); var assetDatabaseAdapter = new AssetDatabaseAdapter(); var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; var addressableSettingsAdapter = new AddressableAssetSettingsAdapter(addressableSettings); - var applyService = new ApplyLayoutRuleService(layoutRule, versionExpressionParser, - addressableSettingsAdapter, assetDatabaseAdapter); - applyService.ApplyAll(); + var applyService = new ApplyLayoutRuleService(layoutRule, + versionExpressionParser, + addressableSettingsAdapter, + assetDatabaseAdapter); + + // Check Corruption + var corruptionNotificationType = + projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnApplyAll; + applyService.ApplyAll(corruptionNotificationType); } else { @@ -65,7 +79,8 @@ public override void OnGUI(string searchContext) projectSettings.VersionExpressionParser = (MonoScript)EditorGUILayout.ObjectField("Version Expression Parser", projectSettings.VersionExpressionParser, - typeof(MonoScript), false); + typeof(MonoScript), + false); EditorGUILayout.LabelField("Validation Settings"); using (new EditorGUI.IndentLevelScope()) @@ -79,8 +94,28 @@ public override void OnGUI(string searchContext) (EntryErrorType)EditorGUILayout.EnumPopup("Entry Has Multiple Versions", projectSettings.ValidationSettings.EntryHasMultipleVersions); if (ccs.changed) - projectSettings.ValidationSettings = new SmartAddresserProjectSettings.Validation(duplicateAddresses, - duplicateAssetPaths, entryHasMultipleVersions); + projectSettings.ValidationSettings = new SmartAddresserProjectSettings.Validation( + duplicateAddresses, + duplicateAssetPaths, + entryHasMultipleVersions); + } + + EditorGUILayout.LabelField("Layout Rule Corruption"); + using (new EditorGUI.IndentLevelScope()) + using (var ccs = new EditorGUI.ChangeCheckScope()) + { + var notificationTypeOnApplyAll = + (LayoutRuleCorruptionNotificationType)EditorGUILayout.EnumPopup( + "On Apply All", + projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnApplyAll); + var notificationTypeOnImport = + (LayoutRuleCorruptionNotificationType)EditorGUILayout.EnumPopup( + "On Import", + projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnImport); + if (ccs.changed) + projectSettings.LayoutRuleCorruptionSettings = + new SmartAddresserProjectSettings.LayoutRuleCorruption(notificationTypeOnApplyAll, + notificationTypeOnImport); } } } From 74e03ec2510253d37faf48f3c3a0c59937b27a17 Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Thu, 3 Oct 2024 10:22:57 +0900 Subject: [PATCH 02/13] Fixed the error that occurs after deleting the Library folder --- .../Importer/SmartAddresserAssetPostProcessor.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs b/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs index d790245..efe702b 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs @@ -16,6 +16,18 @@ private static void OnPostprocessAllAssets( string[] movedAssetPaths, string[] movedFromAssetPaths ) + { + // Delay 1 frame because AddressableAssetSettingsDefaultObject.Settings may be null at this point when the Library folder is deleted. + EditorApplication.delayCall += () => + LazyOnPostprocessAllAssets(importedAssetPaths, deletedAssetPaths, movedAssetPaths, movedFromAssetPaths); + } + + private static void LazyOnPostprocessAllAssets( + string[] importedAssetPaths, + string[] deletedAssetPaths, + string[] movedAssetPaths, + string[] movedFromAssetPaths + ) { var layoutRuleDataRepository = new LayoutRuleDataRepository(); var primaryData = layoutRuleDataRepository.PrimaryData; From 50048fa582d9a3bbe8034f25cc012722f99fb586 Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Thu, 3 Oct 2024 11:22:43 +0900 Subject: [PATCH 03/13] Test --- .../DependentObjectBasedAssetFilterTest.cs | 25 +++++++++++++++- .../ExtensionBasedAssetFilterTest.cs | 18 ++++++++++++ .../ObjectBasedAssetFilterTest.cs | 19 ++++++++++++ .../RegexBasedAssetFilterTest.cs | 24 ++++++++++++++- .../TypeBasedAssetFilterTest.cs | 29 +++++++++++++++++-- 5 files changed, 111 insertions(+), 4 deletions(-) diff --git a/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilterTest.cs b/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilterTest.cs index 20da7fd..2a71c5b 100644 --- a/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilterTest.cs +++ b/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilterTest.cs @@ -25,7 +25,11 @@ public bool IsMatch(string relativeAssetPath, Type assetType) [TestCase(TestAssetRelativePaths.Shared.Texture64, typeof(Texture2D), true, ExpectedResult = false)] [TestCase(TestAssetRelativePaths.Shared.MaterialTex64, typeof(Material), false, ExpectedResult = true)] [TestCase(TestAssetRelativePaths.Shared.MaterialTex64, typeof(Material), true, ExpectedResult = true)] - public bool IsMatch_OnlyDirectDependencies(string relativeAssetPath, Type assetType, bool onlyDirectDependencies) + public bool IsMatch_OnlyDirectDependencies( + string relativeAssetPath, + Type assetType, + bool onlyDirectDependencies + ) { var filter = new DependentObjectBasedAssetFilter(); filter.Object.Value = AssetDatabase.LoadAssetAtPath(TestAssetPaths.Shared.PrefabTex64); @@ -34,5 +38,24 @@ public bool IsMatch_OnlyDirectDependencies(string relativeAssetPath, Type assetT var assetPath = TestAssetPaths.CreateAbsoluteAssetPath(relativeAssetPath); return filter.IsMatch(assetPath, assetType, assetType == typeof(DefaultAsset)); } + + [Test] + public void Validate_ObjectIsNotNull_ReturnTrue() + { + var filter = new DependentObjectBasedAssetFilter(); + filter.Object.Value = AssetDatabase.LoadAssetAtPath(TestAssetPaths.Shared.Texture64); + filter.SetupForMatching(); + + Assert.That(filter.Validate(out _), Is.True); + } + + [Test] + public void Validate_ObjectIsNull_ReturnFalse() + { + var filter = new DependentObjectBasedAssetFilter(); + filter.SetupForMatching(); + + Assert.That(filter.Validate(out _), Is.False); + } } } diff --git a/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilterTest.cs b/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilterTest.cs index ba4f6d9..1a67e20 100644 --- a/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilterTest.cs +++ b/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilterTest.cs @@ -58,5 +58,23 @@ public void IsMatch_RegisterExtensionsAndNotContainsMatched_ReturnFalse() filter.SetupForMatching(); Assert.That(filter.IsMatch("Test.png", typeof(Texture2D), false), Is.False); } + + [Test] + public void Validate_ValidExtension_ReturnTrue() + { + var filter = new ExtensionBasedAssetFilter(); + filter.Extension.Value = "png"; + + Assert.That(filter.Validate(out _), Is.True); + } + + [Test] + public void Validate_ExtensionIsNull_ReturnFalse() + { + var filter = new ExtensionBasedAssetFilter(); + filter.SetupForMatching(); + + Assert.That(filter.Validate(out _), Is.False); + } } } diff --git a/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilterTest.cs b/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilterTest.cs index 1b04438..45d9ae9 100644 --- a/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilterTest.cs +++ b/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilterTest.cs @@ -99,5 +99,24 @@ public void IsMatch_AnotherDirectoryContainsFilterDirectoryName_ReturnFalse() filter.SetupForMatching(); Assert.That(filter.IsMatch(TestAssetPaths.Dummy1.PrefabDummy, typeof(GameObject), false), Is.False); } + + [Test] + public void Validate_ObjectIsNotNull_ReturnTrue() + { + var filter = new ObjectBasedAssetFilter(); + filter.Object.Value = AssetDatabase.LoadAssetAtPath(TestAssetPaths.Shared.Texture64); + filter.SetupForMatching(); + + Assert.That(filter.Validate(out _), Is.True); + } + + [Test] + public void Validate_ObjectIsNull_ReturnFalse() + { + var filter = new ObjectBasedAssetFilter(); + filter.SetupForMatching(); + + Assert.That(filter.Validate(out _), Is.False); + } } } diff --git a/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilterTest.cs b/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilterTest.cs index 0c3e334..574d171 100644 --- a/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilterTest.cs +++ b/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilterTest.cs @@ -43,7 +43,9 @@ public void IsMatch_RegisterInvalidRegex_ReturnFalse() [TestCase(AssetFilterCondition.MatchAll, "^Assets/Test/.+", ".+/Test/.+", ExpectedResult = true)] [TestCase(AssetFilterCondition.MatchAll, "^Assets/Test/.+", ".+/NotMatched/.+", ExpectedResult = false)] [TestCase(AssetFilterCondition.ContainsUnmatched, "^Assets/Test/.+", "^Assets/Test2/.+", ExpectedResult = true)] - [TestCase(AssetFilterCondition.ContainsUnmatched, "^Assets/Test/.+", "^Assets/Test/Test.+", + [TestCase(AssetFilterCondition.ContainsUnmatched, + "^Assets/Test/.+", + "^Assets/Test/Test.+", ExpectedResult = false)] [TestCase(AssetFilterCondition.NotMatchAll, "^Assets/Test2/.+", ".+/Test2/.+", ExpectedResult = true)] [TestCase(AssetFilterCondition.NotMatchAll, "^Assets/Test/.+", ".+/NotMatched/.+", ExpectedResult = false)] @@ -71,5 +73,25 @@ public bool IsMatch_TargetIsFolder(bool matchWithFolder, string targetAssetPath) filter.SetupForMatching(); return filter.IsMatch(targetAssetPath, typeof(DefaultAsset), true); } + + [Test] + public void Validate_ValidRegex_ReturnTrue() + { + var filter = new RegexBasedAssetFilter(); + filter.AssetPathRegex.Value = "^Assets/Test/.+"; + filter.SetupForMatching(); + + Assert.That(filter.Validate(out _), Is.True); + } + + [Test] + public void Validate_InvalidRegex_ReturnFalse() + { + var filter = new RegexBasedAssetFilter(); + filter.AssetPathRegex.Value = "^Assets/(Test/.+"; + filter.SetupForMatching(); + + Assert.That(filter.Validate(out _), Is.False); + } } } diff --git a/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilterTest.cs b/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilterTest.cs index 7b041b3..7f1ca29 100644 --- a/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilterTest.cs +++ b/Assets/SmartAddresser/Tests/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilterTest.cs @@ -15,7 +15,7 @@ public void IsMatch_SetMatchedType_ReturnTrue() filter.SetupForMatching(); Assert.That(filter.IsMatch("Assets/Test.png", typeof(Texture2D), false), Is.True); } - + [Test] public void IsMatch_SetDerivedType_ReturnTrue() { @@ -24,7 +24,7 @@ public void IsMatch_SetDerivedType_ReturnTrue() filter.SetupForMatching(); Assert.That(filter.IsMatch("Assets/Test.png", typeof(Texture2D), false), Is.True); } - + [Test] public void IsMatch_SetNotMatchedType_ReturnFalse() { @@ -55,5 +55,30 @@ public void IsMatch_NotContainsMatched_ReturnTrue() filter.SetupForMatching(); Assert.That(filter.IsMatch("Assets/Test.png", typeof(Texture2DArray), false), Is.False); } + + [Test] + public void Validate_Valid_ReturnTrue() + { + var filter = new TypeBasedAssetFilter(); + var invalidTypeReference = TypeReference.Create(typeof(Texture2D)); + filter.Type.Value = invalidTypeReference; + filter.SetupForMatching(); + + Assert.That(filter.Validate(out _), Is.True); + } + + [Test] + public void Validate_Invalid_ReturnFalse() + { + var filter = new TypeBasedAssetFilter(); + var invalidTypeReference = new TypeReference(); + invalidTypeReference.Name = "Name"; + invalidTypeReference.FullName = "FullName"; + invalidTypeReference.AssemblyQualifiedName = "AssemblyQualifiedName"; + filter.Type.Value = invalidTypeReference; + filter.SetupForMatching(); + + Assert.That(filter.Validate(out _), Is.False); + } } } From 027728e3b865c027f3fec4704295d58c4c1c4241 Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Thu, 3 Oct 2024 14:57:37 +0900 Subject: [PATCH 04/13] Adjusted usability --- .../Models/Services/ApplyLayoutRuleService.cs | 18 +++++++++++------- .../LayoutRuleEditorPresenter.cs | 2 +- .../LayoutRuleEditor/LayoutRuleEditorWindow.cs | 2 +- .../Editor/Core/Tools/CLI/SmartAddresserCLI.cs | 2 +- .../SmartAddresserAssetPostProcessor.cs | 2 +- .../Shared/SmartAddresserProjectSettings.cs | 17 +++++------------ .../SmartAddresserProjectSettingsProvider.cs | 17 ++++------------- 7 files changed, 24 insertions(+), 36 deletions(-) diff --git a/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs b/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs index 0a41b5c..dd13c50 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text; using SmartAddresser.Editor.Core.Models.LayoutRules; -using SmartAddresser.Editor.Core.Models.Shared; using SmartAddresser.Editor.Foundation.AddressableAdapter; using SmartAddresser.Editor.Foundation.AssetDatabaseAdapter; using SmartAddresser.Editor.Foundation.SemanticVersioning; @@ -53,6 +52,8 @@ public void ValidateLayoutRules(LayoutRuleCorruptionNotificationType corruptionN if (ValidateLayoutRulesInternal(out var errorMessage)) return; + + errorMessage = $"SmartAddresser detected corruption of the layout rule{Environment.NewLine}{errorMessage}"; if (corruptionNotificationType == LayoutRuleCorruptionNotificationType.LogError) { @@ -68,18 +69,18 @@ private bool ValidateLayoutRulesInternal(out string errorMessage) { var result = true; var sb = new StringBuilder(); - + result &= _layoutRule.ValidateForAddress(out var message); if (!string.IsNullOrEmpty(message)) - sb.AppendLine(message); + sb.Append(message); result &= _layoutRule.ValidateForLabels(out message); if (!string.IsNullOrEmpty(message)) - sb.AppendLine(message); + sb.Append(message); result &= _layoutRule.ValidateForVersion(out message); if (!string.IsNullOrEmpty(message)) - sb.AppendLine(message); + sb.Append(message); errorMessage = sb.ToString(); return result; @@ -88,13 +89,16 @@ private bool ValidateLayoutRulesInternal(out string errorMessage) /// /// Apply the layout rule to the addressable settings for all assets. /// - public void ApplyAll(LayoutRuleCorruptionNotificationType layoutRuleCorruptionNotificationType = LayoutRuleCorruptionNotificationType.Ignore) + public void ApplyAll( + LayoutRuleCorruptionNotificationType layoutRuleCorruptionNotificationType = + LayoutRuleCorruptionNotificationType.Ignore + ) { Setup(); // Check Corruption ValidateLayoutRules(layoutRuleCorruptionNotificationType); - + // Add all entries to the addressable asset system. var removeTargetAssetGuids = new List(); var versionExpression = _layoutRule.Settings.VersionExpression; diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs index 8470dc9..b898562 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs @@ -291,7 +291,7 @@ void Apply() // Check Corruption var corruptionNotificationType = - projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnApplyAll; + projectSettings.LayoutRuleCorruptionSettings.NotificationType; applyService.ApplyAll(corruptionNotificationType); } }); diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs index a7b1eee..2646421 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs @@ -86,7 +86,7 @@ private void OnLostFocus() // Check Corruption var corruptionNotificationType = - projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnApplyAll; + projectSettings.LayoutRuleCorruptionSettings.NotificationType; applyService.ApplyAll(corruptionNotificationType); } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs b/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs index 9bda1aa..d5bf8ad 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs @@ -85,7 +85,7 @@ public static void ApplyRules() addressableSettingsAdapter, assetDatabaseAdapter); - applyService.ApplyAll(projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnApplyAll); + applyService.ApplyAll(projectSettings.LayoutRuleCorruptionSettings.NotificationType); EditorApplication.Exit(ErrorLevelNone); } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs b/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs index efe702b..3407e59 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs @@ -49,7 +49,7 @@ string[] movedFromAssetPaths // Check Corruption var projectSettings = SmartAddresserProjectSettings.instance; - var corruptionNotificationType = projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnImport; + var corruptionNotificationType = projectSettings.LayoutRuleCorruptionSettings.NotificationType; applyService.ValidateLayoutRules(corruptionNotificationType); var versionExpression = layoutRule.Settings.VersionExpression.Value; diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs index 9c522f6..d0e792b 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs @@ -1,7 +1,6 @@ using System; using SmartAddresser.Editor.Core.Models.LayoutRules; using SmartAddresser.Editor.Core.Models.Layouts; -using SmartAddresser.Editor.Core.Models.Shared; using UnityEditor; using UnityEngine; @@ -97,27 +96,21 @@ EntryErrorType entryHasMultipleVersions [Serializable] public sealed class LayoutRuleCorruption { - [SerializeField] private LayoutRuleCorruptionNotificationType notificationTypeOnApplyAll = - LayoutRuleCorruptionNotificationType.ThrowException; - - [SerializeField] private LayoutRuleCorruptionNotificationType notificationTypeOnImport = - LayoutRuleCorruptionNotificationType.Ignore; + [SerializeField] private LayoutRuleCorruptionNotificationType notificationType = + LayoutRuleCorruptionNotificationType.LogError; public LayoutRuleCorruption() { } public LayoutRuleCorruption( - LayoutRuleCorruptionNotificationType notificationTypeOnApplyAll, - LayoutRuleCorruptionNotificationType notificationTypeOnImport + LayoutRuleCorruptionNotificationType notificationType ) { - this.notificationTypeOnApplyAll = notificationTypeOnApplyAll; - this.notificationTypeOnImport = notificationTypeOnImport; + this.notificationType = notificationType; } - public LayoutRuleCorruptionNotificationType NotificationTypeOnApplyAll => notificationTypeOnApplyAll; - public LayoutRuleCorruptionNotificationType NotificationTypeOnImport => notificationTypeOnImport; + public LayoutRuleCorruptionNotificationType NotificationType => notificationType; } } } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs index 8c77b7f..480d901 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs @@ -1,9 +1,7 @@ -using System; using System.Collections.Generic; using SmartAddresser.Editor.Core.Models.LayoutRules; using SmartAddresser.Editor.Core.Models.Layouts; using SmartAddresser.Editor.Core.Models.Services; -using SmartAddresser.Editor.Core.Models.Shared; using SmartAddresser.Editor.Foundation.AddressableAdapter; using SmartAddresser.Editor.Foundation.AssetDatabaseAdapter; using UnityEditor; @@ -66,7 +64,7 @@ public override void OnGUI(string searchContext) // Check Corruption var corruptionNotificationType = - projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnApplyAll; + projectSettings.LayoutRuleCorruptionSettings.NotificationType; applyService.ApplyAll(corruptionNotificationType); } else @@ -100,22 +98,15 @@ public override void OnGUI(string searchContext) entryHasMultipleVersions); } - EditorGUILayout.LabelField("Layout Rule Corruption"); - using (new EditorGUI.IndentLevelScope()) using (var ccs = new EditorGUI.ChangeCheckScope()) { var notificationTypeOnApplyAll = (LayoutRuleCorruptionNotificationType)EditorGUILayout.EnumPopup( - "On Apply All", - projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnApplyAll); - var notificationTypeOnImport = - (LayoutRuleCorruptionNotificationType)EditorGUILayout.EnumPopup( - "On Import", - projectSettings.LayoutRuleCorruptionSettings.NotificationTypeOnImport); + "Layout Rule Corruption", + projectSettings.LayoutRuleCorruptionSettings.NotificationType); if (ccs.changed) projectSettings.LayoutRuleCorruptionSettings = - new SmartAddresserProjectSettings.LayoutRuleCorruption(notificationTypeOnApplyAll, - notificationTypeOnImport); + new SmartAddresserProjectSettings.LayoutRuleCorruption(notificationTypeOnApplyAll); } } } From ad25c78a9cc4fc72754868ee3eeb0f170c54a560 Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Thu, 3 Oct 2024 15:30:27 +0900 Subject: [PATCH 05/13] Update README --- Documentation/Images/apply_03.png | Bin 0 -> 32917 bytes Documentation/Images/apply_04.png | Bin 0 -> 59252 bytes README.md | 22 ++++++++++++++++++++++ README_JA.md | 22 ++++++++++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 Documentation/Images/apply_03.png create mode 100644 Documentation/Images/apply_04.png diff --git a/Documentation/Images/apply_03.png b/Documentation/Images/apply_03.png new file mode 100644 index 0000000000000000000000000000000000000000..54758aa63a9169dfe729ce7fb4e2543e8d4795b9 GIT binary patch literal 32917 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 au>0H(@sd~#-k{E+mz7kKs1!GT_x}L)H@wyW literal 0 HcmV?d00001 diff --git a/Documentation/Images/apply_04.png b/Documentation/Images/apply_04.png new file mode 100644 index 0000000000000000000000000000000000000000..ce719b50e0da70189a07e31da833409921745e50 GIT binary patch literal 59252 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

+!tj{eJ&= zap}nO%$<9$wf5ShjO_?wHELKwUvN*}q^w?GE0fKQcvs!})fal)2kd#BYr7U(7^7_G zqHv+$t9EkLxhD|u4O9EePcN!a1cprl=LmxAbrp|<4k+SN>z#RZ?yh6#{x;k$Vi#jw z3FmWC`Hc^;H9u~w-|`WK>!{x1JX9Az?8L zh#|zx>G*}>)>912>$e$4v3ywtOi17G(X^{uJG#?t%mj4uVOU$h$7Sl;2r>O|3rQ{9 zYU!@i+BaM-m-tLM+gs(Djn-$?luX9+bdoUCDh2V?DPlPfQ}QwN{c`Rdsl}rgcfQ~< zvoOA}02q0~KH$FM~FLT~7IZCX#YwN-9@Jwv?kZNWd4e@C2yHc&CiB zHe@$5a!+K_j>i668tghT_p?s$T*E%2O>T{43UnNxM6{*o5*s3KuAsA}ghF9Is1(PM zo+68m7X}Y9+ovN?Wwu7+P?@mV1L^2*kc{G{i%k8nzz%S>+M){FDBI#?H}xsxP7AB& zUa71#@T^}Vx!J50Q`rD<1xjl^)S10b=TM}7sx1KNzO(@E@e(ZY! zL>0Hi!asMQpojBE^A~*Eh$HF+2@E3>W5T9ETBv$E-Im|v6S%4fuK;sP`QL6o2V3AF&#V!8LIjN#mQ`5IyZjXYX;`O2_H~9h%qzy)2Z~!+gtsgl+GE&1 zr7oQ@pZfF2I3mctJ9)r-y)Gx20f5p3MT&Qmfg7rC!xp|jOe#v}W zvSNzHCM=!ak^#<$V2H>e%uwmdf4O>PRoR~u-*tnl)=Hj#slBeVf5Fi`)u~tchZN>( z$~05Vdq?BViGGW%c1Dg;>-R@KkC}ctTMlY^Hn=?rt32W|msRj-xU6A?;UJ8VKNK7( z1UX5e&Fhla<=n5EaQGzL*Z~`hl=wg&oJay3KF;@!Q}7!UKV+Ml;N1^;mdZ@rBFb=<=@<)+Q@>3e zZ3v{2hE=j@E~i2%h4N$7GgmKFO1S4VEwz7%LF(h+>2s_Q&uL6FpS|UDda>N!>!(=B zn&up%&=M)zIhS#>Ia_O0uOBtV;)=r)ZTfvDW(O4=vtEn!+nhC|6Atj?43KG(Pauq^ zZ7X$vi_7=#0h6Apy=#KA#XQLI}()I-sckarrnGFV5oMA3k(M@UvDgzfw1#Y^SL%M){ z6sUBT1SUCFmScQ$Yt2TkhSjd|K0*@6Utad^4SXKx%bAsZ z@oLnEq;`IQO+zOlKzYegU}!Cf^P*CSf0Fv2xJ1kg-$`0!ZG>sH7z50Z#qHr#@mh9A zuiu;5;KhPuf~7~j!3nA$yAte7uto4m@>|+&$4+@mxQxG0bW?|6*{$@y(++DQ963<{fR6?{qGjMo?$2%BSl3pRY%+>~i3sZSegU;XV zp*_vF-VW^yJ&Eler6{CSR9Nco(R3tnmfopDR-vY?<{8!`m^NwPz4oF-)l>2DIXLAg zkMwu-8D7#o?z37DSD-e)8o9uV`ch`ixboNLY$v#n=cZQ*lA); z9(v(>(4SZhyP!`&x-|X2Kq{JT%~|?Dj+Pd4MIfZnWU0jG{PalZF|k0R<%JPktswzM zd?UAejoqA?uZR4`NwfLRCuv(>-to<1737rIIu$8@Bfd|d@C`ZfbwanS(z}ivdmft3 zem4ZR0JlhoZKKr~yCo-+t=1fBGdbqprSFb`l)p^oTrQ>M57HhgurHUm9~Ue1Ewr#Mm7O7#2-W8{!MfmOJxVZC@YQ-nE?2A z1rp{|w{GPEt)`W=VO{zI(ZQ31r^iJ*@ogvSy2R4f*N{{*Sbg69##*Oh+g{BA-M9<( zIDT*ogoST}yHwn@MY#6|g{x-LlUoFV0@m>!UTM&~7xmb=*Wa zCxlup{5aWkOYN0mu(5}V8f12yVIB)alFcV%B= zJ?HqoNZfke)rF?DGADmri8h?2opuHI!jYnzqVIzHD+o1HA@Y9`)jRHxDc%rBU6;^k z7NW;?fyoOU2X}CN7u%j&(a=Hn+f7UDsceVPbXEEIR%u)>&i2_h_jLk`1opgT&Q3;V z0}r^6$?Z}jd|^M`=kSt-YuHFuF$y>Bw*rIoGDZ>naujq@j&*7|WFPkLcQHk_uN6A8J z8xsc9EJ)QMJx3uuRrcI-=RxDNDAghxEe4q-_uFmhu$J#+x{NG5e4Ck_0VVbq$fn+H zpRn?)pR7)$#2iF9ZL+;KT>IRAWUFj8&pTO(EMUk>B$o1>BX=*T9byx2tJ}M5zit}H zhb-tlv3*-jgS^Zj!2=%yzyuPH1UfSV;68OD`+0{?dco)l6I1nX1$1bHZ>4eDY$ZJR z9@{07DEMViK{n=psXG+W3CG3>lx)Q&&51R_vHY~Kl$;V8kpFctN3CLA{E$cup6#Gw zZAmiLGzy23z=s-U`K@nrTxtD4nz_3;(>X%TcmDYrMj^~l6`8dwOJ&ep!J?L=tI56c ztiY2~3Nd&;f!-XQY|~_NUV?MIdL(F&IMt=L9Mn;*6}KhH$ zY?f01IZjxesy5Z!j4=IcrF(o@E2t$ts~6{kE4)WUkqZv;;cg)39feY;kzi$Wo85f~ z5{q6a;%eLHO{^IjftNEva`@AJUB3LEo}Ly2B_@B1RxitpYT2YVajK`u+ZfsKKv_GE zBr0YhUG9vZN!9p0a?Q_mrHF$^8Ew}BBbK4VmNE1Z@v@c@t6S9`F-dyw3b4fG^rql4 zd6pE*^^jDxv&=)%lF8TkL~%%zoWr%of`LBTA z)WiPGe4N5%IPRsRNW8=w!5-_~XUicaycr!-JXAQ_*EIAvt!3q&r>{k>Lcd|#hh|lo zZlpqK-E?McQu65q2@jBJUi-+*-D#2|hcuOQ2cJ=%*V5|)8uGQWom}Zlv>f=OPErU~ z)gx@#7j0rMn5Z|MLdPmSuB`gHa;3>h#CS_A8Hrt#BKhJ^QdYe>Z`3_f1i!*gp>DIk z4VzTcUxw2*A#%Qbcrmf#44GpsrUa}4oG_sN!8uf()4|Eh2RA0~@=tX&+Tr+8JNIk4 z&{q}J>VJ9Rbg@WwXI}V~(B&gy6K++KPx&~vbDo>+csS47qkld{b`3x?-d&q+({*fY z@oMsyNEVAr(i+ zcYF|G1@>fY*O}|$@~@se=`*l~Z70A6PHu3%hp64c zrT77_TLS*6Hd`~))Mg0N=Bg%B3glS&R1lgE08Wy@H6-Op)CeofUjA+W>l~q($?o*=Y(q>HA*Qnz``Nz0nL?w?K zpTHd`al~bw>$*pZN(mw%WO6R$ahrb~8ShU4HmD^$n249m4=R#I^`_ZEWUJ4ro^>Cb z=WzI=sH?o^Xs33@-zug;+4Z{}OUd3c8S;d#@+bSZ1T}TyhV1H*+*r-DK&Jh;m;Ijj z$%Y5~L{1P#0OM|gnRxC7i4=?Nv;IqlTVgm+LbPyuD$bdqy63K|?vl1+l}5ci$O4+T z3nYX4M$U6lN+Xv;a!+Jh#GRImR*{?0cHEv+2YhQ)^hjWG+!pwPzF#k8A+60Q4d;iw zv{h;Kq_ebI(fS&#qgf}>PPequKb#ne<1f*4ie&frh|I@mt;vlq9}`eDVaJwPJKE|S z_T1roD1yhYG}N+7j-=tjn7%E$`a z#XdUuLb+l4+u0FGXw=%1o|}#Z2YiEj#%9hZ?W116n{z(Z4|bloWnh#Zw8?nz#%%b_ z1Bnh#o8=U%)qIeMrIQ@#v#+S?x!_5eUdA4V zW7q_pwQNL@MBn71Uf(2J<8>Oh`13Hb?}#iM zhyWWck!mJC-46kQUQ0dtB!0`a+V`2=4kv?GDnvF3weVmWN2#+?VN|00@4V3Q2fmPj zy4!qynwk2egKbb>R*np(m72%*IrPV!_9LUxcNjymzP1^8Dk1OvJl=(4z9G$HcVMNRsCkwCNET4u9XLNI3vCWIzrU-Gp|SH;%N^8AC7|PpGnY9Y1g~3m0oWSo}-SRWxzI zEyZyFyBsuogy=c4?_NAt`mXYaP|R1P`&8(WW7mzq$_ir_;2%Ve@o3}{eBXr6@ce>L z9MN+Z5jq!TTO`3bS>%ONUbR{brDNdzRL9BIc43HFLVih!u3G{6xCjbMlHIpfuq3g6 z{3H6>tSnoBe&_d0qA@m|l))x?Um@m+_AxB;c|Lpf(Mafh5nN-b$t-E}0F%fq5bEr1Yy0WUC3zrKXGxsi*fJWB5N1EBrUecng~-ynB~;3SyyPJ{ zjGuQ^Q>{XKt+p%{s)lXLj{d=A-uJ)c^%hVw6P}d!>Dc4W+)^SBJ=uP~=W}u3!;YT&*^z8d3OE4g zRjdLmlvKi6@+LWA)sQ6a#f45c^faM`h$_8hcP~5Hw#4BLQf!qoUSin`)DUWiq3q{T zgz2k@>8SR7Ao7lDj=FOdXM=%hp|{|3-w9ABmC3P{#(6rXZ0+H7x2}v<6yjPaHthm~ z!aa@B(IVzfc_LGt$h(dmKRnopudX%hdII8cfNT@J?9-BmVN}AVftJd%fApCT@8~9R#)3<9Pas>UUQsjqjr10{l zKm+krlsIqLSsIe;l)QVerrDC!?HkYFZ;<0MDzjy{SWKr`r$Bl~0!XQi9Jr`sEZD~V-hx@ z(w2TK3i}8pBunYsMhklk4H75QE4iz!9s8&G!z~7xT3*gk&GoG4{R~SCGBTs6VW93! z1m;~N>tgMdJ5V|}NDd`TTEob-+^@e)`c>5ta}8~eG?5QcCH1(-sJluztw)ysIE79o zuNiQ?659{{GWfY4bZOh}L0OCf`wnh}rn6;ic{a-O?v066;rkE7$(JT?kP1k2Kb@B3 z`CAS2hj^tDn@)ysrg01HB0xp=?hIsnvUrbgy0n`X4tXKwnlW5qITi(2M+urnk zz%v!!!zF(#eC~>Qt(+p7H4pG-;tf66x!7v0V3FH*6_>aI)qtA~s&~ymvO9>-TPkl3xX;9jgA70b(3ZP} zK4%=uwLAB^vtGEaMxMGph;^CDN zanoazvkj^xOTR~q36ULvRECs$-gE1ArJ*QP727j= z{=Z^9e`^*&J5-=Ny8Fiym-`kCm@Ems4odkz1-33qp>^v$BTd(4?QTTJ^>y(V?12fd)p@-@RW@!R8zb#XF|e{>{XPp8 zgRq9=eoUgYkM~T!>KFw370;mD(M?I=<|gy0falmJKp4lRN!z?wU{LdiaW5vl$d{?B z2Q#1&;&#^e|2Bua$AVVuA%||>o285mqj2C-ExXy=GU0V#aI$d$YF8rnv&|+^ekKZ% zwL!1~?A@L$WkN>~y(e4O#h%rkt)nD2DIjGFuIxuKh5H1GOj5wuG+$&b$91$S4&QDN z90{Y`5s0Diwym7k-cL%CZ#<3b5Za-(d)gknRrUBQ0c9llrqv9u9XLO<5h%~8E}$*c z+L6(f28QB!I$~*GA#3oCAyBH0YCUA*)#v`E2B(VKa1b%w>>aTa=(5~2K`Cfyq%Jy7 zBwlpM9{|)3cYdAea!!4(B| zZcPkqvo|E&cOGB7M4jPzIc#k}04T+AuNnj8G)}CKz3WGo8t%5zja$zsg$LSHg}jHn zaMN*A3o`{SIwdqUpLciICmRE<;nI{8$-q(lnAR7KCNy8Ovzp8UosTuD>k9`}O&O5d zm6-6)EZ!K@H*FHHG_7U4$LKVBWkb$&D(d+c`k;jFRPkh>x{&6Gz+plqB0nA?-a+<( zu~X?py|F)zH~H8PV7>h0iltj5#f|G$(98J7I|^ZM^TtB0D~TI#&`K4ZfV_h2Xslru zoaQd~)oTgm%zzQBAn-c5baevRtt$~F8;HuCy=hD4m%INmD){$k0dCY4?R%)E3<+Cv zN?WN9ZlfG@(-pA3><&r6_a&@{c)<;}#dJAociXcylT2QptNCHMwbd>iO)-y5H0#x~ zYdp133YM&U^N!9>N6Ov}+t#e-RZxMo@16JM8c3$f$ zb#XaRsv18avU$Yq>1GQdd?7>gW2_d~9SKdi6csh!vat8ReEAWo4&Mq$0nHu9FG^im zdR&O+>v{NLJ-meGkSJL?Xd{t1y)Ooa9MoPx22+7)spbpXgW4Pk(!5oI{pFg&S{#^d z!?iD*vscIoFqNr;7@dAfJH8ohSaPmu7@zpl% zw%&|zGhD=t)W>kkgT=u^jb8dD3^z-do5)6lx^6{!?`1B@c9++JAo#ki2{fa#PdMOB zLr3+8JWVOLEz@vA;out?O3;$sK#@l--n&+3|M}W-m)cbG=BGv2V#pd*g(^U^ECr-C z3sEz|E({uE!OfS@5!McKF$MKQVf0i9xA5$Gfn=1+TsfAfM#f<1=o;Yq{iBR;l4S=M=<=~_IDveey zK04ods0Fa6zpp4G2;_|D`Yq}rwa5)Vsye(1CRQVZvGA+Lxe*Q?19L6qJ2Xt&K&t(VrzN4Y~@!Kvwp&}q8NjCt?YTC#K}OL=*SXM^0)wjw_f7m`gD-3cSf z_>!Q?V`p&xlhfEeBnav%%F+<=ZtN%>v;CGJ_MYubk5b zHbF;8Tw1Bx@p@EuA_j?gcg>29In4hWd^9w67N?w$9=M?!2);~){7{-@kCho}19P_^ zljewSfZ1j?;)$=loE+d`%B_&rg_%sj@mpSAK9 zt%ns>zwVMb?7Wo6vtn%~cuk?33(m%5esn|JONW>e0jQ-;5i*c?~wO z%KF`mPU|F zx@kEBV`L#9@e7{AliceO3R<~!2IYm-V-MPcd1$f6iP0gU6{i;YPWU;I7y2$OMqb~T zg{2mxhjIj_TJ_r8!D?R1Z{q_2gu|akTRTE-M5isxum&hWTA=Z-a=SG{!-`zIhvn83 z?e;BYQcY->DUeiFqOBuo)9sdk>?BA!@0M5Qwd4<+3k7_f>HtX;t!WC7_KJlp3^R~t zRW2kwZ7go1Ngk(SU7ky79X*NBQV;fo=#-8;`OvbOSPptGq)v~!PTE8xdH!`p{khne zu3?DXhhst+I2Z)Axrne%f=NBWs#e@4-Mk}3W>^Ul9GkI8U?IrA*p*BQQ~@VG#usM? zku>>VJ18^;Ku4_H9*&Qh`^mt}OazECGnYbqRjC#qGVFosj7E^Wt@3)G97!A{T)>uo%c>tb_grqwsL>Mo zFzbPeiBIOyQ-=>9KYg1eeyb$G5+RUb62j@e86Vt{B44|86!o)7fX+5zN z)eL?#a+i1(K>iX0`dI{mT9^QUao(3`Ozh-$mnA$o4InjKqW@S<@m~wn18sP#CZxvz zIm!6L1Pw^+A0+_WIHGi5%Qq`T2;&!>1t9|=(Ek=!o9G{y;AsEX_3uo02VOuKi_N^H zfrcvbt_0QGjlfE$smQo#=9MyLVwPs~Z(-cu7s9zRUBKKO_>Yj6GSL`PM|06z!0h8j z;;8k1z4a)@-CKC9ENi2S!vo6y`$&PmUD8Gi=@}21^#6~+M15owgDB(V$I(aU|9BJ# zJ*#7V8JsF+4yOM5BgaCQ6_)C~ylqndW05h@`Pnk3L&ozDB`g2?BlSu~@7~6}7kqNZ ztm}U^(;b;9l%XqV&}DT>wB{21`~Tk)Km)c35Kl}zD&3JCpVL{|Sg(*CVxoedq4P`T z2K>O5P*M&li;T*D#iqnc%v5nGkL?meq=Fk!MrdxO+Fv1_U&tlN$6sC+f#@*+w2>J& zHKrH(eQkpgwATg5k)|%d)($~wj>)70=(oB~5zd=hn-PvZju%P!GYq&zF9J;u!l$K) zdtgp}#ggaR@4pP;C4Y?ABVHDm!BwN>w^Q~tuVHXXiF7S$S!B`_3xpP zp+p;|#kFxcRQNsAJ(l$II0*~sIXk}#`|Sn(Ud&*s2ME5j!fE#*vs9r8x=O>%-*{c!Mz?#=q zWExJ`2uh_W&o?j5J~@yqZR7^E#r*Rfz?Hm54)Xx8DlX_0=#;$cJ8}*R8Qaun0a!{y z!mU4K?&IoXO!5_|vc)`su3{R*2l@AcBF<2KS63y?+GDnWS7cpD;ir7MTPXJq^^ko4 z90Z|QEqMqiE+zqXgVeykmlnlcS9d%SdNhv3n*kQ2pP`}=nDyzWk4_$XYR3ZTl)J`S z1T+_&l}uUO0?@>Rp>`1Yic+}@8BYVG<|^>h&@fKg7^?)Grviwzb?Lna^c}l+0dl*M z7tt2?+ZS7ffG>93@#y^78dY5C^St% zsY%L%SHsLYpeZl!^B-#kdD_XQ^lMsJ7^kR+uB*P_@J0N%9~KQ6egO3J(Z&8FYO%Et zjsmj8PoT3eZg5Z6!YbH+oT%d#i*RbVneO^Q+jLV<5a>Vn@xwZ3fd#!{>5^CXV`Lh% z5cbT7-&LfvA=<6qYP{yKRcRnQUVGWt6I7dpqyOP{Aw1aH^xFZnB#Cjib4iL(j&%>f z+t*N@P%}Cd!K5S%D}Y<2kThCe0+{hfrRRL(pv|Q5+gR<8iE5Og!IOs-oZIbE=ZCXR zs9HJilhH{RcWTgfIJuqg!6#5ZA^*)fIriqCJ6|RqI%OyU_^5`uM2n|UkgoJ61GnO- zozzb5R$HXGv)HU?sxuABX=MQOL~c`kk$kggt!2pv!fH5 zuc+V8-vWcoa0myYN8tAZrR84wJA^i z@EXZX4Q~T4n43H{I|ZQm@;ANbp!fPT@ZQj?Ijc337kLTJHmn)#Xkoi|`rZ;|v-fk> zhiAWQ8Pm9F@1tsk|!)jxntgI2+@IU?^y zQ;;d0syz)`r0&3XP;8>6aaR4t@Bv-{SZ!WGGD(*9(P~1$$iY4RtN@9p1G=W+Zkr4- z1pW=p+TE0P9NVf43(|590||si0M87b&srZt2i=v5w@xN6q&Rhp=xic7?rT3_ZcSYf zMU~3K+Sb#WxSkg5$(4_kg|pIB?0w8Ud-FBovdi}5b9N6CCliNS9%1!jdGQ- z2Tn!`#e&Xkus6V9V#6LNU+xh@`~04KN0)tedbn*2%CSRd?Aw(Vk5K}`!C;LOs{;_? zz?!17Y@8OhX>$5f_ctC|q&hbO=IQ0D03Oe2prMmKcQ{3Py|)#SSy?>rG6WmP9u)g2 z$O^r+dHnTpG?@u{BL)M1qo6YvLATa0q8xno5s`SWsFD5D`ws>0#fptHk9yr_YEf09 z(+I{0hEa0DnJSckd~g_=fvL9WjG*D7FW z>JUt&w< zwxQ#&s{5802**rQtlg&Wchm8V)q@!iMAg17IH>)W7p9Z=(Vk9W5i5;6wCeNTD|>ym zX0iM7$nBV5bD0>ZLb< z8lFGMY8O<(%*6)We5V5fMQVT@kW$9s^T`uKqDGivX&~!xrNitDOEa+8_K|d1*fx|O zdMj`WE{BpCF4dsa!JO^Kndfd(y|S$()9P*khvJh*>O|$r@)obV#rI`(MF2|CB)XW= z$D+ZR)k-@Ol&+FkcBME)H^VJd^Yxp-Ds}?;$2)UhH7ga0)S~t2-!O7QwW_})btyeS zFaVy)iAV=J0o#psH7xbS6PfZY0=&q5I8*`yW=nP}omy8LVJqmR^PNMxUz*^h%xjj0 ztWP-h3^OFbJ?z~A%P!z{PKa1kDIvlabH86oy}1OALGk*pjRNr6uWlc(w@EIO^O!Ga zexW?sVv;)B>zkc27(v$2%#yd12AnPD6JzeypYIp8U2Xu#(I4e|Lg+1wOFJ{0hT(3gPjk*#;Qad7CeS!YLNAG6krzQM5uTjqg*X5l@H zcrXQ%EDZuzU=)0!;zh|bvN;xb=!u9P9B&p_(h1Meq2{Yz*=xw6QWZs<&Q2>EzlB3ay=9K;_Z$dp`Lb?m`({gssz@vDc?_tj2&? zun{|2YYP<%zU&rp!^A0Kn-ri8ou-N-2diXC^OK9QoYc(AQi% zE}}_~TA}qUftTK_wG=M=Vn=Z)8#L-tRAq;dkwYf(Wu?x$9HL-@~S(!lw5X z#=g`#_@SCBPwRX-zQ0${zOgvepYi3cg_N3vZcMhN-pWlz6INNl*W3YNG9m?oTh{WA zTM0M1geUVJ%g-ssDAv+(DoniU=@PxQD3Tl~)}6q|Si@c*oXkVqoUTZ~)hiU&wIl2H zd5YI0xF}877Baj$X?a}mUZ;M-JKh)LnB_8tZW>Q>qK3nEALRMf7TFG%n{wMF`<*Rj zIF{nr62q)F((9%0Q@6eVVcCS&Q$U6e8$ko0%3&zHQ4I)~yTpWTToPh3Car^2EJG<8lfc|xZpGN*G3 zBCp>fK(;xj$Q6M9Zon#EnaP}TyY&r%3{7bYOT2}TvUCb{ZruNd`75I|S zYc?yJQwA?&$kHOP9_}_`IAUYP&3(I9XO-w36eB5ZsWKr@EBp~zefuBzy<}tq=qf~3 zP`lGkCM3MCe3#3C!i3gJtmF7CBvqnlXPQv#s%YIaeQi8eB0V77?09u=JWV3@sk<97 zapRm?^G)>^99B!6vsNvu-O3QQcD-`0y7wtF-f?eDpP$cgtAx591N4yG73FWwr|v9d zL(mSp3+pd!beMUVuk}dqR*!YPbzR7Sc;g~S84!iaYoPUQacWs?!lb^uTNod+B*;L5C7P9=>z( zgjsJ51Vfqu{KZNagVS+cL%LJq{ZXB`w)NkDhxf3C(ZniyQ0;Ng{Uq8F&HBRvvq65R z#{p_%1blP@5ZKE6J>~eDJ?(*=x4G;lnOua#-@n|!1$>HQ|HucYovKP+u^kN{_1I&Q zhv$GQqnWX~rDyr7{i9*RV<9p`0F&r8R5tgqWpNQu_Yl(2k4VwxO1@-wY;X6O zFzF>#vpWlwmP-*1T9zp+C2_+r=X2nT!gx+>MRlT`r#ZHW?t48pjF zd9Ve_5qN~IQ{sz#eD$0tLlE+@7(}eCT}LmetyTTYRdsbAvoAP(tDh(eKf*oyTmeFj z0MMHDqXB~3TgB-y!|X&ugV;Ye$hZ>9q9e?%_%8Z@c*m4Toq-z zJ&oemMBsI|92`qojYGN0bIFB8-R;bFi`9eNtLEdbL43QK{jaTZP-TGd+@-#*XP$u3M1GB*BV2 zQS5WQUstm4f*PX}jNUpkU{qIJZ07HGbe34ZQXywKug3JG+uk(8gf45Jwo&VccEuE z#=atT>HqE%0#LLvf5*6~j>Ft6sIi0#XPSnQz~pTl2oz2ohcq z+v__wyR-Al)yK(Fjh@M`#FIYUPI{AuDE&GbyR)=r!anfqbZ3~>;zWmf2IjUAL4ZkQ_2nc_c2&}9CU z5%7YPXb<=jE$)1~S=8_)dzLF{FRcB=%!99VOb*)@gBQn-ra=u*$UzH$qC}~H2MJ%^ zJs~eUeT!cFiNk5(_PMXoO{gbt*#RP9+lDmlLA-&>13T+D=ikwpz)NS8d3EA8$&c$c zJED|m9&FpUHbJHZDQ(e^nV=q_iXf6{QcfZZXcvZfO_~u~O|<8{E)nirB!yaX(kN$4 z%`e@Hv*OeI2m!TRh(z5IoY8|}4^;Q@dho}$cynIo`Gh+ig}ttpnCR1Va?N{MrdF9&P&z zY!j^Q-0dGMo>xwxTcu#HTazoScI~(?WdzMsHHMy|n4!g>%LI)t?bD1<^{B1b)grc; z%Qf!}6;#Jqz+4!hDc@mP`=+HKd4J^20;3Uc*yWFN#HkvcyF{8lC?^jUJ*mD|_Sv=Waj0}^>W{%3x`TPBE&l4Q?K|8U)$6#ygdsARwnl?-e$HVB~2;)ZPqVotfyXwjzGNerI&!_uxI?J%`qbH9h| zlCd&LNLJbiQZ85x@bHRX5nyn#NWrLm4LgP?M-iXyv|T4)gdN_^A?G96%+?+SD#bTB zH#$&&DMN^kI37)VS*W@MpD^V+Qe&w@fU2bP(oTzPow8&zTOHn;)y_}LA8Hd#mnwrr zZamDm*UOjO6~7bDl|0>4WoCaR8?vmg@;&d{Ud(sm{rB1y2Vm|cqh&N;8P8c#BL+hj ztvoW{<(8pD9aPW0(DQi?fumnPrWpht{{T=uF#J4jE=iF_WX8_9n^itw%y!7HZ0(9m z-Jxg7scf*)%V##>d~I$$-5B7fk;q(JVeO-1XZM3jFu73sBx$-ylsojpL#JBBJ}pI^ z{tvkYwkR;Y=MnZ4wagbD%nzU!AjKqEJ1FlxMltVWdqbv?X0BDgF%1CKxdY#b&FQ-R z6AcA;^^}=nKk0D#|{^?A7>M3a%=o6QfMkd@R*C|M)w0aq&OKMMeu_ zWxbS5PB@^#CL8pC7i^VgaVZ0v6~&tX<~D&Sc5(ELLgrpGTi*t0~V$4l-0Z4goKOWf~iS$y`=K{@rsQBO;{E%QDAT)NS{4VXa}&u zUqeDCZv^DX+e#ROX#_NA_~UE%2-UkfKgp7xpGv2NrQ6-2jjEqHJ=!&$jFNhMqmQtf zf*Q4&P`~P!&yR=(O=DctabeGtK;cxn91UiLDaY=C}DG z{~7fCV|;H-37`#QZxCnHasFhMety@#pADe0J_wtfE5*ZWzm4ZFuvL#VHW^{HPnxS9 z*Pjot47o%|;0-*ow)^f~HvbpLB&A@~;CtB*pO9$4R9#gUpSka^SwE(~te8*AW0-e1 zhL5aM`|XK$!eyS_GZD4yNq-rBp&z3Gt44&3{(SRX2nsC52C`VaDgT=Cvt*g8pZzXL0+C@+Cw~W< z%)zEg!Jl z(Lcf`FwFcIOB}rx&39?}OKZB`GdNLFdH>v@m(Z56Fz7`Df87gz43G@Wuirw|>&qKK zYb`i8}U#0%n#HWt_AyV8mK_b9VY{H-=`)(DrO9H_r}1!%%DAMt$Mxpwi_>4oNdq{K=)&Y&V+tb86!V_kFu;0+({i}Kk`)J_cZKK*OY z{Led6v-DWpT)Jrjz=CNWpCgsi+f%Cm>lX^iO}4GJB9ruR8Ul8gjX?9 z=>{OB-3%hh1z1qow*z(I5s^hsrrM^Zm$--LE?Dn%kqrR4n8AOIk->%u~QP?57xrI7<~M3 zBG)7*Hvva|le@wM*Xd(*@@e}0x`n7+#3rfGx2tIwJFzlUP{Q_vLbioHAmEt2skPOB z=t>z;PqEoq;QAhP2M=<(KfwA({kXn`L=QuE%HRNe7eyF_-IB*m%bk1x&1`!znA36I z+fBC>lyuI^sq3;=T`#ow>i!7`6dOU?StyDy0*8l1s_V+vkDot&xzXV(o4;B|KC!>W z8xCR%aR?pRYtS@e+uQqB>Zy3AHh&Mq{5sprp_7&%8%N#-?=M{*79H)WIGaMOF z7H*b5oBgcRT^|5)znm9nlZFGPaID zm=Pkeu~)rU#o$A)$1yh!_!IRIE~NGg7s4C@Eqdh$90vjHxqgNX)%oLj$`VZ!^@%#H zGg6ZNKxZvpb6XI+KSYN^ji%GncRBU65nma5A;C^5UMO3sKdKlPHJZZ|OaA}_ZuqmV zDioc1q0?|AsKGP|2!Z$KG(c_wym4S3K|#{FJ&Z7oRB_Ve`SP54UzBUvhJc`NZG2g$ zz}j~{-J_8~v~XGK&F%rfwB_|6qMi0_@{}GRnM>?gt^lij4~A#-z3at^9UlbvJkc}L zBYDU)s$601HcPspu@6+L=*X-h>qR!AFT$tbBnrOpPDs8`HE+y9;5N4|>85bHVmTt=j|M2a?i_MxxXxX*3cjNpo_{iY_@U@J;rmaAb>y z@iVy-+Xuo$hIm(AsugYs$=+)@;wNsWS5CSy`%)BGGJk9b35RO>YF@O#dt*-dPO_+- zrG@9lNlvW`C64NLR`8T?_My;5>j{)Q_4hcDDzUc#Jdvaf7(%!fTQZ8(%@Bi8FNTh; zs$-&H>%f1PhB^zi2Z^G1^880R{S{vhhJ&}P?u^wpA1aq0a6#4kf4?lcM=>C=+t)7y z^!2VQpoc5&H`B?pa;0^QGs2@&(P$;}Qyb4tOrs+iw7E~GZ+GUcBP_^`B?%P1~YD_-@nG~lj%9-E^wQu<{+|>~(*?M^?$r1Ysz*er7 zfQ6^A+EJDsy4E-eQE?5Ta$ob33M?ayms`-|03_v()b_kAP*7>tL1!KT#t#64i)85g z;tf@mE8#)I37S5yH<2z|J%f&Ig~!-;^nuv%9k%@(=N5K7?0Q65;j^vp=d~CqW%y#8 zNFWIs{T?a3DltoA$rC7Zze1+;ZQ-nCj$>Uz*9;mr-`^s&51u& z1%4=!88L?FasG&}1HloI?zwEl)G{Y7Q}4UX#1N)86gmTl-Gw%xB!CJknVrhHc%cw;N{`;dHC(?b(^L{`1SEaS?Y{ zr{J5?=%&4b649zJT!4}kQ9K@oDwh;6#zdT=AnJBUefiTW=6OP?myQC8Vi4>SP`at7 z8qh0BO18@Kn;3>4y@ho&K2zUBSP^!Et(mg^X!fB9nZ6Idkas*hRV6FlxwC^P(ME!o z(xivc7)=VPqR}!miJ;0ffTE^AC+&#nGhl`@n?@#~3n>i9Klgp;Xk~SLnr9P)oTuUX z=t_<4MkpxCi^wd;^zVW>RR=uR+)tO(YBY=atzRJU=ELVe7>gvEnaX3?zW!@#PoPdRi*#&Hl($t*na9T2z;$EwC1N1OQi zhl}MHWbtI~bzyC)Y1v8>h_B-riBjkrHjsx{N<=+v8Ua{?%=044oYLUyE&~B?%u(R` zKmhgr)}ei~OMz;a<`K!Xb`J9%hzpOS#VRONpC@s^cz`YYex{P^$*!(`sn*|FMy+U7 zB5?FxVl(Z@U<0Hb1o~z2(n#rhCWK`kj1pNNuECsxG_Q&WZ1!*NDkjbu$r3&g5$F#h zvK0WhiqI>F!Am5A3MZ!`L9HN$3-rHs9)uXN{kI5T;WD#<;aacKYcL?N2BM?VW)|8$*$qvI9S5$0}hUl^LD^-QWE^w@&yet zClX>fro6zvV~r1Y^)uEC%>&`W1`frg$88fM46O_QWa__PYVNTpsgJBdWLo=elW19b zTJ>>9%A{X6sl^BJ6a#;HM*ML@mL~t(TO&8GSa14v%*!u-GkCGteRouYS|`izbTfS3 zu=mm9g-6ENYdXm2W$s4J6~I;V4{x5^y7`s2rfR^cp6LlVcp=o}P3PlM3-s6Jb_>m20KLImPk^jH(}y~rx*WX72M7t6M+d7NSL4Jz)xIL~ zi9EhwLy&s7hofxatbk}oFDuaz6Ns7Qr@#3i;oia^l5od=nRhNx@22$vnqhXx&zYTf zSzGTy#SEqJ91$$^3r&oYIS=y%2ii=|86rK_2R|n*#>9|0uB2WCK*=4<}OOSQaz_VI8E*pv9eXy!PJ! z(A77!#^^Rf-z~BdEu{Q*?;24E5kf!*lz(BjcoO+v-$*{$&T?cq29bom^J|9z4f-3d zi>cEf*z(uuK%x1;Znfk*o*Bcqim6_x2ht;4(Avw*8R!abRl+Ahx2CE6Dw;$+si!jZ zcxKrY8xBPhhal*^1~c|*vDzG&OL0c08MtcBPx108pfA{_)!a3-V`A*w(KOE^Y;y5A zd^&sx@sqTi#vLq|Bo_k+R(>Yfbz&45b==j!iZ*Bt^rIH7m)PZ#_B)^lJ(%6oc#KBZ$>UoL+L>2#+&(s8Szfa3urt8z_6mYjHD%Pw?zN(*{}`#Gi=Xc5UgRDgnFRK z=IMNLu&}_(dz@&z4Y-6iVnjXb9$vz92@X-en&!=>AXEUVm?N796mj_#yr+fyxy4H3$@**2K(yObJnKm}w;Lcf zuW%fG?Mj#pF#_r1pTZf4hn(?ey5ytdW8U~cLXyiofzM|FI4fO79|e^M(I`JW#n+^% zF-m(Nv>>nY>$L@#5PTi8!8?u%HXI=>>kjPbKjy=Hq@hm|%2~L!-?c>Ds&f)^3!sglQ)@9&8WGIENEvqQ; zzoou|SM6=z_iCt>@y1n8AvEd39I`*?)vYV)8a!kcO@UX3`q_tNn{$376h$>EKWA`>RPv%OoE9}))|qi z=Pr}<*C?VcaC|EgsK8Tmh$m`^URponr$6DEq5r7*PDM6QX%8$3?F}@#pHdvicEsBR z+)8oeJcxOCU6p5X+ApW;*!K+U0<(cDh$+rxIR&a^Ep=9G~^WezmozYK=% zs?NqN^|cxrC+!(64P&nv1sjlq4Qh_TCN})8Bc7LaieK9Zy#CyTl3B~Yh1mca@WlyN z{q9)QskCz9&nnjC*=_)aI2URPp%#D5cy+r(Dt9^N)`*+1tD6!s8C9C$6?*Qwo}Apm zyL=!VPUm|L($Xec=O;A!G~3jdj2esxBEPnmQc3zsDVs+j(iy)=mK3hv%hX0}0n)GO zJDtWS^F<(Is&{J?Y~G6Z!<1{tA}KT5Sy>c`nTnLv#-ri8{0m&Lcy4Z+tJ-C>=P>VL zovGBZZZ*$052|``^tD@9Gzp=NaGH8x{d>vPOkX zLeB03{^n!$)5(TXca%1QmRSNm;JL+pe1&!Wl8B*i_k_i;p&?Ht8ILov-#jQYYs_f$ zaezUUgB7hd7+IO6&>o%!!8wO>ywKmp-R?>l_M&!wN$L4<{C8!y7bD|dQ=baFA}g6? znf`Z8Uj}WQP&J!0ekJwX4!@J4gtoIwOqjZiuE{bC#QP+(6odHT<(m9|s)ySIKxFZA zi@qKJ0bI(OYpyRkkP~o~cV;*Np0!bUdB-16!3BLEHT053 z>^JTsl1E)RdEDntE=sR_I?vBYFlo+1)Np~v6vC-RT>q3YD6bOn**uv7<$k};Bw;}$ zkn&Lp`m@mesiyu(UlxG)l<4u2 zKyms8Y*M|WhqT1MyPu(4`FDKfwV$3gfQ!Zczu#$vfkJ#>;OO~z`FB0P0Z4TO1G~}w zJm$Za=*#vop6vg=(y{kHRew=U3#Rk;kV4{xdf^hf zh{8Sf5Po(&;RuD_aL?jvbfS+sz<*}OPE=@*N8|4(Qj8nz%YDDO6mf3}%*y*wvhaUB z-*JN2)fC_eeHBtFvVl-EPL%nOpCg|2ulu}{(uH5EX9`NkdUY=^Lb3_}PI;i?%^$Hc zkWg!yRr)n&VyCokBy1?fa4AHDn2XvuFuRYp9(?FOw>w7gYL{b0sNJOx1Dz#%U*{Tu znOXewduFwD#9lo)*{c~$EMw3s+F`hrV=)TpOn62uiq?Vj<`pQ_eqh#! zUxh02>Hybn07v?4R!67R&mOar@gV|S7B3y~C!iXS%am9Aprc~KY0@2Me9-)w3EKJX zy0`%(alt@R0*OhkFy(%bP+Z3q-o7U5D&Mm3L|jFC4!df6FNF|jUOzJd+qJRRn7_ThAjn0>30z;&QV+TqAG=fLT? zcdjS8D~18-0G-UMGH$A>{JnoX(KQtzoCc%4*tEA^_hd7AyJWoZ(M$G;DBup_ z2E}s?kR-ve#a6Nf265E@350HnU#;y-s1!u`=!{0ERM946Ern)_Dv2 z<7swQ5+;;oLSgYIFfKXd{$AMVCdeuf74*M1t#mcE{x;bkD3pXQ;0=KZ~SFCLmtviZ$5!BatZ(#08um8-i2}@dxrg z6J|Mi`bVF`b#fLNaen0ee2yu)Oy!kNSt}gxm3syg2+hRg6jZxXa&;&0+s%*)_wRCk zjxGTwz7M3#VDtPUwtci#lRU1iQQ$D+{BazS4s7XHKQ z83X5AR{2qlQ4D%<1pbImi@ET}pzJMxj@&o^?$lR=-AK8G?BKKrZAE7U$E>s9$_L3Y z#iX<^AA1hOm~U3wh1yQA@2cTHBIQ3ln0@O?pl#hp)=b7fh+u{BX$dy=~XwEqN=mwVR zU6;3;{+*A9-Kk&Xx-KPl%{pOh8={RFcM5H_*SaqNn@Q}+lijig;F6*r4iOe~06FIU zQ7_8eg6Qc15s#m^d{WC7&`z}61}u`&=PKy~wkqY>H~u$RBSC}U14Wmr_XLgefX~D2 z_<$9NU2T99jSrv^3uS1K`h`D-=!BQaol|=9q5KMJ%^kVsb8~+qd}1?H1)g z6lKacgP=h>U=w=kR~3Lb&e1)-)NNy40Mu(>S-U1S5VbFK`56 zkUBl+dJ())xMj$#namvh^Rx}%rAOsnCoP$%idd-@Ak)?d0uUDqE1LpOHY z=kBeLE!8!5YOupJp3adG#$>Kc_V{P8B7DTzRw&Y$w zV0I}9M|8gqw;uG|B84D}hgyajd1H5})y|zE+o)*ULGSssu5rUHw~a-QG`{9%mhG6s z1@2#1;jP{XT0E9hkZ&{=_~<+XDJ8y=^1HYJ9f=Ai>okBOnOzTiHkvl^yQyr*z{`Gv zP!CQE>ZVkcm&#)W~$dZ6iTGJ)IHl#J2Qx?T3RnXsopmu(9DP?jzn< zXGezn3>}gZv4bR=e>K0Ou!3qIXdmh>_#7i`mDuS@+aJp=scfGXLQy9|Ra^Jxq*9U) zzfOFFqJon%fP7u`ym5FC`a~-MCu=0-%Cxuh-k()W4@po@_K3O#Ak1Qmy~G-Vno2B0 z1zoIYbnGVUpU0ifJ;m1CSeYhn7qb;P7ti2|Ay70YHN$k#)H7j} zLs9e5k%;JIK;zIUWb{EL4}`sPo}tF3gOk*)Loz*T?zCxe$j|Pj;Y;4RK$n?5W^p;@ z!!<;AV}BL!tGh){6Iu7ohNxxbj&w6|D_Em0`k(IDQJbrlWfh=&#cXf2qlKm!QB26UIuBgX9 za?@hkSElpDu%64@4fa!jMqPpUMmLF9#qZ5mkN_Hhr#4GNd>ks4&ne{WXPJLpib(37 z4^e)dn@Mb1Au)jqkYgDf6CvsZ`OKa9IVr_86rRSE1@~pGoW6}590cT3l8YunG#iC< z2{IfpoQvClawHtQYpZRT7kY$#psBHDA*>ZGrhL-|^c}iZ%^1G)9JH8!nDo((p+eHsB(j*ei>t=@lvQtqDFaH7Y2A#>~rA>lm zwJfB@+}u`G`?3~K-#!|=PCh?o1CItn^aO7c$a2Q_MY}wkDNgrW9Fbs^7r&oay)}Ia zWBVRjiAT2Bx5627g&fbfN89yP)gC94emGMdQVpCzNdM)V-_U;1IJxN zTQv2dkRFvH%{DSo$mlS)>EGp zMyZF@YCqpowKCQF@h4gPlg*4Ef&7VFlWgP%3P>3;L zhM8W>WlgbkLAO%bRD)W|!1)D_+3vpSVA`~B_E$3u_>5a~-)aWUM89KL z*DZfBY`*xCs2cdVGMv2ho54sVWG^~c(x4yU z1npF^HQ*#6S`w0M8v3y&I?4y*p_f9AatGb3ufXUV5p)TX+;9PxV(W_UnQv({5(ERk zkb+wH6a5%#;IaADo1~ejz{=+KyXU)F8P5bYlM7Fy!5dYpfoSk)tnHduaT?1o>CNw? zRnYn}ba1FdcO^l+q@3f|otMi>v#SEL&&KEBJJ8iX|g>kr<o5`#}IIvtQ?3nnkL|v_4fYNvMZgcTL$q%0%fN&zLlgWp>svBG9ne zybT}?PTS2&rg)$A?gpCF3lIzws%gQSUITwme#{}n5TxnDc7K`m!?J9AAc81p?{*r! zPA;rS&W29va&yj5RmXe3*y2|&+&}>*I;gEsRL(~nLT%#Z^R}IrXe8`}Y2k6^_VfQ3}t;OJ-x{jpMUPh>-=9L6pb?|0Nj>(KW~NeIn87S`c0V||9Z7{q=0 zT8tAi2W(TM3(R!-t@_r}=%nh;wiv1OHBX+iA z#^G4CuQE5^Dq8FX-J6N4(;Br!?>{3pI;#@J`&Jg8-`6ZVR~uPmMejbe`UBfoEMvD- z8|K5tiG7xt@o`n8RHYVyJH+kVU;O#T$)aYCIjCm0zxR$l(t|8GO6PdHztUIpP|&&3 z+3`9-KAoxhn4v2e(R~UqN=Zc~7CStT>gzhrdhyP65alQk-XT30sy^Q6A)_Gt?TuvGlRzR(Ry!YeABUx9K+NC^9H$su zsc3{$=n5#NSjInp3nvpkg=j`+>9%=6m1>=zi3^~`wXE$R=!*Tmz6TB4*dRE^wbz*5 zt1*Z7V!SAz(CA}tw$?4FQ#D@R@Py?^1X_1eO?)*R(AY8U-;q9sIe4>mIY`wWGqctQ zO&ut#f`aOL3BGVCyp?#5$} z#xOjxFsgDs{*<_>3&%_=(0;qk(Y6XVy)^g8d0jJvKNM_xcC zW9#@ZwV+^1JghiBv<;7@_bzdcpL)14Q%;;if`I*rua4N1lyiop=(BI1hw5b2+eoJ+ zKYM?2YGurN=;H}hReYb-%I^X2JeOF9TGwh<(4mgTwTVzIFg|wW1ANwD6{KuDrE{-E zf9eV8`ZU%G9zl#V0Mwe#-RM)XlI;{tCC59cIT~M+QFRvXz3t=SV}m>*3VdgnKuT9F z2r8_zE~CI*HLZn@`qoRHNj+R9x;l$KhU8^i;7=MjahTw3CcY$?Ba(-#zWv)=ZSj5= zcgNFs?8EgJ3Pc1_IUq%3&Jg#Ni8bskE1Vd9rVWLTo@&pya~x0KMHN_3{0qyOJ;QP` zus5~ALBKsbr^fM#`mcB$yo(b7d)BX8uAL>_cWU%jLjz{lqec;Q=Ef!&X%o8Jl8ZF+ z?E17x`s0R;gd%{HmtMw}RN%%|@7o<~{2~+H1hkqHRhkZ$72DR-#V9N7CJ)p-DNIU9tKrKQ(j#(rwGlp=H@t-!LwBW6&FyQgg z4B&@f7B3^0^x+3~W(`1FazT&t0UyTZvua@TuNs}A?Z}*OEM=0Lgc{cQPMN8I_2d+o zrs)Fmgu|CjeKCadqQ=@b$L$>pYs0E#{v=b{Zf$fTgk>M_JHEc2aJxwJNJZqwEJP&w z1VZ3?sM^u+Y@M0Kzg#)VwM}uZ@|*<^rB^WgPQ~B@d8vlOzWiXa0_6pxG=347*6`rb zvgI~(sz|dV9aQeEyKXtE3J95QT%GSXlA3fKg*dH5>EV2X2$Z}ewgSS{x7rA#bL9Zj zuBxC_)xwzz8^lh$k;?~mvVN$~GjRf0Pv1vX2?0xz2?j#%^}0$vXA!P438)H>3Zz3T zf&ojXG0gBo#>D#VonqnS%*dV2=v5~S`)jB_MSG=3o>lvO zmy1Mn-D3MHV0d7=#j)4l#Cv`1nCmX;@`W-j06ma6ik_Q(nV?LL?La zW!bHIPCdSLANLtQdI18?^7y!Ey>p4<@1Ows-a}xlrA?^3utKX)2MS!|4JbC~z<4)6 z^*8Z*n+pv)CX+}F!zcU%e(g>TQnOsjURJqp=F%$u7b|e{r#tUxMw1Y!c!h zCoKdz(UWNT+NLkw`j*&U>Xxa>x#8P> zh}Onx>yd`5`P9E#`XMBngJyY=23=MC>fQe^@Up+?WkF#NIi}rs0Izsi|39$vzcLMz zi>P#L3PuY5hb*K};0+n|KROa;vP?Vw{Pu)iuYpz6d@4bzIW>pZ@5f)mQ+NMD3lAEh zmzQvLJ5!06==7eGS3r_)HXswJH3rfgs)vB#b>|B3hI7IhqW(Pw^04)#zL}f&@|0r# zE1L0=qU68Tzr>pP=7xW_Ujks}{z{WZ@a~{(19e&bvo@CNz=55lhru!4=O5|CADqm~ zLlQ;Lg23fZa82ESZ+~WW&Zm86gk_^8?&Gr8w3OVyq zlivO_({IFRQ?3_Ze|dg3BH-`Jy57OGWxD^-5B+QV|M|0tgc!w?N=FHeFbm9O;b8ck&@%i<;PV5Ca=n-CHxtxS2qal0N9#U_nno$$;i`j6pC45Pn-hq|hmnuh7WZ z^LRm?=DEc^cc=T+bXOy(?ccv(Q39M3AQfS`^Wz*{^`1n^9QAfI=$QMV9+OGR_96VR zCYz?c08)XlL9Ruo&t-#38jF~T>sK0ZMQ=bUMU-D~^3m8rJ#c-k2ewJQhSXex?HO|6 z^*}y5%IyHu3yAt9@Y`fbK$78jbkyr>Hu&{SU_!%K6!t8ST`cu1TM=_dw*vOt_f`*t zfHdR~lOyu{3`(K-%-)&k24iZdrGUcP4>48z-_;ziS_j}i8WsSIjCFA9Z^QIK+M);Q zh7~O7aK1vT= z_cy^wa(mmN3KA?pnG-0QCyFgz@%cRQ6!9IHHa#Us*?4x+b^5c5K_rVu2vQ#a@BpK7 zrBHqd@c8SgUf;ONzZeBj);RBYL(0!hagB94w#t7pJZ z;#bGEbCF0lwih|01iq5zIyQy-_I>Pzdjz9V!9*Ve*c;0@XxvauML~2q#Lse9HY+-q z=1j3#jI>vH__HvWB~dBxID-^$4H7;u_B#Nsz(+d;k|ilpL46;gX+r%*u+YZ}gJ9zd z?xflBGnZy-fFse{vG=GRvfK1DG_U)+BR2F7eK6Rqc1>{@iZ@KnIZCVo=C(n zTqHY5s}fmimGEOl;~7vYnna*bb4(}4+<3EKz;THZO!dh6QW#&abaT}qC@GJ0ErSfTKjkL zbxF*Nk4u8%TZ3L5or@jBc<~oMPV>Q7#DD6}4AV)zYru->G#TK;tXqXF$|SZS@%*Ao z#OBlJGc?Bqa1w2um$I`8E7ORCfNk#w04WrI|Jh>;k()i)p+o0;0Q-h{);$ z(7l}f_8L%U!-Z4V?}-4hNY9URVi5*PHAkNEO<-cylHlxZm=}9!v=yC4(_U{09L~%e zR)HEA8tTmeKC)Y5@f0?X(~P?ib?W5RUGREi>pPOF~`yWD8>6;YDmrB?4CE4TOOeivHN?)MZf^)HmK4 zyb(tuup$89rw)|{T4xq3$5pX0sIRnV&}DqP(ZcBS3zlKQa6E_biTre1&R zu`%Z9x%7LV-V4wh*nHP5eJ%rBM`NQ>7SU}l0T>K>L!lWo{vTpjKrS5kk`0YW-(Gbk zZMKyO2@E?QeF6~9NcN#mR_*8T>n4E0(9Iw=tM_V-l-hxIs$5Jc@%K{jLg+l;KeFUS*2&_Be zm2-mrjULlBl6s=l%j@J#IXvdhQt*ij&w=f0QDxd|#;G&x0|Typ|0n2wNdl_xuX$Xa zk6a)52sUH0S&3OhT5gd25=IMT1}8-&%(_BDGS#HyW~uWw-|**!CmIE_nlApkv+QGE zx8(SOb5RmSmGX`cws$<}zg-e4@||jT@B@dZmF;k-Xx>zV&ucl)%-cR+s0c30OW6ti zGFsTVI)y{`2TD0`$$Wy%MNi6Y_D~BbtDa+B7AU9ZJ~HrIfT1}K(^ok}EY*iC$dvd! zYun`=J+WUYeAer69-hC7dczSWm@t^!bFvb~m9hG+$fUHrc!EYKH5;3Xc%Oh+Cz^=e za%&e@%UzUknc7LfvG&@##r_PQz45GrWI~N7&2r#^K>VdOl^McW_haDs$X=QAcwmE# zF82yZMqQ zyhZGz@p(B1K&*HoPWdV)hTB}#)Y~OEs#iWEwrm4X%MO zMZp_Ub4T3yg&*I=x;pOx7ISM zAE7n;)LqFLkd-$j)ut<0BBucW+bRf1$f}}M48UPzLM>yj;e2_9ZAR|Z0?P)?24#~S z2|P~bTGM+)@_son`IffZ-+#`SO5F(ts*;MJ>(6+!)$;JKPzxT+Aa1g=`#n}1*MzH- zCpl&)0e29VHr;mV?<)HNv|-oYW*a}Y7D_J8iTl*%VTA%oVMwDpPQK^4#aR|WQ=Fts ziP6;m!&<1#<!2?*Vt__n;}Ly5?dYO_mbQQ4%C2WnN%$C>v#F1|H zv{x=O8lCJNHT=8C0!GZ!qaW0q@nRCl41{+|1d4Wr_4Os_Q0rqBvAxb1A(<}dPb@$p zVXLvb0HXNdm$cMeAH!4_t~7q9mh5g?oQfi*634@nix;Q~#C~F^Q+-3w1uHso^!;id zhRzUZLq*Zx`>oB&!5yEs@wq;#+7YdFiCbdfP0SX5VvV zK9a$w`oy;R7Vo}ogYk<`Sw99|VrUl`IySo&t*!-DsQ=9}>pq1+6vb&^pM{_p5bO=} zHVVN@4R0|XmXd^mkVn^?yL(b4Z!{Tzh3gz@h(G&w)%oi64#%!KJjaiH_n(r5JZRLd zCna$3h;3x^y6GMoNmjsMnP=%tLo+l>g#@W*sr-chp%GTcv)LHwQqY&^V!gxEuvQ4j zfGydHQ`MpLr-vC<3NikVVu{$zhg~B#SOk)XIpQ>|FHSxz__^UCh+^%5{_Pukw4%yD zr$t=xgtlAH#+f+fGyaq!QIc+Bl_;P)jo|RuSxfoME4~4CYifm@a!Y`~M3($~^7Le{ z0kr)y_)ML23W<05Kqq1|UxD`o13^q3r8T5E=V;qrA~GnilXH8Ojk#I*_jVCBH|%f0 z$MHh^tP@J^r_1!u-IOQ76?%^=u$Z!#-ELlCNJWKLiP7)%#1&Ar8f!-xi!Zd$iR^$U zygqx@UgZIqkL4zH6zDs4CWgUu-l7(wLC(AlaI)~ve(>ef)0-}rI8!O89Wg`SDE77= z0?N`2!3i6ZkKam+1WLaMJeYha<0v;uOg7EE2z6rs$Xq_CcTdbRemVe#v-njBMF#-` z7|Cd}kWGaG2q>%3*1=0ojV|#eOM$ALPHalp;02(~5iMbSKfE=Z`G&5#_~jaCt40oG z;Oii)dt_FNa(ScrWA1+ao>6}Kj~PjKU|E!MipK|9zl{jtZ?YI$d2=x|izKCjg5xu{ z367z5ksmoiZO&V-7QTbDX7p9Rej#>Jy3m%3)zhGI{M#U7T^BuRIsI&vZ(gN(9NWJC zbE~T$C)f2V_vndhE6?vb&OZgFB$w<#6&u+)aP6yQ2TIMK~nSCVbh6971L#0(8F8&*%PXmrh97{ z5TP+32~F-OPz^VDdUh_?H10vYk|7a|X0~K&!&U2n;UeRfO=ZE=;+POeC;LV(_1I#< zf**jq$qIa+YO;NBr$2QJL?c~h6M(M*;WTK+7_xv>Py~^&{C8kkZY`1|%7*n8eddT^ zADy~o9NTzDye$%3r&cl^3w35pTgXxM)!?I|g`x?8sJF|oNyx4AO2wIgZIDl=OW|FU zC)x#KXN-A+B%bGFV1sj5K~(5Na#Unoj>ZWNF4!A%Joed^|5Qr_FXY_$g2aa8K3AU?hV)GoLX z(0TLcB^SJ6zp{iKB}K!pUz#|VJuh5=L1bcrjtyUk8IiW{57RdK*3dxs@meY=yfB@b ztK|^thF4X_h>yAjShDXQrqb->5rk=^_PPP#xU7|Ps$qkiO%ecD29)?Zp0aAqjZsO> zUaT1nQ%@@Pu0uS9ttv<%J|)huWx1OUuN){G#A`c7u&y=}dCH|~efj}e4-iGuR~5d= z)8!nz@3iq{VAKIDVqk6kq(o!(zy+|Gd~)(DZsh_e33leo7oJ%Z7>$tTyXi{`uIlK=q?PSmdmf44@t2?^QkZy?+S!B2y&h=MT z4If5DDr@O~f(L|`uABNuYLrJr3-vsCymoMBj{CWY%860*&Zah~^3{VeBe_h~2@J}e z`E*!}?_EG8VXsMAP8(i+SGmvC*6#*RgcjIX3~@|G#xc(%G9Rs+La%>jXYkRz0DRju z8U=jo0D@sqO(K@Ggjal@31Gc_cjmmD^QsAz7nYrX#gfwuB7g;>>(j$57YW|cEA==8 zijAr{LiJ*JEyVER`)m5>)<{dzl7p=U-ng5Yx}R+Hrh5AaEZp0c4o*iay(45^GGN!{ zO-Ur=qSw**(c3JGk2$02%`@oK;v*Ib?HAhb;z@Cycwtg2-PyArQCebpBh{OozZYq8 z_;&M_U(ap3or06ruH@uo()<1N+>pKoi6Bv-j9d!l%i zqA`9CeW32rdpjBPK&z19`L!Dq6kQcn)zwy`RkIj)lwzJJsA%Xg@W&Hrv~+oivLMbJ zqi|;>;$W?aiLtSERnD^2mW{Iyc-DTpSSTQP^PQ5CQe1Cu@BHGTNi~(VnRr%$ie@YC z`1p8`AW=Ha|6S3u))y;d+af#e!+_jkgg2j{V0`4)&k1ksCC&KFe_c{c6wRgXp4q@& z@9gQ$GY5zMvB}AQ!Pq`U=M@NYgYpc&=PDM!U!wHO9~gfp!D4;m^5Ya~p(v^a0~4hUc*x;9?Fe!Z>nI}uts{ByJS!oos2aflcA z@D!6*m``WC5u-bIW`DOKvY?>gt_>k}*{{d?HKs)BluU_s9iQGsntv%#l3>N`Hu(M2 zPF}ejop%>4jN+6@SJ&2fpFe+|t{~$S(!=@Jl%(W|g>@+_D|gJ!8iZFpzgF@fO!BI0 zXjlt0U!G!3Xm8u4Rd0w22|Z+{vi~)UzsG3*E+tk>lxbLZ8w0=Z+1)&3coU+iNEfc5 zy%-%GP4~ql=+DIe`U>p=1-O^%LD+1yc z&c@7(oA48|O1n3dl;`(C`gOhBoZ8)rD`TJjdZ6ET2PWkwT3GjNG7Syw+0P_i^sqis zn@_VPeTN~OW%K&%IW3ZE`|HH*$}2~aRaKTcHq?KA@hi-thCY=(T(0P-2|=4$ zBep?v_RJp`(JI$47Trw$J+I*Q?|4PbGUTL2&JsPCC_kHxzef{~@}8HNoEZH>xkXGm zEyUm7{xe6nQRNob!kl#e9^Ky$Wbsa{)i#0-Lz{N&>c4jBKO^WVwg!f + Corrupted Rule +

+ +You can check for corrupted layout rules by setting **Project Settings > Smart Addresser > Layout Rule Corruption**. + +

+ Corrupted Rule +

+ +The items are as follows. + +| Item Name | Description | +|------------------|-----------------------------------------------------------------------------| +| Throws Exception | Throw an exception if the layout rule is corrupted. The application process is not performed. | +| Log Error | Output an error log if the layout rule is corrupted. The application process is performed. | +| Ignore | Ignore the corrupted layout rule and apply the process. | + ## Version Management **Smart Addresser** can provide version to each asset. diff --git a/README_JA.md b/README_JA.md index 0cc25b8..aea22d0 100644 --- a/README_JA.md +++ b/README_JA.md @@ -294,6 +294,28 @@ Layout Rule Editor からは以下の手順で適用することができます コマンドラインインターフェース(CLI)で適用することもできます。 詳しくは [コマンドラインインターフェース (CLI)](#コマンドラインインターフェース-cli) を参照してください。 +### レイアウトルールの破損を検知する + +例えば Object Filter に設定していたオブジェクトが削除された場合など、設定していたレイアウトルールが破損するケースがあります。 + +

+ Corrupted Rule +

+ +**Project Settings > Smart Addresser > Layout Rule Corruption** を設定することでレイアウトルールの破損をチェックすることができます。 + +

+ Corrupted Rule +

+ +項目の説明は以下の通りです。 + +| 項目名 | 説明 | +|------------------|----------------------------------------| +| Throws Exception | レイアウトルールが破損している場合に例外をスローする。適用処理はされない。 | +| Log Error | レイアウトルールが破損している場合にエラーログを出力する。適用処理はされる。 | +| Ignore | レイアウトルールが破損していても無視して適用処理を行う。 | + ## バージョン管理機能 **Smart Addresser** では、各アセットに対してバージョンを付与することができます。 From e4cbc1d9b50914ca8a623d7399218886a00dbf04 Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Thu, 3 Oct 2024 17:18:37 +0900 Subject: [PATCH 06/13] Update CLI --- .../Core/Tools/CLI/SmartAddresserCLI.cs | 37 +++++++++++++++++++ .../Tools/CLI/ValidateLayoutRuleCLIOptions.cs | 22 +++++++++++ .../CLI/ValidateLayoutRuleCLIOptions.cs.meta | 3 ++ README.md | 22 +++++++++++ README_JA.md | 22 +++++++++++ 5 files changed, 106 insertions(+) create mode 100644 Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs.meta diff --git a/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs b/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs index d5bf8ad..41d9ec4 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs @@ -43,6 +43,43 @@ public static void SetVersionExpression() } } + public static void ValidateLayoutRules() + { + try + { + var options = ValidateLayoutRuleCLIOptions.CreateFromCommandLineArgs(); + var layoutRule = LoadLayoutRuleData(options.LayoutRuleAssetPath).LayoutRule; + var versionExpressionParser = new VersionExpressionParserRepository().Load(); + var assetDatabaseAdapter = new AssetDatabaseAdapter(); + var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; + var addressableSettingsAdapter = new AddressableAssetSettingsAdapter(addressableSettings); + + var applyService = new ApplyLayoutRuleService(layoutRule, + versionExpressionParser, + addressableSettingsAdapter, + assetDatabaseAdapter); + + applyService.Setup(); + try + { + applyService.ValidateLayoutRules(LayoutRuleCorruptionNotificationType.ThrowException); + } + catch (Exception e) + { + Debug.LogError(e); + EditorApplication.Exit(ErrorLevelValidateFailed); + return; + } + + EditorApplication.Exit(ErrorLevelNone); + } + catch (Exception e) + { + Debug.LogError(e); + EditorApplication.Exit(ErrorLevelFailed); + } + } + public static void ApplyRules() { try diff --git a/Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs b/Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs new file mode 100644 index 0000000..4c2db5a --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs @@ -0,0 +1,22 @@ +using SmartAddresser.Editor.Foundation; + +namespace SmartAddresser.Editor.Core.Tools.CLI +{ + public sealed class ValidateLayoutRuleCLIOptions + { + private const string LayoutRuleAssetPathArgName = "-layoutRuleAssetPath"; + + public string LayoutRuleAssetPath { get; private set; } + + public static ValidateLayoutRuleCLIOptions CreateFromCommandLineArgs() + { + var options = new ValidateLayoutRuleCLIOptions(); + + // Layout Rule Asset Path + if (CommandLineUtility.TryGetStringValue(LayoutRuleAssetPathArgName, out var layoutRuleAssetPath)) + options.LayoutRuleAssetPath = layoutRuleAssetPath; + + return options; + } + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs.meta b/Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs.meta new file mode 100644 index 0000000..806fcb8 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6806483c25484b799ab29bc3c2118ceb +timeCreated: 1727937199 \ No newline at end of file diff --git a/README.md b/README.md index 035e97a..9bf2ec7 100644 --- a/README.md +++ b/README.md @@ -413,6 +413,28 @@ When completed, Unity is automatically closed and returns the following value. - If successful: 0 - If failed: 1 +### Detecting corrupted layout rules + +You can use the `SmartAddresser.Editor.Core.Tools.CLI.SmartAddresserCLI.ValidateLayoutRules` method to check for corrupted layout rules from the command line. + +The following is an example of how to check for corrupted layout rules from the command line in Mac. + +``` +/Applications/Unity/Hub/Editor/2020.3.40f1/Unity.app/Contents/MacOS/Unity -projectPath [Your Project Path Here] -executeMethod Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.ValidateLayoutRules +``` + +Command line arguments are as follows. + +| Argument Name | Description | +|------------------------------------------|-------------------------------------------------------------------------------------------------| +| -layoutRuleAssetPath \ | Asset Path of the Layout Rule Data to be applied.
If not specified, use the first one found. | + +When completed, Unity is automatically closed and returns the following value. + +- If successful: 0 +- If the layout rule is corrupted: 1 +- If an error occurred during execution: 2 + ### Apply Layout Rules to Addressable Asset System You can apply the layout rules to the Addressable Asset System by calling the following method. diff --git a/README_JA.md b/README_JA.md index aea22d0..ff05950 100644 --- a/README_JA.md +++ b/README_JA.md @@ -420,6 +420,28 @@ Layout Rule Editor からは以下の手順で適用することができます - 実行が成功した場合: 0 - 実行中にエラーが発生した場合: 2 +### レイアウトルールの破損をチェックする + +コマンドラインから **Version Expression** を設定するには`SmartAddresser.Editor.Core.Tools.CLI.SmartAddresserCLI.ValidateLayoutRules`を呼びます。 + +以下はMacでコマンドライン実行を行う例です。 + +``` +/Applications/Unity/Hub/Editor/2020.3.40f1/Unity.app/Contents/MacOS/Unity -projectPath [Your Project Path Here] -executeMethod Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.ValidateLayoutRules +``` + +コマンドライン引数は以下の通りです。 + +| 引数名 | 説明 | +|-----------------------------------|-------------------------------------------------------| +| -layoutRuleAssetPath \ | 適用するレイアウトルールデータのアセットパス。
指定されない場合は最初に見つかったものを使用します。 | + +実行が完了すると自動的にUnityを終了し、戻り値として以下の値を返します。 + +- 実行が成功した場合: 0 +- レイアウトルールに破損があった場合: 1 +- 実行中にエラーが発生した場合: 2 + ### レイアウトルールを Addressables に反映する コマンドラインからレイアウトルールを反映するには `SmartAddresser.Editor.Core.Tools.CLI.SmartAddresserCLI.ApplyRules` を呼びます。 From 0740c00b15ab6ccf01395f3b4ed43118d0f2a08f Mon Sep 17 00:00:00 2001 From: Haruma-K Date: Thu, 3 Oct 2024 08:18:56 +0000 Subject: [PATCH 07/13] chore(docs): update TOC --- README.md | 2 ++ README_JA.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 9bf2ec7..3867402 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ And it also has the version management feature to exclude pre-release assets fro - [Apply from Layout Rule Editor](#apply-from-layout-rule-editor) - [Apply rules automatically](#apply-rules-automatically) - [Apply by CLI](#apply-by-cli) + - [Detecting corrupted layout rules](#detecting-corrupted-layout-rules) - [Version Management](#version-management) - [Versioning Specification](#versioning-specification) - [Create Version Rules](#create-version-rules) @@ -47,6 +48,7 @@ And it also has the version management feature to exclude pre-release assets fro - [Use your own version range expression](#use-your-own-version-range-expression) - [Command Line Interface (CLI)](#command-line-interface-cli) - [Set Version Expression](#set-version-expression) + - [Detecting corrupted layout rules](#detecting-corrupted-layout-rules-1) - [Apply Layout Rules to Addressable Asset System](#apply-layout-rules-to-addressable-asset-system) - [Scripting](#scripting) - [Edit Layout Rule Data](#edit-layout-rule-data) diff --git a/README_JA.md b/README_JA.md index ff05950..2be9b52 100644 --- a/README_JA.md +++ b/README_JA.md @@ -42,6 +42,7 @@ - [Layout Rule Editorから適用する](#layout-rule-editor%E3%81%8B%E3%82%89%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B) - [ルールを自動的に適用する](#%E3%83%AB%E3%83%BC%E3%83%AB%E3%82%92%E8%87%AA%E5%8B%95%E7%9A%84%E3%81%AB%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B) - [CLIで適用する](#cli%E3%81%A7%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B) + - [レイアウトルールの破損を検知する](#%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%AE%E7%A0%B4%E6%90%8D%E3%82%92%E6%A4%9C%E7%9F%A5%E3%81%99%E3%82%8B) - [バージョン管理機能](#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%AE%A1%E7%90%86%E6%A9%9F%E8%83%BD) - [バージョニングの仕様](#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%AE%E4%BB%95%E6%A7%98) - [バージョンを指定する](#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B) @@ -50,6 +51,7 @@ - [独自のバージョン範囲表現を使う](#%E7%8B%AC%E8%87%AA%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%AF%84%E5%9B%B2%E8%A1%A8%E7%8F%BE%E3%82%92%E4%BD%BF%E3%81%86) - [コマンドラインインターフェース (CLI)](#%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9-cli) - [Version Expressionを設定する](#version-expression%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B) + - [レイアウトルールの破損をチェックする](#%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%AE%E7%A0%B4%E6%90%8D%E3%82%92%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B) - [レイアウトルールを Addressables に反映する](#%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E3%83%AB%E3%83%BC%E3%83%AB%E3%82%92-addressables-%E3%81%AB%E5%8F%8D%E6%98%A0%E3%81%99%E3%82%8B) - [スクリプティング](#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0) - [レイアウトルールデータを編集する](#%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E3%83%AB%E3%83%BC%E3%83%AB%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E7%B7%A8%E9%9B%86%E3%81%99%E3%82%8B) From 2235d9493e3e1d9bc85f107bb70bab31ce4a465b Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Fri, 4 Oct 2024 10:29:02 +0900 Subject: [PATCH 08/13] Fix error notification --- .../LayoutRules/AddressRules/AddressRule.cs | 30 ++++++-- .../LayoutRules/LabelRules/LabelRule.cs | 10 ++- .../Core/Models/LayoutRules/LayoutRule.cs | 77 ++++++++----------- .../LayoutRules/VersionRules/VersionRule.cs | 12 ++- .../Models/Services/ApplyLayoutRuleService.cs | 30 +------- .../AssetFilterImpl/AssetFilterAsset.cs | 7 +- .../AssetFilterImpl/AssetFilterBase.cs | 3 +- .../AssetFilterImpl/CustomAssetFilter.cs | 5 +- .../DependentObjectBasedAssetFilter.cs | 7 +- .../ExtensionBasedAssetFilter.cs | 32 ++------ .../FindAssetsBasedAssetFilter.cs | 5 +- .../AssetFilterImpl/ObjectBasedAssetFilter.cs | 13 ++-- .../AssetFilterImpl/RegexBasedAssetFilter.cs | 25 +++--- .../AssetFilterImpl/TypeBasedAssetFilter.cs | 23 +++--- .../Models/Shared/AssetGroups/AssetGroup.cs | 27 +++---- .../AssetGroups/AssetGroupObservableList.cs | 19 +++-- .../Models/Shared/AssetGroups/IAssetFilter.cs | 4 +- .../Shared/AssetGroups/ValidationError.meta | 3 + .../AddressRuleValidationError.cs | 33 ++++++++ .../AddressRuleValidationError.cs.meta | 3 + .../AssetFilterValidationError.cs | 29 +++++++ .../AssetFilterValidationError.cs.meta | 3 + .../AssetGroupValidationError.cs | 32 ++++++++ .../AssetGroupValidationError.cs.meta | 3 + .../LabelRuleValidationError.cs | 30 ++++++++ .../LabelRuleValidationError.cs.meta | 3 + .../LayoutRuleValidationError.cs | 38 +++++++++ .../LayoutRuleValidationError.cs.meta | 3 + .../VersionRuleValidationError.cs | 30 ++++++++ .../VersionRuleValidationError.cs.meta | 3 + 30 files changed, 359 insertions(+), 183 deletions(-) create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError.meta create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AddressRuleValidationError.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AddressRuleValidationError.cs.meta create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetFilterValidationError.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetFilterValidationError.cs.meta create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetGroupValidationError.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetGroupValidationError.cs.meta create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LabelRuleValidationError.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LabelRuleValidationError.cs.meta create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LayoutRuleValidationError.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LayoutRuleValidationError.cs.meta create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/VersionRuleValidationError.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/VersionRuleValidationError.cs.meta diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/AddressRules/AddressRule.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/AddressRules/AddressRule.cs index 33c69d2..7d07fe6 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/AddressRules/AddressRule.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/AddressRules/AddressRule.cs @@ -1,6 +1,7 @@ using System; using SmartAddresser.Editor.Core.Models.Shared; using SmartAddresser.Editor.Core.Models.Shared.AssetGroups; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.TinyRx.ObservableCollection; using SmartAddresser.Editor.Foundation.TinyRx.ObservableProperty; using UnityEditor.AddressableAssets.Settings; @@ -19,9 +20,9 @@ public sealed class AddressRule : ISerializationCallbackReceiver [SerializeField] private ObservableProperty _control = new ObservableProperty(); [SerializeField] private AssetGroupObservableList _assetGroups = new AssetGroupObservableList(); - private ObservableProperty _addressProviderDescription = new ObservableProperty(); - [SerializeReference] private IAddressProvider _addressProviderInternal; + + private ObservableProperty _addressProviderDescription = new ObservableProperty(); private ObservableProperty _assetGroupDescription = new ObservableProperty(); // Define the default constructor for serialization. @@ -76,13 +77,21 @@ public void Setup() AddressProvider.Value.Setup(); } - public bool Validate(out string errorMessage) + public bool Validate(out AddressRuleValidationError error) { - if (_assetGroups.Validate(out errorMessage)) + if (!_control.Value) + { + error = null; return true; + } + + if (_assetGroups.Validate(out var groupErrors)) + { + error = null; + return true; + } - var addressableGroupName = _addressableGroup != null ? _addressableGroup.Name : "(None)"; - errorMessage = $"Address rule is corrupted: {addressableGroupName}{Environment.NewLine}{errorMessage}"; + error = new AddressRuleValidationError(this, groupErrors); return false; } @@ -98,8 +107,13 @@ public bool Validate(out string errorMessage) /// You can pass false if it is guaranteed to be valid. /// /// Return true if successful. - public bool TryProvideAddress(string assetPath, Type assetType, bool isFolder, out string address, - bool checkIsPathValidForEntry = true) + public bool TryProvideAddress( + string assetPath, + Type assetType, + bool isFolder, + out string address, + bool checkIsPathValidForEntry = true + ) { // If this addressable group is not the control target, do nothing. if (!Control.Value) diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LabelRules/LabelRule.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LabelRules/LabelRule.cs index 559409e..ebc8fd7 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LabelRules/LabelRule.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LabelRules/LabelRule.cs @@ -1,6 +1,7 @@ using System; using SmartAddresser.Editor.Core.Models.Shared; using SmartAddresser.Editor.Core.Models.Shared.AssetGroups; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.TinyRx.ObservableCollection; using SmartAddresser.Editor.Foundation.TinyRx.ObservableProperty; using UnityEngine; @@ -65,12 +66,15 @@ public void Setup() LabelProvider.Value.Setup(); } - public bool Validate(out string errorMessage) + public bool Validate(out LabelRuleValidationError error) { - if (_assetGroups.Validate(out errorMessage)) + if (_assetGroups.Validate(out var groupErrors)) + { + error = null; return true; + } - errorMessage = $"Label rule is corrupted: {_name.Value}{Environment.NewLine}{errorMessage}"; + error = new LabelRuleValidationError(this, groupErrors); return false; } diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs index dd74457..0a70d8d 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using SmartAddresser.Editor.Core.Models.LayoutRules.AddressRules; using SmartAddresser.Editor.Core.Models.LayoutRules.LabelRules; using SmartAddresser.Editor.Core.Models.LayoutRules.Settings; using SmartAddresser.Editor.Core.Models.LayoutRules.VersionRules; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.TinyRx.ObservableCollection; using UnityEditor.AddressableAssets.Settings; using UnityEngine; @@ -73,21 +73,6 @@ public void SetupForAddress() addressRule.Setup(); } - public bool ValidateForAddress(out string errorMessage) - { - var result = true; - var sb = new StringBuilder(); - foreach (var addressRule in _addressRules) - { - result &= addressRule.Validate(out var message); - if (!string.IsNullOrEmpty(message)) - sb.Append(message); - } - - errorMessage = sb.ToString(); - return result; - } - public bool TryProvideAddressAndAddressableGroup( string assetPath, Type assetType, @@ -121,21 +106,6 @@ public void SetupForLabels() labelRule.Setup(); } - public bool ValidateForLabels(out string errorMessage) - { - var result = true; - var sb = new StringBuilder(); - foreach (var labelRule in _labelRules) - { - result &= labelRule.Validate(out var message); - if (!string.IsNullOrEmpty(message)) - sb.Append(message); - } - - errorMessage = sb.ToString(); - return result; - } - /// /// Provide the labels. /// @@ -177,21 +147,6 @@ public void SetupForVersion() versionRule.Setup(); } - public bool ValidateForVersion(out string errorMessage) - { - var result = true; - var sb = new StringBuilder(); - foreach (var versionRule in _versionRules) - { - result &= versionRule.Validate(out var message); - if (!string.IsNullOrEmpty(message)) - sb.Append(message); - } - - errorMessage = sb.ToString(); - return result; - } - public string ProvideVersion(string assetPath, Type assetType, bool isFolder, bool doSetup) { foreach (var versionRule in _versionRules) @@ -206,5 +161,35 @@ public string ProvideVersion(string assetPath, Type assetType, bool isFolder, bo return null; } + + public bool Validate(out LayoutRuleValidationError error) + { + var versionRuleErrors = new List(); + foreach (var versionRule in _versionRules) + if (!versionRule.Validate(out var versionRuleError)) + versionRuleErrors.Add(versionRuleError); + + var labelRuleErrors = new List(); + foreach (var labelRule in _labelRules) + if (!labelRule.Validate(out var labelRuleError)) + labelRuleErrors.Add(labelRuleError); + + var addressRuleErrors = new List(); + foreach (var addressRule in _addressRules) + if (!addressRule.Validate(out var addressRuleError)) + addressRuleErrors.Add(addressRuleError); + + if (versionRuleErrors.Count == 0 && labelRuleErrors.Count == 0 && addressRuleErrors.Count == 0) + { + error = null; + return true; + } + + error = new LayoutRuleValidationError( + addressRuleErrors.ToArray(), + labelRuleErrors.ToArray(), + versionRuleErrors.ToArray()); + return false; + } } } diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/VersionRules/VersionRule.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/VersionRules/VersionRule.cs index b41231c..c59e570 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/VersionRules/VersionRule.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/VersionRules/VersionRule.cs @@ -1,6 +1,7 @@ using System; using SmartAddresser.Editor.Core.Models.Shared; using SmartAddresser.Editor.Core.Models.Shared.AssetGroups; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.TinyRx.ObservableCollection; using SmartAddresser.Editor.Foundation.TinyRx.ObservableProperty; using UnityEngine; @@ -64,12 +65,15 @@ public void Setup() VersionProvider.Value.Setup(); } - public bool Validate(out string errorMessage) + public bool Validate(out VersionRuleValidationError error) { - if (_assetGroups.Validate(out errorMessage)) + if (_assetGroups.Validate(out var groupErrors)) + { + error = null; return true; - - errorMessage = $"Version rule is corrupted: {_name.Value}{Environment.NewLine}{errorMessage}"; + } + + error = new VersionRuleValidationError(this, groupErrors); return false; } diff --git a/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs b/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs index dd13c50..c50dabd 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using SmartAddresser.Editor.Core.Models.LayoutRules; using SmartAddresser.Editor.Foundation.AddressableAdapter; using SmartAddresser.Editor.Foundation.AssetDatabaseAdapter; @@ -50,40 +49,17 @@ public void ValidateLayoutRules(LayoutRuleCorruptionNotificationType corruptionN if (corruptionNotificationType == LayoutRuleCorruptionNotificationType.Ignore) return; - if (ValidateLayoutRulesInternal(out var errorMessage)) + if (_layoutRule.Validate(out var errorMessage)) return; - errorMessage = $"SmartAddresser detected corruption of the layout rule{Environment.NewLine}{errorMessage}"; - if (corruptionNotificationType == LayoutRuleCorruptionNotificationType.LogError) { - Debug.LogError(errorMessage); + Debug.LogError(errorMessage.ToJson(true)); return; } if (corruptionNotificationType == LayoutRuleCorruptionNotificationType.ThrowException) - throw new Exception(errorMessage); - } - - private bool ValidateLayoutRulesInternal(out string errorMessage) - { - var result = true; - var sb = new StringBuilder(); - - result &= _layoutRule.ValidateForAddress(out var message); - if (!string.IsNullOrEmpty(message)) - sb.Append(message); - - result &= _layoutRule.ValidateForLabels(out message); - if (!string.IsNullOrEmpty(message)) - sb.Append(message); - - result &= _layoutRule.ValidateForVersion(out message); - if (!string.IsNullOrEmpty(message)) - sb.Append(message); - - errorMessage = sb.ToString(); - return result; + throw new Exception(errorMessage.ToJson(true)); } /// diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterAsset.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterAsset.cs index a99633a..7e4d2a7 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterAsset.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterAsset.cs @@ -1,4 +1,5 @@ using System; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using UnityEngine; namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups.AssetFilterImpl @@ -11,12 +12,12 @@ public abstract class AssetFilterAsset : ScriptableObject, IAssetFilter public abstract void SetupForMatching(); /// - public virtual bool Validate(out string errorMessage) + public virtual bool Validate(out AssetFilterValidationError error) { - errorMessage = null; + error = null; return true; } - + /// public abstract bool IsMatch(string assetPath, Type assetType, bool isFolder); diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterBase.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterBase.cs index 666cddd..324e21a 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterBase.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/AssetFilterBase.cs @@ -1,4 +1,5 @@ using System; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using UnityEngine; namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups.AssetFilterImpl @@ -19,7 +20,7 @@ protected AssetFilterBase() public abstract void SetupForMatching(); /// - public abstract bool Validate(out string errorMessage); + public abstract bool Validate(out AssetFilterValidationError error); /// public abstract bool IsMatch(string assetPath, Type assetType, bool isFolder); diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs index 391da61..f64f47f 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs @@ -1,4 +1,5 @@ using System; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using UnityEngine; namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups.AssetFilterImpl @@ -27,9 +28,9 @@ public void SetupForMatching() assetFilter.SetupForMatching(); } - public bool Validate(out string errorMessage) + public bool Validate(out AssetFilterValidationError error) { - return assetFilter.Validate(out errorMessage); + return assetFilter.Validate(out error); } public bool IsMatch(string assetPath, Type assetType, bool isFolder) diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilter.cs index 5274a78..b0a6483 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/DependentObjectBasedAssetFilter.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Text; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.ListableProperty; using UnityEditor; using UnityEngine; @@ -56,15 +57,15 @@ public override void SetupForMatching() _dependentAssetPaths = _dependentAssetPaths.Distinct().ToList(); } - public override bool Validate(out string errorMessage) + public override bool Validate(out AssetFilterValidationError error) { if (_hasNullObject) { - errorMessage = "There are null reference objects."; + error = new AssetFilterValidationError(this, new[] { "There are null reference objects." }); return false; } - errorMessage = null; + error = null; return true; } diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilter.cs index 90e8e35..90a993c 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ExtensionBasedAssetFilter.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; -using System.IO; using System.Text; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.ListableProperty; using UnityEngine; @@ -40,43 +40,33 @@ public override void SetupForMatching() } var ext = extension; - if (!ext.StartsWith(".")) - { - ext = $".{extension}"; - } + if (!ext.StartsWith(".")) ext = $".{extension}"; _extensions.Add(ext); } } - public override bool Validate(out string errorMessage) + public override bool Validate(out AssetFilterValidationError error) { if (_hasEmptyExtension) { - errorMessage = "There are empty extensions."; + error = new AssetFilterValidationError(this, new[] { "There are empty extensions." }); return false; } - errorMessage = string.Empty; + error = null; return true; } /// public override bool IsMatch(string assetPath, Type assetType, bool isFolder) { - if (string.IsNullOrEmpty(assetPath)) - { - return false; - } + if (string.IsNullOrEmpty(assetPath)) return false; foreach (var extension in _extensions) - { // Return true if any of the extensions match. if (assetPath.EndsWith(extension, StringComparison.Ordinal)) - { return true; - } - } return false; } @@ -87,15 +77,9 @@ public override string GetDescription() var elementCount = 0; foreach (var extension in _extension) { - if (string.IsNullOrEmpty(extension)) - { - continue; - } + if (string.IsNullOrEmpty(extension)) continue; - if (elementCount >= 1) - { - result.Append(" || "); - } + if (elementCount >= 1) result.Append(" || "); result.Append(extension); elementCount++; diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/FindAssetsBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/FindAssetsBasedAssetFilter.cs index a0a9eb3..a5187da 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/FindAssetsBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/FindAssetsBasedAssetFilter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.ListableProperty; using UnityEditor; using UnityEngine; @@ -48,9 +49,9 @@ public override void SetupForMatching() _foundAssetPaths.AddRange(assetPaths); } - public override bool Validate(out string errorMessage) + public override bool Validate(out AssetFilterValidationError error) { - errorMessage = null; + error = null; return true; } diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilter.cs index ec2da7a..ab0f16a 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/ObjectBasedAssetFilter.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Text; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.ListableProperty; using UnityEditor; using UnityEngine; @@ -21,9 +22,9 @@ public sealed class ObjectBasedAssetFilter : AssetFilterBase { [SerializeField] private FolderTargetingMode _folderTargetingMode = FolderTargetingMode.IncludedNonFolderAssets; [SerializeField] private ObjectListableProperty _object = new ObjectListableProperty(); + private bool _hasNullObject; private List<(string assetPath, bool isFolder)> _objectInfoList = new List<(string assetPath, bool isFolder)>(); - private bool _hasNullObject; public FolderTargetingMode FolderTargetingMode { @@ -53,16 +54,16 @@ public override void SetupForMatching() _objectInfoList.Add((path, isFolder)); } } - - public override bool Validate(out string errorMessage) + + public override bool Validate(out AssetFilterValidationError error) { if (_hasNullObject) { - errorMessage = $"[{GetType().Name}] There are null reference objects."; + error = new AssetFilterValidationError(this, new[] { "There are null reference objects." }); return false; } - errorMessage = null; + error = null; return true; } @@ -93,7 +94,7 @@ public override bool IsMatch(string assetPath, Type assetType, bool isFolder) return true; break; case FolderTargetingMode.Both: - if (isInclusion || (isSelf && isFolder)) + if (isInclusion || isSelf && isFolder) return true; break; default: diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilter.cs index f8d877a..f6e231b 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/RegexBasedAssetFilter.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Text.RegularExpressions; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.ListableProperty; using UnityEngine; @@ -21,8 +23,8 @@ public sealed class RegexBasedAssetFilter : AssetFilterBase [SerializeField] private bool _matchWithFolders; [SerializeField] private AssetFilterCondition _condition = AssetFilterCondition.ContainsMatched; [SerializeField] private StringListableProperty _assetPathRegex = new StringListableProperty(); - private List _regexes = new List(); private List _errorRegexStrings = new List(); + private List _regexes = new List(); public bool MatchWithFolders { @@ -63,26 +65,19 @@ public override void SetupForMatching() } } - public override bool Validate(out string errorMessage) + public override bool Validate(out AssetFilterValidationError error) { if (_errorRegexStrings.Count >= 1) { - var sb = new StringBuilder(); - sb.Append("Invalid regexes: "); - foreach (var errorRegexString in _errorRegexStrings) - { - sb.Append(errorRegexString); - sb.Append(", "); - } - - // Remove the last ", ". - sb.Remove(sb.Length - 2, 2); - - errorMessage = sb.ToString(); + error = new AssetFilterValidationError( + this, + _errorRegexStrings + .Select(errorRegexString => $"Invalid regex string: {errorRegexString}") + .ToArray()); return false; } - errorMessage = null; + error = null; return true; } diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilter.cs index af8b75b..cb6b4f6 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/TypeBasedAssetFilter.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using UnityEngine; namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups.AssetFilterImpl @@ -54,26 +56,19 @@ public override void SetupForMatching() _resultCache.Clear(); } - public override bool Validate(out string errorMessage) + public override bool Validate(out AssetFilterValidationError error) { if (_invalidAssemblyQualifiedNames.Count >= 1) { - var sb = new StringBuilder(); - sb.Append($"[{GetType().Name}] Unknown types: "); - foreach (var invalidAssemblyQualifiedNames in _invalidAssemblyQualifiedNames) - { - sb.Append(invalidAssemblyQualifiedNames); - sb.Append(" / "); - } - - // Remove the last " / ". - sb.Remove(sb.Length - 3, 3); - - errorMessage = sb.ToString(); + error = new AssetFilterValidationError( + this, + _invalidAssemblyQualifiedNames + .Select(qualifiedName => $"Invalid type reference: {qualifiedName}") + .ToArray()); return false; } - errorMessage = null; + error = null; return true; } diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroup.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroup.cs index 284adae..38599eb 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroup.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroup.cs @@ -3,7 +3,9 @@ // -------------------------------------------------------------- using System; +using System.Collections.Generic; using System.Text; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.TinyRx.ObservableCollection; using SmartAddresser.Editor.Foundation.TinyRx.ObservableProperty; using UnityEngine; @@ -16,8 +18,6 @@ namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups [Serializable] public sealed class AssetGroup { - private const string Indent = " "; - [SerializeField] private string _id; [SerializeField] private ObservableProperty _name = new ObservableProperty("New Asset Group"); @@ -44,26 +44,21 @@ public void Setup() filter?.SetupForMatching(); } - public bool Validate(out string errorMessage) + public bool Validate(out AssetGroupValidationError error) { - var result = true; - var sb = new StringBuilder(); + var filterErrors = new List(); foreach (var filter in _filters) - { - result &= filter.Validate(out var message); - if (!string.IsNullOrEmpty(message)) - sb.AppendLine($"{Indent}{Indent}{message}"); - } + if (!filter.Validate(out var filterError)) + filterErrors.Add(filterError); - if (sb.Length == 0) + if (filterErrors.Count > 0) { - errorMessage = null; - return result; + error = new AssetGroupValidationError(this, filterErrors.ToArray()); + return false; } - errorMessage = sb.ToString(); - errorMessage = $"{Indent}Group: {_name.Value}{Environment.NewLine}{errorMessage}"; - return result; + error = null; + return true; } /// diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroupObservableList.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroupObservableList.cs index 720c4c9..a5b3288 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroupObservableList.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetGroupObservableList.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; using SmartAddresser.Editor.Foundation.TinyRx.ObservableCollection; namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups @@ -20,19 +21,21 @@ public void Setup() group.Setup(); } - public bool Validate(out string errorMessage) + public bool Validate(out AssetGroupValidationError[] errors) { - var result = true; - var sb = new StringBuilder(); + var groupErrors = new List(); foreach (var group in this) + if (!group.Validate(out var groupError)) + groupErrors.Add(groupError); + + if (groupErrors.Count > 0) { - result &= group.Validate(out var message); - if (!string.IsNullOrEmpty(message)) - sb.Append(message); + errors = groupErrors.ToArray(); + return false; } - errorMessage = sb.ToString(); - return result; + errors = null; + return true; } /// diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/IAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/IAssetFilter.cs index c0233df..44824bd 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/IAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/IAssetFilter.cs @@ -3,6 +3,8 @@ // -------------------------------------------------------------- using System; +using System.Collections.Generic; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups { @@ -26,7 +28,7 @@ public interface IAssetFilter /// This method will be called after . /// /// - bool Validate(out string errorMessage); + bool Validate(out AssetFilterValidationError error); /// /// Return true if the asset passes this filter. diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError.meta b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError.meta new file mode 100644 index 0000000..e0672ee --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 34e10467e78d42128e2894f7b99ddd8c +timeCreated: 1727946191 \ No newline at end of file diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AddressRuleValidationError.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AddressRuleValidationError.cs new file mode 100644 index 0000000..a479c0f --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AddressRuleValidationError.cs @@ -0,0 +1,33 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using SmartAddresser.Editor.Core.Models.LayoutRules.AddressRules; +using UnityEngine; + +namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError +{ + [Serializable] + public sealed class AddressRuleValidationError + { + [SerializeField] private string addressRuleId; + [SerializeField] private string addressableAssetGroupGuid; + [SerializeField] private string addressableAssetGroupName; + [SerializeField] private AssetGroupValidationError[] assetGroupErrors; + + public AddressRuleValidationError(AddressRule addressRule, AssetGroupValidationError[] assetGroupErrors) + { + addressRuleId = addressRule.Id; + addressableAssetGroupGuid = addressRule.AddressableGroup.Guid; + addressableAssetGroupName = addressRule.AddressableGroup.Name; + this.assetGroupErrors = assetGroupErrors; + } + + public string AddressRuleId => addressRuleId; + public string AddressableAssetGroupGuid => addressableAssetGroupGuid; + public string AddressableAssetGroupName => addressableAssetGroupName; + public IReadOnlyList AssetGroupErrors => assetGroupErrors; + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AddressRuleValidationError.cs.meta b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AddressRuleValidationError.cs.meta new file mode 100644 index 0000000..b7e2c43 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AddressRuleValidationError.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8f38f738f6af435da4df036b6c808d9d +timeCreated: 1727948594 \ No newline at end of file diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetFilterValidationError.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetFilterValidationError.cs new file mode 100644 index 0000000..5f27892 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetFilterValidationError.cs @@ -0,0 +1,29 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError +{ + [Serializable] + public sealed class AssetFilterValidationError + { + [SerializeField] private string assetFilterId; + [SerializeField] private string assetFilterTypeName; + [SerializeField] private string[] errorMessages; + + public AssetFilterValidationError(IAssetFilter assetFilter, string[] errorMessages) + { + assetFilterId = assetFilter.Id; + assetFilterTypeName = assetFilter.GetType().Name; + this.errorMessages = errorMessages; + } + + public string AssetFilterId => assetFilterId; + public string AssetFilterTypeName => assetFilterTypeName; + public IReadOnlyList ErrorMessages => errorMessages; + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetFilterValidationError.cs.meta b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetFilterValidationError.cs.meta new file mode 100644 index 0000000..d365b11 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetFilterValidationError.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f22ca4274778493c85aff7c1ae8a788c +timeCreated: 1727946202 \ No newline at end of file diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetGroupValidationError.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetGroupValidationError.cs new file mode 100644 index 0000000..ce225d6 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetGroupValidationError.cs @@ -0,0 +1,32 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError +{ + [Serializable] + public sealed class AssetGroupValidationError + { + [SerializeField] private string assetGroupId; + [SerializeField] private string assetGroupName; + [SerializeField] private AssetFilterValidationError[] assetFilterErrors; + + public AssetGroupValidationError( + AssetGroup assetGroup, + AssetFilterValidationError[] assetFilterErrors + ) + { + assetGroupId = assetGroup.Id; + assetGroupName = assetGroup.Name.Value; + this.assetFilterErrors = assetFilterErrors; + } + + public string AssetGroupId => assetGroupId; + public string AssetGroupName => assetGroupName; + public IReadOnlyList AssetFilterErrors => assetFilterErrors; + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetGroupValidationError.cs.meta b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetGroupValidationError.cs.meta new file mode 100644 index 0000000..6b978fa --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/AssetGroupValidationError.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fe15aac5396c4367a1d899dccba18701 +timeCreated: 1727947568 \ No newline at end of file diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LabelRuleValidationError.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LabelRuleValidationError.cs new file mode 100644 index 0000000..815e662 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LabelRuleValidationError.cs @@ -0,0 +1,30 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using SmartAddresser.Editor.Core.Models.LayoutRules.LabelRules; +using UnityEngine; + +namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError +{ + [Serializable] + public sealed class LabelRuleValidationError + { + [SerializeField] private string labelRuleId; + [SerializeField] private string labelRuleName; + [SerializeField] private AssetGroupValidationError[] assetGroupErrors; + + public LabelRuleValidationError(LabelRule labelRule, AssetGroupValidationError[] assetGroupErrors) + { + labelRuleId = labelRule.Id; + labelRuleName = labelRule.Name.Value; + this.assetGroupErrors = assetGroupErrors; + } + + public string LabelRuleId => labelRuleId; + public string LabelRuleName => labelRuleName; + public IReadOnlyList AssetGroupErrors => assetGroupErrors; + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LabelRuleValidationError.cs.meta b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LabelRuleValidationError.cs.meta new file mode 100644 index 0000000..d1eec5e --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LabelRuleValidationError.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9d33983bc8be4012a0d606c3934354a0 +timeCreated: 1727949093 \ No newline at end of file diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LayoutRuleValidationError.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LayoutRuleValidationError.cs new file mode 100644 index 0000000..6ed3a56 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LayoutRuleValidationError.cs @@ -0,0 +1,38 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError +{ + [Serializable] + public sealed class LayoutRuleValidationError + { + [SerializeField] private AddressRuleValidationError[] addressRuleErrors; + [SerializeField] private LabelRuleValidationError[] labelRuleErrors; + [SerializeField] private VersionRuleValidationError[] versionRuleErrors; + + public LayoutRuleValidationError( + AddressRuleValidationError[] addressRuleErrors, + LabelRuleValidationError[] labelRuleErrors, + VersionRuleValidationError[] versionRuleErrors + ) + { + this.addressRuleErrors = addressRuleErrors; + this.labelRuleErrors = labelRuleErrors; + this.versionRuleErrors = versionRuleErrors; + } + + public IReadOnlyList AddressRuleErrors => addressRuleErrors; + public IReadOnlyList LabelRuleErrors => labelRuleErrors; + public IReadOnlyList VersionRuleErrors => versionRuleErrors; + + public string ToJson(bool prettyPrint) + { + return JsonUtility.ToJson(this, prettyPrint); + } + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LayoutRuleValidationError.cs.meta b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LayoutRuleValidationError.cs.meta new file mode 100644 index 0000000..fea170a --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/LayoutRuleValidationError.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e2cc532dfcc8488c9708c182e36b5eb2 +timeCreated: 1727949303 \ No newline at end of file diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/VersionRuleValidationError.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/VersionRuleValidationError.cs new file mode 100644 index 0000000..2c93139 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/VersionRuleValidationError.cs @@ -0,0 +1,30 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using SmartAddresser.Editor.Core.Models.LayoutRules.VersionRules; +using UnityEngine; + +namespace SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError +{ + [Serializable] + public sealed class VersionRuleValidationError + { + [SerializeField] private string versionRuleId; + [SerializeField] private string versionRuleName; + [SerializeField] private AssetGroupValidationError[] assetGroupErrors; + + public VersionRuleValidationError(VersionRule versionRule, AssetGroupValidationError[] assetGroupErrors) + { + versionRuleId = versionRule.Id; + versionRuleName = versionRule.Name.Value; + this.assetGroupErrors = assetGroupErrors; + } + + public string VersionRuleId => versionRuleId; + public string VersionRuleName => versionRuleName; + public IReadOnlyList AssetGroupErrors => assetGroupErrors; + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/VersionRuleValidationError.cs.meta b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/VersionRuleValidationError.cs.meta new file mode 100644 index 0000000..69ca8d9 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/ValidationError/VersionRuleValidationError.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 63e7111ffa5c42d5a692a11f34020391 +timeCreated: 1727949167 \ No newline at end of file From b61f7a098af46943f38175b595178f74c3f9bcd2 Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Fri, 4 Oct 2024 12:36:16 +0900 Subject: [PATCH 09/13] validate and export service --- .../Core/Models/LayoutRules/LayoutRule.cs | 28 ++++----- ...onType.cs => LayoutRuleErrorHandleType.cs} | 2 +- ...meta => LayoutRuleErrorHandleType.cs.meta} | 0 .../Models/Services/ApplyLayoutRuleService.cs | 44 +------------- .../Models/Services/BuildLayoutService.cs | 16 +++-- .../Services/ValidateLayoutRuleService.cs | 27 +++++++++ .../ValidateLayoutRuleService.cs.meta | 3 + .../AssetFilterImpl/CustomAssetFilter.cs | 12 +++- .../LayoutRuleEditorPresenter.cs | 14 +++-- .../LayoutRuleEditorWindow.cs | 14 +++-- .../LayoutViewer/LayoutViewerPresenter.cs | 4 +- .../Core/Tools/CLI/ApplyRulesCLIOptions.cs | 18 ++++-- .../Core/Tools/CLI/SmartAddresserCLI.cs | 37 +++++++----- .../Tools/CLI/ValidateLayoutRuleCLIOptions.cs | 6 ++ .../SmartAddresserAssetPostProcessor.cs | 57 +++++++++++++----- .../ExportLayoutRuleValidationErrorService.cs | 24 ++++++++ ...rtLayoutRuleValidationErrorService.cs.meta | 3 + .../Shared/SmartAddresserProjectSettings.cs | 26 ++++---- .../SmartAddresserProjectSettingsProvider.cs | 27 +++------ .../ValidateAndExportLayoutRuleService.cs | 60 +++++++++++++++++++ ...ValidateAndExportLayoutRuleService.cs.meta | 3 + .../Services/ApplyLayoutRuleServiceTest.cs | 5 +- .../Models/Services/BuildLayoutServiceTest.cs | 6 +- 23 files changed, 283 insertions(+), 153 deletions(-) rename Assets/SmartAddresser/Editor/Core/Models/LayoutRules/{LayoutRuleCorruptionNotificationType.cs => LayoutRuleErrorHandleType.cs} (84%) rename Assets/SmartAddresser/Editor/Core/Models/LayoutRules/{LayoutRuleCorruptionNotificationType.cs.meta => LayoutRuleErrorHandleType.cs.meta} (100%) create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Services/ValidateLayoutRuleService.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Models/Services/ValidateLayoutRuleService.cs.meta create mode 100644 Assets/SmartAddresser/Editor/Core/Tools/Shared/ExportLayoutRuleValidationErrorService.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Tools/Shared/ExportLayoutRuleValidationErrorService.cs.meta create mode 100644 Assets/SmartAddresser/Editor/Core/Tools/Shared/ValidateAndExportLayoutRuleService.cs create mode 100644 Assets/SmartAddresser/Editor/Core/Tools/Shared/ValidateAndExportLayoutRuleService.cs.meta diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs index 0a70d8d..8a1c72e 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRule.cs @@ -25,6 +25,16 @@ public sealed class LayoutRule public IObservableList LabelRules => _labelRules; public IObservableList VersionRules => _versionRules; + public void Setup() + { + foreach (var addressRule in _addressRules) + addressRule.Setup(); + foreach (var labelRule in _labelRules) + labelRule.Setup(); + foreach (var versionRule in _versionRules) + versionRule.Setup(); + } + /// /// * If there is no address group that hold the addressable group, add it. /// * Remove address rules that hold addressable groups that no longer exists. @@ -67,12 +77,6 @@ public bool SyncAddressRulesWithAddressableAssetGroups(List /// Provide the labels. /// @@ -141,12 +139,6 @@ public IReadOnlyCollection ProvideLabels( return labels; } - public void SetupForVersion() - { - foreach (var versionRule in _versionRules) - versionRule.Setup(); - } - public string ProvideVersion(string assetPath, Type assetType, bool isFolder, bool doSetup) { foreach (var versionRule in _versionRules) diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleErrorHandleType.cs similarity index 84% rename from Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs rename to Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleErrorHandleType.cs index 4b3a648..a5b9acf 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleErrorHandleType.cs @@ -4,7 +4,7 @@ namespace SmartAddresser.Editor.Core.Models.LayoutRules { - public enum LayoutRuleCorruptionNotificationType + public enum LayoutRuleErrorHandleType { Ignore, LogError, diff --git a/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs.meta b/Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleErrorHandleType.cs.meta similarity index 100% rename from Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleCorruptionNotificationType.cs.meta rename to Assets/SmartAddresser/Editor/Core/Models/LayoutRules/LayoutRuleErrorHandleType.cs.meta diff --git a/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs b/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs index c50dabd..1a9b742 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Services/ApplyLayoutRuleService.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using SmartAddresser.Editor.Core.Models.LayoutRules; @@ -6,7 +5,6 @@ using SmartAddresser.Editor.Foundation.AssetDatabaseAdapter; using SmartAddresser.Editor.Foundation.SemanticVersioning; using UnityEditor.AddressableAssets.Settings; -using UnityEngine; using Version = SmartAddresser.Editor.Foundation.SemanticVersioning.Version; namespace SmartAddresser.Editor.Core.Models.Services @@ -31,49 +29,13 @@ IAssetDatabaseAdapter assetDatabaseAdapter _assetDatabaseAdapter = assetDatabaseAdapter; } - /// - /// If you want to process multiple asset by this instance, you should call this method before you call - /// and set false to the doSetup argument of the . - /// If you want to process single asset by this instance, you should not call this method and set true to the - /// doSetup argument of the . - /// - public void Setup() - { - _layoutRule.SetupForAddress(); - _layoutRule.SetupForLabels(); - _layoutRule.SetupForVersion(); - } - - public void ValidateLayoutRules(LayoutRuleCorruptionNotificationType corruptionNotificationType) - { - if (corruptionNotificationType == LayoutRuleCorruptionNotificationType.Ignore) - return; - - if (_layoutRule.Validate(out var errorMessage)) - return; - - if (corruptionNotificationType == LayoutRuleCorruptionNotificationType.LogError) - { - Debug.LogError(errorMessage.ToJson(true)); - return; - } - - if (corruptionNotificationType == LayoutRuleCorruptionNotificationType.ThrowException) - throw new Exception(errorMessage.ToJson(true)); - } - /// /// Apply the layout rule to the addressable settings for all assets. /// - public void ApplyAll( - LayoutRuleCorruptionNotificationType layoutRuleCorruptionNotificationType = - LayoutRuleCorruptionNotificationType.Ignore - ) + public void ApplyAll(bool doSetup) { - Setup(); - - // Check Corruption - ValidateLayoutRules(layoutRuleCorruptionNotificationType); + if (doSetup) + _layoutRule.Setup(); // Add all entries to the addressable asset system. var removeTargetAssetGuids = new List(); diff --git a/Assets/SmartAddresser/Editor/Core/Models/Services/BuildLayoutService.cs b/Assets/SmartAddresser/Editor/Core/Models/Services/BuildLayoutService.cs index aad7f32..1b51f6b 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Services/BuildLayoutService.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Services/BuildLayoutService.cs @@ -25,13 +25,13 @@ public BuildLayoutService(IAssetDatabaseAdapter assetDatabaseAdapter) /// /// Build the from the . /// + /// /// /// - public Layout Execute(LayoutRule layoutRule) + public Layout Execute(bool doSetup, LayoutRule layoutRule) { - layoutRule.SetupForAddress(); - layoutRule.SetupForLabels(); - layoutRule.SetupForVersion(); + if (doSetup) + layoutRule.Setup(); var assetPaths = _assetDatabaseAdapter .GetAllAssetPaths() @@ -61,9 +61,13 @@ public Layout Execute(LayoutRule layoutRule) return layout; } - private static Task BuildGroupAsync(AddressRule addressRule, LayoutRule layoutRule, + private static Task BuildGroupAsync( + AddressRule addressRule, + LayoutRule layoutRule, IReadOnlyList assetPaths, - IReadOnlyList assetTypes, IReadOnlyList isFolders) + IReadOnlyList assetTypes, + IReadOnlyList isFolders + ) { if (!addressRule.Control.Value) return Task.FromResult((Group)null); diff --git a/Assets/SmartAddresser/Editor/Core/Models/Services/ValidateLayoutRuleService.cs b/Assets/SmartAddresser/Editor/Core/Models/Services/ValidateLayoutRuleService.cs new file mode 100644 index 0000000..b85bdd0 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Services/ValidateLayoutRuleService.cs @@ -0,0 +1,27 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using SmartAddresser.Editor.Core.Models.LayoutRules; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; + +namespace SmartAddresser.Editor.Core.Models.Services +{ + public sealed class ValidateLayoutRuleService + { + private readonly LayoutRule _layoutRule; + + public ValidateLayoutRuleService(LayoutRule layoutRule) + { + _layoutRule = layoutRule; + } + + public bool Execute(bool doSetup, out LayoutRuleValidationError error) + { + if (doSetup) + _layoutRule.Setup(); + + return _layoutRule.Validate(out error); + } + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Models/Services/ValidateLayoutRuleService.cs.meta b/Assets/SmartAddresser/Editor/Core/Models/Services/ValidateLayoutRuleService.cs.meta new file mode 100644 index 0000000..15ab90f --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Models/Services/ValidateLayoutRuleService.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4a917f88264b4f57b7c372f13c2d6735 +timeCreated: 1728007229 \ No newline at end of file diff --git a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs index f64f47f..e0cf43c 100644 --- a/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs +++ b/Assets/SmartAddresser/Editor/Core/Models/Shared/AssetGroups/AssetFilterImpl/CustomAssetFilter.cs @@ -25,16 +25,26 @@ public AssetFilterAsset AssetFilter public void SetupForMatching() { - assetFilter.SetupForMatching(); + if (assetFilter != null) + assetFilter.SetupForMatching(); } public bool Validate(out AssetFilterValidationError error) { + if (assetFilter == null) + { + error = new AssetFilterValidationError(this, new[] { "AssetFilter is null." }); + return false; + } + return assetFilter.Validate(out error); } public bool IsMatch(string assetPath, Type assetType, bool isFolder) { + if (assetFilter == null) + return true; + return assetFilter.IsMatch(assetPath, assetType, isFolder); } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs index b898562..c004ee9 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorPresenter.cs @@ -278,8 +278,15 @@ void OnMenuButtonClicked() void Apply() { - // Apply the layout rules to the addressable asset system. var layoutRule = _editingData.Value.LayoutRule; + layoutRule.Setup(); + + // Validate the layout rule. + var validateService = new ValidateAndExportLayoutRuleService(layoutRule); + var ruleErrorHandleType = projectSettings.LayoutRuleErrorSettings.HandleType; + validateService.Execute(false, ruleErrorHandleType, out _); + + // Apply the layout rules to the addressable asset system. var versionExpressionParser = new VersionExpressionParserRepository().Load(); var assetDatabaseAdapter = new AssetDatabaseAdapter(); var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; @@ -289,10 +296,7 @@ void Apply() addressableSettingsAdapter, assetDatabaseAdapter); - // Check Corruption - var corruptionNotificationType = - projectSettings.LayoutRuleCorruptionSettings.NotificationType; - applyService.ApplyAll(corruptionNotificationType); + applyService.ApplyAll(false); } }); diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs index 2646421..0c089b2 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutRuleEditor/LayoutRuleEditorWindow.cs @@ -75,6 +75,14 @@ private void OnLostFocus() if (primaryData != null) { var layoutRule = primaryData.LayoutRule; + layoutRule.Setup(); + + // Validate the layout rule. + var validateService = new ValidateAndExportLayoutRuleService(layoutRule); + var ruleErrorHandleType = projectSettings.LayoutRuleErrorSettings.HandleType; + validateService.Execute(false, ruleErrorHandleType, out _); + + // Apply the layout rule to the addressable asset system. var versionExpressionParser = new VersionExpressionParserRepository().Load(); var assetDatabaseAdapter = new AssetDatabaseAdapter(); var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; @@ -83,11 +91,7 @@ private void OnLostFocus() versionExpressionParser, addressableSettingsAdapter, assetDatabaseAdapter); - - // Check Corruption - var corruptionNotificationType = - projectSettings.LayoutRuleCorruptionSettings.NotificationType; - applyService.ApplyAll(corruptionNotificationType); + applyService.ApplyAll(false); } _hasAnyDataChanged = false; diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutViewer/LayoutViewerPresenter.cs b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutViewer/LayoutViewerPresenter.cs index 6423531..214aa95 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutViewer/LayoutViewerPresenter.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Addresser/LayoutViewer/LayoutViewerPresenter.cs @@ -80,7 +80,7 @@ private void SetupActiveView(LayoutRuleData data) var projectSettings = SmartAddresserProjectSettings.instance; var validationSettings = projectSettings.ValidationSettings; - var layout = _buildLayoutService.Execute(_editingData.Value.LayoutRule); + var layout = _buildLayoutService.Execute(true, _editingData.Value.LayoutRule); layout.Validate(false, validationSettings.DuplicateAddresses, validationSettings.DuplicateAssetPaths, validationSettings.EntryHasMultipleVersions); _layout = layout; @@ -180,7 +180,7 @@ void OnRefreshButtonClicked() { var projectSettings = SmartAddresserProjectSettings.instance; var validationSettings = projectSettings.ValidationSettings; - var layout = _buildLayoutService.Execute(_editingData.Value.LayoutRule); + var layout = _buildLayoutService.Execute(true, _editingData.Value.LayoutRule); layout.Validate(false, validationSettings.DuplicateAddresses, validationSettings.DuplicateAssetPaths, validationSettings.EntryHasMultipleVersions); _layout = layout; diff --git a/Assets/SmartAddresser/Editor/Core/Tools/CLI/ApplyRulesCLIOptions.cs b/Assets/SmartAddresser/Editor/Core/Tools/CLI/ApplyRulesCLIOptions.cs index 7cde80a..4156831 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/CLI/ApplyRulesCLIOptions.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/CLI/ApplyRulesCLIOptions.cs @@ -5,13 +5,16 @@ namespace SmartAddresser.Editor.Core.Tools.CLI public sealed class ApplyRulesCLIOptions { private const string LayoutRuleAssetPathArgName = "-layoutRuleAssetPath"; - private const string ShouldValidateArgName = "-validate"; + private const string ObsoleteShouldValidateArgName = "-validate"; // Keep for backward compatibility private const string ResultFilePathArgName = "-resultFilePath"; private const string FailWhenWarningArgName = "-failWhenWarning"; private const string DefaultResultFilePathWithoutExtensions = "SmartAddresser/validate_result"; + private const string ShouldValidateLayoutRuleArgName = "-validateLayoutRule"; + private const string ShouldValidateLayoutArgName = "-validateLayout"; public string LayoutRuleAssetPath { get; private set; } - public bool ShouldValidate { get; private set; } + public bool ShouldValidateLayout { get; private set; } + public bool ShouldValidateLayoutRule { get; private set; } public string ResultFilePath { get; private set; } public bool FailWhenWarning { get; private set; } @@ -28,8 +31,15 @@ public static ApplyRulesCLIOptions CreateFromCommandLineArgs() resultFilePath = $"{DefaultResultFilePathWithoutExtensions}.json"; options.ResultFilePath = resultFilePath; - // Do Validate - options.ShouldValidate = CommandLineUtility.Contains(ShouldValidateArgName); + // Do Validate Layout + var obsoleteShouldValidateLayout = CommandLineUtility.Contains(ObsoleteShouldValidateArgName); + var newShouldValidateLayout = CommandLineUtility.Contains(ShouldValidateLayoutArgName); + var shouldValidateLayout = obsoleteShouldValidateLayout || newShouldValidateLayout; + options.ShouldValidateLayout = shouldValidateLayout; + + // Do Validate Layout Rule + var shouldValidateLayoutRule = CommandLineUtility.Contains(ShouldValidateLayoutRuleArgName); + options.ShouldValidateLayoutRule = shouldValidateLayoutRule; // Fail When Warning options.FailWhenWarning = CommandLineUtility.Contains(FailWhenWarningArgName); diff --git a/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs b/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs index 41d9ec4..7534576 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.cs @@ -49,20 +49,10 @@ public static void ValidateLayoutRules() { var options = ValidateLayoutRuleCLIOptions.CreateFromCommandLineArgs(); var layoutRule = LoadLayoutRuleData(options.LayoutRuleAssetPath).LayoutRule; - var versionExpressionParser = new VersionExpressionParserRepository().Load(); - var assetDatabaseAdapter = new AssetDatabaseAdapter(); - var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; - var addressableSettingsAdapter = new AddressableAssetSettingsAdapter(addressableSettings); - - var applyService = new ApplyLayoutRuleService(layoutRule, - versionExpressionParser, - addressableSettingsAdapter, - assetDatabaseAdapter); - - applyService.Setup(); + var validateService = new ValidateAndExportLayoutRuleService(layoutRule, options.ErrorLogFilePath); try { - applyService.ValidateLayoutRules(LayoutRuleCorruptionNotificationType.ThrowException); + validateService.Execute(true, LayoutRuleErrorHandleType.ThrowException, out _); } catch (Exception e) { @@ -93,11 +83,28 @@ public static void ApplyRules() var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; var addressableSettingsAdapter = new AddressableAssetSettingsAdapter(addressableSettings); - if (options.ShouldValidate) + layoutRule.Setup(); + + if (options.ShouldValidateLayoutRule) + { + var validateService = new ValidateAndExportLayoutRuleService(layoutRule); + try + { + validateService.Execute(false, LayoutRuleErrorHandleType.ThrowException, out _); + } + catch (Exception e) + { + Debug.LogError(e); + EditorApplication.Exit(ErrorLevelValidateFailed); + return; + } + } + + if (options.ShouldValidateLayout) { // Build and validate the Layout. var buildLayoutService = new BuildLayoutService(assetDatabaseAdapter); - var layout = buildLayoutService.Execute(layoutRule); + var layout = buildLayoutService.Execute(false, layoutRule); layout.Validate(true, validationSettings.DuplicateAddresses, validationSettings.DuplicateAssetPaths, @@ -122,7 +129,7 @@ public static void ApplyRules() addressableSettingsAdapter, assetDatabaseAdapter); - applyService.ApplyAll(projectSettings.LayoutRuleCorruptionSettings.NotificationType); + applyService.ApplyAll(false); EditorApplication.Exit(ErrorLevelNone); } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs b/Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs index 4c2db5a..1b666b5 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/CLI/ValidateLayoutRuleCLIOptions.cs @@ -5,8 +5,10 @@ namespace SmartAddresser.Editor.Core.Tools.CLI public sealed class ValidateLayoutRuleCLIOptions { private const string LayoutRuleAssetPathArgName = "-layoutRuleAssetPath"; + private const string ErrorLogFilePathArgName = "-errorLogFilePath"; public string LayoutRuleAssetPath { get; private set; } + public string ErrorLogFilePath { get; private set; } public static ValidateLayoutRuleCLIOptions CreateFromCommandLineArgs() { @@ -16,6 +18,10 @@ public static ValidateLayoutRuleCLIOptions CreateFromCommandLineArgs() if (CommandLineUtility.TryGetStringValue(LayoutRuleAssetPathArgName, out var layoutRuleAssetPath)) options.LayoutRuleAssetPath = layoutRuleAssetPath; + // Error Log File Path + if (!CommandLineUtility.TryGetStringValue(ErrorLogFilePathArgName, out var errorLogFilePath)) + options.ErrorLogFilePath = errorLogFilePath; + return options; } } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs b/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs index 3407e59..2c35ca5 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs @@ -1,3 +1,5 @@ +using System.Linq; +using SmartAddresser.Editor.Core.Models.LayoutRules; using SmartAddresser.Editor.Core.Models.Services; using SmartAddresser.Editor.Core.Tools.Addresser.Shared; using SmartAddresser.Editor.Core.Tools.Shared; @@ -5,6 +7,7 @@ using SmartAddresser.Editor.Foundation.AssetDatabaseAdapter; using UnityEditor; using UnityEditor.AddressableAssets; +using UnityEngine; namespace SmartAddresser.Editor.Core.Tools.Importer { @@ -17,18 +20,15 @@ private static void OnPostprocessAllAssets( string[] movedFromAssetPaths ) { - // Delay 1 frame because AddressableAssetSettingsDefaultObject.Settings may be null at this point when the Library folder is deleted. - EditorApplication.delayCall += () => - LazyOnPostprocessAllAssets(importedAssetPaths, deletedAssetPaths, movedAssetPaths, movedFromAssetPaths); - } + var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; + + // Check this because AddressableAssetSettingsDefaultObject.Settings may be null at this point when the Library folder is deleted. + if (addressableSettings == null) + return; + + if (!ShouldProcess(importedAssetPaths, deletedAssetPaths, movedAssetPaths, movedFromAssetPaths)) + return; - private static void LazyOnPostprocessAllAssets( - string[] importedAssetPaths, - string[] deletedAssetPaths, - string[] movedAssetPaths, - string[] movedFromAssetPaths - ) - { var layoutRuleDataRepository = new LayoutRuleDataRepository(); var primaryData = layoutRuleDataRepository.PrimaryData; @@ -39,19 +39,21 @@ string[] movedFromAssetPaths var layoutRule = layoutRuleDataRepository.PrimaryData.LayoutRule; var versionExpressionParser = new VersionExpressionParserRepository().Load(); var assetDatabaseAdapter = new AssetDatabaseAdapter(); - var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; var addressableSettingsAdapter = new AddressableAssetSettingsAdapter(addressableSettings); var applyService = new ApplyLayoutRuleService(layoutRule, versionExpressionParser, addressableSettingsAdapter, assetDatabaseAdapter); - applyService.Setup(); + var validateLayoutRuleService = new ValidateAndExportLayoutRuleService(layoutRule); - // Check Corruption + layoutRule.Setup(); + + // Check Layout Rule corruption var projectSettings = SmartAddresserProjectSettings.instance; - var corruptionNotificationType = projectSettings.LayoutRuleCorruptionSettings.NotificationType; - applyService.ValidateLayoutRules(corruptionNotificationType); + var layoutRuleErrorHandleType = projectSettings.LayoutRuleErrorSettings.HandleType; + validateLayoutRuleService.Execute(false, layoutRuleErrorHandleType, out _); + // Apply var versionExpression = layoutRule.Settings.VersionExpression.Value; if (string.IsNullOrEmpty(versionExpression)) versionExpression = null; @@ -70,5 +72,28 @@ string[] movedFromAssetPaths applyService.InvokeBatchModificationEvent(); } + + private static bool ShouldProcess( + string[] importedAssetPaths, + string[] deletedAssetPaths, + string[] movedAssetPaths, + string[] movedFromAssetPaths + ) + { + return importedAssetPaths + .Concat(deletedAssetPaths) + .Concat(movedAssetPaths) + .Concat(movedFromAssetPaths) + .Any(IsTarget); + } + + private static bool IsTarget(string assetPath) + { + var type = AssetDatabase.GetMainAssetTypeAtPath(assetPath); + if (type == typeof(LayoutRuleData)) + return false; + + return true; + } } } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Shared/ExportLayoutRuleValidationErrorService.cs b/Assets/SmartAddresser/Editor/Core/Tools/Shared/ExportLayoutRuleValidationErrorService.cs new file mode 100644 index 0000000..936fa25 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Tools/Shared/ExportLayoutRuleValidationErrorService.cs @@ -0,0 +1,24 @@ +using System.IO; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; +using UnityEngine; + +namespace SmartAddresser.Editor.Core.Tools.Shared +{ + public sealed class ExportLayoutRuleValidationErrorService + { + public void Run(LayoutRuleValidationError error, string filePath) + { + var json = JsonUtility.ToJson(error, true); + ExportText(json, filePath); + } + + private static void ExportText(string text, string filePath) + { + var folderPath = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(folderPath) && !Directory.Exists(folderPath)) + Directory.CreateDirectory(folderPath); + + File.WriteAllText(filePath, text); + } + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Shared/ExportLayoutRuleValidationErrorService.cs.meta b/Assets/SmartAddresser/Editor/Core/Tools/Shared/ExportLayoutRuleValidationErrorService.cs.meta new file mode 100644 index 0000000..fe86531 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Tools/Shared/ExportLayoutRuleValidationErrorService.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 79c4daed860e45978b16ad532d4a2a58 +timeCreated: 1728007390 \ No newline at end of file diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs index d0e792b..a261973 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettings.cs @@ -12,7 +12,7 @@ public sealed class SmartAddresserProjectSettings : ScriptableSingleton layoutRuleCorruption; + get => layoutRuleError; set { - if (value == layoutRuleCorruption) + if (value == layoutRuleError) return; - layoutRuleCorruption = value; + layoutRuleError = value; Save(true); } } @@ -94,23 +94,23 @@ EntryErrorType entryHasMultipleVersions } [Serializable] - public sealed class LayoutRuleCorruption + public sealed class LayoutRuleError { - [SerializeField] private LayoutRuleCorruptionNotificationType notificationType = - LayoutRuleCorruptionNotificationType.LogError; + [SerializeField] private LayoutRuleErrorHandleType handleType = + LayoutRuleErrorHandleType.LogError; - public LayoutRuleCorruption() + public LayoutRuleError() { } - public LayoutRuleCorruption( - LayoutRuleCorruptionNotificationType notificationType + public LayoutRuleError( + LayoutRuleErrorHandleType handleType ) { - this.notificationType = notificationType; + this.handleType = handleType; } - public LayoutRuleCorruptionNotificationType NotificationType => notificationType; + public LayoutRuleErrorHandleType HandleType => handleType; } } } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs index 480d901..e68d3d6 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Shared/SmartAddresserProjectSettingsProvider.cs @@ -1,11 +1,7 @@ using System.Collections.Generic; using SmartAddresser.Editor.Core.Models.LayoutRules; using SmartAddresser.Editor.Core.Models.Layouts; -using SmartAddresser.Editor.Core.Models.Services; -using SmartAddresser.Editor.Foundation.AddressableAdapter; -using SmartAddresser.Editor.Foundation.AssetDatabaseAdapter; using UnityEditor; -using UnityEditor.AddressableAssets; using UnityEngine; namespace SmartAddresser.Editor.Core.Tools.Shared @@ -53,19 +49,12 @@ public override void OnGUI(string searchContext) "Cancel")) { var layoutRule = projectSettings.PrimaryData.LayoutRule; - var versionExpressionParser = new VersionExpressionParserRepository().Load(); - var assetDatabaseAdapter = new AssetDatabaseAdapter(); - var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; - var addressableSettingsAdapter = new AddressableAssetSettingsAdapter(addressableSettings); - var applyService = new ApplyLayoutRuleService(layoutRule, - versionExpressionParser, - addressableSettingsAdapter, - assetDatabaseAdapter); + var validateService = new ValidateAndExportLayoutRuleService(layoutRule); // Check Corruption var corruptionNotificationType = - projectSettings.LayoutRuleCorruptionSettings.NotificationType; - applyService.ApplyAll(corruptionNotificationType); + projectSettings.LayoutRuleErrorSettings.HandleType; + validateService.Execute(true, corruptionNotificationType, out _); } else { @@ -101,12 +90,12 @@ public override void OnGUI(string searchContext) using (var ccs = new EditorGUI.ChangeCheckScope()) { var notificationTypeOnApplyAll = - (LayoutRuleCorruptionNotificationType)EditorGUILayout.EnumPopup( - "Layout Rule Corruption", - projectSettings.LayoutRuleCorruptionSettings.NotificationType); + (LayoutRuleErrorHandleType)EditorGUILayout.EnumPopup( + "Layout Rule Error", + projectSettings.LayoutRuleErrorSettings.HandleType); if (ccs.changed) - projectSettings.LayoutRuleCorruptionSettings = - new SmartAddresserProjectSettings.LayoutRuleCorruption(notificationTypeOnApplyAll); + projectSettings.LayoutRuleErrorSettings = + new SmartAddresserProjectSettings.LayoutRuleError(notificationTypeOnApplyAll); } } } diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Shared/ValidateAndExportLayoutRuleService.cs b/Assets/SmartAddresser/Editor/Core/Tools/Shared/ValidateAndExportLayoutRuleService.cs new file mode 100644 index 0000000..8b1e762 --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Tools/Shared/ValidateAndExportLayoutRuleService.cs @@ -0,0 +1,60 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using SmartAddresser.Editor.Core.Models.LayoutRules; +using SmartAddresser.Editor.Core.Models.Services; +using SmartAddresser.Editor.Core.Models.Shared.AssetGroups.ValidationError; +using UnityEngine; + +namespace SmartAddresser.Editor.Core.Tools.Shared +{ + public sealed class ValidateAndExportLayoutRuleService + { + public const string DefaultExportFilePath = "Logs/SmartAddresser_LayoutRuleError.json"; + private readonly string _exportFilePath; + + private readonly ExportLayoutRuleValidationErrorService _exportService; + private readonly ValidateLayoutRuleService _validateService; + + public ValidateAndExportLayoutRuleService(LayoutRule layoutRule, string overrideExportFilePath = null) + { + _validateService = new ValidateLayoutRuleService(layoutRule); + _exportService = new ExportLayoutRuleValidationErrorService(); + _exportFilePath = string.IsNullOrEmpty(overrideExportFilePath) + ? DefaultExportFilePath + : overrideExportFilePath; + } + + public bool Execute( + bool doSetup, + LayoutRuleErrorHandleType handleType, + out LayoutRuleValidationError error + ) + { + if (handleType == LayoutRuleErrorHandleType.Ignore) + { + error = null; + return false; + } + + // Validate + if (_validateService.Execute(doSetup, out error)) + return true; + + // Export + _exportService.Run(error, _exportFilePath); + + // Log / Exception + var message = + $"[Smart Addresser] There are errors in the layout rule. Please check {_exportFilePath} for details."; + if (handleType == LayoutRuleErrorHandleType.LogError) + Debug.LogError(message); + else if (handleType == LayoutRuleErrorHandleType.ThrowException) + throw new Exception(message); + + return false; + } + } +} diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Shared/ValidateAndExportLayoutRuleService.cs.meta b/Assets/SmartAddresser/Editor/Core/Tools/Shared/ValidateAndExportLayoutRuleService.cs.meta new file mode 100644 index 0000000..650919c --- /dev/null +++ b/Assets/SmartAddresser/Editor/Core/Tools/Shared/ValidateAndExportLayoutRuleService.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a65b3a7658e443358e10aa7c8186f36f +timeCreated: 1728008200 \ No newline at end of file diff --git a/Assets/SmartAddresser/Tests/Editor/Core/Models/Services/ApplyLayoutRuleServiceTest.cs b/Assets/SmartAddresser/Tests/Editor/Core/Models/Services/ApplyLayoutRuleServiceTest.cs index f5b3999..4285d8e 100644 --- a/Assets/SmartAddresser/Tests/Editor/Core/Models/Services/ApplyLayoutRuleServiceTest.cs +++ b/Assets/SmartAddresser/Tests/Editor/Core/Models/Services/ApplyLayoutRuleServiceTest.cs @@ -63,8 +63,7 @@ public void PreSetup() addressableSettingsAdapter, assetDatabaseAdapter); - service.Setup(); - service.Apply(assetGuid, false, false); + service.Apply(assetGuid, true, false); var assetEntry = addressableSettingsAdapter.FindAssetEntry(assetGuid); Assert.That(assetEntry, Is.Not.Null); Assert.That(assetEntry.GroupName, Is.EqualTo(TestAddressableGroupName)); @@ -95,7 +94,6 @@ public void AddressIsNotAssignedAndBelongingToControlGroup_Removed() foreach (var addressRule in layoutRule.AddressRules) addressRule.AssetGroups.Clear(); // Apply again - service.Setup(); service.Apply(assetGuid, true, false); // The entry should be removed. assetEntry = addressableSettingsAdapter.FindAssetEntry(assetGuid); @@ -126,7 +124,6 @@ public void AddressIsNotAssignedAndBelongingToNotControlGroup_NotRemoved() foreach (var addressRule in layoutRule.AddressRules) addressRule.Control.Value = false; // Apply again - service.Setup(); service.Apply(assetGuid, true, false); // The entry should not be removed. assetEntry = addressableSettingsAdapter.FindAssetEntry(assetGuid); diff --git a/Assets/SmartAddresser/Tests/Editor/Core/Models/Services/BuildLayoutServiceTest.cs b/Assets/SmartAddresser/Tests/Editor/Core/Models/Services/BuildLayoutServiceTest.cs index 4ac4527..5bb332e 100644 --- a/Assets/SmartAddresser/Tests/Editor/Core/Models/Services/BuildLayoutServiceTest.cs +++ b/Assets/SmartAddresser/Tests/Editor/Core/Models/Services/BuildLayoutServiceTest.cs @@ -29,7 +29,7 @@ public void LayoutRuleContainsTargetAsset_LayoutIsCreated() var service = new BuildLayoutService(adapter); var layoutRule = CreateLayoutRule(addressableGroupName, assetPath, PartialAssetPathType.AssetPath, labels, versions); - var layout = service.Execute(layoutRule); + var layout = service.Execute(true, layoutRule); Assert.That(layout.Groups.Count, Is.EqualTo(1)); Assert.That(layout.Groups[0].AddressableGroup.Name, Is.EqualTo(addressableGroupName)); @@ -51,7 +51,7 @@ public void LayoutRuleNotContainsTargetAsset_GroupIsNotCreated() var adapter = CreateSingleEntryAssetDatabaseAdapter("", targetAssetPath, typeof(Object), false); var service = new BuildLayoutService(adapter); var layoutRule = CreateLayoutRule(addressableGroupName, ruleAssetPath, PartialAssetPathType.AssetPath); - var layout = service.Execute(layoutRule); + var layout = service.Execute(true, layoutRule); Assert.That(layout.Groups.Count, Is.EqualTo(0)); } @@ -65,7 +65,7 @@ public void TargetAssetIsScript_GroupIsNotCreated() var adapter = CreateSingleEntryAssetDatabaseAdapter("", assetPath, typeof(Object), false); var service = new BuildLayoutService(adapter); var layoutRule = CreateLayoutRule(addressableGroupName, assetPath, PartialAssetPathType.AssetPath); - var layout = service.Execute(layoutRule); + var layout = service.Execute(true, layoutRule); Assert.That(layout.Groups.Count, Is.EqualTo(0)); } From 2db49889a524df4086134e6d6f28cb00ed2ecdbf Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Fri, 4 Oct 2024 12:47:29 +0900 Subject: [PATCH 10/13] README --- .../SmartAddresserAssetPostProcessor.cs | 1 - README.md | 10 ++++++---- README_JA.md | 20 ++++++++++--------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs b/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs index 2c35ca5..ce8f847 100644 --- a/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs +++ b/Assets/SmartAddresser/Editor/Core/Tools/Importer/SmartAddresserAssetPostProcessor.cs @@ -7,7 +7,6 @@ using SmartAddresser.Editor.Foundation.AssetDatabaseAdapter; using UnityEditor; using UnityEditor.AddressableAssets; -using UnityEngine; namespace SmartAddresser.Editor.Core.Tools.Importer { diff --git a/README.md b/README.md index 3867402..cb4e01b 100644 --- a/README.md +++ b/README.md @@ -430,6 +430,7 @@ Command line arguments are as follows. | Argument Name | Description | |------------------------------------------|-------------------------------------------------------------------------------------------------| | -layoutRuleAssetPath \ | Asset Path of the Layout Rule Data to be applied.
If not specified, use the first one found. | +| -errorLogFilePath \ | Output file path for validation result.
Default is Logs/SmartAddresser_LayoutRuleError.json. | When completed, Unity is automatically closed and returns the following value. @@ -451,12 +452,13 @@ The following is an example of how to apply the layout rules from the command li Command line arguments are as follows. -| Argument Name | Description | -|---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| Argument Name | Description | +|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| | -layoutRuleAssetPath \ | Asset Path of the Layout Rule Data to be applied.
If not specified, use the first one found. | -| -validate | If enabled, validation will be executed before applying.
The validation is a time-consuming process so can be skipped if not needed. | +| -validateLayoutRule | If enabled, check for corrupted layout rules before applying. | +| -validateLayout | If enabled, validation will be executed before applying.
The validation is a time-consuming process so can be skipped if not needed. | | -resultFilePath \ | Output file path for validation result.
Default is SmartAddresser/validate_result.json. | -| -failWhenWarning | If enabled, any warning in the validation is considered an execution error. | +| -failWhenWarning | If enabled, any warning in the validation is considered an execution error. | When completed, Unity is automatically closed and returns the following value. diff --git a/README_JA.md b/README_JA.md index 2be9b52..ce88271 100644 --- a/README_JA.md +++ b/README_JA.md @@ -434,9 +434,10 @@ Layout Rule Editor からは以下の手順で適用することができます コマンドライン引数は以下の通りです。 -| 引数名 | 説明 | -|-----------------------------------|-------------------------------------------------------| -| -layoutRuleAssetPath \ | 適用するレイアウトルールデータのアセットパス。
指定されない場合は最初に見つかったものを使用します。 | +| 引数名 | 説明 | +|------------------------------------|---------------------------------------------------------| +| -layoutRuleAssetPath \ | 適用するレイアウトルールデータのアセットパス。
指定されない場合は最初に見つかったものを使用します。 | +| -errorLogFilePath \ | バリデーション結果の出力ファイルパス。
デフォルトはLogs/SmartAddresser_LayoutRuleError.json | 実行が完了すると自動的にUnityを終了し、戻り値として以下の値を返します。 @@ -456,12 +457,13 @@ Layout Rule Editor からは以下の手順で適用することができます コマンドライン引数は以下の通りです。 -| 引数名 | 説明 | -|---------------------------------|---------------------------------------------------------------------------------------------------------| -| -layoutRuleAssetPath \ | 適用するレイアウトルールデータのアセットパス。
指定されない場合は最初に見つかったものを使用します。 | -| -validate | このオプションを有効にした場合、反映する前にバリデーションが実行されます。
バリデーションは処理時間のかかるプロセスであるため、レイアウトルールに問題がないことが保証されている場合にはスキップできます。 | -| -resultFilePath \ | バリデーション結果の出力ファイルパス。
デフォルトはSmartAddresser/validate_result.json。 | -| -failWhenWarning | このオプションを有効にした場合、バリデーションで警告が発生した場合に実行エラーとみなします。 | +| 引数名 | 説明 | +|------------------------------------|-------------------------------------------------------------------------------------------------------------| +| -layoutRuleAssetPath \ | 適用するレイアウトルールデータのアセットパス。
指定されない場合は最初に見つかったものを使用します。 | +| -validateLayoutRule | このオプションを有効にした場合、反映する前に破損しているレイアウトルールがないかチェックされます。 | +| -validateLayout | このオプションを有効にした場合、反映する前にレイアウトのバリデーションが実行されます。
バリデーションは処理時間のかかるプロセスであるため、レイアウトルールに問題がないことが保証されている場合にはスキップできます。 | +| -resultFilePath \ | バリデーション結果の出力ファイルパス。
デフォルトはSmartAddresser/validate_result.json。 | +| -failWhenWarning | このオプションを有効にした場合、バリデーションで警告が発生した場合に実行エラーとみなします。 | 実行が完了すると自動的にUnityを終了し、戻り値として以下の値を返します。 From d3bf392c98acb72ced26a29a79237f6f5f3d07b4 Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Fri, 4 Oct 2024 13:18:41 +0900 Subject: [PATCH 11/13] fix README --- README.md | 4 ++-- README_JA.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cb4e01b..a93f215 100644 --- a/README.md +++ b/README.md @@ -399,7 +399,7 @@ You can set the version expression by calling the following method. The following is an example of how to set the version expression from the command line in Mac. ``` -/Applications/Unity/Hub/Editor/2020.3.40f1/Unity.app/Contents/MacOS/Unity -projectPath [Your Project Path Here] -executeMethod Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.SetVersionExpression +/Applications/Unity/Hub/Editor/2020.3.40f1/Unity.app/Contents/MacOS/Unity -projectPath [Your Project Path Here] -executeMethod SmartAddresser.Editor.Core.Tools.CLI.SmartAddresserCLI.SetVersionExpression ``` Command line arguments are as follows. @@ -422,7 +422,7 @@ You can use the `SmartAddresser.Editor.Core.Tools.CLI.SmartAddresserCLI.Validate The following is an example of how to check for corrupted layout rules from the command line in Mac. ``` -/Applications/Unity/Hub/Editor/2020.3.40f1/Unity.app/Contents/MacOS/Unity -projectPath [Your Project Path Here] -executeMethod Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.ValidateLayoutRules +/Applications/Unity/Hub/Editor/2020.3.40f1/Unity.app/Contents/MacOS/Unity -projectPath [Your Project Path Here] -executeMethod SmartAddresser.Editor.Core.Tools.CLI.SmartAddresserCLI.ValidateLayoutRules ``` Command line arguments are as follows. diff --git a/README_JA.md b/README_JA.md index ce88271..06f0e0a 100644 --- a/README_JA.md +++ b/README_JA.md @@ -407,7 +407,7 @@ Layout Rule Editor からは以下の手順で適用することができます 以下はMacでコマンドライン実行を行う例です。 ``` -/Applications/Unity/Hub/Editor/2020.3.40f1/Unity.app/Contents/MacOS/Unity -projectPath [Your Project Path Here] -executeMethod Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.SetVersionExpression +/Applications/Unity/Hub/Editor/2020.3.40f1/Unity.app/Contents/MacOS/Unity -projectPath [Your Project Path Here] -executeMethod SmartAddresser.Editor.Core.Tools.CLI.SmartAddresserCLI.SetVersionExpression ``` コマンドライン引数は以下の通りです。 @@ -429,7 +429,7 @@ Layout Rule Editor からは以下の手順で適用することができます 以下はMacでコマンドライン実行を行う例です。 ``` -/Applications/Unity/Hub/Editor/2020.3.40f1/Unity.app/Contents/MacOS/Unity -projectPath [Your Project Path Here] -executeMethod Assets/SmartAddresser/Editor/Core/Tools/CLI/SmartAddresserCLI.ValidateLayoutRules +/Applications/Unity/Hub/Editor/2020.3.40f1/Unity.app/Contents/MacOS/Unity -projectPath [Your Project Path Here] -executeMethod SmartAddresser.Editor.Core.Tools.CLI.SmartAddresserCLI.ValidateLayoutRules ``` コマンドライン引数は以下の通りです。 From cbab638b32a4ac6cf3e19e9d21652015f63f7983 Mon Sep 17 00:00:00 2001 From: Haruki Yano Date: Mon, 7 Oct 2024 11:34:52 +0900 Subject: [PATCH 12/13] =?UTF-8?q?=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- README_JA.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a93f215..ed35ef5 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ There are cases where the layout rules are corrupted, such as when the object se You can check for corrupted layout rules by setting **Project Settings > Smart Addresser > Layout Rule Corruption**.

- Corrupted Rule + Layout Rule Corruption

The items are as follows. diff --git a/README_JA.md b/README_JA.md index 06f0e0a..705f5e2 100644 --- a/README_JA.md +++ b/README_JA.md @@ -307,7 +307,7 @@ Layout Rule Editor からは以下の手順で適用することができます **Project Settings > Smart Addresser > Layout Rule Corruption** を設定することでレイアウトルールの破損をチェックすることができます。

- Corrupted Rule + Layout Rule Corruption

項目の説明は以下の通りです。 @@ -422,9 +422,9 @@ Layout Rule Editor からは以下の手順で適用することができます - 実行が成功した場合: 0 - 実行中にエラーが発生した場合: 2 -### レイアウトルールの破損をチェックする +### レイアウトルールの破損を検知する -コマンドラインから **Version Expression** を設定するには`SmartAddresser.Editor.Core.Tools.CLI.SmartAddresserCLI.ValidateLayoutRules`を呼びます。 +コマンドラインからレイアウトルールの破損を検知するには`SmartAddresser.Editor.Core.Tools.CLI.SmartAddresserCLI.ValidateLayoutRules`を呼びます。 以下はMacでコマンドライン実行を行う例です。 From e49610e337cc01a5b6b456bb68c7d54c3abe1f43 Mon Sep 17 00:00:00 2001 From: Haruma-K Date: Mon, 7 Oct 2024 02:35:11 +0000 Subject: [PATCH 13/13] chore(docs): update TOC --- README_JA.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_JA.md b/README_JA.md index 705f5e2..b78ed1a 100644 --- a/README_JA.md +++ b/README_JA.md @@ -51,7 +51,7 @@ - [独自のバージョン範囲表現を使う](#%E7%8B%AC%E8%87%AA%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%AF%84%E5%9B%B2%E8%A1%A8%E7%8F%BE%E3%82%92%E4%BD%BF%E3%81%86) - [コマンドラインインターフェース (CLI)](#%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9-cli) - [Version Expressionを設定する](#version-expression%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B) - - [レイアウトルールの破損をチェックする](#%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%AE%E7%A0%B4%E6%90%8D%E3%82%92%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B) + - [レイアウトルールの破損を検知する](#%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%AE%E7%A0%B4%E6%90%8D%E3%82%92%E6%A4%9C%E7%9F%A5%E3%81%99%E3%82%8B-1) - [レイアウトルールを Addressables に反映する](#%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E3%83%AB%E3%83%BC%E3%83%AB%E3%82%92-addressables-%E3%81%AB%E5%8F%8D%E6%98%A0%E3%81%99%E3%82%8B) - [スクリプティング](#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0) - [レイアウトルールデータを編集する](#%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E3%83%AB%E3%83%BC%E3%83%AB%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E7%B7%A8%E9%9B%86%E3%81%99%E3%82%8B)