13. Story
Goal
Make the important moment stand out.
New in this chapter
emit … by nform emit spec/05 §5.4[f: field]type field spec/04 §4.2
Steps
The card turns amber when a node learns the rumor, but the eye misses it. The moment deserves a mark. The look cannot know the moment unless the behaviour says so, because the look may not guess; it only sees facts. A package can add its own facts with emit.
An event is a record, declared with has like a message. emit (message Learned with Text t) by n records it in the trace, as sim.Emitted, with the node that did it. It is only allowed in node code and the node must be the receiver. A look observes it like any fact: when (sim.Emitted rumor.Learned by n). Exported record types, with a capital, are visible to other packages. Step 1 emits when a node learns the rumor, and the look makes its card pulse and glow with look.Pulse and look.Effect "glow" on:
Step 1
# package rumor
use std/disk
use std/lists
use std/net
use std/random
use std/sim
Rumor has
set Text to ""
Ack has
set Text to ""
Learned has
set Text to ""
Join has
set Peers to {}
Node has
set Peers to {}
set Known to ""
set Acked to {}
set GaveUp to 0
# Every node in the list is told about all the others
define Connect [ns: list of Node]
each n in ns
deliver (message Join with Peers (lists.Without n from ns)) to n
# The peers that have not confirmed the rumor yet
define [n: Node] Pending
set rest to {}
each p in n.Peers
if (p is not in n.Acked)
set rest to (lists.Append p to rest)
result rest
# Run b now. If p has not confirmed 300ms later, run it again, up to k times in all, then run c
define [n: Node] Retry [k: number] times for [p: node] [b: block] otherwise [c: block]
b
after 300ms
if (p is not in n.Acked)
if (k more than 1)
n.Retry (k minus 1) times for p
b
otherwise
c
else
c
# Tell one random peer that has not confirmed, and again until all have
define [n: Node] Tell
set open to n.Pending
if ((lists.Size open) more than 0)
set peer to (random.Pick open)
n.Retry 3 times for peer
net.Send (message Rumor with Text n.Known) from n to peer
otherwise
set n.GaveUp to (n.GaveUp plus 1)
after (random.TimeBetween 200ms and 600ms)
n.Tell
# Believe a rumor the first time it is heard, keep it on disk, announce it, and start telling it
define [n: Node] Learn [t: text]
if (n.Known is "")
set n.Known to t
disk.Write t at "known" on n
emit (message Learned with Text t) by n
n.Tell
# After a restart the fields are fresh, but the disk remembers
define [n: Node] start
set n.Peers to (disk.Read "peers" on n or {})
set n.Known to (disk.Read "known" on n or "")
if (n.Known is not "")
n.Tell
# Peers are kept on disk too
when [n: Node] (sim.Delivered Join)
set n.Peers to Join.Peers
disk.Write n.Peers at "peers" on n
when [n: Node] (sim.Delivered Rumor)
n.Learn Rumor.Text
# Confirm every telling, even of a rumor already known
when [n: Node] (net.Received Rumor from sender)
n.Learn Rumor.Text
net.Send (message Ack with Text Rumor.Text) from n to sender
when [n: Node] (net.Received Ack from sender)
if (sender is not in n.Acked)
set n.Acked to (lists.Append sender to n.Acked)
# True while the running nodes know at most one rumor between them
define Agree
set texts to {}
each n in (sim.NodesRunning rumor)
if ((n.Known is not "") and (n.Known is not in texts))
set texts to (lists.Append n.Known to texts)
result ((lists.Size texts) at most 1)
# Must hold after every step of the run
always Agree
# How many nodes are down
define Down
set count to 0
each n in sim.Nodes
if (not (sim.IsUp n))
set count to (count plus 1)
result count
# Fewer than three nodes down, always
always (Down less than 3)
# package rumorlook
use example.com/tutorial/rumor
use std/look
use std/look/basic
use std/sim
use std/theme
# The color of a card that knows the rumor
define Heard
result #F4A340
# Quiet while a node knows nothing, Heard afterwards
define Tint [t: text]
if (t is "")
result theme.Quiet
result Heard
# Replace the basic card with one whose color follows what the node knows
when (sim.Added [n: rumor.Node])
draw (look.Card for n) as card for n
look.Fill card with (Tint n.rumor.Known)
# A node that learns the rumor lights up
when (sim.Emitted rumor.Learned by n)
look.Pulse (card of n)
look.Effect "glow" on (card of n)
Plain state changes need no event. Each change of an exported node field is already a fact, sim.Changed. The field it names is a field hole: a field written as a name, not read as a value. When the name is a field of a package, the observer filters on it. Step 2 gives a red outline to a node that gives up on a peer, by watching rumor.GaveUp. A bare new word in the field hole would instead bind any field, and old and new would be of type value:
Step 2
# package rumorlook
use example.com/tutorial/rumor
use std/look
use std/look/basic
use std/sim
use std/theme
# The color of a card that knows the rumor
define Heard
result #F4A340
# Quiet while a node knows nothing, Heard afterwards
define Tint [t: text]
if (t is "")
result theme.Quiet
result Heard
# Replace the basic card with one whose color follows what the node knows
when (sim.Added [n: rumor.Node])
draw (look.Card for n) as card for n
look.Fill card with (Tint n.rumor.Known)
# A node that learns the rumor lights up
when (sim.Emitted rumor.Learned by n)
look.Pulse (card of n)
look.Effect "glow" on (card of n)
# A node that gives up on a peer gets a red outline
when (sim.Changed rumor.GaveUp of n from old to new)
look.Stroke (card of n) with theme.Critical
The last piece of the story is told to the viewer. lesson.Note, which you used in chapter 1, adds a caption. In step 3 the demo explains each stage as it happens:
Step 3
# package main
use example.com/tutorial/rumor
use example.com/tutorial/rumorlook
use std/lesson
use std/net
use std/sim
define main
sim.Seed 4
set nodes to (sim.Cluster of 6 running rumor)
set left to {(item 0 of nodes), (item 1 of nodes), (item 2 of nodes)}
set right to {(item 3 of nodes), (item 4 of nodes), (item 5 of nodes)}
rumor.Connect nodes
lesson.Note "n1 hears a rumor and starts telling it."
deliver (message rumor.Rumor with Text "The leader is n1") to (item 0 of nodes)
net.DropNext "Rumor" from (item 0 of nodes) to (item 1 of nodes)
after 300ms
lesson.Note "The network splits in two."
net.Partition left from right
after 3s
lesson.Note "The network heals. The rumor can cross again."
net.Heal
What to watch
Every time a node learns the rumor its card pulses and glows. The nodes of the far side stay quiet while the network is split, and red outlines appear on the nodes that give up. When the network heals, the glow crosses over. The captions explain what you see: the split, the heal.
Why it looks like this
An event says what happened to one node, so the node that emits it must be the one running the code, as with net.Send and disk.Write. Emitting for another node is refused:
rumor/rumor.synod:93:45: error[foreign-state]: "by sender" must name the receiver "n"
|
93 | emit (message Learned with Text "ack") by sender
| ^^^^^^
|
help: write "by n"
|
93 | emit (message Learned with Text "ack") by n
| ~
|
foreign-statesynod check says error[foreign-state]: naming a node other than the receiver would let one node act for another.
Exercises
- Make the Learned event carry the node's name.
Expected
Add a field to Learned and set it in the message expression. The look reads it with Learned.field.
- Remove
look.Effect "glow" on (card of n).
Expected
The card still pulses, without the glow.
- Watch
rumor.Knowninstead ofrumor.GaveUp.
Expected
The red outline appears when a node learns the rumor, since Known changes at that moment.