From f52c8ab18ced33954e6a4ede0b5ab68151a63636 Mon Sep 17 00:00:00 2001 From: popertots Date: Sat, 21 Mar 2026 21:44:06 +0000 Subject: [PATCH] inventory: early return before HashSet alloc, restore doc comments --- src/entities/item/inventory/personal.rs | 28 +++++++++++++++++++++++++ src/entities/item/inventory/systems.rs | 22 +++++++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/entities/item/inventory/personal.rs b/src/entities/item/inventory/personal.rs index c7cec0f..ec33767 100644 --- a/src/entities/item/inventory/personal.rs +++ b/src/entities/item/inventory/personal.rs @@ -1,4 +1,32 @@ //! PersonalInventory — slots, strength-based carry weight, encumbrance tracking. +//! +//! # Design +//! Every entity that can carry items gets a PersonalInventory. Slot count is +//! species- and age-defined. Weight limit is strength-based. +//! +//! # Weight limit +//! max_carry_weight = BASE_CARRY_WEIGHT + strength * STRENGTH_CARRY_MULTIPLIER +//! + sum(carry_weight_bonus for each Container directly in slots) +//! +//! The bonus from held containers is re-evaluated whenever the slot contents +//! change. It is NOT recursive — containers inside containers do not contribute +//! their bonus to the carrier. +//! +//! # Encumbrance +//! When current_weight > max_carry_weight, the entity is encumbered. +//! The update_encumbrance system sets Ambulatory.walk_speed to +//! base_walk_speed * ENCUMBERED_SPEED_MULTIPLIER when over limit. +//! +//! # Item placement +//! Items are always placed in the best available slot at time of pickup — +//! the smallest container whose max_item_size >= item.size with a free slot. +//! No automatic reorganisation occurs. Reorganisation is an explicit task. +//! +//! # Life stage slot counts +//! Adult: species value (e.g. DORF_ADULT_SLOTS = 8) +//! Child: floor(adult / 2), minimum 0 +//! Baby: 1 if adult >= 2, else 0 +//! See slots_for_age(). use bevy::prelude::*; use smallvec::SmallVec; diff --git a/src/entities/item/inventory/systems.rs b/src/entities/item/inventory/systems.rs index 3ba678e..c4768fc 100644 --- a/src/entities/item/inventory/systems.rs +++ b/src/entities/item/inventory/systems.rs @@ -1,5 +1,19 @@ -//! Encumbrance system. Fires `InventoryChangedEvent` after any inventory mutation to -//! trigger walk_speed recalculation without per-tick iteration over all carriers. +//! Encumbrance system — updates walk_speed based on carry weight. +//! +//! # Event-driven design +//! Rather than iterating all carriers every tick, encumbrance recalculates only +//! for entities that fired an InventoryChangedEvent. At 5000 entities with rare +//! inventory changes, this reduces per-tick work from O(entities) to O(changes). +//! +//! Any code that mutates a PersonalInventory or a Container held by an entity +//! MUST fire InventoryChangedEvent { carrier } after the mutation. The carrier +//! is always the top-level entity with PersonalInventory — not a nested container. +//! +//! # base_walk_speed +//! Ambulatory gains base_walk_speed: f32 — the unmodified speed. Encumbrance +//! writes walk_speed = base_walk_speed * ENCUMBERED_SPEED_MULTIPLIER. +//! Restoring writes walk_speed = base_walk_speed. base_walk_speed is never +//! modified by encumbrance. use bevy::prelude::*; use rustc_hash::FxHashSet; @@ -19,10 +33,10 @@ pub fn update_encumbrance( mut carriers: Query<(&mut PersonalInventory, &mut Ambulatory)>, containers: Query<&Container>, ) { - let changed: FxHashSet = events.read().map(|e| e.carrier).collect(); - if changed.is_empty() { + if events.is_empty() { return; } + let changed: FxHashSet = events.read().map(|e| e.carrier).collect(); for carrier_entity in changed { let Ok((mut inventory, mut ambulatory)) = carriers.get_mut(carrier_entity) else {