ludic.shop, ludic.net: nothing made per tick - the week's lists and dice reused, the net's facts from a ring, the room's watch address made once, pools and kept lists through kept_push; what runs once per host, join, room or guest says so in @alloc_ok
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f5834f8f63
commit
4b533b5ff6
18 changed files with 86 additions and 23 deletions
|
|
@ -3,6 +3,7 @@
|
|||
# six of the same letters.
|
||||
const NET_ALPHA: string = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||||
|
||||
@alloc_ok("once per join code shown")
|
||||
export function net_code_of(ip: int, port: int) -> string {
|
||||
let a = NET_ALPHA
|
||||
let buf = buffer(11)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export function np_queue(net_st: mut NetState, slot: int, kind: int, rel: bool)
|
|||
m.id = p.next_id
|
||||
p.next_id += 1
|
||||
}
|
||||
push(p.queue, m)
|
||||
kept_push(p.queue, m)
|
||||
}
|
||||
|
||||
# to everyone this machine talks to (a host: every guest but `except`; a guest: the host)
|
||||
|
|
|
|||
|
|
@ -3,14 +3,12 @@
|
|||
# taken or goes stale; an inbox one comes back two drains after it was handed out
|
||||
function net__msg_take(net_st: mut NetState) -> NetMsg {
|
||||
if len(net_st.net__free) > 0 { return List.pop(net_st.net__free) }
|
||||
let m = new NetMsg
|
||||
m.data = buffer(NET_MTU)
|
||||
return m
|
||||
return net__msg_new()
|
||||
}
|
||||
|
||||
# everything a peer had queued, back to the pool (its slot opened again, or let go)
|
||||
function net__queue_free(net_st: mut NetState, p: NetPeer) -> void {
|
||||
for i in 0 .. len(p.queue) { push(net_st.net__free, p.queue[i]) }
|
||||
for i in 0 .. len(p.queue) { kept_push(net_st.net__free, p.queue[i]) }
|
||||
List.clear(p.queue)
|
||||
}
|
||||
|
||||
|
|
@ -24,9 +22,7 @@ function net__in_take(net_st: mut NetState) -> NetMessage {
|
|||
var pool = net_st.net__ina
|
||||
if net_st.net__in_on_b { pool = net_st.net__inb }
|
||||
if net_st.net__inn >= len(pool) {
|
||||
let m = new NetMessage
|
||||
m.data = buffer(NET_MTU)
|
||||
push(pool, m)
|
||||
net__in_grow(pool)
|
||||
}
|
||||
let m = pool[net_st.net__inn]
|
||||
net_st.net__inn += 1
|
||||
|
|
@ -34,3 +30,31 @@ function net__in_take(net_st: mut NetState) -> NetMessage {
|
|||
}
|
||||
|
||||
export function net_spare_count(net_st: NetState) -> int { return len(net_st.net__free) }
|
||||
|
||||
@alloc_ok("a pool miss: past the most messages in flight at once only, then kept")
|
||||
function net__msg_new() -> NetMsg {
|
||||
let m = new NetMsg
|
||||
m.data = buffer(NET_MTU)
|
||||
return m
|
||||
}
|
||||
|
||||
@alloc_ok("a pool miss: past the most messages in one drain only, then kept")
|
||||
function net__in_grow(pool: []NetMessage) -> void {
|
||||
let m = new NetMessage
|
||||
m.data = buffer(NET_MTU)
|
||||
push(pool, m)
|
||||
}
|
||||
|
||||
# a fact's record, from a ring made with the state: far more than a frame reports between drains
|
||||
const NET_FACTS_MOST: int = 64
|
||||
function net__fpool__new() -> []NetFact {
|
||||
let xs = new []NetFact
|
||||
for i in 0 .. NET_FACTS_MOST { push(xs, new NetFact) }
|
||||
return xs
|
||||
}
|
||||
|
||||
function net__fact_rec(net_st: mut NetState) -> NetFact {
|
||||
let f = net_st.net__fpool[net_st.net__fat]
|
||||
net_st.net__fat = (net_st.net__fat + 1) % len(net_st.net__fpool)
|
||||
return f
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ export function net_facts(net_st: NetState) -> Queue<NetFact> {
|
|||
return net_st.net__facts
|
||||
}
|
||||
|
||||
function net__fact(net_st: NetState, what: int, slot: int, pid: int, why: int, text: string) -> void {
|
||||
let f = new NetFact
|
||||
function net__fact(net_st: mut NetState, what: int, slot: int, pid: int, why: int, text: string) -> void {
|
||||
let f = net__fact_rec(net_st)
|
||||
f.what = what
|
||||
f.slot = slot
|
||||
f.pid = pid
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# expects the guest, while the guest's hello goes the other way: both routers have seen traffic
|
||||
# go out, so the replies come in
|
||||
|
||||
@alloc_ok("once per guest punched for")
|
||||
function net__new_guest(net_st: mut NetState, pub: string, local: string) -> void {
|
||||
let key = `{pub}|{local}`
|
||||
for i in 0 .. len(net_st.net__seen) { if net_st.net__seen[i] == key { return } }
|
||||
|
|
@ -11,6 +12,7 @@ function net__new_guest(net_st: mut NetState, pub: string, local: string) -> voi
|
|||
if net_parse_code(net_st, local) { net__punch_add(net_st, net_st.net__code_ip, net_st.net__code_port) }
|
||||
}
|
||||
|
||||
@alloc_ok("once per guest punched for")
|
||||
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)
|
||||
|
|
@ -34,6 +36,7 @@ function net__punch_tick(net_st: mut NetState, dt: float) -> void {
|
|||
if net_st.net__punch_t > 0.0 { return }
|
||||
net_st.net__punch_t = 0.5
|
||||
if net_st.net__punch_b == null {
|
||||
@alloc_ok("once: the punch's four bytes")
|
||||
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')
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ function net__pump(net_st: mut NetState) -> void {
|
|||
}
|
||||
|
||||
function net__packet(net_st: mut NetState, n: int, ip: int, port: int) -> void {
|
||||
@alloc_ok("only while tracing every packet")
|
||||
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
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ function net__relay_hello(net_st: NetState) -> void {
|
|||
NetSocket.send(net_st.net__sock, net_st.net__relay_ip, net_st.net__relay_port, b, 10)
|
||||
}
|
||||
|
||||
@alloc_ok("once, when the room falls back to the relay")
|
||||
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`)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export function net_room(net_st: NetState) -> string { return net_st.net__room }
|
|||
export function net_room_asking(net_st: NetState) -> bool { return net_st.net__r_state == NET_R_STUN or net_st.net__r_state == NET_R_ASK }
|
||||
|
||||
# the host (after net_host or net_become_host): a room at the matchmaker, if there is one
|
||||
@alloc_ok("once per room opened")
|
||||
export function net_room_host(net_st: mut NetState) -> void {
|
||||
if not net_room_on(net_st) { return }
|
||||
net_st.net__r_role = 1
|
||||
|
|
@ -51,6 +52,7 @@ function net__stun_begin(net_st: mut NetState) -> void {
|
|||
net__stun_send(net_st)
|
||||
}
|
||||
|
||||
@alloc_ok("once per request to the party server")
|
||||
function net__room_body(net_st: NetState, 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) }
|
||||
|
|
@ -62,6 +64,7 @@ function net__json_str(v: Val, key: string) -> string {
|
|||
return value_as_str(value_get(v, key))
|
||||
}
|
||||
|
||||
@alloc_ok("a STUN answer or a log line: a few per session")
|
||||
function net__addr_text(ip: int, port: int) -> string { return `{Udp.ip_text(ip)}:{port}` }
|
||||
|
||||
function net__local_text(net_st: NetState) -> string {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# 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
|
||||
@alloc_ok("once, when a room cannot be had")
|
||||
function net__room_fail(net_st: mut NetState, why: int) -> void {
|
||||
net__log(`matchmaker: {why}`)
|
||||
let was_guest = net_st.net__r_role == 2
|
||||
|
|
@ -11,6 +12,7 @@ function net__room_fail(net_st: mut NetState, why: int) -> void {
|
|||
}
|
||||
|
||||
# the guest: to the host's outside address, or its inside one behind the same router
|
||||
@alloc_ok("once per room answer")
|
||||
function net__room_connect(net_st: mut NetState, host_pub: string, host_local: string) -> void {
|
||||
var target = host_pub
|
||||
var same_router = false
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ function net__room_tick(net_st: mut NetState, dt: float) -> void {
|
|||
}
|
||||
|
||||
# a second and a half for an answer, asked three times; without one, inside addresses only
|
||||
@alloc_ok("a request to the party server: once per room stage")
|
||||
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
|
||||
|
|
@ -63,6 +64,8 @@ function net__room_answer(net_st: mut NetState) -> void {
|
|||
return
|
||||
}
|
||||
net_st.net__room = code
|
||||
@alloc_ok("once per room")
|
||||
net_st.net__watch_url = `{net__conf(net_st).signal_url}/rooms/{code}`
|
||||
net__take_relay(net_st, v)
|
||||
net_st.net__r_state = NET_R_ROOM
|
||||
net_st.net__poll_t = 0.0
|
||||
|
|
@ -100,7 +103,7 @@ function net__room_watch(net_st: mut NetState, dt: float) -> void {
|
|||
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__http = Http.get(net_st.net__watch_url)
|
||||
net_st.net__r_t = 0.0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ function net__flush_one(net_st: mut NetState, p: NetPeer) -> bool {
|
|||
let taken = m.rel and m.id <= p.acked
|
||||
let stale = not m.rel and net_st.net__time - m.made > 0.5
|
||||
if taken or stale {
|
||||
push(net_st.net__free, m) # back to the pool
|
||||
kept_push(net_st.net__free, m) # back to the pool
|
||||
continue
|
||||
}
|
||||
var due = true
|
||||
|
|
@ -33,9 +33,9 @@ function net__flush_one(net_st: mut NetState, p: NetPeer) -> bool {
|
|||
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 }
|
||||
if m.rel { push(keep, m) } else { push(net_st.net__free, m) } # a reliable one kept until acknowledged
|
||||
if m.rel { kept_push(keep, m) } else { kept_push(net_st.net__free, m) } # a reliable one kept until acknowledged
|
||||
} else {
|
||||
push(keep, m) # not due, or waits for the next packet
|
||||
kept_push(keep, m) # not due, or waits for the next packet
|
||||
}
|
||||
}
|
||||
p.back = p.queue
|
||||
|
|
@ -51,6 +51,7 @@ function net__flush_one(net_st: mut NetState, p: NetPeer) -> bool {
|
|||
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
|
||||
@alloc_ok("only while tracing every packet")
|
||||
if NetWorld.trace() { net__log(`out {o} bytes to {Udp.ip_text(p.ip)}:{p.port}: {sent}`) }
|
||||
p.ack_due = false
|
||||
return count > 0
|
||||
|
|
@ -75,7 +76,10 @@ function net__put_msg(net_st: NetState, at: int, m: NetMsg) -> int {
|
|||
|
||||
# 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)
|
||||
if net_st.net__kind_out == null {
|
||||
@alloc_ok("once: the counts are made the first time they are cleared")
|
||||
net_st.net__kind_out = words(64)
|
||||
}
|
||||
for k in 0 .. 64 { net_st.net__kind_out[k] = 0 }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# machine's pid is what its packets say. A token picked by the host names the session.
|
||||
|
||||
# open a socket on `port` and take guests; false (net_why) when the port is taken
|
||||
@alloc_ok("once per party hosted")
|
||||
export function net_host(net_st: mut NetState, port: int, token: int) -> bool {
|
||||
if net_live(net_st) { return true }
|
||||
net_st.net__sock = NetSocket.open(port)
|
||||
|
|
@ -18,6 +19,7 @@ export function net_host(net_st: mut NetState, port: int, token: int) -> bool {
|
|||
}
|
||||
|
||||
# dial a host at an address; the hello (net_hello_set) goes until a welcome or a refusal
|
||||
@alloc_ok("once per join")
|
||||
export function net_join(net_st: mut NetState, ip: int, port: int) -> bool {
|
||||
if net_live(net_st) { return false }
|
||||
if not net__socket(net_st) { return false }
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ export property NetMessage {
|
|||
export state NetState {
|
||||
net__cfg: NetConfig = new NetConfig
|
||||
net__facts: Queue<NetFact> = net__facts__new()
|
||||
net__fpool: []NetFact = net__fpool__new() # the facts' records, taken in turn (pool.ludic)
|
||||
net__fat: int = 0
|
||||
net__watch_url: string = "" # the room's address, made once per room
|
||||
net__seen: []string = new []string # guests already punched for
|
||||
net__free: []NetMsg = new []NetMsg # sent and taken (or stale): made again, never new (pool.ludic)
|
||||
net__ina: []NetMessage = new []NetMessage # the inbox's records, two halves
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ export function net_reset(net_st: mut NetState) -> void {
|
|||
export function net_lose(net_st: mut NetState, why: int) -> void { net__lose_with(net_st, why, "") }
|
||||
|
||||
# ... with what the reason is about (the room code nobody had)
|
||||
@alloc_ok("once, when the party is lost")
|
||||
function net__lose_with(net_st: mut NetState, why: int, text: string) -> void {
|
||||
net_reset(net_st)
|
||||
net_st.net__state = NET_LOST
|
||||
|
|
|
|||
|
|
@ -25,11 +25,12 @@ function shop_pick_one(shop_st: ShopState, v: int, taken: []int) -> int {
|
|||
}
|
||||
|
||||
function shop_roll_vendor(shop_st: mut ShopState, v: int) -> void {
|
||||
let taken = new []int
|
||||
let taken = shop_st.shop_taken
|
||||
List.clear(taken)
|
||||
for k in 0 .. shop_st.shop_wants_n {
|
||||
let it = shop_pick_one(shop_st, v, taken)
|
||||
shop_st.shop_want_list[v * shop_st.shop_wants_n + k] = it
|
||||
if it >= 0 { push(taken, it) }
|
||||
if it >= 0 { kept_push(taken, it) }
|
||||
}
|
||||
shop_st.shop_full_list[v] = shop_pick_one(shop_st, v, taken)
|
||||
}
|
||||
|
|
@ -39,11 +40,8 @@ export function shop_roll(shop_st: mut ShopState) -> void {
|
|||
let week = shop_week(shop_st)
|
||||
if week == shop_st.shop_week_rolled and shop_st.shop_want_list != null { return }
|
||||
shop_st.shop_week_rolled = week
|
||||
shop_st.shop_dice = rng_new(shop_st.shop_seed + week * 37)
|
||||
shop_st.shop_want_list = new []int
|
||||
shop_st.shop_full_list = new []int
|
||||
for i in 0 .. shop_vendors(shop_st) * shop_st.shop_wants_n { push(shop_st.shop_want_list, -1) }
|
||||
for v in 0 .. shop_vendors(shop_st) { push(shop_st.shop_full_list, -1) }
|
||||
rng_seed(shop_st.shop_dice, shop_st.shop_seed + week * 37)
|
||||
shop_lists_empty(shop_st)
|
||||
if shop_items(shop_st) == 0 { return }
|
||||
for v in 0 .. shop_vendors(shop_st) { shop_roll_vendor(shop_st, v) }
|
||||
}
|
||||
|
|
@ -90,3 +88,4 @@ function shop_demand_reset(shop_st: mut ShopState) -> void {
|
|||
shop_st.shop_want_list = null
|
||||
shop_st.shop_full_list = null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ numbers float
|
|||
import "ludic.base"
|
||||
import "state.ludic"
|
||||
import "demand.ludic"
|
||||
import "week_lists.ludic"
|
||||
import "prices.ludic"
|
||||
import "trade.ludic"
|
||||
import "system.ludic"
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ export state ShopState {
|
|||
shop_week_rolled: int = -1
|
||||
shop_want_list: []int = null # shop_wants_n per vendor, -1 empty
|
||||
shop_full_list: []int = null # one per vendor, -1 none
|
||||
shop_dice: Rng = null
|
||||
shop_dice: Rng = rng_new(0)
|
||||
shop_taken: []int = new []int # a vendor's picks while its week is rolled, kept
|
||||
shop_base: []int = null # an item's base price
|
||||
shop_sell_frac: []float = null # what fraction of the curve a vendor pays for it
|
||||
shop_vendor_list: []ShopVendor = null
|
||||
|
|
|
|||
14
packages/ludic.shop/week_lists.ludic
Normal file
14
packages/ludic.shop/week_lists.ludic
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# ludic.shop/week_lists.ludic - the lists a week is rolled into, made once and cleared after
|
||||
# the week's lists, every entry -1: made the first week and cleared after, the vendors being fixed
|
||||
function shop_lists_empty(shop_st: mut ShopState) -> void {
|
||||
if shop_st.shop_want_list == null {
|
||||
@alloc_ok("once: the lists are made the first week they are rolled")
|
||||
shop_st.shop_want_list = new []int
|
||||
@alloc_ok("once: as above")
|
||||
shop_st.shop_full_list = new []int
|
||||
}
|
||||
List.clear(shop_st.shop_want_list)
|
||||
List.clear(shop_st.shop_full_list)
|
||||
for i in 0 .. shop_vendors(shop_st) * shop_st.shop_wants_n { kept_push(shop_st.shop_want_list, -1) }
|
||||
for v in 0 .. shop_vendors(shop_st) { kept_push(shop_st.shop_full_list, -1) }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue