Skip to content

Switch to lodash for memory lookup #22

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

Merged
merged 7 commits into from
Sep 17, 2018
Merged
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
177 changes: 128 additions & 49 deletions screeps-game-api/src/memory.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,62 @@
use std::fmt;
//! Interface with Screeps' `Memory` global variable
//!
//! Screeps' memory lives in the javascript `Memory` global variable and is
//! encoded as a javascript object. This object's reference is tracked within
//! rust as a `MemoryReference`. The [`root`] function gives access to a
//! reference to the `Memory` global object.
//!
//! # Typing
//! Contrary to accessing the memory in javascript, rust's strong type system,
//! requires that read values be assigned a type. To facilitate this, the
//! `MemoryReference` provides methods to read a part of the memory as a
//! certain type. If the value read cannot be transformed to the requested
//! type, the method return `None`.
//!
//! # Accessing the memory
//! Memory can be accessed in two ways:
//! - via _keys_
//! - via _paths_ (methods prefixed with `path_`)
//!
//! In both cases, if the value requested is `undefined`, `null`, or even just
//! of the wrong type, the method returns `None`.
//!
//! ## Accessing memory with a _key_
//! Since a `MemoryReference` represents a javascript object, its children can
//! be accessed using the `object["key"]` javascript syntax using type methods.
//! ```no_run
//! let mem = screeps::memory::root();
//! let cpu_used_last_tick = mem.int("cpu_used_last_tick").unwrap();
//! ```
//!
//! ## Accessing memory with a _path_
//! A quality of life improvement upon the key access is through full path. In
//! javascript, it is possible to query a value with a full path:
//! ```javascript
//! var creep_time = Memory.creeps.John.time;
//! ```
//!
//! To emulate this behavior in rust, you can write such a path to a string and
//! it will fetch the javascript object using
//! [lodash](https://lodash.com/docs/4.17.10#get) and convert the result
//! depending on the method used. For example,
//! ```no_run
//! let mem = screeps::memory::root();
//! let creep_time = mem.path_num("creeps.John.time").unwrap();
//! ```
//!
//! # Other methods that provide `MemoryReference`s
//! In addition to accessing the memory from the root, it is possible to
//! access the memory via creeps, spawns, rooms and flags. Accessing the memory
//! from those objects will also result in a `MemoryReference` which instead
//! points at the root of this object's memory.
//!

use std::fmt;
use stdweb::unstable::{TryFrom, TryInto};
use stdweb::{Array, JsSerialize, Reference, Value};

use ConversionError;

#[derive(Clone, Debug)]
pub struct UnexpectedTypeError;

Expand All @@ -13,7 +67,8 @@ impl fmt::Display for UnexpectedTypeError {
}
}

/// TODO: do we even need this over just a raw 'Reference'?
// TODO: do we even need this over just a raw 'Reference'?
/// A [`Reference`] to a screeps memory object
pub struct MemoryReference(Reference);

impl AsRef<Reference> for MemoryReference {
Expand Down Expand Up @@ -42,46 +97,60 @@ impl MemoryReference {
MemoryReference(reference)
}

pub fn bool(&self, path: &str) -> bool {
js_unwrap!(Boolean(@{self.as_ref()}[@{path}]))
pub fn bool(&self, key: &str) -> bool {
js_unwrap!(Boolean(@{self.as_ref()}[@{key}]))
}

pub fn num(&self, path: &str) -> Option<f64> {
pub fn path_bool(&self, path: &str) -> bool {
js_unwrap!(Boolean(_.get(@{self.as_ref()}, @{path})))
}

pub fn f64(&self, key: &str) -> Result<Option<f64>, ConversionError> {
(js! {
return (@{self.as_ref()})[@{path}];
return (@{self.as_ref()})[@{key}];
}).try_into()
.map(Some)
.unwrap_or_default()
}

pub fn int(&self, path: &str) -> Option<i32> {
pub fn path_f64(&self, path: &str) -> Result<Option<f64>, ConversionError> {
(js! {
return (@{self.as_ref()})[@{path}];
return _.get(@{self.as_ref()}, @{path});
}).try_into()
.map(Some)
.unwrap_or_default()
}

pub fn string(&self, path: &str) -> Option<String> {
pub fn i32(&self, key: &str) -> Result<Option<i32>, ConversionError> {
(js! {
return (@{self.as_ref()})[@{path}];
return (@{self.as_ref()})[@{key}];
}).try_into()
.map(Some)
.unwrap_or_default()
}

pub fn dict(&self, path: &str) -> Option<MemoryReference> {
pub fn path_i32(&self, path: &str) -> Result<Option<i32>, ConversionError> {
(js! {
var v = (@{self.as_ref()})[@{path}];
if (_.isArray(v)) {
return null;
} else {
return v || null;
}
return _.get(@{self.as_ref()}, @{path});
}).try_into()
}

pub fn string(&self, key: &str) -> Result<Option<String>, ConversionError> {
(js! {
return (@{self.as_ref()})[@{key}];
}).try_into()
}

pub fn path_string(&self, path: &str) -> Result<Option<String>, ConversionError> {
(js! {
return _.get(@{self.as_ref()}, @{path});
}).try_into()
}

pub fn dict(&self, key: &str) -> Result<Option<MemoryReference>, ConversionError> {
(js! {
return (@{self.as_ref()})[@{key}];
}).try_into()
}

pub fn path_dict(&self, path: &str) -> Result<Option<MemoryReference>, ConversionError> {
(js! {
return _.get(@{self.as_ref()}, @{path});
}).try_into()
.map(Some)
.unwrap_or_default()
.map(MemoryReference)
}

/// Get a dictionary value or create it if it does not exist.
Expand Down Expand Up @@ -110,48 +179,57 @@ impl MemoryReference {
js_unwrap!(Object.keys(@{self.as_ref()}))
}

pub fn del(&self, path: &str) {
pub fn del(&self, key: &str) {
js! {
(@{self.as_ref()})[@{key}] = undefined;
}
}

pub fn path_del(&self, path: &str) {
js! {
(@{self.as_ref()})[@{path}] = undefined;
_.set(@{self.as_ref()}, @{path}, undefined);
}
}

pub fn set<T>(&self, path: &str, value: T)
pub fn set<T>(&self, key: &str, value: T)
where
T: JsSerialize,
{
js! {
(@{self.as_ref()})[@{path}] = @{value};
(@{self.as_ref()})[@{key}] = @{value};
}
}

pub fn arr<T>(&self, path: &str) -> Option<Vec<T>>
pub fn path_set<T>(&self, path: &str, value: T)
where
T: TryFrom<Value, Error = <Reference as TryFrom<Value>>::Error>,
T: JsSerialize,
{
let x: Reference = (js! {
var v = (@{self.as_ref()})[@{path}];
if (!_.isArray(v)) {
return null;
} else {
return v || null;
}
}).try_into()
.ok()?;
js! {
_.set(@{self.as_ref()}, @{path}, @{value});
}
}

// Memory arrays don't have the regular Array as their prototype - they
// have the 'outside' type.
let as_arr: Array = unsafe {
use stdweb::ReferenceType;
Array::from_reference_unchecked(x)
};
pub fn arr<T>(&self, key: &str) -> Result<Option<Vec<T>>, ConversionError>
where
T: TryFrom<Value, Error = ConversionError>,
{
(js! {
return (@{self.as_ref()})[@{key}];
}).try_into()
}

as_arr.try_into().ok()
pub fn path_arr<T>(&self, path: &str) -> Result<Option<Vec<T>>, ConversionError>
where
T: TryFrom<Value, Error = ConversionError>,
{
(js! {
return _.get(@{self.as_ref()}, @{path});
}).try_into()
}
}

impl TryFrom<Value> for MemoryReference {
type Error = <Reference as TryFrom<Value>>::Error;
type Error = ConversionError;

fn try_from(v: Value) -> Result<Self, Self::Error> {
let r: Reference = v.try_into()?; // fail early.
Expand All @@ -168,6 +246,7 @@ impl TryFrom<Value> for MemoryReference {
}
}

/// Get a reference to the `Memory` global object
pub fn root() -> MemoryReference {
js_unwrap!(Memory)
}