decide(base): Dispatch 탑레벨 싱글톤 확정, 네이밍 케이싱 컨벤션 신설, Handler 세 번째 카테고리 명문화
Ref/Observer/PreRef leaf Handler 위치를 quad-base로 확정하며 question.md 미결 항목 해소.
This commit is contained in:
parent
c865e99860
commit
9ab63863a9
6 changed files with 209 additions and 14 deletions
|
|
@ -85,7 +85,14 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo
|
|||
`quad-roblox`가 담당.
|
||||
13. **모듈은 기본 싱글톤, `New()`는 나중에.** 한 Lua 스레드에서 Roblox/비-Roblox
|
||||
프로바이더를 동시에 쓸 일이 거의 없을 거라 판단 — 필요해지면 그때 `New()`
|
||||
추가.
|
||||
추가. **메커니즘도 이미 정해짐(2026-08-08 두 번째 세션, 새 설계 아니라
|
||||
기존 패턴의 자연스러운 연장)**: v1처럼 `require`를 감싸 `Init(QuadId?)`로
|
||||
격리 인스턴스를 만드는 방식은 안 씀 — 대신 지금 있는 "팩토리가
|
||||
`BaseModule`을 뮤테이션" 패턴(14번) 그대로, `New()`가 생기면 매번 새
|
||||
`BaseModule` 테이블을 만들어 팩토리로 채우는 것뿐. Dispatch의 handler
|
||||
레지스트리를 포함해 지금 module-level state로 사는 모든 것(`_initializedBy`
|
||||
마커, Dispatch 레지스트리 등)이 자동으로 테이블별 스코핑됨 — 상세 근거는
|
||||
`base/bind-system-plan.md`의 "Dispatch는 프리미티브가 아니다" 절.
|
||||
14. **pluggable 초기화는 팩토리 함수로.** rbvm처럼 네임스페이스 하나하나 수동
|
||||
init 하는 방식(`base/lifecycle-pattern.md` 5번 항목 참고)은 피하고,
|
||||
`InitRoblox(Module)` 같은 팩토리 함수가 생성된 모듈을 뮤테이션하는 도구를
|
||||
|
|
@ -132,6 +139,7 @@ quad/
|
|||
│ │ ├── init.luau # process/retract 엔진, isHandlable 우선순위 스캔
|
||||
│ │ ├── Handler.luau # 핸들러 계약 타입(isHandlable/priority/process/retract)
|
||||
│ │ ├── StoreBind.luau # store 값 재귀 재실행 로직(범용, 엔진 무관)
|
||||
│ │ ├── Leaf.luau # (i:number, v=Ref/Observer/PreRef) children-array leaf 매칭 Handler, StoreBind와 같은 층위(범용/엔진무관, 2026-08-08 두 번째 세션 확정)
|
||||
│ │ └── Slot.luau # add/remove/clear 재조정 로직(추상 자식 참조 기준)
|
||||
│ ├── Relate.luau # inst를 weak 키로 하는 범용 릴레이션(`SetWeak`/`GetWeak`/`SetStrong`/`GetStrong`), 비싱글톤 생성자(`base/relate-plan.md`) — 구 PerInstanceState/perInstanceState 대체
|
||||
│ ├── LifetimeHandle.luau # `bindLifetime(inst,value)`/`canExecute(inst,value)` 탑레벨 함수 "인터페이스"(타입/계약만), 내부는 Relate 사용(`base/lifecycle-pattern.md`)
|
||||
|
|
@ -161,6 +169,45 @@ quad/
|
|||
Tween/existing-instance-bind는 여전히 `research/`에 남아있고 이 구조 확정을
|
||||
막지 않음(`purity-and-effects-plan.md`는 이미 `base/`로 승격 완료).
|
||||
|
||||
## 코드 스타일 — 네이밍 케이싱 (2026-08-08 두 번째 세션 신설)
|
||||
|
||||
지금까지 각 문서가 예시 코드를 쓰며 암묵적으로 따라온 패턴을 사용자가
|
||||
명시적 규칙으로 정리해달라고 요청 — 실제로 지금까지 나온 모든 이름이
|
||||
예외 없이 따르는 규칙이라 새로 뭘 바꿀 필요는 없고, 그냥 문서화만:
|
||||
|
||||
- **대문자 시작(PascalCase)** — 다음 세 가지, 공통점은 전부 **어떤
|
||||
프리미티브 타입 자신의 공개 어휘**라는 것:
|
||||
1. 프리미티브 타입 생성자, `Type(args)` 스타일: `Source(default)`/
|
||||
`Ref(default)`/`Store({defaults})`/`Modifier()`/`Relate()`/
|
||||
`Effect(fn, state?)`/`PreRef(default)`.
|
||||
2. 그 인스턴스의 콜론 메서드: `state:Get()`/`:With(...)`/`:Compute(fn)`/
|
||||
`:Observer(fn)`/`:Apply(factory)`/`:Peek(key)`, `source:Set(v)`/`:Emit()`,
|
||||
`ref:Set(v)`/`:Callback(fn)`/`:Wait(thread?)`, `observer:Subscribe()`/
|
||||
`:Unsubscribe()`, `relate:SetWeak(...)`/`:GetWeak(...)`/`:SetStrong(...)`/
|
||||
`:GetStrong(...)`, `mod:FontSize(...)`(필드 setter 체이닝).
|
||||
3. 프리미티브 타입 자신의 네임스페이스에 달린 정적 결합 함수 —
|
||||
`Modifier.Override(mod1, mod2, ...)`가 유일한 현재 사례. 콜론 메서드는
|
||||
아니지만(여러 Modifier를 동등한 인자로 받아야 해서 self 하나로 안
|
||||
됨) `Modifier` 타입 고유의 공개 연산이라는 점에서 1/2과 같은 부류 —
|
||||
`Modifier()` 생성자와 같은 이유로 대문자.
|
||||
- **소문자 시작(camelCase)** — 특정 프리미티브 타입 하나에 안 묶이고 여러
|
||||
타입을 넘나드는 범용 유틸(`isState`/`isSource`/`isRef`/`isPreRef`/
|
||||
`isModifier`/`isObserver`/... `Brand` 절), 생명주기 게이트(`canExecute`/
|
||||
`bindLifetime`, `base/lifecycle-pattern.md`), 그리고 **프리미티브가
|
||||
아닌** 내부 엔진/레지스트리의 네임스페이스 멤버(`Dispatch.process`/
|
||||
`getHandler`/`addHandler`/`drive`, `Brand.set`/`get`) — 이 셋은 "타입
|
||||
고유의 어휘"가 아니라 여러 타입에 걸쳐 쓰이거나(`isX`류) 프리미티브
|
||||
자체가 아닌 것(Dispatch/Brand는 `Type(args)` 생성자가 없는 내부 엔진)의
|
||||
구성원이라 PascalCase 대상이 아님. Handler 계약 필드(`isHandlable`/
|
||||
`priority`/`process`/`retract`)도 여기 속함 — 이건 애초에 "함수"라기보다
|
||||
구현체가 채워 넣는 구조체 필드.
|
||||
- **경계 판단 기준**: 새 이름을 지을 때 "이게 특정 프리미티브 타입 하나의
|
||||
전용 소유물인가?"로 물으면 됨 — 그렇다면 대문자(생성자/메서드/그
|
||||
타입의 정적 결합 함수), 아니면(범용 유틸이거나 프리미티브가 아닌 엔진
|
||||
소속) 소문자. `Dispatch`/`Brand`가 프리미티브가 아닌 이유는
|
||||
`base/bind-system-plan.md`의 "Dispatch는 프리미티브가 아니다" 절/
|
||||
`base/store-semantics.md`의 "세 번째 카테고리 — Handler" 절 참고.
|
||||
|
||||
## 테스트 전략: quad-base용 최소 mock (2026-08-04)
|
||||
|
||||
**결정**: quad-base 테스트는 Vide 선례(`initreq/vide/test/mock.luau`, 약
|
||||
|
|
|
|||
|
|
@ -262,6 +262,57 @@ NoneHandler.process(inst, k, v) = process(inst, k, nil) -- 재귀 재호출
|
|||
통해 담당자 기록을 갱신하게만 해두면(`Dispatch.drive`가 별도로 기록 안
|
||||
하고 `Dispatch.process` 내부에 위임) 자연히 해소됨.
|
||||
|
||||
### Dispatch는 프리미티브가 아니다 — 탑레벨 싱글톤 확정 (2026-08-08 두 번째 세션)
|
||||
|
||||
`Dispatch.process`/`getHandler`/`addHandler`/`drive`를 `Source`/`Ref`/`Store`/
|
||||
`Modifier`처럼 생성자가 있는 프리미티브(예: `Dispatch()`로 인스턴스를 여러 개
|
||||
만들 수 있는 것)로 바꿔야 하는지 검토 후 **기각, 지금 형태(모듈 require로
|
||||
바로 닿는 flat 탑레벨 함수) 유지로 확정**:
|
||||
|
||||
- **재귀 재-dispatch가 요구하는 필연** — Tween/`NoneHandler`/`Dispatch/
|
||||
StoreBind.luau` 전부 자기 `process` 안에서 다시 `Dispatch.process(inst,k,
|
||||
realv)`를 호출함(위 "확정된 디스패치 모델"/"`None` 센티널" 절). 이게
|
||||
성립하려면 Dispatch가 `canExecute`/`bindLifetime`(`base/
|
||||
lifecycle-pattern.md`)과 똑같이 require 한 번으로 바로 닿는 안정된
|
||||
전역이어야 함 — 인스턴스화 가능한 프리미티브로 만들면 모든 Handler
|
||||
등록/호출 경로에 Dispatch 핸들을 인자로 계속 실어날라야 하는 스레딩
|
||||
비용이 생기는데, 지금 형태는 그 비용을 아예 안 짐.
|
||||
- **순환참조로 보이는 건 착시 — 실제로는 단방향.** "Handler"라는 말이 두
|
||||
가지를 가리켜서 헷갈릴 수 있음: (a) `Handler.luau`의 **타입 계약**
|
||||
(`isHandlable`/`priority`/`process`/`retract` 시그니처만 있는 순수 leaf,
|
||||
Dispatch를 몰라도 됨) vs (b) `StoreBind.luau`/`Tween.luau`처럼 그 계약을
|
||||
**구현하는 concrete 값 모듈**(재귀호출 위해 Dispatch를 require함). 의존
|
||||
방향은 항상 한쪽으로만 흐름 — `Handler.luau`(leaf) ← `Dispatch/init.luau`
|
||||
(`addHandler(h: Handler)`가 `Handler` 타입만 참조) ← `StoreBind.luau`/
|
||||
`Tween.luau`(재귀호출 위해 Dispatch를 참조). `Handler.luau` 자신이
|
||||
Dispatch를 되받아 참조하는 일이 없으니 타입 레벨에서도 사이클이 안 생김.
|
||||
런타임에서도 마찬가지 — 어떤 handler의 `process`든 실제로 *호출*되는
|
||||
시점은 컴포넌트가 렌더되는 시점이라, 그때는 이미 Dispatch 모듈 require가
|
||||
완전히 끝나있어 부트스트랩 문제도 없음.
|
||||
- **quad-base 자신의 기본 핸들러도 같은 레지스트리를 씀** — `NoneHandler`,
|
||||
`Dispatch/StoreBind.luau`("범용, 엔진 무관")뿐 아니라, children 배열
|
||||
숫자 슬롯에 `Ref`/`Observer`/`PreRef`를 직접 놓는 leaf 값을 매칭하는
|
||||
Handler도 여기 속함(`inst`를 `any`로 취급, 엔진 특정 API 불필요 —
|
||||
`.claude/question.md`가 2026-08-08 세션에 "quad-base/quad-roblox 중
|
||||
어디 사는지 미확인"으로 남겨뒀던 항목, 이 결론으로 해소: quad-base,
|
||||
`Dispatch/Leaf.luau`, `Dispatch.addHandler`로 등록). quad-roblox의
|
||||
Property/Event/Tween 핸들러도 **같은** `Dispatch.addHandler` 레지스트리에
|
||||
등록됨 — base 기본 핸들러와 backend 핸들러가 별도 경로로 안 갈리고
|
||||
전부 하나의 우선순위 스캔을 공유.
|
||||
- **모듈 재생성(`New()`)과의 관계 — 새 설계 불필요, 이미 있는 선례로 자연히
|
||||
풀림.** v1처럼 `require`를 감싸 `Init(QuadId?)`로 격리 인스턴스를 만드는
|
||||
방식은 안 씀(위 "확정된 것" 절 — id 기반 조회 자체가 Ref로 대체되며
|
||||
기각됨). 대신 이미 확정된 "base 유틸은 인터페이스, 실제 구현은 팩토리가
|
||||
`BaseModule`을 뮤테이션해서 주입"(`RobloxFactory(BaseModule)`) 패턴을
|
||||
그대로 따름 — Dispatch의 handler 레지스트리도 `BaseModule` 테이블에
|
||||
딸린 state 중 하나일 뿐이라, `_initializedBy` 마커에 대해 이미 확정된
|
||||
것과 완전히 같은 논리가 적용됨(위 "base 유틸은 인터페이스" 절, "`New()`가
|
||||
생기면 각 인스턴스가 별도 테이블이 되므로 이 마커도 테이블별로 독립적으로
|
||||
스코핑됨, 재설계 불필요"). `New()`가 실제로 생기면 그 시점에 BaseModule
|
||||
전체를 인스턴스별 테이블로 만드는 메커니즘에 Dispatch도 자연히 같이
|
||||
딸려가고, 호출부는 `module.Dispatch.process(...)`처럼 그 인스턴스
|
||||
테이블을 통해 접근하게 됨 — 지금 미리 프리미티브화해둘 이유가 없음.
|
||||
|
||||
## Store 바인드는 특수 경우인가, 아니면 pluggable 바인드를 재실행하는 래핑인가
|
||||
|
||||
사용자 원 메모: "스토어 바인드는 특수 경우로 둘지, 아니면 다른 pluggable 바인드를
|
||||
|
|
|
|||
|
|
@ -90,6 +90,22 @@ pull-recompute)·`:Compute` 인자 규칙·State 쓰기 금지·`Source` 독립
|
|||
속하는지가 생성자 모양(자유 함수 팩토리 vs 원천에 대한 메소드)을
|
||||
결정하는 기준으로 쓸 수 있음.
|
||||
|
||||
**세 번째 카테고리 — Handler는 둘 중 어디에도 안 낌(2026-08-08 두 번째
|
||||
세션, 명시화).** `Handler`(`isHandlable`/`priority`/`process`/`retract`
|
||||
4종 계약, `base/bind-system-plan.md` "핸들러 계약" 절)는 위 분류가 다루는
|
||||
"quad 사용자가 직접 다루는 리액티브 값"이 아니라 **그 자체로는 구현체가
|
||||
없는 순수 타입 계약**이라 애초에 이 분류표의 대상이 아님 — Source/Ref처럼
|
||||
`Type(args)` 자유 함수로 인스턴스를 만들 수도 없고(계약을 만족하는 값은
|
||||
`PropertyHandler`/`TagHandler`/`Dispatch/StoreBind.luau`의 `NoneHandler`처럼
|
||||
**구현하는 쪽**이 리터럴 테이블로 직접 채워 넣는 것), State/Observer처럼
|
||||
어떤 원천에 종속된 파생물도 아님(애초에 "원천"이라는 개념 자체가 안 맞음).
|
||||
Handler는 quad 사용자가 아니라 **백엔드/핸들러 구현자가 채우는 확장
|
||||
지점**이라는 완전히 다른 축의 개념이라, 여기 분류를 "왜 Handler가
|
||||
빠졌는지" 궁금해할 필요 없음 — 프리미티브 분류가 불완전한 게 아니라
|
||||
Handler가 애초에 다른 층위. 관련해서 Handler를 담는 엔진(`Dispatch`) 자체가
|
||||
왜 프리미티브가 아니라 탑레벨 싱글톤인지는 `base/bind-system-plan.md`의
|
||||
"Dispatch는 프리미티브가 아니다" 절 참고.
|
||||
|
||||
과거 "미해결로 남은 것"으로 적었던 두 항목도 모두 해소됨: `:Compute`의
|
||||
캐싱/무효화 전략은 push-invalidate(신호만)/pull-recompute(`Get()` 시점)로
|
||||
확정(`base/bind-system-plan.md` "전파 모델" 절), `store "key"` 커링의 타입
|
||||
|
|
|
|||
|
|
@ -156,17 +156,14 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만**
|
|||
우선순위 스캔 동률/매치실패 처리, `:Compute`의 `previous` 인자가
|
||||
오버엔지니어링일 수 있음, UI shorthand의 기존 UICorner 매칭 기준 등)는
|
||||
`pre-implementation-audit.md` 본문 참고.
|
||||
- **[신규, 2026-08-08 세션, 미확인]** `Frame { ref }`/`Frame { observer }`처럼
|
||||
children 배열 숫자 슬롯에 직접 놓는 leaf 값을 실제로 매칭·바인드하는
|
||||
Handler(`(i:number, v=Ref/Observer/PreRef)`)가 어느 패키지에 사는지 —
|
||||
`base/bind-system-plan.md:301-303,852-854`는 "`(v=Ref)` 매치 핸들러가
|
||||
처리한다"/"`isObserver`로 판별해 라이프사이클에 묶어준다"까지만 서술하고
|
||||
파일 배치는 안 함(`architecture.md` 소스트리에도 이 Handler가 이름으로
|
||||
안 나와 있음). 제안(미확정): `Ref`/`Observer`/`PreRef` 전부 `inst`를 `any`로
|
||||
취급하는 engine-agnostic 타입이고 process가 `v:Set(inst)`/`LifetimeHandle`
|
||||
위임 정도만 하면 되니, `Dispatch/StoreBind.luau`(이미 "범용, 엔진 무관"으로
|
||||
분류)와 같은 층위로 `quad-base`에 두는 게 맞아 보임 — `quad-roblox/Handlers/`가
|
||||
아니라. 사용자 확인 필요, 아직 base에 반영 안 함.
|
||||
- **[해소됨, 2026-08-08 두 번째 세션]** `Frame { ref }`/`Frame { observer }`처럼
|
||||
children 배열 숫자 슬롯에 직접 놓는 leaf 값을 매칭·바인드하는 Handler
|
||||
(`(i:number, v=Ref/Observer/PreRef)`)의 패키지 배치 — 원래 제안대로
|
||||
`quad-base`, `Dispatch/Leaf.luau`(이미 있던 `Dispatch/StoreBind.luau`와
|
||||
같은 층위)로 확정. Dispatch 자체가 프리미티브가 아니라 탑레벨 싱글톤이고
|
||||
base 기본 핸들러와 quad-roblox 백엔드 핸들러가 같은 `Dispatch.addHandler`
|
||||
레지스트리를 공유한다는 결론과 함께 나온 것 — `base/bind-system-plan.md`
|
||||
"Dispatch는 프리미티브가 아니다" 절, `base/architecture.md` 소스트리 참고.
|
||||
|
||||
### 3. 낮은 우선순위
|
||||
|
||||
|
|
|
|||
84
CLAUDE.md
84
CLAUDE.md
|
|
@ -1443,10 +1443,90 @@ leaf Handler가 quad-base/quad-roblox 중 어디 사는지.** 3번을 풀다가
|
|||
내부 Observer와는 무관) — 제 제안(엔진 특정 API가 필요 없으니 quad-base,
|
||||
`Dispatch/StoreBind.luau`와 같은 층위)은 사용자 확인을 못 받은 채 대화가
|
||||
3번으로 넘어감. `question.md` 2번에 미확인으로 남김, base에는 반영 안 함
|
||||
— 다음에 확인 필요.
|
||||
— 다음에 확인 필요. **[해소됨, 같은 날 두 번째 세션]** 아래 절 참고 —
|
||||
제 원래 제안 그대로 quad-base로 확정.
|
||||
|
||||
**다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터). M0/M2 스파이크 코드가
|
||||
검증해야 할 것 목록에 `Relate`의 lazy 서브테이블 생성/공유 메타테이블
|
||||
전략, `bindLifetime`/`canExecute`의 실제 gcconn 트릭이 새로 추가됨 —
|
||||
`base/lifecycle-pattern.md`/`base/relate-plan.md`의 "실측 필요" 캐비엇
|
||||
참고. 4번(Ref/Observer leaf Handler 위치)도 M2 착수 전 확인 대상.
|
||||
참고.
|
||||
|
||||
## 2026-08-08 두 번째 세션 — Dispatch는 프리미티브가 아니라 탑레벨 싱글톤 확정,
|
||||
네이밍 케이싱 컨벤션 신설, Handler를 세 번째 카테고리로 명문화
|
||||
|
||||
같은 날 이어진 세션. 사용자가 위 4번 미결 항목("Ref/Observer/PreRef leaf
|
||||
Handler가 어디 사는지")을 다시 짚으며 시작 — "Handler도 실제 런타임 값이
|
||||
생기는 요소인데 왜 프리미티브로 안 다루나", "Dispatch는 어떻게 되는 거냐,
|
||||
State 핸들러 안에서 `getHandler`를 부르려면 Dispatch가 이미 존재해야
|
||||
하는데" 하는 질문으로 확장돼 Dispatch 자체의 정체성(싱글톤 top-level
|
||||
함수 모음 vs 인스턴스화 가능한 프리미티브) 논의로 이어짐. 네 가지로 정리,
|
||||
전부 `base/bind-system-plan.md`/`base/store-semantics.md`/
|
||||
`base/architecture.md`/`question.md`/`ROADMAP.md`에 반영 완료:
|
||||
|
||||
**1. Dispatch는 프리미티브가 아니라 탑레벨 싱글톤 — 확정, 지금 형태 유지.**
|
||||
`Dispatch.process`/`getHandler`/`addHandler`/`drive`를 `Source`/`Ref`처럼
|
||||
생성자 있는 프리미티브로 바꿀지 검토했으나 기각. 근거: (a) Tween/
|
||||
`NoneHandler`/`StoreBind`가 자기 `process` 안에서 다시 `Dispatch.process`를
|
||||
재귀 호출해야 해서, `canExecute`/`bindLifetime`처럼 require 한 번으로 바로
|
||||
닿는 안정된 전역이어야 함 — 프리미티브화하면 모든 Handler 호출 경로에
|
||||
Dispatch 핸들을 실어날라야 하는 스레딩 비용이 생기는데 지금은 그 비용이
|
||||
없음. (b) 사용자가 우려한 "Handler가 Dispatch 원하고 Dispatch가 Handler
|
||||
원해서 순환참조" 문제는 착시로 확인됨 — "Handler"가 (i) `Handler.luau`의
|
||||
순수 타입 계약(leaf, Dispatch를 몰라도 됨)과 (ii) 그 계약을 구현하는
|
||||
concrete 값 모듈(`StoreBind.luau`류, 재귀호출 위해 Dispatch를 참조)
|
||||
두 가지를 가리켜서 헷갈렸던 것 — 의존 방향은 `Handler.luau` ←
|
||||
`Dispatch/init.luau` ← `StoreBind.luau`로 항상 한쪽으로만 흐름, 사이클
|
||||
없음. (c) 모듈 재생성(`New()`)과의 관계도 새 설계가 필요 없음 — 이미
|
||||
확정된 "팩토리가 `BaseModule`을 뮤테이션" 패턴을 그대로 따르면
|
||||
`_initializedBy` 마커에 대해 이미 나왔던 결론("`New()`가 생기면 각
|
||||
인스턴스가 별도 테이블이 되므로 자연히 스코핑됨")이 Dispatch의 handler
|
||||
레지스트리에도 그대로 적용됨. v1처럼 `require`를 감싸는 `Init(QuadId?)`
|
||||
방식은 채택 안 함(id 기반 조회 자체가 Ref로 대체되며 이미 기각된 패턴).
|
||||
`base/bind-system-plan.md`의 "Dispatch는 프리미티브가 아니다" 절,
|
||||
`base/architecture.md` 13번 항목에 반영.
|
||||
|
||||
**2. quad-base 기본 핸들러도 전부 같은 `Dispatch.addHandler` 레지스트리를
|
||||
공유 — Ref/Observer/PreRef leaf Handler 위치 확정.** `NoneHandler`/
|
||||
`Dispatch/StoreBind.luau`뿐 아니라, children 배열 숫자 슬롯에 `Ref`/
|
||||
`Observer`/`PreRef`를 직접 놓는 leaf 값을 매칭하는 Handler도 같은 부류 —
|
||||
`inst`를 `any`로 취급하고 엔진 특정 API가 필요 없으니 quad-base,
|
||||
`Dispatch/Leaf.luau`로 확정(위 4번 미결 항목 해소). quad-roblox의
|
||||
Property/Event/Tween 핸들러도 **같은** 레지스트리에 등록되므로, base
|
||||
기본 핸들러와 backend 핸들러가 별도 경로로 안 갈리고 하나의 우선순위
|
||||
스캔을 공유한다는 것도 명시적으로 확인됨. `architecture.md` 소스트리에
|
||||
`Dispatch/Leaf.luau` 반영, `question.md`/`ROADMAP.md` M2 동기화.
|
||||
|
||||
**3. Handler는 "독립 프리미티브 vs 파생 데이터" 분류의 세 번째, 별개
|
||||
카테고리 — 명문화.** 2026-08-06 후속 세션이 확정한 분류(Source/Ref/Store/
|
||||
Modifier=독립 프리미티브, State/Observer=파생 데이터)에 Handler가 왜
|
||||
안 끼는지 사용자가 재확인 요청 — 이유: Handler는 그 자체로 구현체가
|
||||
없는 **순수 타입 계약**이라 quad 사용자가 다루는 리액티브 값이 아님,
|
||||
계약을 만족하는 값(`PropertyHandler`류)은 항상 **구현하는 쪽**(base
|
||||
자신의 기본 핸들러 또는 quad-roblox 백엔드)이 채워 넣는 것이지 `Type(args)`
|
||||
자유 함수로 사용자가 만드는 게 아니고, State/Observer처럼 어떤 원천에
|
||||
종속된 파생물도 아님. `base/store-semantics.md`의 "일반 원칙" 절 뒤에
|
||||
"세 번째 카테고리 — Handler" 절로 반영.
|
||||
|
||||
**4. 네이밍 케이싱 컨벤션 신설 — 지금까지 나온 모든 이름이 이미 따르고
|
||||
있던 규칙을 문서화만 함, 리네임 없음.** 사용자 관찰: "탑레벨 함수는
|
||||
변수처럼 소문자 시작, 프리미티브 타입의 메서드는 대문자 시작(파스칼
|
||||
케이싱)이 맞아 보인다"는 규칙 제안 — 검증 결과 기존 이름 전체(생성자
|
||||
`Source`/`Ref`/`Store`/`Modifier`/`Relate`/`Effect`, 콜론 메서드
|
||||
`:Get`/`:With`/`:Set`/`:Apply`/`:Subscribe`류는 전부 대문자, `canExecute`/
|
||||
`bindLifetime`/`isState`류/`Dispatch.process`류/`Brand.set`류는 전부
|
||||
소문자)가 이미 예외 없이 이 규칙을 따르고 있었음이 확인됨. 유일하게
|
||||
애매해 보였던 `Modifier.Override(mod1, mod2, ...)`(콜론 아니고 dot-access
|
||||
인데 대문자)도 규칙 위반이 아니라 세 번째 하위 규칙으로 설명됨 — 콜론
|
||||
메서드는 아니지만 **`Modifier` 타입 자신의 네임스페이스에 달린 정적
|
||||
결합 함수**라 "그 프리미티브 타입 고유의 공개 어휘"라는 점에서 생성자/
|
||||
메서드와 같은 부류. 반대로 `Dispatch.process`/`Brand.set`이 소문자인
|
||||
이유는 `Dispatch`/`Brand`가 애초에 `Type(args)` 생성자가 없는 프리미티브가
|
||||
**아닌** 내부 엔진/레지스트리라서. 최종 판단 기준: "이 이름이 특정
|
||||
프리미티브 타입 하나의 전용 소유물인가?" — 그렇다면 대문자, 아니면(여러
|
||||
타입에 걸친 범용 유틸이거나 비-프리미티브 엔진 소속) 소문자. `base/
|
||||
architecture.md`의 "코드 스타일 — 네이밍 케이싱" 절 신설.
|
||||
|
||||
**다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터) — 이번 세션도 순수
|
||||
설계/문서 정리라 M0 착수 우선순위 자체는 그대로. 위 2026-08-08 첫 세션이
|
||||
남긴 "M0/M2 스파이크 검증 목록"에 새로 추가되는 항목 없음.
|
||||
|
|
|
|||
|
|
@ -109,6 +109,10 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
|
|||
걸러내기(no-op이라도 필드 자체는 항상 정의 — `Dispatch.process`가 핸들러
|
||||
교체 시 nil 체크 없이 호출, `base/bind-system-plan.md` "핸들러 계약"
|
||||
절, 2026-08-08 세션)
|
||||
- [ ] `Dispatch/Leaf.luau` — `(i:number, v=Ref/Observer/PreRef)` children-array
|
||||
leaf 매칭 Handler, `StoreBind.luau`와 같은 층위(범용/엔진무관) —
|
||||
quad-base 소속으로 확정(2026-08-08 두 번째 세션, `base/
|
||||
bind-system-plan.md` "Dispatch는 프리미티브가 아니다" 절)
|
||||
- [ ] mock 대상 테스트
|
||||
|
||||
## M3 — Store/State/Source
|
||||
|
|
|
|||
Loading…
Reference in a new issue