Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: game state condition queries #215

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
444 changes: 139 additions & 305 deletions src/node/Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ crate-type = ["cdylib"]
ahash = "0.8.3"
memmap2 = "0.9.4"
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "2.12.2", default-features = false, features = ["napi4","serde-json"] }
napi = { version = "2.12.2", default-features = false, features = ["napi6","serde-json"] }
napi-derive = "2.12.2"
serde_json = "1.0.96"
protobuf-support = "3.3.0"
Expand Down
22 changes: 14 additions & 8 deletions src/node/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@

/* auto-generated by NAPI-RS */

export function parseChatMessages(path: string): any
export function listGameEvents(path: string): any
export function parseGrenades(path: string): any
export function parseHeader(path: string): any
export function parseEvent(path: string, eventName: string, playerExtra?: Array<string> | undefined | null, otherExtra?: Array<string> | undefined | null): any
export function parseEvents(path: string, eventNames?: Array<string> | undefined | null, playerExtra?: Array<string> | undefined | null, otherExtra?: Array<string> | undefined | null): any
export function parseTicks(path: string, wantedProps: Array<string>, wantedTicks?: Array<number> | undefined | null, structOfArrays?: boolean | undefined | null): any
export function parsePlayerInfo(path: string): any
export function parseVoice(pathOrBuf: string | Buffer): Record<string, Array<number>>
export function listGameEvents(pathOrBuf: string | Buffer): any
export function parseGrenades(pathOrBuf: string | Buffer): any
export function parseHeader(pathOrBuf: string | Buffer): any
export function parseEvent(pathOrBuf: string | Buffer, eventName: string, playerExtra?: Array<string> | undefined | null, otherExtra?: Array<string> | undefined | null): any
export function parseEvents(pathOrBuf: string | Buffer, eventNames?: Array<string> | undefined | null, playerExtra?: Array<string> | undefined | null, otherExtra?: Array<string> | undefined | null): any
export function parseTicks(pathOrBuf: string | Buffer, wantedProps: Array<string>, wantedTicks?: Array<number> | undefined | null, wantedPlayers?: Array<string> | undefined | null, structOfArrays?: boolean | undefined | null, orderBySteamid?: boolean | undefined | null, propStates?: Array<WantedPropState> | undefined | null): any
export function parsePlayerInfo(pathOrBuf: string | Buffer): any
export function parsePlayerSkins(pathOrBuf: string | Buffer): any
export declare class JsVariant { }
export declare class WantedPropState {
prop: string
state: JsVariant
}
71 changes: 66 additions & 5 deletions src/node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,72 @@ switch (platform) {
}
break
case 'arm':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'demoparser2.linux-arm-musleabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./demoparser2.linux-arm-musleabihf.node')
} else {
nativeBinding = require('@laihoe/demoparser2-linux-arm-musleabihf')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'demoparser2.linux-arm-gnueabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./demoparser2.linux-arm-gnueabihf.node')
} else {
nativeBinding = require('@laihoe/demoparser2-linux-arm-gnueabihf')
}
} catch (e) {
loadError = e
}
}
break
case 'riscv64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'demoparser2.linux-riscv64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./demoparser2.linux-riscv64-musl.node')
} else {
nativeBinding = require('@laihoe/demoparser2-linux-riscv64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'demoparser2.linux-riscv64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./demoparser2.linux-riscv64-gnu.node')
} else {
nativeBinding = require('@laihoe/demoparser2-linux-riscv64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 's390x':
localFileExisted = existsSync(
join(__dirname, 'demoparser2.linux-arm-gnueabihf.node')
join(__dirname, 'demoparser2.linux-s390x-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./demoparser2.linux-arm-gnueabihf.node')
nativeBinding = require('./demoparser2.linux-s390x-gnu.node')
} else {
nativeBinding = require('@laihoe/demoparser2-linux-arm-gnueabihf')
nativeBinding = require('@laihoe/demoparser2-linux-s390x-gnu')
}
} catch (e) {
loadError = e
Expand All @@ -252,13 +310,16 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { parseChatMessages, listGameEvents, parseGrenades, parseHeader, parseEvent, parseEvents, parseTicks, parsePlayerInfo } = nativeBinding
const { JsVariant, WantedPropState, parseVoice, listGameEvents, parseGrenades, parseHeader, parseEvent, parseEvents, parseTicks, parsePlayerInfo, parsePlayerSkins } = nativeBinding

module.exports.parseChatMessages = parseChatMessages
module.exports.JsVariant = JsVariant
module.exports.WantedPropState = WantedPropState
module.exports.parseVoice = parseVoice
module.exports.listGameEvents = listGameEvents
module.exports.parseGrenades = parseGrenades
module.exports.parseHeader = parseHeader
module.exports.parseEvent = parseEvent
module.exports.parseEvents = parseEvents
module.exports.parseTicks = parseTicks
module.exports.parsePlayerInfo = parsePlayerInfo
module.exports.parsePlayerSkins = parsePlayerSkins
133 changes: 133 additions & 0 deletions src/node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use ahash::AHashMap;
use memmap2::MmapOptions;
use napi::bindgen_prelude::*;
use napi::Either;
use napi::JsBigInt;
use napi::JsUnknown;
use parser::first_pass::parser_settings::rm_map_user_friendly_names;
use parser::first_pass::parser_settings::rm_user_friendly_names;
use parser::first_pass::parser_settings::ParserInputs;
use parser::parse_demo::DemoOutput;
Expand All @@ -14,13 +17,116 @@ use parser::second_pass::parser_settings::create_huffman_lookup_table;
use parser::second_pass::variants::soa_to_aos;
use parser::second_pass::variants::BytesVariant;
use parser::second_pass::variants::OutputSerdeHelperStruct;
use parser::second_pass::variants::Variant;
use parser::second_pass::voice_data::convert_voice_data_to_wav;
use serde_json::Value;
use std::collections::HashMap;
use std::fs::File;
use std::hash::RandomState;
use std::result::Result;

#[napi]
#[derive(Clone)]
pub struct JsVariant(Variant);

impl FromNapiValue for JsVariant {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> napi::Result<Self> {
let js_unknown = JsUnknown::from_napi_value(env, napi_val)?;

match js_unknown.get_type() {
Ok(js_unknown_type) => {
if js_unknown_type == ValueType::Boolean {
if let Ok(val) = js_unknown.coerce_to_bool() {
Ok(JsVariant(Variant::Bool(val.get_value()?)))
} else {
Err(Error::new(
Status::InvalidArg,
"Unspported Boolean type for Variant".to_owned(),
))
}
} else if js_unknown_type == ValueType::String {
if let Ok(val) = js_unknown.coerce_to_string() {
Ok(JsVariant(Variant::String(val.into_utf8()?.into_owned()?)))
} else {
Err(Error::new(
Status::InvalidArg,
"Unsupported String for Variant".to_owned(),
))
}
} else if js_unknown_type == ValueType::Number {
if let Ok(val) = js_unknown.coerce_to_number() {
let num = val.get_double()?;
if num.fract() == 0.0 {
if num >= u8::MIN as f64 && num <= u8::MAX as f64 {
Ok(JsVariant(Variant::U8(num as u8)))
} else if let Ok(val) = val.get_int32() {
let int32_val = val;
if int32_val >= i16::MIN as i32 && int32_val <= i16::MAX as i32 {
Ok(JsVariant(Variant::I16(int32_val as i16)))
} else {
Ok(JsVariant(Variant::I32(int32_val)))
}
} else if let Ok(val) = val.get_uint32() {
Ok(JsVariant(Variant::U32(val)))
} else {
Err(Error::new(
Status::InvalidArg,
"Unsupported number type".to_owned(),
))
}
} else {
Ok(JsVariant(Variant::F32(num as f32)))
}
} else {
Err(Error::new(
Status::InvalidArg,
"Unsupported number type".to_owned(),
))
}
} else if js_unknown_type == ValueType::BigInt {
let bigint_val = js_unknown.cast::<JsBigInt>();
match bigint_val.get_u64() {
Ok((val, true)) => Ok(JsVariant(Variant::U64(val))),
_ => Err(Error::new(
Status::InvalidArg,
"Unsupported number type".to_owned(),
)),
}
} else {
Err(Error::new(
Status::InvalidArg,
"Unspported type for Variant".to_owned(),
))
}
}
_ => Err(Error::new(
Status::InvalidArg,
"Unspported type for Variant".to_owned(),
)),
}
}
}

#[napi]
pub struct WantedPropState {
pub prop: String,
pub state: JsVariant,
}

impl FromNapiValue for WantedPropState {
unsafe fn from_napi_value(
env: sys::napi_env,
napi_val: napi::sys::napi_value,
) -> napi::Result<Self> {
let obj: Object = Object::from_napi_value(env, napi_val)?;

let prop: String = obj.get_named_property("prop")?;
let state: JsVariant = obj.get_named_property("state")?;

Ok(WantedPropState { prop, state })
}
}

fn parse_demo(bytes: BytesVariant, parser: &mut Parser) -> Result<DemoOutput, Error> {
match bytes {
BytesVariant::Mmap(m) => match parser.parse_demo(&m) {
Expand All @@ -42,6 +148,7 @@ pub fn parse_voice(path_or_buf: Either<String, Buffer>) -> napi::Result<HashMap<
wanted_other_props: vec![],
wanted_events: vec![],
wanted_ticks: vec![],
wanted_prop_states: AHashMap::default(),
real_name_to_og_name: AHashMap::default(),
parse_ents: false,
parse_projectiles: false,
Expand Down Expand Up @@ -74,6 +181,7 @@ pub fn list_game_events(path_or_buf: Either<String, Buffer>) -> napi::Result<Val
real_name_to_og_name: AHashMap::default(),
wanted_player_props: vec![],
wanted_other_props: vec![],
wanted_prop_states: AHashMap::default(),
wanted_events: vec!["all".to_string()],
parse_ents: false,
wanted_ticks: vec![],
Expand Down Expand Up @@ -106,6 +214,7 @@ pub fn parse_grenades(path_or_buf: Either<String, Buffer>) -> napi::Result<Value
wanted_player_props: vec![],
wanted_other_props: vec![],
wanted_events: vec![],
wanted_prop_states: AHashMap::default(),
parse_ents: true,
wanted_ticks: vec![],
parse_projectiles: true,
Expand Down Expand Up @@ -134,6 +243,7 @@ pub fn parse_header(path_or_buf: Either<String, Buffer>) -> napi::Result<Value>
wanted_players: vec![],
wanted_player_props: vec![],
wanted_other_props: vec![],
wanted_prop_states: AHashMap::default(),
wanted_events: vec![],
parse_ents: false,
wanted_ticks: vec![],
Expand Down Expand Up @@ -198,6 +308,7 @@ pub fn parse_event(
wanted_players: vec![],
wanted_player_props: real_names_player.clone(),
wanted_other_props: real_other_props,
wanted_prop_states: AHashMap::default(),
wanted_events: vec![event_name],
parse_ents: true,
wanted_ticks: vec![],
Expand Down Expand Up @@ -260,6 +371,7 @@ pub fn parse_events(
wanted_players: vec![],
wanted_player_props: real_names_player.clone(),
wanted_other_props: real_other_props.clone(),
wanted_prop_states: AHashMap::default(),
wanted_events: event_names,
parse_ents: true,
wanted_ticks: vec![],
Expand Down Expand Up @@ -287,6 +399,7 @@ pub fn parse_ticks(
wanted_players: Option<Vec<String>>,
struct_of_arrays: Option<bool>,
order_by_steamid: Option<bool>,
prop_states: Option<Vec<WantedPropState>>,
) -> napi::Result<Value> {
let mut real_names = match rm_user_friendly_names(&wanted_props) {
Ok(names) => names,
Expand All @@ -296,6 +409,17 @@ pub fn parse_ticks(
Some(v) => v.iter().map(|x| x.parse::<u64>().unwrap_or(0)).collect(),
None => vec![],
};
let wanted_prop_states: AHashMap<String, Variant> = prop_states
.unwrap_or_default()
.into_iter()
.map(|prop| (prop.prop.clone(), prop.state.0.clone()))
.collect();

let real_wanted_prop_states = rm_map_user_friendly_names(&wanted_prop_states);
let real_wanted_prop_states = match real_wanted_prop_states {
Ok(real_wanted_prop_states) => real_wanted_prop_states,
Err(e) => return Err(Error::new(Status::InvalidArg, format!("{}", e).to_owned())),
};

let bytes = resolve_byte_type(path_or_buf)?;
let huf = create_huffman_lookup_table();
Expand All @@ -304,6 +428,12 @@ pub fn parse_ticks(
for (real_name, user_friendly_name) in real_names.iter().zip(&wanted_props) {
real_name_to_og_name.insert(real_name.clone(), user_friendly_name.clone());
}
for (real_name, user_friendly_name) in real_wanted_prop_states
.keys()
.zip(wanted_prop_states.keys())
{
real_name_to_og_name.insert(real_name.clone(), user_friendly_name.clone());
}

let wanted_ticks = match wanted_ticks {
Some(t) => t,
Expand All @@ -320,6 +450,7 @@ pub fn parse_ticks(
wanted_player_props: real_names.clone(),
wanted_other_props: vec![],
wanted_events: vec![],
wanted_prop_states: real_wanted_prop_states,
parse_ents: true,
wanted_ticks: wanted_ticks,
parse_projectiles: false,
Expand Down Expand Up @@ -391,6 +522,7 @@ pub fn parse_player_info(path_or_buf: Either<String, Buffer>) -> napi::Result<Va
real_name_to_og_name: AHashMap::default(),
wanted_player_props: vec![],
wanted_other_props: vec![],
wanted_prop_states: AHashMap::default(),
wanted_events: vec![],
parse_ents: false,
wanted_ticks: vec![],
Expand Down Expand Up @@ -420,6 +552,7 @@ pub fn parse_player_skins(path_or_buf: Either<String, Buffer>) -> napi::Result<V
real_name_to_og_name: AHashMap::default(),
wanted_player_props: vec![],
wanted_other_props: vec![],
wanted_prop_states: AHashMap::default(),
wanted_events: vec![],
parse_ents: true,
wanted_ticks: vec![],
Expand Down
8 changes: 4 additions & 4 deletions src/parser/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading