From 95a3c32250b37e93c443c24faab237d4e576aff7 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 17 Sep 2026 18:43:03 +0200 Subject: [PATCH] Replace the asset path attribute with a stable GUID identity CultInspectorAssetPathAttribute stored an engine asset path, which breaks when the asset moves. CultInspectorAssetGuidAttribute stores the asset's stable identity (in Unity, the .meta GUID) instead. The Studio drawer now resolves the stored GUID to a path only to load the preview object, and writes back AssetPathToGUID on a change. No path form survives for the next field to pick up. Co-Authored-By: Claude Opus 5 --- src/GameCult.Caching/CultInspectorAttributes.cs | 9 +++++---- src/GameCult.Caching/CultInspectorModel.cs | 4 ++-- .../Caching/Editor/CultCacheStudioDrawers.cs | 9 +++++---- src/GameCult.Unity/Assets/Caching/README.md | 9 +++++---- .../CultInspectorModelTests.cs | 17 +++++++++++++++++ 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/GameCult.Caching/CultInspectorAttributes.cs b/src/GameCult.Caching/CultInspectorAttributes.cs index 881208ea..2ae523e0 100644 --- a/src/GameCult.Caching/CultInspectorAttributes.cs +++ b/src/GameCult.Caching/CultInspectorAttributes.cs @@ -64,12 +64,13 @@ namespace GameCult.Caching public float Max { get; } } - // A string member holding an engine asset path. AssetType is the engine's asset type, named by the consumer - // (typeof(UnityEngine.Texture2D) in a Unity assembly); a lowering that does not know the type ignores it. + // A string member holding an engine asset's stable identity; in Unity, the `.meta` GUID. AssetType is the + // engine's asset type, named by the consumer (typeof(UnityEngine.Texture2D) in a Unity assembly); a lowering + // that does not know the type ignores it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true)] - public sealed class CultInspectorAssetPathAttribute : Attribute + public sealed class CultInspectorAssetGuidAttribute : Attribute { - public CultInspectorAssetPathAttribute(Type? assetType = null) + public CultInspectorAssetGuidAttribute(Type? assetType = null) { AssetType = assetType; } diff --git a/src/GameCult.Caching/CultInspectorModel.cs b/src/GameCult.Caching/CultInspectorModel.cs index 6a86f5ee..e5b5581f 100644 --- a/src/GameCult.Caching/CultInspectorModel.cs +++ b/src/GameCult.Caching/CultInspectorModel.cs @@ -41,7 +41,7 @@ namespace GameCult.Caching Order = Find()?.Order; Range = Find(); TextArea = Find(); - AssetPath = Find(); + AssetGuid = Find(); } public IReadOnlyList Attributes { get; } @@ -53,7 +53,7 @@ namespace GameCult.Caching public int? Order { get; } public CultInspectorRangeAttribute? Range { get; } public CultInspectorTextAreaAttribute? TextArea { get; } - public CultInspectorAssetPathAttribute? AssetPath { get; } + public CultInspectorAssetGuidAttribute? AssetGuid { get; } public T? Find() where T : Attribute => Attributes.OfType().FirstOrDefault(); } diff --git a/src/GameCult.Unity/Assets/Caching/Editor/CultCacheStudioDrawers.cs b/src/GameCult.Unity/Assets/Caching/Editor/CultCacheStudioDrawers.cs index 2b4a87cb..ab1b1e84 100644 --- a/src/GameCult.Unity/Assets/Caching/Editor/CultCacheStudioDrawers.cs +++ b/src/GameCult.Unity/Assets/Caching/Editor/CultCacheStudioDrawers.cs @@ -167,12 +167,13 @@ namespace GameCult.Unity.Caching.Editor private static string DrawString(string label, string value, CultInspectorMetadata metadata) { - if (metadata.AssetPath != null) + if (metadata.AssetGuid != null) { - var assetType = metadata.AssetPath.AssetType != null && typeof(Object).IsAssignableFrom(metadata.AssetPath.AssetType) ? metadata.AssetPath.AssetType : typeof(Object); - var asset = string.IsNullOrEmpty(value) ? null : AssetDatabase.LoadAssetAtPath(value, assetType); + var assetType = metadata.AssetGuid.AssetType != null && typeof(Object).IsAssignableFrom(metadata.AssetGuid.AssetType) ? metadata.AssetGuid.AssetType : typeof(Object); + var path = string.IsNullOrEmpty(value) ? string.Empty : AssetDatabase.GUIDToAssetPath(value); + var asset = string.IsNullOrEmpty(path) ? null : AssetDatabase.LoadAssetAtPath(path, assetType); var next = EditorGUILayout.ObjectField(label, asset, assetType, false); - return next == asset ? value : next == null ? string.Empty : AssetDatabase.GetAssetPath(next); + return next == asset ? value : next == null ? string.Empty : AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(next)); } var textArea = metadata.TextArea; diff --git a/src/GameCult.Unity/Assets/Caching/README.md b/src/GameCult.Unity/Assets/Caching/README.md index d96f674f..49348452 100644 --- a/src/GameCult.Unity/Assets/Caching/README.md +++ b/src/GameCult.Unity/Assets/Caching/README.md @@ -150,8 +150,8 @@ public sealed class ItemData public string Notes = string.Empty; [Key(3)] - [CultInspectorAssetPath] - public string IconPath = string.Empty; + [CultInspectorAssetGuid] + public string IconGuid = string.Empty; } ``` @@ -161,8 +161,9 @@ public sealed class ItemData - `CultInspectorOrder` - `CultInspectorTextArea` - `CultInspectorRange` -- `CultInspectorAssetPath` (optional engine asset `Type`, named by the - consumer; the Studio uses it when it is a `UnityEngine.Object` type) +- `CultInspectorAssetGuid` (a string member holding an engine asset's stable + identity, in Unity the `.meta` GUID; optional engine asset `Type`, named by + the consumer; the Studio uses it when it is a `UnityEngine.Object` type) - `CultInspectorDrawer` (on drawer classes) CultMesh collaboration should feed the same CultCache mutation surface rather diff --git a/tests/GameCult.Caching.Tests/CultInspectorModelTests.cs b/tests/GameCult.Caching.Tests/CultInspectorModelTests.cs index 7ce19824..5c2c171b 100644 --- a/tests/GameCult.Caching.Tests/CultInspectorModelTests.cs +++ b/tests/GameCult.Caching.Tests/CultInspectorModelTests.cs @@ -42,6 +42,15 @@ namespace GameCult.Caching.Tests Assert.That(hidden.ValueType, Is.EqualTo(typeof(string))); } + [Test] + public void AssetGuidMemberExposesItsAttributeAndDeclaredType() + { + var member = Model().MembersOf(typeof(InspectAsset)).Single(); + + Assert.That(member.Metadata.AssetGuid, Is.Not.Null); + Assert.That(member.Metadata.AssetGuid!.AssetType, Is.EqualTo(typeof(Uri))); + } + [Test] public void ClaimsResolveAttributeThenTypeThenBuiltIn() { @@ -321,6 +330,14 @@ namespace GameCult.Caching.Tests public string Name = string.Empty; } + [CultDocument("tests.inspect_asset", "tests.inspect_asset.v1")] + [MessagePackObject] + public sealed class InspectAsset + { + [Key(0)] [CultInspectorAssetGuid(typeof(Uri))] + public string Guid = string.Empty; + } + [CultDocument("tests.inspect_fixed", "tests.inspect_fixed.v1")] [MessagePackObject] public sealed class InspectFixed