From 4772489aad9ffd890c572f506e77671ce89129f6 Mon Sep 17 00:00:00 2001 From: Sam Sung <51686767+samwhosung@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:14:10 +0700 Subject: [PATCH] Arriving bodies composite off the main thread and show once dressed (#732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A crowd of arriving players was composited in one frame, on the main thread, under the archive lock: 40 geared looks took 76–115 ms. In 1.12.1 an unforced full composite never waits on its section loads (`0x44b430`), and a unit whose first composite is pending is not drawn: ShouldRender `0x607da0` feeds the draw flag `0x710c50`, and the flag goes to the mount model when mounted. It appears dressed once the composite is done. Now an arriving body composites on the async pool, one task per look, reading outside the lock. The body, and its mount, are built once the atlas lands. Arriving now costs the main thread 0.26–0.45 ms for 40 looks. The appear fade runs from arrival, as `FadeTo` is armed at create. A re-dress, a rig heal and the glue and dressing-room previews stay in the frame. Baked NPC skins are unchanged. Fixes #609 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/benilla-app/src/entities.rs | 72 +-- .../src/entities/attach/arrival_tests.rs | 455 ++++++++++++++++++ .../src/entities/attach/char_skin.rs | 207 ++++---- .../benilla-app/src/entities/attach/dress.rs | 5 +- crates/benilla-app/src/entities/attach/mod.rs | 101 +++- .../src/entities/attach/preview.rs | 26 +- .../src/entities/attach/redress.rs | 141 +++++- .../src/entities/equipment/spawn.rs | 10 +- .../benilla-app/src/entities/live_display.rs | 7 +- crates/benilla-app/src/entities/mount.rs | 19 +- .../benilla-app/src/entities/mount/tests.rs | 32 ++ .../src/entities/skin_composite.rs | 363 ++++++++++++++ crates/benilla-formats/src/characters/mod.rs | 2 +- .../src/characters/sections.rs | 96 ++-- crates/benilla-formats/src/lib.rs | 4 +- .../benilla-formats/tests/composite_cost.rs | 129 +++++ crates/benilla-world/src/model_fade.rs | 150 +++++- 17 files changed, 1588 insertions(+), 231 deletions(-) create mode 100644 crates/benilla-app/src/entities/attach/arrival_tests.rs create mode 100644 crates/benilla-app/src/entities/skin_composite.rs create mode 100644 crates/benilla-formats/tests/composite_cost.rs diff --git a/crates/benilla-app/src/entities.rs b/crates/benilla-app/src/entities.rs index 92de7054..a324a5f2 100644 --- a/crates/benilla-app/src/entities.rs +++ b/crates/benilla-app/src/entities.rs @@ -28,6 +28,10 @@ use display::{ mod attach; use attach::{attach_entity_visuals, build_dressup_preview, build_glue_pet, build_glue_preview}; +/// Composited body skins by look, built off the main thread. +mod skin_composite; +use skin_composite::{land_skin_composites, SkinComposites, SkinKey, SkinSections}; + /// The dynamic point lights an entity's own model carries, such as a held torch. mod carried_light; use carried_light::spawn_carried_lights; @@ -371,34 +375,28 @@ struct GameObjects { #[derive(Resource)] struct Characters(CharacterGeosets); -/// The `CharSections` skin lookup; without it a player's body skin stays untextured. -#[derive(Resource)] -struct SkinSections(CharSections); - /// Character-creation data (body displays, race and class combos, appearance ranges). #[derive(Resource)] pub(crate) struct CharCreate(pub(crate) CharCreateCatalog); -/// Composited body skins by look, so every player wearing a look shares one 256² atlas. -#[derive(Resource, Default)] -struct SkinComposites(benilla_assets::SpatialCache>); +/// When a net entity arrived, on the appear fade's clock: the reference stamps a unit's fade as its +/// create block is processed (`0x465c50` → `0x5fb880`, a player's through `0x5debe0` → +/// `0x613af0` at `0x5fb956` → `0x614f80`), before its model or composite is ready, so the ramp runs +/// from here however long the visual waits. A mount child carries its rider's, as the reference +/// writes the unit's one fade onto the mount model (`0x614ae6`–`0x614af4`, `0x614b9e`–`0x614bba`). +#[derive(Component, Clone, Copy, Debug, PartialEq)] +pub(crate) struct Arrival(pub(crate) f32); -/// What decides a composited body skin: race and sex pick the `CharSections` rows, the dials pick -/// the variations, and `equip` holds the worn armour display ids by body slot − 2. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub(super) struct SkinKey { - pub(super) race: u8, - pub(super) sex: u8, - pub(super) skin: u8, - pub(super) face: u8, - pub(super) facial_hair: u8, - pub(super) hair_style: u8, - pub(super) hair_color: u8, - pub(super) equip: [u32; 8], - /// The guild emblem: two guilds' members wear one tabard display but must not share an atlas. - pub(super) emblem: Option, - /// The tabard designer's preview: the emblem paints over an empty tabard slot. - pub(super) tabard_preview: bool, +/// Stamp each net entity's [`Arrival`] the frame it streams in. +fn stamp_arrivals( + mut commands: Commands, + time: Res