//////////////////////////////////////////////////////////// //~ World String P_PackWorld(Arena *arena, P_World *src_world) { String result = Zi; result.text = ArenaNext(arena, u8); TempArena scratch = BeginScratch(arena); P_Frame *src_frame = src_world->last_frame; result.len += StringF(arena, "version: %F\n", FmtUint(P_Tv_Latest)).len; result.len += StringF(arena, "\n").len; result.len += StringF(arena, "seed: 0x%F\n", FmtHex(src_world->seed)).len; result.len += StringF(arena, "time: %F\n", FmtSint(src_frame->time_ns)).len; result.len += StringF(arena, "tick: %F\n", FmtSint(src_frame->tick)).len; // Pack entities // FIXME: Precision result.len += PushString(arena, Lit("\nentities:\n")).len; result.len += PushString(arena, Lit("{\n")).len; for (P_Ent *ent = P_FirstEnt(src_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) { // TODO: Pack ignored ents b32 ignore = 0; if (ent->is_weapon || ent->is_bullet || ent->is_bomb || ent->is_hit || ent->is_trail) { ignore = 1; } if (!ignore) { result.len += StringF(arena, " 0x%F:\n", FmtHex(ent->key.v)).len; result.len += PushString(arena, Lit(" {\n")).len; { result.len += StringF(arena, " props: \n").len; result.len += StringF(arena, " {\n").len; { if (ent->is_player) { result.len += PushString(arena, Lit(" player\n")).len; } if (ent->is_guy) { result.len += PushString(arena, Lit(" guy\n")).len; } if (ent->is_bot) { result.len += PushString(arena, Lit(" bot\n")).len; } if (ent->is_weapon) { result.len += PushString(arena, Lit(" weapon\n")).len; } if (ent->is_bullet) { result.len += PushString(arena, Lit(" bullet\n")).len; } if (ent->is_guy_spawn) { result.len += PushString(arena, Lit(" guy_spawn\n")).len; } if (ent->is_health_spawn) { result.len += PushString(arena, Lit(" health_spawn\n")).len; } if (ent->is_health) { result.len += PushString(arena, Lit(" health\n")).len; } if (ent->is_pickup) { result.len += PushString(arena, Lit(" pickup\n")).len; } } result.len += StringF(arena, " }\n").len; result.len += StringF(arena, " created_at_ns: \"%F\"\n", FmtSint(ent->created_at_ns)).len; result.len += StringF(arena, " created_at_tick: \"%F\"\n", FmtSint(ent->created_at_tick)).len; result.len += StringF(arena, " last_spawn_reset_ns: \"%F\"\n", FmtSint(ent->last_spawn_reset_ns)).len; result.len += StringF(arena, " lifetime_seconds: \"%F\"\n", FmtFloat(ent->lifetime_seconds)).len; result.len += StringF(arena, " pickup: \"0x%F\"\n", FmtHex(ent->pickup.v)).len; result.len += StringF(arena, " pos: \"%F\"\n", FmtFloat2(ent->xf.t)).len; result.len += StringF(arena, " angle: \"%F\"\n", FmtFloat(AngleFromVec2(ent->xf.r))).len; result.len += StringF(arena, " look: \"%F\"\n", FmtFloat2(ent->control.look)).len; result.len += StringF(arena, " exists: \"%F\"\n", FmtFloat(ent->exists)).len; result.len += StringF(arena, " health: \"%F\"\n", FmtFloat(ent->health)).len; result.len += StringF(arena, " guy: \"0x%F\"\n", FmtHex(ent->guy.v)).len; result.len += StringF(arena, " weapon: \"0x%F\"\n", FmtHex(ent->weapon.v)).len; result.len += StringF(arena, " source: \"0x%F\"\n", FmtHex(ent->source.v)).len; result.len += StringF(arena, " kills: \"%F\"\n", FmtFloat(ent->kills)).len; result.len += StringF(arena, " deaths: \"%F\"\n", FmtFloat(ent->deaths)).len; result.len += StringF(arena, " text: \"%F\"\n", FmtString(CR_SanitizeString(scratch.arena, STRING(ent->string_len, ent->string_text)))).len; } result.len += PushString(arena, Lit(" }\n")).len; } } result.len += PushString(arena, Lit("}\n")).len; // Pack tiles // TODO: Compress tile data result.len += PushString(arena, Lit("\ntiles:\n")).len; result.len += PushString(arena, Lit("{\n")).len; { String tiles_str = Base64FromString(scratch.arena, STRING(P_TilesCount, src_world->tiles)); u64 tile_chars_per_line = 128; u64 tile_char_pos = 0; while (tile_char_pos < tiles_str.len) { result.len += PushString(arena, Lit(" ")).len; String src = STRING(tile_chars_per_line, tiles_str.text + tile_char_pos); src.len = MinU64(tile_chars_per_line, tiles_str.len - tile_char_pos); result.len += PushString(arena, src).len; result.len += PushString(arena, Lit("\n")).len; tile_char_pos += tile_chars_per_line; } } result.len += PushString(arena, Lit("}\n")).len; EndScratch(scratch); return result; } P_UnpackedWorld P_UnpackWorld(Arena *arena, String packed) { P_UnpackedWorld result = Zi; TempArena scratch = BeginScratch(arena); CR_Item *root = CR_ItemFromString(scratch.arena, packed); // Unpack version P_Tv version = 0; for (CR_Item *root_item = root->first; root_item; root_item = root_item->next) { if (MatchString(root_item->name, Lit("version"))) { version = CR_IntFromString(root_item->value); break; } } for (CR_Item *top_item = root->first; top_item; top_item = top_item->next) { // Unpack metadata if (MatchString(top_item->name, Lit("seed"))) { result.seed = CR_IntFromString(top_item->value); } if (MatchString(top_item->name, Lit("time"))) { result.time_ns = CR_IntFromString(top_item->value); } if (MatchString(top_item->name, Lit("tick"))) { result.tick = CR_IntFromString(top_item->value); } // Unpack entities if (MatchString(top_item->name, Lit("entities"))) { for (CR_Item *ent_item = top_item->first; ent_item; ent_item = ent_item->next) { P_Ent *ent = P_PushTempEnt(arena, &result.ents); ent->key = (P_EntKey) { .v = CR_IntFromString(ent_item->name) }; for (CR_Item *attr = ent_item->first; attr; attr = attr->next) { if (MatchString(attr->name, Lit("props"))) { for (CR_Item *prop = attr->first; prop; prop = prop->next) { if (MatchString(prop->value, Lit("player"))) { ent->is_player = 1; } if (MatchString(prop->value, Lit("guy"))) { ent->is_guy = 1; } if (MatchString(prop->value, Lit("bot"))) { ent->is_bot = 1; } if (MatchString(prop->value, Lit("weapon"))) { ent->is_weapon = 1; } if (MatchString(prop->value, Lit("bullet"))) { ent->is_bullet = 1; } if (MatchString(prop->value, Lit("guy_spawn"))) { ent->is_guy_spawn = 1; } if (MatchString(prop->value, Lit("health_spawn"))) { ent->is_health_spawn = 1; } if (MatchString(prop->value, Lit("health"))) { ent->is_health = 1; } if (MatchString(prop->value, Lit("pickup"))) { ent->is_pickup = 1; } } } if (MatchString(attr->name, Lit("created_at_ns"))) { ent->created_at_ns = CR_IntFromString(attr->value); } if (MatchString(attr->name, Lit("created_at_tick"))) { ent->created_at_tick = CR_IntFromString(attr->value); } if (MatchString(attr->name, Lit("last_spawn_reset_ns"))) { ent->last_spawn_reset_ns = CR_IntFromString(attr->value); } if (MatchString(attr->name, Lit("lifetime_seconds"))) { ent->lifetime_seconds = CR_FloatFromString(attr->value); } if (MatchString(attr->name, Lit("pickup"))) { ent->pickup.v = CR_IntFromString(attr->value); } if (MatchString(attr->name, Lit("pos"))) { ent->xf.t = CR_Vec2FromString(attr->value); } if (MatchString(attr->name, Lit("angle"))) { ent->xf.r = Vec2FromAngle(CR_FloatFromString(attr->value)); } if (MatchString(attr->name, Lit("look"))) { ent->control.look = CR_Vec2FromString(attr->value); } if (MatchString(attr->name, Lit("exists"))) { ent->exists = CR_FloatFromString(attr->value); } if (MatchString(attr->name, Lit("health"))) { ent->health = CR_FloatFromString(attr->value); } if (MatchString(attr->name, Lit("guy"))) { ent->guy.v = CR_IntFromString(attr->value); } if (MatchString(attr->name, Lit("weapon"))) { ent->weapon.v = CR_IntFromString(attr->value); } if (MatchString(attr->name, Lit("source"))) { ent->source.v = CR_IntFromString(attr->value); } if (MatchString(attr->name, Lit("kills"))) { ent->kills = CR_FloatFromString(attr->value); } if (MatchString(attr->name, Lit("deaths"))) { ent->deaths = CR_FloatFromString(attr->value); } if (MatchString(attr->name, Lit("text"))) { P_SetEntString(ent, attr->value); } } } } // Parse tiles String tiles_base64 = Zi; tiles_base64.text = ArenaNext(scratch.arena, u8); for (CR_Item *tile_item = top_item->first; tile_item; tile_item = tile_item->next) { if (tile_item->name.len == 0) { tiles_base64.len += PushString(scratch.arena, tile_item->value).len; } } if (StringLenFromBase64Len(tiles_base64.len) == P_TilesCount) { result.tiles = StringFromBase64(arena, tiles_base64).text; } } if (!result.tiles) { result.tiles = PushStructs(arena, u8, P_TilesCount); } EndScratch(scratch); return result; }; //////////////////////////////////////////////////////////// //~ Message String P_PackMessages(BB_Writer *bbw, P_MsgList msgs) { u64 pos_start = BB_GetNumBytesWritten(bbw); { for (P_MsgNode *msg_node = msgs.first; msg_node; msg_node = msg_node->next) { // TODO: Compress P_Msg *msg = &msg_node->msg; BB_WriteBit(bbw, 0); // Not raw BB_WriteBytes(bbw, StringFromStruct(msg)); BB_WriteString(bbw, msg->data); } } u64 pos_end = BB_GetNumBytesWritten(bbw); String result = Zi; result.text = BB_GetWrittenRaw(bbw) + pos_start; result.len = pos_end - pos_start; return result; } P_MsgList P_UnpackMessages(Arena *arena, String packed, NET_Key sender) { P_MsgList result = Zi; BB_Buff bb = BB_BuffFromString(packed); BB_Reader bbr = BB_ReaderFromBuff(&bb); b32 done = 0; while (!done) { //- Read P_Msg msg = Zi; { b32 is_raw = BB_ReadBit(&bbr); if (is_raw) { msg.kind = P_MsgKind_Raw; msg.data = packed; done = 1; } else { u8 *raw_src_msg = BB_ReadBytesRaw(&bbr, sizeof(P_Msg)); if (raw_src_msg != 0) { CopyStruct(&msg, raw_src_msg); String data_str = BB_ReadStringRaw(&bbr); msg.data = data_str; } else { done = 1; } } } //- Validate b32 skip = 0; { if (msg.kind < P_MsgKind_None || msg.kind > P_MsgKind_COUNT) { skip = 1; } if (msg.prefab < P_PrefabKind_None || msg.prefab > P_PrefabKind_COUNT) { skip = 1; } } if (!skip) { //- Sanitize { msg.src = sender; msg.dst = NET_NilKey; msg.xf = NormXform(msg.xf); msg.data = PushString(arena, msg.data); } //- Collect { P_MsgNode *msg_node = PushStruct(arena, P_MsgNode); DllQueuePush(result.first, result.last, msg_node); result.count += 1; msg_node->msg = msg; } } } return result; }