wip(0.S3): the packages migrated by ludic migrate state packages - every package test green

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 14:02:21 +03:00
parent 1e8b5b0523
commit 07505e7ef2
294 changed files with 14996 additions and 14675 deletions

View file

@ -21,89 +21,89 @@ export function nb_copy(dst: []byte, d: int, src: []byte, s: int, n: int) -> voi
}
# the writer: past the MTU it counts on and keeps nothing, and np_queue cuts the message there
export function nw_begin() -> void {
net__init()
net__wl = 0
export function nw_begin(net_st: mut NetState) -> void {
net__init(net_st)
net_st.net__wl = 0
}
export function nw_i(v: int) -> void {
if net__wl + 4 <= NET_MTU { nb_put32(net__wb, net__wl, v) }
net__wl += 4
export function nw_i(net_st: mut NetState, v: int) -> void {
if net_st.net__wl + 4 <= NET_MTU { nb_put32(net_st.net__wb, net_st.net__wl, v) }
net_st.net__wl += 4
}
export function nw_b(v: bool) -> void { if v { nw_i(1) } else { nw_i(0) } }
export function nw_b(net_st: mut NetState, v: bool) -> void { if v { nw_i(net_st, 1) } else { nw_i(net_st, 0) } }
export function nw_f(v: float) -> void { nw_i(float_bits(v)) }
export function nw_f(net_st: mut NetState, v: float) -> void { nw_i(net_st, float_bits(v)) }
# a string of at most 200 bytes; nw_text takes a longer one up to its own cap
export function nw_s(s: string) -> void { nw_text(s, 200) }
export function nw_s(net_st: mut NetState, s: string) -> void { nw_text(net_st, s, 200) }
export function nw_text(s: string, cap: int) -> void {
export function nw_text(net_st: mut NetState, s: string, cap: int) -> void {
var n = len(s)
if n > cap { n = cap }
nw_i(n)
nw_i(net_st, n)
for i in 0 .. n {
if net__wl < NET_MTU { net__wb[net__wl] = s[i] }
net__wl += 1
if net_st.net__wl < NET_MTU { net_st.net__wb[net_st.net__wl] = s[i] }
net_st.net__wl += 1
}
}
export function nw_len() -> int { return net__wl }
export function nw_len(net_st: NetState) -> int { return net_st.net__wl }
# the reader: a short or bad message reads as zeros and sets nr_bad()
export function nr_i() -> int {
if net__rp + 4 > net__rl {
net__rbad = true
export function nr_i(net_st: mut NetState) -> int {
if net_st.net__rp + 4 > net_st.net__rl {
net_st.net__rbad = true
return 0
}
let v = nb_get32(net__rb, net__rp)
net__rp += 4
let v = nb_get32(net_st.net__rb, net_st.net__rp)
net_st.net__rp += 4
return v
}
export function nr_b() -> bool { return nr_i() != 0 }
export function nr_b(net_st: mut NetState) -> bool { return nr_i(net_st) != 0 }
export function nr_f() -> float { return float_from_bits(nr_i()) }
export function nr_f(net_st: mut NetState) -> float { return float_from_bits(nr_i(net_st)) }
export function nr_s() -> string { return nr_text(200) }
export function nr_s(net_st: mut NetState) -> string { return nr_text(net_st, 200) }
export function nr_text(cap: int) -> string {
let n = nr_i()
if n < 0 or n > cap or net__rp + n > net__rl {
net__rbad = true
export function nr_text(net_st: mut NetState, cap: int) -> string {
let n = nr_i(net_st)
if n < 0 or n > cap or net_st.net__rp + n > net_st.net__rl {
net_st.net__rbad = true
return ""
}
if net__tb == null { net__tb = buffer(NET_MTU) }
nb_copy(net__tb, 0, net__rb, net__rp, n)
net__rp += n
return text_of(net__tb, n)
if net_st.net__tb == null { net_st.net__tb = buffer(NET_MTU) }
nb_copy(net_st.net__tb, 0, net_st.net__rb, net_st.net__rp, n)
net_st.net__rp += n
return text_of(net_st.net__tb, n)
}
export function nr_bad() -> bool { return net__rbad }
export function nr_bad(net_st: NetState) -> bool { return net_st.net__rbad }
# a message that came in, made the one nr_* reads
export function net_read(m: NetMessage) -> void {
net__init()
nb_copy(net__rb, 0, m.data, 0, m.len)
net__rl = m.len
net__rp = 0
net__rbad = false
export function net_read(net_st: mut NetState, m: NetMessage) -> void {
net__init(net_st)
nb_copy(net_st.net__rb, 0, m.data, 0, m.len)
net_st.net__rl = m.len
net_st.net__rp = 0
net_st.net__rbad = false
}
# the message just written, as if it had come in (a loopback: a test, a host telling itself)
export function net_written() -> NetMessage {
net__init()
export function net_written(net_st: mut NetState) -> NetMessage {
net__init(net_st)
let m = new NetMessage
m.from = net__my_pid
m.len = min(net__wl, NET_MTU)
m.from = net_st.net__my_pid
m.len = min(net_st.net__wl, NET_MTU)
m.data = buffer(m.len)
nb_copy(m.data, 0, net__wb, 0, m.len)
nb_copy(m.data, 0, net_st.net__wb, 0, m.len)
return m
}
# the message being read, copied out as the next one to write (a host passing it on)
export function net_copy_read() -> void {
nw_begin()
nb_copy(net__wb, 0, net__rb, 0, net__rl)
net__wl = net__rl
export function net_copy_read(net_st: mut NetState) -> void {
nw_begin(net_st)
nb_copy(net_st.net__wb, 0, net_st.net__rb, 0, net_st.net__rl)
net_st.net__wl = net_st.net__rl
}

View file

@ -28,10 +28,10 @@ export function net_code_of(ip: int, port: int) -> string {
}
# this machine's code: its address on the network and the socket's port
export function net_join_code() -> string {
export function net_join_code(net_st: NetState) -> string {
var ip = NetSocket.local_ip()
if ip == 0 { ip = Udp.ip("127.0.0.1") }
return net_code_of(ip, net_port())
return net_code_of(ip, net_port(net_st))
}
function net__letter(c: int) -> int {
@ -45,7 +45,7 @@ function net__letter(c: int) -> int {
}
# a code or an address: true when net_code_ip() / net_code_port() say where
export function net_parse_code(text: string) -> bool {
export function net_parse_code(net_st: mut NetState, text: string) -> bool {
let p = text
var colon = -1
var dots = 0
@ -53,7 +53,7 @@ export function net_parse_code(text: string) -> bool {
if p[i] == ':' { colon = i }
if p[i] == '.' { dots += 1 }
}
if dots == 3 { return net__parse_addr(text, colon) }
if dots == 3 { return net__parse_addr(net_st, text, colon) }
var hi = 0
var lo = 0
var n = 0
@ -72,24 +72,24 @@ export function net_parse_code(text: string) -> bool {
n += 1
}
if n != 10 { return false }
net__code_ip = lo
net__code_port = hi
return net__code_ip != 0 and net__code_port > 0
net_st.net__code_ip = lo
net_st.net__code_port = hi
return net_st.net__code_ip != 0 and net_st.net__code_port > 0
}
function net__parse_addr(text: string, colon: int) -> bool {
function net__parse_addr(net_st: mut NetState, text: string, colon: int) -> bool {
var host = text
net__code_port = net__conf().port
net_st.net__code_port = net__conf(net_st).port
if colon > 0 {
host = text[0..colon]
net__code_port = Text.to_int(text[colon + 1..len(text)])
net_st.net__code_port = Text.to_int(text[colon + 1..len(text)])
}
net__code_ip = Udp.ip(host)
return net__code_ip != 0 and net__code_port > 0 and net__code_port < 65536
net_st.net__code_ip = Udp.ip(host)
return net_st.net__code_ip != 0 and net_st.net__code_port > 0 and net_st.net__code_port < 65536
}
export function net_code_ip() -> int { return net__code_ip }
export function net_code_port() -> int { return net__code_port }
export function net_code_ip(net_st: NetState) -> int { return net_st.net__code_ip }
export function net_code_port(net_st: NetState) -> int { return net_st.net__code_port }
# six of the code's letters: a room at the matchmaker
export function net_is_room(text: string) -> bool {

View file

@ -1,29 +1,29 @@
# peers.ludic - who this machine talks to, and the written message queued for them
function net__find(ip: int, port: int) -> int {
function net__find(net_st: NetState, ip: int, port: int) -> int {
for i in 0 .. NET_PEERS {
let p = net__peers[i]
let p = net_st.net__peers[i]
if p.on and p.ip == ip and p.port == port { return i }
}
return -1
}
# the slot talking to network player `pid`, or -1
export function np_of_pid(pid: int) -> int {
if net__peers == null { return -1 }
export function np_of_pid(net_st: NetState, pid: int) -> int {
if net_st.net__peers == null { return -1 }
for i in 0 .. NET_PEERS {
let p = net__peers[i]
let p = net_st.net__peers[i]
if p.on and p.pid == pid { return i }
}
return -1
}
function net__open(slot: int, ip: int, port: int, pid: int) -> void {
let p = net__peers[slot]
function net__open(net_st: NetState, slot: int, ip: int, port: int, pid: int) -> void {
let p = net_st.net__peers[slot]
p.on = true
p.ip = ip
p.port = port
p.pid = pid
p.heard = net__time
p.heard = net_st.net__time
p.next_id = 1
p.recv_id = 0
p.acked = 0
@ -32,29 +32,29 @@ function net__open(slot: int, ip: int, port: int, pid: int) -> void {
p.closing = false
}
export function np_close(slot: int) -> void {
if net__peers == null or slot < 0 or slot >= NET_PEERS { return }
let p = net__peers[slot]
export function np_close(net_st: NetState, slot: int) -> void {
if net_st.net__peers == null or slot < 0 or slot >= NET_PEERS { return }
let p = net_st.net__peers[slot]
p.on = false
p.closing = false
p.queue = new []NetMsg
}
# the written message, for one peer (a reliable one is kept until it is taken)
export function np_queue(slot: int, kind: int, rel: bool) -> void {
if net__peers == null or slot < 0 or slot >= NET_PEERS { return }
let p = net__peers[slot]
export function np_queue(net_st: NetState, slot: int, kind: int, rel: bool) -> void {
if net_st.net__peers == null or slot < 0 or slot >= NET_PEERS { return }
let p = net_st.net__peers[slot]
if not p.on { return }
let m = new NetMsg
m.kind = kind
m.rel = rel
m.sent = -1.0
m.made = net__time
var n = net__wl
m.made = net_st.net__time
var n = net_st.net__wl
if n > NET_MTU - 40 { n = NET_MTU - 40 }
m.len = n
m.data = buffer(n)
nb_copy(m.data, 0, net__wb, 0, n)
nb_copy(m.data, 0, net_st.net__wb, 0, n)
if rel {
m.id = p.next_id
p.next_id += 1
@ -63,48 +63,48 @@ export function np_queue(slot: int, kind: int, rel: bool) -> void {
}
# to everyone this machine talks to (a host: every guest but `except`; a guest: the host)
export function np_queue_all(kind: int, rel: bool, except: int) -> void {
if net__peers == null { return }
for i in 0 .. NET_PEERS { if net__peers[i].on and i != except { np_queue(i, kind, rel) } }
export function np_queue_all(net_st: NetState, kind: int, rel: bool, except: int) -> void {
if net_st.net__peers == null { return }
for i in 0 .. NET_PEERS { if net_st.net__peers[i].on and i != except { np_queue(net_st, i, kind, rel) } }
}
export function net_peer_on(slot: int) -> bool {
if net__peers == null or slot < 0 or slot >= NET_PEERS { return false }
return net__peers[slot].on
export function net_peer_on(net_st: NetState, slot: int) -> bool {
if net_st.net__peers == null or slot < 0 or slot >= NET_PEERS { return false }
return net_st.net__peers[slot].on
}
export function net_peer_pid(slot: int) -> int {
if not net_peer_on(slot) { return 0 }
return net__peers[slot].pid
export function net_peer_pid(net_st: NetState, slot: int) -> int {
if not net_peer_on(net_st, slot) { return 0 }
return net_st.net__peers[slot].pid
}
export function net_peer_ip(slot: int) -> int {
if not net_peer_on(slot) { return 0 }
return net__peers[slot].ip
export function net_peer_ip(net_st: NetState, slot: int) -> int {
if not net_peer_on(net_st, slot) { return 0 }
return net_st.net__peers[slot].ip
}
export function net_peer_port(slot: int) -> int {
if not net_peer_on(slot) { return 0 }
return net__peers[slot].port
export function net_peer_port(net_st: NetState, slot: int) -> int {
if not net_peer_on(net_st, slot) { return 0 }
return net_st.net__peers[slot].port
}
export function net_peer_heard(slot: int) -> float {
if not net_peer_on(slot) { return 0.0 }
return net__peers[slot].heard
export function net_peer_heard(net_st: NetState, slot: int) -> float {
if not net_peer_on(net_st, slot) { return 0.0 }
return net_st.net__peers[slot].heard
}
# let the slot go once it has taken everything queued for it so far, or after `secs`
export function net_close_when_sent(slot: int, secs: float) -> void {
if not net_peer_on(slot) { return }
let p = net__peers[slot]
export function net_close_when_sent(net_st: NetState, slot: int, secs: float) -> void {
if not net_peer_on(net_st, slot) { return }
let p = net_st.net__peers[slot]
p.closing = true
p.close_id = p.next_id - 1
p.close_t = net__time + secs
p.close_t = net_st.net__time + secs
}
# the slots in use (a host's count of guests, a welcome not yet sent included)
export function net_peer_count() -> int {
export function net_peer_count(net_st: NetState) -> int {
var n = 0
for i in 0 .. NET_PEERS { if net_peer_on(i) { n += 1 } }
for i in 0 .. NET_PEERS { if net_peer_on(net_st, i) { n += 1 } }
return n
}

View file

@ -58,29 +58,27 @@ export port NetSocket {
local_ip: fn() -> int = fn net__udp_local_ip
}
var net__cfg: NetConfig = null
var net__facts: Queue<NetFact> = null
export function net_config(c: NetConfig) -> void { net__cfg = c }
export function net_config(net_st: mut NetState, c: NetConfig) -> void { net_st.net__cfg = c }
function net__conf() -> NetConfig {
if net__cfg == null { net__cfg = new NetConfig }
return net__cfg
function net__conf(net_st: mut NetState) -> NetConfig {
if net_st.net__cfg == null { net_st.net__cfg = new NetConfig }
return net_st.net__cfg
}
export function net_facts() -> Queue<NetFact> {
if net__facts == null { net__facts = queue_new("net.facts") }
return net__facts
export function net_facts(base_st: mut BaseState, net_st: mut NetState) -> Queue<NetFact> {
if net_st.net__facts == null { net_st.net__facts = queue_new(base_st, "net.facts") }
return net_st.net__facts
}
function net__fact(what: int, slot: int, pid: int, why: int, text: string) -> void {
function net__fact(base_st: mut BaseState, net_st: mut NetState, what: int, slot: int, pid: int, why: int, text: string) -> void {
let f = new NetFact
f.what = what
f.slot = slot
f.pid = pid
f.why = why
f.text = text
q_push(net_facts(), f)
q_push(base_st, net_facts(base_st, net_st), f)
}
function net__no_log(s: string) -> void { }

View file

@ -1,57 +1,51 @@
# punch.ludic - the host sends a few packets to each new guest's outside address, so its router
# expects the guest, while the guest's hello goes the other way: both routers have seen traffic
# go out, so the replies come in
var net__seen: []string = null # guests already punched for
var net__punch_ip: words = null
var net__punch_port: words = null
var net__punch_left: words = null
var net__punch_t: float = 0.0
var net__punch_b: []byte = null # the punch itself, "MP" and two zeros, made once
function net__new_guest(pub: string, local: string) -> void {
function net__new_guest(net_st: mut NetState, pub: string, local: string) -> void {
let key = `{pub}|{local}`
if net__seen == null { net__seen = new []string }
for i in 0 .. len(net__seen) { if net__seen[i] == key { return } }
push(net__seen, key)
net__log(`room {net__room}: a guest at {pub} / {local}`)
if len(pub) > 0 and net_parse_code(pub) { net__punch_add(net__code_ip, net__code_port) }
if net_parse_code(local) { net__punch_add(net__code_ip, net__code_port) }
if net_st.net__seen == null { net_st.net__seen = new []string }
for i in 0 .. len(net_st.net__seen) { if net_st.net__seen[i] == key { return } }
push(net_st.net__seen, key)
net__log(`room {net_st.net__room}: a guest at {pub} / {local}`)
if len(pub) > 0 and net_parse_code(net_st, pub) { net__punch_add(net_st, net_st.net__code_ip, net_st.net__code_port) }
if net_parse_code(net_st, local) { net__punch_add(net_st, net_st.net__code_ip, net_st.net__code_port) }
}
function net__punch_add(ip: int, port: int) -> void {
if net__punch_ip == null {
net__punch_ip = words(16)
net__punch_port = words(16)
net__punch_left = words(16)
for k in 0 .. 16 { net__punch_left[k] = 0 }
function net__punch_add(net_st: mut NetState, ip: int, port: int) -> void {
if net_st.net__punch_ip == null {
net_st.net__punch_ip = words(16)
net_st.net__punch_port = words(16)
net_st.net__punch_left = words(16)
for k in 0 .. 16 { net_st.net__punch_left[k] = 0 }
}
for k in 0 .. 16 {
if net__punch_left[k] > 0 { continue }
net__punch_ip[k] = ip
net__punch_port[k] = port
net__punch_left[k] = 10
if net_st.net__punch_left[k] > 0 { continue }
net_st.net__punch_ip[k] = ip
net_st.net__punch_port[k] = port
net_st.net__punch_left[k] = 10
return
}
}
# twice a second while a guest is new: a packet the guest's side ignores, which is the point
function net__punch_tick(dt: float) -> void {
if net__punch_ip == null { return }
net__punch_t = net__punch_t - dt
if net__punch_t > 0.0 { return }
net__punch_t = 0.5
if net__punch_b == null {
net__punch_b = buffer(4)
nb_put8(net__punch_b, 0, 'M')
nb_put8(net__punch_b, 1, 'P')
function net__punch_tick(net_st: mut NetState, dt: float) -> void {
if net_st.net__punch_ip == null { return }
net_st.net__punch_t = net_st.net__punch_t - dt
if net_st.net__punch_t > 0.0 { return }
net_st.net__punch_t = 0.5
if net_st.net__punch_b == null {
net_st.net__punch_b = buffer(4)
nb_put8(net_st.net__punch_b, 0, 'M')
nb_put8(net_st.net__punch_b, 1, 'P')
}
for k in 0 .. 16 {
if net__punch_left[k] <= 0 { continue }
NetSocket.send(net__sock, net__punch_ip[k], net__punch_port[k], net__punch_b, 4)
net__punch_left[k] -= 1
if net_st.net__punch_left[k] <= 0 { continue }
NetSocket.send(net_st.net__sock, net_st.net__punch_ip[k], net_st.net__punch_port[k], net_st.net__punch_b, 4)
net_st.net__punch_left[k] -= 1
}
}
function net__punch_clear() -> void {
if net__punch_left != null { for k in 0 .. 16 { net__punch_left[k] = 0 } }
function net__punch_clear(net_st: mut NetState) -> void {
if net_st.net__punch_left != null { for k in 0 .. 16 { net_st.net__punch_left[k] = 0 } }
}

View file

@ -1,17 +1,17 @@
# queries.ludic - what the game asks of the session
export function net_state() -> int { return net__state }
export function net_live() -> bool { return net__state == NET_HOSTING or net__state == NET_JOINED }
export function net_active() -> bool { return net__state != NET_IDLE and net__state != NET_LOST }
export function net_hosting() -> bool { return net__state == NET_HOSTING }
export function net_joined() -> bool { return net__state == NET_JOINED }
export function net_joining() -> bool { return net__state == NET_JOINING }
export function net_my_pid() -> int { return net__my_pid }
export function net_token() -> int { return net__token }
export function net_time() -> float { return net__time }
export function net_why() -> int { return net__why }
export function net_join_ip() -> int { return net__join_ip }
export function net_join_port() -> int { return net__join_port }
export function net_port() -> int {
if net__sock <= 0 { return 0 }
return NetSocket.port(net__sock)
export function net_state(net_st: NetState) -> int { return net_st.net__state }
export function net_live(net_st: NetState) -> bool { return net_st.net__state == NET_HOSTING or net_st.net__state == NET_JOINED }
export function net_active(net_st: NetState) -> bool { return net_st.net__state != NET_IDLE and net_st.net__state != NET_LOST }
export function net_hosting(net_st: NetState) -> bool { return net_st.net__state == NET_HOSTING }
export function net_joined(net_st: NetState) -> bool { return net_st.net__state == NET_JOINED }
export function net_joining(net_st: NetState) -> bool { return net_st.net__state == NET_JOINING }
export function net_my_pid(net_st: NetState) -> int { return net_st.net__my_pid }
export function net_token(net_st: NetState) -> int { return net_st.net__token }
export function net_time(net_st: NetState) -> float { return net_st.net__time }
export function net_why(net_st: NetState) -> int { return net_st.net__why }
export function net_join_ip(net_st: NetState) -> int { return net_st.net__join_ip }
export function net_join_port(net_st: NetState) -> int { return net_st.net__join_port }
export function net_port(net_st: NetState) -> int {
if net_st.net__sock <= 0 { return 0 }
return NetSocket.port(net_st.net__sock)
}

View file

@ -1,69 +1,69 @@
# receive.ludic - what arrived, unwrapped from the relay, checked, acknowledged and put in order
# on the inbox. A reliable message is taken strictly in order; one that arrives early is dropped
# and comes again.
function net__pump() -> void {
if net__sock <= 0 { return }
function net__pump(base_st: mut BaseState, net_st: mut NetState) -> void {
if net_st.net__sock <= 0 { return }
for guard in 0 .. 200 {
let n = NetSocket.recv(net__sock, net__in, NET_MTU + 64)
let n = NetSocket.recv(net_st.net__sock, net_st.net__in, NET_MTU + 64)
if n <= 0 { return }
let n2 = net__unwrap(n, NetSocket.from_ip(net__sock), NetSocket.from_port(net__sock))
if n2 > 0 { net__packet(n2, net__from_ip, net__from_port) }
let n2 = net__unwrap(net_st, n, NetSocket.from_ip(net_st.net__sock), NetSocket.from_port(net_st.net__sock))
if n2 > 0 { net__packet(base_st, net_st, n2, net_st.net__from_ip, net_st.net__from_port) }
}
}
function net__packet(n: int, ip: int, port: int) -> void {
function net__packet(base_st: mut BaseState, net_st: mut NetState, n: int, ip: int, port: int) -> void {
if NetWorld.trace() { net__log(`in {n} bytes from {Udp.ip_text(ip)}:{port}`) }
if n < 20 { return }
if net__stun_packet(n) { return } # what the router looks like from outside
if nb_get8(net__in, 0) != 'M' or nb_get8(net__in, 1) != 'L' { return } # a punch ("MP") or noise
if nb_get8(net__in, 2) != net__conf().proto { return }
let slot = net__slot_for(n, ip, port)
if net__stun_packet(net_st, n) { return } # what the router looks like from outside
if nb_get8(net_st.net__in, 0) != 'M' or nb_get8(net_st.net__in, 1) != 'L' { return } # a punch ("MP") or noise
if nb_get8(net_st.net__in, 2) != net__conf(net_st).proto { return }
let slot = net__slot_for(net_st, n, ip, port)
if slot < 0 { return }
let p = net__peers[slot]
p.heard = net__time
let p = net_st.net__peers[slot]
p.heard = net_st.net__time
# only what this connection has sent can be acknowledged: a packet from the one before it
# (a re-join) carries the old numbers and would drop the new hello as already taken
let ack = nb_get32(net__in, 12)
let ack = nb_get32(net_st.net__in, 12)
if ack > p.acked and ack < p.next_id { p.acked = ack }
net__messages(slot, n)
net__messages(base_st, net_st, slot, n)
}
# the known peer at that address, a stranger's new slot (a host, and only with a hello), or -1
function net__slot_for(n: int, ip: int, port: int) -> int {
let token = nb_get32(net__in, 4)
var slot = net__find(ip, port)
function net__slot_for(net_st: NetState, n: int, ip: int, port: int) -> int {
let token = nb_get32(net_st.net__in, 4)
var slot = net__find(net_st, ip, port)
if slot >= 0 {
if token != net__token and net__state != NET_JOINING and net__peers[slot].pid != 0 { return -1 }
if token != net_st.net__token and net_st.net__state != NET_JOINING and net_st.net__peers[slot].pid != 0 { return -1 }
return slot
}
if net__state != NET_HOSTING { return -1 }
if n < 28 or nb_get8(net__in, 20) != NET_HELLO { return -1 }
if net_st.net__state != NET_HOSTING { return -1 }
if n < 28 or nb_get8(net_st.net__in, 20) != NET_HELLO { return -1 }
for i in 1 .. NET_PEERS {
if not net__peers[i].on {
net__open(i, ip, port, 0)
if not net_st.net__peers[i].on {
net__open(net_st, i, ip, port, 0)
return i
}
}
return -1
}
function net__messages(slot: int, n: int) -> void {
let p = net__peers[slot]
let from = nb_get32(net__in, 8)
let count = nb_get32(net__in, 16)
function net__messages(base_st: mut BaseState, net_st: mut NetState, slot: int, n: int) -> void {
let p = net_st.net__peers[slot]
let from = nb_get32(net_st.net__in, 8)
let count = nb_get32(net_st.net__in, 16)
var o = 20
for c in 0 .. count {
if o + 4 > n { return }
let kind = nb_get8(net__in, o)
let rel = nb_get8(net__in, o + 1) == 1
let kind = nb_get8(net_st.net__in, o)
let rel = nb_get8(net_st.net__in, o + 1) == 1
o += 2
var id = 0
if rel {
if o + 4 > n { return }
id = nb_get32(net__in, o)
id = nb_get32(net_st.net__in, o)
o += 4
}
let ml = nb_get8(net__in, o) | (nb_get8(net__in, o + 1) << 8)
let ml = nb_get8(net_st.net__in, o) | (nb_get8(net_st.net__in, o + 1) << 8)
o += 2
if ml < 0 or o + ml > n { return }
var take = true
@ -71,21 +71,21 @@ function net__messages(slot: int, n: int) -> void {
p.ack_due = true
if id != p.recv_id + 1 { take = false } else { p.recv_id = id }
}
if take { net__deliver(slot, from, kind, o, ml) }
if take { net__deliver(base_st, net_st, slot, from, kind, o, ml) }
o += ml
}
}
# onto the inbox - except a heartbeat, and on a host anything but a hello before its welcome
function net__deliver(slot: int, from: int, kind: int, o: int, ml: int) -> void {
function net__deliver(base_st: mut BaseState, net_st: mut NetState, slot: int, from: int, kind: int, o: int, ml: int) -> void {
if kind == NET_BEAT { return }
if net__state == NET_HOSTING and net__peers[slot].pid < 2 and kind != NET_HELLO { return }
if net_st.net__state == NET_HOSTING and net_st.net__peers[slot].pid < 2 and kind != NET_HELLO { return }
let m = new NetMessage
m.slot = slot
m.from = from
m.kind = kind
m.len = ml
m.data = buffer(ml)
nb_copy(m.data, 0, net__in, o, ml)
q_push(net_inbox(), m)
nb_copy(m.data, 0, net_st.net__in, o, ml)
q_push(base_st, net_inbox(base_st, net_st), m)
}

View file

@ -2,22 +2,17 @@
# the host does not answer goes through the party server: "MLRG" + room + packet out, "MLRS" +
# session + packet back. The host keeps its address there fresh ("MLRH" + room) and sees each
# relayed guest as the relay's address with a port of relay_port0 + its session.
var net__relay_ip: int = 0
var net__relay_port: int = 0
var net__via_relay: bool = false # a guest talking to its host through the relay
var net__from_ip: int = 0
var net__from_port: int = 0
function net__take_relay(v: Val) -> void {
function net__take_relay(net_st: mut NetState, v: Val) -> void {
let r = net__json_str(v, "relay")
if len(r) > 0 and net_parse_code(r) {
net__relay_ip = net__code_ip
net__relay_port = net__code_port
if len(r) > 0 and net_parse_code(net_st, r) {
net_st.net__relay_ip = net_st.net__code_ip
net_st.net__relay_port = net_st.net__code_port
}
}
function net__code_bytes(b: []byte, o: int) -> void {
let cp = net__room
function net__code_bytes(net_st: NetState, b: []byte, o: int) -> void {
let cp = net_st.net__room
for k in 0 .. 6 {
var c = 0
if k < len(cp) { c = cp[k] }
@ -25,65 +20,65 @@ function net__code_bytes(b: []byte, o: int) -> void {
}
}
function net__relay_hello() -> void {
function net__relay_hello(net_st: NetState) -> void {
let b = buffer(10)
nb_put8(b, 0, 'M')
nb_put8(b, 1, 'L')
nb_put8(b, 2, 'R')
nb_put8(b, 3, 'H')
net__code_bytes(b, 4)
NetSocket.send(net__sock, net__relay_ip, net__relay_port, b, 10)
net__code_bytes(net_st, b, 4)
NetSocket.send(net_st.net__sock, net_st.net__relay_ip, net_st.net__relay_port, b, 10)
}
function net__use_relay() -> void {
net__via_relay = true
net__log(`room {net__room}: no answer directly, through the relay`)
net_dial(net__relay_ip, net__relay_port)
function net__use_relay(net_st: mut NetState) -> void {
net_st.net__via_relay = true
net__log(`room {net_st.net__room}: no answer directly, through the relay`)
net_dial(net_st, net_st.net__relay_ip, net_st.net__relay_port)
}
export function net_via_relay() -> bool { return net__via_relay }
export function net_via_relay(net_st: NetState) -> bool { return net_st.net__via_relay }
function net__relayed(p: NetPeer) -> bool {
return net__relay_ip != 0 and p.ip == net__relay_ip and (p.port >= net__conf().relay_port0 or net__via_relay)
function net__relayed(net_st: mut NetState, p: NetPeer) -> bool {
return net_st.net__relay_ip != 0 and p.ip == net_st.net__relay_ip and (p.port >= net__conf(net_st).relay_port0 or net_st.net__via_relay)
}
# a packet of `o` bytes in net__out, to peer p: straight, or wrapped for the relay
function net__relay_send(p: NetPeer, o: int) -> int {
if not net__relayed(p) { return NetSocket.send(net__sock, p.ip, p.port, net__out, o) }
function net__relay_send(net_st: mut NetState, p: NetPeer, o: int) -> int {
if not net__relayed(net_st, p) { return NetSocket.send(net_st.net__sock, p.ip, p.port, net_st.net__out, o) }
var hdr = 10
if not net__via_relay { hdr = 6 }
if not net_st.net__via_relay { hdr = 6 }
var k = o - 1
while k >= 0 {
net__out[k + hdr] = net__out[k]
net_st.net__out[k + hdr] = net_st.net__out[k]
k -= 1
}
nb_put8(net__out, 0, 'M')
nb_put8(net__out, 1, 'L')
nb_put8(net__out, 2, 'R')
if net__via_relay {
nb_put8(net__out, 3, 'G')
net__code_bytes(net__out, 4)
nb_put8(net_st.net__out, 0, 'M')
nb_put8(net_st.net__out, 1, 'L')
nb_put8(net_st.net__out, 2, 'R')
if net_st.net__via_relay {
nb_put8(net_st.net__out, 3, 'G')
net__code_bytes(net_st, net_st.net__out, 4)
} else {
let sid = p.port - net__conf().relay_port0
nb_put8(net__out, 3, 'S')
nb_put8(net__out, 4, (sid >> 8) & 255)
nb_put8(net__out, 5, sid & 255)
let sid = p.port - net__conf(net_st).relay_port0
nb_put8(net_st.net__out, 3, 'S')
nb_put8(net_st.net__out, 4, (sid >> 8) & 255)
nb_put8(net_st.net__out, 5, sid & 255)
}
let sent = NetSocket.send(net__sock, net__relay_ip, net__relay_port, net__out, o + hdr)
nb_copy(net__out, 0, net__out, hdr, o) # the packet back where it was
let sent = NetSocket.send(net_st.net__sock, net_st.net__relay_ip, net_st.net__relay_port, net_st.net__out, o + hdr)
nb_copy(net_st.net__out, 0, net_st.net__out, hdr, o) # the packet back where it was
return sent
}
# a relayed packet is unwrapped in place and given the sender it stands for; the game packet's
# length comes back, 0 when there is nothing for the game
function net__unwrap(n: int, ip: int, port: int) -> int {
net__from_ip = ip
net__from_port = port
if n < 6 or net__relay_ip == 0 or ip != net__relay_ip or port != net__relay_port { return n }
if nb_get8(net__in, 0) != 'M' or nb_get8(net__in, 1) != 'L' or nb_get8(net__in, 2) != 'R' or nb_get8(net__in, 3) != 'S' { return n }
let sid = (nb_get8(net__in, 4) << 8) | nb_get8(net__in, 5)
nb_copy(net__in, 0, net__in, 6, n - 6)
function net__unwrap(net_st: mut NetState, n: int, ip: int, port: int) -> int {
net_st.net__from_ip = ip
net_st.net__from_port = port
if n < 6 or net_st.net__relay_ip == 0 or ip != net_st.net__relay_ip or port != net_st.net__relay_port { return n }
if nb_get8(net_st.net__in, 0) != 'M' or nb_get8(net_st.net__in, 1) != 'L' or nb_get8(net_st.net__in, 2) != 'R' or nb_get8(net_st.net__in, 3) != 'S' { return n }
let sid = (nb_get8(net_st.net__in, 4) << 8) | nb_get8(net_st.net__in, 5)
nb_copy(net_st.net__in, 0, net_st.net__in, 6, n - 6)
# the host: this guest's session; a guest: the relay itself is its host
if sid > 0 { net__from_port = net__conf().relay_port0 + sid }
if sid > 0 { net_st.net__from_port = net__conf(net_st).relay_port0 + sid }
return n - 6
}

View file

@ -8,65 +8,54 @@ const NET_R_ROOM: int = 3 # the host: registered, watching for guests
const NET_R_ASK: int = 4 # a guest: asking the room for the host
const NET_R_DONE: int = 5 # a guest: the hello is on its way
var net__r_state: int = 0
var net__r_role: int = 0 # 1 host, 2 guest
var net__r_t: float = 0.0
var net__r_tries: int = 0
var net__pub_ip: int = 0
var net__pub_port: int = 0
var net__room: string = "" # the host's room code, or the one a guest asked for
var net__http: int = 0
var net__poll_t: float = 0.0
var net__relay_t: float = 0.0
var net__dial_heard: float = 0.0
# room codes are on when the game named a matchmaker
export function net_room_on() -> bool { return len(net__conf().signal_url) > 0 }
export function net_room() -> string { return net__room }
export function net_room_asking() -> bool { return net__r_state == NET_R_STUN or net__r_state == NET_R_ASK }
export function net_room_on(net_st: mut NetState) -> bool { return len(net__conf(net_st).signal_url) > 0 }
export function net_room(net_st: NetState) -> string { return net_st.net__room }
export function net_room_asking(net_st: NetState) -> bool { return net_st.net__r_state == NET_R_STUN or net_st.net__r_state == NET_R_ASK }
# the host (after net_host or net_become_host): a room at the matchmaker, if there is one
export function net_room_host() -> void {
if not net_room_on() { return }
net__r_role = 1
net__room = ""
net__seen = new []string
net__stun_begin()
export function net_room_host(net_st: mut NetState) -> void {
if not net_room_on(net_st) { return }
net_st.net__r_role = 1
net_st.net__room = ""
net_st.net__seen = new []string
net__stun_begin(net_st)
}
# a guest by room code: the matchmaker's answer brings the host's address (the hello is set first)
export function net_room_join(code: string) -> bool {
if not net_room_on() {
net__why = NET_WHY_NO_MATCHMAKER
export function net_room_join(net_st: mut NetState, code: string) -> bool {
if not net_room_on(net_st) {
net_st.net__why = NET_WHY_NO_MATCHMAKER
return false
}
net__init()
if not net__socket() { return false }
net__r_role = 2
net__room = Text.upper(code)
net__wait_more = 20 # the matchmaker answers before the host does
net__wait_for_host()
net__stun_begin()
net__init(net_st)
if not net__socket(net_st) { return false }
net_st.net__r_role = 2
net_st.net__room = Text.upper(code)
net_st.net__wait_more = 20 # the matchmaker answers before the host does
net__wait_for_host(net_st)
net__stun_begin(net_st)
return true
}
function net__stun_begin() -> void {
net__r_state = NET_R_STUN
net__r_t = 0.0
net__r_tries = 0
net__pub_ip = 0
net__pub_port = 0
if net__conf().no_stun {
net__r_tries = 3
function net__stun_begin(net_st: mut NetState) -> void {
net_st.net__r_state = NET_R_STUN
net_st.net__r_t = 0.0
net_st.net__r_tries = 0
net_st.net__pub_ip = 0
net_st.net__pub_port = 0
if net__conf(net_st).no_stun {
net_st.net__r_tries = 3
return
}
net__stun_send()
net__stun_send(net_st)
}
function net__room_body(role_key: string) -> string {
function net__room_body(net_st: NetState, role_key: string) -> string {
var pub = ""
if net__pub_ip != 0 { pub = net__addr_text(net__pub_ip, net__pub_port) }
return "{\"" + role_key + "\": \"" + pub + "\", \"local\": \"" + net__local_text() + "\"}"
if net_st.net__pub_ip != 0 { pub = net__addr_text(net_st.net__pub_ip, net_st.net__pub_port) }
return "{\"" + role_key + "\": \"" + pub + "\", \"local\": \"" + net__local_text(net_st) + "\"}"
}
function net__json_str(v: Val, key: string) -> string {
@ -76,8 +65,8 @@ function net__json_str(v: Val, key: string) -> string {
function net__addr_text(ip: int, port: int) -> string { return `{Udp.ip_text(ip)}:{port}` }
function net__local_text() -> string {
function net__local_text(net_st: NetState) -> string {
var ip = NetSocket.local_ip()
if ip == 0 { ip = Udp.ip("127.0.0.1") }
return net__addr_text(ip, net_port())
return net__addr_text(ip, net_port(net_st))
}

View file

@ -1,47 +1,47 @@
# room_connect.ludic - the matchmaker's answer: a guest dials the host it named (or is out),
# and leaving lets the room go
# the matchmaker let this machine down: a guest is out, a host keeps its code-less party
function net__room_fail(why: int) -> void {
function net__room_fail(base_st: mut BaseState, net_st: mut NetState, why: int) -> void {
net__log(`matchmaker: {why}`)
let was_guest = net__r_role == 2
let room = net__room
net__r_state = NET_R_IDLE
net__r_role = 0
if was_guest { net__lose_with(why, room) } else { net__fact(NET_ROOM_FAILED, -1, 0, why, room) }
let was_guest = net_st.net__r_role == 2
let room = net_st.net__room
net_st.net__r_state = NET_R_IDLE
net_st.net__r_role = 0
if was_guest { net__lose_with(base_st, net_st, why, room) } else { net__fact(base_st, net_st, NET_ROOM_FAILED, -1, 0, why, room) }
}
# the guest: to the host's outside address, or its inside one behind the same router
function net__room_connect(host_pub: string, host_local: string) -> void {
function net__room_connect(base_st: mut BaseState, net_st: mut NetState, host_pub: string, host_local: string) -> void {
var target = host_pub
var same_router = false
if len(host_pub) > 0 and net__pub_ip != 0 and net_parse_code(host_pub) and net__code_ip == net__pub_ip { same_router = true }
if len(host_pub) > 0 and net_st.net__pub_ip != 0 and net_parse_code(net_st, host_pub) and net_st.net__code_ip == net_st.net__pub_ip { same_router = true }
if len(host_pub) == 0 or same_router { target = host_local }
if not net_parse_code(target) {
net__room_fail(NET_WHY_BAD_ADDRESS)
if not net_parse_code(net_st, target) {
net__room_fail(base_st, net_st, NET_WHY_BAD_ADDRESS)
return
}
net__r_state = NET_R_DONE
net__r_t = 0.0
net__state = NET_IDLE # net_join wants a fresh start
net_join(net__code_ip, net__code_port)
net__dial_heard = net__peers[0].heard
if net__conf().force_relay and net__relay_ip != 0 { # a test of the relay alone
net__use_relay()
net_st.net__r_state = NET_R_DONE
net_st.net__r_t = 0.0
net_st.net__state = NET_IDLE # net_join wants a fresh start
net_join(net_st, net_st.net__code_ip, net_st.net__code_port)
net_st.net__dial_heard = net_st.net__peers[0].heard
if net__conf(net_st).force_relay and net_st.net__relay_ip != 0 { # a test of the relay alone
net__use_relay(net_st)
return
}
net__log(`room {net__room}: dialling {target}`)
net__log(`room {net_st.net__room}: dialling {target}`)
}
function net__room_reset() -> void {
if net__http != 0 {
Http.free(net__http)
net__http = 0
function net__room_reset(net_st: mut NetState) -> void {
if net_st.net__http != 0 {
Http.free(net_st.net__http)
net_st.net__http = 0
}
net__r_state = NET_R_IDLE
net__r_role = 0
net__room = ""
net__via_relay = false
net__relay_ip = 0
net__relay_port = 0
net__punch_clear()
net_st.net__r_state = NET_R_IDLE
net_st.net__r_role = 0
net_st.net__room = ""
net_st.net__via_relay = false
net_st.net__relay_ip = 0
net_st.net__relay_port = 0
net__punch_clear(net_st)
}

View file

@ -1,82 +1,82 @@
# room_tick.ludic - the matchmaker's side of a frame: STUN asked three times, the room registered
# or asked for, the host's look at who has asked, and a guest's switch to the relay
function net__room_tick(dt: float) -> void {
if net__r_state == NET_R_IDLE or net__r_role == 0 { return }
net__r_t = net__r_t + dt
if net__r_state == NET_R_STUN {
net__room_stun_tick()
function net__room_tick(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void {
if net_st.net__r_state == NET_R_IDLE or net_st.net__r_role == 0 { return }
net_st.net__r_t = net_st.net__r_t + dt
if net_st.net__r_state == NET_R_STUN {
net__room_stun_tick(net_st)
return
}
if net__r_state == NET_R_REGISTER or net__r_state == NET_R_ASK or (net__r_state == NET_R_ROOM and net__http != 0) {
net__room_answer()
if net_st.net__r_state == NET_R_REGISTER or net_st.net__r_state == NET_R_ASK or (net_st.net__r_state == NET_R_ROOM and net_st.net__http != 0) {
net__room_answer(base_st, net_st)
return
}
if net__r_state == NET_R_DONE {
if net_st.net__r_state == NET_R_DONE {
# four seconds and not one packet back from the host: through the relay instead (a slow
# welcome is not a closed router - anything heard means the direct path works)
let silent = net__peers[0].heard == net__dial_heard
let wait = net__conf().force_relay or (silent and net__r_t > 4.0)
if not net__via_relay and net__state == NET_JOINING and net__relay_ip != 0 and wait { net__use_relay() }
let silent = net_st.net__peers[0].heard == net_st.net__dial_heard
let wait = net__conf(net_st).force_relay or (silent and net_st.net__r_t > 4.0)
if not net_st.net__via_relay and net_st.net__state == NET_JOINING and net_st.net__relay_ip != 0 and wait { net__use_relay(net_st) }
return
}
if net__r_state == NET_R_ROOM { net__room_watch(dt) }
if net_st.net__r_state == NET_R_ROOM { net__room_watch(net_st, dt) }
}
# a second and a half for an answer, asked three times; without one, inside addresses only
function net__room_stun_tick() -> void {
if net__pub_ip == 0 and net__r_t > 0.5 and net__r_tries < 3 {
net__r_tries += 1
net__r_t = 0.0
net__stun_send()
function net__room_stun_tick(net_st: mut NetState) -> void {
if net_st.net__pub_ip == 0 and net_st.net__r_t > 0.5 and net_st.net__r_tries < 3 {
net_st.net__r_tries += 1
net_st.net__r_t = 0.0
net__stun_send(net_st)
return
}
if net__pub_ip == 0 and net__r_tries < 3 { return }
let base = net__conf().signal_url
if net__r_role == 1 {
net__http = Http.post(`{base}/rooms`, net__room_body("host"))
net__r_state = NET_R_REGISTER
if net_st.net__pub_ip == 0 and net_st.net__r_tries < 3 { return }
let base = net__conf(net_st).signal_url
if net_st.net__r_role == 1 {
net_st.net__http = Http.post(`{base}/rooms`, net__room_body(net_st, "host"))
net_st.net__r_state = NET_R_REGISTER
} else {
net__http = Http.post(`{base}/rooms/{net__room}/join`, net__room_body("pub"))
net__r_state = NET_R_ASK
net_st.net__http = Http.post(`{base}/rooms/{net_st.net__room}/join`, net__room_body(net_st, "pub"))
net_st.net__r_state = NET_R_ASK
}
net__r_t = 0.0
net_st.net__r_t = 0.0
}
function net__room_answer() -> void {
let st = Http.poll(net__http)
function net__room_answer(base_st: mut BaseState, net_st: mut NetState) -> void {
let st = Http.poll(net_st.net__http)
if st < 0 {
if net__r_t > 12.0 {
Http.free(net__http)
net__http = 0
net__room_fail(NET_WHY_MATCHMAKER_SILENT)
if net_st.net__r_t > 12.0 {
Http.free(net_st.net__http)
net_st.net__http = 0
net__room_fail(base_st, net_st, NET_WHY_MATCHMAKER_SILENT)
}
return
}
var v: Val = null
if st > 0 and Http.ok(net__http) { v = Json.parse(Http.text(net__http)) }
Http.free(net__http)
net__http = 0
if net__r_state == NET_R_REGISTER {
if st > 0 and Http.ok(net_st.net__http) { v = Json.parse(Http.text(net_st.net__http)) }
Http.free(net_st.net__http)
net_st.net__http = 0
if net_st.net__r_state == NET_R_REGISTER {
let code = net__json_str(v, "code")
if len(code) == 0 {
net__room_fail(NET_WHY_NO_ROOM_GIVEN)
net__room_fail(base_st, net_st, NET_WHY_NO_ROOM_GIVEN)
return
}
net__room = code
net__take_relay(v)
net__r_state = NET_R_ROOM
net__poll_t = 0.0
net_st.net__room = code
net__take_relay(net_st, v)
net_st.net__r_state = NET_R_ROOM
net_st.net__poll_t = 0.0
net__log(`room {code}`)
net__fact(NET_ROOM, -1, 0, 0, code)
net__fact(base_st, net_st, NET_ROOM, -1, 0, 0, code)
return
}
if net__r_state == NET_R_ASK {
if net_st.net__r_state == NET_R_ASK {
if v == null {
net__room_fail(NET_WHY_NO_ROOM)
net__room_fail(base_st, net_st, NET_WHY_NO_ROOM)
return
}
net__take_relay(v)
net__room_connect(net__json_str(v, "host"), net__json_str(v, "local"))
net__take_relay(net_st, v)
net__room_connect(base_st, net_st, net__json_str(v, "host"), net__json_str(v, "local"))
return
}
# the host's look at the room
@ -84,23 +84,23 @@ function net__room_answer() -> void {
let l = value_get(v, "guests")
for i in 0 .. value_count(l) {
let g = value_at(l, i)
net__new_guest(net__json_str(g, "pub"), net__json_str(g, "local"))
net__new_guest(net_st, net__json_str(g, "pub"), net__json_str(g, "local"))
}
}
}
# the host, registered: its relay address kept fresh, new guests punched for, the room looked at
function net__room_watch(dt: float) -> void {
net__relay_t = net__relay_t - dt
if net__relay_t < 0.0 and net__relay_ip != 0 {
net__relay_t = 3.0
net__relay_hello()
function net__room_watch(net_st: mut NetState, dt: float) -> void {
net_st.net__relay_t = net_st.net__relay_t - dt
if net_st.net__relay_t < 0.0 and net_st.net__relay_ip != 0 {
net_st.net__relay_t = 3.0
net__relay_hello(net_st)
}
net__punch_tick(dt)
net__poll_t = net__poll_t - dt
if net__poll_t < 0.0 {
net__poll_t = 1.0
net__http = Http.get(`{net__conf().signal_url}/rooms/{net__room}`)
net__r_t = 0.0
net__punch_tick(net_st, dt)
net_st.net__poll_t = net_st.net__poll_t - dt
if net_st.net__poll_t < 0.0 {
net_st.net__poll_t = 1.0
net_st.net__http = Http.get(`{net__conf(net_st).signal_url}/rooms/{net_st.net__room}`)
net_st.net__r_t = 0.0
}
}

View file

@ -3,31 +3,31 @@
# highest reliable id taken in order, a message count - then each message: kind, flags, a
# reliable id when it is reliable, a 16-bit length, the payload. A reliable message goes again
# every quarter second until acknowledged; an unreliable one half a second old is dropped.
export function np_flush(slot: int) -> void {
if not net_peer_on(slot) { return }
let p = net__peers[slot]
export function np_flush(net_st: mut NetState, slot: int) -> void {
if not net_peer_on(net_st, slot) { return }
let p = net_st.net__peers[slot]
for pk in 0 .. 8 {
if not net__flush_one(p) { return }
if not net__flush_one(net_st, p) { return }
}
}
function net__flush_one(p: NetPeer) -> bool {
function net__flush_one(net_st: mut NetState, p: NetPeer) -> bool {
var o = 20
var count = 0
let keep = new []NetMsg
for i in 0 .. len(p.queue) {
let m = p.queue[i]
if m.rel and m.id <= p.acked { continue } # taken: drop it
if not m.rel and net__time - m.made > 0.5 { continue } # stale
if not m.rel and net_st.net__time - m.made > 0.5 { continue } # stale
var due = true
if m.rel and not (m.sent < 0.0) and net__time - m.sent < 0.25 { due = false }
if m.rel and not (m.sent < 0.0) and net_st.net__time - m.sent < 0.25 { due = false }
var size = 4 + m.len
if m.rel { size += 4 }
if due and o + size <= NET_MTU and count < 250 {
o = net__put_msg(o, m)
o = net__put_msg(net_st, o, m)
count += 1
m.sent = net__time
if net__kind_out != null and m.kind < 64 { net__kind_out[m.kind] += size }
m.sent = net_st.net__time
if net_st.net__kind_out != null and m.kind < 64 { net_st.net__kind_out[m.kind] += size }
if m.rel { push(keep, m) } # kept until acknowledged
} else {
push(keep, m) # not due, or waits for the next packet
@ -35,47 +35,47 @@ function net__flush_one(p: NetPeer) -> bool {
}
p.queue = keep
if count == 0 and not p.ack_due { return false }
nb_put8(net__out, 0, 'M')
nb_put8(net__out, 1, 'L')
nb_put8(net__out, 2, net__conf().proto)
nb_put8(net__out, 3, 1)
nb_put32(net__out, 4, net__token)
nb_put32(net__out, 8, net__my_pid)
nb_put32(net__out, 12, p.recv_id)
nb_put32(net__out, 16, count)
let sent = net__relay_send(p, o)
if sent > 0 { net__bytes_out += sent + 28 } # with the IP and UDP headers: what the line carries
nb_put8(net_st.net__out, 0, 'M')
nb_put8(net_st.net__out, 1, 'L')
nb_put8(net_st.net__out, 2, net__conf(net_st).proto)
nb_put8(net_st.net__out, 3, 1)
nb_put32(net_st.net__out, 4, net_st.net__token)
nb_put32(net_st.net__out, 8, net_st.net__my_pid)
nb_put32(net_st.net__out, 12, p.recv_id)
nb_put32(net_st.net__out, 16, count)
let sent = net__relay_send(net_st, p, o)
if sent > 0 { net_st.net__bytes_out += sent + 28 } # with the IP and UDP headers: what the line carries
if NetWorld.trace() { net__log(`out {o} bytes to {Udp.ip_text(p.ip)}:{p.port}: {sent}`) }
p.ack_due = false
return count > 0
}
function net__put_msg(at: int, m: NetMsg) -> int {
function net__put_msg(net_st: NetState, at: int, m: NetMsg) -> int {
var o = at
nb_put8(net__out, o, m.kind)
nb_put8(net__out, o + 1, 0)
if m.rel { nb_put8(net__out, o + 1, 1) }
nb_put8(net_st.net__out, o, m.kind)
nb_put8(net_st.net__out, o + 1, 0)
if m.rel { nb_put8(net_st.net__out, o + 1, 1) }
o += 2
if m.rel {
nb_put32(net__out, o, m.id)
nb_put32(net_st.net__out, o, m.id)
o += 4
}
nb_put8(net__out, o, m.len & 255)
nb_put8(net__out, o + 1, (m.len >> 8) & 255)
nb_put8(net_st.net__out, o, m.len & 255)
nb_put8(net_st.net__out, o + 1, (m.len >> 8) & 255)
o += 2
nb_copy(net__out, o, m.data, 0, m.len)
nb_copy(net_st.net__out, o, m.data, 0, m.len)
return o + m.len
}
# the per-kind byte count starts again (a game measuring what a party costs its upload)
export function net_kind_out_clear() -> void {
net__kind_out = words(64)
for k in 0 .. 64 { net__kind_out[k] = 0 }
export function net_kind_out_clear(net_st: mut NetState) -> void {
net_st.net__kind_out = words(64)
for k in 0 .. 64 { net_st.net__kind_out[k] = 0 }
}
export function net_kind_bytes(kind: int) -> int {
if net__kind_out == null or kind < 0 or kind >= 64 { return 0 }
return net__kind_out[kind]
export function net_kind_bytes(net_st: NetState, kind: int) -> int {
if net_st.net__kind_out == null or kind < 0 or kind >= 64 { return 0 }
return net_st.net__kind_out[kind]
}
export function net_bytes_out() -> int { return net__bytes_out }
export function net_bytes_out(net_st: NetState) -> int { return net_st.net__bytes_out }

View file

@ -1,106 +1,105 @@
# session.ludic - hosting, joining and leaving. The host is pid 1 and hands out 2 upwards; this
# machine's pid is what its packets say. A token picked by the host names the session.
var net__why: int = 0
# open a socket on `port` and take guests; false (net_why) when the port is taken
export function net_host(port: int, token: int) -> bool {
net__init()
if net_live() { return true }
net__sock = NetSocket.open(port)
if net__sock <= 0 {
net__sock = 0
net__why = NET_WHY_PORT_TAKEN
export function net_host(net_st: mut NetState, port: int, token: int) -> bool {
net__init(net_st)
if net_live(net_st) { return true }
net_st.net__sock = NetSocket.open(port)
if net_st.net__sock <= 0 {
net_st.net__sock = 0
net_st.net__why = NET_WHY_PORT_TAKEN
return false
}
for i in 0 .. NET_PEERS { np_close(i) }
net_become_host(token)
net__log(`hosting on port {NetSocket.port(net__sock)}`)
net_room_host()
for i in 0 .. NET_PEERS { np_close(net_st, i) }
net_become_host(net_st, token)
net__log(`hosting on port {NetSocket.port(net_st.net__sock)}`)
net_room_host(net_st)
return true
}
# dial a host at an address; the hello (net_hello_set) goes until a welcome or a refusal
export function net_join(ip: int, port: int) -> bool {
net__init()
if net_live() { return false }
if not net__socket() { return false }
for i in 0 .. NET_PEERS { np_close(i) }
net__token = 0
net__my_pid = 0
net__state = NET_JOINING
net_dial(ip, port)
export function net_join(net_st: mut NetState, ip: int, port: int) -> bool {
net__init(net_st)
if net_live(net_st) { return false }
if not net__socket(net_st) { return false }
for i in 0 .. NET_PEERS { np_close(net_st, i) }
net_st.net__token = 0
net_st.net__my_pid = 0
net_st.net__state = NET_JOINING
net_dial(net_st, ip, port)
net__log(`joining {Udp.ip_text(ip)}:{port}`)
return true
}
function net__socket() -> bool {
if net__sock <= 0 { net__sock = NetSocket.open(0) }
if net__sock > 0 { return true }
net__sock = 0
net__why = NET_WHY_NO_SOCKET
function net__socket(net_st: mut NetState) -> bool {
if net_st.net__sock <= 0 { net_st.net__sock = NetSocket.open(0) }
if net_st.net__sock > 0 { return true }
net_st.net__sock = 0
net_st.net__why = NET_WHY_NO_SOCKET
return false
}
# the message just written is the hello: what a host is asked to let this machine in with
export function net_hello_set() -> void {
net__init()
nb_copy(net__hello_b, 0, net__wb, 0, net__wl)
net__hello_l = net__wl
if net__hello_l > NET_MTU - 40 { net__hello_l = NET_MTU - 40 }
export function net_hello_set(net_st: mut NetState) -> void {
net__init(net_st)
nb_copy(net_st.net__hello_b, 0, net_st.net__wb, 0, net_st.net__wl)
net_st.net__hello_l = net_st.net__wl
if net_st.net__hello_l > NET_MTU - 40 { net_st.net__hello_l = NET_MTU - 40 }
}
# (again) to that address, with a fresh connection and the hello sent anew
export function net_dial(ip: int, port: int) -> void {
net__init()
np_close(0)
net__join_ip = ip
net__join_port = port
net__open(0, ip, port, 1)
net__hello_t = -1.0
export function net_dial(net_st: mut NetState, ip: int, port: int) -> void {
net__init(net_st)
np_close(net_st, 0)
net_st.net__join_ip = ip
net_st.net__join_port = port
net__open(net_st, 0, ip, port, 1)
net_st.net__hello_t = -1.0
}
# a host that set out on a new trip, or a new host: the same socket asks to be let in there
export function net_follow(ip: int, port: int) -> void {
net_dial(ip, port)
net__token = 0
net__my_pid = 0
net__state = NET_JOINING
export function net_follow(net_st: mut NetState, ip: int, port: int) -> void {
net_dial(net_st, ip, port)
net_st.net__token = 0
net_st.net__my_pid = 0
net_st.net__state = NET_JOINING
}
# the old host is gone and this machine keeps the socket, alone (it may become the host)
export function net_stand_alone() -> void { net__state = NET_IDLE }
export function net_stand_alone(net_st: mut NetState) -> void { net_st.net__state = NET_IDLE }
export function net_become_host(token: int) -> void {
net__init()
net__state = NET_HOSTING
net__my_pid = 1
net__next_pid = 2
net__token = token
export function net_become_host(net_st: mut NetState, token: int) -> void {
net__init(net_st)
net_st.net__state = NET_HOSTING
net_st.net__my_pid = 1
net_st.net__next_pid = 2
net_st.net__token = token
}
# a welcome: this machine is `pid` in the session `token`
export function net_joined_as(token: int, pid: int) -> void {
net__token = token
net__my_pid = pid
net__state = NET_JOINED
net__wait_more = 0
export function net_joined_as(net_st: mut NetState, token: int, pid: int) -> void {
net_st.net__token = token
net_st.net__my_pid = pid
net_st.net__state = NET_JOINED
net_st.net__wait_more = 0
}
# the host lets the stranger in `slot` in: its pid, from 2 upwards
export function net_accept(slot: int) -> int {
if net__state != NET_HOSTING or not net_peer_on(slot) { return 0 }
let p = net__peers[slot]
export function net_accept(net_st: mut NetState, slot: int) -> int {
if net_st.net__state != NET_HOSTING or not net_peer_on(net_st, slot) { return 0 }
let p = net_st.net__peers[slot]
if p.pid >= 2 { return p.pid }
p.pid = net__next_pid
net__next_pid += 1
p.pid = net_st.net__next_pid
net_st.net__next_pid += 1
return p.pid
}
# a slow host is given this many more seconds to answer (a map switch, a matchmaker)
export function net_hello_wait(secs: int) -> void { net__wait_more = secs }
export function net_hello_wait(net_st: mut NetState, secs: int) -> void { net_st.net__wait_more = secs }
# a room code: joining, with no address yet - the matchmaker's answer brings one
function net__wait_for_host() -> void {
net__state = NET_JOINING
net__hello_t = 0.0
function net__wait_for_host(net_st: mut NetState) -> void {
net_st.net__state = NET_JOINING
net_st.net__hello_t = 0.0
}

View file

@ -47,51 +47,82 @@ export property NetMessage {
data: []byte
}
var net__state: int = 0
var net__sock: int = 0
var net__time: float = 0.0
var net__token: int = 0
var net__my_pid: int = 0
var net__next_pid: int = 2
var net__peers: []NetPeer = null
var net__in: []byte = null
var net__out: []byte = null
var net__wb: []byte = null # the message being written
var net__wl: int = 0
var net__rb: []byte = null # the message being read
var net__rl: int = 0
var net__rp: int = 0
var net__rbad: bool = false
var net__tb: []byte = null # text read out of a message, through one scratch buffer
var net__hello_b: []byte = null # the game's hello, sent again on every dial
var net__hello_l: int = 0
var net__hello_t: float = 0.0 # seconds since it went; -1 not yet
var net__wait_more: int = 0 # extra seconds a slow host is given (a map switch, a matchmaker)
var net__beat_t: float = 0.0
var net__join_ip: int = 0
var net__join_port: int = 0
var net__code_ip: int = 0
var net__code_port: int = 0
var net__bytes_out: int = 0
var net__kind_out: words = null
var net__inbox: Queue<NetMessage> = null
export function net_inbox() -> Queue<NetMessage> {
if net__inbox == null { net__inbox = queue_new("net.inbox") }
return net__inbox
export state NetState {
net__cfg: NetConfig = null
net__facts: Queue<NetFact> = null
net__seen: []string = null # guests already punched for
net__punch_ip: words = null
net__punch_port: words = null
net__punch_left: words = null
net__punch_t: float = 0.0
net__punch_b: []byte = null # the punch itself, "MP" and two zeros, made once
net__relay_ip: int = 0
net__relay_port: int = 0
net__via_relay: bool = false # a guest talking to its host through the relay
net__from_ip: int = 0
net__from_port: int = 0
net__r_state: int = 0
net__r_role: int = 0 # 1 host, 2 guest
net__r_t: float = 0.0
net__r_tries: int = 0
net__pub_ip: int = 0
net__pub_port: int = 0
net__room: string = "" # the host's room code, or the one a guest asked for
net__http: int = 0
net__poll_t: float = 0.0
net__relay_t: float = 0.0
net__dial_heard: float = 0.0
net__why: int = 0
net__state: int = 0
net__sock: int = 0
net__time: float = 0.0
net__token: int = 0
net__my_pid: int = 0
net__next_pid: int = 2
net__peers: []NetPeer = null
net__in: []byte = null
net__out: []byte = null
net__wb: []byte = null # the message being written
net__wl: int = 0
net__rb: []byte = null # the message being read
net__rl: int = 0
net__rp: int = 0
net__rbad: bool = false
net__tb: []byte = null # text read out of a message, through one scratch buffer
net__hello_b: []byte = null # the game's hello, sent again on every dial
net__hello_l: int = 0
net__hello_t: float = 0.0 # seconds since it went; -1 not yet
net__wait_more: int = 0 # extra seconds a slow host is given (a map switch, a matchmaker)
net__beat_t: float = 0.0
net__join_ip: int = 0
net__join_port: int = 0
net__code_ip: int = 0
net__code_port: int = 0
net__bytes_out: int = 0
net__kind_out: words = null
net__inbox: Queue<NetMessage> = null
net__stun_ip: int = 0
net__stun_port: int = 0
net__txn: words = null
net__dice: Rng = null
}
function net__init() -> void {
if net__peers != null { return }
net__peers = new []NetPeer
export function net_inbox(base_st: mut BaseState, net_st: mut NetState) -> Queue<NetMessage> {
if net_st.net__inbox == null { net_st.net__inbox = queue_new(base_st, "net.inbox") }
return net_st.net__inbox
}
function net__init(net_st: mut NetState) -> void {
if net_st.net__peers != null { return }
net_st.net__peers = new []NetPeer
for i in 0 .. NET_PEERS {
let p = new NetPeer
p.queue = new []NetMsg
push(net__peers, p)
push(net_st.net__peers, p)
}
net__in = buffer(NET_MTU + 64)
net__out = buffer(NET_MTU + 64)
net__wb = buffer(NET_MTU)
net__rb = buffer(NET_MTU)
net__hello_b = buffer(NET_MTU)
net_st.net__in = buffer(NET_MTU + 64)
net_st.net__out = buffer(NET_MTU + 64)
net_st.net__wb = buffer(NET_MTU)
net_st.net__rb = buffer(NET_MTU)
net_st.net__hello_b = buffer(NET_MTU)
}

View file

@ -1,24 +1,20 @@
# stun.ludic - from the party's own socket, ask a public STUN server what this machine looks like
# from outside: the router's address and the port it mapped
var net__stun_ip: int = 0
var net__stun_port: int = 0
var net__txn: words = null
var net__dice: Rng = null
function net__stun_send() -> void {
if net__txn == null { net__txn = words(3) }
if net__dice == null { net__dice = rng_new(net__conf().seed) }
if net__stun_ip == 0 {
let c = net__conf()
if len(c.stun_at) > 0 and net_parse_code(c.stun_at) {
net__stun_ip = net__code_ip
net__stun_port = net__code_port
function net__stun_send(net_st: mut NetState) -> void {
if net_st.net__txn == null { net_st.net__txn = words(3) }
if net_st.net__dice == null { net_st.net__dice = rng_new(net__conf(net_st).seed) }
if net_st.net__stun_ip == 0 {
let c = net__conf(net_st)
if len(c.stun_at) > 0 and net_parse_code(net_st, c.stun_at) {
net_st.net__stun_ip = net_st.net__code_ip
net_st.net__stun_port = net_st.net__code_port
} else if len(c.stun_host) > 0 {
let host: pointer = c.stun_host
net__stun_ip = Udp.resolve(host)
net__stun_port = c.stun_port
net_st.net__stun_ip = Udp.resolve(host)
net_st.net__stun_port = c.stun_port
}
if net__stun_ip == 0 { return }
if net_st.net__stun_ip == 0 { return }
}
let b = buffer(20)
nb_put8(b, 0, 0) # a binding request, no attributes
@ -29,9 +25,9 @@ function net__stun_send() -> void {
nb_put8(b, 5, 0x12)
nb_put8(b, 6, 0xA4)
nb_put8(b, 7, 0x42)
for k in 0 .. 3 { net__txn[k] = rng_between(net__dice, 0, 2000000000) }
for k in 0 .. 12 { nb_put8(b, 8 + k, (net__txn[k / 4] >> ((k % 4) * 8)) & 255) }
NetSocket.send(net__sock, net__stun_ip, net__stun_port, b, 20)
for k in 0 .. 3 { net_st.net__txn[k] = rng_between(net_st.net__dice, 0, 2000000000) }
for k in 0 .. 12 { nb_put8(b, 8 + k, (net_st.net__txn[k / 4] >> ((k % 4) * 8)) & 255) }
NetSocket.send(net_st.net__sock, net_st.net__stun_ip, net_st.net__stun_port, b, 20)
}
function net__be16(p: []byte, o: int) -> int { return (nb_get8(p, o) << 8) | nb_get8(p, o + 1) }
@ -39,23 +35,23 @@ function net__be16(p: []byte, o: int) -> int { return (nb_get8(p, o) << 8) | nb_
function net__be32(p: []byte, o: int) -> int { return (net__be16(p, o) << 16) | net__be16(p, o + 2) }
# a STUN reply: true when it was one (whatever it said)
function net__stun_packet(n: int) -> bool {
if n < 20 or nb_get8(net__in, 0) != 1 or nb_get8(net__in, 1) != 1 { return false }
if net__be32(net__in, 4) != 0x2112A442 { return false }
let len = net__be16(net__in, 2)
function net__stun_packet(net_st: mut NetState, n: int) -> bool {
if n < 20 or nb_get8(net_st.net__in, 0) != 1 or nb_get8(net_st.net__in, 1) != 1 { return false }
if net__be32(net_st.net__in, 4) != 0x2112A442 { return false }
let len = net__be16(net_st.net__in, 2)
var o = 20
while o + 4 <= 20 + len and o + 4 <= n {
let t = net__be16(net__in, o)
let l = net__be16(net__in, o + 2)
if (t == 0x0020 or t == 0x0001) and l >= 8 and o + 12 <= n and nb_get8(net__in, o + 5) == 1 {
var port = net__be16(net__in, o + 6)
var ip = net__be32(net__in, o + 8)
let t = net__be16(net_st.net__in, o)
let l = net__be16(net_st.net__in, o + 2)
if (t == 0x0020 or t == 0x0001) and l >= 8 and o + 12 <= n and nb_get8(net_st.net__in, o + 5) == 1 {
var port = net__be16(net_st.net__in, o + 6)
var ip = net__be32(net_st.net__in, o + 8)
if t == 0x0020 {
port = port ^ 0x2112
ip = ip ^ 0x2112A442
}
net__pub_ip = ip
net__pub_port = port
net_st.net__pub_ip = ip
net_st.net__pub_port = port
net__log(`outside we are {net__addr_text(ip, port)}`)
}
o += 4 + ((l + 3) / 4) * 4
@ -64,5 +60,5 @@ function net__stun_packet(n: int) -> bool {
}
# what this machine looks like from outside, once STUN has said (0 until then)
export function net_outside_ip() -> int { return net__pub_ip }
export function net_outside_port() -> int { return net__pub_port }
export function net_outside_ip(net_st: NetState) -> int { return net_st.net__pub_ip }
export function net_outside_port(net_st: NetState) -> int { return net_st.net__pub_port }

View file

@ -5,50 +5,52 @@ import "ludic.base"
program NetTest {
numbers float
property Pkt { ip: int = 0, port: int = 0, len: int = 0, data: []byte }
var sent: []Pkt = null
var inbound: []Pkt = null
var from_ip: int = 0
var from_port: int = 0
state NettestState {
sent: []Pkt = null
inbound: []Pkt = null
from_ip: int = 0
from_port: int = 0
}
function fake_open(port: int) -> int { return 5 }
function fake_close(h: int) -> void { }
function fake_port(h: int) -> int { return 40000 }
function fake_local() -> int { return Udp.ip("10.0.0.5") }
function fake_from_ip(h: int) -> int { return from_ip }
function fake_from_port(h: int) -> int { return from_port }
function fake_send(h: int, ip: int, port: int, b: []byte, n: int) -> int {
function fake_from_ip(nettest_st: NettestState, h: int) -> int { return nettest_st.from_ip }
function fake_from_port(nettest_st: NettestState, h: int) -> int { return nettest_st.from_port }
function fake_send(nettest_st: mut NettestState, h: int, ip: int, port: int, b: []byte, n: int) -> int {
let p = new Pkt
p.ip = ip
p.port = port
p.len = n
p.data = buffer(n)
nb_copy(p.data, 0, b, 0, n)
push(sent, p)
push(nettest_st.sent, p)
return n
}
function fake_recv(h: int, b: []byte, cap: int) -> int {
if len(inbound) == 0 { return 0 }
let p = inbound[0]
function fake_recv(nettest_st: mut NettestState, h: int, b: []byte, cap: int) -> int {
if len(nettest_st.inbound) == 0 { return 0 }
let p = nettest_st.inbound[0]
let rest = new []Pkt
for i in 1 .. len(inbound) { push(rest, inbound[i]) }
inbound = rest
for i in 1 .. len(nettest_st.inbound) { push(rest, nettest_st.inbound[i]) }
nettest_st.inbound = rest
nb_copy(b, 0, p.data, 0, p.len)
from_ip = p.ip
from_port = p.port
nettest_st.from_ip = p.ip
nettest_st.from_port = p.port
return p.len
}
bind NetSocket { open: fn fake_open, close: fn fake_close, send: fn fake_send, recv: fn fake_recv, from_ip: fn fake_from_ip, from_port: fn fake_from_port, port: fn fake_port, local_ip: fn fake_local }
function fresh() -> void {
sent = new []Pkt
inbound = new []Pkt
net_reset()
q_clear(net_inbox())
q_clear(net_facts())
function fresh(base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) -> void {
nettest_st.sent = new []Pkt
nettest_st.inbound = new []Pkt
net_reset(net_st)
q_clear(base_st, net_inbox(base_st, net_st))
q_clear(base_st, net_facts(base_st, net_st))
}
# a packet from ip:port: token, pid, ack, then one message of `kind` (reliable with `id` > 0)
function arrive(ip: int, port: int, token: int, pid: int, ack: int, kind: int, id: int, payload: int) -> void {
function arrive(nettest_st: mut NettestState, ip: int, port: int, token: int, pid: int, ack: int, kind: int, id: int, payload: int) -> void {
let b = buffer(40)
nb_put8(b, 0, 'M')
nb_put8(b, 1, 'L')
@ -75,201 +77,201 @@ program NetTest {
p.port = port
p.len = o + 6
p.data = b
push(inbound, p)
push(nettest_st.inbound, p)
}
function frame(dt: float) -> void {
net_begin(dt)
net_update(dt)
net_end()
function frame(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void {
net_begin(base_st, net_st, dt)
net_update(base_st, net_st, dt)
net_end(base_st, net_st)
}
# the host with a guest let in at 10.0.0.9:5000 as pid 2
function host_with_guest() -> int {
fresh()
expect(net_host(27415, 77))
arrive(Udp.ip("10.0.0.9"), 5000, 0, 0, 0, NET_HELLO, 1, 42)
net_begin(0.01)
let got = q_drain(net_inbox())
function host_with_guest(base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) -> int {
fresh(base_st, net_st, nettest_st)
expect(net_host(net_st, 27415, 77))
arrive(nettest_st, Udp.ip("10.0.0.9"), 5000, 0, 0, 0, NET_HELLO, 1, 42)
net_begin(base_st, net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st))
expect_eq(len(got), 1)
return net_accept(got[0].slot)
return net_accept(net_st, got[0].slot)
}
function facts_of(what: int) -> []NetFact {
let all = q_drain(net_facts())
function facts_of(base_st: mut BaseState, net_st: mut NetState, what: int) -> []NetFact {
let all = q_drain(base_st, net_facts(base_st, net_st))
let out = new []NetFact
for i in 0 .. len(all) { if all[i].what == what { push(out, all[i]) } }
return out
}
test "the codec carries ints, bools, floats and text, and a short message reads bad" {
fresh()
nw_begin()
nw_i(-7)
nw_b(true)
nw_f(2.5)
nw_s("maroon")
net_read(net_written())
expect_eq(nr_i(), -7)
expect(nr_b())
expect_near(nr_f(), 2.5, 0.0001)
expect(nr_s() == "maroon")
expect(not nr_bad())
expect_eq(nr_i(), 0)
expect(nr_bad())
test "the codec carries ints, bools, floats and text, and a short message reads bad" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
fresh(base_st, net_st, nettest_st)
nw_begin(net_st)
nw_i(net_st, -7)
nw_b(net_st, true)
nw_f(net_st, 2.5)
nw_s(net_st, "maroon")
net_read(net_st, net_written(net_st))
expect_eq(nr_i(net_st), -7)
expect(nr_b(net_st))
expect_near(nr_f(net_st), 2.5, 0.0001)
expect(nr_s(net_st) == "maroon")
expect(not nr_bad(net_st))
expect_eq(nr_i(net_st), 0)
expect(nr_bad(net_st))
}
test "a join code decodes to its address and port; an address and a room code are taken too" {
fresh()
test "a join code decodes to its address and port; an address and a room code are taken too" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
fresh(base_st, net_st, nettest_st)
let ip = Udp.ip("192.168.1.20")
let code = net_code_of(ip, 27415)
expect_eq(len(code), 11)
expect(net_parse_code(code))
expect_eq(net_code_ip(), ip)
expect_eq(net_code_port(), 27415)
expect(net_parse_code(Text.lower(code)))
expect(net_parse_code("10.1.2.3:4000"))
expect_eq(net_code_port(), 4000)
expect(net_parse_code("10.1.2.3"))
expect_eq(net_code_port(), 27415)
expect(not net_parse_code("HELLO"))
expect(net_parse_code(net_st, code))
expect_eq(net_code_ip(net_st), ip)
expect_eq(net_code_port(net_st), 27415)
expect(net_parse_code(net_st, Text.lower(code)))
expect(net_parse_code(net_st, "10.1.2.3:4000"))
expect_eq(net_code_port(net_st), 4000)
expect(net_parse_code(net_st, "10.1.2.3"))
expect_eq(net_code_port(net_st), 27415)
expect(not net_parse_code(net_st, "HELLO"))
expect(net_is_room("abcdef"))
expect(not net_is_room("ABCDE1"))
}
test "a host takes a stranger only with a hello, and hands out pids from 2" {
let pid = host_with_guest()
test "a host takes a stranger only with a hello, and hands out pids from 2" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
let pid = host_with_guest(base_st, net_st, nettest_st)
expect_eq(pid, 2)
expect(net_hosting())
expect_eq(net_my_pid(), 1)
arrive(Udp.ip("10.0.0.10"), 6000, 0, 0, 0, 9, 1, 1)
net_begin(0.01)
expect_eq(q_len(net_inbox()), 0)
arrive(Udp.ip("10.0.0.11"), 6000, 0, 0, 0, NET_HELLO, 1, 1)
net_begin(0.01)
let got = q_drain(net_inbox())
expect_eq(net_accept(got[0].slot), 3)
expect(net_hosting(net_st))
expect_eq(net_my_pid(net_st), 1)
arrive(nettest_st, Udp.ip("10.0.0.10"), 6000, 0, 0, 0, 9, 1, 1)
net_begin(base_st, net_st, 0.01)
expect_eq(q_len(net_inbox(base_st, net_st)), 0)
arrive(nettest_st, Udp.ip("10.0.0.11"), 6000, 0, 0, 0, NET_HELLO, 1, 1)
net_begin(base_st, net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st))
expect_eq(net_accept(net_st, got[0].slot), 3)
}
test "a message's payload reads back as it was written" {
host_with_guest()
arrive(Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 2, 1234)
net_begin(0.01)
let got = q_drain(net_inbox())
test "a message's payload reads back as it was written" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
host_with_guest(base_st, net_st, nettest_st)
arrive(nettest_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 2, 1234)
net_begin(base_st, net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st))
expect_eq(len(got), 1)
expect_eq(got[0].kind, 9)
expect_eq(got[0].from, 2)
net_read(got[0])
expect_eq(nr_i(), 1234)
expect(not nr_bad())
nr_i()
expect(nr_bad())
net_read(net_st, got[0])
expect_eq(nr_i(net_st), 1234)
expect(not nr_bad(net_st))
nr_i(net_st)
expect(nr_bad(net_st))
}
test "a reliable message that arrives early is dropped and taken in order" {
let slot = np_of_pid(host_with_guest())
arrive(Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 3, 30)
net_begin(0.01)
expect_eq(q_len(net_inbox()), 0)
arrive(Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 2, 20)
arrive(Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 3, 30)
net_begin(0.01)
let got = q_drain(net_inbox())
test "a reliable message that arrives early is dropped and taken in order" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
let slot = np_of_pid(net_st, host_with_guest(base_st, net_st, nettest_st))
arrive(nettest_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 3, 30)
net_begin(base_st, net_st, 0.01)
expect_eq(q_len(net_inbox(base_st, net_st)), 0)
arrive(nettest_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 2, 20)
arrive(nettest_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 3, 30)
net_begin(base_st, net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st))
expect_eq(len(got), 2)
net_read(got[1])
expect_eq(nr_i(), 30)
net_read(net_st, got[1])
expect_eq(nr_i(net_st), 30)
expect_eq(got[1].slot, slot)
}
test "a reliable message goes again until it is taken" {
let slot = np_of_pid(host_with_guest())
net_end()
sent = new []Pkt
nw_begin()
nw_i(5)
np_queue(slot, 9, true)
frame(0.01)
expect_eq(len(sent), 1)
frame(0.1)
expect_eq(len(sent), 1)
frame(0.2)
expect_eq(len(sent), 2)
arrive(Udp.ip("10.0.0.9"), 5000, 77, 2, 1, 9, 0, 0)
frame(0.3)
let n = len(sent)
frame(0.3)
expect_eq(len(sent), n)
test "a reliable message goes again until it is taken" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
let slot = np_of_pid(net_st, host_with_guest(base_st, net_st, nettest_st))
net_end(base_st, net_st)
nettest_st.sent = new []Pkt
nw_begin(net_st)
nw_i(net_st, 5)
np_queue(net_st, slot, 9, true)
frame(base_st, net_st, 0.01)
expect_eq(len(nettest_st.sent), 1)
frame(base_st, net_st, 0.1)
expect_eq(len(nettest_st.sent), 1)
frame(base_st, net_st, 0.2)
expect_eq(len(nettest_st.sent), 2)
arrive(nettest_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 1, 9, 0, 0)
frame(base_st, net_st, 0.3)
let n = len(nettest_st.sent)
frame(base_st, net_st, 0.3)
expect_eq(len(nettest_st.sent), n)
}
test "an unreliable message half a second old is dropped" {
let slot = np_of_pid(host_with_guest())
net_end()
sent = new []Pkt
nw_begin()
np_queue(slot, 9, false)
net_begin(0.6)
net_end()
expect_eq(len(sent), 0)
test "an unreliable message half a second old is dropped" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
let slot = np_of_pid(net_st, host_with_guest(base_st, net_st, nettest_st))
net_end(base_st, net_st)
nettest_st.sent = new []Pkt
nw_begin(net_st)
np_queue(net_st, slot, 9, false)
net_begin(base_st, net_st, 0.6)
net_end(base_st, net_st)
expect_eq(len(nettest_st.sent), 0)
}
test "a guest sends its hello until nobody has answered for too long" {
fresh()
nw_begin()
nw_s("v1")
net_hello_set()
expect(net_join(Udp.ip("10.0.0.1"), 27415))
expect(net_joining())
frame(0.1)
expect_eq(len(sent), 1)
expect_eq(nb_get8(sent[0].data, 20), NET_HELLO)
for i in 0 .. 110 { frame(0.1) }
expect_eq(net_state(), NET_LOST)
let down = facts_of(NET_DOWN)
test "a guest sends its hello until nobody has answered for too long" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
fresh(base_st, net_st, nettest_st)
nw_begin(net_st)
nw_s(net_st, "v1")
net_hello_set(net_st)
expect(net_join(net_st, Udp.ip("10.0.0.1"), 27415))
expect(net_joining(net_st))
frame(base_st, net_st, 0.1)
expect_eq(len(nettest_st.sent), 1)
expect_eq(nb_get8(nettest_st.sent[0].data, 20), NET_HELLO)
for i in 0 .. 110 { frame(base_st, net_st, 0.1) }
expect_eq(net_state(net_st), NET_LOST)
let down = facts_of(base_st, net_st, NET_DOWN)
expect_eq(len(down), 1)
expect_eq(down[0].why, NET_WHY_NO_ANSWER)
net_fresh()
expect_eq(net_state(), NET_IDLE)
net_fresh(net_st)
expect_eq(net_state(net_st), NET_IDLE)
}
test "a welcome makes the guest that pid in that session" {
fresh()
nw_begin()
net_hello_set()
net_join(Udp.ip("10.0.0.1"), 27415)
net_joined_as(99, 4)
expect(net_live())
expect_eq(net_my_pid(), 4)
expect_eq(net_token(), 99)
test "a welcome makes the guest that pid in that session" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
fresh(base_st, net_st, nettest_st)
nw_begin(net_st)
net_hello_set(net_st)
net_join(net_st, Udp.ip("10.0.0.1"), 27415)
net_joined_as(net_st, 99, 4)
expect(net_live(net_st))
expect_eq(net_my_pid(net_st), 4)
expect_eq(net_token(net_st), 99)
}
test "a guest silent for the timeout is gone" {
let pid = host_with_guest()
let slot = np_of_pid(pid)
for i in 0 .. 105 { frame(0.1) }
let gone = facts_of(NET_GONE)
test "a guest silent for the timeout is gone" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
let pid = host_with_guest(base_st, net_st, nettest_st)
let slot = np_of_pid(net_st, pid)
for i in 0 .. 105 { frame(base_st, net_st, 0.1) }
let gone = facts_of(base_st, net_st, NET_GONE)
expect_eq(len(gone), 1)
expect_eq(gone[0].pid, pid)
expect_eq(gone[0].slot, slot)
expect(not net_peer_on(slot))
expect(not net_peer_on(net_st, slot))
}
test "a slot is let go once it has taken its last message" {
let slot = np_of_pid(host_with_guest())
nw_begin()
np_queue(slot, 9, true)
net_close_when_sent(slot, 20.0)
frame(0.01)
expect(net_peer_on(slot))
arrive(Udp.ip("10.0.0.9"), 5000, 77, 2, 1, 9, 0, 0)
frame(0.01)
expect(not net_peer_on(slot))
expect_eq(len(facts_of(NET_CLOSED)), 1)
test "a slot is let go once it has taken its last message" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
let slot = np_of_pid(net_st, host_with_guest(base_st, net_st, nettest_st))
nw_begin(net_st)
np_queue(net_st, slot, 9, true)
net_close_when_sent(net_st, slot, 20.0)
frame(base_st, net_st, 0.01)
expect(net_peer_on(net_st, slot))
arrive(nettest_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 1, 9, 0, 0)
frame(base_st, net_st, 0.01)
expect(not net_peer_on(net_st, slot))
expect_eq(len(facts_of(base_st, net_st, NET_CLOSED)), 1)
}
test "with no matchmaker, a room code is refused" {
fresh()
expect(not net_room_on())
expect(not net_room_join("ABCDEF"))
expect_eq(net_why(), NET_WHY_NO_MATCHMAKER)
test "with no matchmaker, a room code is refused" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
fresh(base_st, net_st, nettest_st)
expect(not net_room_on(net_st))
expect(not net_room_join(net_st, "ABCDEF"))
expect_eq(net_why(net_st), NET_WHY_NO_MATCHMAKER)
}
}

View file

@ -1,111 +1,111 @@
# tick.ludic - a frame of the session, in three calls with the game's between them: net_begin
# (time, and what came in onto the inbox), net_update (the matchmaker, the hello, silence) and,
# once the game has queued its own, net_end (heartbeats, sending, letting slots go)
export function net_begin(dt: float) -> void {
if not net_active() { return }
net__time = net__time + dt
net__pump()
export function net_begin(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void {
if not net_active(net_st) { return }
net_st.net__time = net_st.net__time + dt
net__pump(base_st, net_st)
}
export function net_update(dt: float) -> void {
if not net_active() { return }
net__room_tick(dt)
if net__state == NET_JOINING {
net__hello_tick(dt)
export function net_update(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void {
if not net_active(net_st) { return }
net__room_tick(base_st, net_st, dt)
if net_st.net__state == NET_JOINING {
net__hello_tick(base_st, net_st, dt)
return
}
for i in 0 .. NET_PEERS {
let p = net__peers[i]
if not p.on or net__time - p.heard <= float(NET_TIMEOUT) { continue }
if net__state == NET_JOINED {
net_lose(NET_WHY_HOST_SILENT)
let p = net_st.net__peers[i]
if not p.on or net_st.net__time - p.heard <= float(NET_TIMEOUT) { continue }
if net_st.net__state == NET_JOINED {
net_lose(base_st, net_st, NET_WHY_HOST_SILENT)
return
}
let gone = p.pid
np_close(i)
net__fact(NET_GONE, i, gone, 0, "")
np_close(net_st, i)
net__fact(base_st, net_st, NET_GONE, i, gone, 0, "")
}
}
function net__hello_tick(dt: float) -> void {
if net__hello_t < 0.0 {
nb_copy(net__wb, 0, net__hello_b, 0, net__hello_l)
net__wl = net__hello_l
np_queue(0, NET_HELLO, true)
net__hello_t = 0.0
function net__hello_tick(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void {
if net_st.net__hello_t < 0.0 {
nb_copy(net_st.net__wb, 0, net_st.net__hello_b, 0, net_st.net__hello_l)
net_st.net__wl = net_st.net__hello_l
np_queue(net_st, 0, NET_HELLO, true)
net_st.net__hello_t = 0.0
}
net__hello_t = net__hello_t + dt
if net__hello_t > float(NET_TIMEOUT + net__wait_more) {
net_lose(NET_WHY_NO_ANSWER)
net_st.net__hello_t = net_st.net__hello_t + dt
if net_st.net__hello_t > float(NET_TIMEOUT + net_st.net__wait_more) {
net_lose(base_st, net_st, NET_WHY_NO_ANSWER)
return
}
np_flush(0)
np_flush(net_st, 0)
}
export function net_end() -> void {
if not net_live() { return }
let beat = net__time - net__beat_t >= 1.0
if beat { net__beat_t = net__time }
export function net_end(base_st: mut BaseState, net_st: mut NetState) -> void {
if not net_live(net_st) { return }
let beat = net_st.net__time - net_st.net__beat_t >= 1.0
if beat { net_st.net__beat_t = net_st.net__time }
for i in 0 .. NET_PEERS {
let p = net__peers[i]
let p = net_st.net__peers[i]
if not p.on { continue }
if beat {
nw_begin()
np_queue(i, NET_BEAT, false)
nw_begin(net_st)
np_queue(net_st, i, NET_BEAT, false)
}
np_flush(i)
if p.closing and (p.acked >= p.close_id or net__time > p.close_t) {
np_flush(net_st, i)
if p.closing and (p.acked >= p.close_id or net_st.net__time > p.close_t) {
let pid = p.pid
np_close(i)
net__fact(NET_CLOSED, i, pid, 0, "")
np_close(net_st, i)
net__fact(base_st, net_st, NET_CLOSED, i, pid, 0, "")
}
}
}
# while a load holds the frame: keep answering, so nobody times out on this machine
export function net_keepalive() -> void {
if not net_live() { return }
net__pump()
export function net_keepalive(base_st: mut BaseState, net_st: mut NetState) -> void {
if not net_live(net_st) { return }
net__pump(base_st, net_st)
for i in 0 .. NET_PEERS {
let p = net__peers[i]
let p = net_st.net__peers[i]
if p.on {
p.heard = net__time
nw_begin()
np_queue(i, NET_BEAT, false)
np_flush(i)
p.heard = net_st.net__time
nw_begin(net_st)
np_queue(net_st, i, NET_BEAT, false)
np_flush(net_st, i)
}
}
}
# every queue to its peer now, three times over (a machine about to let go)
export function net_flush_all() -> void {
for t in 0 .. 3 { for i in 0 .. NET_PEERS { np_flush(i) } }
export function net_flush_all(net_st: mut NetState) -> void {
for t in 0 .. 3 { for i in 0 .. NET_PEERS { np_flush(net_st, i) } }
}
# the session is over for this machine: every slot, the socket and the matchmaker let go
export function net_reset() -> void {
if net__peers != null { for i in 0 .. NET_PEERS { np_close(i) } }
if net__sock > 0 {
NetSocket.close(net__sock)
net__sock = 0
export function net_reset(net_st: mut NetState) -> void {
if net_st.net__peers != null { for i in 0 .. NET_PEERS { np_close(net_st, i) } }
if net_st.net__sock > 0 {
NetSocket.close(net_st.net__sock)
net_st.net__sock = 0
}
net__state = NET_IDLE
net__my_pid = 0
net__wait_more = 0
net__room_reset()
net_st.net__state = NET_IDLE
net_st.net__my_pid = 0
net_st.net__wait_more = 0
net__room_reset(net_st)
}
# out of the session, and why: the game hears NET_DOWN and says so in its own words
export function net_lose(why: int) -> void { net__lose_with(why, "") }
export function net_lose(base_st: mut BaseState, net_st: mut NetState, why: int) -> void { net__lose_with(base_st, net_st, why, "") }
# ... with what the reason is about (the room code nobody had)
function net__lose_with(why: int, text: string) -> void {
net_reset()
net__state = NET_LOST
net__why = why
function net__lose_with(base_st: mut BaseState, net_st: mut NetState, why: int, text: string) -> void {
net_reset(net_st)
net_st.net__state = NET_LOST
net_st.net__why = why
net__log(`lost ({why})`)
net__fact(NET_DOWN, -1, 0, why, text)
net__fact(base_st, net_st, NET_DOWN, -1, 0, why, text)
}
# a lost session read and done with: idle again
export function net_fresh() -> void { if net__state == NET_LOST { net__state = NET_IDLE } }
export function net_fresh(net_st: mut NetState) -> void { if net_st.net__state == NET_LOST { net_st.net__state = NET_IDLE } }