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

implement get_mut_or_default #562

Closed
wants to merge 1 commit into from

Conversation

JaniM
Copy link
Contributor

@JaniM JaniM commented Mar 7, 2019

Fixes #561

Checklist

  • I've added tests for all code changes and additions (where applicable)
  • I've added a demonstration of the new feature to one or more examples (didn't find any relevant examples)
  • I've updated the book to reflect my changes
  • Usage of new public items is shown in the API docs

API changes

New function: GenericWriteStorage::get_mut_or_default for Default component types. Not breaking.


This change is Reviewable

@@ -103,6 +106,13 @@ where
WriteStorage::get_mut(self, entity)
}

fn get_mut_or_default(&mut self, entity: Entity) -> &mut Self::Component where Self::Component: Default {
if self.get(entity).is_none() {
self.insert(entity, Default::default()).unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

I don't think an unwrap should be best here. Maybe at least an expect.
The one under does not need an except, but maybe an explanation in the comment explaining why it will never be triggered.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there any situation where the expect could be hit?

Copy link
Member

Choose a reason for hiding this comment

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

I believe the specs does not guarantee it would never happen. It's not very expensive to make sure it doesn't go wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is the only possible error case that the component has been added between the check and the insertion? In that case we could ignore the result entirely.

Copy link
Member

Choose a reason for hiding this comment

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

No, this would not even be an error, insert overwrites the previous component.
@torkleyy What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

+1 to expect() instead of unwrap().

Is the only possible error case that the component has been added between the check and the insertion?
it's a mutable borrow of self, so nothing can insert it between

by the way, WriteStorage is a Storage, and has a contains method, so perhaps it expresses the intent better.

if !self.contains(entity) {
    self.insert(entity, Default::default()).expect("..");
}
self.get_mut(entity).expect("..")
The alternate implementation feels a bit verbose
if let Some(component) = self.get_mut(entity) {
    component
} else {
    self.insert(entity, Default::default()).expect("..");
    self.get_mut(entity).expect("..")
}

Same comment for the below implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched to expect.

Copy link
Member

@azriel91 azriel91 left a comment

Choose a reason for hiding this comment

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

yo, this is useful!

have some recommendations, but then realized there's also existing tech debt

@@ -103,6 +106,13 @@ where
WriteStorage::get_mut(self, entity)
}

fn get_mut_or_default(&mut self, entity: Entity) -> &mut Self::Component where Self::Component: Default {
if self.get(entity).is_none() {
self.insert(entity, Default::default()).unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

+1 to expect() instead of unwrap().

Is the only possible error case that the component has been added between the check and the insertion?
it's a mutable borrow of self, so nothing can insert it between

by the way, WriteStorage is a Storage, and has a contains method, so perhaps it expresses the intent better.

if !self.contains(entity) {
    self.insert(entity, Default::default()).expect("..");
}
self.get_mut(entity).expect("..")
The alternate implementation feels a bit verbose
if let Some(component) = self.get_mut(entity) {
    component
} else {
    self.insert(entity, Default::default()).expect("..");
    self.get_mut(entity).expect("..")
}

Same comment for the below implementation

}
}

for i in 0..1_000 {
Copy link
Member

Choose a reason for hiding this comment

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

hm, what are these loops and magic numbers? am finding I have to think quite a bit to understand the logic, and then think again to figure out what its intent is.

if the loops are necessary, it's worth writing the rationale in a comment

  • is it to get the entity index high enough?
  • can we use a smaller backing storage to achieve the same effect?

not obvious to understand what the test is doing 🤔:

  1. insert 500 entities with n + 1290 as its value.
  2. mutate 1000 entities with get_mut_or_default() to be n + n + 710
  3. assert the first 500 are 2n + 2000
  4. assert the last 500 are n + 710

ah 💡! while it is logically correct and does test the effect, it's considerable mental effort to turn the code back into understanding. could you try to make it easier, otherwise my head is like this: 🤕, haha


edit: just expanded the diff, and saw the existing code is like that. oh man 😂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the magic numbers & commented it.

src/storage/generic.rs Outdated Show resolved Hide resolved
@JaniM JaniM force-pushed the feat/get_mut_or_default branch from d6f3c7a to a983117 Compare March 8, 2019 07:01
@JaniM
Copy link
Contributor Author

JaniM commented Mar 8, 2019

Squashed & pushed with relevant modifications.

Copy link
Member

@azriel91 azriel91 left a comment

Choose a reason for hiding this comment

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

🚀

Copy link
Member

@torkleyy torkleyy left a comment

Choose a reason for hiding this comment

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

Thanks, looks useful. I'm a little unsure how to handle dead entities here.

@@ -103,6 +111,14 @@ where
WriteStorage::get_mut(self, entity)
}

fn get_mut_or_default(&mut self, entity: Entity) -> &mut Self::Component where Self::Component: Default {
if !self.contains(entity) {
self.insert(entity, Default::default()).expect("Insertion of default component failed");
Copy link
Member

Choose a reason for hiding this comment

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

This will panic for dead entities.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh boy. I didn't think of that.

@@ -126,6 +142,14 @@ where
WriteStorage::get_mut(*self, entity)
}

fn get_mut_or_default(&mut self, entity: Entity) -> &mut Self::Component where Self::Component: Default {
if !self.contains(entity) {
self.insert(entity, Default::default()).expect("Insertion of default component failed");
Copy link
Member

Choose a reason for hiding this comment

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

Same here

@JaniM JaniM force-pushed the feat/get_mut_or_default branch from a983117 to 0585eb5 Compare March 8, 2019 09:24
@JaniM
Copy link
Contributor Author

JaniM commented Mar 8, 2019

Marked that it will panic if the entity is dead. Another option is to return an Option, which'd make the usage slightly less ergonomic, but in most cases still nice enough.

@JaniM
Copy link
Contributor Author

JaniM commented Mar 8, 2019

^ Opened a separate PR for that. Pick one.

bors bot added a commit that referenced this pull request Mar 29, 2019
563: Optionized get_mut_or_default r=torkleyy a=JaniM

Fixes #561
Alternative to #562 . Implements an Option API instead of panicking if the entity is dead

I have no idea how I managed to mess up the branch name.



Co-authored-by: Jani Mustonen <[email protected]>
@bors bors bot closed this in #563 Mar 29, 2019
AnneKitsune pushed a commit to AnneKitsune/specs that referenced this pull request May 2, 2019
537: Update rand requirement from 0.5.5 to 0.6.1 r=torkleyy a=dependabot[bot]

Updates the requirements on [rand](https://github.com/rust-random/rand) to permit the latest version.
<details>
<summary>Changelog</summary>

*Sourced from [rand's changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md).*

> ## [0.6.1] - 2018-11-22
> - Support sampling `Duration` also for `no_std` (only since Rust 1.25) ([amethyst#649](https://github-redirect.dependabot.com/rust-random/rand/issues/649))
> - Disable default features of `libc` ([amethyst#647](https://github-redirect.dependabot.com/rust-random/rand/issues/647))
> 
> ## [0.6.0] - 2018-11-14
> 
> ### Project organisation
> - Rand has moved from [rust-lang-nursery](https://github.com/rust-lang-nursery/rand)
>   to [rust-random](https://github.com/rust-random/rand)! ([amethyst#578](https://github-redirect.dependabot.com/rust-random/rand/issues/578))
> - Created [The Rust Random Book](https://rust-random.github.io/book/)
>   ([source](https://github.com/rust-random/book))
> - Update copyright and licence notices ([amethyst#591](https://github-redirect.dependabot.com/rust-random/rand/issues/591), [amethyst#611](https://github-redirect.dependabot.com/rust-random/rand/issues/611))
> - Migrate policy documentation from the wiki ([amethyst#544](https://github-redirect.dependabot.com/rust-random/rand/issues/544))
> 
> ### Platforms
> - Add fork protection on Unix ([amethyst#466](https://github-redirect.dependabot.com/rust-random/rand/issues/466))
> - Added support for wasm-bindgen. ([amethyst#541](https://github-redirect.dependabot.com/rust-random/rand/issues/541), [amethyst#559](https://github-redirect.dependabot.com/rust-random/rand/issues/559), [amethyst#562](https://github-redirect.dependabot.com/rust-random/rand/issues/562), [amethyst#600](https://github-redirect.dependabot.com/rust-random/rand/issues/600))
> - Enable `OsRng` for powerpc64, sparc and sparc64 ([amethyst#609](https://github-redirect.dependabot.com/rust-random/rand/issues/609))
> - Use `syscall` from `libc` on Linux instead of redefining it ([amethyst#629](https://github-redirect.dependabot.com/rust-random/rand/issues/629))
> 
> ### RNGs
> - Switch `SmallRng` to use PCG ([amethyst#623](https://github-redirect.dependabot.com/rust-random/rand/issues/623))
> - Implement `Pcg32` and `Pcg64Mcg` generators ([amethyst#632](https://github-redirect.dependabot.com/rust-random/rand/issues/632))
> - Move ISAAC RNGs to a dedicated crate ([amethyst#551](https://github-redirect.dependabot.com/rust-random/rand/issues/551))
> - Move Xorshift RNG to its own crate ([amethyst#557](https://github-redirect.dependabot.com/rust-random/rand/issues/557))
> - Move ChaCha and HC128 RNGs to dedicated crates ([amethyst#607](https://github-redirect.dependabot.com/rust-random/rand/issues/607), [amethyst#636](https://github-redirect.dependabot.com/rust-random/rand/issues/636))
> - Remove usage of `Rc` from `ThreadRng` ([amethyst#615](https://github-redirect.dependabot.com/rust-random/rand/issues/615))
> 
> ### Sampling and distributions
> - Implement `Rng.gen_ratio()` and `Bernoulli::new_ratio()` ([amethyst#491](https://github-redirect.dependabot.com/rust-random/rand/issues/491))
> - Make `Uniform` strictly respect `f32` / `f64` high/low bounds ([amethyst#477](https://github-redirect.dependabot.com/rust-random/rand/issues/477))
> - Allow `gen_range` and `Uniform` to work on non-`Copy` types ([amethyst#506](https://github-redirect.dependabot.com/rust-random/rand/issues/506))
> - `Uniform` supports inclusive ranges: `Uniform::from(a..=b)`. This is
>   automatically enabled for Rust >= 1.27. ([amethyst#566](https://github-redirect.dependabot.com/rust-random/rand/issues/566))
> - Implement `TrustedLen` and `FusedIterator` for `DistIter` ([amethyst#620](https://github-redirect.dependabot.com/rust-random/rand/issues/620))
> 
> #### New distributions
> - Add the `Dirichlet` distribution ([amethyst#485](https://github-redirect.dependabot.com/rust-random/rand/issues/485))
> - Added sampling from the unit sphere and circle. ([amethyst#567](https://github-redirect.dependabot.com/rust-random/rand/issues/567))
> - Implement the triangular distribution ([amethyst#575](https://github-redirect.dependabot.com/rust-random/rand/issues/575))
> - Implement the Weibull distribution ([amethyst#576](https://github-redirect.dependabot.com/rust-random/rand/issues/576))
> - Implement the Beta distribution ([amethyst#574](https://github-redirect.dependabot.com/rust-random/rand/issues/574))
> 
> #### Optimisations
> 
> - Optimise `Bernoulli::new` ([amethyst#500](https://github-redirect.dependabot.com/rust-random/rand/issues/500))
> - Optimise `char` sampling ([amethyst#519](https://github-redirect.dependabot.com/rust-random/rand/issues/519))
> - Optimise sampling of `std::time::Duration` ([amethyst#583](https://github-redirect.dependabot.com/rust-random/rand/issues/583))
> 
> ### Sequences
></table> ... (truncated)
</details>
<details>
<summary>Commits</summary>

- See full diff in [compare view](https://github.com/rust-random/rand/commits/0.6.1)
</details>
<br />

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

**Note:** This repo was added to Dependabot recently, so you'll receive a maximum of 5 PRs for your first few update runs. Once an update run creates fewer than 5 PRs we'll remove that limit.

You can always request more updates by clicking `Bump now` in your [Dependabot dashboard](https://app.dependabot.com).

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot ignore this [patch|minor|major] version` will close this PR and stop Dependabot creating any more for this minor/major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language
- `@dependabot badge me` will comment on this PR with code to add a "Dependabot enabled" badge to your readme

Additionally, you can set the following in your Dependabot [dashboard](https://app.dependabot.com):
- Update frequency (including time of day and day of week)
- Automerge options (never/patch/minor, and dev/runtime dependencies)
- Pull request limits (per update run and/or open at any time)
- Out-of-range updates (receive only lockfile updates, if desired)
- Security updates (receive only security updates, if desired)

Finally, you can contact us by mentioning @dependabot.

</details>



Co-authored-by: dependabot[bot] <[email protected]>
Co-authored-by: Thomas Schaller <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants