diff --git a/.claude/skills/sync-readmes/SKILL.md b/.claude/skills/sync-readmes/SKILL.md
index 9226f12b..0a5362c2 100644
--- a/.claude/skills/sync-readmes/SKILL.md
+++ b/.claude/skills/sync-readmes/SKILL.md
@@ -4,7 +4,7 @@ description: Verify and update Aspid.FastTools README files against the actual c
user-invocable: false
---
-The package ships **fourteen** README files (4 main + 10 sample) that drift from the code easily. Use after any change touching namespaces of public types, public API surface, `[CreateAssetMenu]` paths, source generator output, or sample structure.
+The package ships **sixteen** README files (4 main + 12 sample) that drift from the code easily. Use after any change touching namespaces of public types, public API surface, `[CreateAssetMenu]` paths, source generator output, or sample structure.
## Files in scope
@@ -17,16 +17,18 @@ The package ships **fourteen** README files (4 main + 10 sample) that drift from
| `Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/README.md` | EN | `../Images/` |
| `Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/README.md` | RU | `../Images/` |
-Per-feature references (`SerializedPropertyExtensions.md`, `VisualElementExtensions.md`) live alongside the Documentation copies inside `Documentation/EN/` and `Documentation/RU/`.
+Per-feature references (`Types.md`, `SerializeReferences.md`, `SerializeReferenceTooling.md`, `Ids.md`, `SerializedPropertyExtensions.md`, `VisualElementExtensions.md`) live alongside the Documentation copies inside `Documentation/EN/` and `Documentation/RU/`.
Expected structural differences between root and Documentation copies (not drift):
-- **Badge block** (Unity / Release / License shields) — only in the root copies.
-- **`## Source Code` / `## Исходный код` block** linking to the GitHub repo — only in the Documentation copies.
+- **Badge block** (Unity / Stable / Preview / License shields) — only in the root copies.
+- **Language switcher** under the banner — root links `README.md` / `README_RU.md`; Documentation copies link the sibling locale as `../RU/README.md` / `../EN/README.md`.
+- **Source-code link row** (`[Source Code](…) · [Unity Asset Store](…) · [Releases](…)`, RU: `Исходный код`) under the one-liner — only in the Documentation copies.
+- **LICENSE / CHANGELOG links** (in `## License` / `## Лицензия`) — root uses repo-relative paths; Documentation copies use absolute `github.com/.../blob/main/...` URLs.
- **Image paths** — see table above.
- **Feature-reference links** — root uses the full `Documentation/EN/...` path; Documentation copies link to the bare filename (same folder).
-**Sample READMEs** — one EN + one RU per sample folder under `Aspid.FastTools/Packages/tech.aspid.fasttools/Samples~/` (`Types/`, `Ids/`, `EnumValues/`, `ProfilerMarkers/`, `VisualElements/`).
+**Sample READMEs** — one EN + one RU per sample folder under `Aspid.FastTools/Packages/tech.aspid.fasttools/Samples~/` (`Types/`, `Ids/`, `EnumValues/`, `ProfilerMarkers/`, `VisualElements/`, `SerializeReferences/`).
## Workflow
@@ -45,7 +47,6 @@ For every fact the README states, prove it from the code:
Where READMEs historically lie (re-verify these even if the text looks plausible):
- **Namespaces** — they are split per feature (runtime and editor alike); never assume a type sits in the root `Aspid.FastTools`. Check the `using` lines in samples.
-- **The two ID registries** — `StringIdRegistry` and `IdRegistry` have different APIs, menu paths, and runtime behavior; don't conflate them — read both sources.
- **Sample asset menu paths** — samples use their own menu segment order; always re-grep `[CreateAssetMenu]` instead of trusting the existing README.
### 2. Apply edits to all matching files
diff --git a/Aspid.FastTools.Generators/CLAUDE.md b/Aspid.FastTools.Generators/CLAUDE.md
index 9c3f14c6..521528c0 100644
--- a/Aspid.FastTools.Generators/CLAUDE.md
+++ b/Aspid.FastTools.Generators/CLAUDE.md
@@ -16,16 +16,6 @@ Deploy pipeline: `Aspid.FastTools.Generators/ILRepack.targets` merges `Aspid.Gen
A repo-level PostToolUse hook (`.claude/hooks/rebuild-generators-on-change.sh`) also runs `dotnet build` automatically after any `Edit`/`Write` to `*.cs` under `Aspid.FastTools.Generators/Aspid.FastTools.Generators/`. The hook intentionally **does not** trigger for tests, the Sample project, or Unity-side edits — keep that scope if you modify it.
-## Solution Structure
-
-```
-Aspid.FastTools.Generators/ ← generator implementation (+ ILRepack.targets deploy merge)
-Aspid.FastTools.Generators.Tests/ ← unit tests + GeneratorTestHost helper
-Aspid.FastTools.Generators.Sample/ ← manual smoke-test project
-Aspid.FastTools.Generators.sln
-Directory.Build.targets ← copies the merged DLL into the Unity package
-```
-
## Target Framework
`netstandard2.0` — required by Roslyn. No Unity assemblies, no runtime packages.
@@ -95,32 +85,7 @@ Pipeline data passed between stages **must be value-equatable**. Roslyn caches r
- `Equals` and `GetHashCode` over every field
- For nested arrays use `ImmutableArray` with element-wise comparison (or `EquatableArray` if introduced)
-Reference shapes:
-
-```csharp
-internal readonly struct TypeData : IEquatable
-{
- public readonly string TypeKey; // "Foo.Outer.Inner"
- public readonly string TypeName;
- public readonly string? Namespace;
- public readonly string ContainingTypeChain; // "Outer.Middle." or ""
- public readonly string FullyQualifiedDisplay;
- public readonly string TypeParamList; // "" or ""
- public readonly string ConstraintsClause;
- public readonly int Arity;
- // ... ctor + IEquatable Equals/GetHashCode over all fields
-}
-
-internal readonly struct IdStructData : IEquatable
-{
- public readonly string StructName;
- public readonly string TypeParameters; // "" or ""
- public readonly int Arity;
- public readonly string? Namespace;
- public readonly ImmutableArray ContainingTypes;
- // ... IEquatable walks ContainingTypes element-wise
-}
-```
+Reference shapes: `TypeData` and `IdStructData` in the generator sources.
Cache stability is regression-tested in `IncrementalCacheTests` — that test runs each generator twice over compilations differing only in an unrelated source file, and asserts every output step is `Cached`/`Unchanged`. Adding a non-equatable field to any pipeline struct will fail it.
diff --git a/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/BrokenWeaponPreset.prefab b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/BrokenWeaponPreset.prefab
new file mode 100644
index 00000000..d7776ce7
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/BrokenWeaponPreset.prefab
@@ -0,0 +1,62 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &2681730314047826224
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 3946854324957946979}
+ - component: {fileID: 9052271508596160596}
+ m_Layer: 0
+ m_Name: BrokenWeaponPreset
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &3946854324957946979
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2681730314047826224}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &9052271508596160596
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2681730314047826224}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 812007f2bd1be48ee8eca8c040018591, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::Game.Gear.Loadout
+ _primary:
+ rid: 2339642446330462275
+ _sidearms:
+ - rid: 2339642446330462276
+ references:
+ version: 2
+ RefIds:
+ - rid: 2339642446330462275
+ type: {class: Pistoll, ns: Game.Gear, asm: Assembly-CSharp}
+ data:
+ _damage: 10
+ - rid: 2339642446330462276
+ type: {class: Shotgun, ns: Game.Gear, asm: Assembly-CSharp}
+ data:
+ _pellets: 8
+ _spread: 12
diff --git a/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/BrokenWeaponPreset.prefab.meta b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/BrokenWeaponPreset.prefab.meta
new file mode 100644
index 00000000..c508a437
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/BrokenWeaponPreset.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8434050925dc34c8598973fef832d8ff
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/SharedWeaponPreset.prefab b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/SharedWeaponPreset.prefab
new file mode 100644
index 00000000..20842095
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/SharedWeaponPreset.prefab
@@ -0,0 +1,57 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &2681730314047826224
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 3946854324957946979}
+ - component: {fileID: 9052271508596160596}
+ m_Layer: 0
+ m_Name: SharedWeaponPreset
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &3946854324957946979
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2681730314047826224}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &9052271508596160596
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2681730314047826224}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 812007f2bd1be48ee8eca8c040018591, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::Game.Gear.Loadout
+ _primary:
+ rid: 2339642446330462275
+ _sidearms:
+ - rid: 2339642446330462275
+ references:
+ version: 2
+ RefIds:
+ - rid: 2339642446330462275
+ type: {class: Pistol, ns: Game.Gear, asm: Assembly-CSharp}
+ data:
+ _damage: 10
diff --git a/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/SharedWeaponPreset.prefab.meta b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/SharedWeaponPreset.prefab.meta
new file mode 100644
index 00000000..3ba13a37
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/SharedWeaponPreset.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: e9083bbbc7914bff96a35d7c41dcc7ad
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/WeaponPreset.prefab b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/WeaponPreset.prefab
new file mode 100644
index 00000000..d18ce98f
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/WeaponPreset.prefab
@@ -0,0 +1,62 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &2681730314047826224
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 3946854324957946979}
+ - component: {fileID: 9052271508596160596}
+ m_Layer: 0
+ m_Name: WeaponPreset
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &3946854324957946979
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2681730314047826224}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &9052271508596160596
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2681730314047826224}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 812007f2bd1be48ee8eca8c040018591, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::Game.Gear.Loadout
+ _primary:
+ rid: 2339642446330462275
+ _sidearms:
+ - rid: 2339642446330462276
+ references:
+ version: 2
+ RefIds:
+ - rid: 2339642446330462275
+ type: {class: Pistol, ns: Game.Gear, asm: Assembly-CSharp}
+ data:
+ _damage: 10
+ - rid: 2339642446330462276
+ type: {class: Shotgun, ns: Game.Gear, asm: Assembly-CSharp}
+ data:
+ _pellets: 8
+ _spread: 12
diff --git a/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/WeaponPreset.prefab.meta b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/WeaponPreset.prefab.meta
new file mode 100644
index 00000000..6ebdb869
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Prefabs/WeaponPreset.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 9251356a76325475a88ef163e5505f48
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/DevTests/SerializeReferences/Scripts/DocsMedia.meta b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Scripts/DocsMedia.meta
new file mode 100644
index 00000000..a0dd1ec4
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Scripts/DocsMedia.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5b3fcaf5b1d294f16bb513224b9b1c1d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/DevTests/SerializeReferences/Scripts/DocsMedia/Loadout.cs b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Scripts/DocsMedia/Loadout.cs
new file mode 100644
index 00000000..6dbbc0ea
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Scripts/DocsMedia/Loadout.cs
@@ -0,0 +1,53 @@
+using System;
+using UnityEngine;
+using System.Collections.Generic;
+using Aspid.FastTools.Types;
+
+// Docs-media harness: mirrors the SerializeReference Selector example in
+// Documentation/EN|RU/SerializeReferences.md — IWeapon implementations picked into
+// [SerializeReference] fields. Lives in DevTests, but uses a neutral game-like
+// namespace because the picker breadcrumbs are visible in the recorded media.
+
+// ReSharper disable once CheckNamespace
+namespace Game.Gear
+{
+ public interface IWeapon
+ {
+ void Fire();
+ }
+
+ [Serializable]
+ public sealed class Pistol : IWeapon
+ {
+ [SerializeField] [Min(0)] private int _damage = 10;
+
+ public void Fire() => Debug.Log($"Pistol: {_damage} dmg");
+ }
+
+ [Serializable]
+ public sealed class Shotgun : IWeapon
+ {
+ [SerializeField] [Min(1)] private int _pellets = 8;
+ [SerializeField] [Range(0f, 45f)] private float _spread = 12f;
+
+ public void Fire() => Debug.Log($"Shotgun: {_pellets} pellets, {_spread}° spread");
+ }
+
+ [Serializable]
+ public sealed class PlasmaRifle : IWeapon
+ {
+ [SerializeField] [Min(0f)] private float _power = 40f;
+ [SerializeField] [Min(0f)] private float _range = 60f;
+
+ public void Fire() => Debug.Log($"Plasma rifle: {_power} power, {_range} m");
+ }
+
+ public sealed class Loadout : MonoBehaviour
+ {
+ [TypeSelector]
+ [SerializeReference] private IWeapon _primary;
+
+ [TypeSelector]
+ [SerializeReference] private List _sidearms;
+ }
+}
diff --git a/Aspid.FastTools/Assets/DevTests/SerializeReferences/Scripts/DocsMedia/Loadout.cs.meta b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Scripts/DocsMedia/Loadout.cs.meta
new file mode 100644
index 00000000..3a6fd835
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/SerializeReferences/Scripts/DocsMedia/Loadout.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 812007f2bd1be48ee8eca8c040018591
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia.meta b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia.meta
new file mode 100644
index 00000000..bbf6bf30
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 88918916bea2449949776a97abc6e7ae
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/CombatModifiers.cs b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/CombatModifiers.cs
new file mode 100644
index 00000000..85560a36
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/CombatModifiers.cs
@@ -0,0 +1,27 @@
+using Aspid.FastTools.Types;
+
+// Docs-media harness: [TypeSelectorDisplay] demo types shown in the Types.md picker screenshot.
+// DamageModifier mirrors the attribute example in the docs; KnockbackModifier stays undecorated
+// (group only) to contrast a custom name/icon row with a default one.
+
+// ReSharper disable once CheckNamespace
+namespace Game.Combat
+{
+ public abstract class CombatModifier { }
+
+ [TypeSelectorDisplay(
+ Name = "Damage ×",
+ Group = "Combat/Modifiers",
+ Tooltip = "Scales incoming damage",
+ Icon = "d_ScriptableObject Icon")]
+ public sealed class DamageModifier : CombatModifier { }
+
+ [TypeSelectorDisplay(
+ Name = "Crit %",
+ Group = "Combat/Modifiers",
+ Tooltip = "Adds critical-hit chance")]
+ public sealed class CritChanceModifier : CombatModifier { }
+
+ [TypeSelectorDisplay(Group = "Combat/Modifiers")]
+ public sealed class KnockbackModifier : CombatModifier { }
+}
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/CombatModifiers.cs.meta b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/CombatModifiers.cs.meta
new file mode 100644
index 00000000..58c06b8e
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/CombatModifiers.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: a464f172c19e6498aa8a59aa74a7106b
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/GenericEffects.cs b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/GenericEffects.cs
new file mode 100644
index 00000000..1e99cb9e
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/GenericEffects.cs
@@ -0,0 +1,38 @@
+using System;
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+// Docs-media harness: open-generic picking demo for the TypeSelectorWindow GIF in Types.md —
+// picking Amplify walks through its argument page before returning the constructed type.
+
+// ReSharper disable once CheckNamespace
+namespace Game.Combat
+{
+ [Serializable]
+ public abstract class EffectModifier { }
+
+ [Serializable]
+ public sealed class Haste : EffectModifier { }
+
+ [Serializable]
+ public sealed class Amplify : EffectModifier
+ where T : StatusEffect { }
+
+ [Serializable]
+ public abstract class StatusEffect { }
+
+ [Serializable]
+ public sealed class Burning : StatusEffect { }
+
+ [Serializable]
+ public sealed class Frozen : StatusEffect { }
+
+ [Serializable]
+ public sealed class Slowed : StatusEffect { }
+
+ public sealed class EffectRack : MonoBehaviour
+ {
+ [TypeSelector]
+ [SerializeReference] private EffectModifier _modifier;
+ }
+}
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/GenericEffects.cs.meta b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/GenericEffects.cs.meta
new file mode 100644
index 00000000..80d08b03
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/GenericEffects.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 8c993a6fc65cd46ce915047f98ab3211
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Loadout.cs b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Loadout.cs
new file mode 100644
index 00000000..a11e0774
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Loadout.cs
@@ -0,0 +1,19 @@
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+// Docs-media harness: mirrors the member-reference example in Documentation/EN|RU/Types.md —
+// _category drives _weaponType's picker live in the Inspector.
+
+// ReSharper disable once CheckNamespace
+namespace Game.Combat
+{
+ public sealed class Loadout : MonoBehaviour
+ {
+ // The category chosen here drives the picker of _weaponType below.
+ [SerializeField] private SerializableType _category;
+
+ // Constrained live to whatever _category currently holds.
+ [TypeSelector(nameof(_category))]
+ [SerializeField] private string _weaponType;
+ }
+}
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Loadout.cs.meta b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Loadout.cs.meta
new file mode 100644
index 00000000..76ebd1fb
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Loadout.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 00bf531e3d43e4d7f84ee0555274ceb7
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/WeaponMount.cs b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/WeaponMount.cs
new file mode 100644
index 00000000..d3ecc66d
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/WeaponMount.cs
@@ -0,0 +1,18 @@
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+// Docs-media harness: Required = true demo for the Types.md screenshot — _primaryWeapon is
+// filled in the shot, _secondaryWeapon stays empty to show the inline "required" notice.
+
+// ReSharper disable once CheckNamespace
+namespace Game.Combat
+{
+ public sealed class WeaponMount : MonoBehaviour
+ {
+ [TypeSelector(typeof(Weapon))]
+ [SerializeField] private string _primaryWeapon;
+
+ [TypeSelector(typeof(Weapon), Required = true)]
+ [SerializeField] private string _secondaryWeapon;
+ }
+}
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/WeaponMount.cs.meta b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/WeaponMount.cs.meta
new file mode 100644
index 00000000..e5576baa
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/WeaponMount.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 74e49226c1e7b4c38b839fd9119db53f
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Weapons.cs b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Weapons.cs
new file mode 100644
index 00000000..164a21e6
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Weapons.cs
@@ -0,0 +1,25 @@
+// Docs-media harness: user-facing weapon hierarchy shown in the Types.md pickers.
+// Lives in DevTests, but uses a neutral game-like namespace because the picker
+// breadcrumbs (and thus the namespace) are visible in the recorded media.
+
+// ReSharper disable once CheckNamespace
+namespace Game.Combat
+{
+ public abstract class Weapon { }
+
+ public abstract class MeleeWeapon : Weapon { }
+
+ public abstract class RangedWeapon : Weapon { }
+
+ public sealed class Sword : MeleeWeapon { }
+
+ public sealed class Axe : MeleeWeapon { }
+
+ public sealed class Spear : MeleeWeapon { }
+
+ public sealed class Bow : RangedWeapon { }
+
+ public sealed class Crossbow : RangedWeapon { }
+
+ public sealed class Pistol : RangedWeapon { }
+}
diff --git a/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Weapons.cs.meta b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Weapons.cs.meta
new file mode 100644
index 00000000..cf6bb41f
--- /dev/null
+++ b/Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/Weapons.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 7006c2de36de04b5eae635f2428c3314
\ No newline at end of file
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Ids.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Ids.md
new file mode 100644
index 00000000..5f4e30fb
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Ids.md
@@ -0,0 +1,25 @@
+# ID System
+
+Reference for the ID System internals: generator diagnostics and the `IdRegistry` runtime API. For the setup walkthrough with examples, see the [README](README.md#id-system-beta).
+
+> **Beta:** the ID System is currently in beta. The public API, generated code layout and editor workflow may change in future releases.
+
+## Generator diagnostics
+
+The generator reports `AFID001` if the struct is missing `partial`, and `AFID002` if your code already declares `_id`, `Id`, or `__stringId` (the generator skips emission so you get a clear error pointing at the struct rather than a CS compile error inside generated source). Generic targets (`EnemyId`) and generic containing types are supported.
+
+## IdRegistry
+
+`ScriptableObject` in `Aspid.FastTools.Ids` that stores `(int, string)` entries and keeps the lookup tables available at runtime. Each name is assigned a stable, auto-incrementing ID that never changes when other entries are added or removed.
+
+| Member | Description |
+|--------|-------------|
+| `bool TryGetId(string name, out int id)` | Returns `true` and the ID when found; otherwise `false` |
+| `bool TryGetName(int id, out string name)` | Returns `true` and the name when found; otherwise `false` and `string.Empty` |
+| `bool Contains(int id)` | Whether an ID is registered |
+| `bool Contains(string name)` | Whether a name is registered |
+| `int Count` | Number of entries |
+| `IReadOnlyList Ids` · `IReadOnlyList IdNames` | Registered IDs / names, in registration order |
+| `IEnumerator> GetEnumerator()` | Iterate `(id, name)` pairs |
+
+The registry derives from `ScriptableObject` directly and exposes a generic counterpart `IdRegistry` (with `T : struct, IId`) that adds typed `Contains(T)` and `TryGetName(T, out string)` overloads. Edits — adding, renaming, removing entries — happen through the registry inspector and `RegistryEditorCore`, not via a public runtime API.
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Ids.md.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Ids.md.meta
new file mode 100644
index 00000000..824d0b12
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Ids.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: fc30ab830a1d422eb8f52ec3a0a65e52
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/README.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/README.md
index d9f67814..dbc45199 100644
--- a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/README.md
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/README.md
@@ -1,33 +1,33 @@
-**Aspid.FastTools** is a set of tools designed to minimize routine code writing in Unity: `SerializeReference` tooling (an inspector type picker plus a reference explorer window), Roslyn-powered source generators, and a collection of runtime and editor utilities — from a serializable `System.Type` to fluent UI Toolkit extensions.
+[English](README.md) | [Русский](../RU/README.md)
-### \[[Unity Asset Store](https://assetstore.unity.com/packages/slug/365584)\] \[[Donate](#donate)\]
+**Aspid.FastTools** is a Unity toolset that eliminates routine boilerplate. Inside: a convenient `SerializeReference` workflow (an inspector type picker and a project-wide reference audit window), Roslyn source generators and analyzers, and runtime and editor utilities — from a serializable `System.Type` to fluent UI Toolkit extensions.
-## Source Code
-
-[[Aspid.FastTools](https://github.com/VPDPersonal/Aspid.FastTools)]
+[Source Code](https://github.com/VPDPersonal/Aspid.FastTools) · [Unity Asset Store](https://assetstore.unity.com/packages/slug/365584) · [Releases](https://github.com/VPDPersonal/Aspid.FastTools/releases)
## Table of Contents
- **Getting Started**
- - [Integration](#integration)
- - [Claude Code Plugin](#claude-code-plugin)
- - [Donate](#donate)
+ - [Installation](#installation) — UPM git URL, `.unitypackage`, Asset Store
- **Features**
- - [ProfilerMarker](#profilermarker)
- - [Serializable Type System](#serializable-type-system)
- - [SerializeReference Selector](#serializereference-selector)
- - [Enum System](#enum-system)
- - [ID System (Beta)](#id-system-beta)
- - [VisualElement Extensions](#visualelement-extensions)
- - [SerializedProperty Extensions](#serializedproperty-extensions)
- - [IMGUI Layout Scopes](#imgui-layout-scopes)
- - [Editor Helper Extensions](#editor-helper-extensions)
+ - [Serializable Type System](#serializable-type-system) — `System.Type` as a serialized field, with a searchable type-picker window
+ - [SerializeReference Selector](#serializereference-selector) — a type-picker dropdown for `[SerializeReference]` fields, plus tooling that finds and repairs broken references
+ - [ProfilerMarker](#profilermarker) — source-generated, per-call-site profiler markers
+ - [Enum System](#enum-system) — serializable enum → value maps, `[Flags]`-aware
+ - [ID System (Beta)](#id-system-beta) — asset-assignable names mapped to stable integer IDs
+ - [VisualElement Extensions](#visualelement-extensions) — fluent UI Toolkit tree building in code
+ - [SerializedProperty Extensions](#serializedproperty-extensions) — chainable typed setters and reflection helpers
+ - [IMGUI Layout Scopes](#imgui-layout-scopes) — disposable `Begin*`/`End*` wrappers with `Rect` access
+ - [Editor Helper Extensions](#editor-helper-extensions) — display names for scripts in custom editors
+- **Extras**
+ - [Claude Code Plugin](#claude-code-plugin) — skills that teach Claude Code this package
+ - [Donate](#donate)
+ - [License](#license)
---
-## Integration
+## Installation
Install Aspid.FastTools via UPM: in the Package Manager click **+ → Install package from git URL…** and paste one of the URLs below.
@@ -58,117 +58,16 @@ The `upm-preview` branch always points to the latest **preview** release (rc, be
https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview
```
-To install a specific preview version, target the immutable per-release tag (see [Releases](https://github.com/VPDPersonal/Aspid.FastTools/releases) for the list of available versions):
+Specific preview versions use the same per-release tag scheme:
```
-https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview/1.0.0-rc.2
+https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview/1.0.0-rc.5
```
---
-## Claude Code Plugin
-
-If you use [Claude Code](https://docs.claude.com/en/docs/claude-code), the companion [Aspid.Claude.Plugins](https://github.com/VPDPersonal/Aspid.Claude.Plugins) marketplace ships the `aspid-fasttools` plugin — a set of skills that teach Claude Code this package's conventions and APIs.
-
-> [!WARNING]
-> The plugin is still in beta — its skills and commands may change between releases.
-
-Add the marketplace and install the plugin:
-
-```sh
-/plugin marketplace add VPDPersonal/Aspid.Claude.Plugins
-```
-
-```sh
-/plugin install aspid-fasttools@aspid-claude-plugins
-```
-
-Included skills:
-
-- **`aspid-id-struct`** — scaffold a new `IId` struct and `[UniqueId]` fields for the [ID System](#id-system-beta).
-- **`aspid-profiler-marker`** — insert `this.Marker()` call sites with the right `using`/scope shape.
-- **`aspid-visual-element-fluent`** — build editor or runtime UI using the fluent `VisualElement` extensions.
-
----
-
-## Donate
-
-This project is developed on a voluntary basis. If you find it useful, you can support its development financially. This helps allocate more time to improving and maintaining **Aspid.FastTools**.
-
-You can donate via the following platforms:
-* \[[Unity Asset Store](https://assetstore.unity.com/packages/slug/365584)\]
-
----
-
-## ProfilerMarker
-
-Provides source-generated `ProfilerMarker` registration. The generator creates a static marker per call-site, identified by the calling method and line number.
-
-```csharp
-using UnityEngine;
-
-public class MyBehaviour : MonoBehaviour
-{
- private void Update()
- {
- DoSomething1();
- DoSomething2();
- }
-
- private void DoSomething1()
- {
- using var _ = this.Marker();
- // Some code
- }
-
- private void DoSomething2()
- {
- using (this.Marker())
- {
- // Some code
- using var _ = this.Marker().WithName("Calculate");
- // Some code
- }
- }
-}
-```
-
-
-Generated code
-
-
-```csharp
-using Unity.Profiling;
-using System.Runtime.CompilerServices;
-
-internal static class __MyBehaviourProfilerMarkerExtensions
-{
- private static readonly ProfilerMarker DoSomething1_Marker_Line_13 = new("MyBehaviour.DoSomething1 (13)");
- private static readonly ProfilerMarker DoSomething2_Marker_Line_19 = new("MyBehaviour.DoSomething2 (19)");
- private static readonly ProfilerMarker DoSomething2_Marker_Line_22 = new("MyBehaviour.Calculate (22)");
-
- public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
- {
-#if ENABLE_PROFILER
- if (line is 13) return DoSomething1_Marker_Line_13.Auto();
- if (line is 19) return DoSomething2_Marker_Line_19.Auto();
- if (line is 22) return DoSomething2_Marker_Line_22.Auto();
-#endif
- return default;
- }
-}
-```
-
-
-
-### Result
-
-
-
----
-
## Serializable Type System
Allows serializing a `System.Type` reference in the Unity Inspector. The selected type is stored as an assembly-qualified name and resolved lazily on first access.
@@ -177,7 +76,7 @@ Allows serializing a `System.Type` reference in the Unity Inspector. The selecte
Two variants are available:
-- **`SerializableType`** — stores any type (base type is `object`)
+- **`SerializableType`** — stores any type
- **`SerializableType`** — stores a type constrained to `T` or its subclasses
Both support implicit conversion to `System.Type`.
@@ -202,45 +101,24 @@ public sealed class AbilitySelector : MonoBehaviour
}
}
```
-
+
### TypeSelectorAttribute
-An editor-only `PropertyAttribute` that restricts the type selection popup to specific base types. Applied to a `string` field (storing an assembly-qualified type name), a `SerializableType` / `SerializableType` field, or a `[SerializeReference]` managed-reference field. On a `SerializableType` the attribute's base types are intersected with the generic argument `T`.
+Adds a type-picker button next to a field in the Inspector: it opens a hierarchical, searchable window listing only the types compatible with the given base types (with several bases, a candidate must satisfy all of them; with no arguments, any type qualifies). What picking a type does depends on the field shape:
-```csharp
-[Conditional("UNITY_EDITOR")]
-public sealed class TypeSelectorAttribute : PropertyAttribute
-{
- public TypeSelectorAttribute() // base type: object
- public TypeSelectorAttribute(Type type)
- public TypeSelectorAttribute(params Type[] types)
- public TypeSelectorAttribute(string assemblyQualifiedName)
- public TypeSelectorAttribute(params string[] assemblyQualifiedNames)
-
- public TypeAllow Allow { get; set; } // default: TypeAllow.All
- public bool Required { get; set; } // default: false
-}
+- `string` — the field receives the assembly-qualified name of the chosen type;
+- `SerializableType` / `SerializableType` — narrows the built-in selector; the attribute's base types are intersected with the generic argument `T`;
+- a `[SerializeReference]` managed reference — the chosen type is instantiated into the field right away (see [SerializeReference Selector](#serializereference-selector)).
-[Flags]
-public enum TypeAllow
-{
- None = 0,
- Abstract = 1,
- Interface = 2,
- All = Abstract | Interface
-}
-```
-
-| Property | Description |
-|----------|-------------|
-| `Allow` | Which special type categories (abstract classes, interfaces) the picker includes in addition to plain concrete classes. Default: `TypeAllow.All` (a type-name field lists abstract classes and interfaces too; set `TypeAllow.None` to restrict it to concrete types). Ignored on a `[SerializeReference]` managed reference |
-| `Required` | Flags an unset field: a `[SerializeReference]` managed reference left `null`, or a `string` field left empty, shows an inline "required" warning in the Inspector and counts as a violation for the build/CI gate. Also covers a `SerializableType` field (its stored type name left empty). Default: `false` |
+The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no runtime cost.
```csharp
using UnityEngine;
using Aspid.FastTools.Types;
+public interface IStackable { }
+
public abstract class AbilityModifier
{
public abstract void Apply();
@@ -248,58 +126,36 @@ public abstract class AbilityModifier
public sealed class AbilitySelector : MonoBehaviour
{
+ // string — stores the assembly-qualified name of the chosen type.
// Each element of the array is its own picker constrained to AbilityModifier.
[TypeSelector(typeof(AbilityModifier))]
[SerializeField] private string[] _modifierTypes;
-}
-```
-
-> The complete sample — `Ability` / `AbilitySelector` / `EnemyBase` and their subclasses — ships in the `Types` sample (Package Manager → Aspid.FastTools → Samples).
-
-#### Dynamic base types via member references
-
-The string constructors resolve **member-first**: when the string is a valid C# identifier that matches an instance field or property on the same object, that member's *current value* supplies the base type(s) — so one field can constrain another's picker, live in the Inspector. Any other string is treated as an assembly-qualified type name (`Type.GetType`), which is what you need for a type the call site cannot reference with `typeof` (across an editor or asmdef boundary).
-
-```csharp
-public sealed class Loadout : MonoBehaviour
-{
- // The category chosen here drives the picker of _weaponType below.
- [SerializeField] private SerializableType _category;
- // Constrained live to whatever _category currently holds.
- [TypeSelector(nameof(_category))]
- [SerializeField] private string _weaponType;
+ // SerializableType — narrows the picker the field already has.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private SerializableType _modifierType;
+
+ // SerializableType — T already narrows the picker on its own; the base
+ // types of the attribute intersect with it: only AbilityModifier
+ // implementations that are also IStackable qualify.
+ [TypeSelector(typeof(IStackable))]
+ [SerializeField] private SerializableType _stackableModifierType;
+
+ // For a [SerializeReference] field picking a type immediately creates
+ // an instance and assigns it to the field. With no arguments the attribute
+ // offers subtypes of the field's own type (here — AbilityModifier).
+ // Required = true flags an unset field: an inspector warning
+ // plus a violation for the build/CI gate.
+ [TypeSelector(Required = true)]
+ [SerializeReference] private AbilityModifier _modifier;
}
```
-A referenced member may be a `Type`, `Type[]`, `string`, `string[]`, or a `SerializableType` / `SerializableType` (and arrays of these). Prefer `nameof(...)` so a rename keeps the link. Misuse — an unknown member name, or a member of an unusable type — is a **compile error** (analyzer rules `AFT0006`–`AFT0008`); for cases the analyzer cannot see (precompiled assemblies, a member renamed after compilation) the drawer shows a quiet inline warning below the field instead.
-
-Decorate a candidate type with `[TypeSelectorDisplay]` to tune how it appears in the picker — an editor-only attribute (`[Conditional("UNITY_EDITOR")]`) in `Aspid.FastTools.Types` that carries no runtime cost:
-
-```csharp
-using Aspid.FastTools.Types;
-
-// Rename the type in the picker, place it under an explicit group, give it a tooltip and an icon:
-[TypeSelectorDisplay(
- Name = "Damage ×",
- Group = "Combat/Modifiers",
- Tooltip = "Scales incoming damage",
- Icon = "d_ScriptableObject Icon")]
-public sealed class DamageModifier { }
-```
-
-| Member | Description |
-|--------|-------------|
-| `Name` | Display name shown instead of the type's short name — in the picker rows and in the closed dropdown's caption. Search still matches the real type name too, and the hover tooltip keeps revealing the full `Namespace.Class, Assembly` identity. `null` or whitespace means no override. |
-| `Group` | Explicit picker path with `/` separating levels (e.g. `"Combat/Melee"`). **Replaces** the type's namespace placement — the type appears only under this path, and path segments are shared between types. `null` or whitespace keeps the namespace placement. |
-| `Tooltip` | Tooltip shown when hovering the type's row. `null` means no tooltip override. |
-| `Icon` | Editor icon shown left of the label — an `EditorGUIUtility.IconContent` name, a project-relative asset path with extension (loaded via `AssetDatabase`), or a `Resources` texture path without extension. `null` means no icon. |
-
----
+Beyond the base types, the attribute exposes `Allow` (whether abstract classes and interfaces are listed) and `Required` (an unset field — an inline warning and a violation for the build/CI gate). Covered separately in the reference: [dynamic base types via member references](Types.md#dynamic-base-types-via-member-references) and tuning a candidate type's look in the picker with [`[TypeSelectorDisplay]`](Types.md#typeselectordisplay).
-### Type Selector Window
+### TypeSelectorWindow
-The Inspector shows a button that opens a searchable popup window with:
+A searchable, namespace-hierarchical type-picker popup — the same picker opened by `[TypeSelector]` and `SerializableType`, also available as a public API. The window offers:
- Hierarchical namespace organization
- Text search with filtering
@@ -312,30 +168,13 @@ The Inspector shows a button that opens a searchable popup window with:
- Generic type support — picking an open generic walks through its type parameters and emits the constructed type
- Favorites/Recent tuning (on/off, Recent capacity) in the Settings tab of the SerializeReference window
-
+
-The same window is available as a public API — open it from any editor code (custom inspectors, `EditorWindow`, menu items) when you need a type picker outside the standard `SerializableType` / `[TypeSelector]` flow.
+Picking an open generic walks through its argument page and returns the constructed type:
-```csharp
-namespace Aspid.FastTools.Types.Editors
-{
- public sealed class TypeSelectorWindow : EditorWindow
- {
- public static void Show(
- Rect screenRect,
- TypeSelectorFilter filter = default,
- string currentAqn = "",
- Action onSelected = null);
- }
-}
-```
+
-| Parameter | Description |
-|-----------|-------------|
-| `screenRect` | Screen-space rectangle the dropdown is anchored to. |
-| `filter` | Bundles which types the selector offers: base types (`Types`, only types assignable to **all** entries are listed; defaults to `typeof(object)`), the included kinds (`Allow`), an optional per-type `Predicate`, verbatim `AdditionalTypes`, and the open-generic `ArgumentFilter`. |
-| `currentAqn` | Assembly-qualified name of the currently selected type, used to pre-navigate to its location. Pass `null` or empty to start at the root. |
-| `onSelected` | Callback invoked with the assembly-qualified name of the selected type, or `null` if the user chose ``. |
+The window is available as a public API (`TypeSelectorWindow.Show`) — open it from any editor code (custom inspectors, `EditorWindow`, menu items) when you need a type picker outside the standard `SerializableType` / `[TypeSelector]` flow. Signature and parameters: [Types.md](Types.md#typeselectorwindow).
### ComponentTypeSelector
@@ -343,8 +182,6 @@ A serializable struct that renders a type-switching dropdown in the Inspector. A
The dropdown is automatically constrained to subtypes of the class that declares the field. No additional configuration is required.
-Because the dropdown owns type-switching, the Inspector's built-in **Script** row is hidden while the selector is present — you change the type only through the dropdown (UIToolkit inspectors only; the legacy IMGUI inspector draws that row itself).
-
```csharp
using UnityEngine;
using Aspid.FastTools.Types;
@@ -364,24 +201,21 @@ public sealed class FastEnemy : EnemyBase
public override void Attack() =>
Debug.Log($"Fast enemy strikes! (speed: {_speed})");
}
+```
-public sealed class TankEnemy : EnemyBase
-{
- [SerializeField] [Min(0)] private float _armor = 50f;
+
- public override void Attack() =>
- Debug.Log($"Tank attacks! (armor: {_armor})");
-}
-```
+Notes on the type-switching dropdown's behavior:
+
+- Because the dropdown owns type-switching, the Inspector's built-in **Script** row is hidden while the selector is present — you change the type only through the dropdown (UIToolkit inspectors only; the legacy IMGUI inspector draws that row itself).
-
-
+> Full reference: [Types.md](Types.md)
---
## SerializeReference Selector
-A drop-in type-picker dropdown for `[SerializeReference]` fields. Add `[TypeSelector]` next to `[SerializeReference]` and the Inspector replaces the default managed-reference UI with the same searchable, hierarchical picker used by `SerializableType`. You choose which concrete implementation of the field's type is instantiated, right in the Inspector; `` clears the reference.
+A drop-in type-picker dropdown for `[SerializeReference]` fields. Add `[TypeSelector]` next to `[SerializeReference]` and the Inspector replaces the default managed-reference UI with a searchable, hierarchical [type-picker window](#typeselectorwindow). You choose which concrete implementation of the field's type is instantiated, right in the Inspector; `` clears the reference.
```csharp
using System;
@@ -402,14 +236,6 @@ public sealed class Pistol : IWeapon
public void Fire() => Debug.Log($"Pistol: {_damage} dmg");
}
-[Serializable]
-public sealed class Railgun : IWeapon
-{
- [SerializeField] [Min(0)] private float _chargeTime = 1.5f;
-
- public void Fire() => Debug.Log($"Railgun charged for {_chargeTime}s");
-}
-
public sealed class Loadout : MonoBehaviour
{
[SerializeReference] [TypeSelector]
@@ -420,7 +246,7 @@ public sealed class Loadout : MonoBehaviour
}
```
-The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no runtime cost. It works on single fields, arrays, and `List`, in both IMGUI and UIToolkit inspectors.
+The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no runtime cost. It works on single fields, arrays, and `List`, in both IMGUI and UIToolkit inspectors. The same attribute also drives `string` and `SerializableType` fields — see [TypeSelectorAttribute](#typeselectorattribute).
### Capabilities
@@ -436,51 +262,74 @@ The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no ru
### Repairing broken references
-| Case | Fix |
-|---|---|
-| **Missing type** (renamed or deleted) | A yellow notice instead of a silent clear. The underlined **Fix** opens the picker and re-points the type while keeping its data — at any depth, in saved assets and live in Prefab Mode. |
-| **Smart Fix** | Next to **Fix**, suggests the most likely replacement (`[MovedFrom]`, a different namespace/assembly, casing, a near-miss name) and applies it in one click — never automatically. |
-| **Shared reference** (two fields share one instance) | Flagged with a notice; **Make unique** splits it into an independent copy. Duplicating a list element (Ctrl+D, `+`) no longer aliases the reference. |
+A missing type (renamed or deleted) shows a yellow notice instead of a silent clear: **Fix** re-points the type while keeping its data, and **Smart Fix** suggests the most likely replacement (`[MovedFrom]`, a moved namespace, a near-miss name) — applied in one click, never automatically. A shared reference (two fields holding one instance) is flagged and split with **Make unique**. Bulk repair lives in the **Asset References** and **Project References** tabs (`Tools → Aspid 🐍 → FastTools`): the first maps an asset's whole managed-reference graph from its YAML, the second scans every `.prefab` / `.asset` / `.unity` in the project, fixes broken references group-by-group and bakes `[MovedFrom]` renames into the files.
-
+### Project settings & the build/CI gate
-Bulk repair lives in two dedicated tabs:
+**`Project Settings → Aspid FastTools → SerializeReference`** controls breakage detection, auto de-aliasing of duplicated list elements, excluded scan folders, and the build/CI gate (`Off` / `Warn` / `Fail`) — committed values live in `ProjectSettings/SerializeReferenceSharedSettings.asset`, so teammates and CI behave identically. The same options are mirrored in the window's **Settings** tab and at **`Preferences → Aspid FastTools`**; headless CI runs the same check via `SerializeReferenceCiGate.RunCheck`.
-| Tab | Purpose |
-|---|---|
-| **Asset References** (`Tools → Aspid 🐍 → FastTools → Asset References`) | Maps an asset's whole managed-reference graph from its YAML — a per-component tree with field paths, shared and orphaned references, `MISSING` / `SHARED` badges, and an inline type dropdown on every card. Surfaces the missing references the Inspector cannot show. |
-| **Project References** (`Tools → Aspid 🐍 → FastTools → Project References`) | `Scan Project` sweeps every `.prefab` / `.asset` / `.unity` under `Assets/`, groups broken references by stored type, and rewrites a whole group with a single `Fix all` (plus Smart Fix). A group whose stored type matches a declared `[MovedFrom]` rename reads as a pending migration instead of a breakage — one **Migrate all** click bakes the rename into the files, after which the attribute can be removed from code. |
+> Full reference: [SerializeReferences.md](SerializeReferences.md) · window tabs, settings & CI gate: [SerializeReferenceTooling.md](SerializeReferenceTooling.md)
-### Project settings & the build/CI gate
+---
-**`Project Settings → Aspid FastTools → SerializeReference`** exposes:
+## ProfilerMarker
-| Setting | Scope | What it does |
-|---|---|---|
-| **Breakage detection** | per-user | The proactive toast + console warning when references newly become missing after a recompile / import. |
-| **Auto de-alias duplicated list elements** | committed | A duplicated list element gets its own instance instead of sharing the original's reference id. |
-| **Build / CI gate** | committed | `Off` / `Warn` / `Fail`: at player-build time, log or abort on missing (and, for CI, unset-required) managed references. |
-| **Excluded scan folders** | committed | Paths skipped by every project scan. |
+Provides source-generated `ProfilerMarker` registration. The generator creates a static marker per call-site, identified by the calling method and line number.
-- Committed values live in `ProjectSettings/SerializeReferenceSharedSettings.asset` — commit it so teammates and CI behave identically; breakage detection stays per-machine (`EditorPrefs`).
-- Rid colours are not a setting — a shared reference is always colour-coded by id, so matching colours reveal shared instances at a glance.
+```csharp
+using UnityEngine;
-The same options are mirrored in the window's **Settings** tab (`Tools → Aspid 🐍 → FastTools → Settings`) and at **`Preferences → Aspid FastTools`**, alongside the picker's per-user preferences:
+public class MyBehaviour : MonoBehaviour
+{
+ private void DoSomething1()
+ {
+ using var _ = this.Marker();
+ // Some code
+ }
-- **Favorites** — section on/off toggle.
-- **Recent items** — capacity slider (0–20; 0 hides the section and pauses recording without wiping history).
-- **Saved lists** — clears the stored Favorites / Recent.
-- **Welcome** — auto-show toggle.
+ private void DoSomething2()
+ {
+ using (this.Marker())
+ {
+ // Some code
+ using var _ = this.Marker().WithName("Calculate");
+ // Some code
+ }
+ }
+}
+```
+
+
+Generated code
+
+
+```csharp
+using Unity.Profiling;
+using System.Runtime.CompilerServices;
+
+internal static class __MyBehaviourProfilerMarkerExtensions
+{
+ private static readonly ProfilerMarker DoSomething1_Marker_Line_7 = new("MyBehaviour.DoSomething1 (7)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_13 = new("MyBehaviour.DoSomething2 (13)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_16 = new("MyBehaviour.Calculate (16)");
-Every row carries a scope stripe (green — committed, blue — per-user); a pinned footer offers **Reset to defaults** per scope (saved Favorites / Recent lists survive a reset). All surfaces stay in live sync.
+ public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
+ {
+#if ENABLE_PROFILER
+ if (line is 7) return DoSomething1_Marker_Line_7.Auto();
+ if (line is 13) return DoSomething2_Marker_Line_13.Auto();
+ if (line is 16) return DoSomething2_Marker_Line_16.Auto();
+#endif
+ return default;
+ }
+}
+```
-For headless CI, `SerializeReferenceCiGate.RunCheck` (invoked via `-batchmode -executeMethod`) writes a report and honours the committed gate severity:
+
-- `Off` skips the check, `Warn` logs but exits 0, `Fail` exits non-zero when violations exist.
-- `-srGateRequired` also flags unset `[TypeSelector(Required = true)]` fields across prefabs, ScriptableObjects and scenes (top-level fields, pure-YAML pass).
-- `-srGateWarnOnly` / `-srGateFail` override the committed severity per run.
+### Result
-> The full sample — `Loadout` / `IWeapon` / `Modifier` and the missing-reference repair scenarios — ships in the `SerializeReferences` sample (Package Manager → Aspid.FastTools → Samples). A step-by-step walkthrough lives in that sample's `TUTORIAL.md`.
+
---
@@ -492,12 +341,7 @@ Provides serializable enum-to-value mappings configurable from the Inspector.
A serializable collection of `EnumValue` entries with a configurable default value. Implements `IEnumerable>`.
-| Member | Description |
-|--------|-------------|
-| `TValue GetValue(Enum enumValue)` | Returns the mapped value, or `_defaultValue` if not found |
-| `bool Equals(Enum, Enum)` | Equality check with proper `[Flags]` support |
-
-Supports `[Flags]` enums: `Equals` uses `HasFlag` and treats `0`-valued members correctly.
+`GetValue` returns the mapped value, falling back to the configured default when the key is missing. `[Flags]` enums are supported: matching uses `HasFlag` and treats `0`-valued members correctly.
```csharp
using System;
@@ -507,51 +351,29 @@ using Aspid.FastTools.Enums;
public enum DamageType { Physical, Fire, Ice, Poison }
[Flags]
-public enum StatusEffect
-{
- None = 0,
- Burning = 1,
- Frozen = 2,
- Slowed = 4,
- Stunned = 8,
-}
+public enum StatusEffect { None = 0, Burning = 1, Frozen = 2, Slowed = 4, Stunned = 8 }
public sealed class DamageDealer : MonoBehaviour
{
[SerializeField] private EnumValues _damageMultipliers;
- [SerializeField] private EnumValues _damageColors;
// Flag combinations (e.g. Burning | Slowed) match via HasFlag and first-hit wins,
// so list composite entries BEFORE their constituent flags.
[SerializeField] private EnumValues _speedMultipliersByStatus;
- [SerializeField] private DamageType _currentType;
- [SerializeField] private StatusEffect _activeEffects;
+ public float GetMultiplier(DamageType type) => _damageMultipliers.GetValue(type);
- private void DealDamage()
- {
- var multiplier = _damageMultipliers.GetValue(_currentType);
- var color = _damageColors.GetValue(_currentType);
- var speedMod = _speedMultipliersByStatus.GetValue(_activeEffects);
- // ...
- }
+ public float GetSpeedModifier(StatusEffect effects) => _speedMultipliersByStatus.GetValue(effects);
}
```
-
+
In the Inspector, select the enum type in the `EnumValues` header, then assign a value for each enum member. Right-click the property to open a context menu with **Populate Missing Enum Members** — it appends an entry for every enum member not yet in the list, seeded with the current Default Value.
-> The complete sample — `DamageDealer` / `DamageType` / `StatusEffect` — ships in the `EnumValues` sample (Package Manager → Aspid.FastTools → Samples).
-
### EnumValues\
The typed counterpart of `EnumValues` for the common case where the enum type is already known in code. The enum is fixed by the generic argument, so the Inspector's type picker is disabled and lookups are compile-time safe. Lookups are also boxing-free — keys are compared as cached numeric values — and `foreach` over either variant binds to a struct enumerator, so iteration does not allocate. Implements `IEnumerable>`.
-| Member | Description |
-|--------|-------------|
-| `TValue GetValue(TEnum enumValue)` | Returns the mapped value, or `_defaultValue` if not found |
-| `bool Equals(TEnum, TEnum)` | Equality check with proper `[Flags]` support |
-
```csharp
public sealed class HitEffect : MonoBehaviour
{
@@ -562,7 +384,7 @@ public sealed class HitEffect : MonoBehaviour
}
```
-Lookup semantics (including `[Flags]` handling) are identical to `EnumValues`, and the serialized layout is compatible — switching a field between the two variants keeps the existing data as long as the enum type matches.
+Lookup semantics (including `[Flags]` handling) are identical to `EnumValues`.
---
@@ -570,11 +392,7 @@ Lookup semantics (including `[Flags]` handling) are identical to `EnumValues **Beta:** the ID System is currently in beta. The public API, generated code layout and editor workflow may change in future releases.
-Maps an asset-assignable name to a stable integer ID. Use the resulting `int` in `switch` statements and `Dictionary` keys without paying for string lookups at runtime.
-
-A single `IdRegistry` ScriptableObject maps string names to stable integer IDs and provides full `int ↔ string` lookups at runtime.
-
-> The complete sample — `EnemyId` / `EnemyDefinition` / `EnemySpawner` with a pre-wired registry — ships in the `Ids` sample (Package Manager → Aspid.FastTools → Samples). A step-by-step walkthrough lives in that sample's `TUTORIAL.md`.
+Maps asset-assignable names to stable integer IDs stored in an `IdRegistry` ScriptableObject, with full `int ↔ string` lookups at runtime. Use the resulting `int` in `switch` statements and `Dictionary` keys without paying for string comparisons.
### Setup
@@ -586,7 +404,9 @@ using Aspid.FastTools.Ids;
public partial struct EnemyId : IId { }
```
-Generated code:
+
+Generated code
+
```csharp
public partial struct EnemyId
@@ -598,7 +418,9 @@ public partial struct EnemyId
}
```
-The generator reports `AFID001` if the struct is missing `partial`, and `AFID002` if your code already declares `_id`, `Id`, or `__stringId` (the generator skips emission so you get a clear error pointing at the struct rather than a CS compile error inside generated source). Generic targets (`EnemyId`) and generic containing types are supported.
+
+
+Misuse is caught at compile time by generator diagnostics (`AFID001` — missing `partial`, `AFID002` — a colliding member); generic structs and containing types are supported.
**2.** Create the registry asset and bind it to the struct type in its Inspector:
- `Assets → Create → Aspid → Id Registry`
@@ -614,11 +436,6 @@ public class EnemyDefinition : ScriptableObject
{
[UniqueId] [SerializeField] private EnemyId _id;
}
-```
-
-```csharp
-using UnityEngine;
-using Aspid.FastTools.Ids;
public class EnemySpawner : MonoBehaviour
{
@@ -631,7 +448,7 @@ public class EnemySpawner : MonoBehaviour
}
```
-
+
### UniqueIdAttribute
@@ -642,25 +459,17 @@ Marks a field as requiring a unique value across all assets of the declaring typ
public sealed class UniqueIdAttribute : PropertyAttribute { }
```
-
+
### IdRegistry
`ScriptableObject` in `Aspid.FastTools.Ids` that stores `(int, string)` entries and keeps the lookup tables available at runtime. Each name is assigned a stable, auto-incrementing ID that never changes when other entries are added or removed.
-| Member | Description |
-|--------|-------------|
-| `bool TryGetId(string name, out int id)` | Returns `true` and the ID when found; otherwise `false` |
-| `bool TryGetName(int id, out string name)` | Returns `true` and the name when found; otherwise `false` and `string.Empty` |
-| `bool Contains(int id)` | Whether an ID is registered |
-| `bool Contains(string name)` | Whether a name is registered |
-| `int Count` | Number of entries |
-| `IReadOnlyList Ids` · `IReadOnlyList IdNames` | Registered IDs / names, in registration order |
-| `IEnumerator> GetEnumerator()` | Iterate `(id, name)` pairs |
+Runtime lookups cover both directions — `TryGetId` / `TryGetName`, `Contains`, enumeration of `(id, name)` pairs — and the generic counterpart `IdRegistry` adds typed overloads. Entries are added, renamed and removed only through the registry inspector, not a public runtime API.
-The registry derives from `ScriptableObject` directly and exposes a generic counterpart `IdRegistry` (with `T : struct, IId`) that adds typed `Contains(T)` and `TryGetName(T, out string)` overloads. Edits — adding, renaming, removing entries — happen through the registry inspector and `RegistryEditorCore`, not via a public runtime API.
+
-
+> Full reference: [Ids.md](Ids.md)
---
@@ -668,11 +477,9 @@ The registry derives from `ScriptableObject` directly and exposes a generic coun
Fluent extension methods for building UIToolkit trees in code. All methods return `T` (the element itself) for chaining.
-> Full method-by-method reference: [VisualElementExtensions.md](VisualElementExtensions.md)
-
### Example
-A reactive editor for an `AbilityConfig` `ScriptableObject` — title and status pill in the header, `PropertyField` body, and a Warning `HelpBox` that toggles based on `ManaCost`.
+A reactive editor for an `AbilityConfig` `ScriptableObject` — title and status pill in the header, and a Warning `HelpBox` that toggles based on `ManaCost`.
```csharp
[CustomEditor(typeof(AbilityConfig))]
@@ -684,34 +491,20 @@ internal sealed class AbilityConfigEditor : Editor
var badge = new Label()
.SetFontSize(10).SetUnityFontStyleAndWeight(FontStyle.Bold)
- .SetPaddingX(10).SetPaddingY(3)
- .SetBorderRadius(10).SetBorderWidth(1);
+ .SetPaddingX(10).SetPaddingY(3).SetBorderRadius(10).SetBorderWidth(1);
- var helpBox = new HelpBox(
- "This ability costs no mana — is that intentional?",
- HelpBoxMessageType.Warning)
+ var helpBox = new HelpBox("This ability costs no mana — is that intentional?", HelpBoxMessageType.Warning)
.SetMarginTop(8).SetBorderRadius(6);
- var manaField = new PropertyField(serializedObject.FindProperty("_manaCost"))
- .AddValueChanged(_ => Refresh());
-
Refresh();
return new VisualElement()
- .SetBorderRadius(10).SetBorderWidth(1)
+ .SetBorderRadius(10).SetBorderWidth(1).SetPaddingX(14).SetPaddingY(12)
.AddChild(new VisualElement()
.SetFlexDirection(FlexDirection.Row).SetAlignItems(Align.Center)
- .SetPaddingX(14).SetPaddingY(12)
- .AddChild(new Label(target.GetScriptName())
- .SetFlexGrow(1).SetFontSize(15)
- .SetUnityFontStyleAndWeight(FontStyle.Bold))
+ .AddChild(new Label(target.GetScriptName()).SetFlexGrow(1).SetFontSize(15))
.AddChild(badge))
- .AddChild(new VisualElement()
- .SetPaddingX(14).SetPaddingY(12)
- .AddChild(new PropertyField(serializedObject.FindProperty("_abilityName")))
- .AddChild(new PropertyField(serializedObject.FindProperty("_description")))
- .AddChild(new PropertyField(serializedObject.FindProperty("_cooldown")))
- .AddChild(manaField)
- .AddChild(helpBox));
+ .AddChild(new PropertyField(serializedObject.FindProperty("_manaCost")).AddValueChanged(_ => Refresh()))
+ .AddChild(helpBox);
void Refresh()
{
@@ -723,11 +516,11 @@ internal sealed class AbilityConfigEditor : Editor
}
```
-> The complete sample — `AbilityConfig.cs`, the polished `AbilityConfigEditor.cs` (custom colors, subtitle and divider, used in the screenshot below) and two `.asset` examples — ships in the `VisualElements` sample (Package Manager → Aspid.FastTools → Samples).
-
### Result
-
+
+
+> Full reference: [VisualElementExtensions.md](VisualElementExtensions.md)
---
@@ -746,13 +539,13 @@ property
The package covers:
- **Update / Apply** — `Update`, `UpdateIfRequiredOrScript`, `ApplyModifiedProperties`.
-- **Typed setters** — `SetValue` (generic dispatch) and `SetXxx` for `int`/`uint`/`long`/`ulong`/`float`/`double`/`bool`/`string`/`Color`/`Gradient`/`Hash128`/`Rect`/`RectInt`/`Bounds`/`BoundsInt`/`Vector2..4` (and `Vector2/3Int`)/`Quaternion`/`AnimationCurve`/`EntityId` (Unity 6.2+). Each comes with a paired `SetXxxAndApply` variant.
+- **Typed setters** — `SetValue` (generic dispatch) and `SetXxx` for every common Unity-serializable type, from primitives to `Gradient` and `AnimationCurve` — each with a paired `SetXxxAndApply` variant.
- **Enum setters** — `SetEnumFlag` and `SetEnumIndex` (each + `AndApply`).
- **Arrays** — `SetArraySize`, `AddArraySize`, `RemoveArraySize` (each + `AndApply`).
- **References** — `SetManagedReference`, `SetObjectReference`, `SetExposedReference`, and `SetBoxed` (Unity 6+).
- **Reflection helpers** — `GetPropertyType`, `GetFieldInfo`, `GetDeclaringInstance` for resolving the C# member and runtime instance behind a property.
-> Full method-by-method reference: [SerializedPropertyExtensions.md](SerializedPropertyExtensions.md)
+> Full reference: [SerializedPropertyExtensions.md](SerializedPropertyExtensions.md)
---
@@ -767,12 +560,6 @@ using (VerticalScope.Begin())
EditorGUILayout.LabelField("Item 2");
}
-using (HorizontalScope.Begin())
-{
- EditorGUILayout.LabelField("Left");
- EditorGUILayout.LabelField("Right");
-}
-
var scrollPos = Vector2.zero;
using (ScrollViewScope.Begin(ref scrollPos))
{
@@ -780,37 +567,18 @@ using (ScrollViewScope.Begin(ref scrollPos))
}
```
-Capture the group rect with the `out`-overload when needed:
-
-```csharp
-using (VerticalScope.Begin(out var rect, GUI.skin.box))
-{
- EditorGUI.DrawRect(rect, new Color(0, 0, 0, 0.1f));
- EditorGUILayout.LabelField("Boxed content");
-}
-```
-
-All `Begin` overloads match the corresponding `EditorGUILayout.Begin*` signatures (optional `GUIStyle`, `GUILayoutOption[]`, scroll view options, etc.).
+All `Begin` overloads match the corresponding `EditorGUILayout.Begin*` signatures (optional `GUIStyle`, `GUILayoutOption[]`, scroll view options), plus an `out Rect` variant for drawing into the group's rect.
---
## Editor Helper Extensions
-Utility methods for getting display names of Unity objects in custom editors.
-
-```csharp
-public static string GetScriptName(this Object obj)
-```
-
-Returns the display name of a Unity object:
-- If the type has `[AddComponentMenu]`, returns `ObjectNames.GetInspectorTitle(obj)`
-- Otherwise returns `ObjectNames.NicifyVariableName(typeName)`
-
-```csharp
-public static string GetScriptNameWithIndex(this Component targetComponent)
-```
+Display-name helpers for Unity objects in custom editors:
-Returns the display name with a count suffix when multiple components of the same type exist on the same GameObject. For example, if two `AudioSource` components are attached, the second returns `"Audio Source (2)"`.
+| Method | Returns |
+|---|---|
+| `GetScriptName()` | The object's display name — `ObjectNames.GetInspectorTitle` when the type has `[AddComponentMenu]`, otherwise the nicified type name |
+| `GetScriptNameWithIndex()` | The same name plus a count suffix when the GameObject holds several components of the same type — e.g. `"Audio Source (2)"` |
```csharp
[CustomEditor(typeof(MyBehaviour))]
@@ -828,3 +596,40 @@ public class MyBehaviourEditor : Editor
}
}
```
+
+---
+
+## Claude Code Plugin
+
+If you use [Claude Code](https://docs.claude.com/en/docs/claude-code), the companion [Aspid.Claude.Plugins](https://github.com/VPDPersonal/Aspid.Claude.Plugins) marketplace ships the `aspid-fasttools` plugin — a set of skills that teach Claude Code this package's conventions and APIs.
+
+> [!WARNING]
+> The plugin is still in beta — its skills and commands may change between releases.
+
+Add the marketplace and install the plugin:
+
+```sh
+/plugin marketplace add VPDPersonal/Aspid.Claude.Plugins
+```
+
+```sh
+/plugin install aspid-fasttools@aspid-claude-plugins
+```
+
+Included skills:
+
+- **`aspid-id-struct`** — scaffold a new `IId` struct and `[UniqueId]` fields for the [ID System](#id-system-beta).
+- **`aspid-profiler-marker`** — insert `this.Marker()` call sites with the right `using`/scope shape.
+- **`aspid-visual-element-fluent`** — build editor or runtime UI using the fluent `VisualElement` extensions.
+
+---
+
+## Donate
+
+This project is developed on a voluntary basis. If you find it useful, you can support its development by purchasing the package on the [Unity Asset Store](https://assetstore.unity.com/packages/slug/365584) — that helps allocate more time to improving and maintaining **Aspid.FastTools**.
+
+---
+
+## License
+
+**Aspid.FastTools** is distributed under the [MIT License](https://github.com/VPDPersonal/Aspid.FastTools/blob/main/LICENSE). Release history lives in the [CHANGELOG](https://github.com/VPDPersonal/Aspid.FastTools/blob/main/CHANGELOG.md).
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferenceTooling.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferenceTooling.md
new file mode 100644
index 00000000..ce13f54c
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferenceTooling.md
@@ -0,0 +1,81 @@
+# SerializeReference Tooling
+
+The [Inspector-side selector](SerializeReferences.md) repairs references one field at a
+time; this document covers the project-wide side: the FastTools window tabs that audit
+and mass-repair managed references, the Project Settings page with the player-build gate,
+and the same check running headless in CI. The gate also covers unset
+`[TypeSelector(Required = true)]` fields — see the `Required` property on
+[TypeSelectorAttribute](Types.md#typeselectorattribute).
+
+**Reference sections:**
+
+* [`Bulk repair tabs`](#bulk-repair-tabs) — the **Asset References** and
+ **Project References** tabs for auditing and mass repair across the project;
+* [`Project settings & the build/CI gate`](#project-settings--the-buildci-gate) —
+ the Project Settings page, setting scopes and the player-build gate;
+* [`Headless CI`](#headless-ci) — `SerializeReferenceCiGate.RunCheck` for batchmode pipelines.
+
+**A shorter version with the same examples lives in the** [README](README.md#serializereference-selector).
+
+## Bulk repair tabs
+
+There is no need to [fix references one by one](SerializeReferences.md#repairing-broken-references):
+auditing and mass repair live in two dedicated tabs of the FastTools window.
+
+| Tab | Purpose |
+|---|---|
+| **Asset References** (`Tools → Aspid 🐍 → FastTools → Asset References`) | Maps an asset's whole managed-reference graph from its YAML — a per-component tree with field paths, shared and orphaned references, `MISSING` / `SHARED` badges, and an inline type dropdown on every card. Surfaces the missing references the Inspector cannot show. |
+| **Project References** (`Tools → Aspid 🐍 → FastTools → Project References`) | `Scan Project` sweeps every `.prefab` / `.asset` / `.unity` under `Assets/`, groups broken references by stored type, and rewrites a whole group with a single `Fix all` (plus Smart Fix). A group whose stored type matches a declared `[MovedFrom]` rename reads as a pending migration instead of a breakage — one **Migrate all** click bakes the rename into the files, after which the attribute can be removed from code. |
+
+The **Asset References** tab lays out one asset's managed-reference graph as cards with
+`MISSING` / `SHARED` badges and inline repair:
+
+
+
+The **Project References** tab groups the whole project's findings by stored type — one
+group is repaired at once with a single `Fix all`:
+
+
+
+## Project settings & the build/CI gate
+
+**`Project Settings → Aspid FastTools → SerializeReference`** exposes:
+
+| Setting | Scope | What it does |
+|---|---|---|
+| **Breakage detection** | per-user | The proactive toast + console warning when references newly become missing after a recompile / import. |
+| **Auto de-alias duplicated list elements** | committed | A duplicated list element gets its own instance instead of sharing the original's reference id. |
+| **Build / CI gate** | committed | `Off` / `Warn` / `Fail`: at player-build time, log or abort on missing (and, for CI, unset-required) managed references. |
+| **Excluded scan folders** | committed | Paths skipped by every project scan. |
+
+- Committed values live in `ProjectSettings/SerializeReferenceSharedSettings.asset` — commit it so teammates and CI behave identically; breakage detection stays per-machine (`EditorPrefs`).
+- Rid colours are not a setting — a shared reference is always colour-coded by id, so matching colours reveal shared instances at a glance.
+
+The same options are mirrored in the window's **Settings** tab (`Tools → Aspid 🐍 → FastTools → Settings`) and at **`Preferences → Aspid FastTools`**, alongside the picker's per-user preferences:
+
+- **Favorites** — section on/off toggle.
+- **Recent items** — capacity slider (0–20; 0 hides the section and pauses recording without wiping history).
+- **Saved lists** — clears the stored Favorites / Recent.
+- **Welcome** — auto-show toggle.
+
+Every row carries a scope stripe (green — committed, blue — per-user); a pinned footer offers **Reset to defaults** per scope (saved Favorites / Recent lists survive a reset). All surfaces stay in live sync.
+
+## Headless CI
+
+For headless CI, the same check runs via `SerializeReferenceCiGate.RunCheck`: it scans
+the project, writes a report, logs every violation, and honours the committed gate
+severity — `Off` skips the check, `Warn` logs but exits 0, `Fail` exits 1 when
+violations exist (exit code 2 marks an internal failure of the check itself).
+
+```bash
+Unity -batchmode -quit -projectPath . \
+ -executeMethod Aspid.FastTools.SerializeReferences.Editors.SerializeReferenceCiGate.RunCheck \
+ -srGateReport SerializeReferenceGateReport.txt -srGateRequired
+```
+
+| Flag | Description |
+|---|---|
+| `-srGateReport ` | Report file path; defaults to `SerializeReferenceGateReport.txt` in the project root. Each violation is a machine-readable line with the violation kind, asset path and field path. |
+| `-srGateRequired` | Also flags unset `[TypeSelector(Required = true)]` fields across prefabs, ScriptableObjects and scenes (top-level fields, pure-YAML pass). |
+| `-srGateWarnOnly` | Overrides the committed severity to `Warn` for this run: violations are logged but the exit code is 0. Wins over `-srGateFail` if both are passed. |
+| `-srGateFail` | Overrides the committed severity to `Fail` for this run: exit code 1 when violations exist. |
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferenceTooling.md.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferenceTooling.md.meta
new file mode 100644
index 00000000..8179142f
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferenceTooling.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: cfbf55b2e3ac47e7b210d79cb9da17e3
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferences.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferences.md
new file mode 100644
index 00000000..a269f884
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferences.md
@@ -0,0 +1,95 @@
+# SerializeReference Selector
+
+The stock Inspector cannot populate `[SerializeReference]` fields: a managed reference
+cannot be created from the UI, and when a type is renamed or deleted Unity silently clears
+the data. SerializeReference Selector closes both gaps: a dropdown implementation picker
+right in the Inspector, plus per-field repair actions for broken references. Project-wide
+auditing, mass repair and the build/CI gate live in
+[SerializeReference Tooling](SerializeReferenceTooling.md).
+
+**Reference sections:**
+
+* [`Inspector type dropdown`](#inspector-type-dropdown) — the `[TypeSelector]` dropdown
+ on `[SerializeReference]` fields: implementation picking, nested inspector, generics,
+ copy/paste;
+* [`Repairing broken references`](#repairing-broken-references) — a yellow notice instead
+ of a silent clear, **Fix** / **Smart Fix** / **Make unique**.
+
+**A shorter version with the same examples lives in the** [README](README.md#serializereference-selector).
+
+## Inspector type dropdown
+
+Add `[TypeSelector]` next to `[SerializeReference]` — the Inspector replaces the stock
+managed-reference UI with the hierarchical [type-selection window](Types.md#typeselectorwindow)
+with search. You pick which concrete implementation of the field's type gets created right
+in the Inspector; `` clears the reference.
+
+```csharp
+using System;
+using UnityEngine;
+using System.Collections.Generic;
+using Aspid.FastTools.Types;
+
+public interface IWeapon
+{
+ void Fire();
+}
+
+[Serializable]
+public sealed class Pistol : IWeapon
+{
+ [SerializeField] [Min(0)] private int _damage = 10;
+
+ public void Fire() => Debug.Log($"Pistol: {_damage} dmg");
+}
+
+public sealed class Loadout : MonoBehaviour
+{
+ [TypeSelector]
+ [SerializeReference] private IWeapon _primary;
+
+ [TypeSelector]
+ [SerializeReference] private List _sidearms;
+}
+```
+
+The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no runtime
+cost. It works with single fields, arrays and `List`, in both IMGUI and UIToolkit
+inspectors. The same attribute also works on `string` and `SerializableType` fields —
+see [TypeSelectorAttribute](Types.md#typeselectorattribute).
+
+
+
+| Feature | What it does |
+|---|---|
+| **Implementation picking** | The list shows concrete non-`UnityEngine.Object` classes compatible with the field type. `[TypeSelector(typeof(IMelee))]` narrows it to `IMelee` implementations. |
+| **Open generics** | `Modifier` and friends: arguments are inferred from a closed generic field, or picked on the selector's second page. |
+| **Data preservation** | On a type switch, fields matching by name and serialized shape carry over instead of resetting to defaults. |
+| **Copy / Paste** | Right-clicking the header copies the value and pastes it as an independent instance into any compatible field. |
+| **Multi-selection** | A mixed selection shows a mixed dropdown state; a pick or paste applies to every object in a single Undo group. |
+| **Compiler validation** | Roslyn analyzer: `AFT0004` (error) — the type inherits `UnityEngine.Object`; `AFT0005` (warning) — the selector would be empty. |
+
+An empty field with `[TypeSelector(Required = true)]` shows a "required" notice in the
+Inspector and counts as a violation for the
+[build/CI gate](SerializeReferenceTooling.md#project-settings--the-buildci-gate) —
+see the `Required` property on [TypeSelectorAttribute](Types.md#typeselectorattribute).
+
+## Repairing broken references
+
+When an asset's stored type stops resolving, or two fields silently share one instance,
+the selector does not stay quiet — every problem gets an Inspector notice with a repair
+button next to it:
+
+| Case | Fix |
+|---|---|
+| **Missing type** (renamed or deleted) | A yellow notice instead of a silent clear. The underlined **Fix** opens the picker and re-points the type while keeping its data — at any depth, in saved assets and live in Prefab Mode. |
+| **Smart Fix** | Next to **Fix**, suggests the most likely replacement (`[MovedFrom]`, a different namespace/assembly, casing, a near-miss name) and applies it in one click — never automatically. |
+| **Shared reference** (two fields share one instance) | Flagged with a notice; **Make unique** splits it into an independent copy. Duplicating a list element (Ctrl+D, `+`) no longer aliases the reference. |
+
+
+
+
+
+For auditing and mass repair across the whole project, see
+[Bulk repair tabs](SerializeReferenceTooling.md#bulk-repair-tabs).
+
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferences.md.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferences.md.meta
new file mode 100644
index 00000000..e3a4ac1a
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferences.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 9421bc0a98b941aab1cd2dee0094e099
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializedPropertyExtensions.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializedPropertyExtensions.md
index 3f969ebf..7354c7eb 100644
--- a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializedPropertyExtensions.md
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializedPropertyExtensions.md
@@ -1,4 +1,4 @@
-# SerializedProperty Extensions — full reference
+# SerializedProperty Extensions
Chainable extension methods on `SerializedProperty` for synchronizing the owning `SerializedObject`, setting values, and reflecting on the underlying field.
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Types.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Types.md
new file mode 100644
index 00000000..bc5e3481
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Types.md
@@ -0,0 +1,262 @@
+# Serializable Type System
+
+Unity cannot serialize `System.Type` out of the box — the Serializable Type System closes that gap: the type is picked in the Inspector through a hierarchical, searchable window, stored as an assembly-qualified name, and lazily resolved to a `System.Type` on first access.
+
+**Reference sections:**
+
+* [`SerializableType`](#serializabletype) — a serializable field wrapper over `System.Type`;
+* [`TypeSelectorAttribute`](#typeselectorattribute) — a type-picker button on `string`,
+ `SerializableType` and `[SerializeReference]` fields, including
+ [dynamic base types via member references](#dynamic-base-types-via-member-references);
+* [`TypeSelectorDisplay`](#typeselectordisplay) — a candidate type's name, group, tooltip and icon
+ in the picker;
+* [`TypeSelectorWindow`](#typeselectorwindow) — the same picker window as a public API for
+ your own editor code;
+* [`ComponentTypeSelector`](#componenttypeselector) — an Inspector dropdown that switches
+ a component or ScriptableObject to a subtype.
+
+**A shorter version with the same examples is in the** [README](README.md#serializable-type-system).
+
+## SerializableType
+
+A serializable wrapper over `System.Type`: it stores the selected type as an assembly-qualified name and lazily resolves it to a `System.Type` on first access. Two variants are available:
+
+- **`SerializableType`** — stores any type;
+- **`SerializableType`** — stores a type constrained to `T` or its subclasses.
+
+Both support implicit conversion to `System.Type`.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public abstract class Ability : MonoBehaviour
+{
+ public abstract void Activate();
+}
+
+public sealed class AbilitySelector : MonoBehaviour
+{
+ [SerializeField] private SerializableType _abilityType;
+
+ private void Start()
+ {
+ var ability = (Ability)gameObject.AddComponent(_abilityType.Type);
+ ability.Activate();
+ }
+}
+```
+
+
+
+## TypeSelectorAttribute
+
+Adds a type-picker button to a field in the Inspector: it opens a hierarchical, searchable window listing only the types assignable to the given base types (when several are given, to all of them at once; with no arguments, any type qualifies). What happens on selection depends on the field's shape:
+
+- `string` — the assembly-qualified name of the selected type is written into the field;
+- `SerializableType` / `SerializableType` — narrows the built-in selector; the attribute's base types intersect with the generic argument `T`;
+- `[SerializeReference]` managed reference — the selected type is instantiated into the field immediately (see [SerializeReference Selector](SerializeReferences.md)).
+
+The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no runtime cost.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public interface IStackable { }
+
+public abstract class AbilityModifier
+{
+ public abstract void Apply();
+}
+
+public sealed class AbilitySelector : MonoBehaviour
+{
+ // string — the assembly-qualified name of the selected type is stored.
+ // Each array element is its own picker, constrained to AbilityModifier.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private string[] _modifierTypes;
+
+ // SerializableType — narrows the picker the field already has.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private SerializableType _modifierType;
+
+ // SerializableType — T already narrows the picker on its own; the base
+ // types of the attribute intersect with it: only AbilityModifier
+ // implementations that are also IStackable qualify.
+ [TypeSelector(typeof(IStackable))]
+ [SerializeField] private SerializableType _stackableModifierType;
+
+ // For a [SerializeReference] field picking a type immediately creates
+ // an instance and assigns it to the field. With no arguments the attribute
+ // offers subtypes of the field's own type (here — AbilityModifier).
+ // Required = true flags an unset field: an inspector warning
+ // plus a violation for the build/CI gate.
+ [TypeSelector(Required = true)]
+ [SerializeReference] private AbilityModifier _modifier;
+}
+```
+
+### Constructors and properties
+
+```csharp
+[Conditional("UNITY_EDITOR")]
+public sealed class TypeSelectorAttribute : PropertyAttribute
+{
+ public TypeSelectorAttribute() // base type: object
+ public TypeSelectorAttribute(Type type)
+ public TypeSelectorAttribute(params Type[] types)
+ public TypeSelectorAttribute(string assemblyQualifiedName)
+ public TypeSelectorAttribute(params string[] assemblyQualifiedNames)
+
+ public TypeAllow Allow { get; set; } // default: TypeAllow.All
+ public bool Required { get; set; } // default: false
+}
+
+[Flags]
+public enum TypeAllow
+{
+ None = 0,
+ Abstract = 1,
+ Interface = 2,
+ All = Abstract | Interface
+}
+```
+
+| Property | Description |
+|----------|-------------|
+| `Allow` | Which special type categories (abstract classes, interfaces) the picker includes in addition to plain concrete classes. Default: `TypeAllow.All` (a type-name field lists abstract classes and interfaces too; set `TypeAllow.None` to restrict it to concrete types). Ignored on a `[SerializeReference]` managed reference |
+| `Required` | Flags an unset field: a `[SerializeReference]` managed reference left `null`, or a `string` field left empty, shows an inline "required" warning in the Inspector and counts as a violation for the build/CI gate. Also covers a `SerializableType` field (its stored type name left empty). Default: `false` |
+
+#### The Required notice
+
+An empty field with `Required = true` looks like this in the Inspector:
+
+
+
+To find and fix such violations project-wide from the FastTools window instead of chasing them
+one Inspector at a time, see [Bulk repair tabs](SerializeReferenceTooling.md#bulk-repair-tabs).
+
+## Dynamic base types via member references
+
+The string constructors resolve **member-first**: when the string is a valid C# identifier that matches an instance field or property on the same object, that member's *current value* supplies the base type(s) — so one field can constrain another's picker, live in the Inspector. Any other string is treated as an assembly-qualified type name (`Type.GetType`), which is what you need for a type the call site cannot reference with `typeof` (across an editor or asmdef boundary).
+
+```csharp
+public sealed class Loadout : MonoBehaviour
+{
+ // The category chosen here drives the picker of _weaponType below.
+ [SerializeField] private SerializableType _category;
+
+ // Constrained live to whatever _category currently holds.
+ [TypeSelector(nameof(_category))]
+ [SerializeField] private string _weaponType;
+}
+```
+
+The referenced member must be an instance field or property of type `Type`, `string`, or `SerializableType` / `SerializableType` — or an array of any of these. Prefer `nameof(...)` so a rename keeps the link. An unknown member name, or a member of an unsuitable shape, is a **compile error** (analyzer rules `AFT0006`–`AFT0008`); for cases the analyzer cannot see (precompiled assemblies, a rename without recompilation) the drawer shows an inline warning below the field instead.
+
+## TypeSelectorDisplay
+
+Decorate a candidate type with `[TypeSelectorDisplay]` to tune how it appears in the picker — an editor-only attribute (`[Conditional("UNITY_EDITOR")]`) in `Aspid.FastTools.Types` that carries no runtime cost:
+
+```csharp
+using Aspid.FastTools.Types;
+
+// Rename the type in the picker, place it under an explicit group, give it a tooltip and an icon:
+[TypeSelectorDisplay(
+ Name = "Damage ×",
+ Group = "Combat/Modifiers",
+ Tooltip = "Scales incoming damage",
+ Icon = "d_ScriptableObject Icon")]
+public sealed class DamageModifier { }
+```
+
+| Member | Description |
+|--------|-------------|
+| `Name` | Display name shown instead of the type's short name — in the picker rows and in the closed dropdown's caption. Search still matches the real type name too, and the hover tooltip keeps revealing the full `Namespace.Class, Assembly` identity. `null` or whitespace means no override. |
+| `Group` | Explicit picker path with `/` separating levels (e.g. `"Combat/Melee"`). **Replaces** the type's namespace placement — the type appears only under this path, and path segments are shared between types. `null` or whitespace keeps the namespace placement. |
+| `Tooltip` | Tooltip shown when hovering the type's row. `null` means no tooltip override. |
+| `Icon` | Editor icon shown left of the label — an `EditorGUIUtility.IconContent` name, a project-relative asset path with extension (loaded via `AssetDatabase`), or a `Resources` texture path without extension. `null` means no icon. |
+
+In the picker, the `DamageModifier` from the example above appears under `Combat/Modifiers` as "Damage ×" with its icon — next to siblings that keep their default look:
+
+
+
+## TypeSelectorWindow
+
+A searchable, namespace-hierarchical type-picker popup — the same picker opened by `[TypeSelector]` and `SerializableType`, also available as a public API. The window offers:
+
+- Hierarchical namespace organization
+- Text search with filtering
+- Keyboard navigation (Arrow keys, Enter, Escape; Space toggles a favorite)
+- Breadcrumb trail with back navigation (Left arrow or a click on a crumb)
+- Assembly disambiguation for types with identical names
+- **Favorites** (★ on hover) and **Recent** (last picks) sections on the root page — stored locally per project (`EditorPrefs`, never committed), hidden while searching
+- A `` option pinned at the top and a ✓ mark on the current value — its row is pre-selected on open
+- Type counters on namespace/group rows and section headers
+- Generic type support — picking an open generic walks through its type parameters and emits the constructed type
+- Favorites/Recent tuning (on/off, Recent capacity) in the Settings tab of the SerializeReference window
+
+
+
+Picking an open generic walks through its argument page and returns the constructed type:
+
+
+
+> The argument page only lists types Unity can serialize as a field value: primitives, `enum`, `string`, `UnityEngine.Object`-derived references, and `[Serializable]` classes/structs. Abstract types, interfaces, open generics, and delegates never appear as candidates. Give a candidate type the `[Serializable]` attribute to make it selectable.
+
+The window is available as a public API — open it from any editor code (custom inspectors, `EditorWindow`, menu items) when you need a type picker outside the standard `SerializableType` / `[TypeSelector]` flow.
+
+```csharp
+namespace Aspid.FastTools.Types.Editors
+{
+ public sealed class TypeSelectorWindow : EditorWindow
+ {
+ public static void Show(
+ Rect screenRect,
+ TypeSelectorFilter filter = default,
+ string currentAqn = "",
+ Action onSelected = null);
+ }
+}
+```
+
+| Parameter | Description |
+|-----------|-------------|
+| `screenRect` | Screen-space rectangle the dropdown is anchored to. |
+| `filter` | Bundles which types the selector offers: base types (`Types`, only types assignable to **all** entries are listed; defaults to `typeof(object)`), the included kinds (`Allow`), an optional per-type `Predicate`, verbatim `AdditionalTypes`, and the open-generic `ArgumentFilter`. |
+| `currentAqn` | Assembly-qualified name of the currently selected type, used to pre-navigate to its location. Pass `null` or empty to start at the root. |
+| `onSelected` | Callback invoked with the assembly-qualified name of the selected type, or `null` if the user chose ``. |
+
+## ComponentTypeSelector
+
+A serializable struct that adds a type-switch dropdown to the Inspector. Add it as a field on a base class — picking a subtype rewrites `m_Script` on the `SerializedObject`, effectively turning the component or ScriptableObject into the selected subtype.
+
+The list is automatically restricted to subtypes of the class declaring the field. No extra configuration is required.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public abstract class EnemyBase : MonoBehaviour
+{
+ [SerializeField] private ComponentTypeSelector _enemyType;
+ [SerializeField] [Min(0)] private float _health = 100f;
+
+ public abstract void Attack();
+}
+
+public sealed class FastEnemy : EnemyBase
+{
+ [SerializeField] [Min(0)] private float _speed = 25f;
+
+ public override void Attack() =>
+ Debug.Log($"Fast enemy strikes! (speed: {_speed})");
+}
+```
+
+
+
+Notes on the type-switching dropdown's behavior:
+
+- Because the dropdown owns type-switching, the Inspector's built-in **Script** row is hidden while the selector is present — you change the type only through the dropdown (UIToolkit inspectors only; the legacy IMGUI inspector draws that row itself).
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Types.md.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Types.md.meta
new file mode 100644
index 00000000..61215fda
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Types.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: d865ffc8cfb248c5918ae812a348510d
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/VisualElementExtensions.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/VisualElementExtensions.md
index 6c97d02a..0f72f766 100644
--- a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/VisualElementExtensions.md
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/VisualElementExtensions.md
@@ -1,4 +1,4 @@
-# VisualElement Extensions — full reference
+# VisualElement Extensions
Fluent extension methods for building UIToolkit trees in code. All methods return `T` (the element itself) for chaining.
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_component_type_selector.gif b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_component_type_selector.gif
index cbac450f..79b4955c 100644
Binary files a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_component_type_selector.gif and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_component_type_selector.gif differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serializable_type.gif b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serializable_type.gif
index e0a46576..d7663657 100644
Binary files a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serializable_type.gif and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serializable_type.gif differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_asset_references.png b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_asset_references.png
new file mode 100644
index 00000000..fe9cfb32
Binary files /dev/null and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_asset_references.png differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_asset_references.png.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_asset_references.png.meta
new file mode 100644
index 00000000..81940ee3
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_asset_references.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 8a905f050ae2043e680c6d69045114c1
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_make_unique.png b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_make_unique.png
new file mode 100644
index 00000000..ec5d33d0
Binary files /dev/null and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_make_unique.png differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_make_unique.png.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_make_unique.png.meta
new file mode 100644
index 00000000..1af75b59
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_make_unique.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: ad765ef74b2d845faa7f9d32eab6907c
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_project_references.png b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_project_references.png
new file mode 100644
index 00000000..157936be
Binary files /dev/null and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_project_references.png differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_project_references.png.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_project_references.png.meta
new file mode 100644
index 00000000..78f41b97
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_project_references.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 39aacf2d94019452faf0e5ddc456d70d
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_repair.png b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_repair.png
new file mode 100644
index 00000000..4e5c45a3
Binary files /dev/null and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_repair.png differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_repair.png.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_repair.png.meta
new file mode 100644
index 00000000..1915e2aa
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_repair.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 7096032296ab64a3aa5056506dc26323
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_selector.gif b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_selector.gif
new file mode 100644
index 00000000..48ef295f
Binary files /dev/null and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_selector.gif differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_selector.gif.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_selector.gif.meta
new file mode 100644
index 00000000..860bbf0f
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_serialize_reference_selector.gif.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 5c98b3933d1784e3285d33f92a9bd128
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_display.png b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_display.png
new file mode 100644
index 00000000..386af1a9
Binary files /dev/null and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_display.png differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_display.png.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_display.png.meta
new file mode 100644
index 00000000..2d1455af
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_display.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: c88aff82b17a34472959f82de6aff85b
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_generic.gif b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_generic.gif
new file mode 100644
index 00000000..57850737
Binary files /dev/null and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_generic.gif differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_generic.gif.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_generic.gif.meta
new file mode 100644
index 00000000..e7aba0af
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_generic.gif.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 521c385dd0b754b2b98fde3c4fc041ab
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_required.png b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_required.png
new file mode 100644
index 00000000..1bc7f95d
Binary files /dev/null and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_required.png differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_required.png.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_required.png.meta
new file mode 100644
index 00000000..0564f3ce
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_required.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 4469f7d02ba4f480f8e036d634fc6724
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_window.png b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_window.png
index 918ef3fb..c8eaacee 100644
Binary files a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_window.png and b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/Images/aspid_fasttools_type_selector_window.png differ
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Ids.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Ids.md
new file mode 100644
index 00000000..7f33307c
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Ids.md
@@ -0,0 +1,25 @@
+# ID System
+
+Справочник по внутренностям Системы ID: диагностики генератора и runtime-API `IdRegistry`. Пошаговая настройка с примерами — в [README](README.md#id-system-beta).
+
+> **Бета:** Система ID находится в бета-версии. Публичный API, структура генерируемого кода и редакторский UX могут измениться в будущих релизах.
+
+## Generator diagnostics
+
+Генератор сообщает `AFID001`, если у структуры отсутствует `partial`, и `AFID002`, если вы сами объявили `_id`, `Id` или `__stringId` (генерация пропускается — вы получаете явную ошибку с указанием на структуру вместо CS-ошибки внутри сгенерированного кода). Поддерживаются generic-структуры (`EnemyId`) и generic-контейнеры.
+
+## IdRegistry
+
+`ScriptableObject` из `Aspid.FastTools.Ids`, хранящий записи `(int, string)` и поддерживающий таблицы поиска доступными во рантайме. Каждому имени назначается стабильный, автоинкрементный ID, который не изменяется даже при добавлении или удалении других записей.
+
+| Член | Описание |
+|------|----------|
+| `bool TryGetId(string name, out int id)` | Возвращает `true` и найденный ID; иначе `false` |
+| `bool TryGetName(int id, out string name)` | Возвращает `true` и найденное имя; иначе `false` и `string.Empty` |
+| `bool Contains(int id)` | Зарегистрирован ли ID |
+| `bool Contains(string name)` | Зарегистрировано ли имя |
+| `int Count` | Количество записей |
+| `IReadOnlyList Ids` · `IReadOnlyList IdNames` | Зарегистрированные ID / имена в порядке регистрации |
+| `IEnumerator> GetEnumerator()` | Итерация по парам `(id, name)` |
+
+Реестр наследуется напрямую от `ScriptableObject` и предоставляет генерик-аналог `IdRegistry` (с `T : struct, IId`), добавляющий типизированные перегрузки `Contains(T)` и `TryGetName(T, out string)`. Редактирование — добавление, переименование, удаление записей — выполняется через инспектор реестра и `RegistryEditorCore`, а не через публичный runtime API.
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Ids.md.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Ids.md.meta
new file mode 100644
index 00000000..fa61c2da
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Ids.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: de79d4c6dca7455a96f703e6b98107e4
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/README.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/README.md
index e3426eba..9835118b 100644
--- a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/README.md
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/README.md
@@ -1,33 +1,33 @@
-**Aspid.FastTools** — набор инструментов, предназначенных для минимизации рутинного написания кода в Unity: инструменты для `SerializeReference` (выбор типа в инспекторе и окно-обозреватель ссылок), генераторы кода на базе Roslyn и подборка runtime- и editor-утилит — от сериализуемого `System.Type` до fluent-расширений UI Toolkit.
+[English](../EN/README.md) | [Русский](README.md)
-### \[[Unity Asset Store](https://assetstore.unity.com/packages/slug/365584)\] \[[Donate](#donate)\]
+**Aspid.FastTools** — набор инструментов для Unity, избавляющий от рутинного бойлерплейта. Внутри — удобная работа с `SerializeReference` (выбор типа в инспекторе и окно аудита ссылок по всему проекту), Roslyn-генераторы и анализаторы, а также runtime- и editor-утилиты: от сериализуемого `System.Type` до fluent-расширений UI Toolkit.
-## Source Code
-
-[[Aspid.FastTools](https://github.com/VPDPersonal/Aspid.FastTools)]
+[Исходный код](https://github.com/VPDPersonal/Aspid.FastTools) · [Unity Asset Store](https://assetstore.unity.com/packages/slug/365584) · [Releases](https://github.com/VPDPersonal/Aspid.FastTools/releases)
## Содержание
-- **Getting Started**
- - [Integration](#integration)
- - [Claude Code Plugin](#claude-code-plugin)
- - [Donate](#donate)
-- **Features**
- - [ProfilerMarker](#profilermarker)
- - [Serializable Type System](#serializable-type-system)
- - [SerializeReference Selector](#serializereference-selector)
- - [Enum System](#enum-system)
- - [ID System (Beta)](#id-system-beta)
- - [VisualElement Extensions](#visualelement-extensions)
- - [SerializedProperty Extensions](#serializedproperty-extensions)
- - [IMGUI Layout Scopes](#imgui-layout-scopes)
- - [Editor Helper Extensions](#editor-helper-extensions)
+- **Начало работы**
+ - [Установка](#установка) — UPM git URL, `.unitypackage`, Asset Store
+- **Возможности**
+ - [Serializable Type System](#serializable-type-system) — `System.Type` как сериализуемое поле и окно выбора типа с поиском
+ - [SerializeReference Selector](#serializereference-selector) — выпадающий выбор типа для полей `[SerializeReference]`, а также инструменты поиска и починки битых ссылок
+ - [ProfilerMarker](#profilermarker) — source-generated маркеры профайлера, уникальные для каждого места вызова
+ - [Enum System](#enum-system) — сериализуемые отображения enum → значение с поддержкой `[Flags]`
+ - [ID System (Beta)](#id-system-beta) — назначаемые в ассетах имена со стабильными целочисленными ID
+ - [VisualElement Extensions](#visualelement-extensions) — fluent-построение UI Toolkit-деревьев в коде
+ - [SerializedProperty Extensions](#serializedproperty-extensions) — типизированные сеттеры с fluent-цепочками и рефлексионные хелперы
+ - [IMGUI Layout Scopes](#imgui-layout-scopes) — disposable-обёртки `Begin*`/`End*` с доступом к `Rect`
+ - [Editor Helper Extensions](#editor-helper-extensions) — получение отображаемых имён скриптов для кастомных редакторов
+- **Дополнительно**
+ - [Claude Code Plugin](#claude-code-plugin) — скиллы, обучающие Claude Code этому пакету
+ - [Поддержать проект](#поддержать-проект)
+ - [Лицензия](#лицензия)
---
-## Integration
+## Установка
Установите Aspid.FastTools через UPM: в Package Manager нажмите **+ → Install package from git URL…** и вставьте один из URL ниже.
@@ -58,117 +58,16 @@ https://github.com/VPDPersonal/Aspid.FastTools.git#upm/1.0.0
https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview
```
-Чтобы установить конкретную preview-версию, укажите неизменяемый per-release тег (список доступных версий — на странице [Releases](https://github.com/VPDPersonal/Aspid.FastTools/releases)):
+Конкретные preview-версии используют ту же схему per-release тегов:
```
-https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview/1.0.0-rc.2
+https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview/1.0.0-rc.5
```
---
-## Claude Code Plugin
-
-Если вы используете [Claude Code](https://docs.claude.com/en/docs/claude-code), сопутствующий маркетплейс [Aspid.Claude.Plugins](https://github.com/VPDPersonal/Aspid.Claude.Plugins) поставляет плагин `aspid-fasttools` — набор скиллов, которые обучают Claude Code конвенциям и API этого пакета.
-
-> [!WARNING]
-> Плагин всё ещё находится в бета-версии — его скиллы и команды могут меняться между релизами.
-
-Добавьте маркетплейс и установите плагин:
-
-```sh
-/plugin marketplace add VPDPersonal/Aspid.Claude.Plugins
-```
-
-```sh
-/plugin install aspid-fasttools@aspid-claude-plugins
-```
-
-Включённые скиллы:
-
-- **`aspid-id-struct`** — создаёт новую `IId`-структуру и `[UniqueId]`-поля для [ID System](#id-system-beta).
-- **`aspid-profiler-marker`** — вставляет вызовы `this.Marker()` с правильной формой `using`/scope.
-- **`aspid-visual-element-fluent`** — собирает editor- или runtime-UI через fluent-расширения `VisualElement`.
-
----
-
-## Donate
-
-Этот проект разрабатывается на добровольной основе. Если он оказался для вас полезным, вы можете поддержать его развитие финансово. Это поможет уделять больше времени улучшению и сопровождению **Aspid.FastTools**.
-
-Поддержать проект можно через следующие платформы:
-* \[[Unity Asset Store](https://assetstore.unity.com/packages/slug/365584)\]
-
----
-
-## ProfilerMarker
-
-Предоставляет регистрацию `ProfilerMarker` через source generation. Генератор создаёт статический маркер для каждого места вызова, идентифицируемый по вызывающему методу и номеру строки.
-
-```csharp
-using UnityEngine;
-
-public class MyBehaviour : MonoBehaviour
-{
- private void Update()
- {
- DoSomething1();
- DoSomething2();
- }
-
- private void DoSomething1()
- {
- using var _ = this.Marker();
- // Некоторый код
- }
-
- private void DoSomething2()
- {
- using (this.Marker())
- {
- // Некоторый код
- using var _ = this.Marker().WithName("Calculate");
- // Некоторый код
- }
- }
-}
-```
-
-
-Сгенерированный код
-
-
-```csharp
-using Unity.Profiling;
-using System.Runtime.CompilerServices;
-
-internal static class __MyBehaviourProfilerMarkerExtensions
-{
- private static readonly ProfilerMarker DoSomething1_Marker_Line_13 = new("MyBehaviour.DoSomething1 (13)");
- private static readonly ProfilerMarker DoSomething2_Marker_Line_19 = new("MyBehaviour.DoSomething2 (19)");
- private static readonly ProfilerMarker DoSomething2_Marker_Line_22 = new("MyBehaviour.Calculate (22)");
-
- public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
- {
-#if ENABLE_PROFILER
- if (line is 13) return DoSomething1_Marker_Line_13.Auto();
- if (line is 19) return DoSomething2_Marker_Line_19.Auto();
- if (line is 22) return DoSomething2_Marker_Line_22.Auto();
-#endif
- return default;
- }
-}
-```
-
-
-
-### Result
-
-
-
----
-
## Serializable Type System
Позволяет сериализовать ссылку на `System.Type` в Unity Inspector. Выбранный тип хранится как assembly-qualified name и разрешается лениво при первом обращении.
@@ -177,7 +76,7 @@ internal static class __MyBehaviourProfilerMarkerExtensions
Доступны два варианта:
-- **`SerializableType`** — хранит любой тип (базовый тип — `object`)
+- **`SerializableType`** — хранит любой тип
- **`SerializableType`** — хранит тип, ограниченный `T` или его подклассами
Оба поддерживают неявное преобразование в `System.Type`.
@@ -202,45 +101,24 @@ public sealed class AbilitySelector : MonoBehaviour
}
}
```
-
+
### TypeSelectorAttribute
-Атрибут `PropertyAttribute`, доступный только в редакторе, ограничивающий всплывающее окно выбора типа конкретными базовыми типами. Применяется к полю `string` (хранящему assembly-qualified имя типа), полю `SerializableType` / `SerializableType` или managed-reference полю `[SerializeReference]`. Для `SerializableType` базовые типы атрибута пересекаются с generic-аргументом `T`.
-
-```csharp
-[Conditional("UNITY_EDITOR")]
-public sealed class TypeSelectorAttribute : PropertyAttribute
-{
- public TypeSelectorAttribute() // базовый тип: object
- public TypeSelectorAttribute(Type type)
- public TypeSelectorAttribute(params Type[] types)
- public TypeSelectorAttribute(string assemblyQualifiedName)
- public TypeSelectorAttribute(params string[] assemblyQualifiedNames)
-
- public TypeAllow Allow { get; set; } // по умолчанию: TypeAllow.All
- public bool Required { get; set; } // по умолчанию: false
-}
+Добавляет к полю в Инспекторе кнопку выбора типа: она открывает иерархическое окно с поиском, в котором перечислены только типы, совместимые с указанными базовыми (при нескольких — со всеми сразу; без аргументов подходит любой тип). Что происходит при выборе, зависит от формы поля:
-[Flags]
-public enum TypeAllow
-{
- None = 0,
- Abstract = 1,
- Interface = 2,
- All = Abstract | Interface
-}
-```
+- `string` — в поле записывается assembly-qualified имя выбранного типа;
+- `SerializableType` / `SerializableType` — сужает встроенный селектор; базовые типы атрибута пересекаются с generic-аргументом `T`;
+- managed-ссылка `[SerializeReference]` — выбранный тип сразу инстанцируется в поле (см. [SerializeReference Selector](#serializereference-selector)).
-| Свойство | Описание |
-|----------|----------|
-| `Allow` | Какие специальные категории типов (абстрактные классы, интерфейсы) включаются в список выбора в дополнение к обычным конкретным классам. По умолчанию: `TypeAllow.All` (поле-имя типа показывает и абстрактные классы, и интерфейсы; укажите `TypeAllow.None`, чтобы ограничить только конкретными типами). Игнорируется на managed-ссылке `[SerializeReference]` |
-| `Required` | Помечает незаполненное поле: managed reference `[SerializeReference]`, оставшийся `null`, или пустое `string`-поле показывает предупреждение «required» в инспекторе и считается нарушением для build/CI-гейта. Также покрывает поле `SerializableType` (когда сохранённое имя типа пустое). По умолчанию: `false` |
+Атрибут editor-only (`[Conditional("UNITY_EDITOR")]`) и не несёт стоимости в рантайме.
```csharp
using UnityEngine;
using Aspid.FastTools.Types;
+public interface IStackable { }
+
public abstract class AbilityModifier
{
public abstract void Apply();
@@ -248,58 +126,35 @@ public abstract class AbilityModifier
public sealed class AbilitySelector : MonoBehaviour
{
+ // string — сохраняется assembly-qualified имя выбранного типа.
// Каждый элемент массива — отдельный picker, ограниченный AbilityModifier.
[TypeSelector(typeof(AbilityModifier))]
[SerializeField] private string[] _modifierTypes;
-}
-```
-
-> Полный сэмпл — `Ability` / `AbilitySelector` / `EnemyBase` и их наследники — поставляется в сэмпле `Types` (Package Manager → Aspid.FastTools → Samples).
-
-#### Динамические базовые типы через ссылки на члены
-
-Строковые конструкторы резолвят строку **member-first**: если строка — корректный C#-идентификатор и совпадает с instance-полем или свойством того же объекта, *текущее значение* этого члена задаёт базовый тип(ы) — так одно поле ограничивает пикер другого прямо в Инспекторе, вживую. Любая другая строка трактуется как assembly-qualified имя типа (`Type.GetType`) — то, что нужно для типа, на который в месте вызова нельзя сослаться через `typeof` (за границей editor-сборки или asmdef).
-
-```csharp
-public sealed class Loadout : MonoBehaviour
-{
- // Выбранная здесь категория управляет пикером _weaponType ниже.
- [SerializeField] private SerializableType _category;
- // Ограничен вживую тем, что сейчас лежит в _category.
- [TypeSelector(nameof(_category))]
- [SerializeField] private string _weaponType;
+ // SerializableType — сужает picker, который у поля уже есть.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private SerializableType _modifierType;
+
+ // SerializableType — T сам сужает picker; базовые типы атрибута
+ // пересекаются с ним: подойдут только реализации AbilityModifier,
+ // которые заодно являются IStackable.
+ [TypeSelector(typeof(IStackable))]
+ [SerializeField] private SerializableType _stackableModifierType;
+
+ // Для [SerializeReference]-поля выбор типа сразу создаёт его экземпляр
+ // и записывает в поле. Атрибут без аргументов предлагает наследников
+ // типа поля (здесь — AbilityModifier). Required = true помечает
+ // незаполненное поле: предупреждение в инспекторе + нарушение CI-гейта.
+ [TypeSelector(Required = true)]
+ [SerializeReference] private AbilityModifier _modifier;
}
```
-Член может быть `Type`, `Type[]`, `string`, `string[]` или `SerializableType` / `SerializableType` (и массивами из них). Предпочитайте `nameof(...)`, чтобы переименование не рвало ссылку. Ошибка использования — неизвестное имя члена или член неподходящего типа — это **ошибка компиляции** (правила анализатора `AFT0006`–`AFT0008`); для случаев, которые анализатор не видит (precompiled-сборки, член переименован после компиляции), drawer вместо этого показывает тихое inline-предупреждение под полем.
-
-Пометьте тип-кандидат атрибутом `[TypeSelectorDisplay]`, чтобы настроить, как он показывается в селекторе — это editor-only атрибут (`[Conditional("UNITY_EDITOR")]`) в `Aspid.FastTools.Types`, не несущий стоимости в рантайме:
+Помимо базовых типов, у атрибута есть `Allow` (показывать ли абстрактные классы и интерфейсы) и `Required` (незаполненное поле — inline-предупреждение и нарушение для build/CI-гейта). Отдельно в справочнике: [динамический базовый тип из поля или свойства](Types.md#dynamic-base-types-via-member-references) и настройка вида типа в пикере через [`[TypeSelectorDisplay]`](Types.md#typeselectordisplay).
-```csharp
-using Aspid.FastTools.Types;
-
-// Переименовать тип в пикере, положить его в явную группу, задать tooltip и иконку:
-[TypeSelectorDisplay(
- Name = "Damage ×",
- Group = "Combat/Modifiers",
- Tooltip = "Scales incoming damage",
- Icon = "d_ScriptableObject Icon")]
-public sealed class DamageModifier { }
-```
+### TypeSelectorWindow
-| Член | Описание |
-|------|----------|
-| `Name` | Отображаемое имя вместо короткого имени типа — в строках пикера и в подписи закрытого дропдауна. Поиск по-прежнему находит тип и по настоящему имени, а tooltip при наведении показывает полную идентичность `Namespace.Class, Assembly`. `null` или пробелы — без переопределения. |
-| `Group` | Явный путь в пикере, уровни разделяются `/` (например `"Combat/Melee"`). **Заменяет** размещение по namespace — тип показывается только под этим путём, сегменты пути общие для разных типов. `null` или пробелы — размещение по namespace. |
-| `Tooltip` | Tooltip, показываемый при наведении на строку типа. `null` — без переопределения tooltip. |
-| `Icon` | Иконка редактора слева от лейбла — имя `EditorGUIUtility.IconContent`, путь к ассету в проекте с расширением (загружается через `AssetDatabase`) или путь к текстуре в `Resources` без расширения. `null` — без иконки. |
-
----
-
-### Type Selector Window
-
-В Inspector отображается кнопка, открывающая всплывающее окно с поиском, которое включает:
+Всплывающее окно выбора типа с поиском и иерархией по пространствам имён — тот же пикер, что открывают `[TypeSelector]` и `SerializableType`, доступный и как публичный API. Окно включает:
- Иерархическую организацию по пространствам имён
- Текстовый поиск с фильтрацией
@@ -312,30 +167,13 @@ public sealed class DamageModifier { }
- Поддержку generic-типов — выбор открытого generic ведёт через выбор его аргументов и возвращает сконструированный тип
- Настройку Favorites/Recent (вкл/выкл, ёмкость Recent) во вкладке Settings окна SerializeReference
-
+
-Это же окно доступно как публичный API — открывайте его из любого editor-кода (кастомных инспекторов, `EditorWindow`, пунктов меню), когда нужно вывести выбор типа за пределами стандартного потока `SerializableType` / `[TypeSelector]`.
+Выбор открытого generic проходит через страницу его аргументов и возвращает сконструированный тип:
-```csharp
-namespace Aspid.FastTools.Types.Editors
-{
- public sealed class TypeSelectorWindow : EditorWindow
- {
- public static void Show(
- Rect screenRect,
- TypeSelectorFilter filter = default,
- string currentAqn = "",
- Action onSelected = null);
- }
-}
-```
+
-| Параметр | Описание |
-|----------|----------|
-| `screenRect` | Прямоугольник в экранных координатах, к которому привязывается dropdown. |
-| `filter` | Объединяет, какие типы предлагает селектор: базовые типы (`Types`, в списке остаются только типы, совместимые со **всеми** записями; по умолчанию — `typeof(object)`), включаемые категории (`Allow`), необязательный предикат `Predicate`, дополнительные записи `AdditionalTypes` и предикат аргументов открытых генериков `ArgumentFilter`. |
-| `currentAqn` | Assembly-qualified имя текущего выбранного типа: окно сразу откроется на его уровне иерархии. Передайте `null` или пустую строку, чтобы стартовать с корня. |
-| `onSelected` | Callback с assembly-qualified именем выбранного типа или `null`, если пользователь выбрал ``. |
+Окно доступно как публичный API (`TypeSelectorWindow.Show`) — открывайте его из любого editor-кода (кастомных инспекторов, `EditorWindow`, пунктов меню), когда нужно вывести выбор типа за пределы стандартного потока `SerializableType` / `[TypeSelector]`. Сигнатура и параметры: [Types.md](Types.md#typeselectorwindow).
### ComponentTypeSelector
@@ -343,8 +181,6 @@ namespace Aspid.FastTools.Types.Editors
Список автоматически ограничивается подтипами класса, в котором объявлено поле. Дополнительная настройка не требуется.
-Так как сменой типа управляет сам список, встроенная строка **Script** в Inspector скрывается, пока присутствует селектор — тип меняется только через выпадающий список (только UIToolkit-инспекторы; устаревший IMGUI-инспектор рисует эту строку сам).
-
```csharp
using UnityEngine;
using Aspid.FastTools.Types;
@@ -364,24 +200,21 @@ public sealed class FastEnemy : EnemyBase
public override void Attack() =>
Debug.Log($"Fast enemy strikes! (speed: {_speed})");
}
+```
-public sealed class TankEnemy : EnemyBase
-{
- [SerializeField] [Min(0)] private float _armor = 50f;
+
- public override void Attack() =>
- Debug.Log($"Tank attacks! (armor: {_armor})");
-}
-```
+Заметки о поведении дропдауна смены типа:
+
+- Так как сменой типа управляет сам список, встроенная строка **Script** в Inspector скрывается, пока присутствует селектор — тип меняется только через выпадающий список (только UIToolkit-инспекторы; устаревший IMGUI-инспектор рисует эту строку сам).
-
-
+> Полный справочник: [Types.md](Types.md)
---
## SerializeReference Selector
-Готовый выпадающий список выбора типа для полей `[SerializeReference]`. Добавьте `[TypeSelector]` рядом с `[SerializeReference]` — Inspector заменит стандартный UI managed-ссылки тем же иерархическим селектором с поиском, что и `SerializableType`. Вы прямо в инспекторе выбираете, какая конкретная реализация типа поля будет создана; `` очищает ссылку.
+Готовый выпадающий список выбора типа для полей `[SerializeReference]`. Добавьте `[TypeSelector]` рядом с `[SerializeReference]` — Inspector заменит стандартный UI managed-ссылки иерархическим [окном выбора типа](#typeselectorwindow) с поиском. Вы прямо в инспекторе выбираете, какая конкретная реализация типа поля будет создана; `` очищает ссылку.
```csharp
using System;
@@ -402,14 +235,6 @@ public sealed class Pistol : IWeapon
public void Fire() => Debug.Log($"Pistol: {_damage} dmg");
}
-[Serializable]
-public sealed class Railgun : IWeapon
-{
- [SerializeField] [Min(0)] private float _chargeTime = 1.5f;
-
- public void Fire() => Debug.Log($"Railgun charged for {_chargeTime}s");
-}
-
public sealed class Loadout : MonoBehaviour
{
[SerializeReference] [TypeSelector]
@@ -420,7 +245,7 @@ public sealed class Loadout : MonoBehaviour
}
```
-Атрибут существует только в редакторе (`[Conditional("UNITY_EDITOR")]`) и не несёт стоимости в рантайме. Работает с одиночными полями, массивами и `List`, в инспекторах IMGUI и UIToolkit.
+Атрибут существует только в редакторе (`[Conditional("UNITY_EDITOR")]`) и не несёт стоимости в рантайме. Работает с одиночными полями, массивами и `List`, в инспекторах IMGUI и UIToolkit. Тот же атрибут работает и с полями `string` и `SerializableType` — см. [TypeSelectorAttribute](#typeselectorattribute).
### Возможности
@@ -436,51 +261,74 @@ public sealed class Loadout : MonoBehaviour
### Починка сломанных ссылок
-| Случай | Решение |
-|---|---|
-| **Потерянный тип** (переименован или удалён) | Жёлтое предупреждение вместо молчаливой очистки. Подчёркнутое **Fix** открывает селектор и переназначает тип с сохранением данных — на любой глубине, в сохранённых ассетах и прямо в Prefab Mode. |
-| **Smart Fix** | Рядом с **Fix** предлагает наиболее вероятную замену (`[MovedFrom]`, другой namespace/сборка, регистр, близкое имя) и применяет в один клик — никогда не автоматически. |
-| **Общая ссылка** (два поля делят экземпляр) | Помечается лейблом; **Make unique** расщепляет её в независимую копию. Дублирование элемента списка (Ctrl+D, `+`) больше не создаёт алиас. |
+Потерянный тип (переименован или удалён) показывает жёлтое предупреждение вместо молчаливой очистки: **Fix** переназначает тип с сохранением данных, а **Smart Fix** предлагает наиболее вероятную замену (`[MovedFrom]`, другой namespace, близкое имя) — применяется в один клик и никогда автоматически. Общая ссылка (два поля делят один экземпляр) помечается лейблом и расщепляется через **Make unique**. Массовая починка вынесена во вкладки **Asset References** и **Project References** (`Tools → Aspid 🐍 → FastTools`): первая строит весь граф managed-ссылок ассета прямо из YAML, вторая сканирует каждый `.prefab` / `.asset` / `.unity` в проекте, чинит сломанные ссылки группами и запекает переименования `[MovedFrom]` в файлы.
-
+### Настройки проекта и build/CI gate
-Массовая починка вынесена в отдельные вкладки:
+**`Project Settings → Aspid FastTools → SerializeReference`** управляет breakage detection, авто-расщеплением дублированных элементов списков, исключёнными из сканов папками и build/CI-гейтом (`Off` / `Warn` / `Fail`) — коммитимые значения хранятся в `ProjectSettings/SerializeReferenceSharedSettings.asset`, так что команда и CI ведут себя одинаково. Те же опции продублированы во вкладке **Settings** окна и на странице **`Preferences → Aspid FastTools`**; в headless-CI ту же проверку запускает `SerializeReferenceCiGate.RunCheck`.
-| Вкладка | Назначение |
-|---|---|
-| **Asset References** (`Tools → Aspid 🐍 → FastTools → Asset References`) | Строит весь граф managed-ссылок ассета прямо из YAML — дерево по компонентам с путями полей, общими и осиротевшими ссылками, значками `MISSING` / `SHARED` и инлайн-выбором типа на каждой карточке. Достаёт потерянные ссылки, которые инспектор не показывает. |
-| **Project References** (`Tools → Aspid 🐍 → FastTools → Project References`) | `Scan Project` обходит каждый `.prefab` / `.asset` / `.unity` под `Assets/`, группирует сломанные ссылки по сохранённому типу и чинит всю группу одним `Fix all` (плюс Smart Fix). Группа, чей сохранённый тип совпадает с объявленным переименованием `[MovedFrom]`, читается как ожидающая миграция, а не поломка — один клик **Migrate all** запекает переименование в файлы, после чего атрибут можно удалить из кода. |
+> Полный справочник: [SerializeReferences.md](SerializeReferences.md) · вкладки окна, настройки и CI-гейт: [SerializeReferenceTooling.md](SerializeReferenceTooling.md)
-### Настройки проекта и build/CI gate
+---
-**`Project Settings → Aspid FastTools → SerializeReference`** содержит:
+## ProfilerMarker
-| Настройка | Scope | Что делает |
-|---|---|---|
-| **Breakage detection** | per-user | Проактивный тост + предупреждение в Console, когда ссылки заново становятся потерянными после рекомпиляции / импорта. |
-| **Auto de-alias duplicated list elements** | коммитимая | Дублированный элемент списка получает собственный экземпляр вместо совместного использования id оригинала. |
-| **Build / CI gate** | коммитимая | `Off` / `Warn` / `Fail`: при сборке плеера логировать или прерывать сборку на потерянных (а для CI — и на незаданных обязательных) managed-ссылках. |
-| **Excluded scan folders** | коммитимая | Пути, пропускаемые при всех проектных сканах. |
+Предоставляет регистрацию `ProfilerMarker` через source generation. Генератор создаёт статический маркер для каждого места вызова, идентифицируемый по вызывающему методу и номеру строки.
-- Коммитимые значения хранятся в `ProjectSettings/SerializeReferenceSharedSettings.asset` — закоммитьте его, чтобы команда и CI вели себя одинаково; breakage detection остаётся per-machine (`EditorPrefs`).
-- Rid colours — не настройка: общая ссылка всегда раскрашивается по id — совпадающий цвет и показывает, какие поля делят один экземпляр.
+```csharp
+using UnityEngine;
-Те же опции продублированы во вкладке **Settings** окна (`Tools → Aspid 🐍 → FastTools → Settings`) и на странице **`Preferences → Aspid FastTools`**, рядом с индивидуальными настройками пикера:
+public class MyBehaviour : MonoBehaviour
+{
+ private void DoSomething1()
+ {
+ using var _ = this.Marker();
+ // Некоторый код
+ }
-- **Favorites** — переключатель секции.
-- **Recent items** — слайдер ёмкости (0–20; 0 скрывает секцию и приостанавливает запись, не стирая историю).
-- **Saved lists** — очищает сохранённые Favorites / Recent.
-- **Welcome** — переключатель автопоказа.
+ private void DoSomething2()
+ {
+ using (this.Marker())
+ {
+ // Некоторый код
+ using var _ = this.Marker().WithName("Calculate");
+ // Некоторый код
+ }
+ }
+}
+```
-Каждая строка помечена полоской scope (зелёная — коммитимые, синяя — индивидуальные); закреплённый футер предлагает **Reset to defaults** отдельно для каждого scope (сохранённые списки Favorites / Recent сброс переживают). Все поверхности зеркалят друг друга живьём.
+
+Сгенерированный код
+
-Для headless-CI метод `SerializeReferenceCiGate.RunCheck` (через `-batchmode -executeMethod`) пишет отчёт и учитывает коммитимую строгость гейта:
+```csharp
+using Unity.Profiling;
+using System.Runtime.CompilerServices;
-- `Off` пропускает проверку, `Warn` логирует, но завершается с кодом 0, `Fail` завершается с ненулевым кодом при нарушениях.
-- `-srGateRequired` дополнительно проверяет незаданные поля `[TypeSelector(Required = true)]` в префабах, ScriptableObject и сценах (required-поля верхнего уровня, чистый YAML-проход).
-- Per-run флаги `-srGateWarnOnly` / `-srGateFail` переопределяют коммитимую строгость.
+internal static class __MyBehaviourProfilerMarkerExtensions
+{
+ private static readonly ProfilerMarker DoSomething1_Marker_Line_7 = new("MyBehaviour.DoSomething1 (7)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_13 = new("MyBehaviour.DoSomething2 (13)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_16 = new("MyBehaviour.Calculate (16)");
-> Полный сэмпл — `Loadout` / `IWeapon` / `Modifier` и сценарии починки потерянных ссылок — поставляется в сэмпле `SerializeReferences` (Package Manager → Aspid.FastTools → Samples). Пошаговый разбор — в `TUTORIAL.md` этого сэмпла.
+ public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
+ {
+#if ENABLE_PROFILER
+ if (line is 7) return DoSomething1_Marker_Line_7.Auto();
+ if (line is 13) return DoSomething2_Marker_Line_13.Auto();
+ if (line is 16) return DoSomething2_Marker_Line_16.Auto();
+#endif
+ return default;
+ }
+}
+```
+
+
+
+### Результат
+
+
---
@@ -492,12 +340,7 @@ public sealed class Loadout : MonoBehaviour
Сериализуемая коллекция записей `EnumValue` с настраиваемым значением по умолчанию. Реализует `IEnumerable>`.
-| Член | Описание |
-|------|----------|
-| `TValue GetValue(Enum enumValue)` | Возвращает сопоставленное значение или `_defaultValue`, если не найдено |
-| `bool Equals(Enum, Enum)` | Проверка равенства с поддержкой `[Flags]` |
-
-Поддерживает `[Flags]`-перечисления: `Equals` использует `HasFlag` и корректно обрабатывает члены со значением `0`.
+`GetValue` возвращает сопоставленное значение, а при отсутствии ключа — настроенное значение по умолчанию. `[Flags]`-перечисления поддерживаются: сопоставление использует `HasFlag` и корректно обрабатывает члены со значением `0`.
```csharp
using System;
@@ -507,51 +350,29 @@ using Aspid.FastTools.Enums;
public enum DamageType { Physical, Fire, Ice, Poison }
[Flags]
-public enum StatusEffect
-{
- None = 0,
- Burning = 1,
- Frozen = 2,
- Slowed = 4,
- Stunned = 8,
-}
+public enum StatusEffect { None = 0, Burning = 1, Frozen = 2, Slowed = 4, Stunned = 8 }
public sealed class DamageDealer : MonoBehaviour
{
[SerializeField] private EnumValues _damageMultipliers;
- [SerializeField] private EnumValues _damageColors;
// Flag combinations (e.g. Burning | Slowed) match via HasFlag and first-hit wins,
// so list composite entries BEFORE their constituent flags.
[SerializeField] private EnumValues _speedMultipliersByStatus;
- [SerializeField] private DamageType _currentType;
- [SerializeField] private StatusEffect _activeEffects;
+ public float GetMultiplier(DamageType type) => _damageMultipliers.GetValue(type);
- private void DealDamage()
- {
- var multiplier = _damageMultipliers.GetValue(_currentType);
- var color = _damageColors.GetValue(_currentType);
- var speedMod = _speedMultipliersByStatus.GetValue(_activeEffects);
- // ...
- }
+ public float GetSpeedModifier(StatusEffect effects) => _speedMultipliersByStatus.GetValue(effects);
}
```
-
+
В Inspector выберите тип перечисления в заголовке `EnumValues`, затем назначьте значение для каждого члена перечисления. Нажмите правой кнопкой мыши по свойству, чтобы открыть контекстное меню с пунктом **Populate Missing Enum Members** — он добавит записи для всех отсутствующих членов перечисления, используя текущее Default Value как начальное значение.
-> Полный сэмпл — `DamageDealer` / `DamageType` / `StatusEffect` — поставляется в сэмпле `EnumValues` (Package Manager → Aspid.FastTools → Samples).
-
### EnumValues\
Типизированный вариант `EnumValues` для частого случая, когда тип перечисления уже известен в коде. Тип фиксируется generic-аргументом, поэтому выбор типа в Inspector заблокирован, а обращения проверяются на этапе компиляции. Поиск при этом не использует boxing — ключи сравниваются как закэшированные числовые значения, — а `foreach` по обоим вариантам использует struct-энумератор и не аллоцирует. Реализует `IEnumerable>`.
-| Член | Описание |
-|------|----------|
-| `TValue GetValue(TEnum enumValue)` | Возвращает сопоставленное значение или `_defaultValue`, если не найдено |
-| `bool Equals(TEnum, TEnum)` | Проверка равенства с корректной поддержкой `[Flags]` |
-
```csharp
public sealed class HitEffect : MonoBehaviour
{
@@ -562,7 +383,7 @@ public sealed class HitEffect : MonoBehaviour
}
```
-Семантика поиска (включая обработку `[Flags]`) идентична `EnumValues`, а сериализованный формат совместим — смена типа поля между двумя вариантами сохраняет существующие данные, пока тип перечисления совпадает.
+Семантика поиска (включая обработку `[Flags]`) идентична `EnumValues`.
---
@@ -570,13 +391,9 @@ public sealed class HitEffect : MonoBehaviour
> **Бета:** Система ID находится в бета-версии. Публичный API, структура генерируемого кода и редакторский UX могут измениться в будущих релизах.
-Сопоставляет имя, назначаемое в активе, со стабильным целочисленным ID. Получившийся `int` подходит для `switch` и ключей `Dictionary` без затрат на строковые поиски в рантайме.
-
-Единственный ScriptableObject `IdRegistry` сопоставляет строковые имена стабильным целочисленным ID и предоставляет полные `int ↔ string` поиски в рантайме.
-
-> Полный сэмпл — `EnemyId` / `EnemyDefinition` / `EnemySpawner` с уже настроенным реестром — поставляется в сэмпле `Ids` (Package Manager → Aspid.FastTools → Samples). Пошаговый разбор — в `TUTORIAL_RU.md` этого сэмпла.
+Сопоставляет назначаемые в ассетах имена стабильным целочисленным ID, хранящимся в ScriptableObject `IdRegistry`, с полными `int ↔ string` поисками в рантайме. Получившийся `int` подходит для `switch` и ключей `Dictionary` без затрат на сравнение строк.
-### Setup
+### Настройка
**1.** Объявите `partial struct`, реализующий `IId`. Генератор исходников автоматически добавит необходимые поля и свойство:
@@ -586,7 +403,9 @@ using Aspid.FastTools.Ids;
public partial struct EnemyId : IId { }
```
-Сгенерированный код:
+
+Сгенерированный код
+
```csharp
public partial struct EnemyId
@@ -598,7 +417,9 @@ public partial struct EnemyId
}
```
-Генератор сообщает `AFID001`, если у структуры отсутствует `partial`, и `AFID002`, если вы сами объявили `_id`, `Id` или `__stringId` (генерация пропускается — вы получаете явную ошибку с указанием на структуру вместо CS-ошибки внутри сгенерированного кода). Поддерживаются generic-структуры (`EnemyId`) и generic-контейнеры.
+
+
+Ошибки использования ловятся на этапе компиляции диагностиками генератора (`AFID001` — отсутствует `partial`, `AFID002` — конфликтующий член); generic-структуры и контейнеры поддерживаются.
**2.** Создайте ассет реестра и привяжите его к вашему типу структуры в Inspector:
- `Assets → Create → Aspid → Id Registry`
@@ -614,11 +435,6 @@ public class EnemyDefinition : ScriptableObject
{
[UniqueId] [SerializeField] private EnemyId _id;
}
-```
-
-```csharp
-using UnityEngine;
-using Aspid.FastTools.Ids;
public class EnemySpawner : MonoBehaviour
{
@@ -631,7 +447,7 @@ public class EnemySpawner : MonoBehaviour
}
```
-
+
### UniqueIdAttribute
@@ -642,25 +458,17 @@ public class EnemySpawner : MonoBehaviour
public sealed class UniqueIdAttribute : PropertyAttribute { }
```
-
+
### IdRegistry
`ScriptableObject` из `Aspid.FastTools.Ids`, хранящий записи `(int, string)` и поддерживающий таблицы поиска доступными во рантайме. Каждому имени назначается стабильный, автоинкрементный ID, который не изменяется даже при добавлении или удалении других записей.
-| Член | Описание |
-|------|----------|
-| `bool TryGetId(string name, out int id)` | Возвращает `true` и найденный ID; иначе `false` |
-| `bool TryGetName(int id, out string name)` | Возвращает `true` и найденное имя; иначе `false` и `string.Empty` |
-| `bool Contains(int id)` | Зарегистрирован ли ID |
-| `bool Contains(string name)` | Зарегистрировано ли имя |
-| `int Count` | Количество записей |
-| `IReadOnlyList Ids` · `IReadOnlyList IdNames` | Зарегистрированные ID / имена в порядке регистрации |
-| `IEnumerator> GetEnumerator()` | Итерация по парам `(id, name)` |
+Поиски в рантайме работают в обе стороны — `TryGetId` / `TryGetName`, `Contains`, итерация по парам `(id, name)`, — а генерик-аналог `IdRegistry` добавляет типизированные перегрузки. Записи добавляются, переименовываются и удаляются только через инспектор реестра, а не через публичный runtime API.
-Реестр наследуется напрямую от `ScriptableObject` и предоставляет генерик-аналог `IdRegistry` (с `T : struct, IId`), добавляющий типизированные перегрузки `Contains(T)` и `TryGetName(T, out string)`. Редактирование — добавление, переименование, удаление записей — выполняется через инспектор реестра и `RegistryEditorCore`, а не через публичный runtime API.
+
-
+> Полный справочник: [Ids.md](Ids.md)
---
@@ -668,11 +476,9 @@ public sealed class UniqueIdAttribute : PropertyAttribute { }
Fluent-методы расширения для построения UIToolkit-деревьев в коде. Все методы возвращают `T` (сам элемент) для цепочки вызовов.
-> Полный справочник по методам: [VisualElementExtensions.md](VisualElementExtensions.md)
+### Пример
-### Example
-
-Реактивный редактор для `ScriptableObject` `AbilityConfig` — заголовок и статус-пилла в шапке, тело из `PropertyField`, и Warning `HelpBox`, который переключается в зависимости от `ManaCost`.
+Реактивный редактор для `ScriptableObject` `AbilityConfig` — заголовок и статус-пилла в шапке и Warning `HelpBox`, который переключается в зависимости от `ManaCost`.
```csharp
[CustomEditor(typeof(AbilityConfig))]
@@ -684,34 +490,20 @@ internal sealed class AbilityConfigEditor : Editor
var badge = new Label()
.SetFontSize(10).SetUnityFontStyleAndWeight(FontStyle.Bold)
- .SetPaddingX(10).SetPaddingY(3)
- .SetBorderRadius(10).SetBorderWidth(1);
+ .SetPaddingX(10).SetPaddingY(3).SetBorderRadius(10).SetBorderWidth(1);
- var helpBox = new HelpBox(
- "This ability costs no mana — is that intentional?",
- HelpBoxMessageType.Warning)
+ var helpBox = new HelpBox("This ability costs no mana — is that intentional?", HelpBoxMessageType.Warning)
.SetMarginTop(8).SetBorderRadius(6);
- var manaField = new PropertyField(serializedObject.FindProperty("_manaCost"))
- .AddValueChanged(_ => Refresh());
-
Refresh();
return new VisualElement()
- .SetBorderRadius(10).SetBorderWidth(1)
+ .SetBorderRadius(10).SetBorderWidth(1).SetPaddingX(14).SetPaddingY(12)
.AddChild(new VisualElement()
.SetFlexDirection(FlexDirection.Row).SetAlignItems(Align.Center)
- .SetPaddingX(14).SetPaddingY(12)
- .AddChild(new Label(target.GetScriptName())
- .SetFlexGrow(1).SetFontSize(15)
- .SetUnityFontStyleAndWeight(FontStyle.Bold))
+ .AddChild(new Label(target.GetScriptName()).SetFlexGrow(1).SetFontSize(15))
.AddChild(badge))
- .AddChild(new VisualElement()
- .SetPaddingX(14).SetPaddingY(12)
- .AddChild(new PropertyField(serializedObject.FindProperty("_abilityName")))
- .AddChild(new PropertyField(serializedObject.FindProperty("_description")))
- .AddChild(new PropertyField(serializedObject.FindProperty("_cooldown")))
- .AddChild(manaField)
- .AddChild(helpBox));
+ .AddChild(new PropertyField(serializedObject.FindProperty("_manaCost")).AddValueChanged(_ => Refresh()))
+ .AddChild(helpBox);
void Refresh()
{
@@ -723,11 +515,11 @@ internal sealed class AbilityConfigEditor : Editor
}
```
-> Полный сэмпл — `AbilityConfig.cs`, полированный `AbilityConfigEditor.cs` (свои цвета, подзаголовок и divider — то, что на скриншоте ниже) и два `.asset`-примера — поставляется в сэмпле `VisualElements` (Package Manager → Aspid.FastTools → Samples).
+### Результат
-### Result
+
-
+> Полный справочник: [VisualElementExtensions.md](VisualElementExtensions.md)
---
@@ -746,13 +538,13 @@ property
Пакет покрывает:
- **Update / Apply** — `Update`, `UpdateIfRequiredOrScript`, `ApplyModifiedProperties`.
-- **Типизированные сеттеры** — `SetValue` (обобщённый диспетчер) и `SetXxx` для `int`/`uint`/`long`/`ulong`/`float`/`double`/`bool`/`string`/`Color`/`Gradient`/`Hash128`/`Rect`/`RectInt`/`Bounds`/`BoundsInt`/`Vector2..4` (и `Vector2/3Int`)/`Quaternion`/`AnimationCurve`/`EntityId` (Unity 6.2+). К каждому идёт парный вариант `SetXxxAndApply`.
+- **Типизированные сеттеры** — `SetValue` (обобщённый диспетчер) и `SetXxx` для всех распространённых Unity-сериализуемых типов, от примитивов до `Gradient` и `AnimationCurve` — к каждому идёт парный вариант `SetXxxAndApply`.
- **Enum-сеттеры** — `SetEnumFlag` и `SetEnumIndex` (каждый + `AndApply`).
- **Массивы** — `SetArraySize`, `AddArraySize`, `RemoveArraySize` (каждый + `AndApply`).
- **Ссылки** — `SetManagedReference`, `SetObjectReference`, `SetExposedReference`, а также `SetBoxed` (Unity 6+).
- **Рефлексионные хелперы** — `GetPropertyType`, `GetFieldInfo`, `GetDeclaringInstance` для разрешения C#-члена и runtime-экземпляра, стоящих за property.
-> Полный справочник по методам: [SerializedPropertyExtensions.md](SerializedPropertyExtensions.md)
+> Полный справочник: [SerializedPropertyExtensions.md](SerializedPropertyExtensions.md)
---
@@ -767,12 +559,6 @@ using (VerticalScope.Begin())
EditorGUILayout.LabelField("Item 2");
}
-using (HorizontalScope.Begin())
-{
- EditorGUILayout.LabelField("Left");
- EditorGUILayout.LabelField("Right");
-}
-
var scrollPos = Vector2.zero;
using (ScrollViewScope.Begin(ref scrollPos))
{
@@ -780,37 +566,18 @@ using (ScrollViewScope.Begin(ref scrollPos))
}
```
-Получить rect области через перегрузку с `out`-параметром:
-
-```csharp
-using (VerticalScope.Begin(out var rect, GUI.skin.box))
-{
- EditorGUI.DrawRect(rect, new Color(0, 0, 0, 0.1f));
- EditorGUILayout.LabelField("Boxed content");
-}
-```
-
-Все перегрузки `Begin` соответствуют сигнатурам `EditorGUILayout.Begin*` (опциональные `GUIStyle`, `GUILayoutOption[]`, параметры scroll view и т.д.).
+Все перегрузки `Begin` соответствуют сигнатурам `EditorGUILayout.Begin*` (опциональные `GUIStyle`, `GUILayoutOption[]`, параметры scroll view), плюс `out Rect`-вариант для отрисовки в rect области.
---
## Editor Helper Extensions
-Утилитарные методы для получения отображаемых имён объектов Unity в пользовательских редакторах.
-
-```csharp
-public static string GetScriptName(this Object obj)
-```
-
-Возвращает отображаемое имя объекта Unity:
-- Если тип имеет `[AddComponentMenu]`, возвращает `ObjectNames.GetInspectorTitle(obj)`
-- В противном случае возвращает `ObjectNames.NicifyVariableName(typeName)`
-
-```csharp
-public static string GetScriptNameWithIndex(this Component targetComponent)
-```
+Хелперы отображаемых имён объектов Unity для кастомных редакторов:
-Возвращает отображаемое имя с числовым суффиксом, если на одном GameObject присутствует несколько компонентов одного типа. Например, если прикреплены два компонента `AudioSource`, второй вернёт `"Audio Source (2)"`.
+| Метод | Возвращает |
+|---|---|
+| `GetScriptName()` | Отображаемое имя объекта — `ObjectNames.GetInspectorTitle`, если у типа есть `[AddComponentMenu]`, иначе «очеловеченное» имя типа |
+| `GetScriptNameWithIndex()` | То же имя с числовым суффиксом, когда на GameObject несколько компонентов одного типа — например `"Audio Source (2)"` |
```csharp
[CustomEditor(typeof(MyBehaviour))]
@@ -828,3 +595,40 @@ public class MyBehaviourEditor : Editor
}
}
```
+
+---
+
+## Claude Code Plugin
+
+Если вы используете [Claude Code](https://docs.claude.com/en/docs/claude-code), сопутствующий маркетплейс [Aspid.Claude.Plugins](https://github.com/VPDPersonal/Aspid.Claude.Plugins) поставляет плагин `aspid-fasttools` — набор скиллов, которые обучают Claude Code конвенциям и API этого пакета.
+
+> [!WARNING]
+> Плагин всё ещё находится в бета-версии — его скиллы и команды могут меняться между релизами.
+
+Добавьте маркетплейс и установите плагин:
+
+```sh
+/plugin marketplace add VPDPersonal/Aspid.Claude.Plugins
+```
+
+```sh
+/plugin install aspid-fasttools@aspid-claude-plugins
+```
+
+Включённые скиллы:
+
+- **`aspid-id-struct`** — создаёт новую `IId`-структуру и `[UniqueId]`-поля для [ID System](#id-system-beta).
+- **`aspid-profiler-marker`** — вставляет вызовы `this.Marker()` с правильной формой `using`/scope.
+- **`aspid-visual-element-fluent`** — собирает editor- или runtime-UI через fluent-расширения `VisualElement`.
+
+---
+
+## Поддержать проект
+
+Этот проект разрабатывается на добровольной основе. Если он оказался для вас полезным, поддержать его развитие можно покупкой пакета в [Unity Asset Store](https://assetstore.unity.com/packages/slug/365584) — это помогает уделять больше времени улучшению и сопровождению **Aspid.FastTools**.
+
+---
+
+## Лицензия
+
+**Aspid.FastTools** распространяется по [лицензии MIT](https://github.com/VPDPersonal/Aspid.FastTools/blob/main/LICENSE). История релизов — в [CHANGELOG](https://github.com/VPDPersonal/Aspid.FastTools/blob/main/CHANGELOG.md).
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferenceTooling.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferenceTooling.md
new file mode 100644
index 00000000..eda28ccc
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferenceTooling.md
@@ -0,0 +1,80 @@
+# SerializeReference Tooling
+
+[Селектор в Инспекторе](SerializeReferences.md) чинит ссылки по одному полю; этот
+документ — про проектный масштаб: вкладки окна FastTools для аудита и массовой починки
+managed-ссылок, страница Project Settings с гейтом на сборку плеера и та же проверка
+в headless-CI. Гейт покрывает и незаданные поля `[TypeSelector(Required = true)]` —
+см. свойство `Required` в [TypeSelectorAttribute](Types.md#typeselectorattribute).
+
+**Разделы справочника:**
+
+* [`Bulk repair tabs`](#bulk-repair-tabs) — вкладки **Asset References** и
+ **Project References** для аудита и массовой починки по всему проекту;
+* [`Project settings & the build/CI gate`](#project-settings--the-buildci-gate) —
+ настройки в Project Settings, их scope и гейт на сборку плеера;
+* [`Headless CI`](#headless-ci) — `SerializeReferenceCiGate.RunCheck` для batchmode-пайплайнов.
+
+**Краткая версия с теми же примерами — в** [README](README.md#serializereference-selector).
+
+## Bulk repair tabs
+
+[Чинить ссылки по одной](SerializeReferences.md#repairing-broken-references) необязательно:
+аудит и массовая починка вынесены в отдельные вкладки окна FastTools.
+
+| Вкладка | Назначение |
+|---|---|
+| **Asset References** (`Tools → Aspid 🐍 → FastTools → Asset References`) | Строит весь граф managed-ссылок ассета прямо из YAML — дерево по компонентам с путями полей, общими и осиротевшими ссылками, значками `MISSING` / `SHARED` и инлайн-выбором типа на каждой карточке. Достаёт потерянные ссылки, которые инспектор не показывает. |
+| **Project References** (`Tools → Aspid 🐍 → FastTools → Project References`) | `Scan Project` обходит каждый `.prefab` / `.asset` / `.unity` под `Assets/`, группирует сломанные ссылки по сохранённому типу и чинит всю группу одним `Fix all` (плюс Smart Fix). Группа, чей сохранённый тип совпадает с объявленным переименованием `[MovedFrom]`, читается как ожидающая миграция, а не поломка — один клик **Migrate all** запекает переименование в файлы, после чего атрибут можно удалить из кода. |
+
+Вкладка **Asset References** раскладывает граф managed-ссылок одного ассета по карточкам
+со значками `MISSING` / `SHARED` и инлайн-починкой:
+
+
+
+Вкладка **Project References** группирует находки всего проекта по сохранённому типу —
+одна группа чинится целиком одним `Fix all`:
+
+
+
+## Project settings & the build/CI gate
+
+**`Project Settings → Aspid FastTools → SerializeReference`** содержит:
+
+| Настройка | Scope | Что делает |
+|---|---|---|
+| **Breakage detection** | per-user | Проактивный тост + предупреждение в Console, когда ссылки заново становятся потерянными после рекомпиляции / импорта. |
+| **Auto de-alias duplicated list elements** | коммитимая | Дублированный элемент списка получает собственный экземпляр вместо совместного использования id оригинала. |
+| **Build / CI gate** | коммитимая | `Off` / `Warn` / `Fail`: при сборке плеера логировать или прерывать сборку на потерянных (а для CI — и на незаданных обязательных) managed-ссылках. |
+| **Excluded scan folders** | коммитимая | Пути, пропускаемые при всех проектных сканах. |
+
+- Коммитимые значения хранятся в `ProjectSettings/SerializeReferenceSharedSettings.asset` — закоммитьте его, чтобы команда и CI вели себя одинаково; breakage detection остаётся per-machine (`EditorPrefs`).
+- Rid colours — не настройка: общая ссылка всегда раскрашивается по id — совпадающий цвет и показывает, какие поля делят один экземпляр.
+
+Те же опции продублированы во вкладке **Settings** окна (`Tools → Aspid 🐍 → FastTools → Settings`) и на странице **`Preferences → Aspid FastTools`**, рядом с индивидуальными настройками пикера:
+
+- **Favorites** — переключатель секции.
+- **Recent items** — слайдер ёмкости (0–20; 0 скрывает секцию и приостанавливает запись, не стирая историю).
+- **Saved lists** — очищает сохранённые Favorites / Recent.
+- **Welcome** — переключатель автопоказа.
+
+Каждая строка помечена полоской scope (зелёная — коммитимые, синяя — индивидуальные); закреплённый футер предлагает **Reset to defaults** отдельно для каждого scope (сохранённые списки Favorites / Recent сброс переживают). Все поверхности зеркалят друг друга живьём.
+
+## Headless CI
+
+Для headless-CI та же проверка запускается методом `SerializeReferenceCiGate.RunCheck`:
+он сканирует проект, пишет отчёт, логирует каждое нарушение и учитывает коммитимую
+строгость гейта — `Off` пропускает проверку, `Warn` логирует, но завершается с кодом 0,
+`Fail` завершается с кодом 1 при нарушениях (код 2 — внутренняя ошибка самой проверки).
+
+```bash
+Unity -batchmode -quit -projectPath . \
+ -executeMethod Aspid.FastTools.SerializeReferences.Editors.SerializeReferenceCiGate.RunCheck \
+ -srGateReport SerializeReferenceGateReport.txt -srGateRequired
+```
+
+| Флаг | Описание |
+|---|---|
+| `-srGateReport ` | Путь файла отчёта; по умолчанию `SerializeReferenceGateReport.txt` в корне проекта. Каждое нарушение — машиночитаемая строка с типом нарушения, путём ассета и путём поля. |
+| `-srGateRequired` | Дополнительно проверяет незаданные поля `[TypeSelector(Required = true)]` в префабах, ScriptableObject и сценах (required-поля верхнего уровня, чистый YAML-проход). |
+| `-srGateWarnOnly` | Переопределяет коммитимую строгость на `Warn` для этого запуска: нарушения логируются, но код выхода 0. Выигрывает у `-srGateFail`, если переданы оба. |
+| `-srGateFail` | Переопределяет коммитимую строгость на `Fail` для этого запуска: код выхода 1 при нарушениях. |
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferenceTooling.md.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferenceTooling.md.meta
new file mode 100644
index 00000000..4ee4770c
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferenceTooling.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: dd008ed5c7d84089b652d0f774d0b877
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferences.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferences.md
new file mode 100644
index 00000000..37461baf
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferences.md
@@ -0,0 +1,94 @@
+# SerializeReference Selector
+
+Стандартный Inspector не умеет заполнять поля `[SerializeReference]`: managed-ссылку нельзя
+создать из UI, а при переименовании или удалении типа Unity молча очищает данные.
+SerializeReference Selector закрывает оба пробела: выпадающий выбор реализации прямо
+в Инспекторе плюс точечная починка сломанных ссылок у самого поля. Аудит по всему проекту,
+массовая починка и build/CI-гейт — в [SerializeReference Tooling](SerializeReferenceTooling.md).
+
+**Разделы справочника:**
+
+* [`Inspector type dropdown`](#inspector-type-dropdown) — дропдаун `[TypeSelector]`
+ на полях `[SerializeReference]`: выбор реализации, вложенный inspector, generics,
+ copy/paste;
+* [`Repairing broken references`](#repairing-broken-references) — жёлтое предупреждение
+ вместо молчаливой очистки, **Fix** / **Smart Fix** / **Make unique**.
+
+**Краткая версия с теми же примерами — в** [README](README.md#serializereference-selector).
+
+## Inspector type dropdown
+
+Добавьте `[TypeSelector]` рядом с `[SerializeReference]` — Inspector заменит стандартный
+UI managed-ссылки иерархическим [окном выбора типа](Types.md#typeselectorwindow) с поиском.
+Вы прямо в инспекторе выбираете, какая конкретная реализация типа поля будет создана;
+`` очищает ссылку.
+
+```csharp
+using System;
+using UnityEngine;
+using System.Collections.Generic;
+using Aspid.FastTools.Types;
+
+public interface IWeapon
+{
+ void Fire();
+}
+
+[Serializable]
+public sealed class Pistol : IWeapon
+{
+ [SerializeField] [Min(0)] private int _damage = 10;
+
+ public void Fire() => Debug.Log($"Pistol: {_damage} dmg");
+}
+
+public sealed class Loadout : MonoBehaviour
+{
+ [TypeSelector]
+ [SerializeReference] private IWeapon _primary;
+
+ [TypeSelector]
+ [SerializeReference] private List _sidearms;
+}
+```
+
+Атрибут существует только в редакторе (`[Conditional("UNITY_EDITOR")]`) и не несёт
+стоимости в рантайме. Работает с одиночными полями, массивами и `List`, в инспекторах
+IMGUI и UIToolkit. Тот же атрибут работает и с полями `string` и `SerializableType` —
+см. [TypeSelectorAttribute](Types.md#typeselectorattribute).
+
+
+
+| Возможность | Что делает |
+|---|---|
+| **Выбор реализации** | В списке — конкретные не-`UnityEngine.Object` классы, совместимые с типом поля. `[TypeSelector(typeof(IMelee))]` сужает его до реализаций `IMelee`. |
+| **Open generics** | `Modifier` и подобные: аргументы выводятся из закрытого generic-поля либо выбираются на второй странице селектора. |
+| **Сохранение данных** | При смене типа поля, совпадающие по имени и сериализуемой форме, переносятся, а не сбрасываются в значения по умолчанию. |
+| **Copy / Paste** | Правый клик по заголовку копирует значение и вставляет его независимым экземпляром в любое совместимое поле. |
+| **Мультивыделение** | Смешанное выделение показывает смешанное состояние dropdown; выбор или вставка применяется к каждому объекту в одной группе Undo. |
+| **Проверка компилятором** | Анализатор Roslyn: `AFT0004` (ошибка) — тип наследует `UnityEngine.Object`; `AFT0005` (предупреждение) — селектор оказался бы пустым. |
+
+Пустое поле с `[TypeSelector(Required = true)]` показывает предупреждение «required»
+в инспекторе и считается нарушением для
+[build/CI-гейта](SerializeReferenceTooling.md#project-settings--the-buildci-gate) —
+см. свойство `Required` в [TypeSelectorAttribute](Types.md#typeselectorattribute).
+
+## Repairing broken references
+
+Когда сохранённый в ассете тип перестаёт резолвиться или два поля незаметно делят
+один экземпляр, селектор не молчит — каждая проблема получает заметку в инспекторе
+и кнопку починки рядом:
+
+| Случай | Решение |
+|---|---|
+| **Потерянный тип** (переименован или удалён) | Жёлтое предупреждение вместо молчаливой очистки. Подчёркнутое **Fix** открывает селектор и переназначает тип с сохранением данных — на любой глубине, в сохранённых ассетах и прямо в Prefab Mode. |
+| **Smart Fix** | Рядом с **Fix** предлагает наиболее вероятную замену (`[MovedFrom]`, другой namespace/сборка, регистр, близкое имя) и применяет в один клик — никогда не автоматически. |
+| **Общая ссылка** (два поля делят экземпляр) | Помечается лейблом; **Make unique** расщепляет её в независимую копию. Дублирование элемента списка (Ctrl+D, `+`) больше не создаёт алиас. |
+
+
+
+
+
+Про аудит и массовую починку по всему проекту — см.
+[Bulk repair tabs](SerializeReferenceTooling.md#bulk-repair-tabs).
+
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferences.md.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferences.md.meta
new file mode 100644
index 00000000..54b5f089
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferences.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 01239ab2339240e3be2dfe322e5ccedd
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializedPropertyExtensions.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializedPropertyExtensions.md
index 2647e553..e8ef3a1a 100644
--- a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializedPropertyExtensions.md
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializedPropertyExtensions.md
@@ -1,4 +1,4 @@
-# SerializedProperty Extensions — full reference
+# SerializedProperty Extensions
Цепочные методы расширения над `SerializedProperty` для синхронизации владеющего `SerializedObject`, установки значений и рефлексии над полем-источником.
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Types.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Types.md
new file mode 100644
index 00000000..8ae56f9b
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Types.md
@@ -0,0 +1,263 @@
+# Serializable Type System
+
+В Unity нельзя сериализовать `System.Type` из коробки — **Serializable Type System** закрывает
+этот пробел: тип выбирается в Инспекторе через иерархическое окно с поиском, хранится как
+**assembly-qualified name** и лениво разрешается в `System.Type` при первом обращении.
+
+**Разделы справочника:**
+
+* [`SerializableType`](#serializabletype) — сериализуемое поле-обёртка над `System.Type`;
+* [`TypeSelectorAttribute`](#typeselectorattribute) — кнопка выбора типа у `string`,
+ `SerializableType` и `[SerializeReference]` полей, включая
+ [динамический базовый тип из поля или свойства](#dynamic-base-types-via-member-references);
+* [`TypeSelectorDisplay`](#typeselectordisplay) — имя, группа, tooltip и иконка типа-кандидата
+ в пикере;
+* [`TypeSelectorWindow`](#typeselectorwindow) — то же окно выбора как публичный API для
+ собственного editor-кода;
+* [`ComponentTypeSelector`](#componenttypeselector) — выпадающий список в Инспекторе,
+ меняющий тип компонента или ScriptableObject на подтип.
+
+**Краткая версия с теми же примерами — в** [README](README.md#serializable-type-system).
+
+## SerializableType
+
+Сериализуемая обёртка над `System.Type`: хранит выбранный тип как assembly-qualified name и лениво разрешает его в `System.Type` при первом обращении. Доступны два варианта:
+
+- **`SerializableType`** — хранит любой тип;
+- **`SerializableType`** — хранит тип, ограниченный `T` и его подклассами.
+
+Оба поддерживают неявное преобразование в `System.Type`.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public abstract class Ability : MonoBehaviour
+{
+ public abstract void Activate();
+}
+
+public sealed class AbilitySelector : MonoBehaviour
+{
+ [SerializeField] private SerializableType _abilityType;
+
+ private void Start()
+ {
+ var ability = (Ability)gameObject.AddComponent(_abilityType.Type);
+ ability.Activate();
+ }
+}
+```
+
+
+
+## TypeSelectorAttribute
+
+Добавляет к полю в Инспекторе кнопку выбора типа: она открывает иерархическое окно с поиском, в котором перечислены только типы, совместимые с указанными базовыми (при нескольких — со всеми сразу; без аргументов подходит любой тип). Что происходит при выборе, зависит от формы поля:
+
+- `string` — в поле записывается assembly-qualified имя выбранного типа;
+- `SerializableType` / `SerializableType` — сужает встроенный селектор; базовые типы атрибута пересекаются с generic-аргументом `T`;
+- managed-ссылка `[SerializeReference]` — выбранный тип сразу инстанцируется в поле (см. [SerializeReference Selector](SerializeReferences.md)).
+
+Атрибут editor-only (`[Conditional("UNITY_EDITOR")]`) и не несёт стоимости в рантайме.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public interface IStackable { }
+
+public abstract class AbilityModifier
+{
+ public abstract void Apply();
+}
+
+public sealed class AbilitySelector : MonoBehaviour
+{
+ // string — сохраняется assembly-qualified имя выбранного типа.
+ // Каждый элемент массива — отдельный picker, ограниченный AbilityModifier.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private string[] _modifierTypes;
+
+ // SerializableType — сужает picker, который у поля уже есть.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private SerializableType _modifierType;
+
+ // SerializableType — T сам сужает picker; базовые типы атрибута
+ // пересекаются с ним: подойдут только реализации AbilityModifier,
+ // которые заодно являются IStackable.
+ [TypeSelector(typeof(IStackable))]
+ [SerializeField] private SerializableType _stackableModifierType;
+
+ // Для [SerializeReference]-поля выбор типа сразу создаёт его экземпляр
+ // и записывает в поле. Атрибут без аргументов предлагает наследников
+ // типа поля (здесь — AbilityModifier). Required = true помечает
+ // незаполненное поле: предупреждение в инспекторе + нарушение CI-гейта.
+ [TypeSelector(Required = true)]
+ [SerializeReference] private AbilityModifier _modifier;
+}
+```
+
+### Конструкторы и свойства
+
+```csharp
+[Conditional("UNITY_EDITOR")]
+public sealed class TypeSelectorAttribute : PropertyAttribute
+{
+ public TypeSelectorAttribute() // базовый тип: object
+ public TypeSelectorAttribute(Type type)
+ public TypeSelectorAttribute(params Type[] types)
+ public TypeSelectorAttribute(string assemblyQualifiedName)
+ public TypeSelectorAttribute(params string[] assemblyQualifiedNames)
+
+ public TypeAllow Allow { get; set; } // по умолчанию: TypeAllow.All
+ public bool Required { get; set; } // по умолчанию: false
+}
+
+[Flags]
+public enum TypeAllow
+{
+ None = 0,
+ Abstract = 1,
+ Interface = 2,
+ All = Abstract | Interface
+}
+```
+
+| Свойство | Описание |
+|----------|----------|
+| `Allow` | Какие специальные категории типов (абстрактные классы, интерфейсы) включаются в список выбора в дополнение к обычным конкретным классам. По умолчанию: `TypeAllow.All` (поле-имя типа показывает и абстрактные классы, и интерфейсы; укажите `TypeAllow.None`, чтобы ограничить только конкретными типами). Игнорируется на managed-ссылке `[SerializeReference]` |
+| `Required` | Помечает незаполненное поле: managed reference `[SerializeReference]`, оставшийся `null`, или пустое `string`-поле показывает предупреждение «required» в инспекторе и считается нарушением для build/CI-гейта. Также покрывает поле `SerializableType` (когда сохранённое имя типа пустое). По умолчанию: `false` |
+
+#### Предупреждение Required
+
+Так выглядит пустое поле с `Required = true` в Инспекторе:
+
+
+
+Как находить и чинить такие нарушения по всему проекту из окна FastTools, а не по одному
+инспектору за раз, — см. [Bulk repair tabs](SerializeReferenceTooling.md#bulk-repair-tabs).
+
+## Dynamic base types via member references
+
+Строковые конструкторы резолвят строку **member-first**: если строка — корректный C#-идентификатор и совпадает с instance-полем или свойством того же объекта, *текущее значение* этого члена задаёт базовый тип(ы) — так одно поле ограничивает пикер другого прямо в Инспекторе, вживую. Любая другая строка трактуется как assembly-qualified имя типа (`Type.GetType`) — то, что нужно для типа, на который в месте вызова нельзя сослаться через `typeof` (за границей editor-сборки или asmdef).
+
+```csharp
+public sealed class Loadout : MonoBehaviour
+{
+ // Выбранная здесь категория управляет пикером _weaponType ниже.
+ [SerializeField] private SerializableType _category;
+
+ // Ограничен вживую тем, что сейчас лежит в _category.
+ [TypeSelector(nameof(_category))]
+ [SerializeField] private string _weaponType;
+}
+```
+
+Член должен быть instance-полем или свойством типа `Type`, `string`, `SerializableType` / `SerializableType` — либо массивом любого из них. Предпочитайте `nameof(...)`, чтобы переименование не рвало ссылку. Неизвестное имя или член неподходящего вида — это **ошибка компиляции** (правила анализатора `AFT0006`–`AFT0008`); в случаях, которые анализатор не видит (precompiled-сборки, переименование без перекомпиляции), drawer вместо этого показывает inline-предупреждение под полем.
+
+## TypeSelectorDisplay
+
+Пометьте тип-кандидат атрибутом `[TypeSelectorDisplay]`, чтобы настроить, как он показывается в селекторе — это editor-only атрибут (`[Conditional("UNITY_EDITOR")]`) в `Aspid.FastTools.Types`, не несущий стоимости в рантайме:
+
+```csharp
+using Aspid.FastTools.Types;
+
+// Переименовать тип в пикере, положить его в явную группу, задать tooltip и иконку:
+[TypeSelectorDisplay(
+ Name = "Damage ×",
+ Group = "Combat/Modifiers",
+ Tooltip = "Scales incoming damage",
+ Icon = "d_ScriptableObject Icon")]
+public sealed class DamageModifier { }
+```
+
+| Член | Описание |
+|------|----------|
+| `Name` | Отображаемое имя вместо короткого имени типа — в строках пикера и в подписи закрытого дропдауна. Поиск по-прежнему находит тип и по настоящему имени, а tooltip при наведении показывает полную идентичность `Namespace.Class, Assembly`. `null` или пробелы — без переопределения. |
+| `Group` | Явный путь в пикере, уровни разделяются `/` (например `"Combat/Melee"`). **Заменяет** размещение по namespace — тип показывается только под этим путём, сегменты пути общие для разных типов. `null` или пробелы — размещение по namespace. |
+| `Tooltip` | Tooltip, показываемый при наведении на строку типа. `null` — без переопределения tooltip. |
+| `Icon` | Иконка редактора слева от лейбла — имя `EditorGUIUtility.IconContent`, путь к ассету в проекте с расширением (загружается через `AssetDatabase`) или путь к текстуре в `Resources` без расширения. `null` — без иконки. |
+
+В пикере `DamageModifier` из примера выше показывается в группе `Combat/Modifiers` как «Damage ×» со своей иконкой — рядом с типами, сохранившими вид по умолчанию:
+
+
+
+## TypeSelectorWindow
+
+Всплывающее окно выбора типа с поиском и иерархией по пространствам имён — тот же пикер, что открывают `[TypeSelector]` и `SerializableType`, доступный и как публичный API. Окно включает:
+
+- Иерархическую организацию по пространствам имён
+- Текстовый поиск с фильтрацией
+- Навигацию с клавиатуры (стрелки, Enter, Escape; Space — в избранное)
+- Хлебные крошки и возврат назад (стрелка ← или клик по крошке)
+- Разрешение неоднозначности для типов с одинаковыми именами из разных сборок
+- Секции **Favorites** (★ при наведении) и **Recent** (последние выборы) на корневой странице — хранятся локально для каждого проекта (`EditorPrefs`, не попадают в репозиторий), скрыты во время поиска
+- Пункт `` вверху списка и галочку ✓ у текущего значения — его строка выбирается при открытии
+- Счётчики типов у групп и заголовков секций
+- Поддержку generic-типов — выбор открытого generic ведёт через выбор его аргументов и возвращает сконструированный тип
+- Настройку Favorites/Recent (вкл/выкл, ёмкость Recent) во вкладке Settings окна SerializeReference
+
+
+
+Выбор открытого generic проходит через страницу его аргументов и возвращает сконструированный тип:
+
+
+
+> Страница аргументов показывает только типы, которые Unity умеет сериализовать как значение поля: примитивы, `enum`, `string`, ссылки на наследников `UnityEngine.Object` и классы/структуры с `[Serializable]`. Абстрактные типы, интерфейсы, открытые generic и делегаты никогда не попадают в список кандидатов. Чтобы тип стал доступен для выбора, пометьте его атрибутом `[Serializable]`.
+
+Окно доступно как публичный API — открывайте его из любого editor-кода (кастомных инспекторов, `EditorWindow`, пунктов меню), когда нужно вывести выбор типа за пределы стандартного потока `SerializableType` / `[TypeSelector]`.
+
+```csharp
+namespace Aspid.FastTools.Types.Editors
+{
+ public sealed class TypeSelectorWindow : EditorWindow
+ {
+ public static void Show(
+ Rect screenRect,
+ TypeSelectorFilter filter = default,
+ string currentAqn = "",
+ Action onSelected = null);
+ }
+}
+```
+
+| Параметр | Описание |
+|----------|----------|
+| `screenRect` | Прямоугольник в экранных координатах, к которому привязывается dropdown. |
+| `filter` | Объединяет, какие типы предлагает селектор: базовые типы (`Types`, в списке остаются только типы, совместимые со **всеми** записями; по умолчанию — `typeof(object)`), включаемые категории (`Allow`), необязательный предикат `Predicate`, дополнительные записи `AdditionalTypes` и предикат аргументов открытых генериков `ArgumentFilter`. |
+| `currentAqn` | Assembly-qualified имя текущего выбранного типа: окно сразу откроется на его уровне иерархии. Передайте `null` или пустую строку, чтобы стартовать с корня. |
+| `onSelected` | Callback с assembly-qualified именем выбранного типа или `null`, если пользователь выбрал ``. |
+
+## ComponentTypeSelector
+
+Сериализуемая структура, добавляющая в Inspector выпадающий список для смены типа объекта. Добавьте её как поле в базовый класс — при выборе подтипа редактор перезаписывает `m_Script` на `SerializedObject`, фактически превращая компонент или ScriptableObject в выбранный подтип.
+
+Список автоматически ограничивается подтипами класса, в котором объявлено поле. Дополнительная настройка не требуется.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public abstract class EnemyBase : MonoBehaviour
+{
+ [SerializeField] private ComponentTypeSelector _enemyType;
+ [SerializeField] [Min(0)] private float _health = 100f;
+
+ public abstract void Attack();
+}
+
+public sealed class FastEnemy : EnemyBase
+{
+ [SerializeField] [Min(0)] private float _speed = 25f;
+
+ public override void Attack() =>
+ Debug.Log($"Fast enemy strikes! (speed: {_speed})");
+}
+```
+
+
+
+Заметки о поведении дропдауна смены типа:
+
+- Так как сменой типа управляет сам список, встроенная строка **Script** в Inspector скрывается, пока присутствует селектор — тип меняется только через выпадающий список (только UIToolkit-инспекторы; устаревший IMGUI-инспектор рисует эту строку сам).
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Types.md.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Types.md.meta
new file mode 100644
index 00000000..8b116e2c
--- /dev/null
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Types.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 2d9105a9425548cdb64254d1e45d8bfa
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/VisualElementExtensions.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/VisualElementExtensions.md
index 5e483f5c..613e63f7 100644
--- a/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/VisualElementExtensions.md
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/VisualElementExtensions.md
@@ -1,4 +1,4 @@
-# VisualElement Extensions — full reference
+# VisualElement Extensions
Fluent-методы расширения для построения UIToolkit-деревьев в коде. Все методы возвращают `T` (сам элемент) для цепочки вызовов.
diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Ids/CLAUDE.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Ids/CLAUDE.md
index a2ebc494..b37faf85 100644
--- a/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Ids/CLAUDE.md
+++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Ids/CLAUDE.md
@@ -5,42 +5,7 @@ Runtime contracts live in `Unity/Runtime/Ids/`; struct-side boilerplate is gener
## Layout
-Mirrors the sibling `Types/` feature — `Drawers/` for IMGUI + UIToolkit drawers, `Selectors/` for the picker window, `VisualElements/` for the bound field, `Registries/` for the `IdRegistry` inspector, `Resolvers/` for the AQN → registry index.
-
-```
-Ids/
-├── Constants.cs ← USS class names + field names (single source of truth)
-├── UniqueIdIndex.cs ← project-wide [UniqueId] collision index
-├── Drawers/
-│ ├── IdStructPropertyDrawer.cs ← [CustomPropertyDrawer(typeof(IId), useForChildren:true)]
-│ ├── IdStructIMGUIPropertyDrawer.cs ← static IMGUI drawer body (mirrors TypeIMGUIPropertyDrawer)
-│ ├── IdStructUIToolkitPropertyDrawer.cs ← static UIToolkit drawer body (mirrors TypeUIToolkitPropertyDrawer)
-│ ├── IdStructDrawerContext.cs ← shared drawer DTO (label + field/declaring types + child SerializedProperties)
-│ └── IdStructDrawerHelper.cs ← caption building, int↔string sync, selection apply
-├── Selectors/
-│ └── IdSelectorWindow.cs ← dropdown picker shown by the drawer
-├── VisualElements/
-│ ├── IdField.cs ← UIToolkit field; mirrors TypeField
-│ └── InspectorIdField.cs ← Inspector-styled variant; mirrors InspectorTypeField
-├── Resolvers/
-│ ├── IdRegistryResolver.cs ← AQN → registry asset, with one-per-type enforcement
-│ └── IdRegistryResolverCacheInvalidator.cs ← AssetPostprocessor invalidating the resolver cache
-└── Registries/
- ├── RegistryEditorCore.cs ← orchestrator: SerializedObject, view-model, mutation cycle, event wiring
- ├── IdRegistryEditor.cs ← [CustomEditor(typeof(IdRegistry))]
- ├── IdRegistryEntryData.cs ← row DTO
- ├── IdRegistryValidator.cs ← IsValidName + Summarize for the Clean-up flow
- ├── CleanUpSummary.cs ← Clean-up summary DTO
- ├── AddRowValidation.cs / NextIdWarning.cs ← DTOs returned by core's validation funnels
- ├── RegistrySortMode.cs / RegistryGroupMode.cs ← enums shared between core and toolbar element
- └── VisualElements/
- ├── IdRegistryEntryVisualElement.cs ← single row VisualElement
- ├── IdRegistryToolbarVisualElement.cs ← Sort/Group enum-dropdowns, fires SortChanged/GroupChanged
- ├── IdRegistryAddRowVisualElement.cs ← input + button + error label, fires AddRequested
- ├── IdRegistryNextIdRowVisualElement.cs ← PropertyField(_nextId) + warning icon, fires ValueChanged
- ├── IdRegistryWarningVisualElement.cs ← Clean-up warning row, fires ReviewRequested
- └── IdRegistryListVisualElement.cs ← flat ListView + grouped foldouts, re-emits row events
-```
+Mirrors the sibling `Types/` feature — `Drawers/` for IMGUI + UIToolkit drawers, `Selectors/` for the picker window, `VisualElements/` for the bound field, `Registries/` for the `IdRegistry` inspector, `Resolvers/` for the AQN → registry index. The `Drawers/`, `Selectors/`, and `VisualElements/` classes mirror their `Types/` counterparts (`TypeIMGUIPropertyDrawer`, `TypeField`, `InspectorTypeField`, …). Cross-component USS classes and field names live in `Constants.cs`; `UniqueIdIndex.cs` is the project-wide `[UniqueId]` collision index.
`IdRegistryEditor` is a 5-line shell that hands its `SerializedObject` to `RegistryEditorCore`. **`IdRegistry` mutations live only in `RegistryEditorCore`** (via the `Record` → `Add`/`SetName`/`RemoveAt` → `Commit` cycle, see Storage below). UI is split into `IdRegistry*VisualElement` components that are dumb shells: they own DOM and emit events, never touch the asset's `SerializedProperty`s. Wiring (event subscriptions and `Bind` calls) happens once inside `RegistryEditorCore.Build()`. Adding new inspector behavior means: a new component if it's a UI surface, plus the wiring + handler in core. The one allowed exception is `IdRegistryNextIdRowVisualElement`, which holds a `PropertyField(_nextId)` because Unity's `PropertyField` already records its own Undo and writes through `SerializedObject` — core listens for the change to invalidate the runtime cache.
diff --git a/README.md b/README.md
index a337e2fd..3c251a41 100644
--- a/README.md
+++ b/README.md
@@ -7,30 +7,32 @@
-**Aspid.FastTools** is a set of tools designed to minimize routine code writing in Unity: `SerializeReference` tooling (an inspector type picker plus a reference explorer window), Roslyn-powered source generators, and a collection of runtime and editor utilities — from a serializable `System.Type` to fluent UI Toolkit extensions.
+[English](README.md) | [Русский](README_RU.md)
-### \[[Unity Asset Store](https://assetstore.unity.com/packages/slug/365584)\] \[[Donate](#donate)\]
+**Aspid.FastTools** is a Unity toolset that eliminates routine boilerplate. Inside: a convenient `SerializeReference` workflow (an inspector type picker and a project-wide reference audit window), Roslyn source generators and analyzers, and runtime and editor utilities — from a serializable `System.Type` to fluent UI Toolkit extensions.
## Table of Contents
- **Getting Started**
- - [Integration](#integration)
- - [Claude Code Plugin](#claude-code-plugin)
- - [Donate](#donate)
+ - [Installation](#installation) — UPM git URL, `.unitypackage`, Asset Store
- **Features**
- - [ProfilerMarker](#profilermarker)
- - [Serializable Type System](#serializable-type-system)
- - [SerializeReference Selector](#serializereference-selector)
- - [Enum System](#enum-system)
- - [ID System (Beta)](#id-system-beta)
- - [VisualElement Extensions](#visualelement-extensions)
- - [SerializedProperty Extensions](#serializedproperty-extensions)
- - [IMGUI Layout Scopes](#imgui-layout-scopes)
- - [Editor Helper Extensions](#editor-helper-extensions)
+ - [Serializable Type System](#serializable-type-system) — `System.Type` as a serialized field, with a searchable type-picker window
+ - [SerializeReference Selector](#serializereference-selector) — a type-picker dropdown for `[SerializeReference]` fields, plus tooling that finds and repairs broken references
+ - [ProfilerMarker](#profilermarker) — source-generated, per-call-site profiler markers
+ - [Enum System](#enum-system) — serializable enum → value maps, `[Flags]`-aware
+ - [ID System (Beta)](#id-system-beta) — asset-assignable names mapped to stable integer IDs
+ - [VisualElement Extensions](#visualelement-extensions) — fluent UI Toolkit tree building in code
+ - [SerializedProperty Extensions](#serializedproperty-extensions) — chainable typed setters and reflection helpers
+ - [IMGUI Layout Scopes](#imgui-layout-scopes) — disposable `Begin*`/`End*` wrappers with `Rect` access
+ - [Editor Helper Extensions](#editor-helper-extensions) — display names for scripts in custom editors
+- **Extras**
+ - [Claude Code Plugin](#claude-code-plugin) — skills that teach Claude Code this package
+ - [Donate](#donate)
+ - [License](#license)
---
-## Integration
+## Installation
Install Aspid.FastTools via UPM: in the Package Manager click **+ → Install package from git URL…** and paste one of the URLs below.
@@ -61,115 +63,14 @@ The `upm-preview` branch always points to the latest **preview** release (rc, be
https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview
```
-To install a specific preview version, target the immutable per-release tag (see [Releases](https://github.com/VPDPersonal/Aspid.FastTools/releases) for the list of available versions):
-
-```
-https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview/1.0.0-rc.2
-```
-
-
-
----
+Specific preview versions use the same per-release tag scheme:
-## Claude Code Plugin
-
-If you use [Claude Code](https://docs.claude.com/en/docs/claude-code), the companion [Aspid.Claude.Plugins](https://github.com/VPDPersonal/Aspid.Claude.Plugins) marketplace ships the `aspid-fasttools` plugin — a set of skills that teach Claude Code this package's conventions and APIs.
-
-> [!WARNING]
-> The plugin is still in beta — its skills and commands may change between releases.
-
-Add the marketplace and install the plugin:
-
-```sh
-/plugin marketplace add VPDPersonal/Aspid.Claude.Plugins
-```
-
-```sh
-/plugin install aspid-fasttools@aspid-claude-plugins
-```
-
-Included skills:
-
-- **`aspid-id-struct`** — scaffold a new `IId` struct and `[UniqueId]` fields for the [ID System](#id-system-beta).
-- **`aspid-profiler-marker`** — insert `this.Marker()` call sites with the right `using`/scope shape.
-- **`aspid-visual-element-fluent`** — build editor or runtime UI using the fluent `VisualElement` extensions.
-
----
-
-## Donate
-
-This project is developed on a voluntary basis. If you find it useful, you can support its development financially. This helps allocate more time to improving and maintaining **Aspid.FastTools**.
-
-You can donate via the following platforms:
-* \[[Unity Asset Store](https://assetstore.unity.com/packages/slug/365584)\]
-
----
-
-## ProfilerMarker
-
-Provides source-generated `ProfilerMarker` registration. The generator creates a static marker per call-site, identified by the calling method and line number.
-
-```csharp
-using UnityEngine;
-
-public class MyBehaviour : MonoBehaviour
-{
- private void Update()
- {
- DoSomething1();
- DoSomething2();
- }
-
- private void DoSomething1()
- {
- using var _ = this.Marker();
- // Some code
- }
-
- private void DoSomething2()
- {
- using (this.Marker())
- {
- // Some code
- using var _ = this.Marker().WithName("Calculate");
- // Some code
- }
- }
-}
```
-
-
-Generated code
-
-
-```csharp
-using Unity.Profiling;
-using System.Runtime.CompilerServices;
-
-internal static class __MyBehaviourProfilerMarkerExtensions
-{
- private static readonly ProfilerMarker DoSomething1_Marker_Line_13 = new("MyBehaviour.DoSomething1 (13)");
- private static readonly ProfilerMarker DoSomething2_Marker_Line_19 = new("MyBehaviour.DoSomething2 (19)");
- private static readonly ProfilerMarker DoSomething2_Marker_Line_22 = new("MyBehaviour.Calculate (22)");
-
- public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
- {
-#if ENABLE_PROFILER
- if (line is 13) return DoSomething1_Marker_Line_13.Auto();
- if (line is 19) return DoSomething2_Marker_Line_19.Auto();
- if (line is 22) return DoSomething2_Marker_Line_22.Auto();
-#endif
- return default;
- }
-}
+https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview/1.0.0-rc.5
```
-### Result
-
-
-
---
## Serializable Type System
@@ -180,7 +81,7 @@ Allows serializing a `System.Type` reference in the Unity Inspector. The selecte
Two variants are available:
-- **`SerializableType`** — stores any type (base type is `object`)
+- **`SerializableType`** — stores any type
- **`SerializableType`** — stores a type constrained to `T` or its subclasses
Both support implicit conversion to `System.Type`.
@@ -205,45 +106,24 @@ public sealed class AbilitySelector : MonoBehaviour
}
}
```
-
+
### TypeSelectorAttribute
-An editor-only `PropertyAttribute` that restricts the type selection popup to specific base types. Applied to a `string` field (storing an assembly-qualified type name), a `SerializableType` / `SerializableType` field, or a `[SerializeReference]` managed-reference field. On a `SerializableType` the attribute's base types are intersected with the generic argument `T`.
+Adds a type-picker button next to a field in the Inspector: it opens a hierarchical, searchable window listing only the types compatible with the given base types (with several bases, a candidate must satisfy all of them; with no arguments, any type qualifies). What picking a type does depends on the field shape:
-```csharp
-[Conditional("UNITY_EDITOR")]
-public sealed class TypeSelectorAttribute : PropertyAttribute
-{
- public TypeSelectorAttribute() // base type: object
- public TypeSelectorAttribute(Type type)
- public TypeSelectorAttribute(params Type[] types)
- public TypeSelectorAttribute(string assemblyQualifiedName)
- public TypeSelectorAttribute(params string[] assemblyQualifiedNames)
-
- public TypeAllow Allow { get; set; } // default: TypeAllow.All
- public bool Required { get; set; } // default: false
-}
+- `string` — the field receives the assembly-qualified name of the chosen type;
+- `SerializableType` / `SerializableType` — narrows the built-in selector; the attribute's base types are intersected with the generic argument `T`;
+- a `[SerializeReference]` managed reference — the chosen type is instantiated into the field right away (see [SerializeReference Selector](#serializereference-selector)).
-[Flags]
-public enum TypeAllow
-{
- None = 0,
- Abstract = 1,
- Interface = 2,
- All = Abstract | Interface
-}
-```
-
-| Property | Description |
-|----------|-------------|
-| `Allow` | Which special type categories (abstract classes, interfaces) the picker includes in addition to plain concrete classes. Default: `TypeAllow.All` (a type-name field lists abstract classes and interfaces too; set `TypeAllow.None` to restrict it to concrete types). Ignored on a `[SerializeReference]` managed reference |
-| `Required` | Flags an unset field: a `[SerializeReference]` managed reference left `null`, or a `string` field left empty, shows an inline "required" warning in the Inspector and counts as a violation for the build/CI gate. Also covers a `SerializableType` field (its stored type name left empty). Default: `false` |
+The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no runtime cost.
```csharp
using UnityEngine;
using Aspid.FastTools.Types;
+public interface IStackable { }
+
public abstract class AbilityModifier
{
public abstract void Apply();
@@ -251,58 +131,36 @@ public abstract class AbilityModifier
public sealed class AbilitySelector : MonoBehaviour
{
+ // string — stores the assembly-qualified name of the chosen type.
// Each element of the array is its own picker constrained to AbilityModifier.
[TypeSelector(typeof(AbilityModifier))]
[SerializeField] private string[] _modifierTypes;
-}
-```
-
-> The complete sample — `Ability` / `AbilitySelector` / `EnemyBase` and their subclasses — ships in the `Types` sample (Package Manager → Aspid.FastTools → Samples).
-
-#### Dynamic base types via member references
-
-The string constructors resolve **member-first**: when the string is a valid C# identifier that matches an instance field or property on the same object, that member's *current value* supplies the base type(s) — so one field can constrain another's picker, live in the Inspector. Any other string is treated as an assembly-qualified type name (`Type.GetType`), which is what you need for a type the call site cannot reference with `typeof` (across an editor or asmdef boundary).
-
-```csharp
-public sealed class Loadout : MonoBehaviour
-{
- // The category chosen here drives the picker of _weaponType below.
- [SerializeField] private SerializableType _category;
- // Constrained live to whatever _category currently holds.
- [TypeSelector(nameof(_category))]
- [SerializeField] private string _weaponType;
+ // SerializableType — narrows the picker the field already has.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private SerializableType _modifierType;
+
+ // SerializableType — T already narrows the picker on its own; the base
+ // types of the attribute intersect with it: only AbilityModifier
+ // implementations that are also IStackable qualify.
+ [TypeSelector(typeof(IStackable))]
+ [SerializeField] private SerializableType _stackableModifierType;
+
+ // For a [SerializeReference] field picking a type immediately creates
+ // an instance and assigns it to the field. With no arguments the attribute
+ // offers subtypes of the field's own type (here — AbilityModifier).
+ // Required = true flags an unset field: an inspector warning
+ // plus a violation for the build/CI gate.
+ [TypeSelector(Required = true)]
+ [SerializeReference] private AbilityModifier _modifier;
}
```
-A referenced member may be a `Type`, `Type[]`, `string`, `string[]`, or a `SerializableType` / `SerializableType` (and arrays of these). Prefer `nameof(...)` so a rename keeps the link. Misuse — an unknown member name, or a member of an unusable type — is a **compile error** (analyzer rules `AFT0006`–`AFT0008`); for cases the analyzer cannot see (precompiled assemblies, a member renamed after compilation) the drawer shows a quiet inline warning below the field instead.
-
-Decorate a candidate type with `[TypeSelectorDisplay]` to tune how it appears in the picker — an editor-only attribute (`[Conditional("UNITY_EDITOR")]`) in `Aspid.FastTools.Types` that carries no runtime cost:
-
-```csharp
-using Aspid.FastTools.Types;
-
-// Rename the type in the picker, place it under an explicit group, give it a tooltip and an icon:
-[TypeSelectorDisplay(
- Name = "Damage ×",
- Group = "Combat/Modifiers",
- Tooltip = "Scales incoming damage",
- Icon = "d_ScriptableObject Icon")]
-public sealed class DamageModifier { }
-```
-
-| Member | Description |
-|--------|-------------|
-| `Name` | Display name shown instead of the type's short name — in the picker rows and in the closed dropdown's caption. Search still matches the real type name too, and the hover tooltip keeps revealing the full `Namespace.Class, Assembly` identity. `null` or whitespace means no override. |
-| `Group` | Explicit picker path with `/` separating levels (e.g. `"Combat/Melee"`). **Replaces** the type's namespace placement — the type appears only under this path, and path segments are shared between types. `null` or whitespace keeps the namespace placement. |
-| `Tooltip` | Tooltip shown when hovering the type's row. `null` means no tooltip override. |
-| `Icon` | Editor icon shown left of the label — an `EditorGUIUtility.IconContent` name, a project-relative asset path with extension (loaded via `AssetDatabase`), or a `Resources` texture path without extension. `null` means no icon. |
-
----
+Beyond the base types, the attribute exposes `Allow` (whether abstract classes and interfaces are listed) and `Required` (an unset field — an inline warning and a violation for the build/CI gate). Covered separately in the reference: [dynamic base types via member references](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Types.md#dynamic-base-types-via-member-references) and tuning a candidate type's look in the picker with [`[TypeSelectorDisplay]`](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Types.md#typeselectordisplay).
-### Type Selector Window
+### TypeSelectorWindow
-The Inspector shows a button that opens a searchable popup window with:
+A searchable, namespace-hierarchical type-picker popup — the same picker opened by `[TypeSelector]` and `SerializableType`, also available as a public API. The window offers:
- Hierarchical namespace organization
- Text search with filtering
@@ -315,30 +173,13 @@ The Inspector shows a button that opens a searchable popup window with:
- Generic type support — picking an open generic walks through its type parameters and emits the constructed type
- Favorites/Recent tuning (on/off, Recent capacity) in the Settings tab of the SerializeReference window
-
+
-The same window is available as a public API — open it from any editor code (custom inspectors, `EditorWindow`, menu items) when you need a type picker outside the standard `SerializableType` / `[TypeSelector]` flow.
+Picking an open generic walks through its argument page and returns the constructed type:
-```csharp
-namespace Aspid.FastTools.Types.Editors
-{
- public sealed class TypeSelectorWindow : EditorWindow
- {
- public static void Show(
- Rect screenRect,
- TypeSelectorFilter filter = default,
- string currentAqn = "",
- Action onSelected = null);
- }
-}
-```
+
-| Parameter | Description |
-|-----------|-------------|
-| `screenRect` | Screen-space rectangle the dropdown is anchored to. |
-| `filter` | Bundles which types the selector offers: base types (`Types`, only types assignable to **all** entries are listed; defaults to `typeof(object)`), the included kinds (`Allow`), an optional per-type `Predicate`, verbatim `AdditionalTypes`, and the open-generic `ArgumentFilter`. |
-| `currentAqn` | Assembly-qualified name of the currently selected type, used to pre-navigate to its location. Pass `null` or empty to start at the root. |
-| `onSelected` | Callback invoked with the assembly-qualified name of the selected type, or `null` if the user chose ``. |
+The window is available as a public API (`TypeSelectorWindow.Show`) — open it from any editor code (custom inspectors, `EditorWindow`, menu items) when you need a type picker outside the standard `SerializableType` / `[TypeSelector]` flow. Signature and parameters: [Types.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Types.md#typeselectorwindow).
### ComponentTypeSelector
@@ -346,8 +187,6 @@ A serializable struct that renders a type-switching dropdown in the Inspector. A
The dropdown is automatically constrained to subtypes of the class that declares the field. No additional configuration is required.
-Because the dropdown owns type-switching, the Inspector's built-in **Script** row is hidden while the selector is present — you change the type only through the dropdown (UIToolkit inspectors only; the legacy IMGUI inspector draws that row itself).
-
```csharp
using UnityEngine;
using Aspid.FastTools.Types;
@@ -367,24 +206,21 @@ public sealed class FastEnemy : EnemyBase
public override void Attack() =>
Debug.Log($"Fast enemy strikes! (speed: {_speed})");
}
+```
-public sealed class TankEnemy : EnemyBase
-{
- [SerializeField] [Min(0)] private float _armor = 50f;
+
- public override void Attack() =>
- Debug.Log($"Tank attacks! (armor: {_armor})");
-}
-```
+Notes on the type-switching dropdown's behavior:
+
+- Because the dropdown owns type-switching, the Inspector's built-in **Script** row is hidden while the selector is present — you change the type only through the dropdown (UIToolkit inspectors only; the legacy IMGUI inspector draws that row itself).
-
-
+> Full reference: [Types.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Types.md)
---
## SerializeReference Selector
-A drop-in type-picker dropdown for `[SerializeReference]` fields. Add `[TypeSelector]` next to `[SerializeReference]` and the Inspector replaces the default managed-reference UI with the same searchable, hierarchical picker used by `SerializableType`. You choose which concrete implementation of the field's type is instantiated, right in the Inspector; `` clears the reference.
+A drop-in type-picker dropdown for `[SerializeReference]` fields. Add `[TypeSelector]` next to `[SerializeReference]` and the Inspector replaces the default managed-reference UI with a searchable, hierarchical [type-picker window](#typeselectorwindow). You choose which concrete implementation of the field's type is instantiated, right in the Inspector; `` clears the reference.
```csharp
using System;
@@ -405,14 +241,6 @@ public sealed class Pistol : IWeapon
public void Fire() => Debug.Log($"Pistol: {_damage} dmg");
}
-[Serializable]
-public sealed class Railgun : IWeapon
-{
- [SerializeField] [Min(0)] private float _chargeTime = 1.5f;
-
- public void Fire() => Debug.Log($"Railgun charged for {_chargeTime}s");
-}
-
public sealed class Loadout : MonoBehaviour
{
[SerializeReference] [TypeSelector]
@@ -423,7 +251,7 @@ public sealed class Loadout : MonoBehaviour
}
```
-The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no runtime cost. It works on single fields, arrays, and `List`, in both IMGUI and UIToolkit inspectors.
+The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no runtime cost. It works on single fields, arrays, and `List`, in both IMGUI and UIToolkit inspectors. The same attribute also drives `string` and `SerializableType` fields — see [TypeSelectorAttribute](#typeselectorattribute).
### Capabilities
@@ -439,51 +267,74 @@ The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no ru
### Repairing broken references
-| Case | Fix |
-|---|---|
-| **Missing type** (renamed or deleted) | A yellow notice instead of a silent clear. The underlined **Fix** opens the picker and re-points the type while keeping its data — at any depth, in saved assets and live in Prefab Mode. |
-| **Smart Fix** | Next to **Fix**, suggests the most likely replacement (`[MovedFrom]`, a different namespace/assembly, casing, a near-miss name) and applies it in one click — never automatically. |
-| **Shared reference** (two fields share one instance) | Flagged with a notice; **Make unique** splits it into an independent copy. Duplicating a list element (Ctrl+D, `+`) no longer aliases the reference. |
+A missing type (renamed or deleted) shows a yellow notice instead of a silent clear: **Fix** re-points the type while keeping its data, and **Smart Fix** suggests the most likely replacement (`[MovedFrom]`, a moved namespace, a near-miss name) — applied in one click, never automatically. A shared reference (two fields holding one instance) is flagged and split with **Make unique**. Bulk repair lives in the **Asset References** and **Project References** tabs (`Tools → Aspid 🐍 → FastTools`): the first maps an asset's whole managed-reference graph from its YAML, the second scans every `.prefab` / `.asset` / `.unity` in the project, fixes broken references group-by-group and bakes `[MovedFrom]` renames into the files.
-
+### Project settings & the build/CI gate
-Bulk repair lives in two dedicated tabs:
+**`Project Settings → Aspid FastTools → SerializeReference`** controls breakage detection, auto de-aliasing of duplicated list elements, excluded scan folders, and the build/CI gate (`Off` / `Warn` / `Fail`) — committed values live in `ProjectSettings/SerializeReferenceSharedSettings.asset`, so teammates and CI behave identically. The same options are mirrored in the window's **Settings** tab and at **`Preferences → Aspid FastTools`**; headless CI runs the same check via `SerializeReferenceCiGate.RunCheck`.
-| Tab | Purpose |
-|---|---|
-| **Asset References** (`Tools → Aspid 🐍 → FastTools → Asset References`) | Maps an asset's whole managed-reference graph from its YAML — a per-component tree with field paths, shared and orphaned references, `MISSING` / `SHARED` badges, and an inline type dropdown on every card. Surfaces the missing references the Inspector cannot show. |
-| **Project References** (`Tools → Aspid 🐍 → FastTools → Project References`) | `Scan Project` sweeps every `.prefab` / `.asset` / `.unity` under `Assets/`, groups broken references by stored type, and rewrites a whole group with a single `Fix all` (plus Smart Fix). A group whose stored type matches a declared `[MovedFrom]` rename reads as a pending migration instead of a breakage — one **Migrate all** click bakes the rename into the files, after which the attribute can be removed from code. |
+> Full reference: [SerializeReferences.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferences.md) · window tabs, settings & CI gate: [SerializeReferenceTooling.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializeReferenceTooling.md)
-### Project settings & the build/CI gate
+---
-**`Project Settings → Aspid FastTools → SerializeReference`** exposes:
+## ProfilerMarker
-| Setting | Scope | What it does |
-|---|---|---|
-| **Breakage detection** | per-user | The proactive toast + console warning when references newly become missing after a recompile / import. |
-| **Auto de-alias duplicated list elements** | committed | A duplicated list element gets its own instance instead of sharing the original's reference id. |
-| **Build / CI gate** | committed | `Off` / `Warn` / `Fail`: at player-build time, log or abort on missing (and, for CI, unset-required) managed references. |
-| **Excluded scan folders** | committed | Paths skipped by every project scan. |
+Provides source-generated `ProfilerMarker` registration. The generator creates a static marker per call-site, identified by the calling method and line number.
-- Committed values live in `ProjectSettings/SerializeReferenceSharedSettings.asset` — commit it so teammates and CI behave identically; breakage detection stays per-machine (`EditorPrefs`).
-- Rid colours are not a setting — a shared reference is always colour-coded by id, so matching colours reveal shared instances at a glance.
+```csharp
+using UnityEngine;
-The same options are mirrored in the window's **Settings** tab (`Tools → Aspid 🐍 → FastTools → Settings`) and at **`Preferences → Aspid FastTools`**, alongside the picker's per-user preferences:
+public class MyBehaviour : MonoBehaviour
+{
+ private void DoSomething1()
+ {
+ using var _ = this.Marker();
+ // Some code
+ }
-- **Favorites** — section on/off toggle.
-- **Recent items** — capacity slider (0–20; 0 hides the section and pauses recording without wiping history).
-- **Saved lists** — clears the stored Favorites / Recent.
-- **Welcome** — auto-show toggle.
+ private void DoSomething2()
+ {
+ using (this.Marker())
+ {
+ // Some code
+ using var _ = this.Marker().WithName("Calculate");
+ // Some code
+ }
+ }
+}
+```
+
+
+Generated code
+
+
+```csharp
+using Unity.Profiling;
+using System.Runtime.CompilerServices;
+
+internal static class __MyBehaviourProfilerMarkerExtensions
+{
+ private static readonly ProfilerMarker DoSomething1_Marker_Line_7 = new("MyBehaviour.DoSomething1 (7)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_13 = new("MyBehaviour.DoSomething2 (13)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_16 = new("MyBehaviour.Calculate (16)");
-Every row carries a scope stripe (green — committed, blue — per-user); a pinned footer offers **Reset to defaults** per scope (saved Favorites / Recent lists survive a reset). All surfaces stay in live sync.
+ public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
+ {
+#if ENABLE_PROFILER
+ if (line is 7) return DoSomething1_Marker_Line_7.Auto();
+ if (line is 13) return DoSomething2_Marker_Line_13.Auto();
+ if (line is 16) return DoSomething2_Marker_Line_16.Auto();
+#endif
+ return default;
+ }
+}
+```
-For headless CI, `SerializeReferenceCiGate.RunCheck` (invoked via `-batchmode -executeMethod`) writes a report and honours the committed gate severity:
+
-- `Off` skips the check, `Warn` logs but exits 0, `Fail` exits non-zero when violations exist.
-- `-srGateRequired` also flags unset `[TypeSelector(Required = true)]` fields across prefabs, ScriptableObjects and scenes (top-level fields, pure-YAML pass).
-- `-srGateWarnOnly` / `-srGateFail` override the committed severity per run.
+### Result
-> The full sample — `Loadout` / `IWeapon` / `Modifier` and the missing-reference repair scenarios — ships in the `SerializeReferences` sample (Package Manager → Aspid.FastTools → Samples). A step-by-step walkthrough lives in that sample's `TUTORIAL.md`.
+
---
@@ -495,12 +346,7 @@ Provides serializable enum-to-value mappings configurable from the Inspector.
A serializable collection of `EnumValue` entries with a configurable default value. Implements `IEnumerable>`.
-| Member | Description |
-|--------|-------------|
-| `TValue GetValue(Enum enumValue)` | Returns the mapped value, or `_defaultValue` if not found |
-| `bool Equals(Enum, Enum)` | Equality check with proper `[Flags]` support |
-
-Supports `[Flags]` enums: `Equals` uses `HasFlag` and treats `0`-valued members correctly.
+`GetValue` returns the mapped value, falling back to the configured default when the key is missing. `[Flags]` enums are supported: matching uses `HasFlag` and treats `0`-valued members correctly.
```csharp
using System;
@@ -510,51 +356,29 @@ using Aspid.FastTools.Enums;
public enum DamageType { Physical, Fire, Ice, Poison }
[Flags]
-public enum StatusEffect
-{
- None = 0,
- Burning = 1,
- Frozen = 2,
- Slowed = 4,
- Stunned = 8,
-}
+public enum StatusEffect { None = 0, Burning = 1, Frozen = 2, Slowed = 4, Stunned = 8 }
public sealed class DamageDealer : MonoBehaviour
{
[SerializeField] private EnumValues _damageMultipliers;
- [SerializeField] private EnumValues _damageColors;
// Flag combinations (e.g. Burning | Slowed) match via HasFlag and first-hit wins,
// so list composite entries BEFORE their constituent flags.
[SerializeField] private EnumValues _speedMultipliersByStatus;
- [SerializeField] private DamageType _currentType;
- [SerializeField] private StatusEffect _activeEffects;
+ public float GetMultiplier(DamageType type) => _damageMultipliers.GetValue(type);
- private void DealDamage()
- {
- var multiplier = _damageMultipliers.GetValue(_currentType);
- var color = _damageColors.GetValue(_currentType);
- var speedMod = _speedMultipliersByStatus.GetValue(_activeEffects);
- // ...
- }
+ public float GetSpeedModifier(StatusEffect effects) => _speedMultipliersByStatus.GetValue(effects);
}
```
-
+
In the Inspector, select the enum type in the `EnumValues` header, then assign a value for each enum member. Right-click the property to open a context menu with **Populate Missing Enum Members** — it appends an entry for every enum member not yet in the list, seeded with the current Default Value.
-> The complete sample — `DamageDealer` / `DamageType` / `StatusEffect` — ships in the `EnumValues` sample (Package Manager → Aspid.FastTools → Samples).
-
### EnumValues\
The typed counterpart of `EnumValues` for the common case where the enum type is already known in code. The enum is fixed by the generic argument, so the Inspector's type picker is disabled and lookups are compile-time safe. Lookups are also boxing-free — keys are compared as cached numeric values — and `foreach` over either variant binds to a struct enumerator, so iteration does not allocate. Implements `IEnumerable>`.
-| Member | Description |
-|--------|-------------|
-| `TValue GetValue(TEnum enumValue)` | Returns the mapped value, or `_defaultValue` if not found |
-| `bool Equals(TEnum, TEnum)` | Equality check with proper `[Flags]` support |
-
```csharp
public sealed class HitEffect : MonoBehaviour
{
@@ -565,7 +389,7 @@ public sealed class HitEffect : MonoBehaviour
}
```
-Lookup semantics (including `[Flags]` handling) are identical to `EnumValues`, and the serialized layout is compatible — switching a field between the two variants keeps the existing data as long as the enum type matches.
+Lookup semantics (including `[Flags]` handling) are identical to `EnumValues`.
---
@@ -573,11 +397,7 @@ Lookup semantics (including `[Flags]` handling) are identical to `EnumValues **Beta:** the ID System is currently in beta. The public API, generated code layout and editor workflow may change in future releases.
-Maps an asset-assignable name to a stable integer ID. Use the resulting `int` in `switch` statements and `Dictionary` keys without paying for string lookups at runtime.
-
-A single `IdRegistry` ScriptableObject maps string names to stable integer IDs and provides full `int ↔ string` lookups at runtime.
-
-> The complete sample — `EnemyId` / `EnemyDefinition` / `EnemySpawner` with a pre-wired registry — ships in the `Ids` sample (Package Manager → Aspid.FastTools → Samples). A step-by-step walkthrough lives in that sample's `TUTORIAL.md`.
+Maps asset-assignable names to stable integer IDs stored in an `IdRegistry` ScriptableObject, with full `int ↔ string` lookups at runtime. Use the resulting `int` in `switch` statements and `Dictionary` keys without paying for string comparisons.
### Setup
@@ -589,7 +409,9 @@ using Aspid.FastTools.Ids;
public partial struct EnemyId : IId { }
```
-Generated code:
+
+Generated code
+
```csharp
public partial struct EnemyId
@@ -601,7 +423,9 @@ public partial struct EnemyId
}
```
-The generator reports `AFID001` if the struct is missing `partial`, and `AFID002` if your code already declares `_id`, `Id`, or `__stringId` (the generator skips emission so you get a clear error pointing at the struct rather than a CS compile error inside generated source). Generic targets (`EnemyId`) and generic containing types are supported.
+
+
+Misuse is caught at compile time by generator diagnostics (`AFID001` — missing `partial`, `AFID002` — a colliding member); generic structs and containing types are supported.
**2.** Create the registry asset and bind it to the struct type in its Inspector:
- `Assets → Create → Aspid → Id Registry`
@@ -617,11 +441,6 @@ public class EnemyDefinition : ScriptableObject
{
[UniqueId] [SerializeField] private EnemyId _id;
}
-```
-
-```csharp
-using UnityEngine;
-using Aspid.FastTools.Ids;
public class EnemySpawner : MonoBehaviour
{
@@ -634,7 +453,7 @@ public class EnemySpawner : MonoBehaviour
}
```
-
+
### UniqueIdAttribute
@@ -645,25 +464,17 @@ Marks a field as requiring a unique value across all assets of the declaring typ
public sealed class UniqueIdAttribute : PropertyAttribute { }
```
-
+
### IdRegistry
`ScriptableObject` in `Aspid.FastTools.Ids` that stores `(int, string)` entries and keeps the lookup tables available at runtime. Each name is assigned a stable, auto-incrementing ID that never changes when other entries are added or removed.
-| Member | Description |
-|--------|-------------|
-| `bool TryGetId(string name, out int id)` | Returns `true` and the ID when found; otherwise `false` |
-| `bool TryGetName(int id, out string name)` | Returns `true` and the name when found; otherwise `false` and `string.Empty` |
-| `bool Contains(int id)` | Whether an ID is registered |
-| `bool Contains(string name)` | Whether a name is registered |
-| `int Count` | Number of entries |
-| `IReadOnlyList Ids` · `IReadOnlyList IdNames` | Registered IDs / names, in registration order |
-| `IEnumerator> GetEnumerator()` | Iterate `(id, name)` pairs |
+Runtime lookups cover both directions — `TryGetId` / `TryGetName`, `Contains`, enumeration of `(id, name)` pairs — and the generic counterpart `IdRegistry` adds typed overloads. Entries are added, renamed and removed only through the registry inspector, not a public runtime API.
-The registry derives from `ScriptableObject` directly and exposes a generic counterpart `IdRegistry` (with `T : struct, IId`) that adds typed `Contains(T)` and `TryGetName(T, out string)` overloads. Edits — adding, renaming, removing entries — happen through the registry inspector and `RegistryEditorCore`, not via a public runtime API.
+
-
+> Full reference: [Ids.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/Ids.md)
---
@@ -671,11 +482,9 @@ The registry derives from `ScriptableObject` directly and exposes a generic coun
Fluent extension methods for building UIToolkit trees in code. All methods return `T` (the element itself) for chaining.
-> Full method-by-method reference: [VisualElementExtensions.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/VisualElementExtensions.md)
-
### Example
-A reactive editor for an `AbilityConfig` `ScriptableObject` — title and status pill in the header, `PropertyField` body, and a Warning `HelpBox` that toggles based on `ManaCost`.
+A reactive editor for an `AbilityConfig` `ScriptableObject` — title and status pill in the header, and a Warning `HelpBox` that toggles based on `ManaCost`.
```csharp
[CustomEditor(typeof(AbilityConfig))]
@@ -687,34 +496,20 @@ internal sealed class AbilityConfigEditor : Editor
var badge = new Label()
.SetFontSize(10).SetUnityFontStyleAndWeight(FontStyle.Bold)
- .SetPaddingX(10).SetPaddingY(3)
- .SetBorderRadius(10).SetBorderWidth(1);
+ .SetPaddingX(10).SetPaddingY(3).SetBorderRadius(10).SetBorderWidth(1);
- var helpBox = new HelpBox(
- "This ability costs no mana — is that intentional?",
- HelpBoxMessageType.Warning)
+ var helpBox = new HelpBox("This ability costs no mana — is that intentional?", HelpBoxMessageType.Warning)
.SetMarginTop(8).SetBorderRadius(6);
- var manaField = new PropertyField(serializedObject.FindProperty("_manaCost"))
- .AddValueChanged(_ => Refresh());
-
Refresh();
return new VisualElement()
- .SetBorderRadius(10).SetBorderWidth(1)
+ .SetBorderRadius(10).SetBorderWidth(1).SetPaddingX(14).SetPaddingY(12)
.AddChild(new VisualElement()
.SetFlexDirection(FlexDirection.Row).SetAlignItems(Align.Center)
- .SetPaddingX(14).SetPaddingY(12)
- .AddChild(new Label(target.GetScriptName())
- .SetFlexGrow(1).SetFontSize(15)
- .SetUnityFontStyleAndWeight(FontStyle.Bold))
+ .AddChild(new Label(target.GetScriptName()).SetFlexGrow(1).SetFontSize(15))
.AddChild(badge))
- .AddChild(new VisualElement()
- .SetPaddingX(14).SetPaddingY(12)
- .AddChild(new PropertyField(serializedObject.FindProperty("_abilityName")))
- .AddChild(new PropertyField(serializedObject.FindProperty("_description")))
- .AddChild(new PropertyField(serializedObject.FindProperty("_cooldown")))
- .AddChild(manaField)
- .AddChild(helpBox));
+ .AddChild(new PropertyField(serializedObject.FindProperty("_manaCost")).AddValueChanged(_ => Refresh()))
+ .AddChild(helpBox);
void Refresh()
{
@@ -726,11 +521,11 @@ internal sealed class AbilityConfigEditor : Editor
}
```
-> The complete sample — `AbilityConfig.cs`, the polished `AbilityConfigEditor.cs` (custom colors, subtitle and divider, used in the screenshot below) and two `.asset` examples — ships in the `VisualElements` sample (Package Manager → Aspid.FastTools → Samples).
-
### Result
-
+
+
+> Full reference: [VisualElementExtensions.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/VisualElementExtensions.md)
---
@@ -749,13 +544,13 @@ property
The package covers:
- **Update / Apply** — `Update`, `UpdateIfRequiredOrScript`, `ApplyModifiedProperties`.
-- **Typed setters** — `SetValue` (generic dispatch) and `SetXxx` for `int`/`uint`/`long`/`ulong`/`float`/`double`/`bool`/`string`/`Color`/`Gradient`/`Hash128`/`Rect`/`RectInt`/`Bounds`/`BoundsInt`/`Vector2..4` (and `Vector2/3Int`)/`Quaternion`/`AnimationCurve`/`EntityId` (Unity 6.2+). Each comes with a paired `SetXxxAndApply` variant.
+- **Typed setters** — `SetValue` (generic dispatch) and `SetXxx` for every common Unity-serializable type, from primitives to `Gradient` and `AnimationCurve` — each with a paired `SetXxxAndApply` variant.
- **Enum setters** — `SetEnumFlag` and `SetEnumIndex` (each + `AndApply`).
- **Arrays** — `SetArraySize`, `AddArraySize`, `RemoveArraySize` (each + `AndApply`).
- **References** — `SetManagedReference`, `SetObjectReference`, `SetExposedReference`, and `SetBoxed` (Unity 6+).
- **Reflection helpers** — `GetPropertyType`, `GetFieldInfo`, `GetDeclaringInstance` for resolving the C# member and runtime instance behind a property.
-> Full method-by-method reference: [SerializedPropertyExtensions.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializedPropertyExtensions.md)
+> Full reference: [SerializedPropertyExtensions.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/EN/SerializedPropertyExtensions.md)
---
@@ -770,12 +565,6 @@ using (VerticalScope.Begin())
EditorGUILayout.LabelField("Item 2");
}
-using (HorizontalScope.Begin())
-{
- EditorGUILayout.LabelField("Left");
- EditorGUILayout.LabelField("Right");
-}
-
var scrollPos = Vector2.zero;
using (ScrollViewScope.Begin(ref scrollPos))
{
@@ -783,37 +572,18 @@ using (ScrollViewScope.Begin(ref scrollPos))
}
```
-Capture the group rect with the `out`-overload when needed:
-
-```csharp
-using (VerticalScope.Begin(out var rect, GUI.skin.box))
-{
- EditorGUI.DrawRect(rect, new Color(0, 0, 0, 0.1f));
- EditorGUILayout.LabelField("Boxed content");
-}
-```
-
-All `Begin` overloads match the corresponding `EditorGUILayout.Begin*` signatures (optional `GUIStyle`, `GUILayoutOption[]`, scroll view options, etc.).
+All `Begin` overloads match the corresponding `EditorGUILayout.Begin*` signatures (optional `GUIStyle`, `GUILayoutOption[]`, scroll view options), plus an `out Rect` variant for drawing into the group's rect.
---
## Editor Helper Extensions
-Utility methods for getting display names of Unity objects in custom editors.
-
-```csharp
-public static string GetScriptName(this Object obj)
-```
-
-Returns the display name of a Unity object:
-- If the type has `[AddComponentMenu]`, returns `ObjectNames.GetInspectorTitle(obj)`
-- Otherwise returns `ObjectNames.NicifyVariableName(typeName)`
-
-```csharp
-public static string GetScriptNameWithIndex(this Component targetComponent)
-```
+Display-name helpers for Unity objects in custom editors:
-Returns the display name with a count suffix when multiple components of the same type exist on the same GameObject. For example, if two `AudioSource` components are attached, the second returns `"Audio Source (2)"`.
+| Method | Returns |
+|---|---|
+| `GetScriptName()` | The object's display name — `ObjectNames.GetInspectorTitle` when the type has `[AddComponentMenu]`, otherwise the nicified type name |
+| `GetScriptNameWithIndex()` | The same name plus a count suffix when the GameObject holds several components of the same type — e.g. `"Audio Source (2)"` |
```csharp
[CustomEditor(typeof(MyBehaviour))]
@@ -831,3 +601,40 @@ public class MyBehaviourEditor : Editor
}
}
```
+
+---
+
+## Claude Code Plugin
+
+If you use [Claude Code](https://docs.claude.com/en/docs/claude-code), the companion [Aspid.Claude.Plugins](https://github.com/VPDPersonal/Aspid.Claude.Plugins) marketplace ships the `aspid-fasttools` plugin — a set of skills that teach Claude Code this package's conventions and APIs.
+
+> [!WARNING]
+> The plugin is still in beta — its skills and commands may change between releases.
+
+Add the marketplace and install the plugin:
+
+```sh
+/plugin marketplace add VPDPersonal/Aspid.Claude.Plugins
+```
+
+```sh
+/plugin install aspid-fasttools@aspid-claude-plugins
+```
+
+Included skills:
+
+- **`aspid-id-struct`** — scaffold a new `IId` struct and `[UniqueId]` fields for the [ID System](#id-system-beta).
+- **`aspid-profiler-marker`** — insert `this.Marker()` call sites with the right `using`/scope shape.
+- **`aspid-visual-element-fluent`** — build editor or runtime UI using the fluent `VisualElement` extensions.
+
+---
+
+## Donate
+
+This project is developed on a voluntary basis. If you find it useful, you can support its development by purchasing the package on the [Unity Asset Store](https://assetstore.unity.com/packages/slug/365584) — that helps allocate more time to improving and maintaining **Aspid.FastTools**.
+
+---
+
+## License
+
+**Aspid.FastTools** is distributed under the [MIT License](LICENSE). Release history lives in the [CHANGELOG](CHANGELOG.md).
diff --git a/README_RU.md b/README_RU.md
index ce757940..4c3d0c1a 100644
--- a/README_RU.md
+++ b/README_RU.md
@@ -7,30 +7,32 @@
-**Aspid.FastTools** — набор инструментов, предназначенных для минимизации рутинного написания кода в Unity: инструменты для `SerializeReference` (выбор типа в инспекторе и окно-обозреватель ссылок), генераторы кода на базе Roslyn и подборка runtime- и editor-утилит — от сериализуемого `System.Type` до fluent-расширений UI Toolkit.
+[English](README.md) | [Русский](README_RU.md)
-### \[[Unity Asset Store](https://assetstore.unity.com/packages/slug/365584)\] \[[Donate](#donate)\]
+**Aspid.FastTools** — набор инструментов для Unity, избавляющий от рутинного бойлерплейта. Внутри — удобная работа с `SerializeReference` (выбор типа в инспекторе и окно аудита ссылок по всему проекту), Roslyn-генераторы и анализаторы, а также runtime- и editor-утилиты: от сериализуемого `System.Type` до fluent-расширений UI Toolkit.
## Содержание
-- **Getting Started**
- - [Integration](#integration)
- - [Claude Code Plugin](#claude-code-plugin)
- - [Donate](#donate)
-- **Features**
- - [ProfilerMarker](#profilermarker)
- - [Serializable Type System](#serializable-type-system)
- - [SerializeReference Selector](#serializereference-selector)
- - [Enum System](#enum-system)
- - [ID System (Beta)](#id-system-beta)
- - [VisualElement Extensions](#visualelement-extensions)
- - [SerializedProperty Extensions](#serializedproperty-extensions)
- - [IMGUI Layout Scopes](#imgui-layout-scopes)
- - [Editor Helper Extensions](#editor-helper-extensions)
+- **Начало работы**
+ - [Установка](#установка) — UPM git URL, `.unitypackage`, Asset Store
+- **Возможности**
+ - [Serializable Type System](#serializable-type-system) — `System.Type` как сериализуемое поле и окно выбора типа с поиском
+ - [SerializeReference Selector](#serializereference-selector) — выпадающий выбор типа для полей `[SerializeReference]`, а также инструменты поиска и починки битых ссылок
+ - [ProfilerMarker](#profilermarker) — source-generated маркеры профайлера, уникальные для каждого места вызова
+ - [Enum System](#enum-system) — сериализуемые отображения enum → значение с поддержкой `[Flags]`
+ - [ID System (Beta)](#id-system-beta) — назначаемые в ассетах имена со стабильными целочисленными ID
+ - [VisualElement Extensions](#visualelement-extensions) — fluent-построение UI Toolkit-деревьев в коде
+ - [SerializedProperty Extensions](#serializedproperty-extensions) — типизированные сеттеры с fluent-цепочками и рефлексионные хелперы
+ - [IMGUI Layout Scopes](#imgui-layout-scopes) — disposable-обёртки `Begin*`/`End*` с доступом к `Rect`
+ - [Editor Helper Extensions](#editor-helper-extensions) — получение отображаемых имён скриптов для кастомных редакторов
+- **Дополнительно**
+ - [Claude Code Plugin](#claude-code-plugin) — скиллы, обучающие Claude Code этому пакету
+ - [Поддержать проект](#поддержать-проект)
+ - [Лицензия](#лицензия)
---
-## Integration
+## Установка
Установите Aspid.FastTools через UPM: в Package Manager нажмите **+ → Install package from git URL…** и вставьте один из URL ниже.
@@ -61,117 +63,16 @@ https://github.com/VPDPersonal/Aspid.FastTools.git#upm/1.0.0
https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview
```
-Чтобы установить конкретную preview-версию, укажите неизменяемый per-release тег (список доступных версий — на странице [Releases](https://github.com/VPDPersonal/Aspid.FastTools/releases)):
+Конкретные preview-версии используют ту же схему per-release тегов:
```
-https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview/1.0.0-rc.2
+https://github.com/VPDPersonal/Aspid.FastTools.git#upm-preview/1.0.0-rc.5
```
---
-## Claude Code Plugin
-
-Если вы используете [Claude Code](https://docs.claude.com/en/docs/claude-code), сопутствующий маркетплейс [Aspid.Claude.Plugins](https://github.com/VPDPersonal/Aspid.Claude.Plugins) поставляет плагин `aspid-fasttools` — набор скиллов, которые обучают Claude Code конвенциям и API этого пакета.
-
-> [!WARNING]
-> Плагин всё ещё находится в бета-версии — его скиллы и команды могут меняться между релизами.
-
-Добавьте маркетплейс и установите плагин:
-
-```sh
-/plugin marketplace add VPDPersonal/Aspid.Claude.Plugins
-```
-
-```sh
-/plugin install aspid-fasttools@aspid-claude-plugins
-```
-
-Включённые скиллы:
-
-- **`aspid-id-struct`** — создаёт новую `IId`-структуру и `[UniqueId]`-поля для [ID System](#id-system-beta).
-- **`aspid-profiler-marker`** — вставляет вызовы `this.Marker()` с правильной формой `using`/scope.
-- **`aspid-visual-element-fluent`** — собирает editor- или runtime-UI через fluent-расширения `VisualElement`.
-
----
-
-## Donate
-
-Этот проект разрабатывается на добровольной основе. Если он оказался для вас полезным, вы можете поддержать его развитие финансово. Это поможет уделять больше времени улучшению и сопровождению **Aspid.FastTools**.
-
-Поддержать проект можно через следующие платформы:
-* \[[Unity Asset Store](https://assetstore.unity.com/packages/slug/365584)\]
-
----
-
-## ProfilerMarker
-
-Предоставляет регистрацию `ProfilerMarker` через source generation. Генератор создаёт статический маркер для каждого места вызова, идентифицируемый по вызывающему методу и номеру строки.
-
-```csharp
-using UnityEngine;
-
-public class MyBehaviour : MonoBehaviour
-{
- private void Update()
- {
- DoSomething1();
- DoSomething2();
- }
-
- private void DoSomething1()
- {
- using var _ = this.Marker();
- // Некоторый код
- }
-
- private void DoSomething2()
- {
- using (this.Marker())
- {
- // Некоторый код
- using var _ = this.Marker().WithName("Calculate");
- // Некоторый код
- }
- }
-}
-```
-
-
-Сгенерированный код
-
-
-```csharp
-using Unity.Profiling;
-using System.Runtime.CompilerServices;
-
-internal static class __MyBehaviourProfilerMarkerExtensions
-{
- private static readonly ProfilerMarker DoSomething1_Marker_Line_13 = new("MyBehaviour.DoSomething1 (13)");
- private static readonly ProfilerMarker DoSomething2_Marker_Line_19 = new("MyBehaviour.DoSomething2 (19)");
- private static readonly ProfilerMarker DoSomething2_Marker_Line_22 = new("MyBehaviour.Calculate (22)");
-
- public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
- {
-#if ENABLE_PROFILER
- if (line is 13) return DoSomething1_Marker_Line_13.Auto();
- if (line is 19) return DoSomething2_Marker_Line_19.Auto();
- if (line is 22) return DoSomething2_Marker_Line_22.Auto();
-#endif
- return default;
- }
-}
-```
-
-
-
-### Result
-
-
-
----
-
## Serializable Type System
Позволяет сериализовать ссылку на `System.Type` в Unity Inspector. Выбранный тип хранится как assembly-qualified name и разрешается лениво при первом обращении.
@@ -180,7 +81,7 @@ internal static class __MyBehaviourProfilerMarkerExtensions
Доступны два варианта:
-- **`SerializableType`** — хранит любой тип (базовый тип — `object`)
+- **`SerializableType`** — хранит любой тип
- **`SerializableType`** — хранит тип, ограниченный `T` или его подклассами
Оба поддерживают неявное преобразование в `System.Type`.
@@ -205,45 +106,24 @@ public sealed class AbilitySelector : MonoBehaviour
}
}
```
-
+
### TypeSelectorAttribute
-Атрибут `PropertyAttribute`, доступный только в редакторе, ограничивающий всплывающее окно выбора типа конкретными базовыми типами. Применяется к полю `string` (хранящему assembly-qualified имя типа), полю `SerializableType` / `SerializableType` или managed-reference полю `[SerializeReference]`. Для `SerializableType` базовые типы атрибута пересекаются с generic-аргументом `T`.
-
-```csharp
-[Conditional("UNITY_EDITOR")]
-public sealed class TypeSelectorAttribute : PropertyAttribute
-{
- public TypeSelectorAttribute() // базовый тип: object
- public TypeSelectorAttribute(Type type)
- public TypeSelectorAttribute(params Type[] types)
- public TypeSelectorAttribute(string assemblyQualifiedName)
- public TypeSelectorAttribute(params string[] assemblyQualifiedNames)
-
- public TypeAllow Allow { get; set; } // по умолчанию: TypeAllow.All
- public bool Required { get; set; } // по умолчанию: false
-}
+Добавляет к полю в Инспекторе кнопку выбора типа: она открывает иерархическое окно с поиском, в котором перечислены только типы, совместимые с указанными базовыми (при нескольких — со всеми сразу; без аргументов подходит любой тип). Что происходит при выборе, зависит от формы поля:
-[Flags]
-public enum TypeAllow
-{
- None = 0,
- Abstract = 1,
- Interface = 2,
- All = Abstract | Interface
-}
-```
+- `string` — в поле записывается assembly-qualified имя выбранного типа;
+- `SerializableType` / `SerializableType` — сужает встроенный селектор; базовые типы атрибута пересекаются с generic-аргументом `T`;
+- managed-ссылка `[SerializeReference]` — выбранный тип сразу инстанцируется в поле (см. [SerializeReference Selector](#serializereference-selector)).
-| Свойство | Описание |
-|----------|----------|
-| `Allow` | Какие специальные категории типов (абстрактные классы, интерфейсы) включаются в список выбора в дополнение к обычным конкретным классам. По умолчанию: `TypeAllow.All` (поле-имя типа показывает и абстрактные классы, и интерфейсы; укажите `TypeAllow.None`, чтобы ограничить только конкретными типами). Игнорируется на managed-ссылке `[SerializeReference]` |
-| `Required` | Помечает незаполненное поле: managed reference `[SerializeReference]`, оставшийся `null`, или пустое `string`-поле показывает предупреждение «required» в инспекторе и считается нарушением для build/CI-гейта. Также покрывает поле `SerializableType` (когда сохранённое имя типа пустое). По умолчанию: `false` |
+Атрибут editor-only (`[Conditional("UNITY_EDITOR")]`) и не несёт стоимости в рантайме.
```csharp
using UnityEngine;
using Aspid.FastTools.Types;
+public interface IStackable { }
+
public abstract class AbilityModifier
{
public abstract void Apply();
@@ -251,58 +131,35 @@ public abstract class AbilityModifier
public sealed class AbilitySelector : MonoBehaviour
{
+ // string — сохраняется assembly-qualified имя выбранного типа.
// Каждый элемент массива — отдельный picker, ограниченный AbilityModifier.
[TypeSelector(typeof(AbilityModifier))]
[SerializeField] private string[] _modifierTypes;
-}
-```
-
-> Полный сэмпл — `Ability` / `AbilitySelector` / `EnemyBase` и их наследники — поставляется в сэмпле `Types` (Package Manager → Aspid.FastTools → Samples).
-
-#### Динамические базовые типы через ссылки на члены
-
-Строковые конструкторы резолвят строку **member-first**: если строка — корректный C#-идентификатор и совпадает с instance-полем или свойством того же объекта, *текущее значение* этого члена задаёт базовый тип(ы) — так одно поле ограничивает пикер другого прямо в Инспекторе, вживую. Любая другая строка трактуется как assembly-qualified имя типа (`Type.GetType`) — то, что нужно для типа, на который в месте вызова нельзя сослаться через `typeof` (за границей editor-сборки или asmdef).
-
-```csharp
-public sealed class Loadout : MonoBehaviour
-{
- // Выбранная здесь категория управляет пикером _weaponType ниже.
- [SerializeField] private SerializableType _category;
- // Ограничен вживую тем, что сейчас лежит в _category.
- [TypeSelector(nameof(_category))]
- [SerializeField] private string _weaponType;
+ // SerializableType — сужает picker, который у поля уже есть.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private SerializableType _modifierType;
+
+ // SerializableType — T сам сужает picker; базовые типы атрибута
+ // пересекаются с ним: подойдут только реализации AbilityModifier,
+ // которые заодно являются IStackable.
+ [TypeSelector(typeof(IStackable))]
+ [SerializeField] private SerializableType _stackableModifierType;
+
+ // Для [SerializeReference]-поля выбор типа сразу создаёт его экземпляр
+ // и записывает в поле. Атрибут без аргументов предлагает наследников
+ // типа поля (здесь — AbilityModifier). Required = true помечает
+ // незаполненное поле: предупреждение в инспекторе + нарушение CI-гейта.
+ [TypeSelector(Required = true)]
+ [SerializeReference] private AbilityModifier _modifier;
}
```
-Член может быть `Type`, `Type[]`, `string`, `string[]` или `SerializableType` / `SerializableType` (и массивами из них). Предпочитайте `nameof(...)`, чтобы переименование не рвало ссылку. Ошибка использования — неизвестное имя члена или член неподходящего типа — это **ошибка компиляции** (правила анализатора `AFT0006`–`AFT0008`); для случаев, которые анализатор не видит (precompiled-сборки, член переименован после компиляции), drawer вместо этого показывает тихое inline-предупреждение под полем.
-
-Пометьте тип-кандидат атрибутом `[TypeSelectorDisplay]`, чтобы настроить, как он показывается в селекторе — это editor-only атрибут (`[Conditional("UNITY_EDITOR")]`) в `Aspid.FastTools.Types`, не несущий стоимости в рантайме:
+Помимо базовых типов, у атрибута есть `Allow` (показывать ли абстрактные классы и интерфейсы) и `Required` (незаполненное поле — inline-предупреждение и нарушение для build/CI-гейта). Отдельно в справочнике: [динамический базовый тип из поля или свойства](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Types.md#dynamic-base-types-via-member-references) и настройка вида типа в пикере через [`[TypeSelectorDisplay]`](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Types.md#typeselectordisplay).
-```csharp
-using Aspid.FastTools.Types;
-
-// Переименовать тип в пикере, положить его в явную группу, задать tooltip и иконку:
-[TypeSelectorDisplay(
- Name = "Damage ×",
- Group = "Combat/Modifiers",
- Tooltip = "Scales incoming damage",
- Icon = "d_ScriptableObject Icon")]
-public sealed class DamageModifier { }
-```
+### TypeSelectorWindow
-| Член | Описание |
-|------|----------|
-| `Name` | Отображаемое имя вместо короткого имени типа — в строках пикера и в подписи закрытого дропдауна. Поиск по-прежнему находит тип и по настоящему имени, а tooltip при наведении показывает полную идентичность `Namespace.Class, Assembly`. `null` или пробелы — без переопределения. |
-| `Group` | Явный путь в пикере, уровни разделяются `/` (например `"Combat/Melee"`). **Заменяет** размещение по namespace — тип показывается только под этим путём, сегменты пути общие для разных типов. `null` или пробелы — размещение по namespace. |
-| `Tooltip` | Tooltip, показываемый при наведении на строку типа. `null` — без переопределения tooltip. |
-| `Icon` | Иконка редактора слева от лейбла — имя `EditorGUIUtility.IconContent`, путь к ассету в проекте с расширением (загружается через `AssetDatabase`) или путь к текстуре в `Resources` без расширения. `null` — без иконки. |
-
----
-
-### Type Selector Window
-
-В Inspector отображается кнопка, открывающая всплывающее окно с поиском, которое включает:
+Всплывающее окно выбора типа с поиском и иерархией по пространствам имён — тот же пикер, что открывают `[TypeSelector]` и `SerializableType`, доступный и как публичный API. Окно включает:
- Иерархическую организацию по пространствам имён
- Текстовый поиск с фильтрацией
@@ -315,30 +172,13 @@ public sealed class DamageModifier { }
- Поддержку generic-типов — выбор открытого generic ведёт через выбор его аргументов и возвращает сконструированный тип
- Настройку Favorites/Recent (вкл/выкл, ёмкость Recent) во вкладке Settings окна SerializeReference
-
+
-Это же окно доступно как публичный API — открывайте его из любого editor-кода (кастомных инспекторов, `EditorWindow`, пунктов меню), когда нужно вывести выбор типа за пределами стандартного потока `SerializableType` / `[TypeSelector]`.
+Выбор открытого generic проходит через страницу его аргументов и возвращает сконструированный тип:
-```csharp
-namespace Aspid.FastTools.Types.Editors
-{
- public sealed class TypeSelectorWindow : EditorWindow
- {
- public static void Show(
- Rect screenRect,
- TypeSelectorFilter filter = default,
- string currentAqn = "",
- Action onSelected = null);
- }
-}
-```
+
-| Параметр | Описание |
-|----------|----------|
-| `screenRect` | Прямоугольник в экранных координатах, к которому привязывается dropdown. |
-| `filter` | Объединяет, какие типы предлагает селектор: базовые типы (`Types`, в списке остаются только типы, совместимые со **всеми** записями; по умолчанию — `typeof(object)`), включаемые категории (`Allow`), необязательный предикат `Predicate`, дополнительные записи `AdditionalTypes` и предикат аргументов открытых генериков `ArgumentFilter`. |
-| `currentAqn` | Assembly-qualified имя текущего выбранного типа: окно сразу откроется на его уровне иерархии. Передайте `null` или пустую строку, чтобы стартовать с корня. |
-| `onSelected` | Callback с assembly-qualified именем выбранного типа или `null`, если пользователь выбрал ``. |
+Окно доступно как публичный API (`TypeSelectorWindow.Show`) — открывайте его из любого editor-кода (кастомных инспекторов, `EditorWindow`, пунктов меню), когда нужно вывести выбор типа за пределы стандартного потока `SerializableType` / `[TypeSelector]`. Сигнатура и параметры: [Types.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Types.md#typeselectorwindow).
### ComponentTypeSelector
@@ -346,8 +186,6 @@ namespace Aspid.FastTools.Types.Editors
Список автоматически ограничивается подтипами класса, в котором объявлено поле. Дополнительная настройка не требуется.
-Так как сменой типа управляет сам список, встроенная строка **Script** в Inspector скрывается, пока присутствует селектор — тип меняется только через выпадающий список (только UIToolkit-инспекторы; устаревший IMGUI-инспектор рисует эту строку сам).
-
```csharp
using UnityEngine;
using Aspid.FastTools.Types;
@@ -367,24 +205,21 @@ public sealed class FastEnemy : EnemyBase
public override void Attack() =>
Debug.Log($"Fast enemy strikes! (speed: {_speed})");
}
+```
-public sealed class TankEnemy : EnemyBase
-{
- [SerializeField] [Min(0)] private float _armor = 50f;
+
- public override void Attack() =>
- Debug.Log($"Tank attacks! (armor: {_armor})");
-}
-```
+Заметки о поведении дропдауна смены типа:
+
+- Так как сменой типа управляет сам список, встроенная строка **Script** в Inspector скрывается, пока присутствует селектор — тип меняется только через выпадающий список (только UIToolkit-инспекторы; устаревший IMGUI-инспектор рисует эту строку сам).
-
-
+> Полный справочник: [Types.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Types.md)
---
## SerializeReference Selector
-Готовый выпадающий список выбора типа для полей `[SerializeReference]`. Добавьте `[TypeSelector]` рядом с `[SerializeReference]` — Inspector заменит стандартный UI managed-ссылки тем же иерархическим селектором с поиском, что и `SerializableType`. Вы прямо в инспекторе выбираете, какая конкретная реализация типа поля будет создана; `` очищает ссылку.
+Готовый выпадающий список выбора типа для полей `[SerializeReference]`. Добавьте `[TypeSelector]` рядом с `[SerializeReference]` — Inspector заменит стандартный UI managed-ссылки иерархическим [окном выбора типа](#typeselectorwindow) с поиском. Вы прямо в инспекторе выбираете, какая конкретная реализация типа поля будет создана; `` очищает ссылку.
```csharp
using System;
@@ -405,14 +240,6 @@ public sealed class Pistol : IWeapon
public void Fire() => Debug.Log($"Pistol: {_damage} dmg");
}
-[Serializable]
-public sealed class Railgun : IWeapon
-{
- [SerializeField] [Min(0)] private float _chargeTime = 1.5f;
-
- public void Fire() => Debug.Log($"Railgun charged for {_chargeTime}s");
-}
-
public sealed class Loadout : MonoBehaviour
{
[SerializeReference] [TypeSelector]
@@ -423,7 +250,7 @@ public sealed class Loadout : MonoBehaviour
}
```
-Атрибут существует только в редакторе (`[Conditional("UNITY_EDITOR")]`) и не несёт стоимости в рантайме. Работает с одиночными полями, массивами и `List`, в инспекторах IMGUI и UIToolkit.
+Атрибут существует только в редакторе (`[Conditional("UNITY_EDITOR")]`) и не несёт стоимости в рантайме. Работает с одиночными полями, массивами и `List`, в инспекторах IMGUI и UIToolkit. Тот же атрибут работает и с полями `string` и `SerializableType` — см. [TypeSelectorAttribute](#typeselectorattribute).
### Возможности
@@ -439,51 +266,74 @@ public sealed class Loadout : MonoBehaviour
### Починка сломанных ссылок
-| Случай | Решение |
-|---|---|
-| **Потерянный тип** (переименован или удалён) | Жёлтое предупреждение вместо молчаливой очистки. Подчёркнутое **Fix** открывает селектор и переназначает тип с сохранением данных — на любой глубине, в сохранённых ассетах и прямо в Prefab Mode. |
-| **Smart Fix** | Рядом с **Fix** предлагает наиболее вероятную замену (`[MovedFrom]`, другой namespace/сборка, регистр, близкое имя) и применяет в один клик — никогда не автоматически. |
-| **Общая ссылка** (два поля делят экземпляр) | Помечается лейблом; **Make unique** расщепляет её в независимую копию. Дублирование элемента списка (Ctrl+D, `+`) больше не создаёт алиас. |
+Потерянный тип (переименован или удалён) показывает жёлтое предупреждение вместо молчаливой очистки: **Fix** переназначает тип с сохранением данных, а **Smart Fix** предлагает наиболее вероятную замену (`[MovedFrom]`, другой namespace, близкое имя) — применяется в один клик и никогда автоматически. Общая ссылка (два поля делят один экземпляр) помечается лейблом и расщепляется через **Make unique**. Массовая починка вынесена во вкладки **Asset References** и **Project References** (`Tools → Aspid 🐍 → FastTools`): первая строит весь граф managed-ссылок ассета прямо из YAML, вторая сканирует каждый `.prefab` / `.asset` / `.unity` в проекте, чинит сломанные ссылки группами и запекает переименования `[MovedFrom]` в файлы.
-
+### Настройки проекта и build/CI gate
-Массовая починка вынесена в отдельные вкладки:
+**`Project Settings → Aspid FastTools → SerializeReference`** управляет breakage detection, авто-расщеплением дублированных элементов списков, исключёнными из сканов папками и build/CI-гейтом (`Off` / `Warn` / `Fail`) — коммитимые значения хранятся в `ProjectSettings/SerializeReferenceSharedSettings.asset`, так что команда и CI ведут себя одинаково. Те же опции продублированы во вкладке **Settings** окна и на странице **`Preferences → Aspid FastTools`**; в headless-CI ту же проверку запускает `SerializeReferenceCiGate.RunCheck`.
-| Вкладка | Назначение |
-|---|---|
-| **Asset References** (`Tools → Aspid 🐍 → FastTools → Asset References`) | Строит весь граф managed-ссылок ассета прямо из YAML — дерево по компонентам с путями полей, общими и осиротевшими ссылками, значками `MISSING` / `SHARED` и инлайн-выбором типа на каждой карточке. Достаёт потерянные ссылки, которые инспектор не показывает. |
-| **Project References** (`Tools → Aspid 🐍 → FastTools → Project References`) | `Scan Project` обходит каждый `.prefab` / `.asset` / `.unity` под `Assets/`, группирует сломанные ссылки по сохранённому типу и чинит всю группу одним `Fix all` (плюс Smart Fix). Группа, чей сохранённый тип совпадает с объявленным переименованием `[MovedFrom]`, читается как ожидающая миграция, а не поломка — один клик **Migrate all** запекает переименование в файлы, после чего атрибут можно удалить из кода. |
+> Полный справочник: [SerializeReferences.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferences.md) · вкладки окна, настройки и CI-гейт: [SerializeReferenceTooling.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializeReferenceTooling.md)
-### Настройки проекта и build/CI gate
+---
-**`Project Settings → Aspid FastTools → SerializeReference`** содержит:
+## ProfilerMarker
-| Настройка | Scope | Что делает |
-|---|---|---|
-| **Breakage detection** | per-user | Проактивный тост + предупреждение в Console, когда ссылки заново становятся потерянными после рекомпиляции / импорта. |
-| **Auto de-alias duplicated list elements** | коммитимая | Дублированный элемент списка получает собственный экземпляр вместо совместного использования id оригинала. |
-| **Build / CI gate** | коммитимая | `Off` / `Warn` / `Fail`: при сборке плеера логировать или прерывать сборку на потерянных (а для CI — и на незаданных обязательных) managed-ссылках. |
-| **Excluded scan folders** | коммитимая | Пути, пропускаемые при всех проектных сканах. |
+Предоставляет регистрацию `ProfilerMarker` через source generation. Генератор создаёт статический маркер для каждого места вызова, идентифицируемый по вызывающему методу и номеру строки.
-- Коммитимые значения хранятся в `ProjectSettings/SerializeReferenceSharedSettings.asset` — закоммитьте его, чтобы команда и CI вели себя одинаково; breakage detection остаётся per-machine (`EditorPrefs`).
-- Rid colours — не настройка: общая ссылка всегда раскрашивается по id — совпадающий цвет и показывает, какие поля делят один экземпляр.
+```csharp
+using UnityEngine;
-Те же опции продублированы во вкладке **Settings** окна (`Tools → Aspid 🐍 → FastTools → Settings`) и на странице **`Preferences → Aspid FastTools`**, рядом с индивидуальными настройками пикера:
+public class MyBehaviour : MonoBehaviour
+{
+ private void DoSomething1()
+ {
+ using var _ = this.Marker();
+ // Некоторый код
+ }
-- **Favorites** — переключатель секции.
-- **Recent items** — слайдер ёмкости (0–20; 0 скрывает секцию и приостанавливает запись, не стирая историю).
-- **Saved lists** — очищает сохранённые Favorites / Recent.
-- **Welcome** — переключатель автопоказа.
+ private void DoSomething2()
+ {
+ using (this.Marker())
+ {
+ // Некоторый код
+ using var _ = this.Marker().WithName("Calculate");
+ // Некоторый код
+ }
+ }
+}
+```
-Каждая строка помечена полоской scope (зелёная — коммитимые, синяя — индивидуальные); закреплённый футер предлагает **Reset to defaults** отдельно для каждого scope (сохранённые списки Favorites / Recent сброс переживают). Все поверхности зеркалят друг друга живьём.
+
+Сгенерированный код
+
-Для headless-CI метод `SerializeReferenceCiGate.RunCheck` (через `-batchmode -executeMethod`) пишет отчёт и учитывает коммитимую строгость гейта:
+```csharp
+using Unity.Profiling;
+using System.Runtime.CompilerServices;
-- `Off` пропускает проверку, `Warn` логирует, но завершается с кодом 0, `Fail` завершается с ненулевым кодом при нарушениях.
-- `-srGateRequired` дополнительно проверяет незаданные поля `[TypeSelector(Required = true)]` в префабах, ScriptableObject и сценах (required-поля верхнего уровня, чистый YAML-проход).
-- Per-run флаги `-srGateWarnOnly` / `-srGateFail` переопределяют коммитимую строгость.
+internal static class __MyBehaviourProfilerMarkerExtensions
+{
+ private static readonly ProfilerMarker DoSomething1_Marker_Line_7 = new("MyBehaviour.DoSomething1 (7)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_13 = new("MyBehaviour.DoSomething2 (13)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_16 = new("MyBehaviour.Calculate (16)");
-> Полный сэмпл — `Loadout` / `IWeapon` / `Modifier` и сценарии починки потерянных ссылок — поставляется в сэмпле `SerializeReferences` (Package Manager → Aspid.FastTools → Samples). Пошаговый разбор — в `TUTORIAL.md` этого сэмпла.
+ public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
+ {
+#if ENABLE_PROFILER
+ if (line is 7) return DoSomething1_Marker_Line_7.Auto();
+ if (line is 13) return DoSomething2_Marker_Line_13.Auto();
+ if (line is 16) return DoSomething2_Marker_Line_16.Auto();
+#endif
+ return default;
+ }
+}
+```
+
+
+
+### Результат
+
+
---
@@ -495,12 +345,7 @@ public sealed class Loadout : MonoBehaviour
Сериализуемая коллекция записей `EnumValue` с настраиваемым значением по умолчанию. Реализует `IEnumerable>`.
-| Член | Описание |
-|------|----------|
-| `TValue GetValue(Enum enumValue)` | Возвращает сопоставленное значение или `_defaultValue`, если не найдено |
-| `bool Equals(Enum, Enum)` | Проверка равенства с поддержкой `[Flags]` |
-
-Поддерживает `[Flags]`-перечисления: `Equals` использует `HasFlag` и корректно обрабатывает члены со значением `0`.
+`GetValue` возвращает сопоставленное значение, а при отсутствии ключа — настроенное значение по умолчанию. `[Flags]`-перечисления поддерживаются: сопоставление использует `HasFlag` и корректно обрабатывает члены со значением `0`.
```csharp
using System;
@@ -510,51 +355,29 @@ using Aspid.FastTools.Enums;
public enum DamageType { Physical, Fire, Ice, Poison }
[Flags]
-public enum StatusEffect
-{
- None = 0,
- Burning = 1,
- Frozen = 2,
- Slowed = 4,
- Stunned = 8,
-}
+public enum StatusEffect { None = 0, Burning = 1, Frozen = 2, Slowed = 4, Stunned = 8 }
public sealed class DamageDealer : MonoBehaviour
{
[SerializeField] private EnumValues _damageMultipliers;
- [SerializeField] private EnumValues _damageColors;
// Flag combinations (e.g. Burning | Slowed) match via HasFlag and first-hit wins,
// so list composite entries BEFORE their constituent flags.
[SerializeField] private EnumValues _speedMultipliersByStatus;
- [SerializeField] private DamageType _currentType;
- [SerializeField] private StatusEffect _activeEffects;
+ public float GetMultiplier(DamageType type) => _damageMultipliers.GetValue(type);
- private void DealDamage()
- {
- var multiplier = _damageMultipliers.GetValue(_currentType);
- var color = _damageColors.GetValue(_currentType);
- var speedMod = _speedMultipliersByStatus.GetValue(_activeEffects);
- // ...
- }
+ public float GetSpeedModifier(StatusEffect effects) => _speedMultipliersByStatus.GetValue(effects);
}
```
-
+
В Inspector выберите тип перечисления в заголовке `EnumValues`, затем назначьте значение для каждого члена перечисления. Нажмите правой кнопкой мыши по свойству, чтобы открыть контекстное меню с пунктом **Populate Missing Enum Members** — он добавит записи для всех отсутствующих членов перечисления, используя текущее Default Value как начальное значение.
-> Полный сэмпл — `DamageDealer` / `DamageType` / `StatusEffect` — поставляется в сэмпле `EnumValues` (Package Manager → Aspid.FastTools → Samples).
-
### EnumValues\
Типизированный вариант `EnumValues` для частого случая, когда тип перечисления уже известен в коде. Тип фиксируется generic-аргументом, поэтому выбор типа в Inspector заблокирован, а обращения проверяются на этапе компиляции. Поиск при этом не использует boxing — ключи сравниваются как закэшированные числовые значения, — а `foreach` по обоим вариантам использует struct-энумератор и не аллоцирует. Реализует `IEnumerable>`.
-| Член | Описание |
-|------|----------|
-| `TValue GetValue(TEnum enumValue)` | Возвращает сопоставленное значение или `_defaultValue`, если не найдено |
-| `bool Equals(TEnum, TEnum)` | Проверка равенства с корректной поддержкой `[Flags]` |
-
```csharp
public sealed class HitEffect : MonoBehaviour
{
@@ -565,7 +388,7 @@ public sealed class HitEffect : MonoBehaviour
}
```
-Семантика поиска (включая обработку `[Flags]`) идентична `EnumValues`, а сериализованный формат совместим — смена типа поля между двумя вариантами сохраняет существующие данные, пока тип перечисления совпадает.
+Семантика поиска (включая обработку `[Flags]`) идентична `EnumValues`.
---
@@ -573,13 +396,9 @@ public sealed class HitEffect : MonoBehaviour
> **Бета:** Система ID находится в бета-версии. Публичный API, структура генерируемого кода и редакторский UX могут измениться в будущих релизах.
-Сопоставляет имя, назначаемое в активе, со стабильным целочисленным ID. Получившийся `int` подходит для `switch` и ключей `Dictionary` без затрат на строковые поиски в рантайме.
-
-Единственный ScriptableObject `IdRegistry` сопоставляет строковые имена стабильным целочисленным ID и предоставляет полные `int ↔ string` поиски в рантайме.
-
-> Полный сэмпл — `EnemyId` / `EnemyDefinition` / `EnemySpawner` с уже настроенным реестром — поставляется в сэмпле `Ids` (Package Manager → Aspid.FastTools → Samples). Пошаговый разбор — в `TUTORIAL_RU.md` этого сэмпла.
+Сопоставляет назначаемые в ассетах имена стабильным целочисленным ID, хранящимся в ScriptableObject `IdRegistry`, с полными `int ↔ string` поисками в рантайме. Получившийся `int` подходит для `switch` и ключей `Dictionary` без затрат на сравнение строк.
-### Setup
+### Настройка
**1.** Объявите `partial struct`, реализующий `IId`. Генератор исходников автоматически добавит необходимые поля и свойство:
@@ -589,7 +408,9 @@ using Aspid.FastTools.Ids;
public partial struct EnemyId : IId { }
```
-Сгенерированный код:
+
+Сгенерированный код
+
```csharp
public partial struct EnemyId
@@ -601,7 +422,9 @@ public partial struct EnemyId
}
```
-Генератор сообщает `AFID001`, если у структуры отсутствует `partial`, и `AFID002`, если вы сами объявили `_id`, `Id` или `__stringId` (генерация пропускается — вы получаете явную ошибку с указанием на структуру вместо CS-ошибки внутри сгенерированного кода). Поддерживаются generic-структуры (`EnemyId`) и generic-контейнеры.
+
+
+Ошибки использования ловятся на этапе компиляции диагностиками генератора (`AFID001` — отсутствует `partial`, `AFID002` — конфликтующий член); generic-структуры и контейнеры поддерживаются.
**2.** Создайте ассет реестра и привяжите его к вашему типу структуры в Inspector:
- `Assets → Create → Aspid → Id Registry`
@@ -617,11 +440,6 @@ public class EnemyDefinition : ScriptableObject
{
[UniqueId] [SerializeField] private EnemyId _id;
}
-```
-
-```csharp
-using UnityEngine;
-using Aspid.FastTools.Ids;
public class EnemySpawner : MonoBehaviour
{
@@ -634,7 +452,7 @@ public class EnemySpawner : MonoBehaviour
}
```
-
+
### UniqueIdAttribute
@@ -645,25 +463,17 @@ public class EnemySpawner : MonoBehaviour
public sealed class UniqueIdAttribute : PropertyAttribute { }
```
-
+
### IdRegistry
`ScriptableObject` из `Aspid.FastTools.Ids`, хранящий записи `(int, string)` и поддерживающий таблицы поиска доступными во рантайме. Каждому имени назначается стабильный, автоинкрементный ID, который не изменяется даже при добавлении или удалении других записей.
-| Член | Описание |
-|------|----------|
-| `bool TryGetId(string name, out int id)` | Возвращает `true` и найденный ID; иначе `false` |
-| `bool TryGetName(int id, out string name)` | Возвращает `true` и найденное имя; иначе `false` и `string.Empty` |
-| `bool Contains(int id)` | Зарегистрирован ли ID |
-| `bool Contains(string name)` | Зарегистрировано ли имя |
-| `int Count` | Количество записей |
-| `IReadOnlyList Ids` · `IReadOnlyList IdNames` | Зарегистрированные ID / имена в порядке регистрации |
-| `IEnumerator> GetEnumerator()` | Итерация по парам `(id, name)` |
+Поиски в рантайме работают в обе стороны — `TryGetId` / `TryGetName`, `Contains`, итерация по парам `(id, name)`, — а генерик-аналог `IdRegistry` добавляет типизированные перегрузки. Записи добавляются, переименовываются и удаляются только через инспектор реестра, а не через публичный runtime API.
-Реестр наследуется напрямую от `ScriptableObject` и предоставляет генерик-аналог `IdRegistry` (с `T : struct, IId`), добавляющий типизированные перегрузки `Contains(T)` и `TryGetName(T, out string)`. Редактирование — добавление, переименование, удаление записей — выполняется через инспектор реестра и `RegistryEditorCore`, а не через публичный runtime API.
+
-
+> Полный справочник: [Ids.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/Ids.md)
---
@@ -671,11 +481,9 @@ public sealed class UniqueIdAttribute : PropertyAttribute { }
Fluent-методы расширения для построения UIToolkit-деревьев в коде. Все методы возвращают `T` (сам элемент) для цепочки вызовов.
-> Полный справочник по методам: [VisualElementExtensions.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/VisualElementExtensions.md)
+### Пример
-### Example
-
-Реактивный редактор для `ScriptableObject` `AbilityConfig` — заголовок и статус-пилла в шапке, тело из `PropertyField`, и Warning `HelpBox`, который переключается в зависимости от `ManaCost`.
+Реактивный редактор для `ScriptableObject` `AbilityConfig` — заголовок и статус-пилла в шапке и Warning `HelpBox`, который переключается в зависимости от `ManaCost`.
```csharp
[CustomEditor(typeof(AbilityConfig))]
@@ -687,34 +495,20 @@ internal sealed class AbilityConfigEditor : Editor
var badge = new Label()
.SetFontSize(10).SetUnityFontStyleAndWeight(FontStyle.Bold)
- .SetPaddingX(10).SetPaddingY(3)
- .SetBorderRadius(10).SetBorderWidth(1);
+ .SetPaddingX(10).SetPaddingY(3).SetBorderRadius(10).SetBorderWidth(1);
- var helpBox = new HelpBox(
- "This ability costs no mana — is that intentional?",
- HelpBoxMessageType.Warning)
+ var helpBox = new HelpBox("This ability costs no mana — is that intentional?", HelpBoxMessageType.Warning)
.SetMarginTop(8).SetBorderRadius(6);
- var manaField = new PropertyField(serializedObject.FindProperty("_manaCost"))
- .AddValueChanged(_ => Refresh());
-
Refresh();
return new VisualElement()
- .SetBorderRadius(10).SetBorderWidth(1)
+ .SetBorderRadius(10).SetBorderWidth(1).SetPaddingX(14).SetPaddingY(12)
.AddChild(new VisualElement()
.SetFlexDirection(FlexDirection.Row).SetAlignItems(Align.Center)
- .SetPaddingX(14).SetPaddingY(12)
- .AddChild(new Label(target.GetScriptName())
- .SetFlexGrow(1).SetFontSize(15)
- .SetUnityFontStyleAndWeight(FontStyle.Bold))
+ .AddChild(new Label(target.GetScriptName()).SetFlexGrow(1).SetFontSize(15))
.AddChild(badge))
- .AddChild(new VisualElement()
- .SetPaddingX(14).SetPaddingY(12)
- .AddChild(new PropertyField(serializedObject.FindProperty("_abilityName")))
- .AddChild(new PropertyField(serializedObject.FindProperty("_description")))
- .AddChild(new PropertyField(serializedObject.FindProperty("_cooldown")))
- .AddChild(manaField)
- .AddChild(helpBox));
+ .AddChild(new PropertyField(serializedObject.FindProperty("_manaCost")).AddValueChanged(_ => Refresh()))
+ .AddChild(helpBox);
void Refresh()
{
@@ -726,11 +520,11 @@ internal sealed class AbilityConfigEditor : Editor
}
```
-> Полный сэмпл — `AbilityConfig.cs`, полированный `AbilityConfigEditor.cs` (свои цвета, подзаголовок и divider — то, что на скриншоте ниже) и два `.asset`-примера — поставляется в сэмпле `VisualElements` (Package Manager → Aspid.FastTools → Samples).
+### Результат
-### Result
+
-
+> Полный справочник: [VisualElementExtensions.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/VisualElementExtensions.md)
---
@@ -749,13 +543,13 @@ property
Пакет покрывает:
- **Update / Apply** — `Update`, `UpdateIfRequiredOrScript`, `ApplyModifiedProperties`.
-- **Типизированные сеттеры** — `SetValue` (обобщённый диспетчер) и `SetXxx` для `int`/`uint`/`long`/`ulong`/`float`/`double`/`bool`/`string`/`Color`/`Gradient`/`Hash128`/`Rect`/`RectInt`/`Bounds`/`BoundsInt`/`Vector2..4` (и `Vector2/3Int`)/`Quaternion`/`AnimationCurve`/`EntityId` (Unity 6.2+). К каждому идёт парный вариант `SetXxxAndApply`.
+- **Типизированные сеттеры** — `SetValue` (обобщённый диспетчер) и `SetXxx` для всех распространённых Unity-сериализуемых типов, от примитивов до `Gradient` и `AnimationCurve` — к каждому идёт парный вариант `SetXxxAndApply`.
- **Enum-сеттеры** — `SetEnumFlag` и `SetEnumIndex` (каждый + `AndApply`).
- **Массивы** — `SetArraySize`, `AddArraySize`, `RemoveArraySize` (каждый + `AndApply`).
- **Ссылки** — `SetManagedReference`, `SetObjectReference`, `SetExposedReference`, а также `SetBoxed` (Unity 6+).
- **Рефлексионные хелперы** — `GetPropertyType`, `GetFieldInfo`, `GetDeclaringInstance` для разрешения C#-члена и runtime-экземпляра, стоящих за property.
-> Полный справочник по методам: [SerializedPropertyExtensions.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializedPropertyExtensions.md)
+> Полный справочник: [SerializedPropertyExtensions.md](Aspid.FastTools/Packages/tech.aspid.fasttools/Documentation/RU/SerializedPropertyExtensions.md)
---
@@ -770,12 +564,6 @@ using (VerticalScope.Begin())
EditorGUILayout.LabelField("Item 2");
}
-using (HorizontalScope.Begin())
-{
- EditorGUILayout.LabelField("Left");
- EditorGUILayout.LabelField("Right");
-}
-
var scrollPos = Vector2.zero;
using (ScrollViewScope.Begin(ref scrollPos))
{
@@ -783,37 +571,18 @@ using (ScrollViewScope.Begin(ref scrollPos))
}
```
-Получить rect области через перегрузку с `out`-параметром:
-
-```csharp
-using (VerticalScope.Begin(out var rect, GUI.skin.box))
-{
- EditorGUI.DrawRect(rect, new Color(0, 0, 0, 0.1f));
- EditorGUILayout.LabelField("Boxed content");
-}
-```
-
-Все перегрузки `Begin` соответствуют сигнатурам `EditorGUILayout.Begin*` (опциональные `GUIStyle`, `GUILayoutOption[]`, параметры scroll view и т.д.).
+Все перегрузки `Begin` соответствуют сигнатурам `EditorGUILayout.Begin*` (опциональные `GUIStyle`, `GUILayoutOption[]`, параметры scroll view), плюс `out Rect`-вариант для отрисовки в rect области.
---
## Editor Helper Extensions
-Утилитарные методы для получения отображаемых имён объектов Unity в пользовательских редакторах.
-
-```csharp
-public static string GetScriptName(this Object obj)
-```
-
-Возвращает отображаемое имя объекта Unity:
-- Если тип имеет `[AddComponentMenu]`, возвращает `ObjectNames.GetInspectorTitle(obj)`
-- В противном случае возвращает `ObjectNames.NicifyVariableName(typeName)`
-
-```csharp
-public static string GetScriptNameWithIndex(this Component targetComponent)
-```
+Хелперы отображаемых имён объектов Unity для кастомных редакторов:
-Возвращает отображаемое имя с числовым суффиксом, если на одном GameObject присутствует несколько компонентов одного типа. Например, если прикреплены два компонента `AudioSource`, второй вернёт `"Audio Source (2)"`.
+| Метод | Возвращает |
+|---|---|
+| `GetScriptName()` | Отображаемое имя объекта — `ObjectNames.GetInspectorTitle`, если у типа есть `[AddComponentMenu]`, иначе «очеловеченное» имя типа |
+| `GetScriptNameWithIndex()` | То же имя с числовым суффиксом, когда на GameObject несколько компонентов одного типа — например `"Audio Source (2)"` |
```csharp
[CustomEditor(typeof(MyBehaviour))]
@@ -831,3 +600,40 @@ public class MyBehaviourEditor : Editor
}
}
```
+
+---
+
+## Claude Code Plugin
+
+Если вы используете [Claude Code](https://docs.claude.com/en/docs/claude-code), сопутствующий маркетплейс [Aspid.Claude.Plugins](https://github.com/VPDPersonal/Aspid.Claude.Plugins) поставляет плагин `aspid-fasttools` — набор скиллов, которые обучают Claude Code конвенциям и API этого пакета.
+
+> [!WARNING]
+> Плагин всё ещё находится в бета-версии — его скиллы и команды могут меняться между релизами.
+
+Добавьте маркетплейс и установите плагин:
+
+```sh
+/plugin marketplace add VPDPersonal/Aspid.Claude.Plugins
+```
+
+```sh
+/plugin install aspid-fasttools@aspid-claude-plugins
+```
+
+Включённые скиллы:
+
+- **`aspid-id-struct`** — создаёт новую `IId`-структуру и `[UniqueId]`-поля для [ID System](#id-system-beta).
+- **`aspid-profiler-marker`** — вставляет вызовы `this.Marker()` с правильной формой `using`/scope.
+- **`aspid-visual-element-fluent`** — собирает editor- или runtime-UI через fluent-расширения `VisualElement`.
+
+---
+
+## Поддержать проект
+
+Этот проект разрабатывается на добровольной основе. Если он оказался для вас полезным, поддержать его развитие можно покупкой пакета в [Unity Asset Store](https://assetstore.unity.com/packages/slug/365584) — это помогает уделять больше времени улучшению и сопровождению **Aspid.FastTools**.
+
+---
+
+## Лицензия
+
+**Aspid.FastTools** распространяется по [лицензии MIT](LICENSE). История релизов — в [CHANGELOG](CHANGELOG.md).