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

Refactor string enums to integer representations, add optional snippet optimizations #512

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ seasonal-season-5 = []
# enable compatibility with the sim environment for positions
sim = []

snippets = []

# Enable unsafe conversions of return codes with undefined behavior when values
# aren't in the expected range
unsafe-return-conversion = []
26 changes: 26 additions & 0 deletions js/object_id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports.object_id_into_uint8array = function(id, arr) {
// passed array is exactly 16 bytes -- 12 for id, 3 blank, 1 for string pad length
// initialize all of them so that we can assume they're initialized on the rust side
const padding = id.length;
let skip_bytes = 12 - Math.ceil(padding / 2);

for (let byte = 0; byte < skip_bytes; byte++) {
arr[byte] = 0;
}

const offset = padding % 2;
// if there's an odd number of characters, grab one for the next byte
if (offset === 1) {
arr[skip_bytes] = parseInt(id.substr(0, 1), 16)
skip_bytes++;
}

for (let byte = 0; byte < 12 - skip_bytes; byte++) {
arr[byte + skip_bytes] = parseInt(id.substr(byte * 2 + offset, 2), 16)
}

arr[12] = 0;
arr[13] = 0;
arr[14] = 0;
arr[15] = padding;
}
9 changes: 9 additions & 0 deletions js/part.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports.bodypart_to_part_num = function(part_str_to_num_map, bodypart) {
return part_str_to_num_map.get(bodypart.type)
}

module.exports.part_nums_to_str_array = function(part_num_to_str_map, body_num_array) {
// this is a Uint8Array and its map can't produce strings as-is,
// spread it first so the map can result in an array with constant strings
return [...body_num_array].map((v) => part_num_to_str_map.get(v));
}
5 changes: 3 additions & 2 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,14 @@ pub mod extra;
pub mod find;
pub mod look;
mod numbers;
mod part;
mod recipes;
pub mod seasonal;
mod small_enums;
mod types;

pub use self::{
extra::*, find::FindConstant, look::LookConstant, numbers::*, recipes::FactoryRecipe,
extra::*, find::FindConstant, look::LookConstant, numbers::*, part::*, recipes::FactoryRecipe,
small_enums::*, types::*,
};

Expand All @@ -95,7 +96,7 @@ pub mod creep {
MAX_CREEP_SIZE, RANGED_HEAL_POWER, REPAIR_COST, REPAIR_POWER, SPAWN_RENEW_RATIO,
UPGRADE_CONTROLLER_POWER,
},
small_enums::Part,
part::Part,
};
}

Expand Down
4 changes: 2 additions & 2 deletions src/constants/numbers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use super::types::{ResourceType, StructureType};

// OBSTACLE_OBJECT_TYPES not yet implemented

// BODYPART_COST defined in `small_enums.rs`
// body parts and BODYPART_COST defined in `part.rs`

// WORLD_WIDTH/HEIGHT deprecated, not implemented

Expand Down Expand Up @@ -817,7 +817,7 @@ pub const fn stronghold_rampart_hits(core_level: u32) -> Option<u32> {
pub const STRONGHOLD_DECAY_TICKS: u32 = 75_000;

// POWER_INFO not yet implemented
// BODYPARTS_ALL implemented via Sequence trait in `small_enums.rs`
// BODYPARTS_ALL implemented via Sequence trait in `part.rs`
// RESOURCES_ALL implemented via Sequence trait in `types.rs`
// COLORS_ALL implemented via Sequence trait in `small_enums.rs`
// INTERSHARD_RESOURCES defined in `types.rs`
Expand Down
181 changes: 181 additions & 0 deletions src/constants/part.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use enum_iterator::Sequence;
use js_sys::{Array, JsString, Map};
use num_derive::FromPrimitive;
use serde_repr::{Deserialize_repr, Serialize_repr};
use wasm_bindgen::prelude::*;

use crate::objects::BodyPart;

/// Translates body part type and `BODYPARTS_ALL` constants
#[wasm_bindgen]
#[derive(
Debug,
PartialEq,
Eq,
Clone,
Copy,
Hash,
FromPrimitive,
Serialize_repr,
Deserialize_repr,
Sequence,
)]
#[repr(u8)]
pub enum Part {
Move = 0,
Work = 1,
Carry = 2,
Attack = 3,
RangedAttack = 4,
Tough = 5,
Heal = 6,
Claim = 7,
}

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = MOVE)]
static MOVE_JS: JsString;
#[wasm_bindgen(js_name = WORK)]
static WORK_JS: JsString;
#[wasm_bindgen(js_name = CARRY)]
static CARRY_JS: JsString;
#[wasm_bindgen(js_name = ATTACK)]
static ATTACK_JS: JsString;
#[wasm_bindgen(js_name = RANGED_ATTACK)]
static RANGED_ATTACK_JS: JsString;
#[wasm_bindgen(js_name = TOUGH)]
static TOUGH_JS: JsString;
#[wasm_bindgen(js_name = HEAL)]
static HEAL_JS: JsString;
#[wasm_bindgen(js_name = CLAIM)]
static CLAIM_JS: JsString;
}

thread_local! {
static PART_NUM_TO_STR_MAP: js_sys::Map = {
js_sys::Map::new()
.set(&JsValue::from(Part::Move as u8), &MOVE_JS)
.set(&JsValue::from(Part::Work as u8), &WORK_JS)
.set(&JsValue::from(Part::Carry as u8), &CARRY_JS)
.set(&JsValue::from(Part::Attack as u8), &ATTACK_JS)
.set(&JsValue::from(Part::RangedAttack as u8), &RANGED_ATTACK_JS)
.set(&JsValue::from(Part::Tough as u8), &TOUGH_JS)
.set(&JsValue::from(Part::Heal as u8), &HEAL_JS)
.set(&JsValue::from(Part::Claim as u8), &CLAIM_JS)
};

static PART_STR_TO_NUM_MAP: js_sys::Map = {
js_sys::Map::new()
.set(&MOVE_JS, &JsValue::from(Part::Move as u8))
.set(&WORK_JS, &JsValue::from(Part::Work as u8))
.set(&CARRY_JS, &JsValue::from(Part::Carry as u8))
.set(&ATTACK_JS, &JsValue::from(Part::Attack as u8))
.set(&RANGED_ATTACK_JS, &JsValue::from(Part::RangedAttack as u8))
.set(&TOUGH_JS, &JsValue::from(Part::Tough as u8))
.set(&HEAL_JS, &JsValue::from(Part::Heal as u8))
.set(&CLAIM_JS, &JsValue::from(Part::Claim as u8))
};
}

#[cfg(feature = "snippets")]
#[wasm_bindgen(module = "/js/part.js")]
extern "C" {
fn bodypart_to_part_num(map: &Map, body_part: &BodyPart) -> Part;
fn part_nums_to_str_array(map: &Map, part_array: &[u8]) -> Array;
}

#[cfg(not(feature = "snippets"))]
fn bodypart_to_part_num(map: &Map, body_part: &BodyPart) -> Part {
use num_traits::FromPrimitive;

let n = map.get(&body_part.part_jsvalue()).as_f64().expect("number") as u8;
Part::from_u8(n).expect("known part")
}

#[cfg(not(feature = "snippets"))]
fn part_nums_to_str_array(map: &Map, part_array: &[u8]) -> Array {
let array = Array::new();
for part_num in part_array {
array.push(&map.get(&JsValue::from(*part_num)));
}
array
}

impl Part {
/// Translates the `BODYPART_COST` constant.
#[inline]
pub const fn cost(self) -> u32 {
match self {
Part::Move => 50,
Part::Work => 100,
Part::Carry => 50,
Part::Attack => 80,
Part::RangedAttack => 150,
Part::Tough => 10,
Part::Heal => 250,
Part::Claim => 600,
}
}

pub(crate) fn slice_to_js_array(parts: &[Self]) -> Array {
PART_NUM_TO_STR_MAP.with(|map| {
// SAFETY: &[Part] contains u8 values because it's repr(u8), safe to transmute
// to &[u8]
part_nums_to_str_array(map, unsafe { std::mem::transmute::<&[Part], &[u8]>(parts) })
})
}

pub(crate) fn from_bodypart(body_part: &BodyPart) -> Self {
PART_STR_TO_NUM_MAP.with(|map| bodypart_to_part_num(map, body_part))
}
}

#[cfg(test)]
mod test {
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;

use super::{part_nums_to_str_array, Part};

thread_local! {
static TEST_PART_NUM_TO_STR_MAP: js_sys::Map = {
js_sys::Map::new()
.set(&JsValue::from(Part::Move as u8), &JsValue::from_str("move"))
.set(&JsValue::from(Part::Work as u8), &JsValue::from_str("work"))
.set(&JsValue::from(Part::Carry as u8), &JsValue::from_str("carry"))
.set(&JsValue::from(Part::Attack as u8), &JsValue::from_str("attack"))
.set(&JsValue::from(Part::RangedAttack as u8), &JsValue::from_str("ranged_attack"))
.set(&JsValue::from(Part::Tough as u8), &JsValue::from_str("tough"))
.set(&JsValue::from(Part::Heal as u8), &JsValue::from_str("heal"))
.set(&JsValue::from(Part::Claim as u8), &JsValue::from_str("claim"))
};

static TEST_PART_STR_TO_NUM_MAP: js_sys::Map = {
js_sys::Map::new()
.set(&JsValue::from_str("move"), &JsValue::from(Part::Move as u8))
.set(&JsValue::from_str("work"), &JsValue::from(Part::Work as u8))
.set(&JsValue::from_str("carry"), &JsValue::from(Part::Carry as u8))
.set(&JsValue::from_str("attack"), &JsValue::from(Part::Attack as u8))
.set(&JsValue::from_str("ranged_attack"), &JsValue::from(Part::RangedAttack as u8))
.set(&JsValue::from_str("tough"), &JsValue::from(Part::Tough as u8))
.set(&JsValue::from_str("heal"), &JsValue::from(Part::Heal as u8))
.set(&JsValue::from_str("claim"), &JsValue::from(Part::Claim as u8))
};
}

#[wasm_bindgen_test]
pub fn parts_to_array() {
let body = [Part::Work, Part::Carry, Part::Move, Part::Move].as_slice();
let array = TEST_PART_NUM_TO_STR_MAP.with(|map| {
// SAFETY: &[Part] contains u8 values because it's repr(u8), safe to transmute
// to &[u8]
part_nums_to_str_array(map, unsafe { std::mem::transmute(body) })
});
assert_eq!(array.length(), 4);
assert_eq!(array.get(0), "work");
assert_eq!(array.get(1), "carry");
assert_eq!(array.get(2), "move");
assert_eq!(array.get(3), "move");
}
}
44 changes: 2 additions & 42 deletions src/constants/small_enums.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
//! Various constants translated as small enums.
use std::{borrow::Cow, fmt, slice::Iter};
use std::{fmt, slice::Iter};

use enum_iterator::Sequence;
use js_sys::JsString;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use serde::{
de::{Error as _, Unexpected},
Deserialize, Serialize,
};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use wasm_bindgen::prelude::*;

use super::{macros::named_enum_serialize_deserialize, InvalidConstantString};
use crate::{
constants::find::{Exit, Find},
prelude::*,
Expand Down Expand Up @@ -459,42 +455,6 @@ impl Terrain {
}
}

/// Translates body part type and `BODYPARTS_ALL` constants
#[wasm_bindgen]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Sequence)]
pub enum Part {
Move = "move",
Work = "work",
Carry = "carry",
Attack = "attack",
RangedAttack = "ranged_attack",
Tough = "tough",
Heal = "heal",
Claim = "claim",
}

named_enum_serialize_deserialize!(Part);

impl Part {
/// Translates the `BODYPART_COST` constant.
#[inline]
pub const fn cost(self) -> u32 {
match self {
Part::Move => 50,
Part::Work => 100,
Part::Carry => 50,
Part::Attack => 80,
Part::RangedAttack => 150,
Part::Tough => 10,
Part::Heal => 250,
Part::Claim => 600,
// I guess bindgen is adding a `#[non_exhaustive]` onto the enum and forcing us to do
// this:
_ => 0,
}
}
}

/// Translates the `DENSITY_*` constants.
#[wasm_bindgen]
#[derive(
Expand Down
2 changes: 1 addition & 1 deletion src/local/object_id/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl Serialize for RawObjectId {

struct RawObjectIdVisitor;

impl<'de> Visitor<'de> for RawObjectIdVisitor {
impl Visitor<'_> for RawObjectIdVisitor {
type Value = RawObjectId;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
Expand Down
2 changes: 1 addition & 1 deletion src/local/room_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ mod serde {

struct RoomNameVisitor;

impl<'de> Visitor<'de> for RoomNameVisitor {
impl Visitor<'_> for RoomNameVisitor {
type Value = RoomName;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Expand Down
8 changes: 7 additions & 1 deletion src/objects/impls/creep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,8 +679,14 @@ extern "C" {
pub fn boost(this: &BodyPart) -> Option<ResourceType>;

#[wasm_bindgen(method, getter = type)]
pub fn part(this: &BodyPart) -> Part;
pub(crate) fn part_jsvalue(this: &BodyPart) -> JsValue;

#[wasm_bindgen(method, getter)]
pub fn hits(this: &BodyPart) -> u32;
}

impl BodyPart {
pub fn part(&self) -> Part {
Part::from_bodypart(self)
}
}
Loading