5. Messages
Goal
Make two nodes play ping-pong over the network.
New in this chapter
Ping hasform has-record spec/04 §4.3when [n: Node] (net.Received …)form when-node spec/05 §5.6deliver … to …form deliver spec/07 §7.3message Ping with …form message spec/04 §4.3message pingpong.Start with …form message-qualified spec/04 §4.3[p: node]type node spec/04 §4.2Pingtype record spec/04 §4.2net.Sendpackage std/net std/README.md § std/net
Steps
Nodes share nothing. The only way one node learns something from another is a message sent over the network. We add a second package to the module, pingpong/, next to counter/. It holds two files again: the node's behaviour in pingpong.synod, and demo.synod with define main.
A message is a record: a named bundle of fields. A record type is declared with has, like a node, one set field to value line per field, with the value the field has when you do not give one. Our messages are Ping and Pong, each carrying the number of the round. Start is a third record, the signal that tells the first node to serve; its capital Round makes it readable from the demo.
Node has keeps a Peer, the node to talk to, which starts as sim.Nobody, the value that means "no node". A node is a value of type node; it has no fields of its own, it is just a name you can send to.
The behaviour is three when blocks. when [n: Node] (…) runs on every node of the package whenever the event in parentheses happens, with n bound to that node:
sim.Delivered Starthappens when the demo hands aStartto a node withdeliver. The node answers by sending aPing.net.Received Ping from senderhappens when aPingarrives. The wordsPingandsenderare new names for the block:Pingis the record that arrived, soPing.roundis its field, andsenderis the node that sent it.net.Received Pong from senderis the same for aPong.
message Ping with round Start.Round builds a record with round set. net.Send … from n to p sends it; the network takes some time to carry it. The from n repeats who is sending, because a node can only send as itself.
In the demo a.pingpong.Peer is a field of the node a that belongs to the package pingpong: from outside, a field is written node.package.Field. And deliver (message pingpong.Start with Round 1) to a builds a Start from another package, so the record is written pingpong.Start. Step 1 plays a single round:
Step 1
# package main
use example.com/tutorial/pingpong
use std/sim
define main
set a to (sim.Spawn pingpong)
set b to (sim.Spawn pingpong)
set a.pingpong.Peer to b
deliver (message pingpong.Start with Round 1) to a
sim.StopAt 2s
# package pingpong
use std/net
use std/sim
Start has
set Round to 0
Ping has
set round to 0
Pong has
set round to 0
Node has
set Peer to sim.Nobody
set Rounds to 0
# The first node serves
when [n: Node] (sim.Delivered Start)
net.Send (message Ping with round Start.Round) from n to n.Peer
# Whoever is pinged answers
when [n: Node] (net.Received Ping from sender)
net.Send (message Pong with round Ping.round) from n to sender
when [n: Node] (net.Received Pong from sender)
set n.Rounds to (n.Rounds plus 1)
Run synod run pingpong. The summary shows one Ping going from n1 to n2, and one Pong back.
To keep playing, the node that gets a Pong sends the next Ping, one round later, and the node that gets a Ping counts it too. Messages now need time to be seen: net.Latency between 50ms and 150ms makes every message take a random time between the two durations. Because nothing else stops the game, sim.StopAt 3s ends the run:
Step 2
# package main
use example.com/tutorial/pingpong
use std/net
use std/sim
define main
set a to (sim.Spawn pingpong)
set b to (sim.Spawn pingpong)
set a.pingpong.Peer to b
net.Latency between 50ms and 150ms
deliver (message pingpong.Start with Round 1) to a
sim.StopAt 3s
# package pingpong
use std/net
use std/sim
Start has
set Round to 0
Ping has
set round to 0
Pong has
set round to 0
Node has
set Peer to sim.Nobody
set Rounds to 0
# The first node serves
when [n: Node] (sim.Delivered Start)
net.Send (message Ping with round Start.Round) from n to n.Peer
# Whoever is pinged answers
when [n: Node] (net.Received Ping from sender)
set n.Rounds to (n.Rounds plus 1)
net.Send (message Pong with round Ping.round) from n to sender
# Whoever hears the answer pings again, one round later
when [n: Node] (net.Received Pong from sender)
set n.Rounds to (n.Rounds plus 1)
net.Send (message Ping with round (Pong.round plus 1)) from n to sender
What to watch
Two cards, and a dot travelling between them for each message. The delays differ, so the rhythm is uneven, but the order is always Ping, Pong, Ping, … The Rounds counter of both nodes grows together. Run it again: the picture is identical, because the random delays come from a fixed seed. Chapter 9 explains how.
Exercises
- Make
n2start the game instead ofn1.
Expected
Give b the peer and deliver the Start to b: set b.pingpong.Peer to a and deliver … to b. The roles swap.
- Change
net.Latencyto500msand1s. What happens in the three seconds?
Expected
Only a couple of rounds fit before sim.StopAt 3s (two in the default run), since each message takes between half a second and a second.
- Crash
bafter one second: addafter 1swithsim.Crash binside, beforesim.StopAt 3s. What doesado?
Expected
a sends its next Ping, but a message that arrives at a crashed node is dropped (net.Dropped, reason crashed), no Pong comes back, and the game ends there.