Skip to content

Instantly share code, notes, and snippets.

@naranyala
Created July 25, 2025 19:17
Show Gist options
  • Select an option

  • Save naranyala/c2a87e4600ccddb569fbb36bd7470d16 to your computer and use it in GitHub Desktop.

Select an option

Save naranyala/c2a87e4600ccddb569fbb36bd7470d16 to your computer and use it in GitHub Desktop.
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