feat(v4.0.0): every message begins through the wait -- a stuck node holds up no one

(GATE) offers a message's first word and sleeps until it is taken, taking
in whatever is written to the node meanwhile; (PAY) offers every refusal
owed at once, one to a port; a node that owes one sleeps in the wait.
Writing without looking, looking again and again, and the lower-number
rule are gone.  Text from the console for a node whose own text waits to
begin a message ends that text Interrupted.  The lone node's hosts serve
an offer as a write.  MESH.md 7c; plan step 6d task 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-07 23:38:07 -04:00
co-authored by Claude Opus 5.5
parent 075385ab63
commit 482cc75faf
8 changed files with 188 additions and 113 deletions
+97 -73
View File
@@ -131,25 +131,22 @@ header CMOVE
NONE: drop drop (ROUTE-DEFAULT) a! @ ;
HIT: drop drop @+ pop drop ;
\ LOOKING BEFORE WRITING (MESH.md section 7a). A write waits until the
\ neighbour reads, and a node that is waiting to write reads nothing: two
\ neighbours that each began to write to the other would wait for ever. So
\ a node does not begin a message until it has looked, (GATE):
\ - while any neighbour is waiting to write to it, it takes that message
\ in and keeps it with the messages waiting, to deal with when it has
\ nothing else to do;
\ - then, if the neighbour it means to write to is waiting to read, it
\ writes;
\ - and if that neighbour is not, it writes all the same when the
\ neighbour's number is the higher of the two, and otherwise looks
\ again. Of two neighbours one only may wait to write to the other, and
\ it is the lower: so those that wait are waiting on ever higher
\ numbers, and the highest of them is not waiting to write; it is
\ looking, and takes in what is being written to it.
\ (WRITERS) and (READERS) are the node's two looks at its neighbours, a bit
\ for each port. (NEAR) is the number of the node on each port, told by
\ whoever wires it (NEIGHBOUR, quit.v4); a port not told of -- a device's
\ -- is written to only when what is there is waiting to read.
\ BEGINNING A MESSAGE: THE WAIT (MESH.md section 7c). A write waits until
\ the neighbour reads, and a node that is waiting to write reads nothing:
\ two neighbours that each began to write to the other would wait for ever,
\ and a node that wrote to one that was stuck would be stuck with it. So
\ a node does not write the first word of a message. It offers it, (GATE):
\ the word is stored at the port's offer address, and the node then reads
\ the wait, where it sleeps until the offer is taken or a word comes for
\ it, whichever is first. If a word comes, that message is taken in and
\ kept with the messages waiting, and the node offers again. When the
\ offer is taken the neighbour has the first word and reads to the end:
\ the rest is written, each word waiting for it.
\ (OFFER) is the offer address of port 0, and (WAIT) the wait. (READERS)
\ is the node's look at its neighbours, a bit for each port that has one
\ waiting to read, and above those a bit for each port that has anything
\ on it at all. (NEAR) is the number of the node on each port, told by
\ whoever wires it (NEIGHBOUR, quit.v4): it is how a node knows its centre.
\
\ THE MESSAGES WAITING are kept in (MQ) .. (MQ-END), one after another,
\ going round: for each its seven words, the port it came on, and its text.
@@ -162,12 +159,6 @@ header CMOVE
if Z -1 + FOR 2/ NEXT 1 and ;
Z: drop 1 and ;
\ ( mask -- index ) the lowest port in it; the mask is not zero
: (LOW)
0 push
L: dup 1 and if UP drop drop pop ;
UP: drop 2/ pop 1 + push jump L
\ ( w -- ) one more cell of the messages waiting. B is kept.
: (MQ!)
(MQ-TAIL) a! @ a! !+
@@ -260,25 +251,47 @@ header CMOVE
\ ( port to -- ) take in a message and keep it
: (TAKE) (TAKE-HDR) jump (TAKE-KEEP)
\ ( port -- ) wait until a message may be begun on the port, by its
\ address, taking in whatever is being written to this node meanwhile; and
\ leave B at the port. The port is kept in (GATE-PORT), not on the stack:
\ this is run with the stack as full as EMIT may be.
: (GATE)
\ ( -- flag ) THE WAIT, once. Whatever has been offered stands until
\ this returns. Zero: a word came for this node instead; that whole
\ message has been taken in and kept, (TAKE), and its seven words are
\ still in (MQ-HDR). Not zero: an offer was taken; B is at the port it
\ was taken on, and (GATE-PORT) holds that port's address.
: (OFFERS)
(WAIT) b! @b push (PORT)+9 b! @b \ ( which ) R: the word
dup -PORTS + -if TAKEN
drop (PORT) + dup b! pop (TAKE) 0 ; \ a word, from that port: the message's first
TAKEN: (PORT) + (GATE-PORT) a! ! drop pop drop
(GATE-PORT) a! @ b! -1 ;
\ ( w port -- ) BEGIN A MESSAGE: the word, its first, is offered on the
\ port, by its address, until it is taken; whatever is written to this
\ node meanwhile is taken in. B is left at the port. The two are kept in
\ (GATE-WORD) and (GATE-PORT), not on the stack: (GATE1), which is this
\ with the word already in (GATE-WORD), is run with the stack as full as
\ EMIT may be.
\ TEXT FROM THE CONSOLE for this node, coming while text it is doing waits
\ here, ends that text in error 21, Interrupted (MESH.md 7c.3); it is with
\ the messages waiting and is done next. It is how a node whose text must
\ begin a message to a node that is stuck is typed to. Not while (QUIET)
\ is set: what waits then is no text's doing.
\ With nothing on the port the fabric raises error 18.
: (GATE1) ( port -- )
(GATE-PORT) a! !
L: (WRITERS) b! @b if QUIET
(LOW) (PORT) + dup b! @b (TAKE) jump L
QUIET: drop
(GATE-PORT) a! @ (PORT) - (NEAR) + a! @ if ASK
(ME) a! @ - -if GO jump ASK \ its number less this node's
GO: drop (GATE-PORT) a! @ b! ; \ the neighbour's is the higher
ASK: drop
(READERS) b! @b (GATE-PORT) a! @ (PORT) - (BIT) if NOTYET
drop (GATE-PORT) a! @ b! ; \ it is waiting to read
NOTYET: drop
(READERS) b! @b (GATE-PORT) a! @ (PORT) - -PORTS - (BIT) if NOONE \ is there anything on the port at all?
drop jump L
NOONE: drop NODE-ERROR b! 18 !b ; \ no: what waits here would wait for ever
L: (GATE-WORD) a! @ (GATE-PORT) a! @ -PORTS - 4 + a! ! \ the offer
(OFFERS) if CAME drop ;
CAME: drop
(QUIET) a! @ if TEXTS drop jump L
TEXTS: drop
(MQ-HDR) a! @ (ME) a! @ xor if MINE drop jump L
MINE: drop
(MQ-HDR)+2 a! @ -1 + if TEXT drop jump L
TEXT: drop
(CONSOLE) a! @ if C0 jump C1
C0: drop (MSG)+1 a! @ \ no console told of: whoever sent the text being done
C1: (MQ-HDR)+1 a! @ xor if BREAK drop jump L
BREAK: drop NODE-ERROR b! 21 !b ;
: (GATE) ( w port -- ) push (GATE-WORD) a! ! pop jump (GATE1)
\ ( type -- ) words 1 to 5 of a message this node sends: from, type, and
\ the three that are carried and not used yet. Its caller has put B at the
@@ -289,14 +302,14 @@ header CMOVE
!b \ type
16 !b 0 !b 0 !b ; \ how many nodes may pass it on (the TTL: MESH.md 7b); ACL tag; sequence
\ PAYING WHAT IS OWED does not hold this node up. A refusal is sent if the
\ neighbour it goes by is reading at this moment. If it is not, and its
\ number is the higher, the refusal is written all the same, as any message
\ would be (MESH.md 7a): that neighbour takes it in when it next looks. If
\ its number is the lower, or it has not been told of, the refusal stays
\ owed, and this node goes on with what it has to do and tries again; it
\ does not sleep while it owes one (quit.v4, (IDLE)). One there is no way
\ for, or whose way is a port with nothing on it, is let go and counted.
\ PAYING WHAT IS OWED (MESH.md 7c.3). Every refusal owed is offered at
\ once, each on the port it goes by, one to a port, and the node sleeps in
\ the wait: so one owed to a node that is stuck does not keep back one
\ owed to a node that waits for it, and the node takes in whatever is
\ written to it meanwhile. One there is no way for, or whose way is a
\ port with nothing on it, is let go and counted first. (PAY-K) has a
\ cell for each port: which refusal is offered there, counted from 1; 0,
\ none.
\ ( k -- ) the k-th refusal owed is done with: the last takes its place
: (PAID)
@@ -304,36 +317,47 @@ header CMOVE
2* (OWED) + a! @+ @
push push 2* (OWED) + a! pop !+ pop ! ;
\ ( k -- ) send the k-th refusal owed, if it can go now
: (PAY1)
dup 2* (OWED) + a! @+ @ \ k to about
over (PORT-FOR) if NOWAY \ k to about port
dup (THERE) if GONE
drop (READERS) b! @b over (PORT) - (BIT) if BUSY
drop
WRITE: b! \ k to about B: the port
SWAP !b 4 (HDR) 4 !b !b \ k to; from, type 4; four characters; about
jump (PAID)
BUSY: drop \ it is not reading:
dup (PORT) - (NEAR) + a! @ if LEAVE \ k to about port near
(ME) a! @ - -if BLIND jump LEAVE \ its number less this node's
BLIND: drop jump WRITE \ the higher: it will take it in
LEAVE: drop drop drop drop drop ;
GONE: drop
NOWAY: drop drop drop (LOST) a! @ 1 + ! jump (PAID)
\ ( k -- ) the k-th refusal owed is let go if it cannot be sent at all
: (PAY-DROP)
dup 2* (OWED) + a! @ \ k to
(PORT-FOR) if NOWAY \ k port
(THERE) if GONE drop drop ;
GONE:
NOWAY: drop (LOST) a! @ 1 + ! jump (PAID)
\ ( -- ) send every refusal this node owes that can go now
\ ( k -- ) offer the k-th refusal owed on its port, if nothing is there yet
: (PAY-OFFER)
dup 2* (OWED) + a! @ \ k to
dup (PORT-FOR) (PORT) - \ k to n the port, by its number
dup (PAY-K) + a! @ if FREE
drop drop drop drop ;
FREE: drop
dup push (OFFER) + a! ! \ k the message's first word, whom it is to
1 + pop (PAY-K) + a! ! ;
\ ( -- flag ) offer every refusal this node owes and wait once: if one
\ is taken its other words are written and it is done with. Zero if
\ nothing is owed, and then nothing was waited for.
: (PAY)
(OWED#) a! @ if NONE -1 +
FOR pop dup push (PAY1) NEXT ;
NONE: drop ;
(OWED#) a! @ if NONE
-1 + FOR pop dup push (PAY-DROP) NEXT
(OWED#) a! @ if NONE
(PAY-K) a! PORTS-1 FOR 0 !+ NEXT
-1 + FOR pop dup push (PAY-OFFER) NEXT
(OFFERS) if CAME
drop (GATE-PORT) a! @ (PORT) - (PAY-K) + a! @ -1 + \ k: the one that was taken
dup 2* (OWED) + 1 + a! @ \ k about
4 (HDR) 4 !b !b \ from, type 4; four characters; about
(PAID) -1 ;
CAME: drop -1 ;
NONE: drop 0 ;
\ ( -- ) send what has been printed, to where (PRINT-TO) says, by the port
\ (PRINT-PORT): its length, then its characters, four to a word, the first
\ lowest. quit.v4 sets those two when text for this node arrives.
: (FLUSH-OUT)
(OUT^) a! @ (OUT) xor if NONE drop
(PRINT-PORT) a! @ (GATE) (PRINT-TO) a! @ !b
(PRINT-TO) a! @ (GATE-WORD) a! ! (PRINT-PORT) a! @ (GATE1)
2 (HDR)
(OUT^) a! @ (OUT) - dup !b
3 + 2/ 2/ -1 + (OUT) a!
+9 -13
View File
@@ -58,7 +58,7 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
1 (QUIET) a! ! \ from here an error is not this text's: see (RAISED)
(LINE-STATUS) b! dup !b
(FLUSH-OUT)
(DONE-PORT) a! @ (GATE) (MSG)+1 a! @ !b \ to whoever sent the text
(MSG)+1 a! @ (DONE-PORT) a! @ (GATE) \ to whoever sent the text
3 (HDR) 4 !b !b \ a message of type 3, four characters long: how it ended
jump (IDLE)
@@ -80,12 +80,8 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
\ is passed on, (PASS-ON).
: (IDLE)
L: 1 (QUIET) a! ! \ nothing here is any text's doing: see (RAISED)
(PAY) \ what refusals can be sent now, are (core.v4)
(MQ#) a! @ if WAIT drop jump HAVE
WAIT: drop (OWED#) a! @ if BLOCK \ with one still owed it does not sleep:
drop (WRITERS) b! @b if L0 \ it takes in whatever is being written to it,
(LOW) (PORT) + dup b! @b (TAKE) jump L
L0: drop jump L \ and tries again
WAIT: drop (PAY) if BLOCK drop jump L \ what it owes is offered, and it sleeps (core.v4)
BLOCK: drop (PORT)+8 b! @b (PORT)+9 b! @b (PORT) + dup b! \ ( to port ) B: the port it came on
SWAP (TAKE) jump L
HAVE:
@@ -131,7 +127,7 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
COUNT: -1 + if SPENT (MSG)+3 a! !
(MSG) a! @ (PORT-FOR) if NOWAY
dup (THERE) if NOONE drop
(GATE) (MSG) a! 6 FOR @+ !b UNEXT \ its seven words
(MSG) a! @ SWAP (GATE) (MSG)+1 a! 5 FOR @+ !b UNEXT \ its seven words
(MSG)+6 a! @ 3 + 2/ 2/ if SENT
-1 + TIB 2/ 2/ a! FOR @+ !b UNEXT \ and its text
jump (IDLE)
@@ -169,8 +165,8 @@ header ROUTE
\ ( node port -- ) the node on the other end of the port, by its number;
\ 0: none, or a device. Whoever wires the node tells it, and tells it again
\ when the wiring changes. It is what lets one of two neighbours, and one
\ only, wait to write to the other (core.v4, (GATE)).
\ when the wiring changes. It is how a node knows who its centre is
\ ((CENTRE)), from whom alone it believes a GONE.
header NEIGHBOUR
: NEIGHBOUR
dup -if POS jump BAD
@@ -201,7 +197,7 @@ header NO-ROUTE
\ it is error 12.
: (SEND1)
push dup (PORT-FOR) if NOWAY
(GATE) !b pop (HDR) 4 !b !b ;
(GATE) pop (HDR) 4 !b !b ;
NOWAY: drop drop drop pop drop NODE-ERROR b! 12 !b ;
\ ( n node -- ) tell the node that node n is gone. It acts on that only
@@ -216,10 +212,10 @@ header NO-ROUTES
\ ( baddr u node port -- ) send the node u characters of text to
\ interpret, by the port given, by its address. It is on its way when this
\ returns. The write waits for the neighbour to read, and is not begun
\ until it may be (core.v4, (GATE)).
\ returns. Its first word is offered until the neighbour takes it
\ (core.v4, (GATE)); the rest is written, each word waiting for it.
: (SEND-ON)
(GATE) !b \ to
(GATE) \ to
1 (HDR)
dup !b \ length
3 + 2/ 2/ if NONE -1 +