fix(m2): 감사 2라운드 반영 — Ref 시그니처를 문서대로 Ref<T>(T)로(H-167), mock docstring 자기모순, architecture 소스 트리에 Brand.luau + LifetimeHandle 줄 정정
Co-authored-by: qwreey <me@qwreey.moe>
This commit is contained in:
parent
9ef44ca1e2
commit
7a1c511c11
6 changed files with 34 additions and 15 deletions
|
|
@ -286,8 +286,9 @@ quad/
|
|||
│ │ ├── Slot.luau # SlotHandler — 마운트/언마운트 + 그 자리 Length/Offset 부기(값 타입 본체는 위 top-level `Slot.luau`)
|
||||
│ │ └── Modifier.luau # [2026-08-24 `H-35`] ProcessedModifierHandler — flatten이 소진한 자리를 캐치해 `setOffsetSource(None)`/`setLength(0)`만 등록하는 nop 핸들러(`base/modifier-plan.md`)
|
||||
│ ├── Void.luau # **[2026-08-28 `H-162`]** `return function() end` 한 줄 — 단일 no-op. 의존 없는 잎(`None`/`Brand`/`Relate`와 같은 급), `Dispatch/*`·핸들러·최상위 `init.luau`가 require
|
||||
│ ├── Brand.luau # **[2026-08-28 M2 첫 단위]** `Brand()` 생성자 + **브랜드 인스턴스 전부**(`EpochBrand`를 `Source`/`Ref`/`GateNode`가 공유하므로 타입 모듈마다 두면 순환 require) + `is*` 술어(타입이 생길 때 그 술어를 여기 추가, 최상위 `init.luau`가 재export). 의존 없는 잎(`base/brand-plan.md`)
|
||||
│ ├── Relate.luau # inst를 weak 키로 하는 범용 릴레이션(`SetWeak`/`GetWeak`/`SetStrong`/`GetStrong`), 비싱글톤 생성자(`base/relate-plan.md`) — 구 PerInstanceState/perInstanceState 대체
|
||||
│ ├── LifetimeHandle.luau # `bindLifetime(inst,value)`/`unbindLifetime(value)`/`canBound(value)`/`canExecute(value)` 탑레벨 함수 "인터페이스"(타입/계약만), 내부는 Relate 사용(`base/lifecycle-pattern.md`)
|
||||
│ ├── LifetimeHandle.luau # `bindLifetime(inst,value)`/`unbindLifetime(value)`/`canBound(value)`/`canExecute(value)` 탑레벨 함수 "인터페이스"(타입/계약만) — **[2026-08-28 M2 첫 단위]** `InitLifetimeHandle(module)`이 모듈 인스턴스에 영어 `level 2` 에러 스텁 4종을 설치하고 백엔드가 덮어쓴다, `Relate`는 안 쓴다(그건 아래 quad-roblox 실 구현 몫 — `base/lifecycle-pattern.md`)
|
||||
│ ├── Ref.luau # 범용 값 박스(.Value/.Revision 읽기 + :Set()/:WeakCallback()/:Callback()/:Uncallback()/:Wait(); `Epoch`를 만족 — `base/ref-plan.md`. **[2026-08-27 `H-128`]** `:Wait`·핸들러 뺀 최소형은 M2 공통 기반), `Ref(default)`를 children 배열 숫자 슬롯에 직접 놓으면 (v=Ref) 매치 핸들러가 바인드 — 별도 CreatedRef 래퍼 없음
|
||||
│ ├── PreRef.luau # Ref 런타임 재사용 + children 배열 전용, Modifier/Store 타입 차단, 호이스팅되는 pre-pass 특수화(별도 파일, `ref-plan.md` "PreRef 신설" 절, 2026-08-07 여섯 번째 세션에서 분리)
|
||||
│ ├── PostRef.luau # PreRef의 거울상 — 같은 Ref 런타임/제약, 같은 pre-pass가 수집만 하고 두 패스가 전부 끝난 뒤 fire(`ref-plan.md` "`PostRef`" 절, 2026-08-14 아홉 번째 세션 확정)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|---|---|---|---|---|---|
|
||||
| `H-165` | ① | 1 | 🟡 | `quad-types`에 `export type`을 더하면 pesde shim이 그걸 모른다 — `pesde install` 재실행 없이는 `QuadTypes.Ref`가 Unknown type | ✅ 반영(`project-setup-plan.md`) |
|
||||
| `H-166` | ① | 1 | 🟢 | `Ref.Revision` 초기값을 어느 문서도 안 정했다 | ✅ 반영(`ref-plan.md`: `0`) |
|
||||
| `H-167` | ① | 1 | 🟡 | 옮기며 `Ref<T>(default: T?)`/`.Value: T?`로 바꿔 놓았다 — 문서는 `Ref<T>(T)` 단일 파라미터, nil은 `Ref<<T?>>(nil)`로 | ✅ 코드를 문서에 맞춤(감사 2라운드) |
|
||||
|
||||
## 상세
|
||||
|
||||
|
|
@ -43,6 +44,16 @@
|
|||
- **무엇이**: 갱신식(`bit32.bnot(-rev)`)과 표만 있고 시작값이 없다.
|
||||
- **처리**: `0`으로 구현하고 그 절에 한 줄 추가. 계약이 `==`/`~=`뿐이라 값은 무관.
|
||||
|
||||
### `H-167` 🟡 — 구현이 `Ref` 시그니처를 `T?`로 바꿔 놓았다 (자기 실수)
|
||||
|
||||
- **어디서**: `quad-types/src/init.luau`·`quad-base/src/Ref.luau` vs `base/ref-plan.md`
|
||||
"제네릭 시그니처(2026-08-07 확정)".
|
||||
- **무엇이**: 문서는 `Ref<T>(T) -> Ref<T>`, `.Value: T` — nil이 올 수 있는 자리는 호출자가
|
||||
`Ref<<T?>>(nil)`로 넓힌다(그 문서의 언바인딩 절도 같은 전제). 옮기면서 `Ref()` 관용구를
|
||||
타입에서 받으려고 `default: T?`/`Value: T?`로 적었는데 그러면 `Ref(5).Value`까지
|
||||
nil 검사를 강요한다 — 문서가 이미 기각한 모양. 감사 2라운드가 발견.
|
||||
- **처리**: 코드를 문서대로 되돌림. 테스트의 `Ref()`는 `Ref<<number?>>(nil)`로.
|
||||
|
||||
## §4 ⭐ 사용자 결정이 필요한 것 (배치 회신용)
|
||||
|
||||
| 문항 | 무엇 | 선택지 | 권고 | 권고 근거 | 옛 메커니즘 복원? |
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
- `.claude/base/state-epoch-plan.md` §2: revision bump is the uint32
|
||||
wrap-around decrement `bit32.bnot(-rev)`; only `==`/`~=` are contract.
|
||||
|
||||
Single type parameter, `Ref<T>(T) -> Ref<T>` (`ref-plan.md` "제네릭
|
||||
시그니처"): a nil-able slot is widened by the caller, `Ref<<T?>>(nil)`.
|
||||
|
||||
Callback signature is `fn(value, ref)` — the second argument is the Ref
|
||||
itself, i.e. the `Epoch` a consumer (`Effect`) feeds to `EpochMap:Update`.
|
||||
Plain user callbacks just ignore it.
|
||||
|
|
@ -37,7 +40,7 @@ local WEAK_KEY_MT = { __mode = "k" }
|
|||
local RefImpl = {}
|
||||
RefImpl.__index = RefImpl
|
||||
|
||||
function RefImpl.Set<T>(self: Ref<T>, value: T?): Ref<T>
|
||||
function RefImpl.Set<T>(self: Ref<T>, value: T): Ref<T>
|
||||
self.Value = value -- (1) settle the value first
|
||||
self.Revision = bit32.bnot(-self.Revision) -- (2) before callbacks (`H-108`)
|
||||
|
||||
|
|
@ -98,7 +101,7 @@ function RefImpl.Uncallback<T>(self: Ref<T>, fn: Callback<T>): Ref<T>
|
|||
return self
|
||||
end
|
||||
|
||||
local function Ref<T>(default: T?): Ref<T>
|
||||
local function Ref<T>(default: T): Ref<T>
|
||||
local self = setmetatable({
|
||||
Value = default,
|
||||
Revision = 0,
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@
|
|||
않으므로 mock이 실제 Roblox 충실도를 가질 이유가 없다(같은 절).
|
||||
|
||||
Vide의 mock과 달리 GC-독립 userdata 프록시 트릭을 안 씀 — 그건 Roblox
|
||||
엔진 userdata의 GC 동일성을 흉내내려는 것이고, 그 동일성 문제는
|
||||
quad-roblox의 LifetimeHandle(gcconn 트릭)이 다루는 자리라 quad-base
|
||||
정적 스냅샷 테스트 범위 밖.
|
||||
엔진 userdata가 Lua 참조와 무관하게 회수·재생성되는 **엔진 GC 동일성**을
|
||||
흉내내려는 것이고, 그건 quad-roblox의 `nativeClaim`이 실기기에서 다루는
|
||||
자리라 mock 범위 밖. 반면 **생명주기 계약 로직**(`bindLifetime`/`canBound`/
|
||||
`canExecute`/`unbindLifetime`이 gcconn의 `.Connected`로 판정하는 것)은
|
||||
mock 시그널 위에서 그대로 재현한다 — 아래 `installLifetime`.
|
||||
|
||||
**[2026-08-28 M2, ROADMAP `H-97`] `installLifetime(quad)`** — 생명주기 4종
|
||||
(`bindLifetime`/`unbindLifetime`/`canBound`/`canExecute`)의 mock 백엔드.
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ do
|
|||
assert(not Quad.isPreRef(r) and not Quad.isPostRef(r) and not Quad.isState(r), "plain Ref only")
|
||||
assert((getmetatable(r.WeakCallbacks :: any) :: any).__mode == "k", "WeakCallbacks is weak-keyed")
|
||||
assert(getmetatable(r.Callbacks) == nil, "Callbacks is a plain strong set")
|
||||
local empty = Ref()
|
||||
assert(empty.Value == nil, "Ref() has no value")
|
||||
local empty = Ref(nil :: number?) -- nil이 올 수 있는 자리는 호출자가 `T?`로 넓힌다(`ref-plan.md` "제네릭 시그니처")
|
||||
assert(empty.Value == nil, "Ref(nil) has no value")
|
||||
print("PASS")
|
||||
end
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ end
|
|||
print()
|
||||
print("=== 3. 리비전 — bit32.bnot(-rev) 랩어라운드 감소: 0 → 4294967295 → 4294967294, 매번 다름 ===")
|
||||
do
|
||||
local r = Ref()
|
||||
local r = Ref(nil :: number?)
|
||||
r:Set(1)
|
||||
assert(r.Revision == 4294967295, "0 wraps to 4294967295, got " .. r.Revision)
|
||||
r:Set(2)
|
||||
|
|
@ -62,7 +62,7 @@ end
|
|||
print()
|
||||
print("=== 4. 즉시 1회 호출 — nil/미설정이어도 그 상태 그대로 (H-120), Weak도 동일 ===")
|
||||
do
|
||||
local r = Ref()
|
||||
local r = Ref(nil :: number?)
|
||||
local calls = 0
|
||||
local weakCalls = 0
|
||||
r:Callback(function(value)
|
||||
|
|
|
|||
|
|
@ -31,14 +31,16 @@ export type Relate = {
|
|||
}
|
||||
|
||||
-- `Ref<T>` 최소형(`.claude/base/ref-plan.md`, M2 `H-128`). 콜백은 `fn(value, ref)` —
|
||||
-- 두 번째 인자가 Ref 자신(= `Epoch`). `:Wait`는 M8.
|
||||
export type RefCallback<T> = (value: T?, ref: Ref<T>) -> ()
|
||||
-- 두 번째 인자가 Ref 자신(= `Epoch`). `:Wait`는 M8. **단일 타입 파라미터
|
||||
-- `Ref<T>(T)`** — nil이 들어올 수 있는 자리는 호출자가 `Ref<<T?>>(nil)`로 넓힌다
|
||||
-- (`ref-plan.md` "제네릭 시그니처").
|
||||
export type RefCallback<T> = (value: T, ref: Ref<T>) -> ()
|
||||
export type Ref<T> = {
|
||||
Value: T?,
|
||||
Value: T,
|
||||
Revision: number,
|
||||
Callbacks: { [RefCallback<T> | thread]: true }, -- 강한 해시맵 셋(+ M8 `:Wait` 대기자)
|
||||
WeakCallbacks: { [RefCallback<T>]: true }, -- weak-key 테이블
|
||||
Set: (self: Ref<T>, value: T?) -> Ref<T>,
|
||||
Set: (self: Ref<T>, value: T) -> Ref<T>,
|
||||
Callback: (self: Ref<T>, fn: RefCallback<T>) -> Ref<T>,
|
||||
WeakCallback: (self: Ref<T>, fn: RefCallback<T>) -> Ref<T>,
|
||||
Uncallback: (self: Ref<T>, fn: RefCallback<T>) -> Ref<T>,
|
||||
|
|
@ -55,7 +57,7 @@ export type Quad = {
|
|||
-- `Effect`/`Blocker`는 각 단위에서 추가된다.
|
||||
Relate: () -> Relate,
|
||||
Void: (...any) -> (),
|
||||
Ref: <T>(default: T?) -> Ref<T>,
|
||||
Ref: <T>(default: T) -> Ref<T>,
|
||||
|
||||
-- 생명주기 4종 — quad-base는 인터페이스(에러 스텁)만, 백엔드가 주입
|
||||
-- (`.claude/base/lifecycle-pattern.md`). `canBound(v) == not canExecute(v)`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue