v0.22.0 — software-render dispatch overhead reduction
Software-render dispatch overhead reduction. Four independent paths cut per-entity and per-system work in the dirty + render flow: hash-indexed ECS lookups, an opt-in component filter on View, a scheduler skip hint on System, and a one-frame layout cache shared between the dirty and render walkers.
Added
World::has_any_by_id(type_id) -> bool(src/ecs/world.rs): true iff any live entity owns a component of the givenTypeId. Pairs with the existing per-entityhas_typefor a whole-storage emptiness probe in O(1).View::with_filter::<T>()builder (src/widget/view.rs): restrictsrenderdispatch to entities owning componentT. The walker checksworld.has_type(entity, TypeId::of::<T>())before invoking the view's render fn, hoisting the early-return guard most built-in views already had into the walker. All thirteen rendering built-in views opt in (Style,Button,Checkbox,Image,Text,Slider,Switch,TabBar,ProgressBar,TextInput,MirrorOf,TemporalMix,BackgroundBlur); user views without the filter behave as before.System::expect: &'static [fn() -> TypeId]field +with_expect(...)builder (src/ecs/system.rs): a non-empty slice gates the system onworld.has_any_by_id(tid)for any of the listed types. Empty slice (the default) preserves unconditional run. The slice element isfn() -> TypeIdrather thanTypeIdso callers can build it in const context on stable Rust.#[mirui::system(expect = T)]and#[mirui::system(expect = [T1, T2])]macro arguments (mirui-macros/src/lib.rs): forward the listed types toSystem::with_expect. Multi-entry slices use OR semantics — the system runs if any listed type has a live entity. Type paths resolve at the call site, supporting bare names,crate::...,::other_crate::..., and nested module paths.- Seven built-in systems gain
expecttags (src/components/switch.rs,src/components/text_input.rs,src/components/lazy_list.rs,src/components/tab_pages.rs,src/widget/offscreen.rs):switch_init_system(Switch),animate_switch_bg_t_system(AnimateSwitchBgT),animate_thumb_x_system(AnimateThumbX),cursor_blink_system(TextInput),lazy_list_system(LazyList),tab_pages_system(TabBar),maintain_widget_texture_refs(WidgetTextureReforOffscreenAutoAdded). Apps that don't instantiate these widgets avoid running those system bodies entirely.
Changed
World::storagesswitches fromVec<(TypeId, Box<dyn ...>)>toHashMap<TypeId, Box<dyn ...>>(src/ecs/world.rs): everyworld.get<T>/has_type/storage<T>lookup was a linear scan over a Vec; with dozens of registered component types and per-entity render dispatch hitting them thousands of times per frame, the scan dominated the hot path.despawniteratesvalues_mutinstead of indexed positions. Public API signatures are unchanged; see Breaking for drop-order consequences.World::resourcesswitches fromVectoHashMap(src/ecs/world.rs): same lookup shape as storages, applied to resource access. Public API signatures unchanged; see Breaking for drop-order consequences.- Dirty walker publishes a
LayoutSnapshotresource consumed by the render walker on the same frame (src/widget/render_system.rs,src/app.rs):collect_dirty_regionswrites its solved layout tree + entity preorder;App::render_dirtyhands the snapshot to a new internalrender_region_cachedentry point that skips the build / compute / collect phases. The publicpub fn render_regionkeeps building from scratch — the cached path ispub(crate)and only invoked when the same-frame dirty walker has just produced the snapshot, so external callers cannot consume a stale cache.
Breaking
Systemgains a publicexpect: &'static [fn() -> TypeId]field (src/ecs/system.rs): callers constructingSystemvia struct literal must addexpect: &[]for unconditional run, or switch to theSystem::new(name, priority, run)builder (and chain.with_expect(...)if needed). The builder path is unchanged.Worldno longer drops resources or component storages in insertion order (src/ecs/world.rs): both backing containers switched fromVectoHashMap, so cross-type drop order duringWorld::despawn(per-entity component drops) and duringWorldteardown (resource + storage drops) follows hash order instead. Code whoseDropimplementations rely on cross-type ordering must be updated to be order-independent. Single-type drops, and drops that touch only the world (not other types' state), are unaffected.
Fixed
View::installforwardsSystem::expectto the scheduler (src/widget/view.rs): the previous version rebuilt each registered system withSystem::new(name, priority, run), dropping theexpectslice that#[mirui::system(expect = ...)]had attached. Built-in widget systems registered through views (switch / text-input / lazy-list / tab-pages / texture-refs) carried tags that the scheduler then never saw. Nowview.installchains.with_expect(s.expect)so the tag round-trips. A regression test inview::testsinstalls a tagged dummy view and asserts the slice survives.