Blocker
Blocker는 전파를 잠시 붙잡아 두는 스위치입니다. 여러 값을 한 번에 바꿀 때 중간 상태가 하류로 새는 걸 막고, 다 바꾼 뒤 한 번만 통지하고 싶을 때 씁니다.
Blocker 자체는 새 메커니즘이 아니라 state:Gate(setup) 위에 얹힌 정책입니다 — 게이트 계약(무엇이 유보되고 emit이 무엇을 하는지)은 그쪽이 정본이고, 이 페이지는 그 위의 스위치만 다룹니다.
이 페이지의 심볼: q.Blocker() · blocker.Blocking · blocker:On() · blocker:Off() · blocker:OffWithoutEmit() · blocker:Policy(emit) · state:Apply(blocker)
-- 01장의 설정 모듈: quad_base에 quad_roblox를 설치하고 타입을 다시 내보낸다(시작하기 01 참고)local q = require("@game/ReplicatedStorage/Client/UI/Quad")q.Blocker()
섹션 제목: “q.Blocker()”시그니처
Blocker: () -> Blocker
export type Blocker = { read Blocking: boolean, On: (self: Blocker) -> Blocker, Off: (self: Blocker) -> Blocker, OffWithoutEmit: (self: Blocker) -> Blocker, Policy: (self: Blocker, emit: GateEmit) -> () -> (), __apply: (self: any, state: any) -> any,}반환 — 꺼진 상태(Blocking = false)의 새 Blocker.
동작
- 하나의
Blocker를 여러 노드에 붙일 수 있습니다.:Off()한 번이 붙어 있는 게이트 전부를 풉니다. - 게이트 쪽 등록은 약하게 잡습니다 — 게이트 노드가 사라지면 그 등록도 같이 사라집니다.
Blocker가 게이트를(그리고 그 상류 사슬을) 붙잡아 두지 않습니다. print(blocker)는Blocker(on)/Blocker(off)로 찍힙니다.
예제
local hp = q.Source(100)local mp = q.Source(50)local blocker = q.Blocker()
local shownHp: q.State<number> = hp:Apply(blocker)local shownMp: q.State<number> = mp:Apply(blocker)
blocker:On()hp:Set(80)mp:Set(40) -- 아직 아무도 통지받지 않았다blocker:Off() -- 두 게이트가 각각 한 번씩 통지하류가 하나로 합쳐지는 화면이라면 게이트는 합쳐진 뒤 한 자리에만 두는 편이 낫습니다 — 통지도 한 번으로 접힙니다(시작하기 18 §2). 위처럼 원천마다 두는 것은 서로 다른 하류를 같은 스위치로 묶고 싶을 때의 모양입니다.
관련 — 03-state · 05-observer-effect
blocker.Blocking
섹션 제목: “blocker.Blocking”boolean 필드 — 지금 막고 있는지. 평범한 불리언이지 카운터가 아닙니다.
그래서 같은 Blocker를 겹쳐 잠그는 것은 지원하지 않습니다 — 안쪽 구간이 :Off()를 부르면 바깥 구간이 아직 끝나지 않았어도 풀립니다. 겹치는 구간이 필요하면 구간마다 별도의 Blocker를 만드세요.
“유보된 게 있는가”를 알려주는 필드는 없습니다. 그 정보는 게이트의 emit(commit) 반환값이 유일한 통로입니다(03-state).
strict 캐비엇 — 필드 비교는 메소드 호출을 건너 좁혀진 채 남습니다. 한 함수 안에서 blocker.Blocking == false를 비교한 뒤 blocker:On()을 부르고 다시 blocker.Blocking == true를 비교하면, 타입 검사기는 앞 비교로 좁힌 값(false)을 그대로 들고 있어 두 번째 비교를 모순으로 봅니다(Cannot compare unrelated types) — 메소드가 필드를 바꾼다는 걸 검사기가 모르기 때문이고, 런타임 값은 정상입니다. 같은 함수에서 다시 읽어야 하면 함수를 거쳐 읽으세요(local function blocking(b) return b.Blocking end). observer.Subscribed도 같습니다.
blocker:On()
섹션 제목: “blocker:On()”시그니처 — On: (self: Blocker) -> Blocker (self 반환)
동작 — 플래그를 올립니다. 그게 전부입니다 — 이 시점 이후 붙어 있는 게이트들에 도착하는 emit이 각자의 흡수 집합에 쌓입니다. 값은 막지 않습니다: 유보 중에도 하류의 :Get()은 최신 값을 봅니다.
blocker:Off()
섹션 제목: “blocker:Off()”시그니처 — Off: (self: Blocker) -> Blocker (self 반환)
동작
- 먼저 플래그를 내립니다(푸는 도중 재진입한 흐름이 열린 상태를 보도록).
- 등록된 핸들 목록의 스냅샷을 뜬 뒤 순회하며 각 게이트를 flush합니다 — 게이트마다 쌓인 배치가 한 번의 통지로 나갑니다. 쌓인 게 없는 게이트에서는 아무 일도 없습니다.
- 순회 도중 누군가(방금 flush로 깨어난 하류가) 이
Blocker를 다시 잠그면 거기서 멈춥니다. 남은 게이트들은 유보분을 그대로 들고 다음:Off()를 기다립니다. 순회 도중 하류가 던진 뒤의 상태는 정해져 있지 않습니다 — 관측(계약 아님, [2026-09-27 mock 실측])으로는Blocking은 이미false가 된 뒤이고 스냅샷에서 뒤에 있던 게이트들은 유보분을 든 채 열린 상태로 남아, 다음:Off()나 다음 상류 신호에 실려 나갔습니다.
“게이트마다 한 번”은 게이트 노드 하나의 통지 횟수입니다. 하류가 한 :Off()에서 통지를 몇 번 받는지는 그래프 모양에 달립니다 — 두 게이트 아래에서 합류하는 Effect나 두 게이트를 다 거치는 State는 핸들 순회 순서(미정)에 따라 한 번 또는 두 번 다시 돌 수 있습니다(값은 언제나 최신 — [2026-09-27 mock 실측] 같은 그래프 300개에서 1회·2회가 갈렸습니다). 또 게이트 뒤에 게이트를 잇고(S:Apply(b1):Apply(b2)) 바깥 게이트에 값을 읽지 않는 Observer를 직접 붙이면, 안쪽 게이트가 나중에 풀릴 때 이미 흘려보낸 리비전을 한 번 더 통지받을 수 있습니다(콜백이 :Get()을 부르면 한 번 — [2026-09-27 mock 실측]). 통지 횟수에 기대지 마세요 — 값에 기대세요.
blocker:OffWithoutEmit()
섹션 제목: “blocker:OffWithoutEmit()”시그니처 — OffWithoutEmit: (self: Blocker) -> Blocker (self 반환)
동작 — :Off()와 같은 경로를 돌되 쌓인 배치를 버립니다. 통지도, 기록도 없습니다.
버린 뒤에도 그래프는 망가지지 않습니다 — 하류의 값은 어차피 :Get() 시점에 최신으로 맞춰지고, 그 다음에 오는 진짜 emit은 정상적으로 흐릅니다. 단, 값이 아니라 구독이 걸린 자리(예: 프로퍼티에 놓인 State<State<T>>의 안쪽 사슬)는 버린 통지가 갱신했을 구독을 다음 emit까지 옛 안쪽에 그대로 둡니다 — 막힌 동안 which:Set(a)로 되돌린 뒤 OffWithoutEmit()하면 gated:Get()은 a인데 화면은 b의 갱신을 계속 따릅니다([2026-09-27 mock 관측] — 관측, 계약 아님).
예제
local hp = q.Source(100)local blocker = q.Blocker()local shown: q.State<number> = hp:Apply(blocker)
blocker:On()hp:Set(80)blocker:OffWithoutEmit() -- 80이라는 통지는 나가지 않는다print(shown:Get()) --> 80 (값은 언제나 최신)blocker:Policy(emit)
섹션 제목: “blocker:Policy(emit)”시그니처
Policy: (self: Blocker, emit: GateEmit) -> () -> ()export type GateEmit = (commit: boolean?) -> boolean인자
| 이름 | 타입 | 설명 |
|---|---|---|
emit |
GateEmit |
게이트가 setup에 넘겨준 flush 함수. |
반환 — 게이트의 onUpstreamEmit(상류 emit마다 불릴 클로저).
동작
- 이
Blocker의 정책을 값으로 꺼내 직접 게이트에 배선할 때 씁니다.state:Apply(blocker)가 내부에서 하는 일이 바로 이것이라, 특별한 사정이 없다면Apply쪽을 쓰면 됩니다. - 부르는 그 자리에서 이
Blocker에 해제 핸들을 등록합니다(약한 등록). 그 핸들의 강한 주인은 반환된 클로저이므로, 게이트가 살아 있는 동안만 핸들도 삽니다. emit이 함수가 아니면 그 자리에서 거절합니다:Quad0017 Blocker: Policy emit must be a function (got {typeof(emit)})
예제
local hp = q.Source(100)local blocker = q.Blocker()
-- state:Apply(blocker)와 동등한 수동 배선local gated: q.State<number> = hp:Gate(function(emit) return blocker:Policy(emit)end)state:Apply(blocker)
섹션 제목: “state:Apply(blocker)”Blocker는 __apply를 가진 애플리커티브 팩토리이므로 state:Apply의 객체 팔로 붙습니다(대상이 State가 아니면 Quad0018 Blocker: Apply target must be a State (got {typeof(state)})). 정확히 다음과 같습니다:
state:Apply(blocker)-- ≡state:Gate(function(emit) return blocker:Policy(emit)end)반환 — 같은 T의 게이트된 State<T>. 객체 팔의 반환 타입이 any이므로 결과 타입은 호출부에서 명시하세요:
local gated: q.State<number> = hp:Apply(blocker)붙이지 않은 Blocker는 아무 일도 하지 않습니다 — :On()/:Off()는 붙어 있는 게이트를 통해서만 효과를 냅니다.
관련 — 03-state · 05-observer-effect · How-To 03 가상 스크롤
답변함: 0 / 0 (0%)