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

September 2018 game update #71

Merged
merged 9 commits into from
Oct 6, 2018
55 changes: 55 additions & 0 deletions screeps-game-api/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,3 +630,58 @@ macro_rules! mem_set {
compile_error!(concat!("Unexpected usage of mem_set! usage: ", stringify!($($not_valid)*)))
}
}

/// Taken from https://serde.rs/enum-number.html
macro_rules! enum_number {
($name:ident { $($variant:ident = $value:expr, )* }) => {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum $name {
$($variant = $value,)*
}

impl ::serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::Serializer,
{
// Serialize the enum as a u64.
serializer.serialize_u64(*self as u64)
}
}

impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
struct Visitor;

impl<'de> ::serde::de::Visitor<'de> for Visitor {
type Value = $name;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("positive integer")
}

fn visit_u64<E>(self, value: u64) -> Result<$name, E>
where
E: ::serde::de::Error,
{
// Rust does not come with a simple way of converting a
// number to an enum, so use a big `match`.
match value {
$( $value => Ok($name::$variant), )*
_ => Err(E::custom(
format!("unknown {} value: {}",
stringify!($name), value))),
}
}
}

// Deserialize the enum from a u64.
deserializer.deserialize_u64(Visitor)
}
}
}
}
164 changes: 138 additions & 26 deletions screeps-game-api/src/objects/impls/room.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{marker::PhantomData, mem, ops::Range};
use std::{fmt, marker::PhantomData, mem, ops::Range};

use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
use serde_json;
use stdweb::Reference;

Expand Down Expand Up @@ -105,8 +106,11 @@ impl Room {
}

pub fn get_event_log(&self) -> Vec<Event> {
let raw_event_log: String = js_unwrap!{@{self.as_ref()}.getEventLog(true)};
serde_json::from_str(&raw_event_log).expect("Malformed Event Log")
serde_json::from_str(&self.get_event_log_raw()).expect("Malformed Event Log")
}

pub fn get_event_log_raw(&self) -> String {
js_unwrap!{@{self.as_ref()}.getEventLog(true)}
}

pub fn get_position_at(&self, x: u32, y: u32) -> Option<RoomPosition> {
Expand Down Expand Up @@ -432,36 +436,148 @@ pub enum Path {

js_deserializable!{Path}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Event {
#[serde(flatten)]
pub event: EventType,
#[serde(rename = "objectId")]
pub object_id: String,
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(tag = "event", content = "data")]
impl<'de> Deserialize<'de> for Event {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "camelCase")]
enum Field {
Event,
ObjectId,
Data,
};

struct EventVisitor;

impl<'de> Visitor<'de> for EventVisitor {
type Value = Event;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Event")
}

fn visit_map<V>(self, mut map: V) -> Result<Event, V::Error>
where
V: MapAccess<'de>,
{
let mut event_type = None;
let mut obj_id = None;
let mut data = None;
let mut data_buffer: Option<serde_json::Value> = None;

while let Some(key) = map.next_key()? {
match key {
Field::Event => {
if event_type.is_some() {
return Err(de::Error::duplicate_field("event"));
}
event_type = Some(map.next_value()?);
}
Field::ObjectId => {
if obj_id.is_some() {
return Err(de::Error::duplicate_field("objectId"));
}
obj_id = Some(map.next_value()?);
}
Field::Data => {
if data.is_some() {
return Err(de::Error::duplicate_field("data"));
}

match event_type {
None => data_buffer = map.next_value()?,
Some(event_id) => {
data = match event_id {
1 => Some(EventType::Attack(map.next_value()?)),
2 => Some(EventType::ObjectDestroyed(map.next_value()?)),
3 => Some(EventType::AttackController),
4 => Some(EventType::Build(map.next_value()?)),
5 => Some(EventType::Harvest(map.next_value()?)),
6 => Some(EventType::Heal(map.next_value()?)),
7 => Some(EventType::Repair(map.next_value()?)),
8 => Some(EventType::ReserveController(map.next_value()?)),
9 => Some(EventType::UpgradeController(map.next_value()?)),
10 => Some(EventType::Exit(map.next_value()?)),
_ => {
return Err(de::Error::custom(format!(
"Event Type Unrecognized: {}",
event_id
)))
}
};
}
};
}
}
}

if data_buffer.is_some() && event_type.is_some() && data.is_none() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be able to remove the redundancy between these is_some calls and the unwrap() / if let below if we directly match on the values here. What would you think of something like this?

if data.is_none() {
    if let (Some(data_buffer), Some(event_type)) = (data_buffer, event_type) {
        ...
    }
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had avoided doing that because I didn't want to get 3 levels deep, but that fixes my concern nicely. Looks great!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

We might even be able to do

if let (Some(data_buffer), Some(event_type), None) = (data_buffer, event_type, data) {

but I'm not 100% sure rust will allow that since we're modifying data inside and lifetimes are lexical (can't wait for non-lexical lifetimes but that's a whole other topic).

let val: serde_json::Value = data_buffer.unwrap();
let err = |_| de::Error::custom("Can't parse Event data.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we include the display representation of the original error here? It'd be much more useful than a static string. Just something like de::Error::custom(format_args!("can't parse event data due to inner error {}", e)) could work? We should also avoid capitalization and an ending period as serde inserts this as part of a larger sentence (or at least that's what custom's documentation states?).

if let Some(event_id) = event_type {
data = match event_id {
1 => Some(EventType::Attack(serde_json::from_value(val).map_err(err)?)),
2 => Some(EventType::ObjectDestroyed(
serde_json::from_value(val).map_err(err)?,
)),
3 => Some(EventType::AttackController),
4 => Some(EventType::Build(serde_json::from_value(val).map_err(err)?)),
5 => Some(EventType::Harvest(
serde_json::from_value(val).map_err(err)?,
)),
6 => Some(EventType::Heal(serde_json::from_value(val).map_err(err)?)),
7 => Some(EventType::Repair(serde_json::from_value(val).map_err(err)?)),
8 => Some(EventType::ReserveController(
serde_json::from_value(val).map_err(err)?,
)),
9 => Some(EventType::UpgradeController(
serde_json::from_value(val).map_err(err)?,
)),
10 => Some(EventType::Exit(serde_json::from_value(val).map_err(err)?)),
_ => {
return Err(de::Error::custom(format!(
"Event Type Unrecognized: {}",
event_id
)))
}
};
}
}

let data = data.ok_or_else(|| de::Error::missing_field("data"))?;
let obj_id = obj_id.ok_or_else(|| de::Error::missing_field("objectId"))?;

Ok(Event {
event: data,
object_id: obj_id,
})
}
}

const FIELDS: &'static [&'static str] = &["event", "objectId", "data"];
deserializer.deserialize_struct("Event", FIELDS, EventVisitor)
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EventType {
#[serde(rename = "1")]
Attack(AttackEvent),
#[serde(rename = "2")]
ObjectDestroyed(ObjectDestroyedEvent),
#[serde(rename = "3")]
AttackController,
#[serde(rename = "4")]
Build(BuildEvent),
#[serde(rename = "5")]
Harvest(HarvestEvent),
#[serde(rename = "6")]
Heal(HealEvent),
#[serde(rename = "7")]
Repair(RepairEvent),
#[serde(rename = "8")]
ReserveController(ReserveControllerEvent),
#[serde(rename = "9")]
UpgradeController(UpgradeControllerEvent),
#[serde(rename = "10")]
Exit(ExitEvent),
}

Expand All @@ -473,16 +589,14 @@ pub struct AttackEvent {
pub attack_type: AttackType,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
#[repr(u32)]
pub enum AttackType {
enum_number!(AttackType {
Melee = 1,
Ranged = 2,
RangedMass = 3,
Dismantle = 4,
HitBack = 5,
Nuke = 6,
}
});

#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct ObjectDestroyedEvent {
Expand Down Expand Up @@ -513,12 +627,10 @@ pub struct HealEvent {
pub heal_type: HealType,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
#[repr(u32)]
pub enum HealType {
enum_number!(HealType {
Melee = 1,
Ranged = 2,
}
});

#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down