wip(0.S2): migrate - a var nothing writes becomes a let; a state is keyed by its module, directory or program, so every program's plan agrees; paths normalized; program states named SceneDemoState / scene_demo_st; the LSP reads state, mut and entry/handler parameters
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3eda72e8c2
commit
5ffe50ed02
463 changed files with 72045 additions and 67421 deletions
|
|
@ -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(net_st: mut NetState) -> void {
|
||||
net__init(net_st)
|
||||
net_st.net__wl = 0
|
||||
export function nw_begin() -> void {
|
||||
net__init()
|
||||
net__wl = 0
|
||||
}
|
||||
|
||||
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_i(v: int) -> void {
|
||||
if net__wl + 4 <= NET_MTU { nb_put32(net__wb, net__wl, v) }
|
||||
net__wl += 4
|
||||
}
|
||||
|
||||
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_b(v: bool) -> void { if v { nw_i(1) } else { nw_i(0) } }
|
||||
|
||||
export function nw_f(net_st: mut NetState, v: float) -> void { nw_i(net_st, float_bits(v)) }
|
||||
export function nw_f(v: float) -> void { nw_i(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(net_st: mut NetState, s: string) -> void { nw_text(net_st, s, 200) }
|
||||
export function nw_s(s: string) -> void { nw_text(s, 200) }
|
||||
|
||||
export function nw_text(net_st: mut NetState, s: string, cap: int) -> void {
|
||||
export function nw_text(s: string, cap: int) -> void {
|
||||
var n = len(s)
|
||||
if n > cap { n = cap }
|
||||
nw_i(net_st, n)
|
||||
nw_i(n)
|
||||
for i in 0 .. n {
|
||||
if net_st.net__wl < NET_MTU { net_st.net__wb[net_st.net__wl] = s[i] }
|
||||
net_st.net__wl += 1
|
||||
if net__wl < NET_MTU { net__wb[net__wl] = s[i] }
|
||||
net__wl += 1
|
||||
}
|
||||
}
|
||||
|
||||
export function nw_len(net_st: NetState) -> int { return net_st.net__wl }
|
||||
export function nw_len() -> int { return net__wl }
|
||||
|
||||
# the reader: a short or bad message reads as zeros and sets nr_bad()
|
||||
export function nr_i(net_st: mut NetState) -> int {
|
||||
if net_st.net__rp + 4 > net_st.net__rl {
|
||||
net_st.net__rbad = true
|
||||
export function nr_i() -> int {
|
||||
if net__rp + 4 > net__rl {
|
||||
net__rbad = true
|
||||
return 0
|
||||
}
|
||||
let v = nb_get32(net_st.net__rb, net_st.net__rp)
|
||||
net_st.net__rp += 4
|
||||
let v = nb_get32(net__rb, net__rp)
|
||||
net__rp += 4
|
||||
return v
|
||||
}
|
||||
|
||||
export function nr_b(net_st: mut NetState) -> bool { return nr_i(net_st) != 0 }
|
||||
export function nr_b() -> bool { return nr_i() != 0 }
|
||||
|
||||
export function nr_f(net_st: mut NetState) -> float { return float_from_bits(nr_i(net_st)) }
|
||||
export function nr_f() -> float { return float_from_bits(nr_i()) }
|
||||
|
||||
export function nr_s(net_st: mut NetState) -> string { return nr_text(net_st, 200) }
|
||||
export function nr_s() -> string { return nr_text(200) }
|
||||
|
||||
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
|
||||
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
|
||||
return ""
|
||||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
export function nr_bad(net_st: NetState) -> bool { return net_st.net__rbad }
|
||||
export function nr_bad() -> bool { return net__rbad }
|
||||
|
||||
# a message that came in, made the one nr_* reads
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
# the message just written, as if it had come in (a loopback: a test, a host telling itself)
|
||||
export function net_written(net_st: mut NetState) -> NetMessage {
|
||||
net__init(net_st)
|
||||
export function net_written() -> NetMessage {
|
||||
net__init()
|
||||
let m = new NetMessage
|
||||
m.from = net_st.net__my_pid
|
||||
m.len = min(net_st.net__wl, NET_MTU)
|
||||
m.from = net__my_pid
|
||||
m.len = min(net__wl, NET_MTU)
|
||||
m.data = buffer(m.len)
|
||||
nb_copy(m.data, 0, net_st.net__wb, 0, m.len)
|
||||
nb_copy(m.data, 0, 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(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
|
||||
export function net_copy_read() -> void {
|
||||
nw_begin()
|
||||
nb_copy(net__wb, 0, net__rb, 0, net__rl)
|
||||
net__wl = net__rl
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(net_st: NetState) -> string {
|
||||
export function net_join_code() -> string {
|
||||
var ip = NetSocket.local_ip()
|
||||
if ip == 0 { ip = Udp.ip("127.0.0.1") }
|
||||
return net_code_of(ip, net_port(net_st))
|
||||
return net_code_of(ip, net_port())
|
||||
}
|
||||
|
||||
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(net_st: mut NetState, text: string) -> bool {
|
||||
export function net_parse_code(text: string) -> bool {
|
||||
let p = text
|
||||
var colon = -1
|
||||
var dots = 0
|
||||
|
|
@ -53,7 +53,7 @@ export function net_parse_code(net_st: mut NetState, text: string) -> bool {
|
|||
if p[i] == ':' { colon = i }
|
||||
if p[i] == '.' { dots += 1 }
|
||||
}
|
||||
if dots == 3 { return net__parse_addr(net_st, text, colon) }
|
||||
if dots == 3 { return net__parse_addr(text, colon) }
|
||||
var hi = 0
|
||||
var lo = 0
|
||||
var n = 0
|
||||
|
|
@ -72,24 +72,24 @@ export function net_parse_code(net_st: mut NetState, text: string) -> bool {
|
|||
n += 1
|
||||
}
|
||||
if n != 10 { return false }
|
||||
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
|
||||
net__code_ip = lo
|
||||
net__code_port = hi
|
||||
return net__code_ip != 0 and net__code_port > 0
|
||||
}
|
||||
|
||||
function net__parse_addr(net_st: mut NetState, text: string, colon: int) -> bool {
|
||||
function net__parse_addr(text: string, colon: int) -> bool {
|
||||
var host = text
|
||||
net_st.net__code_port = net__conf(net_st).port
|
||||
net__code_port = net__conf().port
|
||||
if colon > 0 {
|
||||
host = text[0..colon]
|
||||
net_st.net__code_port = Text.to_int(text[colon + 1..len(text)])
|
||||
net__code_port = Text.to_int(text[colon + 1..len(text)])
|
||||
}
|
||||
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
|
||||
net__code_ip = Udp.ip(host)
|
||||
return net__code_ip != 0 and net__code_port > 0 and net__code_port < 65536
|
||||
}
|
||||
|
||||
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 }
|
||||
export function net_code_ip() -> int { return net__code_ip }
|
||||
export function net_code_port() -> int { return net__code_port }
|
||||
|
||||
# six of the code's letters: a room at the matchmaker
|
||||
export function net_is_room(text: string) -> bool {
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
# peers.ludic - who this machine talks to, and the written message queued for them
|
||||
function net__find(net_st: NetState, ip: int, port: int) -> int {
|
||||
function net__find(ip: int, port: int) -> int {
|
||||
for i in 0 .. NET_PEERS {
|
||||
let p = net_st.net__peers[i]
|
||||
let p = 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(net_st: NetState, pid: int) -> int {
|
||||
if net_st.net__peers == null { return -1 }
|
||||
export function np_of_pid(pid: int) -> int {
|
||||
if net__peers == null { return -1 }
|
||||
for i in 0 .. NET_PEERS {
|
||||
let p = net_st.net__peers[i]
|
||||
let p = net__peers[i]
|
||||
if p.on and p.pid == pid { return i }
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
function net__open(net_st: mut NetState, slot: int, ip: int, port: int, pid: int) -> void {
|
||||
let p = net_st.net__peers[slot]
|
||||
function net__open(slot: int, ip: int, port: int, pid: int) -> void {
|
||||
let p = net__peers[slot]
|
||||
p.on = true
|
||||
p.ip = ip
|
||||
p.port = port
|
||||
p.pid = pid
|
||||
p.heard = net_st.net__time
|
||||
p.heard = net__time
|
||||
p.next_id = 1
|
||||
p.recv_id = 0
|
||||
p.acked = 0
|
||||
|
|
@ -32,29 +32,29 @@ function net__open(net_st: mut NetState, slot: int, ip: int, port: int, pid: int
|
|||
p.closing = false
|
||||
}
|
||||
|
||||
export function np_close(net_st: mut 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]
|
||||
export function np_close(slot: int) -> void {
|
||||
if net__peers == null or slot < 0 or slot >= NET_PEERS { return }
|
||||
let p = 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(net_st: mut 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]
|
||||
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]
|
||||
if not p.on { return }
|
||||
let m = new NetMsg
|
||||
m.kind = kind
|
||||
m.rel = rel
|
||||
m.sent = -1.0
|
||||
m.made = net_st.net__time
|
||||
var n = net_st.net__wl
|
||||
m.made = net__time
|
||||
var n = net__wl
|
||||
if n > NET_MTU - 40 { n = NET_MTU - 40 }
|
||||
m.len = n
|
||||
m.data = buffer(n)
|
||||
nb_copy(m.data, 0, net_st.net__wb, 0, n)
|
||||
nb_copy(m.data, 0, net__wb, 0, n)
|
||||
if rel {
|
||||
m.id = p.next_id
|
||||
p.next_id += 1
|
||||
|
|
@ -63,48 +63,48 @@ export function np_queue(net_st: mut NetState, slot: int, kind: int, rel: bool)
|
|||
}
|
||||
|
||||
# to everyone this machine talks to (a host: every guest but `except`; a guest: the host)
|
||||
export function np_queue_all(net_st: mut 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 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 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_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_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_pid(slot: int) -> int {
|
||||
if not net_peer_on(slot) { return 0 }
|
||||
return net__peers[slot].pid
|
||||
}
|
||||
|
||||
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_ip(slot: int) -> int {
|
||||
if not net_peer_on(slot) { return 0 }
|
||||
return net__peers[slot].ip
|
||||
}
|
||||
|
||||
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_port(slot: int) -> int {
|
||||
if not net_peer_on(slot) { return 0 }
|
||||
return net__peers[slot].port
|
||||
}
|
||||
|
||||
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
|
||||
export function net_peer_heard(slot: int) -> float {
|
||||
if not net_peer_on(slot) { return 0.0 }
|
||||
return 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(net_st: mut NetState, slot: int, secs: float) -> void {
|
||||
if not net_peer_on(net_st, slot) { return }
|
||||
let p = net_st.net__peers[slot]
|
||||
export function net_close_when_sent(slot: int, secs: float) -> void {
|
||||
if not net_peer_on(slot) { return }
|
||||
let p = net__peers[slot]
|
||||
p.closing = true
|
||||
p.close_id = p.next_id - 1
|
||||
p.close_t = net_st.net__time + secs
|
||||
p.close_t = net__time + secs
|
||||
}
|
||||
|
||||
# the slots in use (a host's count of guests, a welcome not yet sent included)
|
||||
export function net_peer_count(net_st: NetState) -> int {
|
||||
export function net_peer_count() -> int {
|
||||
var n = 0
|
||||
for i in 0 .. NET_PEERS { if net_peer_on(net_st, i) { n += 1 } }
|
||||
for i in 0 .. NET_PEERS { if net_peer_on(i) { n += 1 } }
|
||||
return n
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,27 +58,29 @@ 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(net_st: mut NetState, c: NetConfig) -> void { net_st.net__cfg = c }
|
||||
export function net_config(c: NetConfig) -> void { net__cfg = c }
|
||||
|
||||
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
|
||||
function net__conf() -> NetConfig {
|
||||
if net__cfg == null { net__cfg = new NetConfig }
|
||||
return net__cfg
|
||||
}
|
||||
|
||||
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
|
||||
export function net_facts() -> Queue<NetFact> {
|
||||
if net__facts == null { net__facts = queue_new("net.facts") }
|
||||
return net__facts
|
||||
}
|
||||
|
||||
function net__fact(base_st: mut BaseState, net_st: mut NetState, what: int, slot: int, pid: int, why: int, text: string) -> void {
|
||||
function net__fact(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(base_st, net_facts(base_st, net_st), f)
|
||||
q_push(net_facts(), f)
|
||||
}
|
||||
|
||||
function net__no_log(s: string) -> void { }
|
||||
|
|
|
|||
|
|
@ -1,51 +1,57 @@
|
|||
# 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(net_st: mut NetState, pub: string, local: string) -> void {
|
||||
function net__new_guest(pub: string, local: string) -> void {
|
||||
let key = `{pub}|{local}`
|
||||
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) }
|
||||
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) }
|
||||
}
|
||||
|
||||
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 }
|
||||
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 }
|
||||
}
|
||||
for k in 0 .. 16 {
|
||||
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
|
||||
if net__punch_left[k] > 0 { continue }
|
||||
net__punch_ip[k] = ip
|
||||
net__punch_port[k] = port
|
||||
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(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')
|
||||
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')
|
||||
}
|
||||
for k in 0 .. 16 {
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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 } }
|
||||
function net__punch_clear() -> void {
|
||||
if net__punch_left != null { for k in 0 .. 16 { net__punch_left[k] = 0 } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
# queries.ludic - what the game asks of the session
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(base_st: mut BaseState, net_st: mut NetState) -> void {
|
||||
if net_st.net__sock <= 0 { return }
|
||||
function net__pump() -> void {
|
||||
if net__sock <= 0 { return }
|
||||
for guard in 0 .. 200 {
|
||||
let n = NetSocket.recv(net_st.net__sock, net_st.net__in, NET_MTU + 64)
|
||||
let n = NetSocket.recv(net__sock, net__in, NET_MTU + 64)
|
||||
if n <= 0 { return }
|
||||
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) }
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
function net__packet(base_st: mut BaseState, net_st: mut NetState, n: int, ip: int, port: int) -> void {
|
||||
function net__packet(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(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 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 slot < 0 { return }
|
||||
let p = net_st.net__peers[slot]
|
||||
p.heard = net_st.net__time
|
||||
let p = net__peers[slot]
|
||||
p.heard = 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_st.net__in, 12)
|
||||
let ack = nb_get32(net__in, 12)
|
||||
if ack > p.acked and ack < p.next_id { p.acked = ack }
|
||||
net__messages(base_st, net_st, slot, n)
|
||||
net__messages(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(net_st: mut 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)
|
||||
function net__slot_for(n: int, ip: int, port: int) -> int {
|
||||
let token = nb_get32(net__in, 4)
|
||||
var slot = net__find(ip, port)
|
||||
if slot >= 0 {
|
||||
if token != net_st.net__token and net_st.net__state != NET_JOINING and net_st.net__peers[slot].pid != 0 { return -1 }
|
||||
if token != net__token and net__state != NET_JOINING and net__peers[slot].pid != 0 { return -1 }
|
||||
return slot
|
||||
}
|
||||
if net_st.net__state != NET_HOSTING { return -1 }
|
||||
if n < 28 or nb_get8(net_st.net__in, 20) != NET_HELLO { return -1 }
|
||||
if net__state != NET_HOSTING { return -1 }
|
||||
if n < 28 or nb_get8(net__in, 20) != NET_HELLO { return -1 }
|
||||
for i in 1 .. NET_PEERS {
|
||||
if not net_st.net__peers[i].on {
|
||||
net__open(net_st, i, ip, port, 0)
|
||||
if not net__peers[i].on {
|
||||
net__open(i, ip, port, 0)
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
var o = 20
|
||||
for c in 0 .. count {
|
||||
if o + 4 > n { return }
|
||||
let kind = nb_get8(net_st.net__in, o)
|
||||
let rel = nb_get8(net_st.net__in, o + 1) == 1
|
||||
let kind = nb_get8(net__in, o)
|
||||
let rel = nb_get8(net__in, o + 1) == 1
|
||||
o += 2
|
||||
var id = 0
|
||||
if rel {
|
||||
if o + 4 > n { return }
|
||||
id = nb_get32(net_st.net__in, o)
|
||||
id = nb_get32(net__in, o)
|
||||
o += 4
|
||||
}
|
||||
let ml = nb_get8(net_st.net__in, o) | (nb_get8(net_st.net__in, o + 1) << 8)
|
||||
let ml = nb_get8(net__in, o) | (nb_get8(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(base_st: mut BaseState, net_st: mut NetState, slot: int,
|
|||
p.ack_due = true
|
||||
if id != p.recv_id + 1 { take = false } else { p.recv_id = id }
|
||||
}
|
||||
if take { net__deliver(base_st, net_st, slot, from, kind, o, ml) }
|
||||
if take { net__deliver(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(base_st: mut BaseState, net_st: mut NetState, slot: int, from: int, kind: int, o: int, ml: int) -> void {
|
||||
function net__deliver(slot: int, from: int, kind: int, o: int, ml: int) -> void {
|
||||
if kind == NET_BEAT { return }
|
||||
if net_st.net__state == NET_HOSTING and net_st.net__peers[slot].pid < 2 and kind != NET_HELLO { return }
|
||||
if net__state == NET_HOSTING and 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_st.net__in, o, ml)
|
||||
q_push(base_st, net_inbox(base_st, net_st), m)
|
||||
nb_copy(m.data, 0, net__in, o, ml)
|
||||
q_push(net_inbox(), m)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,22 @@
|
|||
# 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(net_st: mut NetState, v: Val) -> void {
|
||||
function net__take_relay(v: Val) -> void {
|
||||
let r = net__json_str(v, "relay")
|
||||
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
|
||||
if len(r) > 0 and net_parse_code(r) {
|
||||
net__relay_ip = net__code_ip
|
||||
net__relay_port = net__code_port
|
||||
}
|
||||
}
|
||||
|
||||
function net__code_bytes(net_st: NetState, b: []byte, o: int) -> void {
|
||||
let cp = net_st.net__room
|
||||
function net__code_bytes(b: []byte, o: int) -> void {
|
||||
let cp = net__room
|
||||
for k in 0 .. 6 {
|
||||
var c = 0
|
||||
if k < len(cp) { c = cp[k] }
|
||||
|
|
@ -20,65 +25,65 @@ function net__code_bytes(net_st: NetState, b: []byte, o: int) -> void {
|
|||
}
|
||||
}
|
||||
|
||||
function net__relay_hello(net_st: NetState) -> void {
|
||||
function net__relay_hello() -> 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(net_st, b, 4)
|
||||
NetSocket.send(net_st.net__sock, net_st.net__relay_ip, net_st.net__relay_port, b, 10)
|
||||
net__code_bytes(b, 4)
|
||||
NetSocket.send(net__sock, net__relay_ip, net__relay_port, b, 10)
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
export function net_via_relay(net_st: NetState) -> bool { return net_st.net__via_relay }
|
||||
export function net_via_relay() -> bool { return 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)
|
||||
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)
|
||||
}
|
||||
|
||||
# a packet of `o` bytes in net__out, to peer p: straight, or wrapped for the relay
|
||||
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) }
|
||||
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) }
|
||||
var hdr = 10
|
||||
if not net_st.net__via_relay { hdr = 6 }
|
||||
if not net__via_relay { hdr = 6 }
|
||||
var k = o - 1
|
||||
while k >= 0 {
|
||||
net_st.net__out[k + hdr] = net_st.net__out[k]
|
||||
net__out[k + hdr] = net__out[k]
|
||||
k -= 1
|
||||
}
|
||||
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)
|
||||
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)
|
||||
} else {
|
||||
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 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 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
|
||||
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
|
||||
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(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)
|
||||
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)
|
||||
# the host: this guest's session; a guest: the relay itself is its host
|
||||
if sid > 0 { net_st.net__from_port = net__conf(net_st).relay_port0 + sid }
|
||||
if sid > 0 { net__from_port = net__conf().relay_port0 + sid }
|
||||
return n - 6
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,54 +8,65 @@ 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(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 }
|
||||
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 }
|
||||
|
||||
# the host (after net_host or net_become_host): a room at the matchmaker, if there is one
|
||||
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)
|
||||
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()
|
||||
}
|
||||
|
||||
# a guest by room code: the matchmaker's answer brings the host's address (the hello is set first)
|
||||
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
|
||||
export function net_room_join(code: string) -> bool {
|
||||
if not net_room_on() {
|
||||
net__why = NET_WHY_NO_MATCHMAKER
|
||||
return false
|
||||
}
|
||||
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)
|
||||
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()
|
||||
return true
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
return
|
||||
}
|
||||
net__stun_send(net_st)
|
||||
net__stun_send()
|
||||
}
|
||||
|
||||
function net__room_body(net_st: NetState, role_key: string) -> string {
|
||||
function net__room_body(role_key: string) -> string {
|
||||
var pub = ""
|
||||
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) + "\"}"
|
||||
if net__pub_ip != 0 { pub = net__addr_text(net__pub_ip, net__pub_port) }
|
||||
return "{\"" + role_key + "\": \"" + pub + "\", \"local\": \"" + net__local_text() + "\"}"
|
||||
}
|
||||
|
||||
function net__json_str(v: Val, key: string) -> string {
|
||||
|
|
@ -65,8 +76,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(net_st: NetState) -> string {
|
||||
function net__local_text() -> string {
|
||||
var ip = NetSocket.local_ip()
|
||||
if ip == 0 { ip = Udp.ip("127.0.0.1") }
|
||||
return net__addr_text(ip, net_port(net_st))
|
||||
return net__addr_text(ip, net_port())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(base_st: mut BaseState, net_st: mut NetState, why: int) -> void {
|
||||
function net__room_fail(why: int) -> void {
|
||||
net__log(`matchmaker: {why}`)
|
||||
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) }
|
||||
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) }
|
||||
}
|
||||
|
||||
# the guest: to the host's outside address, or its inside one behind the same router
|
||||
function net__room_connect(base_st: mut BaseState, net_st: mut NetState, host_pub: string, host_local: string) -> void {
|
||||
function net__room_connect(host_pub: string, host_local: string) -> void {
|
||||
var target = host_pub
|
||||
var same_router = false
|
||||
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 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 or same_router { target = host_local }
|
||||
if not net_parse_code(net_st, target) {
|
||||
net__room_fail(base_st, net_st, NET_WHY_BAD_ADDRESS)
|
||||
if not net_parse_code(target) {
|
||||
net__room_fail(NET_WHY_BAD_ADDRESS)
|
||||
return
|
||||
}
|
||||
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)
|
||||
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()
|
||||
return
|
||||
}
|
||||
net__log(`room {net_st.net__room}: dialling {target}`)
|
||||
net__log(`room {net__room}: dialling {target}`)
|
||||
}
|
||||
|
||||
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
|
||||
function net__room_reset() -> void {
|
||||
if net__http != 0 {
|
||||
Http.free(net__http)
|
||||
net__http = 0
|
||||
}
|
||||
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)
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(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)
|
||||
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()
|
||||
return
|
||||
}
|
||||
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)
|
||||
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()
|
||||
return
|
||||
}
|
||||
if net_st.net__r_state == NET_R_DONE {
|
||||
if 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_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) }
|
||||
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() }
|
||||
return
|
||||
}
|
||||
if net_st.net__r_state == NET_R_ROOM { net__room_watch(net_st, dt) }
|
||||
if net__r_state == NET_R_ROOM { net__room_watch(dt) }
|
||||
}
|
||||
|
||||
# a second and a half for an answer, asked three times; without one, inside addresses only
|
||||
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)
|
||||
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()
|
||||
return
|
||||
}
|
||||
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
|
||||
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
|
||||
} else {
|
||||
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__http = Http.post(`{base}/rooms/{net__room}/join`, net__room_body("pub"))
|
||||
net__r_state = NET_R_ASK
|
||||
}
|
||||
net_st.net__r_t = 0.0
|
||||
net__r_t = 0.0
|
||||
}
|
||||
|
||||
function net__room_answer(base_st: mut BaseState, net_st: mut NetState) -> void {
|
||||
let st = Http.poll(net_st.net__http)
|
||||
function net__room_answer() -> void {
|
||||
let st = Http.poll(net__http)
|
||||
if st < 0 {
|
||||
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)
|
||||
if net__r_t > 12.0 {
|
||||
Http.free(net__http)
|
||||
net__http = 0
|
||||
net__room_fail(NET_WHY_MATCHMAKER_SILENT)
|
||||
}
|
||||
return
|
||||
}
|
||||
var v: Val = null
|
||||
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 {
|
||||
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 {
|
||||
let code = net__json_str(v, "code")
|
||||
if len(code) == 0 {
|
||||
net__room_fail(base_st, net_st, NET_WHY_NO_ROOM_GIVEN)
|
||||
net__room_fail(NET_WHY_NO_ROOM_GIVEN)
|
||||
return
|
||||
}
|
||||
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__room = code
|
||||
net__take_relay(v)
|
||||
net__r_state = NET_R_ROOM
|
||||
net__poll_t = 0.0
|
||||
net__log(`room {code}`)
|
||||
net__fact(base_st, net_st, NET_ROOM, -1, 0, 0, code)
|
||||
net__fact(NET_ROOM, -1, 0, 0, code)
|
||||
return
|
||||
}
|
||||
if net_st.net__r_state == NET_R_ASK {
|
||||
if net__r_state == NET_R_ASK {
|
||||
if v == null {
|
||||
net__room_fail(base_st, net_st, NET_WHY_NO_ROOM)
|
||||
net__room_fail(NET_WHY_NO_ROOM)
|
||||
return
|
||||
}
|
||||
net__take_relay(net_st, v)
|
||||
net__room_connect(base_st, net_st, net__json_str(v, "host"), net__json_str(v, "local"))
|
||||
net__take_relay(v)
|
||||
net__room_connect(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(base_st: mut BaseState, net_st: mut NetState) -> void
|
|||
let l = value_get(v, "guests")
|
||||
for i in 0 .. value_count(l) {
|
||||
let g = value_at(l, i)
|
||||
net__new_guest(net_st, net__json_str(g, "pub"), net__json_str(g, "local"))
|
||||
net__new_guest(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(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)
|
||||
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()
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(net_st: mut NetState, slot: int) -> void {
|
||||
if not net_peer_on(net_st, slot) { return }
|
||||
let p = net_st.net__peers[slot]
|
||||
export function np_flush(slot: int) -> void {
|
||||
if not net_peer_on(slot) { return }
|
||||
let p = net__peers[slot]
|
||||
for pk in 0 .. 8 {
|
||||
if not net__flush_one(net_st, p) { return }
|
||||
if not net__flush_one(p) { return }
|
||||
}
|
||||
}
|
||||
|
||||
function net__flush_one(net_st: mut NetState, p: NetPeer) -> bool {
|
||||
function net__flush_one(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_st.net__time - m.made > 0.5 { continue } # stale
|
||||
if not m.rel and net__time - m.made > 0.5 { continue } # stale
|
||||
var due = true
|
||||
if m.rel and not (m.sent < 0.0) and net_st.net__time - m.sent < 0.25 { due = false }
|
||||
if m.rel and not (m.sent < 0.0) and 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(net_st, o, m)
|
||||
o = net__put_msg(o, m)
|
||||
count += 1
|
||||
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 }
|
||||
m.sent = net__time
|
||||
if net__kind_out != null and m.kind < 64 { 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(net_st: mut NetState, p: NetPeer) -> bool {
|
|||
}
|
||||
p.queue = keep
|
||||
if count == 0 and not p.ack_due { return false }
|
||||
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
|
||||
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
|
||||
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(net_st: NetState, at: int, m: NetMsg) -> int {
|
||||
function net__put_msg(at: int, m: NetMsg) -> int {
|
||||
var o = at
|
||||
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) }
|
||||
nb_put8(net__out, o, m.kind)
|
||||
nb_put8(net__out, o + 1, 0)
|
||||
if m.rel { nb_put8(net__out, o + 1, 1) }
|
||||
o += 2
|
||||
if m.rel {
|
||||
nb_put32(net_st.net__out, o, m.id)
|
||||
nb_put32(net__out, o, m.id)
|
||||
o += 4
|
||||
}
|
||||
nb_put8(net_st.net__out, o, m.len & 255)
|
||||
nb_put8(net_st.net__out, o + 1, (m.len >> 8) & 255)
|
||||
nb_put8(net__out, o, m.len & 255)
|
||||
nb_put8(net__out, o + 1, (m.len >> 8) & 255)
|
||||
o += 2
|
||||
nb_copy(net_st.net__out, o, m.data, 0, m.len)
|
||||
nb_copy(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(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_out_clear() -> void {
|
||||
net__kind_out = words(64)
|
||||
for k in 0 .. 64 { net__kind_out[k] = 0 }
|
||||
}
|
||||
|
||||
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_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_bytes_out(net_st: NetState) -> int { return net_st.net__bytes_out }
|
||||
export function net_bytes_out() -> int { return net__bytes_out }
|
||||
|
|
|
|||
|
|
@ -1,105 +1,106 @@
|
|||
# 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(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
|
||||
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
|
||||
return false
|
||||
}
|
||||
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)
|
||||
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()
|
||||
return true
|
||||
}
|
||||
|
||||
# dial a host at an address; the hello (net_hello_set) goes until a welcome or a refusal
|
||||
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)
|
||||
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)
|
||||
net__log(`joining {Udp.ip_text(ip)}:{port}`)
|
||||
return true
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
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(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 }
|
||||
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 }
|
||||
}
|
||||
|
||||
# (again) to that address, with a fresh connection and the hello sent anew
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
# 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(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
|
||||
export function net_follow(ip: int, port: int) -> void {
|
||||
net_dial(ip, port)
|
||||
net__token = 0
|
||||
net__my_pid = 0
|
||||
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(net_st: mut NetState) -> void { net_st.net__state = NET_IDLE }
|
||||
export function net_stand_alone() -> void { net__state = NET_IDLE }
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
# a welcome: this machine is `pid` in the session `token`
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
# the host lets the stranger in `slot` in: its pid, from 2 upwards
|
||||
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]
|
||||
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]
|
||||
if p.pid >= 2 { return p.pid }
|
||||
p.pid = net_st.net__next_pid
|
||||
net_st.net__next_pid += 1
|
||||
p.pid = net__next_pid
|
||||
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(net_st: mut NetState, secs: int) -> void { net_st.net__wait_more = secs }
|
||||
export function net_hello_wait(secs: int) -> void { net__wait_more = secs }
|
||||
|
||||
# a room code: joining, with no address yet - the matchmaker's answer brings one
|
||||
function net__wait_for_host(net_st: mut NetState) -> void {
|
||||
net_st.net__state = NET_JOINING
|
||||
net_st.net__hello_t = 0.0
|
||||
function net__wait_for_host() -> void {
|
||||
net__state = NET_JOINING
|
||||
net__hello_t = 0.0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,82 +47,51 @@ export property NetMessage {
|
|||
data: []byte
|
||||
}
|
||||
|
||||
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
|
||||
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 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
|
||||
function net__init() -> void {
|
||||
if net__peers != null { return }
|
||||
net__peers = new []NetPeer
|
||||
for i in 0 .. NET_PEERS {
|
||||
let p = new NetPeer
|
||||
p.queue = new []NetMsg
|
||||
push(net_st.net__peers, p)
|
||||
push(net__peers, p)
|
||||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
# 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(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
|
||||
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
|
||||
} else if len(c.stun_host) > 0 {
|
||||
let host: pointer = c.stun_host
|
||||
net_st.net__stun_ip = Udp.resolve(host)
|
||||
net_st.net__stun_port = c.stun_port
|
||||
net__stun_ip = Udp.resolve(host)
|
||||
net__stun_port = c.stun_port
|
||||
}
|
||||
if net_st.net__stun_ip == 0 { return }
|
||||
if net__stun_ip == 0 { return }
|
||||
}
|
||||
let b = buffer(20)
|
||||
nb_put8(b, 0, 0) # a binding request, no attributes
|
||||
|
|
@ -25,9 +29,9 @@ function net__stun_send(net_st: mut NetState) -> void {
|
|||
nb_put8(b, 5, 0x12)
|
||||
nb_put8(b, 6, 0xA4)
|
||||
nb_put8(b, 7, 0x42)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
function net__be16(p: []byte, o: int) -> int { return (nb_get8(p, o) << 8) | nb_get8(p, o + 1) }
|
||||
|
|
@ -35,23 +39,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(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)
|
||||
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)
|
||||
var o = 20
|
||||
while o + 4 <= 20 + len and o + 4 <= n {
|
||||
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)
|
||||
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)
|
||||
if t == 0x0020 {
|
||||
port = port ^ 0x2112
|
||||
ip = ip ^ 0x2112A442
|
||||
}
|
||||
net_st.net__pub_ip = ip
|
||||
net_st.net__pub_port = port
|
||||
net__pub_ip = ip
|
||||
net__pub_port = port
|
||||
net__log(`outside we are {net__addr_text(ip, port)}`)
|
||||
}
|
||||
o += 4 + ((l + 3) / 4) * 4
|
||||
|
|
@ -60,5 +64,5 @@ function net__stun_packet(net_st: mut NetState, n: int) -> bool {
|
|||
}
|
||||
|
||||
# what this machine looks like from outside, once STUN has said (0 until then)
|
||||
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 }
|
||||
export function net_outside_ip() -> int { return net__pub_ip }
|
||||
export function net_outside_port() -> int { return net__pub_port }
|
||||
|
|
|
|||
|
|
@ -5,52 +5,50 @@ import "ludic.base"
|
|||
program NetTest {
|
||||
numbers float
|
||||
property Pkt { ip: int = 0, port: int = 0, len: int = 0, data: []byte }
|
||||
state NettestState {
|
||||
sent: []Pkt = null
|
||||
inbound: []Pkt = null
|
||||
from_ip: int = 0
|
||||
from_port: int = 0
|
||||
}
|
||||
var sent: []Pkt = null
|
||||
var inbound: []Pkt = null
|
||||
var from_ip: int = 0
|
||||
var 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(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 {
|
||||
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 {
|
||||
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(nettest_st.sent, p)
|
||||
push(sent, p)
|
||||
return n
|
||||
}
|
||||
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]
|
||||
function fake_recv(h: int, b: []byte, cap: int) -> int {
|
||||
if len(inbound) == 0 { return 0 }
|
||||
let p = inbound[0]
|
||||
let rest = new []Pkt
|
||||
for i in 1 .. len(nettest_st.inbound) { push(rest, nettest_st.inbound[i]) }
|
||||
nettest_st.inbound = rest
|
||||
for i in 1 .. len(inbound) { push(rest, inbound[i]) }
|
||||
inbound = rest
|
||||
nb_copy(b, 0, p.data, 0, p.len)
|
||||
nettest_st.from_ip = p.ip
|
||||
nettest_st.from_port = p.port
|
||||
from_ip = p.ip
|
||||
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(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))
|
||||
function fresh() -> void {
|
||||
sent = new []Pkt
|
||||
inbound = new []Pkt
|
||||
net_reset()
|
||||
q_clear(net_inbox())
|
||||
q_clear(net_facts())
|
||||
}
|
||||
|
||||
# a packet from ip:port: token, pid, ack, then one message of `kind` (reliable with `id` > 0)
|
||||
function arrive(nettest_st: mut NettestState, ip: int, port: int, token: int, pid: int, ack: int, kind: int, id: int, payload: int) -> void {
|
||||
function arrive(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')
|
||||
|
|
@ -77,201 +75,201 @@ program NetTest {
|
|||
p.port = port
|
||||
p.len = o + 6
|
||||
p.data = b
|
||||
push(nettest_st.inbound, p)
|
||||
push(inbound, p)
|
||||
}
|
||||
|
||||
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)
|
||||
function frame(dt: float) -> void {
|
||||
net_begin(dt)
|
||||
net_update(dt)
|
||||
net_end()
|
||||
}
|
||||
|
||||
# the host with a guest let in at 10.0.0.9:5000 as pid 2
|
||||
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))
|
||||
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())
|
||||
expect_eq(len(got), 1)
|
||||
return net_accept(net_st, got[0].slot)
|
||||
return net_accept(got[0].slot)
|
||||
}
|
||||
|
||||
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))
|
||||
function facts_of(what: int) -> []NetFact {
|
||||
let all = q_drain(net_facts())
|
||||
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" (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 "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 "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)
|
||||
test "a join code decodes to its address and port; an address and a room code are taken too" {
|
||||
fresh()
|
||||
let ip = Udp.ip("192.168.1.20")
|
||||
let code = net_code_of(ip, 27415)
|
||||
expect_eq(len(code), 11)
|
||||
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_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_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" (base_st: mut BaseState, net_st: mut NetState, nettest_st: mut NettestState) {
|
||||
let pid = host_with_guest(base_st, net_st, nettest_st)
|
||||
test "a host takes a stranger only with a hello, and hands out pids from 2" {
|
||||
let pid = host_with_guest()
|
||||
expect_eq(pid, 2)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
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))
|
||||
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())
|
||||
expect_eq(len(got), 1)
|
||||
expect_eq(got[0].kind, 9)
|
||||
expect_eq(got[0].from, 2)
|
||||
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))
|
||||
net_read(got[0])
|
||||
expect_eq(nr_i(), 1234)
|
||||
expect(not nr_bad())
|
||||
nr_i()
|
||||
expect(nr_bad())
|
||||
}
|
||||
|
||||
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))
|
||||
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())
|
||||
expect_eq(len(got), 2)
|
||||
net_read(net_st, got[1])
|
||||
expect_eq(nr_i(net_st), 30)
|
||||
net_read(got[1])
|
||||
expect_eq(nr_i(), 30)
|
||||
expect_eq(got[1].slot, slot)
|
||||
}
|
||||
|
||||
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 "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 "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 "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 "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)
|
||||
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)
|
||||
expect_eq(len(down), 1)
|
||||
expect_eq(down[0].why, NET_WHY_NO_ANSWER)
|
||||
net_fresh(net_st)
|
||||
expect_eq(net_state(net_st), NET_IDLE)
|
||||
net_fresh()
|
||||
expect_eq(net_state(), NET_IDLE)
|
||||
}
|
||||
|
||||
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 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 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)
|
||||
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)
|
||||
expect_eq(len(gone), 1)
|
||||
expect_eq(gone[0].pid, pid)
|
||||
expect_eq(gone[0].slot, slot)
|
||||
expect(not net_peer_on(net_st, slot))
|
||||
expect(not net_peer_on(slot))
|
||||
}
|
||||
|
||||
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 "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 "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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(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_begin(dt: float) -> void {
|
||||
if not net_active() { return }
|
||||
net__time = net__time + dt
|
||||
net__pump()
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
return
|
||||
}
|
||||
for i in 0 .. NET_PEERS {
|
||||
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)
|
||||
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)
|
||||
return
|
||||
}
|
||||
let gone = p.pid
|
||||
np_close(net_st, i)
|
||||
net__fact(base_st, net_st, NET_GONE, i, gone, 0, "")
|
||||
np_close(i)
|
||||
net__fact(NET_GONE, i, gone, 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
|
||||
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
|
||||
}
|
||||
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)
|
||||
net__hello_t = net__hello_t + dt
|
||||
if net__hello_t > float(NET_TIMEOUT + net__wait_more) {
|
||||
net_lose(NET_WHY_NO_ANSWER)
|
||||
return
|
||||
}
|
||||
np_flush(net_st, 0)
|
||||
np_flush(0)
|
||||
}
|
||||
|
||||
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 }
|
||||
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 }
|
||||
for i in 0 .. NET_PEERS {
|
||||
let p = net_st.net__peers[i]
|
||||
let p = net__peers[i]
|
||||
if not p.on { continue }
|
||||
if beat {
|
||||
nw_begin(net_st)
|
||||
np_queue(net_st, i, NET_BEAT, false)
|
||||
nw_begin()
|
||||
np_queue(i, NET_BEAT, false)
|
||||
}
|
||||
np_flush(net_st, i)
|
||||
if p.closing and (p.acked >= p.close_id or net_st.net__time > p.close_t) {
|
||||
np_flush(i)
|
||||
if p.closing and (p.acked >= p.close_id or net__time > p.close_t) {
|
||||
let pid = p.pid
|
||||
np_close(net_st, i)
|
||||
net__fact(base_st, net_st, NET_CLOSED, i, pid, 0, "")
|
||||
np_close(i)
|
||||
net__fact(NET_CLOSED, i, pid, 0, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# while a load holds the frame: keep answering, so nobody times out on this machine
|
||||
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)
|
||||
export function net_keepalive() -> void {
|
||||
if not net_live() { return }
|
||||
net__pump()
|
||||
for i in 0 .. NET_PEERS {
|
||||
let p = net_st.net__peers[i]
|
||||
let p = net__peers[i]
|
||||
if p.on {
|
||||
p.heard = net_st.net__time
|
||||
nw_begin(net_st)
|
||||
np_queue(net_st, i, NET_BEAT, false)
|
||||
np_flush(net_st, i)
|
||||
p.heard = net__time
|
||||
nw_begin()
|
||||
np_queue(i, NET_BEAT, false)
|
||||
np_flush(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# every queue to its peer now, three times over (a machine about to let go)
|
||||
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) } }
|
||||
export function net_flush_all() -> void {
|
||||
for t in 0 .. 3 { for i in 0 .. NET_PEERS { np_flush(i) } }
|
||||
}
|
||||
|
||||
# the session is over for this machine: every slot, the socket and the matchmaker let go
|
||||
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
|
||||
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
|
||||
}
|
||||
net_st.net__state = NET_IDLE
|
||||
net_st.net__my_pid = 0
|
||||
net_st.net__wait_more = 0
|
||||
net__room_reset(net_st)
|
||||
net__state = NET_IDLE
|
||||
net__my_pid = 0
|
||||
net__wait_more = 0
|
||||
net__room_reset()
|
||||
}
|
||||
|
||||
# out of the session, and why: the game hears NET_DOWN and says so in its own words
|
||||
export function net_lose(base_st: mut BaseState, net_st: mut NetState, why: int) -> void { net__lose_with(base_st, net_st, why, "") }
|
||||
export function net_lose(why: int) -> void { net__lose_with(why, "") }
|
||||
|
||||
# ... with what the reason is about (the room code nobody had)
|
||||
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
|
||||
function net__lose_with(why: int, text: string) -> void {
|
||||
net_reset()
|
||||
net__state = NET_LOST
|
||||
net__why = why
|
||||
net__log(`lost ({why})`)
|
||||
net__fact(base_st, net_st, NET_DOWN, -1, 0, why, text)
|
||||
net__fact(NET_DOWN, -1, 0, why, text)
|
||||
}
|
||||
|
||||
# a lost session read and done with: idle again
|
||||
export function net_fresh(net_st: mut NetState) -> void { if net_st.net__state == NET_LOST { net_st.net__state = NET_IDLE } }
|
||||
export function net_fresh() -> void { if net__state == NET_LOST { net__state = NET_IDLE } }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue