From c685dffaa0481b7537ed34f0310c42f5dc490317 Mon Sep 17 00:00:00 2001 From: qwreey Date: Fri, 28 Aug 2026 18:40:24 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=EA=B0=90=EC=82=AC=205=EB=9D=BC?= =?UTF-8?q?=EC=9A=B4=EB=93=9C=20=EB=B0=98=EC=98=81=20=E2=80=94=20lifecycle?= =?UTF-8?q?-pattern=20=EC=9B=90=20=ED=99=95=EC=A0=95=20=EB=AC=B8=EB=8B=A8?= =?UTF-8?q?=EC=97=90=20'=ED=8F=89=ED=8F=89=ED=95=9C=20=3D=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=20=ED=95=84?= =?UTF-8?q?=EB=93=9C'=20=EC=A0=95=EC=A0=95,=20=EA=B9=A8=EC=A7=84=20?= =?UTF-8?q?=EC=A0=88=20=EC=9D=B8=EC=9A=A9=20=EB=91=98,=20=EB=B8=8C?= =?UTF-8?q?=EB=9E=9C=EB=93=9C=20=EA=B0=9C=EC=88=98=20=EB=A6=AC=ED=84=B0?= =?UTF-8?q?=EB=9F=B4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: qwreey --- .claude/base/lifecycle-pattern.md | 12 +++++++++--- .../pre-implementation-handtrace-round11-brief.md | 2 +- ROADMAP.md | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.claude/base/lifecycle-pattern.md b/.claude/base/lifecycle-pattern.md index 17ca6cb..d766425 100644 --- a/.claude/base/lifecycle-pattern.md +++ b/.claude/base/lifecycle-pattern.md @@ -182,7 +182,13 @@ GC에 묶이지 않음 — v1이 여기저기서 `PropertyChangedSignal`에 연 `canBound`/`canExecute`/`unbindLifetime`는 `isState`/`isObserver`처럼 핸들러 작성자가 직접 호출하는 **1급 프리미티브 연산**이라 `LifetimeHandle.bind(...)`류로 감싸면 안 됨 — `LifetimeHandle.luau` 파일 -안에 있어도 되지만 export는 평평한 함수: +안에 있어도 되지만 export는 평평한 함수. **[2026-08-28 정정, M2 첫 단위] +"평평한"은 모듈 인스턴스의 필드라는 뜻이다** — `quad.bindLifetime(inst, v)`처럼 +`isState`와 같은 자리에 놓이는 것이고, 파일 `LifetimeHandle.luau` 자신이 이 +넷을 return하는 게 아니다(파일은 `InitLifetimeHandle(module)`을 return하고, 그 +팩토리가 `module.bindLifetime = …` 에러 스텁 4종을 심는다 — 백엔드가 같은 필드를 +덮어쓴다, 아래 "`Connected` 체크는 rbvm 패턴을 그대로 베끼는 게 아니라" 절). +아래 시그니처의 이름은 그 필드 이름이다: ```lua bindLifetime(inst: any, value: any): () -- inst가 필요한 건 이것 하나뿐 @@ -738,7 +744,7 @@ quad-roblox 구현 단계에서 실측 확인 대상 — 문제가 되면 gcconn 첫 단위]** 다만 그 실 구현을 갖는 건 base가 아니라 **백엔드**다 — quad-base의 `LifetimeHandle.luau`는 `InitLifetimeHandle(module)`이 에러 스텁 4종을 모듈 인스턴스에 설치하는 인터페이스뿐이고 `Relate`를 require하지 않는다(아래 -"base가 인터페이스로만 내보내는 것" 절, `base/architecture.md` 소스 트리). +"`Connected` 체크는 rbvm 패턴을 그대로 베끼는 게 아니라" 절, `base/architecture.md` 소스 트리). 여기 한때 *"둘 다 base가 제공하는 범용 유틸로 확정"*이라 적혀 있었으나 그건 `Relate` 쪽에만 맞는 말이다. @@ -766,7 +772,7 @@ store-plan.md`가 예전에 "state 옵저빙 결과로 slot을 조작할 때 생 Destroy 시 모든 커넥션을 즉시 끊어주지만, 다른 엔진에서도 라이프사이클을 확인할 수 있어야 하므로 base는 "이 바인드가 아직 유효한가"를 묻는 람다/인터페이스만 정의하고, quad-roblox가 그 구현을 Roblox의 실제 `Connected`로 채워넣는다(구현 -주입 방식은 아래 "base 유틸은 인터페이스, 구현은 백엔드 팩토리" 절 참고). 이게 +주입 방식은 아래 "`Connected` 체크는 rbvm 패턴을 그대로 베끼는 게 아니라" 절 참고). 이게 필요한 이유: rbvm처럼 GC 트릭으로 라이프사이클을 연결하면 GC가 즉발이 아니라서 중간에 죽은 참조가 남아있을 수 있고, 그 시점에 store에 새 값이 들어오면 죽은 대상에 처리를 시도하다 터질 수 있음 — 그래서 처리 직전에 유효성을 확인. diff --git a/.claude/qa-request/pre-implementation-handtrace-round11-brief.md b/.claude/qa-request/pre-implementation-handtrace-round11-brief.md index 00bc014..59035e2 100644 --- a/.claude/qa-request/pre-implementation-handtrace-round11-brief.md +++ b/.claude/qa-request/pre-implementation-handtrace-round11-brief.md @@ -130,7 +130,7 @@ | 파일 | 내용 | 옮겨 적는 절 | |---|---|---| -| `Brand.luau` | `Brand()` 생성자 + **브랜드 인스턴스 열다섯을 이 잎 파일에**(`brand-plan.md` 스니펫이 한 자리에 선언하는 그대로 — `EpochBrand`를 `Source`/`Ref`/`GateNode`가 공유하므로 타입 모듈마다 두면 순환 require) + M2 타입의 `is*`(`isEpoch`/`isSource`/`isState`/`isStore`/`isObserver`/`isEffect`/`isBlocker`/`isRef`/`isPreRef`/`isPostRef`/`isModifier`). `isTag`/`isAttribute*`/`isTween`/`isSlot`은 그 타입의 마일스톤에서 | `base/brand-plan.md` "구현 — 인스턴스 브랜드" / `isRef` 계층 절 | +| `Brand.luau` | `Brand()` 생성자 + **브랜드 인스턴스 전부를 이 잎 파일에**(개수는 그 파일이 소스)(`brand-plan.md` 스니펫이 한 자리에 선언하는 그대로 — `EpochBrand`를 `Source`/`Ref`/`GateNode`가 공유하므로 타입 모듈마다 두면 순환 require) + M2 타입의 `is*`(`isEpoch`/`isSource`/`isState`/`isStore`/`isObserver`/`isEffect`/`isBlocker`/`isRef`/`isPreRef`/`isPostRef`/`isModifier`). `isTag`/`isAttribute*`/`isTween`/`isSlot`은 그 타입의 마일스톤에서 | `base/brand-plan.md` "구현 — 인스턴스 브랜드" / `isRef` 계층 절 | | `Relate.luau` | **기존 파일**(M1). 코드 변경 없음 — `base/relate-plan.md` "API"/"실제 구조" 대조 + 테스트만 | `base/relate-plan.md` | | `LifetimeHandle.luau` | 4종 타입 시그니처 + **미주입 에러 스텁**(영어, `error(…, 2)`) + 공유 술어 `isBoundAlive`는 백엔드 몫이라 여기 없음 | `base/lifecycle-pattern.md` "확정" 절, `module-lifecycle-plan.md` 주입 절 | | `Ref.luau` | 최소형 — `.Value`/`.Revision`/`:Set`/`:Callback`/`:WeakCallback`/`:Uncallback`, `Callbacks`(강) + `WeakCallbacks`(weak-key), `:Set` 순서 값→리비전(`bit32.bnot(-rev)`)→스냅샷 순회·함수키 dedup·thread 소진, `EpochBrand`+`RefBrand` 등록 | `base/ref-plan.md` "`Ref`는 `Epoch`를 만족한다" 절 + `H-128` | diff --git a/ROADMAP.md b/ROADMAP.md index 264a04b..b505be5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -272,7 +272,7 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검 > 여기 있는 것 전부 State-free이자 dispatch-free라 어느 쪽에도 안 걸립니다. > 이 절이 끝나야 아래 반응형 본체를 짤 수 있습니다. -- [x] **[2026-08-28 완료 — `quad-base/src/Brand.luau` + `test/spec.brand.luau`, 브랜드 인스턴스 15개와 M2 `is*` 11개가 이 잎 파일에]** `Brand.luau`(**[2026-08-21 재작성]** 인스턴스 브랜드 — `Brand()`가 +- [x] **[2026-08-28 완료 — `quad-base/src/Brand.luau` + `test/spec.brand.luau`, 브랜드 인스턴스 전부와 M2 `is*`가 이 잎 파일에 — 개수는 그 파일이 소스]** `Brand.luau`(**[2026-08-21 재작성]** 인스턴스 브랜드 — `Brand()`가 브랜드마다 weak-key 집합 하나를 들고 `:register(x)`/`:is(x)`, **다중 태깅 허용**(`Source`가 `SourceBrand`이면서 동시에 `EpochBrand`). 옛 공유 레지스트리 + `Brand.get(x) -> tag`는 @@ -629,7 +629,7 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검 호출하므로(`base/gate-plan.md` 9번이 소스 — Blocker 인스턴스를 lazy 조회하는 `getBlocker(ownerKey)`는 Blocker 메서드가 아니라 Dispatch 쪽 헬퍼다) **최소한 그 셋이 도는 형태까지는 M3(디스패치)가 요구** -- [ ] **[2026-08-28 부분 — 첫 단위분(`Relate`/`Void`/`Ref`/`is*` 11개/생명주기 4종)은 `quad-types` `Quad`에 추가됨, `Source`/`Store`/`Effect`/`Blocker`는 각 단위에서]** **[2026-08-24 `H-25` 파생, 2026-08-25 `H-80`으로 목록 확장]** +- [ ] **[2026-08-28 부분 — 첫 단위분(`Relate`/`Void`/`Ref`/`is*`/생명주기 4종)은 `quad-types` `Quad`에 추가됨, `Source`/`Store`/`Effect`/`Blocker`는 각 단위에서]** **[2026-08-24 `H-25` 파생, 2026-08-25 `H-80`으로 목록 확장]** `quad-types`의 `Quad`에 **이 마일스톤이 얹는 탑레벨 값 전부** 추가 — `Source` / `Store` / `Effect` / `Blocker` / `Relate` / **`Void`**(단일 no-op 함수 export — no-op 클로저를 돌려주는 자리는 새 클로저 대신 이것, **[2026-08-28 `H-162`]**) / **`Ref`**(최소형, 2026-08-27 `H-128`) /