From d13a7c70e751884a1863e97810a7d0548b5ae782 Mon Sep 17 00:00:00 2001 From: jacob Date: Fri, 7 Feb 2025 10:56:37 -0600 Subject: [PATCH] host progress --- src/game.c | 43 ++++++++++------ src/host.c | 125 ++++++++++++++++++++++++++++++++++++++--------- src/sock.h | 1 + src/sock_win32.c | 77 +++++++++++++++++++++-------- src/sys_win32.c | 1 + src/user.c | 3 -- 6 files changed, 191 insertions(+), 59 deletions(-) diff --git a/src/game.c b/src/game.c index 1f4331ee..e0a899a3 100644 --- a/src/game.c +++ b/src/game.c @@ -1350,6 +1350,7 @@ INTERNAL void game_update(void) struct string game_string_from_cmds(struct arena *arena, struct game_cmd_list cmds) { + __prof; struct byte_writer bw = bw_from_arena(arena); for (struct game_cmd *cmd = cmds.first; cmd; cmd = cmd->next) { @@ -1387,33 +1388,46 @@ struct string game_string_from_cmds(struct arena *arena, struct game_cmd_list cm void game_cmds_from_host_events(struct arena *arena, struct host_event_array host_events, struct game_cmd_list *cmds_out) { + __prof; for (u64 i = 0; i < host_events.count; ++i) { - struct game_cmd *cmd = arena_push_zero(arena, struct game_cmd); struct host_event host_event = host_events.events[i]; enum host_event_kind host_event_kind = host_event.kind; - cmd->channel_id = host_event.channel_id; switch (host_event_kind) { case HOST_EVENT_KIND_CHANNEL_OPENED: { + struct game_cmd *cmd = arena_push_zero(arena, struct game_cmd); cmd->kind = GAME_CMD_KIND_CLIENT_CONNECT; + cmd->channel_id = host_event.channel_id; + if (cmds_out->last) { + cmds_out->last->next = cmd; + } else { + cmds_out->first = cmd; + } + cmds_out->last = cmd; } break; case HOST_EVENT_KIND_CHANNEL_CLOSED: { + struct game_cmd *cmd = arena_push_zero(arena, struct game_cmd); cmd->kind = GAME_CMD_KIND_CLIENT_DISCONNECT; cmd->disconnect_reason = LIT("Connection lost"); + if (cmds_out->last) { + cmds_out->last->next = cmd; + } else { + cmds_out->first = cmd; + } + cmds_out->last = cmd; } break; case HOST_EVENT_KIND_MSG: { struct byte_reader br = br_from_buffer(host_event.msg); while (br_bytes_left(&br) > 0) { + struct game_cmd *cmd = arena_push_zero(arena, struct game_cmd); u64 cmd_size = br_read_u64(&br); u64 cmd_pos_end = br_pos(&br) + cmd_size; - cmd->kind = br_read_i8(&br); cmd->state = br_read_i8(&br); - #if RTC cmd->collider_gjk_steps = br_read_u32(&br); #endif @@ -1435,18 +1449,18 @@ void game_cmds_from_host_events(struct arena *arena, struct host_event_array hos ASSERT(br_pos(&br) == cmd_pos_end); br_seek_to(&br, cmd_pos_end); + + if (cmds_out->last) { + cmds_out->last->next = cmd; + } else { + cmds_out->first = cmd; + } + cmds_out->last = cmd; } } break; default: break; } - - if (cmds_out->last) { - cmds_out->last->next = cmd; - } else { - cmds_out->first = cmd; - } - cmds_out->last = cmd; } } @@ -1456,6 +1470,7 @@ void game_cmds_from_host_events(struct arena *arena, struct host_event_array hos struct string game_string_from_events(struct arena *arena, struct game_event_list events) { + __prof; struct byte_writer bw = bw_from_arena(arena); for (struct game_event *event = events.first; event; event = event->next) { struct byte_writer bw_size = bw_branch(&bw, sizeof(u64)); @@ -1479,6 +1494,7 @@ struct string game_string_from_events(struct arena *arena, struct game_event_lis void game_events_from_host_events(struct arena *arena, struct host_event_array host_events, struct game_event_list *events_out) { + __prof; for (u64 i = 0; i < host_events.count; ++i) { struct game_event *game_event = arena_push_zero(arena, struct game_event); struct host_event host_event = host_events.events[i]; @@ -1536,6 +1552,7 @@ void game_events_from_host_events(struct arena *arena, struct host_event_array h struct string game_string_from_tick(struct arena *arena, struct world *tick) { + __prof; struct byte_writer bw = bw_from_arena(arena); bw_write_var_uint(&bw, tick->continuity_gen); @@ -1559,6 +1576,7 @@ struct string game_string_from_tick(struct arena *arena, struct world *tick) void game_tick_from_string(struct string str, struct world *tick_out) { + __prof; struct byte_reader br = br_from_buffer(str); tick_out->continuity_gen = br_read_var_uint(&br); @@ -1579,11 +1597,8 @@ void game_tick_from_string(struct string str, struct world *tick_out) struct entity *src = &entities_src[i]; struct entity *dst = &tick_out->entity_store->entities[i]; *dst = *src; - DEBUGBREAKABLE; } } - - DEBUGBREAKABLE; } /* ========================== * diff --git a/src/host.c b/src/host.c index f78220f7..97c92d51 100644 --- a/src/host.c +++ b/src/host.c @@ -4,6 +4,7 @@ #include "byteio.h" #include "sys.h" #include "util.h" +#include "log.h" //#define HOST_NETWORK_ADDRESS_STRING(str) //#define HOST_NETWORK_ADDRESS_ALL_LOCAL_INTERFACES(port) @@ -58,6 +59,8 @@ struct host_channel { struct host_packet *last_reliable_packet; struct host_packet *first_unreliable_packet; struct host_packet *last_unreliable_packet; + u64 num_reliable_packets; + u64 num_unreliable_packets; /* NOTE: Msg assemblers are allocated in host's `arena` */ struct host_msg_assembler *least_recent_msg_assembler; @@ -66,6 +69,7 @@ struct host_channel { u64 last_sent_msg_id; u64 their_acked_seq; u64 our_acked_seq; + u64 last_sent_seq; }; struct host_channel_node { @@ -92,7 +96,7 @@ struct host_msg_assembler { struct host *host; struct host_channel *channel; - + b32 is_reliable; /* TODO: Remove this (testing) */ struct arena testarena; @@ -350,7 +354,7 @@ INTERNAL struct host_msg_assembler *host_get_msg_assembler(struct host *host, st return NULL; } -INTERNAL struct host_msg_assembler *host_msg_assembler_alloc(struct host_channel *channel, u64 msg_id, u64 chunk_count, u64 now_ns) +INTERNAL struct host_msg_assembler *host_msg_assembler_alloc(struct host_channel *channel, u64 msg_id, u64 chunk_count, u64 now_ns, b32 is_reliable) { struct host *host = channel->host; struct host_msg_assembler *ma; @@ -361,17 +365,20 @@ INTERNAL struct host_msg_assembler *host_msg_assembler_alloc(struct host_channel ma = arena_push(&host->arena, struct host_msg_assembler); } MEMZERO_STRUCT(ma); + ma->host = channel->host; ma->channel = channel; ma->msg_id = msg_id; ma->num_chunks_total = chunk_count; /* FIXME: Use buddy allocator or something */ - u64 buff_size = chunk_count * PACKET_CHUNK_MAX_LEN; - ma->testarena = arena_alloc(buff_size); + u64 data_size = chunk_count * PACKET_CHUNK_MAX_LEN; + u64 bitmap_size = ((chunk_count - 1) / 8) + 1; + ma->testarena = arena_alloc(data_size + bitmap_size); /* FIXME: Ensure chunk_count > 0 */ - ma->chunks_received_bitmap = arena_push_array_zero(&ma->testarena, u8, (((chunk_count - 1) / 8) + 1)); - ma->data = arena_push_array(&ma->testarena, u8, buff_size); + ma->chunks_received_bitmap = arena_push_array_zero(&ma->testarena, u8, bitmap_size); + ma->data = arena_push_array(&ma->testarena, u8, data_size); + ma->is_reliable = is_reliable; /* Insert into channel list */ ma->touched_ns = now_ns; @@ -444,6 +451,40 @@ INTERNAL void host_msg_assembler_release(struct host_msg_assembler *ma) host->first_free_msg_assembler = ma; } +INTERNAL void host_msg_assembler_touch(struct host_msg_assembler *ma, i64 now_ns) +{ + struct host_channel *channel = ma->channel; + if (ma != channel->most_recent_msg_assembler) { + /* Remove from channel list */ + { + struct host_msg_assembler *prev = ma->less_recent; + struct host_msg_assembler *next = ma->more_recent; + if (prev) { + prev->more_recent = next; + } else { + channel->least_recent_msg_assembler = next; + } + if (next) { + next->less_recent = prev; + } else { + channel->most_recent_msg_assembler = prev; + } + } + + /* Insert at end of channel list */ + { + if (channel->most_recent_msg_assembler) { + channel->most_recent_msg_assembler->more_recent = ma; + ma->less_recent = channel->most_recent_msg_assembler; + } else { + channel->least_recent_msg_assembler = ma; + } + channel->most_recent_msg_assembler = ma; + } + } + ma->touched_ns = now_ns; +} + INTERNAL b32 host_msg_assembler_is_chunk_filled(struct host_msg_assembler *ma, u64 chunk_id) { if (chunk_id < ma->num_chunks_total) { @@ -466,31 +507,34 @@ INTERNAL void host_msg_assembler_set_chunk_received(struct host_msg_assembler *m INTERNAL struct host_packet *host_channel_packet_alloc(struct host_channel *channel, b32 is_reliable) { struct host *host = channel->host; - struct host_packet *host_packet = NULL; + struct host_packet *packet = NULL; if (host->first_free_packet) { - host_packet = host->first_free_packet; - host->first_free_packet = host_packet->next; + packet = host->first_free_packet; + host->first_free_packet = packet->next; } else { - host_packet = arena_push(&host->channel_arena, struct host_packet); + packet = arena_push(&host->arena, struct host_packet); } - MEMZERO_STRUCT(host_packet); + MEMZERO_STRUCT(packet); if (is_reliable) { if (channel->last_reliable_packet) { - channel->last_reliable_packet->next = host_packet; + channel->last_reliable_packet->next = packet; } else { - channel->first_reliable_packet = host_packet; + channel->first_reliable_packet = packet; } - channel->last_reliable_packet = host_packet; + channel->last_reliable_packet = packet; + ++channel->num_reliable_packets; + packet->seq = ++channel->last_sent_seq; } else { if (channel->last_unreliable_packet) { - channel->last_unreliable_packet->next = host_packet; + channel->last_unreliable_packet->next = packet; } else { - channel->first_unreliable_packet = host_packet; + channel->first_unreliable_packet = packet; } - channel->last_unreliable_packet = host_packet; + channel->last_unreliable_packet = packet; + ++channel->num_unreliable_packets; } - return host_packet; + return packet; } /* ========================== * @@ -551,6 +595,7 @@ INTERNAL struct host_queued_event *host_queued_event_alloc_and_append(struct hos void host_update(struct host *host) { + __prof; struct temp_arena scratch = scratch_begin_no_conflict(); i64 now_ns = sys_time_ns(); @@ -580,7 +625,8 @@ void host_update(struct host *host) } b32 should_process_packet = false; - if (packet_flags & HOST_PACKET_FLAG_RELIABLE) { + b32 is_reliable = packet_flags & HOST_PACKET_FLAG_RELIABLE; + if (is_reliable) { u64 packet_seq = br_read_var_uint(&br); if (packet_seq == channel->our_acked_seq + 1) { channel->our_acked_seq = packet_seq; @@ -596,6 +642,7 @@ void host_update(struct host *host) { /* A foreign host is trying to connect to us */ if (!channel->valid) { + logf_info("Received conection attempt from %F", FMT_STR(sock_string_from_address(scratch.arena, address))); /* TODO: Verify that some per-host uuid isn't * present in a rolling window to prevent reconnects right after a disconnect? */ channel = host_channel_alloc(host, address); @@ -606,6 +653,7 @@ void host_update(struct host *host) { /* We successfully connected to a foreign host and they are ready to receive messages */ if (channel->valid && !channel->connected) { + logf_info("Received connection from %F", FMT_STR(sock_string_from_address(scratch.arena, address))); struct host_queued_event *queued_event = host_queued_event_alloc_and_append(host); queued_event->event.kind = HOST_EVENT_KIND_CHANNEL_OPENED; queued_event->event.channel_id = channel->id; @@ -617,6 +665,7 @@ void host_update(struct host *host) { /* A foreign host disconnected from us */ if (channel->valid) { + logf_info("Received disconnection from %F", FMT_STR(sock_string_from_address(scratch.arena, address))); struct host_queued_event *queued_event = host_queued_event_alloc_and_append(host); queued_event->event.kind = HOST_EVENT_KIND_CHANNEL_CLOSED; queued_event->event.channel_id = channel->id; @@ -638,7 +687,7 @@ void host_update(struct host *host) struct host_msg_assembler *ma = host_get_msg_assembler(host, channel->id, msg_id); if (!ma) { - ma = host_msg_assembler_alloc(channel, msg_id, chunk_count, now_ns); + ma = host_msg_assembler_alloc(channel, msg_id, chunk_count, now_ns, is_reliable); } if (chunk_count == ma->num_chunks_total && chunk_id < chunk_count) { @@ -652,7 +701,7 @@ void host_update(struct host *host) } host_msg_assembler_set_chunk_received(ma, chunk_id); ++ma->num_chunks_received; - ma->touched_ns = now_ns; + host_msg_assembler_touch(ma, now_ns); if (ma->num_chunks_received == chunk_count) { /* All chunks filled, message has finished assembling */ /* TODO: Message ordering */ @@ -664,9 +713,17 @@ void host_update(struct host *host) queued_event->event.kind = HOST_EVENT_KIND_MSG; queued_event->event.msg = data; queued_event->event.channel_id = channel->id; + if (is_reliable) { + /* Release assembler if reliable */ + host_msg_assembler_release(ma); + } } + } else { + ASSERT(false); } } + } else { + ASSERT(false); } } } break; @@ -702,6 +759,7 @@ void host_update(struct host *host) host_packet->next = host->first_free_packet; host->first_free_packet = host_packet; channel->first_reliable_packet = next; + --channel->num_reliable_packets; } else { break; } @@ -712,6 +770,25 @@ void host_update(struct host *host) } } /* TODO: Release timed out unreliable msg buffers */ + { + /* TODO: Configurable timeout */ + i64 timeout_ns = NS_FROM_SECONDS(4); + struct host_msg_assembler *ma = channel->least_recent_msg_assembler; + while (ma) { + struct host_msg_assembler *next = ma->more_recent; + if ((now_ns - ma->touched_ns) > timeout_ns) { + if (!ma->is_reliable) { + host_msg_assembler_release(ma); + if (ma->num_chunks_received != ma->num_chunks_total) { + DEBUGBREAKABLE; + } + } + } else { + break; + } + ma = next; + } + } } } @@ -726,6 +803,8 @@ void host_update(struct host *host) switch (kind) { case HOST_CMD_KIND_TRY_CONNECT: { + logf_info("TRY CONNECT TO %F family: %F, valid: %F", FMT_STR(sock_string_from_address(scratch.arena, channel->address)), FMT_SINT(channel->address.family), FMT_SINT(channel->address.valid)); + u8 packet_flags = 0; struct host_packet *host_packet = host_channel_packet_alloc(channel, false); struct byte_writer bw = bw_from_buffer(STRING_FROM_ARRAY(host_packet->data)); @@ -822,10 +901,11 @@ void host_update(struct host *host) } /* Release unreliable packets */ if (channel->first_unreliable_packet) { - host->first_free_packet = channel->first_unreliable_packet; channel->last_unreliable_packet->next = host->first_free_packet; + host->first_free_packet = channel->first_unreliable_packet; channel->first_unreliable_packet = NULL; channel->last_unreliable_packet = NULL; + channel->num_unreliable_packets = 0; } } } @@ -845,6 +925,7 @@ void host_update(struct host *host) struct host_event_array host_pop_events(struct arena *arena, struct host *host) { + __prof; struct host_event_array res = ZI; res.count = host->num_queued_events; res.events = arena_push_array(arena, struct host_event, res.count); diff --git a/src/sock.h b/src/sock.h index dd5beb8a..4c1dcf13 100644 --- a/src/sock.h +++ b/src/sock.h @@ -40,6 +40,7 @@ struct sock_startup_receipt sock_startup(void); struct sock_address sock_address_from_string(struct string str); struct sock_address sock_address_from_port(u16 port); +struct string sock_string_from_address(struct arena *arena, struct sock_address address); struct sock sock_alloc(u16 listen_port, u32 flags); void sock_release(struct sock *sock); diff --git a/src/sock_win32.c b/src/sock_win32.c index 115a9403..6b194f5a 100644 --- a/src/sock_win32.c +++ b/src/sock_win32.c @@ -4,7 +4,11 @@ #include "arena.h" #include "scratch.h" #include "string.h" +#include "log.h" +#define WIN32_LEAN_AND_MEAN +#define UNICODE +#include #include #include @@ -46,20 +50,22 @@ INTERNAL struct sock_address sock_address_from_ip_port_cstr(char *ip_cstr, char while (ai_res) { if (ai_res->ai_family == AF_INET) { struct sockaddr_in *sockaddr = (struct sockaddr_in *)ai_res->ai_addr; + res.valid = true; res.family = SOCK_ADDRESS_FAMILY_IPV4; res.portnb = sockaddr->sin_port; CT_ASSERT(sizeof(sockaddr->sin_addr) == 4); MEMCPY(res.ipnb, (void *)&sockaddr->sin_addr, 4); break; - } else if (ai_res->ai_family == AF_INET6) { /* FIXME: Why must this be disabled to work? */ #if 0 struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)ai_res->ai_addr; + res.valid = true; res.family = SOCK_ADDRESS_FAMILY_IPV6; res.portnb = sockaddr->sin6_port; CT_ASSERT(sizeof(sockaddr->sin6_addr) == 16); MEMCPY(res.ipnb, (void *)&sockaddr->sin6_addr, 16); + break; #endif } ai_res = ai_res->ai_next; @@ -73,7 +79,9 @@ INTERNAL struct sock_address sock_address_from_ip_port_cstr(char *ip_cstr, char struct sock_address sock_address_from_string(struct string str) { /* Parse string into ip & port */ - char *address_cstr = NULL; + u8 ip_buff[1024]; + u8 port_buff[ARRAY_COUNT(ip_buff)]; + char *ip_cstr = NULL; char *port_cstr = NULL; { u64 colon_count = 0; @@ -83,11 +91,9 @@ struct sock_address sock_address_from_string(struct string str) ++colon_count; } } - u8 address_buff[1024]; - u8 port_buff[ARRAY_COUNT(address_buff)]; - u64 address_len = 0; + u64 ip_len = 0; u64 port_len = 0; - u64 parse_len = min_u64(min_u64(str.len, ARRAY_COUNT(address_buff) - 1), ARRAY_COUNT(port_buff) - 1); + u64 parse_len = min_u64(min_u64(str.len, ARRAY_COUNT(ip_buff) - 1), ARRAY_COUNT(port_buff) - 1); if (colon_count > 1 && str.text[0] == '[') { /* Parse ipv6 with port */ b32 parse_addr = true; @@ -97,8 +103,8 @@ struct sock_address sock_address_from_string(struct string str) if (c == ']') { parse_addr = false; } else { - address_buff[address_len] = c; - ++address_len; + ip_buff[ip_len] = c; + ++ip_len; } } else if (c != ':') { port_buff[port_len] = c; @@ -114,8 +120,8 @@ struct sock_address sock_address_from_string(struct string str) if (c == ':') { parse_addr = false; } else { - address_buff[address_len] = c; - ++address_len; + ip_buff[ip_len] = c; + ++ip_len; } } else { port_buff[port_len] = c; @@ -124,12 +130,12 @@ struct sock_address sock_address_from_string(struct string str) } } else { /* Copy address without port */ - address_len = min_u64(str.len, ARRAY_COUNT(address_buff) - 1); - MEMCPY(address_buff, str.text, address_len); + ip_len = min_u64(str.len, ARRAY_COUNT(ip_buff) - 1); + MEMCPY(ip_buff, str.text, ip_len); } - if (address_len > 0) { - address_buff[address_len] = 0; - address_cstr = (char *)address_buff; + if (ip_len > 0) { + ip_buff[ip_len] = 0; + ip_cstr = (char *)ip_buff; } if (port_len > 0) { port_buff[port_len] = 0; @@ -137,15 +143,16 @@ struct sock_address sock_address_from_string(struct string str) } } - struct sock_address res = sock_address_from_ip_port_cstr(address_cstr, port_cstr); + + struct sock_address res = sock_address_from_ip_port_cstr(ip_cstr, port_cstr); return res; } struct sock_address sock_address_from_port(u16 port) { + u8 port_buff[128]; char *port_cstr = NULL; { - u8 port_buff[128]; u8 port_buff_reverse[ARRAY_COUNT(port_buff)]; u64 port_len = 0; while (port > 0 && port_len < (ARRAY_COUNT(port_buff) - 1)) { @@ -169,6 +176,24 @@ struct sock_address sock_address_from_port(u16 port) return res; } +struct string sock_string_from_address(struct arena *arena, struct sock_address address) +{ + struct string res = ZI; + + if (address.family == SOCK_ADDRESS_FAMILY_IPV6) { + /* TODO */ + } else { + u8 ip[4]; + for (u32 i = 0; i < 4; ++i) { + ip[i] = ntohs(address.ipnb[i]); + } + u16 port = ntohs(address.portnb); + res = string_format(arena, LIT("%F.%F.%F.%F:%F"), FMT_UINT(ip[0]), FMT_UINT(ip[1]), FMT_UINT(ip[2]), FMT_UINT(ip[3]), FMT_UINT(port)); + } + + return res; +} + INTERNAL struct winsock_address winsock_address_from_sock_address(struct sock_address addr) { struct winsock_address res = ZI; @@ -211,10 +236,14 @@ struct sock sock_alloc(u16 listen_port, u32 flags) struct winsock_address ws_addr = winsock_address_from_sock_address(addr); SOCKET ws = socket(ws_addr.family, SOCK_DGRAM, IPPROTO_UDP); +#if 0 if (flags & SOCK_FLAG_NON_BLOCKING) { u_long mode = 1; ioctlsocket(ws, FIONBIO, &mode); } +#else + (UNUSED)flags; +#endif //setsockopt(ws, SOL_SOCKET, SO_REUSEADDR #if 0 @@ -239,10 +268,11 @@ void sock_release(struct sock *sock) struct sock_read_result sock_read(struct sock *sock, struct string read_buff) { SOCKET ws = *(SOCKET *)&sock->handle; - struct sock_read_result res = ZI; + struct winsock_address ws_addr = ZI; ws_addr.size = sizeof(ws_addr.sas); + i32 size = recvfrom(ws, (char *)read_buff.text, read_buff.len, 0, &ws_addr.sa, &ws_addr.size); ws_addr.family = ws_addr.sin.sin_family; @@ -254,7 +284,7 @@ struct sock_read_result sock_read(struct sock *sock, struct string read_buff) } else { #if RTC i32 err = WSAGetLastError(); - if (err != WSAEWOULDBLOCK && err != WSAECONNRESET) { + if (err != WSAEWOULDBLOCK && err != WSAETIMEDOUT) { ASSERT(false); } #endif @@ -267,5 +297,12 @@ void sock_write(struct sock *sock, struct sock_address address, struct string da { SOCKET ws = *(SOCKET *)&sock->handle; struct winsock_address ws_addr = winsock_address_from_sock_address(address); - sendto(ws, (char *)data.text, data.len, 0, &ws_addr.sa, ws_addr.size); + i32 size = sendto(ws, (char *)data.text, data.len, 0, &ws_addr.sa, ws_addr.size); +#if RTC + if (size != (i32)data.len) { + i32 err = WSAGetLastError(); + (UNUSED)err; + ASSERT(false); + } +#endif } diff --git a/src/sys_win32.c b/src/sys_win32.c index 731b15f8..094e3504 100644 --- a/src/sys_win32.c +++ b/src/sys_win32.c @@ -1864,6 +1864,7 @@ void sys_panic(struct string msg) { if (atomic_i32_eval_compare_exchange(&G.panicking, 0, 1) == 0) { log_panic(msg); + ASSERT(false); wchar_t *wstr = G.panic_wstr; u64 wstr_len = 0; diff --git a/src/user.c b/src/user.c index fb4ce2c7..d73ebfe3 100644 --- a/src/user.c +++ b/src/user.c @@ -612,9 +612,6 @@ INTERNAL void user_update(void) * Process game events * ========================== */ - - - { static f64 last_try_connect = 0; f64 now = SECONDS_FROM_NS(sys_time_ns());