quad/.claude/session/2026-08-09-07-list-observer-lazy.md
qwreey 1f56c75978
chore(docs): split CLAUDE.md session log into .claude/session/, keep 2-4 line summaries
CLAUDE.md had grown to 3196 lines of accumulated session logs, causing
context bloat. Full session narratives (including trial-and-error and
later-corrected reasoning — quadnomicon devlog raw material) now live
as 39 individual files under .claude/session/. CLAUDE.md keeps only a
short "지금 할 일" (re-synced against question.md/pre-implementation-audit.md,
stale detail dropped) and a compact per-session summary+link table.
No design decisions changed; base/research/question.md were already
in sync with every session (verified against README.md/question.md
before archiving), so no unreflected content needed migrating first.
2026-08-11 14:40:43 +09:00

4.1 KiB

2026-08-09 일곱 번째 세션 — Slot:Listdata:Observer(fn) 구독도

마운트 시점 lazy bindLifetime으로 확정 (Destroy 후 재실행 gap 해소)

사용자가 "Slot이 마운트된 대상이 Destroy로 죽으면 updateFn 재실행이 canExecute로 막히고 있는 게 맞냐"고 질문하며 시작 — 확인 결과 두 메커니즘이 다른 상태였음: Dispatch.setLength(Length/Offset, 여섯 번째 세션 확정)는 이미 정확히 그렇게 돼 있었지만(Slot 마운트 시점에 bindLifetime(inst,observer)), Slot:Listdata:Observer(fn) 구독은 :List() 호출 그 자리에서 즉시 만들어져(inst를 모르는 시점) bindLifetime이 전혀 안 걸려있던 실제 gap — 사용자가 정확히 캐치함. 사용자가 이어서 "실제로 Instance에 바인드되려 시도될 때(=마운트 시점)로 구독 자체를 lazy하게 미루면 되지 않냐"고 제안, 검증 후 확정. base/slot-plan.md(:List의 "구현"/"구독 시점" 절 재작성 + "base/roblox 패키지 경계" 절 보강)/ROADMAP.md(M6)에 반영 완료:

  • Dispatch.setLength가 이미 쓰던 패턴을 그대로 재사용, 새 메커니즘 없음. :List(data,updateFn,keyFn)는 이제 설정만 저장하고 반환 — 실제 data:Observer(fn) 구독과 최초 reconcile은 Slot 컨테이너 자신이 마운트되는 순간(Dispatch/Slot.luauprocess(inst,k,self), self._mounted를 세팅하는 바로 그 자리)에 activateList(self,inst)가 수행.
  • :List()가 마운트 이후에 불리는 경우 — self._mounted면 즉시 활성화로 확정(사용자 확인, 세 가지 대안 중 1번). 마운트는 1회성 이벤트라 순서가 뒤바뀌면 그 이벤트를 못 기다리므로, :List()self._mounted를 직접 확인해서 이미 참이면 그 자리에서 즉시 activateList — 호출 순서 제약을 새로 추가하지 않음.
  • canExecute와 "등록 즉시 1회 실행"의 관계를 사용자가 직접 짚어 확정: data:Observer(fn) 등록 시점(=bindLifetime 호출 이전)의 최초 1회 실행은 canExecute/Subscribed 게이팅과 무관하게 무조건 일어남 — 이 시점엔 아직 Subscribed가 안 세팅돼 canExecute를 물으면 거짓이겠지만, 애초에 최초 실행은 게이팅 대상이 아니라서 상관없음(Dispatch.setLength가 이미 "등록 즉시 1회와 겹쳐도 무해"로 같은 구조를 갖고 있었음). bindLifetime은 등록 직후에 걸려 이후 재실행만 게이팅.
  • Destroy 이후 "재실행 막기"+"관측 자체를 관두기"가 새 코드 없이 한 번에 해결됨inst Destroy 시 gcconn이 죽어 canExecute가 거짓이 되고 향후 재실행이 no-op되는 동시에, gcholdRelate(inst)(weak-keyed) 아래 있어서 inst가 죽으면 그 안에 강참조로 잡혀있던 Observer/클로저 (mounted/userdata/keyIndex 포함)가 전부 GC 대상이 됨 — 명시적 구독 해제 코드가 안 필요함, lifecycle-pattern.md의 "정리는 기본적으로 GC에 위임" 원칙 그대로.
  • 부수 관찰(메모만, 설계 아님): 사용자가 "Relate로 마운트된 대상을 weak하게 구할 수도 있겠다"고 언급 — bindLifetimeRelate(inst) 기반이라 나중에 "이 inst에 지금 뭐가 붙어있는가" 역조회가 같은 저장소로 가능해 보임, quad-debug 그래프 UX와 맞닿을 수 있음. 지금 설계 안 함, 필요성 확인되면 그때.

다음 세션이 할 일: 안 바뀜(ROADMAP.md M0부터) — 이번 세션도 순수 설계 확정이라 M0 착수 우선순위 자체는 그대로.