-
Notifications
You must be signed in to change notification settings - Fork 45
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
Switch to lodash for memory lookup #22
Conversation
Definitely less cumbersome! Still slightly more overhead than I'd like to include, though, and lodash is much more accepting of invalid things than I'd prefer. Not that this crate is high performance in the first place, but it is nice to have minimal access available. What would you think of using a macro to do this kind of access? let memory = screeps::memory::root();
let val = memory_get!(int, memory.creeps.John.time);
let val2 = memory_get!(int, memory.creeps[name].time);
// translating into
let val = memory.dict("creeps").and_then(|v| v.dict("John")).and_then(|v| v.int("time"))
let val = memory.dict("creeps").and_then(|v| v.dict(name)).and_then(|v| v.int("time")) It'd be slightly more verbose, but possibly easier to include include our own values. I'd also really like to keep at least one interface into This might be a bit hypocritical, but I think it'd be alright to include a utility macro like this in Edit: I can work on implementing this macro, since I think it's a useful thing. I'd also be open to introducing the changes you've made here too as long as we can keep a non-lodash-using Thoughts? |
Because of the use of lodash? I thought to use it after I saw it used in one of the creep's method, IIRC. Still, I am completely fine with the macro with two caveats:
It all boils down to what you have in mind as a FFI to the screeps API. My first thought was to completely skip Now, I thought that going the route of emulating the way the memory access is done in screeps as closely as possible would be a better way to go, but I get the concerns of overhead. Making a whole other struct for lodash support seems a bit too heavy, but I'd like having two versions of each methods, one lodash, the other vanilla. But pretty please, don't call the vanilla parameters So my suggestion would look like: pub fn num(&self, key: &str) -> Option<f64> {
(js! {
return (@{self.as_ref()})[@{key}];
}).try_into()
.map(Some)
.unwrap_or_default()
}
pub fn path_num(&self, path: &str) -> Option<f64> {
(js! {
return _.get(@{self.as_ref()}, @{path});
}).try_into()
.map(Some)
.unwrap_or_default()
} with maybe an eventual rewrite using a macro to avoid using lodash. How's that? |
That'd be good! The only reason to avoid lodash here is parsing strings at runtime which could instead be separated at compile time, but I wasn't thinking about crossing the rust<->js boundary multiple times. It's my understanding that it's almost as cheap as a regular JS function call since node runs WASM within the same runtime as JS, but there still could be overhead. Writing the macro itself shouldn't be too much work if we did want to go that path. |
Let's put this PR on the ice while you have a look at the macro then! |
Alright- I'm still not 100% convinced the macro is the best idea, but it is simple-ish to implement. While function calls might be trivial, I'd forgotten that that also involve sending strings across the rust<->js boundary, which might be less trivial. What would you think of having both as alternatives? The macro could be nicer to use if there's variables included, but I have no idea which is more performant, and the macro syntax isn't necessarily as nice as including a string. |
I'm fine having more choices rather than fewer. We should take some care to document this discussion however. |
How about switching from |
Another thing that came to me: should we rename |
screeps-game-api/src/memory.rs
Outdated
(js! { | ||
return (@{self.as_ref()})[@{path}]; | ||
return _.get(@{self.as_ref()}, @{path}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably deserves some discussion on what the best method is.
I originally used = undefined
since it's strictly faster (or at least was in the V8 version that was benchmarked a while ago), but it's true that it doesn't exactly represent the right semantics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I though it would just store an empty object, but it turns out that it does get rid of it! Maybe a result of the serialization process.
In that case, I can revert the last commit!
If we're going to return conversion errors, I think it'd be most appropriate to return Re: float: I'm not 100% sure on what the naming convention should be. We could go all the way to the rust side on naming conventions and rename them to |
My preference would be for For the conversion to |
For the enum MemoryError {
ConversionError,
UndefinedError
} I understand the logic behind having an unexpected error and an expected error, but I would argue that you would never try and fetch memory that you know is Thoughts? |
I'd argue fetching memory possibly undefined is almost one of the most common operations? At least in my experience it's much more common than fetching a value you know to be defined. I mean for a lot of values, undefined reasonably means "whatever the default is". Take a simple example with a creep storing whether it's currently gathering energy. The most efficient way to store this would be to have For many other things, they might be uncommonly stored. Like For memory values stored globally, sure, it does make sense to always have a value and initialize it at start. But even then you'd want to be able to delete that memory manually in the console if it's somehow gotten bad data and have the AI reinitialize it with the default. In all of these cases I would argue "not present" is a fairly normal case, whereas "wrong type of value" is an irreparable error. If Hope that perspective is helpful? Let me know what you think. |
That makes a lot of sense! This is where it shows I don't know the game very well. Now, here is partly why I am asking. Look at this: fn try_convert<T : TryFrom<Value> + AsRef<Reference>>(&self, key: &str) -> Result<Option<T>, <T as TryFrom<Value>>::Error> {
let v = js!{ return @{self.as_ref()}[@{key}]; };
v.try_into()
} I thought this would be a very reasonable function. However, the compiler gives me this very helpful error:
This is all the more annoying given that it's perfectly fine if I do: fn try_convert(&self, key: &str) -> Result<Option<String>, <String as TryFrom<Value>>::Error> {
let v = js!{ return @{self.as_ref()}[@{key}]; };
v.try_into()
} Any clue what I could be missing? Stdweb seems to have been to great length to ensure you cannot put your hand on their |
@ASalvail Yeah, I see you've got the To get the generic one, I think you need to require that |
Seems like it is related to rust-lang/rust#34430 |
Using |
@ASalvail Yeah- I definitely mean using I've opened koute/stdweb#280 to see if |
Alright, I abandoned (temporarily!) my big plans for conversion errors that make sense. I'd rather come back to it later once we get some insight on koute/stdweb#280 and koute/stdweb#281 which makes it very difficult to have |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good!
I do think we should keep the checks to prevent returning arrays as MemoryReference
references, but the rest of it's good.
screeps-game-api/src/memory.rs
Outdated
}).try_into() | ||
.map(Some) | ||
.unwrap_or_default() | ||
pub fn get<T : TryFrom<Value, Error = ConversionError> + AsRef<Reference>>(&self, key: &str) -> Result<Option<T>, ConversionError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for style, I'd prefer longer generic requirements be written in a where
clause rather than being linline.
Something like this separates things out a bit more cleanly, and definitely wraps better:
pub fn get<T>(&self, key: &str) -> Result<Option<T>, ConversionError>
where
T: TryFrom<Value, Error = ConversionError> + AsRef<Reference>,
{
}
screeps-game-api/src/memory.rs
Outdated
|
||
pub fn dict(&self, key: &str) -> Result<Option<MemoryReference>, ConversionError> { | ||
(js! { | ||
return _.get(@{self.as_ref()}, @{key}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the array check this will incorrectly succeed on arrays since they are Reference
s.
Don't need the whole v || null
thing (not sure why I felt the need to replace undefined
with null
), but the if (_.isArray(v))
check is necessary to prevent incorrectly returning a MemoryReference
wrapping an array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, that should be taken care of by the defn of TryFrom<Value> for MemoryReference
given later since I'm not wrapping the MemoryReference
around the Reference
myself.
screeps-game-api/src/memory.rs
Outdated
|
||
pub fn dict(&self, key: &str) -> Result<Option<MemoryReference>, ConversionError> { | ||
(js! { | ||
return _.get(@{self.as_ref()}, @{key}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to arrays: I think this was meant to be a non-path lookup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
I addressed your previous comment. Regarding the check for the |
That's a good point- I totally missed that. Thanks! |
While the parameters name led me to believe that I could dereference paths using the
obj[path]
syntax, I couldn't make it work. However, during my search, I did discover lodash'sget
method that actually does that. This means that instead ofnow we can directly query any layers of the memory tree:
This is not quite native structs, but it is a lot less cumbersome.