Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8aca8ef
feat(serialize-references): add [SerializeReferenceSelector] dropdown…
VPDPersonal Jun 6, 2026
10581a0
fix(serialize-references): import UnityEngine for Rect in SerializeRe…
VPDPersonal Jun 6, 2026
0840d23
docs(serialize-references): add SerializeReferences sample
VPDPersonal Jun 6, 2026
05ce9e6
docs(serialize-references): add prefabs to SerializeReferences sample
VPDPersonal Jun 6, 2026
f463b7f
feat(serialize-references): support open generic type selection
VPDPersonal Jun 7, 2026
1237a56
docs(serialize-references): add generic Modifiers example to sample
VPDPersonal Jun 7, 2026
83baf4e
docs(serialize-references): document open generic type selection
VPDPersonal Jun 7, 2026
6653fc8
refactor(serialize-references): resolve generic arguments inline in t…
VPDPersonal Jun 7, 2026
5ffd30e
fix(serialize-references): hide compiler-generated types from the gen…
VPDPersonal Jun 7, 2026
a80db48
fix(serialize-references): align UIToolkit selector header layout
VPDPersonal Jun 8, 2026
2e1667f
feat(serialize-references): preserve data on type switch and add copy…
VPDPersonal Jun 8, 2026
675df91
feat(serialize-references): repair missing types and un-share aliased…
VPDPersonal Jun 8, 2026
c7a8b82
refactor(serialize-references): detect and repair missing types from …
VPDPersonal Jun 8, 2026
4b31066
feat(serialize-references): add Repair Missing References window
VPDPersonal Jun 8, 2026
5164103
feat(serialize-references): compact missing/shared notices and Prefab…
VPDPersonal Jun 8, 2026
6dd2f90
feat(serialize-references): resolve and repair missing types nested i…
VPDPersonal Jun 9, 2026
0ab5096
feat(serialize-references): restyle Repair window, embed type picker
VPDPersonal Jun 10, 2026
eeb48c2
docs(serialize-references): update Repair window menu path
VPDPersonal Jun 10, 2026
3804ed8
refactor(types): unify SerializeReference selector into TypeSelector
VPDPersonal Jun 9, 2026
e3c85e6
build: add analyzer submodule and ship its Roslyn DLL into the package
VPDPersonal Jun 9, 2026
a155af3
refactor(samples): migrate SlottedLoadout to [TypeSelector]
VPDPersonal Jun 10, 2026
18cf123
build: widen AFT0003, add analyzer rebuild automation, HTTPS submodule
VPDPersonal Jun 11, 2026
5b42a44
build: wire analyzer rebuild hook into PostToolUse
VPDPersonal Jun 11, 2026
7d6e215
Merge pull request #50 from VPDPersonal/refactor/unify-type-selector-…
VPDPersonal Jun 11, 2026
b3905fd
fix(analyzers): recognize [SerializeReference] fields in AFT0001
VPDPersonal Jun 11, 2026
a4ed9e9
docs(samples): rename sample description to [TypeSelector]
VPDPersonal Jun 11, 2026
f167ee9
feat(analyzers): add AFT0004 and AFT0005 usage diagnostics
VPDPersonal Jun 11, 2026
aa9aca0
feat(type-selector): add TypeSelectorItem metadata and favorites/recents
VPDPersonal Jun 11, 2026
9853f12
feat(serialize-references): rank and suggest one-click missing-type f…
VPDPersonal Jun 11, 2026
c0f9918
feat(serialize-references): add Managed References graph window
VPDPersonal Jun 11, 2026
b20464b
feat(serialize-references): support multi-object editing in TypeSelec…
VPDPersonal Jun 11, 2026
c8248e5
feat(serialize-references): add project-wide scan and bulk fix to Rep…
VPDPersonal Jun 11, 2026
40e330e
feat(serialize-references): auto-de-alias duplicated list elements
VPDPersonal Jun 11, 2026
29e05f0
feat(serialize-references): add per-rid color highlighting for shared…
VPDPersonal Jun 11, 2026
98c2ae5
docs(serialize-references): document new SerializeReference and picke…
VPDPersonal Jun 11, 2026
225572e
fix(serialize-references): address review findings
VPDPersonal Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .claude/hooks/rebuild-analyzers-on-change.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# PostToolUse hook: rebuild the Roslyn analyzer after edits inside the analyzer
# project (a git submodule), then redeploy the DLL into the Unity package.
# The submodule has no Directory.Build.targets on purpose (it stays independent
# of this repo's layout), so the copy step lives here.
#
# Path-scoped on purpose:
# - Triggers ONLY for *.cs under Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/
# - Skips the Tests and Sample projects.
#
# Build success -> exit 0 (silent).
# Path mismatch -> exit 0 (silent).
# Build failure -> exit 2 with stderr piped through, so the assistant sees it.

set -uo pipefail

file_path=$(jq -r '.tool_input.file_path // empty' 2>/dev/null)

case "$file_path" in
*/Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/*.cs) ;;
*) exit 0 ;;
esac

cd "$CLAUDE_PROJECT_DIR" || exit 0

dotnet build \
Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers.csproj \
-c Release --nologo -v quiet 1>&2 || exit 2

cp Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/bin/Release/netstandard2.0/Aspid.FastTools.Analyzers.dll \
Aspid.FastTools/Packages/tech.aspid.fasttools/Aspid.FastTools.Analyzers.dll 1>&2 || exit 2
4 changes: 4 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/rebuild-generators-on-change.sh\""
},
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/rebuild-analyzers-on-change.sh\""
}
]
}
Expand Down
17 changes: 17 additions & 0 deletions .claude/skills/build-analyzer/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: build-analyzer
description: Build the Roslyn analyzer submodule and deploy the resulting DLL into the Unity package
user-invocable: true
---

Build the Aspid.FastTools analyzer (git submodule) and deploy to Unity:

1. If `Aspid.FastTools.Analyzers/` is empty, run `git submodule update --init` first
2. Run `dotnet build Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers.csproj -c Release` from the repository root
3. Run `dotnet test Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers.sln -c Release` and stop if any test fails
4. Copy `Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/Aspid.FastTools.Analyzers/bin/Release/netstandard2.0/Aspid.FastTools.Analyzers.dll` to `Aspid.FastTools/Packages/tech.aspid.fasttools/Aspid.FastTools.Analyzers.dll`
5. Report the result: build/test output, any errors, and confirm the DLL was copied successfully

Note: diagnostic IDs use the `AFT*` prefix. After bumping the submodule commit, remember the gitlink change in the superproject (`git add Aspid.FastTools.Analyzers`).

Arguments: $ARGUMENTS (optional: pass `Debug` to build in Debug configuration instead of Release)
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "Aspid.FastTools.Analyzers"]
path = Aspid.FastTools.Analyzers
url = https://github.com/VPDPersonal/Aspid.FastTools.Analyzers.git
1 change: 1 addition & 0 deletions Aspid.FastTools.Analyzers
Binary file not shown.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- **Features**
- [ProfilerMarker](#profilermarker)
- [Serializable Type System](#serializable-type-system)
- [SerializeReference Selector](#serializereference-selector)
- [Enum System](#enum-system)
- [ID System (Beta)](#id-system-beta)
- [SerializedProperty Extensions](#serializedproperty-extensions)
Expand Down Expand Up @@ -247,6 +248,29 @@ public sealed class AbilitySelector : MonoBehaviour

> The complete sample — `Ability` / `AbilitySelector` / `EnemyBase` and their subclasses — ships in the `Types` sample (Package Manager → Aspid.FastTools → Samples).

Decorate a candidate type with `[TypeSelectorItem]` 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;

// Re-home the type under a category and give it a tooltip and ordering hint:
[TypeSelectorItem("Combat/Damage Modifier", Tooltip = "Scales incoming damage", Order = 10)]
public sealed class DamageModifier { }

// A plain name (no '/') just renames the leaf in place, keeping its namespace location:
[TypeSelectorItem("Damage Modifier")]
public sealed class DamageModifierAlt { }
```

| Member | Description |
|--------|-------------|
| `DisplayPath` | A `"Category/Name"` value re-homes the type under those category nodes; a plain value renames the leaf in place. `null`/empty keeps the default type name. |
| `Tooltip` | Tooltip shown when hovering the type's row. |
| `Order` | Ordering hint within the group — lower values appear higher; ties are broken alphabetically. Default `0`. |
| `Icon` | Editor icon shown left of the label — an `EditorGUIUtility.IconContent` name or a `Resources` texture path. |

> Search still matches the real type name, so a re-homed or renamed entry stays findable by its original name.

---

### Type Selector Window
Expand All @@ -258,6 +282,7 @@ The Inspector shows a button that opens a searchable popup window with:
- Keyboard navigation (Arrow keys, Enter, Escape)
- Navigation history (back button)
- Assembly disambiguation for types with identical names
- **Favorites** and **Recent** sections on the root page: a hover-revealed ★ toggle pins a type to Favorites, and the last 8 picked types are kept under Recent (both persisted per project, hidden while searching)

![aspid_fasttools_type_selector_window.png](../Images/aspid_fasttools_type_selector_window.png)

Expand All @@ -273,7 +298,8 @@ namespace Aspid.FastTools.Types.Editors
Type[] types = null,
string currentAqn = "",
TypeAllow allow = TypeAllow.None,
Action<string> onSelected = null);
Action<string> onSelected = null,
Func<Type, bool> filter = null);
}
}
```
Expand All @@ -285,6 +311,7 @@ namespace Aspid.FastTools.Types.Editors
| `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. |
| `allow` | Which special type kinds (abstract classes, interfaces) are included in addition to concrete classes. Default: `TypeAllow.None`. |
| `onSelected` | Callback invoked with the assembly-qualified name of the selected type, or `null` if the user chose `<None>`. |
| `filter` | Optional predicate applied to each candidate type after the base-type and `allow` checks. Return `false` to hide a type. Pass `null` to keep every match. |

### ComponentTypeSelector

Expand Down Expand Up @@ -325,6 +352,69 @@ public sealed class TankEnemy : EnemyBase

---

## SerializeReference Selector

A drop-in dropdown for `[SerializeReference]` fields. Add `[TypeSelector]` next to `[SerializeReference]` and the Inspector replaces the default managed-reference UI with the same searchable, hierarchical type picker used by `SerializableType` — letting you choose which concrete implementation of the field's declared type is instantiated.

- Lists every concrete, non-`UnityEngine.Object` class assignable to the field's declared interface / base type.
- Passing base types narrows the candidates below the field's declared type — `[TypeSelector(typeof(IMelee))]` on an `IWeapon` field offers only `IMelee` implementations.
- Picking a type instantiates it; `<None>` clears the reference.
- The assigned instance's serialized fields are drawn inline under a foldout.
- A stored type that no longer resolves (renamed or deleted) is surfaced as a missing-type warning instead of silently clearing.
- Open generic implementations (e.g. `Modifier<T>`) are offered too: arguments are inferred from a closed-generic field, or picked in a follow-up window (validated against the field type) before instantiation.
- Switching the selected type preserves matching data — fields shared by the old and new implementation (by name and serialized shape) carry over instead of resetting to defaults.
- Right-click the header for a Copy / Paste context menu: it copies the managed-reference value and pastes it as an independent instance into any compatible field (paste is disabled when the clipboard type is not assignable to the target).
- A missing type can be repaired in place: the warning is a compact yellow notice whose underlined **Fix** word opens the type picker — choose the correct type and the reference is re-pointed while keeping its stored data; hover the notice for the full missing-type detail. Works for saved assets (ScriptableObjects and prefab assets) selected in the Project **and for objects open in Prefab Mode** — saved assets are rewritten in their YAML, while a Prefab Mode object is repaired on the live instance, recovering the data Unity still holds for the missing type. The repair also reaches nested references — through nested managed references and through plain `[Serializable]` containers (a struct/class field or a `List<T>` of them) — so a missing type buried in a slot or list element is fixed inline too.
- The notice can also surface a **Smart Fix** suggestion — a second clickable segment next to **Fix** (e.g. `· → Pistol?`) that ranks the most likely replacement (a declared `[MovedFrom]` rename, the same class name in a different namespace/assembly, a casing-only rename, or a near-miss name backed by a matching field shape) and applies it in one click. The suggestion is only ever a type the picker would offer, and is never auto-applied — you always click.
- For missing references the Inspector cannot surface in the moment — components on child objects when the asset is not open in Prefab Mode, plus bulk repair and orphaned entries no field points at — the **Repair Missing References** window (`Tools → Aspid 🐍 → Repair Missing References FastTools`) scans the whole asset file and lists every one with its own **Fix** picker, no Prefab Mode required. A `Scan Project` button extends this project-wide: it sweeps every `.prefab` / `.asset` / `.unity` file under `Assets/`, groups the broken references by their stored type, and rewrites every entry across every affected file with a single `Fix all` (plus a Smart Fix quick-apply) per group — entries in currently open scenes are skipped during a bulk apply.
- The **Managed References** window (`Tools → Aspid 🐍 → Managed References FastTools`) maps an asset's whole managed-reference graph from the YAML: a per-component tree of field-pointer roots, nested children, shared references and orphaned payloads, with `MISSING` / `SHARED` badges, deterministic per-rid colours, and a constrained inline **Fix** for missing entries. It surfaces references at any nesting depth and the orphans the Inspector cannot navigate to.
- An aliased reference (two fields sharing one instance, e.g. after duplicating a list element) is flagged by the same compact notice, whose underlined **Make unique** word (also a right-click → **Make Unique Reference** action) splits it into an independent copy; the shared fields are tinted with a deterministic per-rid colour stripe and chip that matches the **Managed References** window.
- Duplicating a list element (Duplicate / Ctrl+D, or `+`-appending a copy of the last element) no longer aliases the reference in the first place — the copy silently becomes an independent instance in a single Undo step. Intentional cross-field sharing is left untouched and keeps the **Make unique** notice.
- Multi-object editing is supported: a mixed selection shows a mixed-type dropdown, and picking a type (or pasting) applies an independent instance to each selected object in one Undo group; per-asset notices are suppressed under a multi-object selection.
- Usage is validated at compile time by the Roslyn analyzer: `AFT0004` (error) flags a `[SerializeReference]` + `[TypeSelector]` field whose type derives from `UnityEngine.Object`, and `AFT0005` (warning) flags a constraint no visible concrete type can satisfy — the picker would be empty.
- Works on single fields, arrays, and `List<T>`, in both IMGUI and UIToolkit inspectors.

```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");
}

[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]
private IWeapon _primary;

[SerializeReference] [TypeSelector]
private List<IWeapon> _sidearms;
}
```

The attribute is editor-only (`[Conditional("UNITY_EDITOR")]`) and carries no runtime cost.

---

## Enum System

Provides serializable enum-to-value mappings configurable from the Inspector.
Expand Down
Loading
Loading