|
| 1 | +# Asset Property Graph |
| 2 | + |
| 3 | +## Role |
| 4 | + |
| 5 | +`Stride.Core.Assets.Quantum` wraps the core Quantum graph with asset-specific semantics: override tracking, base-asset linking, and archetype/prefab inheritance. When a derived prefab overrides a value from its base, the graph records that override and can reset it. This is the layer that makes "this property is bold because it's overridden" possible in GameStudio. |
| 6 | + |
| 7 | +## `IAssetNode` Extensions |
| 8 | + |
| 9 | +`IAssetNode` extends `IGraphNode` with asset-aware members: |
| 10 | + |
| 11 | +| Member | Type | Purpose | |
| 12 | +|---|---|---| |
| 13 | +| `PropertyGraph` | `AssetPropertyGraph` | The graph that owns this node | |
| 14 | +| `BaseNode` | `IGraphNode` | The corresponding node in the base asset, or `null` if none | |
| 15 | +| `OverrideChanging` | event | Raised before override state changes | |
| 16 | +| `OverrideChanged` | event | Raised after override state changes | |
| 17 | +| `ResetOverrideRecursively()` | method | Resets this node and all descendants to inherited values | |
| 18 | +| `SetContent(key, node)` | method | Attaches an auxiliary node (used internally) | |
| 19 | +| `GetContent(key)` | method | Retrieves an attached auxiliary node | |
| 20 | + |
| 21 | +`IAssetMemberNode` (extends `IAssetNode`, `IMemberNode`) and `IAssetObjectNode` (extends `IAssetNode`, `IObjectNode`) are the concrete asset-aware node types. `IAssetObjectNode` adds per-item override tracking for collections (`IsItemInherited`, `IsItemOverridden`, `OverrideItem`, etc.). |
| 22 | + |
| 23 | +## Override Model |
| 24 | + |
| 25 | +A property in a derived asset is in one of three states: |
| 26 | + |
| 27 | +| State | Meaning | GameStudio visual | |
| 28 | +|---|---|---| |
| 29 | +| **Inherited** | Value comes from the base asset; any change to the base propagates here | Normal weight, italic | |
| 30 | +| **Overridden** | Value was explicitly set on this derived asset, shadowing the base | Bold | |
| 31 | +| **No base** | Asset has no base (or this property has no base equivalent) | Normal weight | |
| 32 | + |
| 33 | +**`ResetOverride()`** is a method on `IAssetNodePresenter` (the presenter layer — see `property-grid.md`), not on `IAssetNode` directly. Calling it restores the overridden value to its inherited state, and the graph then re-propagates the base value. The underlying graph node's `ResetOverrideRecursively()` handles the recursive reset; the presenter method is the entry point from the UI. |
| 34 | + |
| 35 | +When a composite node (an object with children) is reset, all descendant nodes are also reset recursively. |
| 36 | + |
| 37 | +> [!NOTE] Just adding a new asset type |
| 38 | +> If your asset has no base/derived relationship and you are not implementing archetypes or prefab composition, the override model is invisible to you. `IsInherited` will always be `false` and `HasBase` will always be `false`. You do not need to understand this layer to add a new asset type. |
| 39 | +
|
| 40 | +## `AssetPropertyGraph` |
| 41 | + |
| 42 | +`AssetPropertyGraph` wraps a `NodeContainer` and adds override semantics on top: |
| 43 | + |
| 44 | +- Created via `AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, logger)` where `container` is an `AssetPropertyGraphContainer` (not a raw `NodeContainer`) — this is obtained from the editor session; do not instantiate it manually |
| 45 | +- Tied to the editor session — created when an asset is opened, disposed when closed |
| 46 | +- Links each node to its counterpart in the base asset graph (if the asset has an archetype) |
| 47 | +- Propagates base values to all inherited nodes on load |
| 48 | + |
| 49 | +You rarely interact with `AssetPropertyGraph` directly. The presenter layer reads from it via `IAssetNodePresenter.Asset` and `IAssetNodePresenter.HasBase`. |
| 50 | + |
| 51 | +## `AssetPropertyGraphDefinition` |
| 52 | + |
| 53 | +`AssetPropertyGraphDefinition` tells the graph which member values are **object references** (shared identities, loaded separately by `ContentManager`) vs **inline data** (copied into the asset). |
| 54 | + |
| 55 | +Provide one only when your asset type holds references to other content objects. If you don't, the default definition treats all values as inline — which is correct for most new asset types. |
| 56 | + |
| 57 | +```csharp |
| 58 | +// sources/engine/Stride.Assets/YourFeature/YourAssetPropertyGraphDefinition.cs |
| 59 | +using Stride.Core.Assets.Quantum; |
| 60 | +using Stride.Core.Quantum; |
| 61 | + |
| 62 | +namespace Stride.Assets.YourFeature; |
| 63 | + |
| 64 | +[AssetPropertyGraphDefinition(typeof(YourAsset))] |
| 65 | +public class YourAssetPropertyGraphDefinition : AssetPropertyGraphDefinition |
| 66 | +{ |
| 67 | + // Return true when the value stored in 'member' is an object reference |
| 68 | + // (i.e. a handle to a separately-loaded content object, not an inline copy). |
| 69 | + public override bool IsMemberTargetObjectReference(IMemberNode member, object? value) |
| 70 | + { |
| 71 | + // Example: treat any Prefab member as an object reference |
| 72 | + if (value is Prefab) |
| 73 | + return true; |
| 74 | + |
| 75 | + return base.IsMemberTargetObjectReference(member, value); |
| 76 | + } |
| 77 | + |
| 78 | + // Return true when a collection item is an object reference. |
| 79 | + public override bool IsTargetItemObjectReference(IObjectNode collection, NodeIndex itemIndex, object? value) |
| 80 | + { |
| 81 | + // Example: treat items in PrefabCollection as object references |
| 82 | + if (collection.Descriptor.ElementType == typeof(Prefab)) |
| 83 | + return true; |
| 84 | + |
| 85 | + return base.IsTargetItemObjectReference(collection, itemIndex, value); |
| 86 | + } |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +The `[AssetPropertyGraphDefinition(typeof(YourAsset))]` attribute is discovered automatically when the assembly is registered. No manual registration is needed beyond `AssetQuantumRegistry.RegisterAssembly()` in `Module.cs`. |
| 91 | + |
| 92 | +> [!NOTE] Just adding a new asset type |
| 93 | +> If all your asset's properties are plain data values (numbers, strings, lists of structs), you do not need an `AssetPropertyGraphDefinition`. Only provide one when your asset class has members that hold references to other content objects (Prefabs, Textures, Materials, etc.) that should remain as references rather than be embedded inline. |
| 94 | +
|
| 95 | +## `AssetQuantumRegistry` |
| 96 | + |
| 97 | +| Method | When to call | |
| 98 | +|---|---| |
| 99 | +| `AssetQuantumRegistry.RegisterAssembly(assembly)` | From `Module.cs` — call once per assembly containing asset graph types | |
| 100 | +| `AssetQuantumRegistry.ConstructPropertyGraph(AssetPropertyGraphContainer, AssetItem, ILogger?)` | Called internally by the editor session; do not call manually | |
| 101 | +| `AssetQuantumRegistry.GetDefinition(assetType)` | Called internally; do not call manually | |
| 102 | + |
| 103 | +`RegisterAssembly` scans the assembly for `AssetPropertyGraph` subclasses (decorated with `[AssetPropertyGraph(typeof(T))]`) and `AssetPropertyGraphDefinition` subclasses (decorated with `[AssetPropertyGraphDefinition(typeof(T))]`) and registers them. |
| 104 | + |
| 105 | +In `Module.cs` for an assembly that contains both asset classes and graph types: |
| 106 | +```csharp |
| 107 | +[ModuleInitializer] |
| 108 | +public static void Initialize() |
| 109 | +{ |
| 110 | + // AssemblyRegistry.Register is required for assemblies that contain Asset subclasses. |
| 111 | + // If the assembly only contains graph types (no Asset subclasses), omit this line. |
| 112 | + AssemblyRegistry.Register(typeof(Module).Assembly, AssemblyCommonCategories.Assets); |
| 113 | + AssetQuantumRegistry.RegisterAssembly(typeof(Module).Assembly); |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +## Assembly Placement |
| 118 | + |
| 119 | +`Stride.Core.Assets.Quantum` — `sources/assets/Stride.Core.Assets.Quantum/` |
| 120 | + |
| 121 | +Concrete `AssetPropertyGraphDefinition` subclasses for engine assets live alongside their asset classes (e.g. `sources/engine/Stride.Assets/`). |
0 commit comments