decide(bind-system): OnChange 특수 키 신설 - GetPropertyChangedSignal 바인딩, 제네릭 없이 확정
이벤트 문자열 키 패턴이 GetPropertyChangedSignal엔 안 통해서(프로퍼티 이름이 값 세팅 키와 겹침) 별도 OnChange(name) DI 키를 신설. Attribute와 달리 제네릭 타입 파라미터 없이 콜백 타입은 인라인 명시 - 이벤트 바인딩과 같은 급의 타입 안전성 트레이드오프. 전부 quad-roblox 소속, State<function>은 기존 이벤트 store-bind 메커니즘 재사용. 프로퍼티별 정적 코드 생성 안은 규모 폭발로 기각.
This commit is contained in:
parent
7466e9216b
commit
2be4fffeca
7 changed files with 154 additions and 2 deletions
|
|
@ -40,6 +40,7 @@
|
|||
| `ui-shorthand-plan.md` | **[2026-08-07 `research/`에서 승격]** `UICorner`/`UIPadding`/`UIScale` 인라인 편의 키 — 이름(v1 `Corner`/`PaddingAll`/`Scale`에서 Modifier 필드명과 안 겹치게 `UI` 프리픽스로 확정)·메커니즘(Handler)·패키지 배치(quad-roblox 코어)·store-bind 가능성까지 전부 확정. 이미지 라운드 트릭(`RoundSize`)은 드롭 — `archive/ui-shorthand-roundsize-dropped.md` 참고. `v=nil`이면 `process` 자신이 만든 자식 제거(`retract` 아님) |
|
||||
| `tag-plan.md` | **[2026-08-08 세 번째 세션 재설계]** `Tag(...)` — array-part 값 객체, `Modifier`와 같은 immutable clone 체이닝(`:Added`/`:Removed`/`:Contains`/`:Apply`/`Merged`), `CollectionService` 글루만 quad-roblox. 이제 `retract`가 의미 있음(타입이 바뀌면 전체 삭제, 같은 Tag끼리는 `process`가 diff). 구 해시 파트 boolean 모델은 `archive/tag-hash-key-model-reversed.md` |
|
||||
| `attribute-plan.md` | **[2026-08-07 여덟 번째 세션 신설]** `[Attribute "Name"]` — `SetAttribute(name, nil)`이 네이티브 지우기라 `None` 센티널과 가장 깔끔하게 맞아떨어짐, `retract` 불필요. 타입 파라미터화 이름(`Attribute<T>` vs `BooleanAttribute`류)만 미확정 |
|
||||
| `onchange-plan.md` | **[2026-08-10 세션 신설]** `OnChange(name)` — `GetPropertyChangedSignal` 바인딩 전용 DI 키, `Attribute`와 달리 제네릭 타입 파라미터 없음(콜백 타입은 인라인 명시, 이벤트 바인딩과 같은 급 트레이드오프). 전부 quad-roblox(`Handlers/OnChange.luau`), `State<function>`은 기존 이벤트 store-bind 메커니즘 재사용 |
|
||||
| `relate-plan.md` | **[2026-08-08 신설]** `Relate` — `inst`를 weak 키로 하는 범용 릴레이션 프리미티브(`SetWeak`/`GetWeak`/`SetStrong`/`GetStrong`, 비싱글톤 생성자). 구 `base.perInstanceState(inst)` placeholder를 대체·정식 승격, `lifecycle-pattern.md`의 `bindLifetime`/`canExecute`가 그 위에 얹힘 |
|
||||
|
||||
## `reference/` — 온디맨드 참고 자료 (2026-08-07 신설)
|
||||
|
|
@ -81,6 +82,7 @@
|
|||
| `keyed-collection-state-method-rejected.md` | **[기각됨, 2026-08-09 코퍼스 정리 신설]** 키 기반 동적 컬렉션 재조정 프리미티브를 `state:Keyed(...)` State 메소드로 두려던 초안 — Source 미사용 컴포넌트가 접근 못 한다는 반례로 철회, 현재는 자유 함수로 확정 |
|
||||
| `debug-channel-replicatedstorage-rejected.md` | **[기각됨, 2026-08-09 코퍼스 정리 신설]** quad-debug 채널을 `ReplicatedStorage`에 자동 생성하던 초안 — 게임 트리 오염 부작용으로 기각, quad 모듈 자신의 트리+`CollectionService` 태그로 대체 |
|
||||
| `tween-special-bind-key-reversed.md` | **[역전됨, 2026-08-10 신설]** 구 Tween 모델(`[Tween(key,tweenData...)] = storeValue` 특수 bind key, 우선순위 최상위 Dispatch 핸들러) — 값-레벨 `Tween<T>` 래퍼 모델로 완전히 대체됨(`research/tween-plan.md`) |
|
||||
| `onchange-per-property-codegen-rejected.md` | **[기각됨, 2026-08-10 신설]** `OnChange.PropertyName` 프로퍼티별 정적 코드 생성 — Attribute의 정적 지름길과 달리 (클래스 수 × 프로퍼티 수) 규모로 폭발해 기각, `OnChange(name)` 단일 팩토리로 대체 |
|
||||
|
||||
## 참고
|
||||
|
||||
|
|
|
|||
38
.claude/archive/onchange-per-property-codegen-rejected.md
Normal file
38
.claude/archive/onchange-per-property-codegen-rejected.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# [기각됨] `OnChange.PropertyName` 프로퍼티별 정적 코드 생성
|
||||
|
||||
**기각 일시**: 2026-08-10. **현재 유효한 설계**: `base/onchange-plan.md` —
|
||||
`OnChange(name)` 제네릭 없는 단일 팩토리, 콜백 파라미터 타입은 호출부가
|
||||
직접 명시.
|
||||
|
||||
## 무엇이었나
|
||||
|
||||
`Attribute`가 `[Attribute<<T>> "name"]`(제네릭 경로)과 `[BooleanAttribute
|
||||
"name"]`(자주 쓰는 타입만 정적 지름길)을 둘 다 채택했던 것(`base/
|
||||
attribute-plan.md`)과 같은 모양으로, `OnChange`도 `OnChange.Position`/
|
||||
`OnChange.Size`처럼 프로퍼티 이름별로 이미 타입이 박힌 정적 필드를 코드
|
||||
생성기로 전부 만들어두는 안이 검토됐음.
|
||||
|
||||
## 기각 이유
|
||||
|
||||
Attribute의 정적 지름길과 겉보기엔 같은 절충처럼 보이지만 실제로는 규모가
|
||||
다른 문제:
|
||||
|
||||
- Attribute의 타입 파라미터 `T`는 Roblox Attribute가 지원하는 좁고 고정된
|
||||
프리미티브 집합(string/boolean/number/Color3/UDim/UDim2/Vector2/Vector3/
|
||||
CFrame/Instance 등, ~10종)에서만 옴 — 정적 지름길 후보가 유한하고 작음.
|
||||
- `OnChange`가 감쌀 수 있는 프로퍼티는 **클래스마다 이름/타입 집합이 전부
|
||||
다름** — `Frame.Position`, `TextLabel.Text`, `ScrollingFrame.CanvasSize`
|
||||
등 클래스 종류만큼 프로퍼티 집합이 갈라지므로, "자주 쓰는 것만 정적
|
||||
지름길"이 성립하려면 사실상 (클래스 수 × 프로퍼티 수) 규모의 조합을
|
||||
전부 커버해야 함 — 유한한 지름길 목록으로 수렴하지 않음.
|
||||
- 지름길을 특정 클래스 몇 개(Frame 등)로만 좁혀도, 그 클래스의 `OnChange`
|
||||
네임스페이스가 실제로 그 클래스에서만 유효한 프로퍼티인지 타입 레벨에서
|
||||
강제할 방법이 마땅치 않음 — 결국 반쯤 타입 안전한 것처럼 보이는 인터페이스만
|
||||
남고 실제 검증은 여전히 없음.
|
||||
|
||||
## 대안(채택됨)
|
||||
|
||||
콜백 파라미터 타입을 호출부가 직접 명시하는 것으로 충분 — 이미 이벤트
|
||||
바인딩(`Frame { MouseButton1Click = fn }`)이 콜백 시그니처 검증을 포기하는
|
||||
것과 같은 급의 트레이드오프를 받아들이는 것뿐, `OnChange`만 유별나게 정적
|
||||
타입 안전성을 추구할 근거가 약함.
|
||||
|
|
@ -159,6 +159,7 @@ quad/
|
|||
├── Handlers/
|
||||
│ ├── Property.luau # 일반 프로퍼티 세팅 + `isTween(realv)` 분기(3-상태 릴레이션 슬롯 `RobloxTween|true|nil`, hasBeenSet 억제, override 정책) — 구 `Handlers/Tween.luau`(높은 우선순위 store-bind 핸들러)는 폐기(`archive/tween-special-bind-key-reversed.md`)
|
||||
│ ├── Event.luau # ReflectionService 기반 자동 판별
|
||||
│ ├── OnChange.luau # `OnChange(name)` DI 키 팩토리+Handler, `GetPropertyChangedSignal` 바인딩(`base/onchange-plan.md`, 2026-08-10 세션)
|
||||
│ ├── Attribute.luau
|
||||
│ ├── Tag.luau # CollectionService 글루만(process/retract) — 값 타입/API는 quad-base Tag.luau(`base/tag-plan.md`)
|
||||
│ ├── Slot.luau # base Slot 재조정 로직의 실제 적용/해제(Instance Parent 조작)
|
||||
|
|
|
|||
|
|
@ -245,7 +245,8 @@ NoneHandler.process(inst, k, v) = process(inst, k, nil) -- 재귀 재호출
|
|||
- `Dispatch.addHandler(handler: Handler)` — 핸들러를 우선순위 레지스트리에
|
||||
등록. `Dispatch.process`/`getHandler`와 마찬가지로 base엔 인터페이스만
|
||||
있고, quad-roblox의 concrete Handler들(PropertyHandler/EventHandler/
|
||||
UICornerHandler/TagHandler/AttributeHandler 등)은 팩토리가 `BaseModule`을
|
||||
OnChangeHandler/UICornerHandler/TagHandler/AttributeHandler 등)은
|
||||
팩토리가 `BaseModule`을
|
||||
뮤테이션하는 시점에 이걸로 등록됨(아래 "base 유틸은 인터페이스" 절과
|
||||
같은 패턴, 새 메커니즘 아님).
|
||||
- Handler 자신의 필드는 계속 `process`/`retract`(이미 확정된 이름,
|
||||
|
|
@ -2009,6 +2010,13 @@ Service` 기반으로 구현)로 두면 됨 — 별도 `On` 모듈/필드 접근
|
|||
유지 — 이벤트만 예외였을 뿐, "정적으로 알려진 것=필드 접근" 원칙 자체가
|
||||
깨진 건 아님.
|
||||
|
||||
**`GetPropertyChangedSignal`은 이 문자열 키 패턴이 안 통함 — 별도 `OnChange`
|
||||
DI 키로 확정(2026-08-10 세션).** 이벤트는 `inst[key]`가 이미 Signal이라
|
||||
그대로 `Connect`하면 되지만, `GetPropertyChangedSignal(name)`은 프로퍼티
|
||||
이름을 인자로 받아야 하고 그 이름이 "값 세팅" 키 네임스페이스와 겹쳐서
|
||||
평범한 문자열 키로는 세팅과 리스닝을 구분할 수 없음 — 상세는
|
||||
`base/onchange-plan.md`.
|
||||
|
||||
**PA님 코드와 대조해서 재확인한 것(변경 없음)**:
|
||||
- **OOP 회피 결정은 오히려 보강됨** — PA님의 `ObjectOrientedProgramming/
|
||||
class.luau`도 `setmetatable(methods, {__index = parent})` 체이닝 상속이라
|
||||
|
|
|
|||
60
.claude/base/onchange-plan.md
Normal file
60
.claude/base/onchange-plan.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# `OnChange` 특수 키 — `GetPropertyChangedSignal` 바인딩
|
||||
|
||||
**상태**: base — 2026-08-10 세션에서 확정. quad-roblox 전용(값 타입/API
|
||||
레이어 없음, `Attribute`와 같은 패키지 배치).
|
||||
|
||||
## 문제
|
||||
|
||||
이벤트 바인딩은 이미 평범한 문자열 키 + reflection(`GetEventsOfClass`)으로
|
||||
확정돼 있음(`bind-system-plan.md`의 "인스턴스 생성 / 이벤트 네이밍 인체공학"
|
||||
절) — `inst[key]`가 이미 `RBXScriptSignal`이라 그냥 `Connect`하면 됨.
|
||||
`GetPropertyChangedSignal(name)`은 이 패턴이 그대로 안 통함: 프로퍼티 이름을
|
||||
인자로 받아 **별도 메소드 호출**로 시그널을 얻어야 하고, 그 프로퍼티 이름은
|
||||
이미 "값 세팅" 키 네임스페이스(`Frame.Position = x`)와 겹침 — 값 타입만으론
|
||||
"세팅"과 "변경 리스닝"을 구분할 방법이 없어서 별도 마커가 필요함.
|
||||
|
||||
## 확정
|
||||
|
||||
- **`OnChange(propertyName): OnChangeKey`** — 프로퍼티 이름을 감싸는 DI 키
|
||||
팩토리, `Attribute(name)`/`Tag(...)`와 같은 패턴. 사용 예:
|
||||
`Frame { [OnChange "Position"] = function(v: UDim2) ... end }`.
|
||||
- **제네릭 타입 파라미터 없음 — `OnChange<<T>>` 같은 타입 파라미터화는 안
|
||||
함.** 콜백 파라미터 타입은 호출부가 인라인으로 직접 명시
|
||||
(`function(v: UDim2) ... end`) — Luau가 그 타입이 실제 프로퍼티 타입과
|
||||
일치하는지 검증해주지 않음. 이미 확정된 "이벤트 바인딩은 콜백 시그니처를
|
||||
Luau가 검증 못 하는 대가를 받아들인다"는 결정(`bind-system-plan.md` "이벤트
|
||||
바인딩 — `On.EventName` 도트액세스 안 씀" 절, "타입 안전성을 어느 정도
|
||||
포기하는 대가")과 같은 급의 트레이드오프라 새로 정당화할 것 없음 — 오히려
|
||||
`Attribute<<T>>`처럼 제네릭으로 정확히 맞추려는 시도는 이벤트 키보다 더
|
||||
엄격한 걸 요구하는 셈이라 일관성이 깨짐.
|
||||
- **기각안 — 프로퍼티별 정적 `OnChange.PropertyName` 전량 코드 생성**:
|
||||
`archive/onchange-per-property-codegen-rejected.md` 참고. Attribute의
|
||||
"제네릭 + 자주 쓰는 것만 정적 지름길" 절충과 겉보기엔 비슷해 보이지만
|
||||
규모가 다른 문제라 기각.
|
||||
- **패키지 경계: 전부 quad-roblox** — `Handlers/OnChange.luau`에 `OnChange(name)`
|
||||
키 팩토리와 Handler를 같이 둠(`Attribute.luau`와 같은 배치, base 쪽 값
|
||||
타입 파일 없음). `GetPropertyChangedSignal` 자체가 Roblox 엔진 API라 base에
|
||||
둘 이유가 없음 — Tag처럼 백엔드 무관한 값/API 레이어가 따로 있는 경우와
|
||||
다름.
|
||||
- **`process(inst,k,v)`**: `inst:GetPropertyChangedSignal(name):Connect(function()
|
||||
v(inst[name]) end)`. **`retract(inst,k,v)`**: 그 Connection을
|
||||
`:Disconnect()`. 일반 `Handlers/Event.luau`와 같은 결(Connection
|
||||
관리뿐, 새 메커니즘 없음).
|
||||
- **`State<function>` 지원 — 새 메커니즘 없음.** 이미 확정된 "이벤트도
|
||||
store-bind 가능 — `false`로 disconnect" 메커니즘(`bind-system-plan.md`)이
|
||||
`OnChange` 키에도 그대로 적용됨 — `OnChangeHandler`는 `process`/`retract`만
|
||||
구현하면 되고, `v`가 State/Source면 범용 `Dispatch/StoreBind.luau`가 알아서
|
||||
언랩+재귀 재-dispatch해서 `process`를 다시 호출해줌. `OnChange` 전용 분기
|
||||
불필요.
|
||||
|
||||
## 다른 특수 DI 키와의 대조
|
||||
|
||||
| | 소스 | 값 타입 | 패키지 경계 |
|
||||
|---|---|---|---|
|
||||
| 이벤트(`MouseButton1Click = fn`) | `inst[key]`가 이미 Signal | 콜백, 타입 미검증 | quad-roblox(`Handlers/Event.luau`) |
|
||||
| `Attribute(name)` | `SetAttribute`/`GetAttribute` | 값(제네릭 또는 정적 타입 패밀리로 타입 파라미터화) | quad-roblox(`Handlers/Attribute.luau`) |
|
||||
| `OnChange(name)` | `GetPropertyChangedSignal(name)` | 콜백, 타입 미검증(제네릭 없음) | quad-roblox(`Handlers/OnChange.luau`) |
|
||||
|
||||
`OnChange`가 Attribute처럼 제네릭화되지 않은 이유는 "콜백을 받는다"는
|
||||
성질이 Attribute(값을 직접 받음)보다 이벤트에 더 가깝기 때문 — 카테고리가
|
||||
헷갈리지 않도록 표로 명확히 구분해둠.
|
||||
40
CLAUDE.md
40
CLAUDE.md
|
|
@ -2656,3 +2656,43 @@ Tween 제거, `None` 센티널 절 예시 갱신, Ref/Brand 절 문구 정정),
|
|||
**다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터, luau-test 결과 확인
|
||||
우선) — 이번 세션도 순수 설계 확정/문서 정리라 M0 착수 우선순위 자체는
|
||||
그대로. M11 착수 시점이 오면 위 "여전히 열려있는 것" 목록부터 확인.
|
||||
|
||||
## 2026-08-10 세 번째 세션 — `OnChange` 특수 키 신설: `GetPropertyChangedSignal`
|
||||
바인딩, 제네릭 없이 확정
|
||||
|
||||
사용자가 `GetPropertyChangedSignal`을 어떻게 다뤄야 할지 물으며 시작 —
|
||||
이벤트는 이미 평범한 문자열 키(`inst[key]`가 곧 Signal)로 확정돼 있는데,
|
||||
`GetPropertyChangedSignal(name)`은 프로퍼티 이름을 인자로 받아야 하고 그
|
||||
이름이 "값 세팅" 키 네임스페이스와 겹쳐서 같은 패턴을 못 씀 — 사용자가
|
||||
`[OnChange "PropertyName"] = function(v) ... end` 형태(타입은 콜백에 직접
|
||||
명시)와 "`OnChange.PropertyName`을 전부 코드 생성"하는 대안 두 가지를
|
||||
제시하며 의견을 물음.
|
||||
|
||||
**확정**: `OnChange(name)` DI 키 팩토리, **제네릭 타입 파라미터 없음** —
|
||||
`Attribute<<T>>`와 달리 콜백 파라미터 타입은 호출부가 직접 명시. 이미
|
||||
확정된 "이벤트 바인딩은 콜백 시그니처를 Luau가 검증 못 하는 대가를
|
||||
받아들인다"는 결정과 같은 급의 트레이드오프라 새로 정당화할 것 없다는 게
|
||||
근거 — 오히려 `Attribute`처럼 제네릭으로 정확히 맞추려 들면 이벤트 키보다
|
||||
더 엄격한 걸 요구하는 셈이라 일관성이 깨짐. 프로퍼티별 정적 코드 생성 안은
|
||||
기각(`archive/onchange-per-property-codegen-rejected.md`) — Attribute의
|
||||
정적 지름길은 타입 파라미터가 좁고 고정된 프리미티브 집합(~10종)에서만
|
||||
와서 지름길 후보가 유한한데, 프로퍼티는 클래스마다 이름/타입 집합이 전부
|
||||
달라 (클래스 수 × 프로퍼티 수) 규모로 폭발함 — 겉보기엔 비슷한 절충
|
||||
같지만 실제로는 규모가 다른 문제.
|
||||
|
||||
패키지 경계는 **전부 quad-roblox**(`Handlers/OnChange.luau`, `Attribute`와
|
||||
같은 배치 — `GetPropertyChangedSignal` 자체가 Roblox 엔진 API라 base에 둘
|
||||
값 타입/API 레이어가 없음). `process`는 `GetPropertyChangedSignal(name):Connect`,
|
||||
`retract`는 `:Disconnect` — 일반 `Handlers/Event.luau`와 같은 결. **`State<function>`
|
||||
지원도 새 메커니즘 없이 해소** — 이미 확정된 "이벤트도 store-bind 가능
|
||||
(`false`로 disconnect)" 메커니즘이 그대로 적용됨, `OnChangeHandler`는
|
||||
`process`/`retract`만 구현하면 범용 `Dispatch/StoreBind.luau`가 State/Source
|
||||
언랩+재귀 재-dispatch를 알아서 해줌.
|
||||
|
||||
`base/onchange-plan.md`(신규)/`base/bind-system-plan.md`(이벤트 네이밍 절
|
||||
교차 참조)/`base/architecture.md`(소스트리 `Handlers/OnChange.luau`)/
|
||||
`ROADMAP.md`(M10 제목·체크박스)/`.claude/README.md`(base/archive 인덱스)
|
||||
전부 반영 완료.
|
||||
|
||||
**다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터, luau-test 결과 확인
|
||||
우선) — 이번 세션도 순수 설계 확정이라 M0 착수 우선순위 자체는 그대로.
|
||||
|
|
|
|||
|
|
@ -331,9 +331,12 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
|
|||
- [ ] `props.Modifier`/`props.Ref` 전달 관례를 정식 컴포넌트로 검증(M0
|
||||
스파이크를 정식화)
|
||||
|
||||
## M10 — Event / Attribute / Tag
|
||||
## M10 — Event / OnChange / Attribute / Tag
|
||||
|
||||
- [ ] `Handlers/Event.luau`(`ReflectionService` 기반 자동 판별)
|
||||
- [ ] `Handlers/OnChange.luau`(`OnChange(name)` DI 키 팩토리+Handler,
|
||||
`GetPropertyChangedSignal` 바인딩 — 제네릭 없이 콜백 타입은 인라인
|
||||
명시, `base/onchange-plan.md`, 2026-08-10 세션 확정)
|
||||
- [ ] `Handlers/Attribute.luau`(`base/attribute-plan.md` — 메커니즘/`None`/
|
||||
`retract` 불필요 확정, 타입 파라미터화 이름만 착수 전 확인)
|
||||
- [ ] `Tag.luau`(quad-base — 값 타입+immutable clone 체이닝: `Tag(...)`/
|
||||
|
|
|
|||
Loading…
Reference in a new issue