pub const ABI_DESCRIBE_VERSION: u32 = 1;
pub const CORE_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const ENGINE_GAME_OPS: &[&str] = &["debug.json"];
pub const ENGINE_CLIENT_OPS: &[&str] = &["debug.json"];
pub fn describe_json(engine_ops: &[&str], game_ops: &[&str]) -> String {
let mut ops: Vec<&str> = engine_ops.to_vec();
for op in game_ops {
if !ops.contains(op) {
ops.push(op);
}
}
::serde_json::json!({
"abi": ABI_DESCRIBE_VERSION,
"core": CORE_VERSION,
"ops": ops,
})
.to_string()
}
pub fn dispatch_result(out: Option<Vec<u8>>) -> Vec<u8> {
match out {
None => Vec::new(),
Some(bytes) if bytes.is_empty() => vec![0x00],
Some(bytes) => bytes,
}
}
#[macro_export]
macro_rules! export_game_core_abi {
($GameCoreTy:ty) => {
#[::wasm_bindgen::prelude::wasm_bindgen]
impl $GameCoreTy {
pub fn load_map(&mut self, map_json: &str) -> Result<(), ::wasm_bindgen::JsError> {
self.state
.load_map(map_json)
.map_err(|e| ::wasm_bindgen::JsError::new(&e))
}
pub fn map_info(&self) -> String {
self.state.map_info_json()
}
pub fn spawn_actor(
&mut self,
game_id: u32,
model: &str,
team_id: u8,
x: f32,
y: f32,
angle_deg: f32,
) -> Result<(), ::wasm_bindgen::JsError> {
self.state
.spawn_actor(game_id, model, team_id, x, y, angle_deg)
.map_err(|e| ::wasm_bindgen::JsError::new(&e))
}
pub fn remove_actor(&mut self, game_id: u32) {
self.state.remove_actor(game_id);
}
pub fn reset_actor(
&mut self,
game_id: u32,
team_id: u8,
x: f32,
y: f32,
angle_deg: f32,
) {
self.state.reset_actor(game_id, team_id, x, y, angle_deg);
}
pub fn reset_all_vitals(&mut self) {
self.state.reset_all_vitals();
}
pub fn spawn_scripted_actor(
&mut self,
game_id: u32,
model: &str,
team_id: u8,
x: f32,
y: f32,
angle_deg: f32,
) -> Result<(), ::wasm_bindgen::JsError> {
self.state
.spawn_scripted_actor(game_id, model, team_id, x, y, angle_deg)
.map_err(|e| ::wasm_bindgen::JsError::new(&e))
}
pub fn remove_scripted_actor(&mut self, game_id: u32) {
self.state.remove_scripted_actor(game_id);
}
pub fn apply_input(&mut self, game_id: u32, seq: u32, action: &str, key_name: &str) {
self.state.apply_input(game_id, seq, action, key_name);
}
pub fn apply_aim(&mut self, game_id: u32, seq: u32, x: f32, y: f32, flags: u32) {
self.state.apply_aim(game_id, seq, x, y, flags);
}
pub fn step(&mut self, dt: f32) {
self.state.step(dt);
}
pub fn take_events(&mut self) -> String {
self.state.take_events_json()
}
pub fn last_input_seq(&self, game_id: u32) -> u32 {
self.state.last_input_seq(game_id)
}
pub fn is_alive(&self, game_id: u32) -> bool {
self.state.is_alive(game_id)
}
pub fn position_of(&self, game_id: u32) -> Vec<f32> {
self.state
.actor_position(game_id)
.map(|p| p.to_vec())
.unwrap_or_default()
}
pub fn players_data(&self) -> String {
self.state.players_json()
}
pub fn alive_players(&self) -> Vec<f32> {
self.state.alive_players_flat()
}
pub fn pack_body(&mut self) -> Result<(), ::wasm_bindgen::JsError> {
let blocks = self.state.build_snapshot_blocks();
self.packer
.pack_body(&blocks)
.map_err(|e| ::wasm_bindgen::JsError::new(&e))
}
#[allow(clippy::too_many_arguments)]
pub fn pack_frame(
&mut self,
server_time: f64,
seq: u32,
has_camera: bool,
camera_x: f32,
camera_y: f32,
force_reset: bool,
shake: Option<String>,
player_id: i32,
) -> usize {
let camera = has_camera.then_some($crate::snapshot::CameraData {
x: camera_x,
y: camera_y,
force_reset,
shake,
});
let player = if player_id >= 0 {
let game_id = player_id as u32;
self.state.prediction_state(game_id).map(|(state, centering)| {
$crate::snapshot::PlayerBlock {
game_id: game_id as u8,
input_seq: self.state.last_input_seq(game_id),
state,
centering,
}
})
} else {
None
};
self.packer
.pack_frame(server_time, seq, camera.as_ref(), player.as_ref())
.len()
}
pub fn body_has_events(&self) -> bool {
self.state.body_has_events()
}
pub fn frame_ptr(&self) -> *const u8 {
self.packer.frame_bytes().as_ptr()
}
pub fn frame_bytes(&self) -> Vec<u8> {
self.packer.frame_bytes().to_vec()
}
pub fn remove_players_and_shots(&mut self) -> String {
::serde_json::to_string(&self.state.remove_players_and_shots())
.unwrap_or_else(|_| "[]".to_string())
}
pub fn clear(&mut self) {
self.state.clear();
}
pub fn debug_json(&self) -> String {
self.state.debug_json()
}
pub fn serialize_state(&self) -> Result<Vec<u8>, ::wasm_bindgen::JsError> {
self.state
.serialize_state()
.map_err(|e| ::wasm_bindgen::JsError::new(&e))
}
pub fn deserialize_state(&mut self, data: &[u8]) -> Result<(), ::wasm_bindgen::JsError> {
self.state
.deserialize_state(data)
.map_err(|e| ::wasm_bindgen::JsError::new(&e))
}
pub fn abi_describe(&self) -> String {
self.state.abi_describe()
}
pub fn dispatch(&mut self, op: &str, payload: &[u8]) -> Vec<u8> {
self.state.dispatch(op, payload)
}
}
};
}
#[macro_export]
macro_rules! export_client_core_abi {
($ClientCoreTy:ty) => {
#[::wasm_bindgen::prelude::wasm_bindgen]
impl $ClientCoreTy {
pub fn push_frame(&mut self, data: &[u8], local_now: f64) -> bool {
self.state.push_frame(data, local_now)
}
pub fn my_game_id(&self) -> i32 {
self.state.my_game_id().map(|id| id as i32).unwrap_or(-1)
}
pub fn offset(&self) -> f64 {
self.state.offset().unwrap_or(f64::NAN)
}
pub fn sample(&mut self, local_now: f64) -> usize {
self.state.sample(local_now)
}
pub fn hot_ptr(&self) -> *const f32 {
self.state.hot().as_ptr()
}
pub fn hot_values(&self) -> Vec<f32> {
self.state.hot().to_vec()
}
pub fn take_frames(&mut self) -> String {
self.state.take_frames()
}
pub fn apply_input(&mut self, action: &str, key_name: &str, local_now: f64) {
self.state.apply_input(action, key_name, local_now);
}
pub fn apply_aim(&mut self, x: f32, y: f32, flags: u32, local_now: f64) {
self.state.apply_aim(x, y, flags, local_now);
}
pub fn set_active(&mut self, active: bool) {
self.state.set_active(active);
}
pub fn set_map(&mut self, map_json: &str) -> Result<(), ::wasm_bindgen::JsError> {
self.state
.set_map(map_json)
.map_err(|e| ::wasm_bindgen::JsError::new(&e))
}
pub fn reset(&mut self) {
self.state.reset();
}
pub fn resync(&mut self) {
self.state.resync();
}
pub fn debug_json(&self) -> String {
self.state.debug_json()
}
pub fn take_divergence(&mut self) -> String {
self.state.take_divergence()
}
pub fn decode_frame(&self, data: &[u8]) -> String {
self.state.decode_frame(data)
}
pub fn abi_describe(&self) -> String {
self.state.abi_describe()
}
pub fn dispatch(&mut self, op: &str, payload: &[u8]) -> Vec<u8> {
self.state.dispatch(op, payload)
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn describe_carries_engine_crate_version_and_ops() {
let json: ::serde_json::Value =
::serde_json::from_str(&describe_json(ENGINE_GAME_OPS, &["snakes.grow"])).unwrap();
assert_eq!(json["abi"], ABI_DESCRIBE_VERSION);
assert_eq!(json["core"], env!("CARGO_PKG_VERSION"));
assert_eq!(
json["ops"],
::serde_json::json!(["debug.json", "snakes.grow"])
);
}
#[test]
fn describe_does_not_repeat_an_engine_op() {
let json: ::serde_json::Value =
::serde_json::from_str(&describe_json(ENGINE_GAME_OPS, &["debug.json"])).unwrap();
assert_eq!(json["ops"], ::serde_json::json!(["debug.json"]));
}
#[test]
fn dispatch_result_separates_unhandled_from_empty_answer() {
assert_eq!(dispatch_result(None), Vec::<u8>::new());
assert_eq!(dispatch_result(Some(Vec::new())), vec![0x00]);
assert_eq!(dispatch_result(Some(vec![1, 2])), vec![1, 2]);
}
}