quad/.claude/archive/effect-internal-observer-cascade-reversed.md
qwreey 7f5868302e
qa: 9라운드 Q4~Q10 + H-138/H-139/H-142 확정·반영, 감사 8라운드, code-review 반영 — H-143~H-146 판단 대기
Q4 EffectHandle 네 진입점 의사코드(Observer 것 재사용, Unsubscribe만 게이트 통과
뒤 cleanup) / Q5 M2 공통 기반에 Ref 최소형(+ Quad.Ref 필드) / Q6 WeakUnsubscribe
관대 명문화 / Q7 폐기 블록 archive/effect-internal-observer-cascade-reversed.md /
Q8 InstanceChildHandler 부기(Parent → setLength(1), retractor는 Parent=nil →
해제) / Q9 문항 전제 정정 — Tween 절 스케치 hint==nil 줄은 복사 오류, retractor는
function() end / Q10 :List reconcile 재실행도 배치 Blocker(ownsGate — 네스팅 불가) /
H-138 숏핸드 우선순위 > PropertyHandler, 충돌 방지는 UI 접두어 / H-139
New(name)(props)·Dispatch.drive 파이프라인 의사코드(bind-system-plan) — 쓰면서
빈 배열 파트 가드와 H-142 발견 / H-142 props에 Parent 금지(부모가 하는 일) —
순서 문제 소멸, D·Modifier 타입 제외 + PropertyHandler 거부(배선은 에이전트 선택으로
갈라 적음). H-129/H-131 정정.

감사 8라운드(5→3→4→5→3→1→3→1) — 1라운드가 drive 의사코드의 H-17 위반(post-pass
포함 전체 감쌈)을 잡음, 이후는 기록 문서 표기. /code-review high 10건 — 여섯
반영(그중 셋이 이 세션의 H-134 반영이 만든 것), 넷은 새 메커니즘이라 문항
H-143~H-146으로(question.md 최우선 절). doc-check ERROR 0.

Claude-Session: https://claude.ai/code/session_01546hjsYNLSMZdHdPyTZaGb
Co-authored-by: qwreey <me@qwreey.moe>
2026-08-27 15:52:48 +09:00

5.3 KiB

[역전됨] EffectHandle의 내부 Observer 바인딩 세부 — _observers 배열 + bindLifetime/:Subscribe() cascade

역전일: 2026-08-25(7라운드 H-58/H-59). 대체된 곳: base/effect-plan.md의 "확정 구조 — 강한 주인은 항상 Effect" 절과 "의사코드 — 생성자 / bindLifetime이 부르는 두 훅 / Rerun" 절 — bindLifetime/unbindLifetimeEffect 핸들 하나에만 적용되고 내부 Observer로 cascade하지 않는다. dep 등록은 생성자에서 한 번만 WeakSubscribe/WeakCallback으로 하고, 발화 여부는 canExecute(handle)이 전담한다. 아래가 서술하는 _observers 배열/cascade/Subscribe 순회는 전부 _deps 하나와 _blocker로 대체됐다. 이 문단대로 짜면 H-58의 중복 Rerun이 되살아난다.

archive/로 왔나 (2026-08-27, 9라운드 H-130, 사용자 결정 Q7-(b)): 이 블록은 base/effect-plan.md 안에 폐기 배너를 단 채 남아 있었는데, 배너 아래 죽은 문단을 두 번이나 살아 있는 문장처럼 편집하는 사고가 났다(8라운드 2차 #1, 그리고 커밋 9dd8213이 아래 40번째 줄 근처의 ".Subscribed는 구독 경로 전용"을 날짜 마커 없이 고친 것 — 옛 표기는 "전역 :Subscribe() 전용"). conventions.md의 핸드오버 체크리스트 3번 ("뒤집힌 원문은 archive/로 옮기고 포인터만 남길 것")이 정확히 이 실패 모드를 규정하고 있어 그대로 따랐다. 아래는 옮기는 시점의 원문 그대로(그 편집 흔적 포함)이고, 더 이상 갱신하지 않는다.

옮겨온 원문 (base/effect-plan.md, 2026-08-27 시점)

보강 — EffectHandle의 내부 Observer 바인딩 세부(2026-08-09 열한 번째 세션, 재확인 후 명시화):

[2026-08-25 폐기, 7라운드 H-58/H-59] 이 문단 전체는 옛 모델이다. 위 "확정 구조 — 강한 주인은 항상 Effect" 절이 정반대로 확정했다 — bindLifetime/unbindLifetimeEffect 핸들 하나에만 적용되고 내부 Observer로 cascade하지 않는다. dep 등록은 생성자에서 한 번만 WeakSubscribe/WeakCallback으로 하고, 발화 여부는 canExecute(handle)이 전담한다. 아래가 서술하는 _observers 배열/cascade/Subscribe 순회는 전부 _deps 하나와 _blocker로 대체됐다(위 "필드 목록"). 아래는 히스토리로만 읽을 것 — 이 문단대로 짜면 H-58의 중복 Rerun이 되살아난다.

⚠️ [2026-08-24 6라운드 손 트레이싱 H-8, 2026-08-25 폐기] 이 문단 전체가 아직 "Observer 하나" 전제로 쓰여 있었다 — _observer(단수)를 _observers(배열)로 읽을 것. 아래 절이 확정한 Effect(fn, ...deps)(N-deps)와 정면으로 어긋났고, 그대로 구현하면 2번째 이후 dep의 Observer엔 canExecute 판정 근거가 아예 안 실려 그 Observer의 재실행이 통째로 죽는다 — 바로 이 문단 자신이 경고하는 실패 모드다. 필드를 배열로 바꾸고 cascade/Subscribe/Unsubscribe를 전부 순회로 고친다(새 결정 없음, 반영 누락). Ref dep은 Observer가 아니라 콜백이라 이 배열에 안 들어간다 — 그쪽 해제는 아래 H-7 문단이 소스.

  • EffectHandle은 내부 Observer를 필드로 강참조handle._observers[i] = observer(dep이 State/Source인 경우만 존재). 이건 GC 방지가 목적이 아니라 (그건 아래 bindLifetime/gchold가 담당) :Unsubscribe()/bindLifetime cascade가 이 필드를 통해 내부 Observer에 접근하기 위한 것.
  • bindLifetime(inst, handle)state가 있는 경우 내부 Observer도 같은 insthandle._observers 전부에 대해 bindLifetime(inst, observer)를 cascade해야 함Dispatch/Leaf.luau가 children 배열의 EffectHandle을 매치해 bindLifetime(inst, handle)을 부르는 시점(leaf 부착)과, :Subscribe()handle을 전역 레지스트리에 등록하는 시점(아래) 둘 다 해당. 이유: canExecute(observer)가 보는 gcconn 참조는 그 Observer 자신이 bindLifetime(inst, observer)될 때 그 Observer 쪽 릴레이션에 복사되는 것이라, EffectHandle만 바인드하고 내부 Observer는 안 하면 그 Observer에겐 판정 근거가 아예 없어서 canExecute가 항상 거짓이 됨 (=재실행이 통째로 죽음). 같은 이유로 unbindLifetime(handle)도 내부 Observer까지 같이 풀어야 대칭이 맞음. [정정, 2026-08-14 다섯 번째 세션] 이 항목이 원래 근거로 든 "canExecuteSubscribed 필드 + inst의 gcconn을 함께 본다"는 틀렸음 — .Subscribed는 구독 경로 전용이고 leaf 경로와 무관(archive/canexecute-inst-arg-reversed.md). cascade가 필요하다는 결론은 그대로이고 오히려 근거가 더 직접적이 됨.
  • :Subscribe()도 마찬가지로 state가 있으면 내부 Observer를 같은 전역 강참조 레지스트리에 같이 등록(handle 자신 + handle._observers 전부, 또는 handle._observers만으로 충분한지는 구현 세부 — 어느 쪽이든 "EffectHandle은 등록됐는데 내부 Observer는 등록 안 됨" 상태가 생기면 안 됨).