flake packaging

This commit is contained in:
Robin Appelman 2023-01-27 20:03:45 +01:00
commit 31d19cdcdd
12 changed files with 1252 additions and 433 deletions

170
wasm/src/lib.rs Normal file
View file

@ -0,0 +1,170 @@
mod search;
mod utils;
use crate::search::{packet_matches, SearchFilter};
use bitbuffer::{BitRead, BitReadBuffer, BitReadStream, LittleEndian};
use js_sys::Function;
use serde::Serialize;
use tf_demo_parser::demo::header::Header;
use tf_demo_parser::demo::packet::datatable::{DataTablePacket, SendTableName, ServerClassName};
use tf_demo_parser::demo::packet::Packet;
use tf_demo_parser::demo::parser::DemoHandler;
use tf_demo_parser::demo::parser::RawPacketStream;
use tf_demo_parser::demo::sendprop::SendPropName;
use wasm_bindgen::prelude::*;
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[derive(Debug, Serialize)]
pub struct PacketMeta {
index: usize,
tick: u32,
ty: u8,
}
#[wasm_bindgen]
pub struct Parser {
header: Header,
packets: Vec<Packet<'static>>,
prop_names: Vec<(u64, SendTableName, SendPropName)>,
class_names: Vec<(u16, ServerClassName)>,
}
#[wasm_bindgen]
impl Parser {
#[wasm_bindgen(constructor)]
pub fn new(input: Vec<u8>, progress: Function) -> Self {
let buffer = BitReadBuffer::new_owned(input, LittleEndian);
let mut stream = BitReadStream::new(buffer);
let header = Header::read(&mut stream).unwrap();
let mut packet_stream = RawPacketStream::new(stream);
let mut handler = DemoHandler::default();
handler.handle_header(&header);
let mut packets = Vec::new();
let mut last_progress = 0.0;
let mut prop_names = Vec::new();
let mut class_names = Vec::new();
while let Some(packet) = packet_stream.next(handler.get_parser_state()).unwrap() {
let tick = packet.tick();
packets.push(packet.clone());
if let Packet::DataTables(DataTablePacket {
tables,
server_classes,
..
}) = &packet
{
for table in tables {
for prop in &table.props {
prop_names.push((
prop.identifier.into(),
prop.identifier
.table_name()
.unwrap_or_else(|| table.name.clone()),
prop.identifier
.prop_name()
.unwrap_or_else(|| prop.name.clone()),
));
}
}
for class in server_classes {
class_names.push((class.id.into(), class.name.clone()))
}
}
handler.handle_packet(packet).unwrap();
let new_progress = ((tick as f32 / header.ticks as f32) * 100.0).floor();
if new_progress > last_progress {
last_progress = new_progress;
progress
.call1(&JsValue::NULL, &JsValue::from(new_progress))
.unwrap();
}
}
Parser {
header,
packets,
prop_names,
class_names,
}
}
pub fn header(&self) -> JsValue {
JsValue::from_serde(&self.header).unwrap()
}
pub fn packets(&self) -> Vec<JsValue> {
self.packets
.iter()
.enumerate()
.map(|(index, packet)| PacketMeta {
index,
tick: packet.tick(),
ty: packet.packet_type() as u8,
})
.map(|meta| JsValue::from_serde(&meta).unwrap())
.collect()
}
pub fn packet(&self, index: usize) -> JsValue {
JsValue::from_serde(&self.packets[index]).unwrap()
}
pub fn prop_names(&self) -> Vec<JsValue> {
self.prop_names
.iter()
.map(|(identifier, table, prop)| {
JsValue::from_serde(&PropName {
identifier: identifier.to_string(),
table: table.to_string(),
prop: prop.to_string(),
})
.unwrap()
})
.collect()
}
pub fn class_names(&self) -> Vec<JsValue> {
self.class_names
.iter()
.map(|(identifier, name)| {
JsValue::from_serde(&ClassName {
identifier: *identifier,
name: name.to_string(),
})
.unwrap()
})
.collect()
}
pub fn search(&self, filter: JsValue) -> Vec<usize> {
let filter: SearchFilter = filter.into_serde().expect("failed to parse search filter");
self.packets
.iter()
.enumerate()
.filter_map(|(index, packet)| packet_matches(packet, &filter).then_some(index))
.collect()
}
}
#[derive(Serialize)]
pub struct PropName {
pub identifier: String,
pub table: String,
pub prop: String,
}
#[derive(Serialize)]
pub struct ClassName {
pub identifier: u16,
pub name: String,
}

154
wasm/src/search.rs Normal file
View file

@ -0,0 +1,154 @@
use serde::{Deserialize, Serialize};
use tf_demo_parser::demo::message::gameevent::GameEventMessage;
use tf_demo_parser::demo::message::packetentities::{PacketEntitiesMessage, PacketEntity};
use tf_demo_parser::demo::message::setconvar::SetConVarMessage;
use tf_demo_parser::demo::message::stringtable::{
CreateStringTableMessage, UpdateStringTableMessage,
};
use tf_demo_parser::demo::message::tempentities::TempEntitiesMessage;
use tf_demo_parser::demo::message::{
EntityMessage, FileMessage, GetCvarValueMessage, Message, PrintMessage, SetViewMessage,
StringCmdMessage,
};
use tf_demo_parser::demo::packet::consolecmd::ConsoleCmdPacket;
use tf_demo_parser::demo::packet::datatable::ClassId;
use tf_demo_parser::demo::packet::message::MessagePacket;
use tf_demo_parser::demo::packet::stringtable::{StringTableEntry, StringTablePacket};
use tf_demo_parser::demo::packet::Packet;
use tf_demo_parser::demo::sendprop::SendPropIdentifier;
#[derive(Serialize, Deserialize)]
pub struct SearchFilter {
pub entity: u32,
pub search: String,
pub prop_ids: Vec<SendPropIdentifier>,
pub class_ids: Vec<ClassId>,
}
impl SearchFilter {
pub fn has_entity_filter(&self) -> bool {
!self.search.is_empty() || !self.prop_ids.is_empty() || !self.class_ids.is_empty()
}
}
pub fn packet_matches(packet: &Packet, filter: &SearchFilter) -> bool {
// return false;
// if packet
// .packet_type()
// .as_lowercase_str()
// .contains(&filter.search)
// {
// return true;
// }
match packet {
Packet::Signon(MessagePacket { messages, .. })
| Packet::Message(MessagePacket { messages, .. }) => messages
.iter()
.any(|message| message_matches(message, filter)),
Packet::SyncTick(_) => false,
Packet::ConsoleCmd(ConsoleCmdPacket { command, .. }) => command.contains(&filter.search),
Packet::UserCmd(_) => false,
Packet::DataTables(_) => false,
Packet::Stop(_) => false,
Packet::StringTables(StringTablePacket { tables, .. }) => tables.iter().any(|table| {
table.name.contains(&filter.search)
|| table.entries.iter().any(|(_, entry)| {
entry
.text
.as_deref()
.map(|text| text.contains(&filter.search))
.unwrap_or_default()
})
}),
}
}
fn message_matches(message: &Message, filter: &SearchFilter) -> bool {
let has_search = !filter.search.is_empty();
match message {
Message::File(FileMessage { file_name, .. }) => {
has_search && file_name.contains(&filter.search)
}
Message::StringCmd(StringCmdMessage { command, .. }) => {
has_search && command.contains(&filter.search)
}
Message::SetConVar(SetConVarMessage { vars, .. }) => {
has_search
&& vars.iter().any(|var| {
var.key.contains(&filter.search) || var.value.contains(&filter.search)
})
}
Message::Print(PrintMessage { value }) => {
has_search && value.as_ref().contains(&filter.search)
}
Message::CreateStringTable(CreateStringTableMessage { table, .. }) => {
has_search && table.name.contains(&filter.search)
|| table
.entries
.iter()
.any(|(_, entry)| string_entry_matches(entry, filter))
}
Message::UpdateStringTable(UpdateStringTableMessage { entries, .. }) => {
has_search
&& entries
.iter()
.any(|(_, entry)| string_entry_matches(entry, filter))
}
Message::SetView(SetViewMessage { index }) => (*index as u32) == filter.entity,
Message::UserMessage(_) => false,
Message::EntityMessage(EntityMessage { class_id, .. }) => {
filter.class_ids.contains(&((*class_id).into()))
}
Message::GameEvent(GameEventMessage { event, .. }) => {
has_search && event.event_type().as_str().contains(&filter.search)
}
Message::PacketEntities(PacketEntitiesMessage {
entities,
removed_entities,
..
}) => {
(removed_entities.contains(&filter.entity.into()) && !filter.has_entity_filter())
|| entities.iter().any(|entity| entity_matches(entity, filter))
}
Message::TempEntities(TempEntitiesMessage { events }) => events.iter().any(|event| {
filter
.class_ids
.contains(&(u16::from(event.class_id).into()))
}),
Message::GetCvarValue(GetCvarValueMessage { value, .. }) => {
has_search && value.contains(&filter.search)
}
_ => false,
}
}
fn string_entry_matches(entry: &StringTableEntry, filter: &SearchFilter) -> bool {
(!filter.search.is_empty())
&& entry
.text
.as_deref()
.map(|text| text.contains(&filter.search))
.unwrap_or_default()
}
fn entity_matches(entity: &PacketEntity, filter: &SearchFilter) -> bool {
if entity.entity_index != filter.entity && filter.entity != 0 {
return false;
}
if !filter.has_entity_filter() {
return true;
}
if filter
.class_ids
.contains(&u16::from(entity.server_class).into())
{
return true;
}
entity
.props
.iter()
.any(|prop| filter.prop_ids.contains(&prop.identifier.into()))
}

11
wasm/src/utils.rs Normal file
View file

@ -0,0 +1,11 @@
#[allow(dead_code)]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}