Created
July 25, 2025 19:17
-
-
Save naranyala/c2a87e4600ccddb569fbb36bd7470d16 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import raylib | |
| import strformat | |
| type | |
| ReactiveCallback[T] = object | |
| callback: proc(value: T, ctx: pointer) | |
| ctx: pointer | |
| Reactive[T] = ref object | |
| value: T | |
| callbacks: seq[ReactiveCallback[T]] | |
| batchDepth: int | |
| isDirty: bool | |
| proc newReactive[T](initial: T): Reactive[T] = | |
| result = Reactive[T]() | |
| result.value = initial | |
| result.callbacks = newSeq[ReactiveCallback[T]]() | |
| result.batchDepth = 0 | |
| result.isDirty = false | |
| proc destroyReactive[T](r: Reactive[T]) = | |
| if r.isNil: return | |
| r.callbacks = @[] | |
| proc setReactive[T](r: Reactive[T], newValue: T) = | |
| if r.isNil: return | |
| if r.batchDepth > 0: | |
| let oldValue = r.value | |
| r.value = newValue | |
| if oldValue != newValue: | |
| r.isDirty = true | |
| return | |
| if r.value != newValue: | |
| r.value = newValue | |
| for cb in r.callbacks: | |
| cb.callback(r.value, cb.ctx) | |
| proc getReactive[T](r: Reactive[T]): T = | |
| if r.isNil: return default(T) | |
| return r.value | |
| proc subscribe[T](r: Reactive[T], callback: proc(value: T, ctx: pointer), ctx: pointer = nil) = | |
| if r.isNil or callback.isNil: return | |
| r.callbacks.add(ReactiveCallback[T](callback: callback, ctx: ctx)) | |
| proc unsubscribe[T](r: Reactive[T], ctx: pointer) = | |
| if r.isNil: return | |
| var newCallbacks = newSeq[ReactiveCallback[T]](0) | |
| for cb in r.callbacks: | |
| if cb.ctx != ctx: | |
| newCallbacks.add(cb) | |
| r.callbacks = newCallbacks | |
| proc beginBatch[T](r: Reactive[T]) = | |
| if r.isNil: return | |
| inc r.batchDepth | |
| proc endBatch[T](r: Reactive[T]) = | |
| if r.isNil or r.batchDepth <= 0: return | |
| dec r.batchDepth | |
| if r.batchDepth == 0 and r.isDirty: | |
| r.isDirty = false | |
| for cb in r.callbacks: | |
| cb.callback(r.value, cb.ctx) | |
| type | |
| ListenerCtx = object | |
| name: string | |
| active: ptr bool | |
| triggerCount: ptr int | |
| ListenerState = object | |
| active: bool | |
| triggerCount: int | |
| ctx: ListenerCtx | |
| CounterUpdateCtx = object | |
| triggerCount: ptr int | |
| proc genericCallback(value: int, ctx: pointer) = | |
| if ctx.isNil: return | |
| let c = cast[ptr ListenerCtx](ctx) | |
| if not c.triggerCount.isNil: | |
| inc c.triggerCount[] | |
| echo fmt"{c.name} notified with value: {value}" | |
| proc counterCallback(value: int, ctx: pointer) = | |
| if ctx.isNil: return | |
| let updateCtx = cast[ptr CounterUpdateCtx](ctx) | |
| inc updateCtx.triggerCount[] | |
| echo fmt"Callback triggered with value: {value}" | |
| proc main() = | |
| const | |
| screenWidth = 800 | |
| screenHeight = 600 | |
| initWindow(screenWidth, screenHeight, "Reactive Batch Example") | |
| defer: closeWindow() | |
| setTargetFPS(60) | |
| let counter = newReactive[int](0) | |
| defer: destroyReactive(counter) | |
| var triggerCount = 0 | |
| var ctx = CounterUpdateCtx(triggerCount: addr triggerCount) | |
| counter.subscribe(counterCallback, addr ctx) | |
| while not windowShouldClose(): | |
| # Input handling | |
| if isKeyPressed(KeyboardKey.One): | |
| counter.setReactive(counter.getReactive() + 1) | |
| if isKeyPressed(KeyboardKey.Two): | |
| counter.beginBatch() | |
| for i in 0..<5: | |
| counter.setReactive(counter.getReactive() + 1) | |
| counter.endBatch() | |
| # Rendering | |
| beginDrawing() | |
| clearBackground(Raywhite) | |
| drawText("Press [1] to increment (callback every time)", 20.int32, 20.int32, 20.int32, Darkgray) | |
| drawText("Press [2] to batch increment x5 (single callback)", 20.int32, 50.int32, 20.int32, Darkgray) | |
| let counterText = fmt"Counter: {counter.getReactive()}" | |
| drawText(counterText, 20.int32, 120.int32, 30.int32, Maroon) | |
| let triggerText = fmt"Callback Trigger Count: {triggerCount}" | |
| drawText(triggerText, 20.int32, 160.int32, 30.int32, Darkblue) | |
| endDrawing() | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment