{
  "path": "/bevy-labels.html",
  "site": "at://did:plc:x67qh7v3fd7znbdhauc45ng3/site.standard.publication/3mjcd2t6afe25",
  "$type": "site.standard.document",
  "title": "How Bevy uses Rust traits for labeling",
  "updatedAt": "2022-01-10T00:00:00.000Z",
  "publishedAt": "2022-01-10T00:00:00.000Z",
  "textContent": "Out of curiosity I've recently started following the development of [Bevy],\na game engine written in [Rust].\nToday I want to talk about how Bevy uses Rust traits to let users very conveniently label elements.\n\nNote: The implementation we arrive at is actually very generic\n-- you can easily apply it to any other Rust project.\n\nHow to bevy\n\nBevy really wants you to use its entity-component-system (ECS) architecture\nto structure your games.\nWhat is boils down to is writing functions (\"systems\")\nthat use queries to fetch and update components and resources.\nYou define what you app/game is by telling Bevy which systems exists\nand how they might be combined.\n\nAside: These \"systems functions\" are super interesting in themselves:\nThey are just regular Rust functions with specific parameters\nand through type-system magic (read: traits) Bevy knows how to call them.\nI wrote a separate article on them [here][ecs post].\n\n[ecs post]: https://deterministic.space/bevy-ecs-rust-type-system.html \"The Rust features that make Bevy’s systems work\"\n\nHere's a simple Bevy 0.6 app:\n\n_Spoiler:_ This will spam your terminal with how long the app has been running.\n\nDefining system relationships using labels\n\nBevy has a very neat scheduler\nthat is able to run all systems that operate on disjoint data in parallel.\nIf you want to specify that some systems have to run before others,\nyou have to annotate this.\n\nHere's another, slightly more complex example.\nNote that to not be immediately presented with a wall of text,\nwe have changed the add_system to add_startup_system.\nThis means the system is only run once, at start-up.\n\nThe idea is that we first setup the world with its map\nand then spawn the component(s) that represent our player.\nIf you run this, you will see two lines \"one\", \"two\", in that order.\n\nThe important two lines of code are the where we give our system the label(\"world\"),\nand where the other system can refer to that label\nand declare it wants to run after(\"world\").\n\nAside: How does this work internally?\nWell, long story short, that [after][ParallelSystemDescriptorCoercion] method turns your system function into a [ParallelSystemDescriptor]\nwith metadata that the scheduler can pick up and build a graph from.\n\n[ParallelSystemDescriptorCoercion]: https://docs.rs/bevy/0.6.0/bevy/ecs/schedule/trait.ParallelSystemDescriptorCoercion.html \"ParallelSystemDescriptorCoercion in bevy::ecs::schedule\"\n\nFeel free to play with this!\nChange it to before(\"world\"),\nchange the order in which the systems are added,\nadd more systems, etc.\n\nImagine this:\nIt's a bit later in the month and\nwe have a whole game built using dozens of systems.\nBut for some reason the player movement seems a bit broken,\nlike it's rendering one frame too late.\nWhat is the issue?\nAfter two hours and too much coffee we realize[^warn] that\nwe wrote .after(\"imput\").\n\nHow can we make sure that a simple typo won't break our game again?\n\n[^warn]:\n    To be fair, with the LogPlugin Bevy prints a _warning_ on start-up about an unknown label.\n    But our example immediately starts printing a lot of other things,\n    and I guess in this imaginary scenario drinking all this coffee didn't make us more alert after all.\n\nGet me out of this stringly-typed mess\n\nSo far we've used strings to define and refer to labels,\nbut if you look at the definition of the label, before, and after methods [here][ParallelSystemDescriptorCoercion]\nyou will see they actually accept anything that implements [SystemLabel].\n\nIf you go to Bevy's API docs you can see SystemLabel is a trait and defined as\n\nLook at all these bounds!\nYou might recognize a few from usual Rust code,\nbut DynHash stands out as one trait defined in bevy::utils.\nWe'll come back to it later, and just treat it as the regular Hash trait for now.\n\nSystemLabel also looks like an empty trait -- but that's actually an illusion.\nIts only item is hidden in the docs.\nWe can assume that's because its an implementation detail,\nand instead of implementing this trait manually,\nwe are supposed to derive it.\nIndeed, there is a [SystemLabel derive macro].\n\nOkay, so to get a type to be a SystemLabel,\nit needs to implement Debug, Hash\n(the compiler will figure 'static + Send + Sync out for us).\nAs you might know, to derive Hash, we also need to derive PartialEq + Eq.\nAnd by experimentation and reading compiler errors,\nwe can see that the SystemLabel derive actually also adds a requirement on Clone.\n\nIn the end we arrive at something like this:\n\nWhich we can use just like our string previously:\n\nSome notes on the magic\n\nSo what is the deal with that [DynHash] trait?\nIf you look at the [API docs][DynHash],\nyou can see that it requires an implementation of DynEq\n(also from Bevy),\nwhich in turn requires an implementation of Any.\n\n[DynHash]: https://docs.rs/bevy/0.6.0/bevy/utils/label/trait.DynHash.html \"DynHash in bevy::utils::label\"\n\nIts methods are also kind of strange:\nCompared to standard library's [Hash] trait,\nthere are no generics, but a lot of dyn keywords.\nThis looks to me that someone went out of their way to make\nan [object-safe] version of Hash.\n\nThe good news is: Users of the API don't have to care:\nDynHash is implemented for all types that implement Hash and DynEq,\nand DynEq is in turn implemented for all types that implement Eq and Any,\n\nAnother thing that seems magical is that hidden trait method on SystemLabel,\nwhich is actually called dyn_clone.\nSimilar to the other dynamic trait implementations,\nthis allows cloning any SystemLabel type,\neven if all you have is a Box<dyn SystemLabel>.\n\nGeneric label types\n\nDid you think we were done?\nOh no! There one more thing:\nSystemLabel is not alone!\nThere is also StageLabel, AmbiguitySetLabel, and RunCriteriaLabel.\n\nThese label types are pretty much all the same,\nbut distinct traits in the type system.\nThat means you will have to explicitly derive StageLabel\nif you want to use your type to refer to a stage;\nyou can't use a SystemLabel or any other label for that.\n\nThis is another safety guarantee:\nWe already saw that you can mess up your stringy labels by making typos,\nbut you can also type everything correctly\nand still refer to a \"stage label\" in place of a \"system label\".\nIf you use custom types instead of strings, however,\nthe compiler will not let you confuse them.\n\nIn true Rust fashion all of these labels are implemented using macros.\nThe macro is called [define_label]\nand it's used [here][label.rs]\nto create all the label traits for the scheduler.\n\n[label.rs]: https://github.com/bevyengine/bevy/blob/e56685370ba82003af60a491667fac209a0f7897/crates/bevy_ecs/src/schedule/label.rs#L4-L7 \"bevy/crates/bevy_ecs/src/schedule/label.rs\"\n\nThe derive macros are a bit more manual,\nand they live in the bevy_ecs_macros crate [here][macros].\n\n[macros]: https://github.com/bevyengine/bevy/blob/8009af3879fcdb8bad70ee19b36f79100da5ea22/crates/bevy_ecs/macros/src/lib.rs#L429-L438 \"bevy/crates/bevy_ecs/macros/src/lib.rs\"\n[Bevy]: https://bevyengine.org/ \"Bevy Engine\"\n[Rust]: https://www.rust-lang.org/ \"Rust Programming Language\"\n[ParallelSystemDescriptor]: https://docs.rs/bevy/0.6.0/bevy/ecs/schedule/struct.ParallelSystemDescriptor.html \"ParallelSystemDescriptor in bevy::ecs::schedule\"\n[SystemLabel]: https://docs.rs/bevy/0.6.0/bevy/ecs/schedule/trait.SystemLabel.html \"SystemLabel in bevy::ecs::schedule\"\n[SystemLabel derive macro]: https://docs.rs/bevy/0.6.0/bevy/ecs/schedule/derive.SystemLabel.html \"SystemLabel in bevy::ecs::schedule\"\n[Hash]: https://doc.rust-lang.org/1.57.0/core/hash/trait.Hash.html \"Hash in core::hash\"\n[object-safe]: https://doc.rust-lang.org/book/ch17-02-trait-objects.html \"Redirecting...\"\n[define_label]: https://docs.rs/bevy/0.6.0/bevy/utils/macro.define_label.html \"define_label in bevy::utils\"",
  "canonicalUrl": "https://deterministic.space/bevy-labels.html"
}