콘텐츠로 이동

Blocker

Blocker전파를 잠시 붙잡아 두는 스위치입니다. 여러 값을 한 번에 바꿀 때 중간 상태가 하류로 새는 걸 막고, 다 바꾼 뒤 한 번만 통지하고 싶을 때 씁니다.

Blocker 자체는 새 메커니즘이 아니라 state:Gate(setup) 위에 얹힌 정책입니다 — 게이트 계약(무엇이 유보되고 emit이 무엇을 하는지)은 그쪽이 정본이고, 이 페이지는 그 위의 스위치만 다룹니다.

이 페이지의 심볼: q.Blocker() · blocker.IsBlocked · blocker:IsOn() · blocker:On() · blocker:Off() · blocker:OffWithoutEmit() · blocker:Policy(emit) · state:Apply(blocker)

-- 설치 경로는 프로젝트 구성에 따라 다르다(00-installation 참고)
local Quad = require(<quad-base 모듈 경로>)
local QuadRoblox = require(<quad-roblox 모듈 경로>).QuadRoblox
local QuadTypes = require(<quad-types 모듈 경로>) -- 타입 주석용(`QuadTypes.State<T>` 등)
local q = Quad:UseProvider(QuadRoblox) -- quad-roblox 백엔드 설치: D/Tween/Animate/OnChange가 생긴다

시그니처

Blocker: () -> Blocker
export type Blocker = {
IsBlocked: boolean,
IsOn: (self: Blocker) -> boolean,
On: (self: Blocker) -> Blocker,
Off: (self: Blocker) -> Blocker,
OffWithoutEmit: (self: Blocker) -> Blocker,
Policy: (self: Blocker, emit: GateEmit) -> () -> (),
__apply: (self: any, state: any) -> any,
}

반환 — 꺼진 상태(IsBlocked = 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: QuadTypes.State<number> = hp:Apply(blocker)
local shownMp: QuadTypes.State<number> = mp:Apply(blocker)
blocker:On()
hp:Set(80)
mp:Set(40) -- 아직 아무도 통지받지 않았다
blocker:Off() -- 두 게이트가 각각 한 번씩 통지

관련03-state · 05-observer-effect


boolean 필드 — 지금 막고 있는지. 평범한 불리언이지 카운터가 아닙니다.

그래서 같은 Blocker를 겹쳐 잠그는 것은 지원하지 않습니다 — 안쪽 구간이 :Off()를 부르면 바깥 구간이 아직 끝나지 않았어도 풀립니다. 겹치는 구간이 필요하면 구간마다 별도의 Blocker를 만드세요.

“유보된 게 있는가”를 알려주는 필드는 없습니다. 그 정보는 게이트의 emit(commit) 반환값이 유일한 통로입니다(03-state).


시그니처IsOn: (self: Blocker) -> boolean

IsBlocked를 그대로 읽는 얇은 접근자입니다.


시그니처On: (self: Blocker) -> Blocker (self 반환)

동작 — 플래그를 올립니다. 그게 전부입니다 — 이 시점 이후 붙어 있는 게이트들에 도착하는 emit이 각자의 흡수 집합에 쌓입니다. 값은 막지 않습니다: 유보 중에도 하류의 :Get()은 최신 값을 봅니다.


시그니처Off: (self: Blocker) -> Blocker (self 반환)

동작

  1. 먼저 플래그를 내립니다(푸는 도중 재진입한 흐름이 열린 상태를 보도록).
  2. 등록된 핸들 목록의 스냅샷을 뜬 뒤 순회하며 각 게이트를 flush합니다 — 게이트마다 쌓인 배치가 한 번의 통지로 나갑니다. 쌓인 게 없는 게이트에서는 아무 일도 없습니다.
  3. 순회 도중 누군가(방금 flush로 깨어난 하류가) 이 Blocker다시 잠그면 거기서 멈춥니다. 남은 게이트들은 유보분을 그대로 들고 다음 :Off()를 기다립니다.

시그니처OffWithoutEmit: (self: Blocker) -> Blocker (self 반환)

동작:Off()와 같은 경로를 돌되 쌓인 배치를 버립니다. 통지도, 기록도 없습니다.

버린 뒤에도 그래프는 망가지지 않습니다 — 하류의 값은 어차피 :Get() 시점에 최신으로 맞춰지고, 그 다음에 오는 진짜 emit은 정상적으로 흐릅니다.

예제

local hp = q.Source(100)
local blocker = q.Blocker()
local shown: QuadTypes.State<number> = hp:Apply(blocker)
blocker:On()
hp:Set(80)
blocker:OffWithoutEmit() -- 80이라는 통지는 나가지 않는다
print(shown:Get()) --> 80 (값은 언제나 최신)

시그니처

Policy: (self: Blocker, emit: GateEmit) -> () -> ()
export type GateEmit = (commit: boolean?) -> boolean

인자

이름 타입 설명
emit GateEmit 게이트가 setup에 넘겨준 flush 함수.

반환 — 게이트의 onUpstreamEmit(상류 emit마다 불릴 클로저).

동작

  • Blocker의 정책을 값으로 꺼내 직접 게이트에 배선할 때 씁니다. state:Apply(blocker)가 내부에서 하는 일이 바로 이것이라, 특별한 사정이 없다면 Apply 쪽을 쓰면 됩니다.
  • 부르는 그 자리에서Blocker에 해제 핸들을 등록합니다(약한 등록). 그 핸들의 강한 주인은 반환된 클로저이므로, 게이트가 살아 있는 동안만 핸들도 삽니다.
  • emit이 함수가 아니면 그 자리에서 거절합니다: Blocker: Policy emit must be a function (got {typeof(emit)})

예제

local hp = q.Source(100)
local blocker = q.Blocker()
-- state:Apply(blocker)와 동등한 수동 배선
local gated: QuadTypes.State<number> = hp:Gate(function(emit)
return blocker:Policy(emit)
end)

Blocker__apply를 가진 애플리커티브 팩토리이므로 state:Apply의 객체 팔로 붙습니다. 정확히 다음과 같습니다:

state:Apply(blocker)
-- ≡
state:Gate(function(emit)
return blocker:Policy(emit)
end)

반환 — 같은 T의 게이트된 State<T>. 객체 팔의 반환 타입이 any이므로 결과 타입은 호출부에서 명시하세요:

local gated: QuadTypes.State<number> = hp:Apply(blocker)

붙이지 않은 Blocker는 아무 일도 하지 않습니다 — :On()/:Off()붙어 있는 게이트를 통해서만 효과를 냅니다.

관련03-state · 05-observer-effect · How-To 03 가상 스크롤