base 밖으로 늘 읽을 필요 없는 참고자료(quad-v1-architecture, comparison-fusion-vide)를 새 reference/ 폴더로 분리하고, ui-shorthand-plan을 base로 승격(RoundSize 드롭+ UICorner/UIPadding/UIScale 리네임), additional-primitives-plan을 Blocker/Effect(base 승격)·Batch/Context(archive 기각)·키 기반 컬렉션 재조정(research 잔류)으로 4분할했다. component-composition-plan의 중복 역전 서사는 기존 archive 포인터로 압축하고, archive 제목 컨벤션을 [역전됨]/[기각됨]로 분화했다. tween-plan에는 retract/canExecute 구분 메모와 트윈 옵션 값 모양 논의를 추가했다. Effect가 Observer 변형인지는 임의로 결론내지 않고 question.md에 열린 질문으로 남겼다. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
136 lines
9.1 KiB
Markdown
136 lines
9.1 KiB
Markdown
# Slot — 뮤터블 자식 배열, 엄격한 단일 마운트 소유권 (base로 승격됨)
|
|
|
|
**상태**: base — 설계 방향(소유권 귀속, 재마운트 시 throw, retract=폐기)과
|
|
소스 트리 상 패키지 경계까지 확정되어 `research/`에서 승격됨(`base/
|
|
architecture.md`의 "구현 착수: 소스 트리 구조 확정" 절 참고). 원본:
|
|
`.claude/initreq/raw-userinput.md` "slot을 구현하도록 하기로 했음" 절. Fusion의
|
|
`Children` SpecialKey와 Vide의 mount 무가드 비교는 `reference/comparison-fusion-vide.md`
|
|
참고 — 결론: **두 라이브러리 어디에도 이런 엄격한 단일 마운트 가드가 없음,
|
|
quad의 진짜 개선점.**
|
|
|
|
## base/roblox 패키지 경계 (2026-08-04, 5차 라운드 확정)
|
|
|
|
Slot의 add/remove/clear 재조정 로직(추상 자식 참조 기준 — "이 자리에 뭐가
|
|
있어야 하는가"를 결정하는 순수 로직)은 `quad-base/src/Dispatch/Slot.luau`가
|
|
소유. 실제 트리 조작(Instance `Parent` 설정/`Destroy`)은 `quad-roblox/src/
|
|
Handlers/Slot.luau`가 그 위에서 적용/해제만 담당 — 다른 모든 인터페이스/구현
|
|
분리와 동일한 패턴(`base/architecture.md`의 소스 트리 참고). Slot 자체는
|
|
당연히 Instance들을 담게 될 것으로 취급.
|
|
|
|
**추가로 필요해진 핸들러**: Slot과는 별개로, `k`가 number이고 `v`가 이미
|
|
만들어진 Instance인 경우(중첩 인스턴스를 자식으로 직접 넣는 경우, 예:
|
|
`Frame { Frame {} }`)를 위한 핸들러도 필요 — `quad-roblox/src/Handlers/
|
|
InstanceChild.luau`. Slot은 "뮤터블 배열"을 다루고 이 핸들러는 "정적으로
|
|
하나 박아넣는" 더 단순한 경우라 별개로 둠.
|
|
|
|
## 개념
|
|
|
|
`add`/`remove`/`clear`/`get`/`set` 등 뮤터블 연산을 지원하는 메타 배열. 실제
|
|
바인드가 일어나면 child로 풀리고, 이 메타 배열에 CRUD를 하면 실제 children이
|
|
적절히 제어됨.
|
|
|
|
## 핵심 제약: 소유권 귀속과 단일 마운트
|
|
|
|
Slot에 들어간 요소는 **ownership이 귀속**되며 다른 곳에 마운트할 수 없게 된다.
|
|
`isMounted`를 관리해서, **한 인스턴스에 대한 다중 마운팅이 라이브러리 차원에서
|
|
절대 일어나지 않도록 강제**하는 게 v1 대비 핵심 디자인 변화. v1의 `mount()`는
|
|
별다른 강제를 안 했지만(`reference/quad-v1-architecture.md`의 mount.lua 분석 참고 —
|
|
실제로는 부모/자식 부기까지 했지만 다중 마운트 방지는 없었음), v2는 mount
|
|
함수 자체가 이 강제를 담당.
|
|
|
|
Fusion의 `Children` SpecialKey는 이걸 "특정 SpecialKey 하나의 내부 부기"로만
|
|
구현했고(재사용 가능한 1급 프리미티브가 아님), Vide는 아예 이 개념이 없어서
|
|
같은 target에 두 번 `mount()`하면 조용히 두 개의 독립 루트가 생김 — 둘 다
|
|
반면교사.
|
|
|
|
## 여럿 존재 가능, 부모가 실제 데이터 테이블만 다루면 됨
|
|
|
|
Slot은 하나의 instance 안에 여럿 존재할 수 있다. 전부 하나의 children으로
|
|
들어가지만, 실제 렌더된 instance에서 `GetChildren()`을 직접 하지 않고도 부모가
|
|
생성한 "실제 slot 데이터 테이블"만 다루면 되게 해서 **추상화 수준을 낮은 직접
|
|
바인딩에서 한 단계 떼어냄**(간접화를 통한 추상화).
|
|
|
|
## 마운트된 Slot의 재마운트는 즉시 throw (확정)
|
|
|
|
**사용자 확인 완료**: 이미 사용된(마운트된) slot을 재마운트하려 하면 **즉시
|
|
`error()`로 중단** — warn+no-op 아님. 개발 중 바로 잡아낼 수 있게 강하게
|
|
실패하는 쪽 선택. 마운트되는 순간 slot의 실제 대상은 고정된다 — 따라서
|
|
**글로벌 스코프에서 slot을 쓰는 건 그다지 좋지 않을 수 있음**(재사용/재마운트가
|
|
막히므로).
|
|
|
|
## 클래스가 슬롯을 받는 방법
|
|
|
|
"네이밍된 슬롯"이 필요한가에 대한 사용자 자문: 그냥 슬롯 바인드 테이블을
|
|
값으로 넘기면 되는 것 아닌가 — 결국 array처럼 구현된 Store라고 생각하는 게
|
|
편하다는 방향. **기울어진 결론**: 별도 "Named Slot" 개념 없이, store나
|
|
파라미터로 넘기고 그게 그냥 ref처럼 바인드되는 모양.
|
|
|
|
**확정(2026-08-04, 로드맵 인수인계 라운드)**: 위 방향 그대로 확정 — 별도
|
|
"Named Slot" 개념 없음, 슬롯 바인드 테이블을 store나 파라미터로 그냥 넘기면
|
|
ref처럼 바인드됨 — **사용자 확정**("A. 맞음. 리프노드에선 그렇게 마운트됨").
|
|
단, 이 확정은 "리프 레벨에서 슬롯 하나가 마운트되는 방식"에 한정 — 여러
|
|
Slot이 형제로 섞이는 경우의 순서 보장 문제는 별도로 열려있음, 바로 아래
|
|
참고.
|
|
|
|
### 여러 Slot이 섞일 때 순서 보장 — 열린 질문 (2026-08-04 신규)
|
|
|
|
`Frame { Slot1, 일반자식, Slot2 }`처럼 Slot과 Slot 사이에 다른 요소가 끼거나
|
|
Slot이 여럿 형제로 존재할 때, 최종 자식 순서가 저작 순서(위쪽 Slot의 요소가
|
|
항상 아래쪽 Slot의 요소보다 앞)를 안정적으로 지키는지는 아직 설계 안 됨 —
|
|
**사용자 확정**("순서도 따름, 만약 웹이라면 순서에 맞춰서 마운트 되어야 하고,
|
|
아래쪽 slot은 위쪽 slot의 요소보다 무조건 아래 있어야함... 이게 가능하냐는
|
|
생각해봐야할 이야기임"). Roblox 자체는 z-순서를 주로 `LayoutOrder`/`ZIndex`로
|
|
푸는 편이라 당장 급하게 막히는 지점은 아니지만, `architecture.md`가 명시한
|
|
"quad-base는 다른 렌더 백엔드(GTK, 웹 DOM 등)에서도 재사용 가능해야 한다"는
|
|
전제와 직결되는 문제 — DOM류 백엔드는 형제 순서 자체가 렌더 순서라 이 보장이
|
|
없으면 못 씀. **후순위 열린 질문으로 유지**, Slot 코어 로직 구현 시점에
|
|
다시 볼 것 — M0/M1 단계를 막지는 않음(Roblox 단일 백엔드로는 당장 문제
|
|
없음).
|
|
|
|
## Slot과 Store 바인드의 관계 (`retract` 순서)
|
|
|
|
Slot이 store 바인드로 들어오는 경우, pluggable 처리기에 `retract`(구 cleanup,
|
|
`base/lifecycle-pattern.md` 참고) 핸들러가 필요함 — 한번 넘어간 slot 요소가
|
|
나중에 `retract`되면 삭제되는지, 아니면 "부모의 소유이니 부모가 처리"해야
|
|
하는지 검토 필요. **기울어진 결론(잠정안, 이후 정정됨)**: 부모가 정리 정도만
|
|
미리 수행하고 다시 `process`하면 되므로, 부모에게 위임(자식 slot 요소 자체가
|
|
스스로 정리를 실행하는 게 아니라).
|
|
|
|
> **정정(2026-08-04 검증 라운드)**: 위 "부모 위임" 잠정안은 이후 **폐기**
|
|
> 쪽으로 정정됨 — 아래 "확정" 절과 `.claude/question.md`("Slot의 `retract`
|
|
> 동작이 '부모 위임' 잠정안에서 '폐기(옮기지 않음)'로 확정") 참고. 이 문단은
|
|
> 검토 과정의 히스토리로만 남겨둠, 현재 유효한 동작 아님.
|
|
|
|
이건 `base/bind-system-plan.md`의 "Store 바인드는 재실행 래핑" 확정
|
|
모델과 맞물림 — slot이 store 값으로 오면, store 바인드 핸들러가 이전 slot
|
|
상태를 `retract`하고 새 slot 상태로 다시 `process`하는 사이클을 돈다는 뜻.
|
|
Slot 핸들러 자신이 감시 중인 값(배열/스토어)이 바뀔 때 child를 갱신하는
|
|
추적(구독)도 `base/bind-system-plan.md`가 말하는 "process 함수가 다른 값
|
|
변경을 추적해도 됨" 범위에 속하고, `retract` 시점엔 그 추적만 풀면 됨 —
|
|
Destroy 시점엔 `retract`가 호출되지 않는다는 원칙(`base/lifecycle-pattern.md`)도
|
|
동일하게 적용.
|
|
|
|
**확정(2026-08-04 검증 라운드): retract되는 slot은 옮겨지지 않고 그냥 폐기된다.**
|
|
Slot은 바인딩되는 순간 그 안의 요소를 전부 own해버리는 데이터형 — 새 slot
|
|
상태로 교체될 때 이전 slot의 내용을 다른 곳으로 옮기는 경로는 없음, 그냥
|
|
버림. React의 portal(`<></>`)류로 나중에 옮길 수 있게 하는 것도 검토됐으나
|
|
**이번 마일스톤에서는 오버엔지니어링으로 판단, 하지 않음** — 필요성이 명확해지면
|
|
그때 별도로 다시 논의.
|
|
|
|
## 자식으로 넘기는 클래스 스토어
|
|
|
|
자식에게 내려주는 클래스 스토어는 부모 쪽에서 미리 만들어서 내려보내는 게
|
|
편할 것 같다는 방향 — `store<<ChildClass.Props>>` 형태로 구성된 스토어를 만들면
|
|
됨(타입 표기는 러프한 스케치, 실제 문법은 tbox의 명시적 제네릭 적용 패턴
|
|
`f<<T>>(...)` — `.claude/initreq/tbox/CLAUDE.md:40-41` — 참고해서 확정할 것).
|
|
|
|
## 열린 질문 (`.claude/question.md`에도 취합)
|
|
|
|
- 재마운트 에러 처리(throw), retract 시 폐기(옮기지 않음) 둘 다 확정. 남은 건
|
|
실제 구현 단계에서 이 "폐기" 동작이 실사용에서 불편하지 않은지 재검증하는
|
|
정도 — 설계 방향 자체는 더 이상 열려있지 않음.
|
|
- "클래스가 슬롯을 받는 방법"(Named Slot 없음)도 확정됨(위 "클래스가 슬롯을
|
|
받는 방법" 절 참고).
|
|
- **여러 Slot이 형제로 섞일 때 순서 보장**은 아직 열려있음(위 "여러 Slot이
|
|
섞일 때 순서 보장" 절 참고) — Roblox 단일 백엔드로는 급하지 않음, Slot
|
|
코어 로직 구현 시점에 재검토.
|