Skip to content

feat: Hot reload status now reports how often each added member has been called - #3036

Merged
hatayama merged 4 commits into
mainfrom
feat/hot-reload-added-member-invocation-count
Sep 30, 2026
Merged

hatayama merged 4 commits into
mainfrom
feat/hot-reload-added-member-invocation-count

Conversation

@hatayama

@hatayama hatayama commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • uloop hot-reload --status now reports InvocationCount for members that hot reload added (new methods, property accessors, auto-properties), not only for patched methods.

User Impact

  • Before: every Added row showed InvocationCount 0 with a "not instrumented" reason, so there was no way to tell whether a hot-reloaded caller ever reached a newly added member.
  • After: Added rows count calls into the added member since it was applied.
    • Reason explains why the member has not run only while the count is 0, and the status Message counts those rows together with never-invoked Active rows.
    • An unchanged reload's AlreadyActive row for an added member carries that member's count.
    • Re-applying the member (editing it, or re-applying its file together with an edited file of the same assembly) restarts the count from zero.
    • An added iterator counts when its enumeration starts; async members and patched methods count when they are called.

Changes

  • Transform worker: every added-member shim gets a public static long <shimMethodName>__uloopCalls field, incremented with Interlocked.Increment at the top of the shim body after the null-receiver check, so a refused call is not counted. All three added-member emit paths go through one preamble function. Auto-property accessors now get the receiver check too (the store already threw the same NullReferenceException; only the throwing frame moves).
  • Editor:
    • Entry resolution looks up the counter beside the shim. A missing counter, or one that is not a static long, fails the whole file, and the Failed row names the counter.
    • Registration refuses an added method without a readable counter, so no Added row exists without a count.
    • --status reads the counter for Added rows.
    • The unchanged-reload short circuit carries the added member itself, so the response reads the member's counter instead of the patch ledger (which, after a return-type change, holds the old patch's count).
  • Skill docs describe the new count; the generated .claude / .agents copies are regenerated.
  • No protocol version change: the response shape is unchanged; only values and Reason text change.

Verification

  • New E2E class HotReloadAddedMemberInvocationCountE2ETests 16/16: methods, expression bodies, static members, bodied and auto properties, throw expressions, iterators, async, re-apply and sibling re-apply resets, and a return-type change after a body patch.
  • Related unit suites 243/243, hot reload E2E 97/97, added-member E2E 254/254.
  • Mutation checks: moving or removing the worker increment, dropping the accessor receiver check, and removing each Editor read each failed the expected tests.
  • check-skill-size, sync-tool-docs --check, CA1502 complexity (max 15) and file length (max 500 SLOC) pass.

Closes #2869

Review in cubic

--status reports InvocationCount 0 for every Added row because only
Harmony-patched methods carry the registry increment, and an added member
has no original method to patch. The worker now gives every added-member
shim a public static long <shimMethodName>__uloopCalls field and increments
it with Interlocked at the top of the shim body, after the null-receiver
check, so a call the check refuses is not counted.

- One function (PrependAddedMemberPreamble, generalized from
  GuardAddedMemberReceiver) prepends the receiver check and the increment.
  All three added-member emit paths reach it through
  ShimTypeBuilder.AddAddedMemberMethod, so no added-member shim can be
  emitted without its counter. Static expression bodies now become blocks
  too, and a shim with no body stops with an explicit exception (body-less
  members are skipped earlier, so this is not reachable).
- Auto-property accessors now get the receiver check as well. The store
  already threw the same bare NullReferenceException, so only the throwing
  frame moves, and the check keeps null-receiver calls out of the count.
- The next commit mirrors the suffix constant on the Editor side, which
  reads the counter.
- Five test helpers that located a shim method by the first occurrence of
  its name now search for the name followed by '(', because the counter
  field's name starts with it.

Verification: the new worker test for the counter and its position passes
9/9 (red before the change); worker and related suites 131/131, 230/230
and 87/87; null-receiver E2E 10/10. The mutations "increment before the
check plus no increment for static members" and "accessor shims without
the check" each failed the expected cases.
`uloop hot-reload --status` reported InvocationCount 0 on every Added row
and told the reader the member was not instrumented, so an agent could not
tell whether a hot-reloaded caller ever reached a member it had added. The
worker now declares a static counter beside every added-member shim; the
Editor reads it.

- Entry resolution looks up `<shim>__uloopCalls` beside the shim. A
  missing counter, or one that is not a static long, fails the whole file
  and the Failed row names the counter; registration refuses an added
  method without a readable counter, so no Added row exists without a count.
- `--status` Added rows show the live count. Reason explains why the member
  has not run only while the count is 0, and the Message aggregate counts
  those rows together with never-invoked Active rows.
- An unchanged reload's AlreadyActive row for an added member now carries
  the member and reads its counter when the response is built. It used to
  read the patch ledger, which after a return-type change holds the old
  patch's count rather than the added member's.
- Re-applying a member emits a new shim whose counter starts at zero; an
  unchanged reload keeps the member and its count.

Verification: new E2E class (methods, expression bodies, static members,
bodied and auto properties, throw expressions, iterators, async, re-apply
and sibling re-apply resets, return-type change after a body patch) 16/16;
added-member E2E 254/254, other hot reload E2E 97/97, related unit suites
243/243. Mutations of the worker increment and of each Editor read failed
the expected tests.
The skill told agents that Added rows always show InvocationCount 0
because added members are not instrumented. They are now counted, so the
docs explain what the count means and when it stays 0: compiled code
cannot call an added member, so only a hot-reloaded caller, or the Play
Mode proxy for a forwarded Unity message, can run it.

- output.md and troubleshooting.md describe the Added-row count, its
  reset on re-apply, AlreadyActive rows keeping it, and that an added
  iterator counts when its enumeration starts; the example status row now
  shows a counted added member.
- SKILL.md and mechanism-and-lifecycle.md cover added members wherever
  they described the live InvocationCount; SKILL.md stays at 7,999 bytes.
- The .claude and .agents copies are regenerated from the sources.
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: hatayama/unity-cli-loop/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: d4bf012c-eeee-4025-a0f3-790a40e3aca1

📥 Commits

Reviewing files that changed from the base of the PR and between 29672ae and fd1ffb7.

⛔ Files ignored due to path filters (2)
  • Assets/Tests/Editor/HotReload/HotReloadAddedMemberInfoTests.cs.meta is excluded by none and included by none
  • Assets/Tests/Editor/HotReload/HotReloadAddedMemberInvocationCountE2ETests.cs.meta is excluded by none and included by none
📒 Files selected for processing (4)
  • .agents/skills/uloop-hot-reload/references/output.md
  • .claude/skills/uloop-hot-reload/references/output.md
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadConstants.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Skill/references/output.md

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

Added-member shims now track calls with generated counters. Hot-reload registration and responses carry those counters, and status reports added-member invocation counts, zero-count reasons, and aggregate counts. Tests and hot-reload skill documentation cover the updated behavior.

Changes

Added-member invocation counting

Layer / File(s) Summary
Emit counters in added-member shims
Packages/src/Editor/FirstPartyTools/HotReload/TransformWorker~/*, Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadConstants.cs, Assets/Tests/Editor/HotReload/TransformWorker*Tests.cs
Generated shims for added methods and property accessors increment a static long counter after any receiver check and before the body. Tests cover counter emission and shim parsing.
Resolve and retain counters
Packages/src/Editor/FirstPartyTools/HotReload/HotReloadEntryResolution.cs, Packages/src/Editor/FirstPartyTools/HotReload/Patching/*, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadMethodOutcome.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadAppliedSourceLifecycle.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadFileEntryApplier.cs, Assets/Tests/Editor/HotReload/HotReload*Tests.cs
Entry resolution validates each added-method counter and passes it into added-member registration. Unchanged reloads produce an AlreadyActive outcome that retains the added-member record. Tests cover counter validation, registration, and lifecycle handling.
Report counts and update guidance
Packages/src/Editor/FirstPartyTools/HotReload/HotReloadApplyResponseBuilder.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadStatusExecutor.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadTools.cs, Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadConstants.cs, Assets/Tests/Editor/HotReload/HotReloadAddedMemberInvocationCountE2ETests.cs, Assets/Tests/Editor/HotReload/HotReloadToolTests.cs, Assets/Tests/Editor/HotReload/HotReloadOrchestratorTests.cs, .agents/skills/uloop-hot-reload/*, .claude/skills/uloop-hot-reload/*, Packages/src/Editor/FirstPartyTools/HotReload/Skill/*
Status rows and applicable apply results expose added-member counts. Zero-count reasons and aggregate messages include Added rows. Tests and skill documentation describe count timing, reset behavior, and unchanged-reload counts.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature · Severity of issue fixed: Low

Sequence Diagram(s)

sequenceDiagram
  participant AddedMemberShim
  participant InvocationCounterField
  participant HotReloadStatusExecutor
  participant HotReloadAddedMemberInfo
  AddedMemberShim->>InvocationCounterField: Increment before the body runs
  HotReloadStatusExecutor->>HotReloadAddedMemberInfo: Read InvocationCount for the status row
  HotReloadAddedMemberInfo->>InvocationCounterField: Read the current long value
Loading

Merge Risk: ⚪ Minimal · up to fd1ff

Added-member invocation counts flow through status and apply results, with no concrete merge-blocking issue identified.

Security Architecture Review

Security architecture risk: 🔵 Low · up to fd1ff

The inspected counter flow does not establish a new execution or privilege boundary. Remaining uncertainty concerns interrupted reload recovery and compatibility for external consumers, rather than a demonstrated new security vulnerability.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The assessed change affects generated shim counters, per-file added-member registrations, and Editor diagnostic responses. These inspected paths do not establish additional credential access, tenant scope, or service authority.

Trust Boundaries and Controls

  • observed — The producer-to-consumer transition validates generated counter identity and field shape. The changed reason and aggregate-message constants are consumed for reporting; they do not dispatch calls or grant execution authority.

Resilience and Maintainability Implications

  • inferred — The interrupted-run recovery uncertainty concerns existing activation-to-ledger ordering, not a demonstrated counter-specific regression. The available changed ranges do not modify that ordering, but they do not prove recovery or unchanged exposure under every interruption scenario.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 49.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 117 functions across 34 files. (3 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains that hot-reload-added members now report InvocationCount, including count reset and persistence behavior.
Title check ✅ Passed The title clearly and concisely summarizes the main change: hot-reload status reports call counts for added members.
Linked Issues check ✅ Passed PR #3036 meets the coding requirements in issue #2869. Added-method shims now emit long counters and increment them after receiver validation and before the added body. Resolution, registration, app…
Out of Scope Changes check ✅ Passed The changes remain within issue #2869. Production changes implement added-member invocation counting, lifecycle handling, status reporting, and required reason and documentation updates. Property acce…
Full details: Docstring Coverage

Explanation

Docstring coverage is 49.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 117 functions across 34 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hatayama
hatayama merged commit 5f4bbf5 into main Sep 30, 2026
16 checks passed
@hatayama
hatayama deleted the feat/hot-reload-added-member-invocation-count branch September 30, 2026 00:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hot reload: Added method rows cannot show whether the added body was ever invoked (InvocationCount is always 0)

1 participant