Files
reliquary/docs/workpad/research/bevy-skill-research/02-ecs-core.md
T
g4borg 325e278af1 Scaffold bevy plugin with bevy-0.19 and bevy-upgrade skills
Add plugin skeleton with versioned skill directories, TODO SKILL.md
scaffolds with goals for skill-creator, and copy over bevy-skill-research
reports from spacecraft into docs/workpad/research/.
2026-07-07 17:52:00 +02:00

4.5 KiB

Bevy Skill Research: ECS Core

Bevy version: 0.18, with 0.19 notes
Date: 2026-07-07

Components

Derive: #[derive(Component)] on structs/enums. Requires Send + Sync + 'static.

Storage:

  • Table (default) — fast iteration
  • #[component(storage = "SparseSet")] — fast insert/remove

Immutable components (0.18+): #[component(immutable)] — restricts to &T only, no &mut T.

Required components: #[require(B, C)] — auto-inserts B and C when this component is added. Supports constructors: B(value), C { field: val }, function calls, = expr.

Hooks: on_add, on_insert, on_replace, on_remove, on_despawn — set via #[component(on_add = fn_name)] or self-referential #[component(on_add)].

Clone behavior: #[component(clone_behavior = Ignore)] to skip during entity clone.

Bundles removed (0.16+): Use #[require(...)] or spawn with tuples instead of #[derive(Bundle)].

Resources

Type Purpose
Res<T> Shared read access
ResMut<T> Exclusive write access
Option<Res<T>> / Option<ResMut<T>> Resources that may not exist
Local<'a, T> Per-system private state, persists across calls, default-initialized
NonSend<T> / NonSendMut<T> For !Send resources

World methods: init_resource::<R>() (uses Default), insert_resource(value), remove_resource::<R>().

0.19 change: Resources implement Component and live on abstract entities. Query<Entity> now matches resource entities — filter with Without<IsResource>. Cannot derive both Component and Resource on one type.

Systems

Standard: any fn(params...) { } where each param implements SystemParam.

Fallible (0.18+): fn system(params...) -> Result — bare Result (alias for Result<(), BevyError>). Errors are logged, system continues on next tick. ? operator works with any error convertible to BevyError.

Exclusive: fn system(world: &mut World) { } — full world access, blocks parallelism.

Registration: app.add_systems(Update, (sys_a, sys_b)) or app.add_systems(Startup, setup).

Valid SystemParam Types

Query, Res, ResMut, Commands, Local, MessageReader, MessageWriter, ParamSet, Single, Populated, SystemName, SystemChangeTick, RemovedComponents<T>, DeferredWorld, &World, (), Option<T>, Result<T, SystemParamValidationError>, PhantomData<T>.

Queries

Signature: Query<D, F> where D: QueryData, F: QueryFilter (default ()).

Data Parameters

  • &T — read
  • &mut T — write
  • Entity — entity ID
  • Has<T> — returns bool
  • Tuples of the above

Filters

Filter Fires when
With<T> Entity has component T
Without<T> Entity lacks component T
Added<T> First tick after component added
Changed<T> First tick after add or mut deref
Spawned First tick after entity spawn (new)
Or<(A, B)> Either filter matches

Key Methods

  • .iter() / .iter_mut() — iterate all matching
  • .get(entity) / .get_mut(entity) — single entity lookup
  • .single() — panics if not exactly one match
  • .par_iter() / .par_iter_mut() — parallel iteration

Specialized System Params

  • Single<D, F> — system param alternative to query.single(), panics if not exactly one
  • Populated<D, F> — system skips execution if query is empty

Custom Derives

  • #[derive(QueryData)] — for complex query data
  • #[derive(QueryFilter)] — for reusable filter combinations

Commands

Method Purpose
commands.spawn((CompA, CompB)) Spawn entity with tuple, returns EntityCommands
commands.spawn_empty() Empty entity, chain .insert(bundle)
commands.spawn_batch(iter) Batch spawn from iterator
commands.entity(e).insert(comp) Add component to existing entity
commands.entity(e).insert_if_new(comp) Add only if absent
commands.entity(e).remove::<T>() Remove component
commands.entity(e).despawn() Destroy entity (auto-recursive with children)
commands.queue(custom_command) Push custom Command impl

System Ordering

  • (a, b).chain()a runs before b
  • .before(system_or_set) / .after(system_or_set) — explicit ordering
  • #[derive(SystemSet)] — define named sets for grouping
  • app.configure_sets(Update, SetA.before(SetB)) — order sets
  • AmbiguousSystemConflictsWarning warns about unordered systems with overlapping access