diff --git a/rfcs/53-consistent-hierarchy.md b/rfcs/53-consistent-hierarchy.md new file mode 100644 index 00000000..c939cec1 --- /dev/null +++ b/rfcs/53-consistent-hierarchy.md @@ -0,0 +1,453 @@ +# Feature Name: `consistent-hierarchy` + +## Summary +The current transform hierarchy operations currently allow for it to remain in a +inconsistent state. This RFC proposes a few changes to make it globally +consistent at any given time, as well as make it easier to support non-Tranform +use cases. + +## Motivation +Hierarchies are present in all parts of game development. The one that +immediately comes to mind is the Transform hierarchy, where children exist in the +local space of their parents. However, this concept is can be more general than +transform hierarchies: + + - UI elements have their own parent-sibling-child relationships that are not + strictly expressible in 3D affine transforms. + - Animations are usually bound to a bone hierarchy that may or may not involve + Transforms, and is typically queried based on paths within the hierarchy. + - Certain attributes intuitively should be inherited from parent to child. For + example, hiding a parent via the `Visibility` component typically means the + children of that parent (and all of their descendants) are also hidden. Many + of these properties are not directly tied to transforms or their propagation. + However, many of these use cases also share a similar pattern of detecting + changes and propagating them either down to their descendants or bubbling up a + message through their ancestors. + +All of these cases involve mutating, querying, and traversing these large trees +or forests of linked entities. Throughout each of these operations, it's +imperative that Bevy provides a globally consistent view of the +hierarchy/hiearchies. + +Many of these operations are both abundantly common and moderately expensive. +Queries and traversals can be linear in the number of children and involve heavy +random access patterns which can be detrimental to game performance. + +## Background +In this RFC, we will be using the following terms repeatedly: + + - **Parent:** this refers to the upper level Entity relative to their children. Each + Entity can either have zero or one parents, but may have zero or more + children. + - **Child:** this refers to a lower level Entity relative to their parent. Any child + will have exactly one parent. Children can have children of their own. + - **Sibling:** a child that shares the same parent as another different entity. + - **Roots:** These are entities without parents. May or may not have children. + - **Leaves:** These are entities without children. May or may not have a parent. + +Currently, there are two components under `bevy_transform` that describe Bevy's +current hierarchy: `Parent` and `Children`. Adding or altering the `Parent` +component will not immediately manifest in an update to `Children`. + +A dedicated transform hierarchy maintenance system is run every game tick to +update `Children` to match the actual Entities that point to said Entity as a +Parent. separate `PreviousParent` component is used to keep track of which +entity was the entity's parent before it's current one. This is created, managed, +and removed by the maintenance system. This introduces state where both +components on different entities are out of sync, and relies on the maintenance +system to make it eventually consistent. + +In the meantime, querying for either results in a inconsistent state of the +world, and is the subject of much frustration from those not intimately familiar +with the inner workings of the system. + +## User-facing explanation +Both of the current components' public interface will be made *read-only*. +Structural mutations to the hierarchy: de-parenting, re-parenting, and moving +children from one parent to another, etc. can only be done via commands. This +defers all changes to the hierarchy until the next command buffer flush. +This enforces a globally consistent view at any given time. Delaying any +modifications to a global synchronization point, much like component removal. +Both components will not publicly constructible and must be made via the +dedicated hierarchy management commands. + +`PreviousParent` will also be removed and replaced with `ChildAdded`, +`ChildRemoved` and `ChildMoved` events instead. These will signal changes in the +hierachy instead of relying on change detection on `Parent` and `PreviousParent`. + +## Implementation strategy +This design attempts to minimize the changes made to the current design while +addressing the issues enumerated above. The core changes to be made are as +follows: + + - Make `Parent` and `Children` (mostly) publicly immutable. + - Make mutations to the hierarchy rely solely on commands. + - Update hierarchy commands to automatically update both `Parent` and + `Children` to remain consistent with each other. + - Remove the `parent_update_system`. + - Remove `PreviousParent`. + - Register several hierarchy events that fires whenever time a new child is + added or removed from a parent. + - Create a `HiearchyQuery` custom SystemParam for more easily iterating over + components in the hierarchy. + +This change will repurpose the following existing `EntityCommand` extension +functions for this purpose: + + - `add_child` + - `push_children` + - `insert_children` + - `remove_child` (this will be added as a wrapper around `remove_children`) + - `remove_children` + +An additional `set_parent(Option)` command should also be added as a +child-side way of altering the hierarchy, defaulting to adding the entity as the +last child to the provided parent if provided, and detaching the child from it's +parent if `None`. + +### HierarchyQuery +An extension to this design is to create dedicated `HierarchyQuery`, which +is a custom `SystemParam` that wraps multiple `Query` objects to simplify +querying for items in the hierarchy: + + - `roots(_mut)` - returns an iterator over entities that match the provided + query parameters that do not have a parent. + - `parent(_mut)` - returns single result from the parent of an entity. + - `iter_children(_mut)` - returns an iterator over `(parent, child)` results + from a query parameter. This does not return Children of Children. + - `iter_descendants(_mut)` - returns an iterator over `(parent, child)` results + from a query parameter. This does a depth-first traversal over the hierarchy + starting at some entity. + - `iter_ancestors(_mut)` - returns an iterator over `(parent, child)` results + from a query parameter. This bubbles up queries from the bottom to the top + starting at some entity. + +As suggested by the generic parameters, the query results and filters are +identical to the ones used by a normal Query or QueryState. + +The main motivation for this is to make creating systems like +`transform_propagate_system` easier. These queries can be generically used for +any system that needs some form of hierachy traversal. These queries can be quite +expensive to run, so the documentation for them should reflect this and +discourage misuing it. + +### HierarchyEvent +One new event should be added here, with enum variants corresponding to a +specific change in the hierarchy: + + - `ChildAdded` - Fired when a previous root entity has been added as a child to + a entity in the hierarchy. + - `ChildRemoved` - Fired when an entity is removed from the hierarchy and is now + a root entity. + - `ChildMoved` - Fired when an child is moved from one parent in the hierarchy + to another. + +These can be used to detect changes where `PreviousParent` is currently used. +Each event is exclusive, meaning that any change only generates one event. A move +will not fire additional separate `ChildAdded` and `ChildRemoved` events. + +Only one event type is used here to ensure that hierarchy alterations are +properly sequenced. + +As hierarchy edits typically happen in bursts (i.e. scene loads). There may be +large number of these events generated at once. To reclaim this memory, it may be +necessary to add support for automatic garbage collection to events. + +```rust +#[derive(Clone)] +pub enum HierarchyEvent { + ChildAdded { + parent: Entity, + child: Entity, + }, + ChildRemoved { + parent: Entity, + child: Entity, + }, + ChildMoved { + parent: Entity, + previous_parent: Entity, + new_parent: Entity, + } +} +``` + +### `bevy_hierarchy`-`bevy_transform` split. +These hierarchy types should be moved to a new `bevy_hierarchy` crate to avoid +requiring compiling the hierachy alongside the default "f32-based affine +transform system in Euclidean space". This should allow users to create their own +transform systems (i.e. f64-based transforms, fixed-point transforms, +non-Euclidean spaces) while also being able to leverage the same +parent-sibling-child hierarchies currently found in `bevy_transform`. This should +also allow us to internally leverage the same decoupling too for specialized +transforms for 2D and UI. + +## Benefits + + - This rocks the boat the least compared to the following alternatives and + largely provides the same benefits. + - This design has (nearly) guarenteed global consistency. The hierarchy cannot + be arbitrarily mutated in inconsistent between synchronization points. + Changes are deferred to a until the end of the current stage instead of a + single global maintainence system. + - The cache locality when iterating over the immediate children of an entity is + retained. This saves an extra cache miss per entity when traversing the + hierarchy. + - The removal of `PreviousParent` should reduce the archetype fragmentation of + the current solution. + - Any update to the hierarchy is "atomic" and is `O(n)` in the number of + children an entity has. For any practical hierarchy, this will typically be + a very small n. + - Existing query filters still work as is. For example, `Without` can + still be used to find root entities, and `Changed` can be used to + find any entity with either new children, removed children, or reordered + children. + - The hierarchy is decoupled from the base transform system, allowing both Bevy + developer and third-party crates to make their own independent transform + system without compiling the default transform system. + +## Drawbacks + + - The fact that the hierarchy is not optimally stored in memory is still an + issue, and all hiearchy traversals require heavy random access into memory. + - Updates are still not immediately visible within a stage and deferred until + the next command buffer flush. + - Hierarchy updates are now single threaded. Commands can be generated from + multiple systems, [or from multiple threads in the same + system](https://github.com/bevyengine/bevy/pull/4749) at the same time, but + they now require exclusive access to apply. + - Each of the changed commands do a lot more work before and are signifgantly + more branch heavy. This could negatively impact the performance of + command-heavy workloads (i.e. scene loading) + - The commands applied for controlling the hierarchy are computationally more + expensive, requiring more branching access and more `World::get` calls. + - Some potential implementations of this hierarchy requires normal ECS commands + to be aware of the hierarchy. Using `EntityCommands::remove` to remove + `Children` or `Parent` will break the invariants of the system without some + hooks to enforce the invariants on removal. This is partially mitigatable via + documentation until hooks or relations lands. + +## Rationale and alternatives +The primary goal of this proposal is to get rid of the hierarchy maintenance +system and the consistency issues that come with it's design, which it delivers +on. + +### Alternative: Hierarchy as a Resource +This would store the resource of the entire world as a dedicated Resource +instead of as components in normal ECS storage. A prototypical design for this +resource would be: + +```rust +pub struct Hierarchy { + relations: Vec<(Option, Vec)>, + entities: Vec, + indices: SparseArray, +} +``` + +This would be strikingly similar to the `SparseSet` used in `bevy_ecs`. The main +difference here is that the parallel `Vec`s are roughly kept in topological order. +Inserting should be `O(1)` as it just updates the new parent. Removing should be +`O(n)` with the number of children of that `Entity`. At least once per frame, a +system should run a topological sort over the resource if it's dirty. + +An alternative version of this hierarchy resource only stores parents. This may +speed up iteration as individual relations are smaller during swaps, but finding +children in the hierarchy is O(n) over the entire world. If the depth of each +entity within the hierarchy, and the hierarchy is kept sorted in depth order this +can be kept to O(n) in the next depth. + +#### Benefits + + - Iteration in hierarchy order (parents first) is entirely linear. This may make + transform and other parent-reliant propagation signfigantly faster. + - Finding roots in the sorted hierarchy is both linear and compacted tightly in + memory. + - Removes `Parent` and `Children` ECS components entirely. Could further reduce + archetype fragmentation. The hierarchy is stored densely in a single location + and is not spread between multiple archetypes/tables. + - Can still be used with the command-driven interface proposed in this design + instead of using ECS storage. + +#### Drawbacks + + - Merging and splitting hierarchies loaded from scenes can be quite difficult to + maintain. + - The non-hierarchy components are still stored in an unordered way in ECS data. + Iterating over the hierarchy will still incur random access costs for anything + non-trivial. + - Requires a dedicated topological sort system. Mitigated by only sorting when + the hierarchy is dirty, or simply tracking which entities in the hierarchy are + dirty. + - Editing the hierarchy requires exclusive access to the resource, which can + will serialize all systems editing the hierarchy. This also prevents internal + parallelism (i.e. via `Query::par_for_each`) as `ResMut` cannot be cloned. + This can be partially mitigated by deferring edits via commands, as proposed + in this RFC. + +### Alternative: Linked List Hierarchy +Instead of using two separate components to manage the hierarchy, this +alternative aims to have only one: `Hierarchy`. This hierarchy component provides +*read-only* access into an entity's immediate hierarchial relationships: its +parent, previous sibling, next sibling, and it's first child. All of these may +return `Option`, signalling that such relationships do not exist. This +creates a single component that creates a top-down forest of entity trees, where +each entity forms a doubly-linked list of siblings. It may also cache infromation +about the component's position within the hierarchy like it's depth. + +```rust +#[derive(Component)] +pub struct Hierarchy { + parent: Option, + prev: Option, + next: Option + first_child: Option, +} +``` + +Like the main proposal here, there are no public constructors or mutation APIs, +relying only on commands to mutate the internals. + +#### Benefits + + - Hierarchy updates are all `O(1)`, and can be maintained within a system. Does + not require commands to ensure consistency. + - The Hierarchy component is smaller in ECS storage than the current + Parent/Children combination. With Entity-niching, a `Hierarchy` component is + only 32 bytes in memory, compared to the 64 + 8 used by the current two + components. + - Requires less archetype fragmentation due to only requiring one component + instaed of two. + - Getting siblings does not require a parent Query::get call. + +#### Drawbacks + + - Iterating over children requires `O(n)` Query::get calls. + - Despawning a child without updating it's sibilings causes all subsequent + children to be in a broken state. This exacerbates the lack of hooks, and + would require query access in hooks to fix up. + - Change detection and With/Without filters no longer work as is. Will need + dedicated ZST marker components to get the same filtering capacity. + +### Alternative: HashSet `Children` +The internals of `Children` could be replaced with a `HashSet`. +operations close to `O(1)`, versus `O(n)` operations against a `SmallVec`. + +#### Benefits +This makes all operations against `Children` O(1). Could speed up a number of +hierarchy related commands. + +#### Drawbacks + + - We lose any ability to order children. This is critically important for + certain use cases like UI. + - We lose cache locality for entities with few children. Adding a single child + forces an allocation. + +### Alternative: Hierarchical ECS Storage +This involves directly adding a third ECS storage option. In this solution, a +data-oriented SoA structure of split BlobVec columns is used, virtually identical +to the way Tables are currently stored. However, entities are stored in +depth-first order, and an implicit `Option` parent reference is stored +alongisde every Entity. Hierarchy roots are stored at the beginning of the +slices, and leaves near the end. When a entity's components are mutated, it's +marked "dirty" by moving it to the end of the slice. A separate "first dirty" +index is kept to track a list of all dirty hierarchy entities as a form of change +detection. Finally, when iterating over the storage, if a child is found before +it's parent, it's swapped with it's parent to enforce the depth-first invariant. + +This is very similar to the "Hierarchy as a Resource" alternative, but + +#### Benefits + + - Iteration in hierarchy order (parents first) is entirely linear. This may make + transform and other parent-reliant propagation signfigantly faster. + - Unlike the hierarchy as a resource, this guarentees linearity of all iterated + components, not just the hierarchy relations itself. + +#### Drawbacks +This method is very write heavy. Components are moved whenever there is a +mutation, and shuffled around to ensure that the topologically ordered iteration +invariant is maintained. This may be impractical for hierarchy dependent +components that are very big in size. + +The need to mutate and shuffle the storage during iteration also means parallel +iteration cannot be supported on it, so going wide might not be possible. Even +read-only queries against this storage requires exclusive access to the table. + +### Alternative: ECS Relations +ECS relations can be used to represent the exact same links in the hierarchy, and +might be usable as a different backing for the hierarchy, provided the same +consistency guarentees. + +#### Benefits +Relations are generic, expressing more than just hierarchical relations, but any +link between multiple entities. The exact benefits and drawbacks relative to this +design is reliant on the exact implementation. + +## Prior art +The original design that this proposal is based on is this 2014 article from +[bitsquid +developers](https://bitsquid.blogspot.com/2014/10/building-data-oriented-entity-system.html). +The memory layout sugggested is awfully similar to bevy's ECS tables. However, +components are regularly swapped based on their dirty status. Placing dirty +entities near the end of each contiguous slice. The hierarchical ECS storage +alterantive aims to implement this. + +A similar [blog +post](https://blog.molecular-matters.com/2013/02/22/adventures-in-data-oriented-design-part-2-hierarchical-data/) +from 2013 suggests a similar approach. + +Unity offers similar APIs to the `HierarchyQuery`, with similar performance +penalties for heavy use. + + - [GameObject.GetComponentsInChildren](https://docs.unity3d.com/ScriptReference/GameObject.GetComponentsInChildren.html) + - [GameObject.GetComponentsInParent](https://docs.unity3d.com/ScriptReference/GameObject.GetComponentsInParent.html) + +## Future Work +One of the immediate pieces of future work here is to patch up the potential +footgun of using `EntityCommands::remove` on either `Parent` or `Children` via +add/remove/changed hooks. This is currently mitigatable via documentation, but a +complete solution prevents users from breaking the invariant in the first place. + +### Dedicated RectTransform and Transform2D systems +Transform/GlobalTransform is not particularly well suited for encoding the +hierarchy of UIs and 2D-only scenes. By splitting out the hierarchy from the +exsisting transform system, dedicated transform types and systems for these two +different types of hierarchies. This could also enable further parallelism by +allowing each transform system to independently handle propagation. + +### Multiple Hierarchies via Type Markers +It may be possible to include type markers as a part of the hierarchy components +to differentiate multiple hierachies from each other. This would allow an entity +to be part of multiple hierarchies at the same time. + +```rust +#[derive(Component)] +pub struct Parent { + parent: Entity, + _marker: PhantomData, +} + +#[derive(Component)] +pub struct Children { + children: SmallVec<[Entity, 8]>, + _marker: PhantomData, +} +``` + +This allows us to directly tie hierarchies to the components they're targetting. +Instead of a general `Parent`, a `Parent` declares it's meant +for `bevy_transform`'s transform components. + +It also allows multiple overlapping hierarchies to be made that may differ from +each other depending on the use case. + +However, this may be quite confusing to show in an editor and is reasonably +complex to understand from a user perspective. A compelling use case might be +needed to justify this kind of change. + +## Unresolved questions + + - How do we resolve ordering problems for the same entity? This ties into the + question of Command determinism. diff --git a/rfcs/54-global-task-pools.md b/rfcs/54-global-task-pools.md new file mode 100644 index 00000000..984ad60b --- /dev/null +++ b/rfcs/54-global-task-pools.md @@ -0,0 +1,118 @@ +# Feature Name: `global-task-pools` + +## Summary +Globally initialize the newtyped TaskPools (ComputeTaskPool, +AsyncComputeTaskPool, IoTaskPool) as statics and provide static accessors to +them. + +## Motivation +Bevy's TaskPools are currently passed around as ref-counted shared references, +and accessed primarily as ECS resources. This presents an ergonomics challenge +when integrating third-party crates without direct access to the ECS systems or +World. An example of this [Backroll's use of the TaskPools][backroll] +where the references must be passed and clone repeatedly. + +This also presents an ergonomics issue for existing Bevy APIs, like +`par_for_each(_mut)`. All internally parallel systems must remember to add +a `Res` parameter. This also presents a (rather small) footgun +for new users as to which task pool resource they should use, and choosing the +wrong one will give the wrong performance characteristics due to improper thread +allocation, or higher thread contention from the threads already in the wrong +pool. + +Multitenant projects (processes with multiple Bevy apps running concurrently) +currently spin up new task pools for every top-level App in the process. The +number of threads per CPU core scales with the number of Apps in the process, +which may cause OS level thrashing from the context switches. This is most +commonly seen in server environments where processes may spin up and spin down +Apps in response to players starting and ending multiplayer matches. + +## User-facing explanation +Instead of being publicly constructable, the newtyped TaskPools will become +static singletons, initialized on their first access. + +Users can get a static reference to a task pool via the `get` associated +function (i.e. `ComputeTaskPool::get`), which will initialize the TaskPool with +the default configuration if it hasn't already been initialized. + +For more explicit control over initialization, a corresponding `init` associated +function will take a task pool configuration and initialize the task pool, or +panic if the pool is already initialized. By convention, libraries, including +Bevy itself, should just use `get` and defer configuration to the final end +user's configuration. + +## Implementation strategy +Prototype implementation: https://github.com/bevyengine/bevy/pull/2250 + +This implementation is rather simple and can be broken down into the following +smaller tasks: + + - The public construction of the newtyped TaskPools should be disabled/removed. + - New static variables holding [`OnceCell`][once-cell] wrappers around each + task pool will be added. + - Move the default task pool initialization configuration from `bevy_core` into + `bevy_tasks`. + - Implement `get` and `init` functions as described above for each type. + - Replace `Res<*TaskPool>` accesses with `*TaskPool::get` calls. + - Optional: Remove the internal `Arc`s as the task pools to remove one level of + indirection. + - Optional: Remove the `TaskPool` parameters from `par_for_each(_mut)` on Query + +## Drawbacks +Reconfiguring the task pools is now impossible under this initial design. +Calling `init` a second time will panic. This could be remedied by +moving reconfiguration into the TaskPool itself. The current way of +reconfiguring the task pools is to construct a new task pool and replace the +resource. However, this does not properly clean up the TaskPools if they +internally are self-referential: a long-lived, or indefinitely looping scheduled +task that contains a reference to the same task pool will keep the TaskPool +alive until all tasks terminate. Moving this reconfiguration as a first class +operation on TaskPool may be required to support these use cases. + +Multitenant processes now share the same threads for all apps. Highly active or +long running apps may starve out others. Whether or not this is preferable over +OS-level thread thrashing is likely use case dependent. + +## Rationale and alternatives +This is a significant improvement in terms of ergonomics over the status quo. +This API design removes the need to have a task pool parameter on systems, +`Query`, and `QueryState` types. It also removes integration friction for third +party crates that aren't directly integrating with the ECS state. + +### Alternative: One Global Task Pool, Enum Tasks +This alternative proposes to remove the newtyped task pools entirely and just +have one global task pool instead. To get the same scheduling guarantee, tasks +would internally state which of the three task types it is. This would allow the +executors to independently schedule and prioritize tasks as they currently are, +but without forcing the user to make that decision, or at least allow users to +choose a common default. + +This is still technically possible, but would require much more effort to +implement. + +## Prior art +Other async executors also provide global versions of their executors: + + - [tokio][tokio]'s runtime is global by default + - [async-global-executor][async-global-executor] + +Other language's async runtimes also largely use globals as well: + + - Python's [asyncio event loop][asyncio-event-loop] is thread-local. Fetched + via `asyncio.get_event_loop()`. + - C#'s [managed-thread-pool][csharp-pool] is used for it's async runtime + - Go spins up a 1:1 threadpool that all of it's async goroutines run on, + scheduling is global and requires no reference. + +Other game engines have a built in global threadpool used for their form of +multithreading: + + - Unity has dedicated threads for rendering, audio, and a dedicated worker + thread pool for running multihreaded jobs. + - Unreal: ??? + - Godot: ??? + +[backroll]: https://github.com/HouraiTeahouse/backroll-rs/blob/219f4b6fda27250a7c4f7928a381418faebd5544/backroll/src/backend/p2p.rs +[once-cell]: https://docs.rs/once_cell/latest/once_cell/sync/struct.OnceCell.html +[asyncio-event-loop]: https://docs.python.org/3/library/asyncio-eventloop.html +[csharp-pool]: https://docs.microsoft.com/en-us/dotnet/standard/threading/the-managed-thread-pool diff --git a/rfcs/55-deterministic_rng.md b/rfcs/55-deterministic_rng.md new file mode 100644 index 00000000..d68496e8 --- /dev/null +++ b/rfcs/55-deterministic_rng.md @@ -0,0 +1,155 @@ +# Feature Name: `Deterministic RNG` + +## Summary + +Include a source of entropy to (optionally) enable deterministic random number generation. + +## Motivation + +Bevy games / applications often need to use randomness[1](#1) but doing so makes execution non-deterministic. This is problematic for automated testing as well as deterministic execution. Deterministic execution is important for those wishing to create games with [deterministic lockstep](https://gafferongames.com/post/deterministic_lockstep/) as well as high-fidelity simulations. + +Currently there is no official way to introduce randomness in bevy, a plugin, or an app. Other engines such as [Godot](https://docs.godotengine.org/en/stable/tutorials/math/random_number_generation.html), [Unity](https://docs.unity3d.com/ScriptReference/Random.html), and [Unreal](https://docs.unrealengine.com/4.27/en-US/BlueprintAPI/Math/Random/) include engine support for randomness. + + +1 For example, 16 of bevy's examples currently use `rand` +
+
+examples/games/contributors.rs
+examples/games/alien_cake_addict.rs
+examples/async_tasks/external_source_external_thread.rs
+examples/async_tasks/async_compute.rs
+examples/ecs/iter_combinations.rs
+examples/stress_tests/transform_hierarchy.rs
+examples/stress_tests/many_lights.rs
+examples/animation/custom_skinned_mesh.rs
+examples/stress_tests/bevymark.rs
+examples/stress_tests/many_sprites.rs
+examples/ecs/parallel_query.rs
+examples/ecs/component_change_detection.rs
+examples/ecs/ecs_guide.rs
+examples/app/random.rs
+crates/bevy_ecs/examples/resources.rs
+crates/bevy_ecs/examples/change_detection.rs
+
+
+
+ +## User-facing explanation + +### Overview + +Games often use randomness as a core mechanic. +For example, card games generate a random deck for each game and killing monsters in an RPG often rewards players with a random item. +While randomness makes games more interesting and increases replayability, it also makes games harder to test and prevents advanced techniques such as [deterministic lockstep](https://gafferongames.com/post/deterministic_lockstep/). + +Let's pretend you are creating a poker game where a human player can play against the computer. +The computer's poker logic is very simple--when the computer has a good hand, it bets all of its money. +To make sure the behavior works, you write a test to first check the computer's hand and if it is good confirm that all its money is bet. +If the test passes does it ensure the computer behaves as intended? Sadly, no. + +Because the deck is randomly shuffled for each game--without doing so the player would already know the card order from the previous game--it is not guaranteed that the computer player gets a good hand and thus the betting logic goes unchecked. +While there are ways around this--a fake deck that is not shuffled, running the test many times to increase confidence, breaking the logic into units and testing those--it would be very helpful to have randomness as well as a way to make it _less_ random. + +Luckily, when a computer needs a random number it doesn't use real randomness and instead uses a [pseudorandom number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator). +Popular Rust libraries containing pseudorandom number generators are [`rand`](https://crates.io/crates/rand) and [`fastrand`](https://crates.io/crates/fastrand). + +Pseudorandom number generators require a source of [entropy](https://en.wikipedia.org/wiki/Entropy) called a [random seed](https://en.wikipedia.org/wiki/Random_seed). +The random seed is used as input to generate numbers that _appear_ random but are instead in a specific and deterministic order. +For the same random seed, a pseudorandom number generator always returns the same numbers in the same order. + +For example, let's say you seed a pseudorandom number generator with `1234`. +You then ask for a random number between `10` and `99` and the pseudorandom number generator returns `12`. +If you run the program again with the same seed (`1234`) and ask for another random number between `1` and `99`, you will again get `12`. +If you then change the seed to `4567` and run the program, more than likely the result will not be `12` and will instead be a different number. +If you run the program again with the `4567` seed, you should see the same number from the previous `4567`-seeded run. + +There are many types of pseudorandom number generators each with their own strengths and weaknesses. +Because of this, Bevy does not include a pseudorandom number generator. +Instead, the `bevy_entropy` plugin includes a source of entropy to use as a random seed for your chosen pseudorandom number generator. + +Note that Bevy currently has [other sources of non-determinism](https://github.com/bevyengine/bevy/discussions/2480) unrelated to pseudorandom number generators. + +### Usage + +The `bevy_entropy` plugin ships with Bevy and is enabled by default. +If you do not need randomness, you may [disable the plugin](https://docs.rs/bevy/latest/bevy/app/struct.PluginGroupBuilder.html#method.disable) or use Bevy's [minimal set of plugins](https://docs.rs/bevy/latest/bevy/struct.MinimalPlugins.html). + +When enabled, `bevy_entropy` provides one world resource: `Entropy`. +`Entropy` is then used as the seed for the pseudorandom number generator of your choosing. +A complete example leveraging the [`StdRng`](https://docs.rs/rand/latest/rand/rngs/struct.StdRng.html) pseudorandom number generator from the [`rand`](https://crates.io/crates/rand) crate can be found in the examples. + +The default source of world entropy [`Entropy::default()`] is non-deterministic and seeded from the operating system. +It is guarenteed to be suitable for [cryptographic applications](https://en.wikipedia.org/wiki/Pseudorandom_number_generator#Cryptographic_PRNGs) but the actual algorithm is an implementation detail that will change over time. + +You may choose to determinstically seed your own world entropy via [`Entropy::from`]. +The seed you choose may have security implications or influence the distribution of the resulting random numbers. +See [this resource](https://rust-random.github.io/book/guide-seeding.html) for more details about how to pick a "good" random seed for your needs. + +Depending on your game and the type of randomness you require, when specifying a seed you will normally do one of the following: + +1. Get a good random seed out-of-band and hardcode it in the source. +2. Dynamically call to the OS and print the seed so the user can rerun deterministically. In games like [Factorio](https://www.factorio.com/) sharing random seeds is encouraged and supported. +3. Dynamically call to the OS and share the seed with a server so the client and server deterministically execute together. +4. Load the seed from a server so the client and server deterministically execute together. + +## Implementation strategy + +https://github.com/bevyengine/bevy/pull/2504 + +## Drawbacks + +- This may not be general enough to include in Bevy. +- This may not be general enough to be on by default. +- Includes `rand_core` and `rand_chacha` as a dependency. + - But not in public API. + +## Rationale and alternatives + +- Why is this design the best in the space of possible designs? + - It gives flexibility for PRNG crates while also enforcing standards on the ecosystem. + - It defaults to what someone would resonably expect if they wrote it themselves. + - It defaults to something safe (suitable for cryptographic functions), removing a possible footgun. +- What other designs have been considered and what is the rationale for not choosing them? + - We could go higher and expose an API closer to `rand`. This is what [Godot](https://docs.godotengine.org/en/stable/tutorials/math/random_number_generation.html), [Unity](https://docs.unity3d.com/ScriptReference/Random.html), and [Unreal](https://docs.unrealengine.com/4.27/en-US/BlueprintAPI/Math/Random/) do. We are not experts in PRNG API design, and while `rand` is clearly the most popular random crate in the Rust ecosystem we don't currently want to tie bevy to any particular API in case something better emerges. + - We could go lower and merely expose a static `WorldSeed`. I'm worried about what the default would be and forcing a seed at world creation feels heavyweight. + - We could default to a faster PRNG rather than a safer one. I wanted folks to fall into the pit of success. +- What objections immediately spring to mind? How have you addressed them? + - `rand_core` and `rand_chacha`are too heavy of a dependency. + - This should not be in core. +- What is the impact of not doing this? + - There is no chance of the Bevy ecosystem supporting deterministic execution. +- Why is this important to implement as a feature of Bevy itself, rather than an ecosystem crate? + - There needs to be one true way to keep the ecosystem coherant. + +## Prior art + +- https://github.com/bevyengine/bevy/discussions/2480 +- https://github.com/bevyengine/bevy/discussions/1678 + +Other engines such as [Godot](https://docs.godotengine.org/en/stable/tutorials/math/random_number_generation.html), [Unity](https://docs.unity3d.com/ScriptReference/Random.html), and [Unreal](https://docs.unrealengine.com/4.27/en-US/BlueprintAPI/Math/Random/) include engine support for randomness (with a higher-level API). + +- https://towardsdatascience.com/how-to-use-random-seeds-effectively-54a4cd855a79 +- https://www.whatgamesare.com/determinism.html +- https://gafferongames.com/post/deterministic_lockstep/ +- https://ruoyusun.com/2019/03/29/game-networking-2.html +- https://arxiv.org/abs/2104.06262 + +## Unresolved questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? + - Do we want this? + - ~~What about `rand`?~~ + - `rand` was [switched to](https://github.com/bevyengine/bevy/pull/2504/commits/d255fc40b65ea358c40c71a17852cd865387b869) `rand_core` and `rand_chacha`, thanks @bjorn3! + - Are `rand_core` and `rand_chacha` small enough? + - Is `Entropy` naming too obscure? + - How do we deal with security vs speed tradeoff here? + - Is this the right level of abstraction? Lower? Higher? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + - Built-in higher-level randomness APIs + - Entropy per-system rather than per-world + +## Future possibilities + +- Higher-level randomness APIs +- Entropy per-system rather than per-world +- Seed from arbitrary-length [`&[u8]`](https://github.com/bevyengine/rfcs/pull/55#issuecomment-1138159824)