bevy systemparam grouping

Guide for bevy systemparam grouping

Bevy ECS UI Parameter Grouping Pattern

Problem

Bevy has a hard limit of 16 parameters for system functions. Complex UI systems often need:
  • Multiple queries (player, inventory, equipment, skills, etc.)
  • Multiple resources (game data, UI state, assets, etc.)
  • Multiple event writers (use item, equip, unequip, cast spell, etc.)
This quickly exceeds the 16-parameter limit.

Solution: SystemParam Struct

Group related parameters into a custom SystemParam struct.

Implementation

File: src/systems/ui/mod.rs
#[derive(bevy::ecs::system::SystemParam)]
pub struct UiParams<'w, 's> {
    pub ui_assets: Res<'w, UiAssets>,
    pub interactables: Query<'w, 's, &'static crate::systems::api::Interactable>,
    pub player_q: Query<'w, 's, (
        Entity, 
        &'static crate::systems::player::skills::Skills, 
        Option<&'static crate::systems::skills::magic::ActiveSpell>
    ), With<crate::systems::player::Player>>,
    pub skills_q: Query<'w, 's, &'static crate::systems::player::skills::Skills>,
    pub inventory_q: Query<'w, 's, &'static crate::systems::player::inventory::Inventory>,
    pub equipment_q: Query<'w, 's, &'static crate::systems::player::equipment::Equipment>,
    pub skills_data: Res<'w, crate::systems::data::SkillsData>,
}

Usage in System

fn ui_main_layout(
    mut contexts: EguiContexts,
    mut ui_state: ResMut<UiState>,
    mut combat_style_writer: EventWriter<CombatStyleEvent>,
    editor_state: Option<Res<crate::systems::editor::EditorState>>,
    mut last_interaction: Local<String>,
    mut interaction_reader: EventReader<InteractionEvent>,
    
    params: UiParams,  // Single parameter containing all grouped params
    
    mut plugin_ui_state: ResMut<PluginUiState>,
    mut use_item_writer: EventWriter<crate::systems::player::inventory::UseItemEvent>,
    mut unequip_item_writer: EventWriter<crate::systems::player::equipment::UnequipItemEvent>,
    mut spell_book_state: ResMut<spell_book_ui::SpellBookState>,
    mut select_spell_writer: EventWriter<crate::systems::skills::magic::SelectSpellEvent>,
    mut coconut_ui_state: ResMut<coconut_stacking_ui::CoconutUIState>,
) {
    // Unpack for easier usage
    let ui_assets = params.ui_assets;
    let player_q = params.player_q;
    let inventory_q = params.inventory_q;
    let equipment_q = params.equipment_q;
    // ... use as normal
}

Benefits

  1. Stays Under Limit: Counts as 1 parameter instead of N
  2. Organized: Groups related parameters logically
  3. Reusable: Can use same struct in multiple systems
  4. Type-Safe: Full compile-time checking

Grouping Guidelines

Good Candidates for Grouping

  • Read-only queries (player, inventory, skills, etc.)
  • Resources that are frequently used together
  • Data that represents a "view" of the game state

Keep Separate

  • Event writers (often need mut)
  • State resources that are modified (ResMut)
  • Local state (Local<T>)
  • Context objects (EguiContexts)

Example Grouping Strategy

// Group 1: Read-only game state
#[derive(SystemParam)]
pub struct GameStateParams<'w, 's> {
    pub player_q: Query<...>,
    pub inventory_q: Query<...>,
    pub equipment_q: Query<...>,
}

// Group 2: UI resources
#[derive(SystemParam)]
pub struct UiResourceParams<'w> {
    pub ui_assets: Res<'w, UiAssets>,
    pub game_data: Res<'w, GameData>,
    pub theme: Res<'w, UiTheme>,
}

// Keep separate: mutable state and event writers
fn my_system(
    game_state: GameStateParams,
    ui_resources: UiResourceParams,
    mut ui_state: ResMut<UiState>,
    mut events: EventWriter<MyEvent>,
) { ... }

Lifetime Parameters

  • 'w: World lifetime (for resources)
  • 's: System state lifetime (for queries)
Both are required when deriving SystemParam.

Common Pitfalls

❌ Don't group mutable state

// BAD: Can't have multiple mutable borrows
#[derive(SystemParam)]
pub struct BadParams<'w> {
    pub state1: ResMut<'w, State1>,
    pub state2: ResMut<'w, State2>,  // Might conflict!
}

✅ Do group read-only queries

// GOOD: Multiple immutable borrows are fine
#[derive(SystemParam)]
pub struct GoodParams<'w, 's> {
    pub query1: Query<'w, 's, &'static Component1>,
    pub query2: Query<'w, 's, &'static Component2>,
}

Real-World Example

Before (18 parameters - won't compile):
fn ui_system(
    contexts: EguiContexts,
    ui_state: ResMut<UiState>,
    ui_assets: Res<UiAssets>,
    player_q: Query<...>,
    inventory_q: Query<...>,
    equipment_q: Query<...>,
    skills_q: Query<...>,
    game_data: Res<GameData>,
    // ... 10 more parameters
) { }
After (12 parameters - compiles):
fn ui_system(
    contexts: EguiContexts,
    ui_state: ResMut<UiState>,
    params: UiParams,  // Groups 7 parameters
    // ... 9 more parameters
) { }