power_play/src/pp/pp_transcode.c

392 lines
12 KiB
C

////////////////////////////////////////////////////////////
//~ 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;
}