Counter
Build a counter that ticks forever, survives restarts, and mirrors to a browser. Two stages, one new concept per stage.
Before you start: finish the hello tutorial and set up the nu.ui Fabric per Install.
Stage 1: Remember a number across restarts
The hello loop prints forever. You want it to tick a number, and to pick that number back up after you kill the process.
Save this as counter.py:
import asyncio
import nu
class Counter(nu.Shape):
value: nu.v.IntRef
tree = nu.With(
nu.v.presets.rocksdb_navigator(".dbtest"),
body=(
nu.IfDo(Counter.value.missing(), Counter.value.set(0))
>> nu.ForeverDo(
Counter.value.inc() >> nu.print(Counter.value) >> nu.Delay(1.0),
)
),
)
if __name__ == "__main__":
asyncio.run(nu.arun(nu.v.auto_flow_atomic(tree)))Run it:
python counter.pyYou see 1, 2, 3, ... one per second. Stop it with Ctrl-C. Run again. It resumes: 12, 13, 14. The number lived through the restart, in a RocksDB directory called .dbtest next to your script.
The new concepts
A Shape is a class that declares the data your program has. Counter has one Slot named value, and that slot holds a nu.v.IntRef.
A Ref is a typed address for a piece of data. Counter.value is not an integer; it is a handle that points at where the integer lives. To read it, some atom in the tree reads through it. To write it, some atom writes through it. Counter.value.inc() is a Command that reads-and-writes through the Ref.
nu.With binds Fabrics for the tree it wraps. Here it binds the RocksDB Navigator to the path .dbtest, and the tree in body= runs against it. nu.IfDo(Counter.value.missing(), Counter.value.set(0)) seeds the counter to 0 the first time it runs, and does nothing on later runs.
Ignore nu.v.auto_flow_atomic for now. It wraps writes in the transactions the storage Fabric needs. You will meet it properly later.
Stage 2: Mirror the counter in a browser, live
Now show the number in a browser tab, updating as it ticks. When you kill the process and restart, the tab reconnects and picks up from the current count.
Replace counter.py with:
import asyncio
import nu
class Counter(nu.Shape):
value: nu.v.IntRef
class Dashboard(nu.ui.Page):
count: nu.ui.TextRef
class App(nu.ui.Index):
pages = nu.ui.Pages({"/": Dashboard})
ui = nu.ReactForever(
Counter.value.on_change(),
Dashboard.count.set(Counter.value),
)
tree = nu.With(
nu.v.presets.rocksdb_navigator(".dbtest"),
nu.ui.nudle.server(nu.v.auto_flow_atomic(ui)),
body=(
nu.IfDo(Counter.value.missing(), Counter.value.set(0))
>> nu.ForeverDo(
Counter.value.inc() >> nu.Delay(1.0),
)
),
)
if __name__ == "__main__":
asyncio.run(nu.arun(nu.v.auto_flow_atomic(tree)))Run it. A browser tab opens on http://localhost:8080. The page shows the counter, ticking once a second. Kill the process, restart it, refresh the tab: same live mirror.
The new concepts
A Fabric is a backend Nu talks to. Storage is one Fabric (RocksDB, in-memory). The browser UI is another. Each has its own Shapes: nu.Shape and nu.v.IntRef live on the storage Fabric; nu.ui.Page and nu.ui.TextRef live on the UI Fabric. Same declaration style, different Fabric. App is the site index, and nu.ui.Pages({"/": Dashboard}) mounts Dashboard at /.
nu.With now binds two Fabrics: the RocksDB Navigator and the nudle UI server. The server takes a tree to run whenever it needs to update the UI.
That tree uses reactivity. nu.ReactForever(source, body) subscribes to a change source and re-runs the body every time it fires. Counter.value.on_change() is a change source that fires every time the int at Counter.value is written. The body copies the current value into the UI Ref: Dashboard.count.set(Counter.value). That is what makes the browser mirror the tick.
What you built
- A persistent int on a RocksDB Fabric.
- A browser dashboard that mirrors it live through a
ReactForever.
Next
Build a real app with lists, forms, and multiple pages: Movies.