Skip to content

Latest commit

 

History

History
75 lines (64 loc) · 4.13 KB

CONTRIBUTING.md

File metadata and controls

75 lines (64 loc) · 4.13 KB

Contributing

This document outlines some helpful guidelines if you wish to contribute to the code.

CI Requirements

  • We enforce rustfmt in CI. Try setting it up to format on save in your IDE (links for vscode, rustrover, neovim).
  • We enforce that cargo clippy passes in CI. Be sure to fix any warnings.
  • We also enforce that tests pass.

Project Structure

Code broadly useful to the ecosystem should go under the crates/ directory. Application-specific code should go under the apps/ directory. It is advisable to treat your code as application specific initially, and later migrate it to crates/ once you are more confident about how to use it outside your application specific context.

Style Guide

General guidance:

  • Key types, and any pub re-exports should always be at the top of the module. Readers should be able to get the big picture of what is happening from the top of the file.
  • Avoid over-commenting code. Only comment when you wouldn't otherwise know what a type or function is doing based on its name and arguments alone.
  • DO comment high level details that help a newcomer understand the organization of the codebase. Typically these are module-level doc comments that explain the scope and/or responsiblity of the module.
  • Limit your use of third party dependencies. Only bring it in if it greatly reduces the amound of code. In particular, avoid esoteric macro crates unless they are widely used. Generally you can do the same thing easily with a quick macro_rules.
  • Leverage cargo's workspace inheritance.
  • Avoid IO when possible. For example, instead of having a function called spawn_from_file(path: Path), consider a spawn_from_data(data: &[u8]). This is broadly useful for running the code in more contexts (including sandboxed ones), as well as atypical environments (such as on a server, or in tests). In other words, write code sans-io.
  • If a function might be called a lot, avoid allocating. For example instead of create_things() -> Vec<Thing>, consider if create_things(&mut Vec<Thing>) would be better - it allows the caller to reuse the vector. Don't take this rule too far though, if it looks like it is going to overcomplicate the API.
  • If a function is almost pure, try to make it pure (Carmack agrees).
  • If a function is only used in one place and is less than 25 lines of code, it should be inlined (Carmack agrees).
  • Don't associate functions to structs just because. Module-level functions are perfectly fine and are often preferred because they have less indentation.
  • Don't go crazy introducing generics and traits everywhere. Box<dyn T> or an enum is often fine, and usually a trait is overkill to begin with.
  • Unless there is some specific invariant you need to protect, prefer directly exposing fields of structs via pub over getters and setters.
  • Prefer newtypes for stronger type safety whenever possible. Types document code better than comments.

Bevy-specific guidance:

  • Group related systems and components into a single Plugin.
  • Keep plugins small if possible.
  • Try to break plugins into multiple submodules if there is more than a couple hundred lines of code. This helps keep the surface area of the plugin clear.
  • Plugins generally should not have modules that they are the grandparent of. I.e. either have my_plugin.rs, or my_plugin/mod.rs + my_plugin/group_of_things.rs Do not have my_plugin/subgroup/another_subgroup.rs, no one benefits from that deep of nesting and this isn't Java.
  • In a system, have the commands first, then mutable queries, then immutable ones.