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

Idea - retrying setup #485

Closed
idanarye opened this issue Sep 27, 2018 · 12 comments
Closed

Idea - retrying setup #485

idanarye opened this issue Sep 27, 2018 · 12 comments

Comments

@idanarye
Copy link

I was looking at the Amethyst UI example and the way they update their FPS display text thingie seems horrid - they check every frame if the field that remembers the entity is None, if so they look for the relevant entity in the world, and if they find it they store in the game data struct. Then, they check again (because it's possible that they couldn't find it yet) and only then they can use it to fetch the relevant UiText component.

I tried doing it in a system, finding the entity in the setup:

struct UpdateText {
    ui_entity: Option<Entity>,
}

impl<'s> System<'s> for UpdateText {
    type SystemData = (
        WriteStorage<'s, UiText>,
        // ...
    );

    fn setup(&mut self, res: &mut Resources) {
        let ui_finder = UiFinder::fetch(res);
        self.ui_entity = ui_finder.find("my_text_display");
    }

    fn run(&mut self, (mut ui_texts, /*...*/): Self::SystemData) {
        if let Some(ui_text) = self.ui_entity.and_then(|entity| ui_texts.get_mut(entity)) {
            // update the text
        }
    }
}

But that didn't work - it turns out the entity did not exist yet (!!!) when setup was invoked. So I had to move it to run:

struct UpdateText {
    ui_entity: Option<Entity>,
}

impl<'s> System<'s> for UpdateText {
    type SystemData = (
        UiFinder<'s>,
        WriteStorage<'s, UiText>,
        // ...
    );

    fn run(&mut self, (ui_finder, mut ui_texts, /*...*/): Self::SystemData) {
        if self.ui_entity.is_none() {
            self.ui_entity = ui_finder.find("my_text_display");
        }
        if let Some(ui_text) = self.ui_entity.and_then(|entity| ui_texts.get_mut(entity)) {
            // update the text
        }
    }
}

And now I have another dependency in SystemData... this may not be so bad, but these things stack. Also, I remain with this cumbersome and weird code that needs to check ui_entity every single time.

I also did some logging, and it turns out that on the first iteration run couldn't find the UI entity! Only from the second iteration could it start working.

This had me thinking - what if we could delay the registration of a System until after all it's resources are available? An API like this:

fn create_update_text(res: &mut Resources) -> Result<UpdateText, SystemCreationError<()>> {
    let ui_finder = UiFinder::fetch(res);
    Ok(UpdateText {
        ui_entity: ui_finder
            .find("my_text_display")
            .ok_or(SystemCreationError::RetryLater)?,
    })
}

struct UpdateText {
    ui_entity: Entity,
}

impl<'s> System<'s> for UpdateText {
    type SystemData = (
        WriteStorage<'s, UiText>,
        // ...
    );

    fn run(&mut self, (mut ui_texts, /*...*/): Self::SystemData) {
        if let Some(ui_text) = ui_texts.get_mut(self.ui_entity) {
            // update the text
        }
    }
}

Where `SystemCreationError` is:
```rust
pub enum SystemCreationError<E> {
    RetryLater,
    Failure(E),
}

Instead of registring a UpdateText object, we register the create_update_text function (or a closure that calls it). It'll go into a list, and before we run the systems on each frame we check that list and run every Fn in it. If it returns Ok(system), we add system to the dispatcher and remove the creator from the list. If it returns Err(SystemCreationError::RetryLater) nothing happens - and the same creator will be attempted again on the next iteration. Err(SystemCreationError::Failure(error)) will fail everything with Err(error). Or we could just give up on that part, let the creator return an Option, and actual setup failures will panic! instead.

This delayed setup phase can be a bit expensive, since it can't be parallelized, but it should only happen at the first few frames - after that the queue will be empty and the other systems will run a little bit faster because they won't have to check their Options - which will also make their code cleaner.

@torkleyy
Copy link
Member

Hm, I think you're missing something here. The setup method is only for the purpose of adding missing resources to the world and eventually subscribing to them, but it's not some kind of "start" phase. And in the example you gave there's an asset that needs to be loaded first; that could happen many frames after the game started (or maybe it's done at a later point in the game, we don't know); my point being that this doesn't belong into System::setup. Maybe the system should be working different (although I don't see anything wrong with what is essentially a null pointer check).

Okay, just looked at the link you posted, the code doesn't look very nice, but I still don't think this is the way to solve it.

cc @Moxinilian @Xaeroxe @Jojolepro

@Moxinilian
Copy link
Member

Moxinilian commented Sep 28, 2018 via email

@idanarye
Copy link
Author

When I'm talking about performance, I'm not talking about the extra None checks - these are surely insignificant. My main concern is the extra resources in SystemData that are only used once (or twice, in my example) but still need to be locked on every frame. True, this is a read lock so the damage is limited, but it can still block some parallelization opportunities if something else needs a write lock on the same resource...

@AnneKitsune
Copy link
Contributor

I'm already working on a thing that automatically updates ui texts based on the id. Both the UiText and the UiTextUpdated will be on the same component, so I will just have a join over both of those. In this specific case the current api design will look like this:

Components

  • UiText

  • AutoFpsText(component): UiAutoText(trait)

  • AutoFpsSystem (FPSCounter resource -> updates AutoFpsText

  • AutoTextSystem<T: UiAutoText> (UiAutoText -> updated UiText)

AutoFpsSystem would always run before AutoTextSystem
and both would be in the fps counter bundle.

@Moxinilian
Copy link
Member

Moxinilian commented Sep 28, 2018 via email

@AnneKitsune
Copy link
Contributor

@Moxinilian its not about resources, in this case its about doing an entity search. Its rare to have a system that needs to wait for a resource to be created. Also, in most of the cases where you do need to wait for that resource, you would be better off using a second dispatcher instead of managing the option in the system.

@Moxinilian
Copy link
Member

Moxinilian commented Sep 28, 2018 via email

@torkleyy
Copy link
Member

@Moxinilian I think we're discussing two different things here. Making setup create the system instance is one (posible) way to go, the example that's discussed here though does not rely only on a resource, but an IO operation and other stuff to run first. Please don't mix these things here.

@torkleyy
Copy link
Member

Let's split this issue:

  1. one issue for this specific example (in the Amethyst repo)
  2. another one for possible improvements for System::setup

@AnneKitsune
Copy link
Contributor

AnneKitsune commented Sep 29, 2018

As I said, the amethyst one is going to be fixed soon. There are multiple ui related issues that cover this.

@torkleyy
Copy link
Member

Okay, great. As for setup suggestions please open dedicated issues and I'll reject them one by one xD

Nah seriously I want the discussions to be on topic, so Mox if you want something to change, please create another issue here or in the shred repo. @idanarye As for the delayed setup phase I think already answered that that is beyond the scope of setup, besides not being very helpful for this case. Should something be unclear or for any other suggestions, please open new, separate issues.

@torkleyy
Copy link
Member

Also related: #437

bors bot added a commit that referenced this issue Jan 4, 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) ([#649](https://github-redirect.dependabot.com/rust-random/rand/issues/649))
> - Disable default features of `libc` ([#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)! ([#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 ([#591](https://github-redirect.dependabot.com/rust-random/rand/issues/591), [#611](https://github-redirect.dependabot.com/rust-random/rand/issues/611))
> - Migrate policy documentation from the wiki ([#544](https://github-redirect.dependabot.com/rust-random/rand/issues/544))
> 
> ### Platforms
> - Add fork protection on Unix ([#466](https://github-redirect.dependabot.com/rust-random/rand/issues/466))
> - Added support for wasm-bindgen. ([#541](https://github-redirect.dependabot.com/rust-random/rand/issues/541), [#559](https://github-redirect.dependabot.com/rust-random/rand/issues/559), [#562](https://github-redirect.dependabot.com/rust-random/rand/issues/562), [#600](https://github-redirect.dependabot.com/rust-random/rand/issues/600))
> - Enable `OsRng` for powerpc64, sparc and sparc64 ([#609](https://github-redirect.dependabot.com/rust-random/rand/issues/609))
> - Use `syscall` from `libc` on Linux instead of redefining it ([#629](https://github-redirect.dependabot.com/rust-random/rand/issues/629))
> 
> ### RNGs
> - Switch `SmallRng` to use PCG ([#623](https://github-redirect.dependabot.com/rust-random/rand/issues/623))
> - Implement `Pcg32` and `Pcg64Mcg` generators ([#632](https://github-redirect.dependabot.com/rust-random/rand/issues/632))
> - Move ISAAC RNGs to a dedicated crate ([#551](https://github-redirect.dependabot.com/rust-random/rand/issues/551))
> - Move Xorshift RNG to its own crate ([#557](https://github-redirect.dependabot.com/rust-random/rand/issues/557))
> - Move ChaCha and HC128 RNGs to dedicated crates ([#607](https://github-redirect.dependabot.com/rust-random/rand/issues/607), [#636](https://github-redirect.dependabot.com/rust-random/rand/issues/636))
> - Remove usage of `Rc` from `ThreadRng` ([#615](https://github-redirect.dependabot.com/rust-random/rand/issues/615))
> 
> ### Sampling and distributions
> - Implement `Rng.gen_ratio()` and `Bernoulli::new_ratio()` ([#491](https://github-redirect.dependabot.com/rust-random/rand/issues/491))
> - Make `Uniform` strictly respect `f32` / `f64` high/low bounds ([#477](https://github-redirect.dependabot.com/rust-random/rand/issues/477))
> - Allow `gen_range` and `Uniform` to work on non-`Copy` types ([#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. ([#566](https://github-redirect.dependabot.com/rust-random/rand/issues/566))
> - Implement `TrustedLen` and `FusedIterator` for `DistIter` ([#620](https://github-redirect.dependabot.com/rust-random/rand/issues/620))
> 
> #### New distributions
> - Add the `Dirichlet` distribution ([#485](https://github-redirect.dependabot.com/rust-random/rand/issues/485))
> - Added sampling from the unit sphere and circle. ([#567](https://github-redirect.dependabot.com/rust-random/rand/issues/567))
> - Implement the triangular distribution ([#575](https://github-redirect.dependabot.com/rust-random/rand/issues/575))
> - Implement the Weibull distribution ([#576](https://github-redirect.dependabot.com/rust-random/rand/issues/576))
> - Implement the Beta distribution ([#574](https://github-redirect.dependabot.com/rust-random/rand/issues/574))
> 
> #### Optimisations
> 
> - Optimise `Bernoulli::new` ([#500](https://github-redirect.dependabot.com/rust-random/rand/issues/500))
> - Optimise `char` sampling ([#519](https://github-redirect.dependabot.com/rust-random/rand/issues/519))
> - Optimise sampling of `std::time::Duration` ([#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] <support@dependabot.com>
Co-authored-by: Thomas Schaller <torkleyy@gmail.com>
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

No branches or pull requests

4 participants