Skip to content

feat(hsm): let a composite state pick its entry child - #29

Merged
RodionGayvoronskiy merged 3 commits into
mainfrom
feat/entry-selector
Oct 16, 2026
Merged

RodionGayvoronskiy merged 3 commits into
mainfrom
feat/entry-selector

Conversation

@RodionGayvoronskiy

@RodionGayvoronskiy RodionGayvoronskiy commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Important

  • The generator changed: the DLL from this PR's generator-dll CI artifact is committed in a follow-up
  • After merge: set version 0.0.1-alpha.5 and release

Why. The race tree in street keeps kind-specific leaves (sprint, drift, drag…) under stage composites. A controller must change to the stage without knowing the kind, but ChangeState<Composite>() made the composite the leaf. And a pause bound to the stage had to list every kind leaf in [ExtensionFor].

What

  • IEntrySelector.SelectEntry(): an entered composite target descends into the child it names, same transition
  • [ExtensionFor(..., IncludeDescendants = true)]: the host anywhere in the active chain
  • StateHierarchy.GetParentType / IsSelfOrDescendantOf; the factory delegates to it
  • A cycle in declared parents throws instead of hanging the change

Start with: Unity/Runtime/StateMachines/StateMachineBase.cs (SelectEntryType and the descent after the enter loop), then StateMachineBase.Async.cs, then ExtensionState/Bodies/ExtensionStateBody.cs

+public interface IEntrySelector { Type SelectEntry(); }
+[ExtensionFor(typeof(ClassicDrivingState), IncludeDescendants = true)]
+public static class StateHierarchy
+{
+    public static Type? GetParentType(Type stateType);
+    public static bool IsSelfOrDescendantOf(Type stateType, Type ancestorType);
+}
Verification
  • dotnet test → 234/234 (+23): EntrySelectorTests, ExtensionWithDescendantsTests, ExtensionForDescendantsEmitTests
  • Selector: composite → leaf with one OnChangingState/OnChangedState that sees the final leaf; nested selectors; straight to a leaf skips it; siblings do not re-enter it; TransitionTo; null / non-child / grandchild / cycle errors; sync into async-only entry throws; async enter completes before SelectEntry; cancelled and superseded enters never ask it; OnEntered after the final leaf
  • Extension: attaches on a child of the host, survives a leaf change inside it, detached on host exit; without the flag the generated pattern is unchanged
  • Aspid.Core.HSM.Runtime (C# 9) clean; Unity 6000.4.0f1 batch import clean, .meta for the two new files from it
Context

Spec: street Logs/race-hsm-books/HSM_PACKAGE_PROMPT.md. Extension flag chosen as variant (b), no change to existing [ExtensionFor]. CanAttachTo still receives only the leaf: "the host is in the active chain" is "the leaf is the host or its descendant", which the leaf's type answers through IChildState<>.
Not done: item 3 (registration validation hook, low priority) — finding a selector's children needs an assembly scan; street can test it with StateHierarchy.GetParentType over its own types. The generator diagnostic for a type-parameter controller argument (optional) is left for later.
IsStateEnabled / IsTransitionEnabled guard the target composite, not the entries a selector picks.

🤖 Generated with Claude Code

- IEntrySelector: a change that ends at a composite enters it, then the
  child its SelectEntry names, in the same transition, until the leaf
  is no selector. Null, non-child entries and cycles throw.
- [ExtensionFor(..., IncludeDescendants = true)] attaches while a
  listed state is anywhere in the active chain.
- StateHierarchy reads declared parents; a cycle in them no longer
  hangs the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generator-dll artifact of this PR's CI run 38882539393, as is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RodionGayvoronskiy

Copy link
Copy Markdown
Collaborator Author

@claude

@claude

claude Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Claude finished @RodionGayvoronskiy's task in 1m 39s —— View job


Ревью PR #29

  • Собрать контекст и дифф
  • Проверить runtime (StateMachineBase, StateHierarchy, StateFactory)
  • Проверить генератор и тесты
  • Опубликовать ревью: 3 inline-комментария и итоговый комментарий с вердиктом

Verdict: 0 blocking, 3 minor

  1. StateMachineBase.cs:222: нет теста и описания для ChangeState<Composite>(), когда активен ребёнок этого композита. Composite повторно не входит, но SelectEntry() вызывается снова.
  2. StateMachineBase.cs:226: async-only entry бросает исключение уже после входа в композит. Это расходится с <exception> в ChangeState («chain is left unchanged»).
  3. StateHierarchy.cs:47: сравнение точное (==), а ветка без флага использует is. С IncludeDescendants базовый тип или интерфейс в [ExtensionFor] молча перестаёт совпадать.

Проверено: инвариант factory/machine, Source/ без UnityEngine, .meta для новых файлов, CHANGELOG, XML-доки, snapshot-тест новой ветки emit.

dotnet test и Release-сборку здесь запустить не удалось, нужно разрешение на эти команды. Соответствие закоммиченного DLL свежей сборке проверят CI и release.yml.

@claude

claude Bot commented Oct 16, 2026

Copy link
Copy Markdown

Verdict: 0 blocking, 3 minor
Инвариант factory/machine сохранён, .meta, CHANGELOG, XML-доки и snapshot-тест для новой ветки emit на месте. Замечания касаются непокрытого случая повторного таргета композита, документации async-only и точного сравнения типов при IncludeDescendants.

@RodionGayvoronskiy
RodionGayvoronskiy marked this pull request as ready for review October 16, 2026 10:05
@claude

claude Bot commented Oct 16, 2026

Copy link
Copy Markdown

Verdict: 0 blocking, 2 minor
Спуск в IEntrySelector и IncludeDescendants не ломают инварианты factory/machine. Совпадение DLL с Release-сборкой я локально не проверял, его проверит release.yml.

- IncludeDescendants matches as `is` does: a base class or an interface
  of a state in the chain counts, like the pattern without the flag.
- Document and test a change that targets the active composite again,
  and that an entry is checked only after the selector was entered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RodionGayvoronskiy
RodionGayvoronskiy merged commit 22bf1e7 into main Oct 16, 2026
20 checks passed
@RodionGayvoronskiy
RodionGayvoronskiy deleted the feat/entry-selector branch October 16, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant