perf(engine): route remaining drag card→entity lookups through CardEntityIndex
Replace O(n) `Query::iter().find()` card scans with O(1) `CardEntityIndex`
lookups in the mouse and touch drag pipelines (`follow_drag`, `end_drag`,
`touch_follow_drag`, `touch_end_drag`) and `update_drag_shadow` — 7 sites
across 5 systems. Each ran per dragged card per frame during a drag.
`InputPlugin` now defensively `init_resource::<CardEntityIndex>()` (idempotent;
`CardPlugin` still owns and rebuilds it) so the plugin is self-sufficient in
tests. The lone remaining card-keyed `.find` is a `#[cfg(test)]` world-query
helper, which is the correct pattern there.
Completes the CardEntityIndex migration started in ef1efdc.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1487,6 +1487,7 @@ fn update_drag_shadow(
|
|||||||
drag: Res<DragState>,
|
drag: Res<DragState>,
|
||||||
layout: Option<Res<LayoutResource>>,
|
layout: Option<Res<LayoutResource>>,
|
||||||
card_entities: Query<(&CardEntity, &Transform)>,
|
card_entities: Query<(&CardEntity, &Transform)>,
|
||||||
|
card_index: Res<CardEntityIndex>,
|
||||||
mut shadow: Local<Option<Entity>>,
|
mut shadow: Local<Option<Entity>>,
|
||||||
) {
|
) {
|
||||||
if drag.is_idle() {
|
if drag.is_idle() {
|
||||||
@@ -1503,9 +1504,9 @@ fn update_drag_shadow(
|
|||||||
|
|
||||||
// Find the world position of the first (top) dragged card.
|
// Find the world position of the first (top) dragged card.
|
||||||
let top_pos = drag.cards.first().and_then(|first_card| {
|
let top_pos = drag.cards.first().and_then(|first_card| {
|
||||||
card_entities
|
card_index
|
||||||
.iter()
|
.get(first_card)
|
||||||
.find(|(marker, _)| marker.card == *first_card)
|
.and_then(|entity| card_entities.get(entity).ok())
|
||||||
.map(|(_, t)| t.translation)
|
.map(|(_, t)| t.translation)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ use solitaire_core::game_state::GameState;
|
|||||||
use crate::auto_complete_plugin::AutoCompleteState;
|
use crate::auto_complete_plugin::AutoCompleteState;
|
||||||
use crate::card_animation::tuning::AnimationTuning;
|
use crate::card_animation::tuning::AnimationTuning;
|
||||||
use crate::card_animation::{CardAnimation, MotionCurve};
|
use crate::card_animation::{CardAnimation, MotionCurve};
|
||||||
use crate::card_plugin::{CardEntity, HintHighlight, HintHighlightTimer, STACK_FAN_FRAC};
|
use crate::card_plugin::{
|
||||||
|
CardEntity, CardEntityIndex, HintHighlight, HintHighlightTimer, STACK_FAN_FRAC,
|
||||||
|
};
|
||||||
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
||||||
use crate::events::{
|
use crate::events::{
|
||||||
DrawRequestEvent, ForfeitRequestEvent, HintVisualEvent, InfoToastEvent, MoveRejectedEvent,
|
DrawRequestEvent, ForfeitRequestEvent, HintVisualEvent, InfoToastEvent, MoveRejectedEvent,
|
||||||
@@ -121,6 +123,10 @@ impl Plugin for InputPlugin {
|
|||||||
.init_resource::<HintSolverConfig>()
|
.init_resource::<HintSolverConfig>()
|
||||||
.init_resource::<crate::pending_hint::PendingHintTask>()
|
.init_resource::<crate::pending_hint::PendingHintTask>()
|
||||||
.init_resource::<GameInputConsumedResource>()
|
.init_resource::<GameInputConsumedResource>()
|
||||||
|
// The drag systems resolve cards via `CardEntityIndex`; `CardPlugin`
|
||||||
|
// owns and rebuilds it, but init here too so `InputPlugin` is
|
||||||
|
// self-sufficient in tests (idempotent if already registered).
|
||||||
|
.init_resource::<CardEntityIndex>()
|
||||||
.add_message::<StartZenRequestEvent>()
|
.add_message::<StartZenRequestEvent>()
|
||||||
.add_message::<InfoToastEvent>()
|
.add_message::<InfoToastEvent>()
|
||||||
.add_message::<ForfeitRequestEvent>()
|
.add_message::<ForfeitRequestEvent>()
|
||||||
@@ -674,6 +680,7 @@ fn follow_drag(
|
|||||||
layout: Option<Res<LayoutResource>>,
|
layout: Option<Res<LayoutResource>>,
|
||||||
tuning: Res<AnimationTuning>,
|
tuning: Res<AnimationTuning>,
|
||||||
mut card_transforms: Query<(&CardEntity, &mut Transform, &mut Sprite)>,
|
mut card_transforms: Query<(&CardEntity, &mut Transform, &mut Sprite)>,
|
||||||
|
card_index: Res<CardEntityIndex>,
|
||||||
) {
|
) {
|
||||||
// Skip if idle or if a touch drag is running.
|
// Skip if idle or if a touch drag is running.
|
||||||
if drag.is_idle() || drag.active_touch_id.is_some() {
|
if drag.is_idle() || drag.active_touch_id.is_some() {
|
||||||
@@ -704,9 +711,8 @@ fn follow_drag(
|
|||||||
// Elevate cards: push to DRAG_Z and dim slightly so the board
|
// Elevate cards: push to DRAG_Z and dim slightly so the board
|
||||||
// beneath stays readable.
|
// beneath stays readable.
|
||||||
for (i, card) in drag.cards.iter().enumerate() {
|
for (i, card) in drag.cards.iter().enumerate() {
|
||||||
if let Some((_, mut transform, mut sprite)) = card_transforms
|
if let Some(entity) = card_index.get(card)
|
||||||
.iter_mut()
|
&& let Ok((_, mut transform, mut sprite)) = card_transforms.get_mut(entity)
|
||||||
.find(|(ce, _, _)| ce.card == *card)
|
|
||||||
{
|
{
|
||||||
transform.translation.z = dragged_card_z(i);
|
transform.translation.z = dragged_card_z(i);
|
||||||
sprite.color.set_alpha(0.85);
|
sprite.color.set_alpha(0.85);
|
||||||
@@ -719,9 +725,8 @@ fn follow_drag(
|
|||||||
let fan = -layout.0.card_size.y * layout.0.tableau_fan_frac;
|
let fan = -layout.0.card_size.y * layout.0.tableau_fan_frac;
|
||||||
|
|
||||||
for (i, card) in drag.cards.iter().enumerate() {
|
for (i, card) in drag.cards.iter().enumerate() {
|
||||||
if let Some((_, mut transform, _)) = card_transforms
|
if let Some(entity) = card_index.get(card)
|
||||||
.iter_mut()
|
&& let Ok((_, mut transform, _)) = card_transforms.get_mut(entity)
|
||||||
.find(|(ce, _, _)| ce.card == *card)
|
|
||||||
{
|
{
|
||||||
transform.translation.x = bottom_pos.x;
|
transform.translation.x = bottom_pos.x;
|
||||||
transform.translation.y = bottom_pos.y + fan * i as f32;
|
transform.translation.y = bottom_pos.y + fan * i as f32;
|
||||||
@@ -743,6 +748,7 @@ fn end_drag(
|
|||||||
mut changed: MessageWriter<StateChangedEvent>,
|
mut changed: MessageWriter<StateChangedEvent>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
card_entities: Query<(Entity, &CardEntity, &Transform)>,
|
card_entities: Query<(Entity, &CardEntity, &Transform)>,
|
||||||
|
card_index: Res<CardEntityIndex>,
|
||||||
) {
|
) {
|
||||||
if paused.is_some_and(|p| p.0) {
|
if paused.is_some_and(|p| p.0) {
|
||||||
drag.clear();
|
drag.clear();
|
||||||
@@ -830,9 +836,8 @@ fn end_drag(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let target_pos = card_position(&game.0, &layout.0, &origin, stack_index);
|
let target_pos = card_position(&game.0, &layout.0, &origin, stack_index);
|
||||||
if let Some((entity, _, transform)) = card_entities
|
if let Some(entity) = card_index.get(card)
|
||||||
.iter()
|
&& let Ok((_, _, transform)) = card_entities.get(entity)
|
||||||
.find(|(_, ce, _)| ce.card == *card)
|
|
||||||
{
|
{
|
||||||
let drag_pos = transform.translation.truncate();
|
let drag_pos = transform.translation.truncate();
|
||||||
let drag_z = transform.translation.z;
|
let drag_z = transform.translation.z;
|
||||||
@@ -930,6 +935,7 @@ fn touch_follow_drag(
|
|||||||
layout: Option<Res<LayoutResource>>,
|
layout: Option<Res<LayoutResource>>,
|
||||||
tuning: Res<AnimationTuning>,
|
tuning: Res<AnimationTuning>,
|
||||||
mut card_transforms: Query<(&CardEntity, &mut Transform, &mut Sprite)>,
|
mut card_transforms: Query<(&CardEntity, &mut Transform, &mut Sprite)>,
|
||||||
|
card_index: Res<CardEntityIndex>,
|
||||||
) {
|
) {
|
||||||
let Some(active_id) = drag.active_touch_id else {
|
let Some(active_id) = drag.active_touch_id else {
|
||||||
return; // Mouse drag or idle.
|
return; // Mouse drag or idle.
|
||||||
@@ -957,9 +963,8 @@ fn touch_follow_drag(
|
|||||||
drag.committed = true;
|
drag.committed = true;
|
||||||
|
|
||||||
for (i, card) in drag.cards.iter().enumerate() {
|
for (i, card) in drag.cards.iter().enumerate() {
|
||||||
if let Some((_, mut transform, mut sprite)) = card_transforms
|
if let Some(entity) = card_index.get(card)
|
||||||
.iter_mut()
|
&& let Ok((_, mut transform, mut sprite)) = card_transforms.get_mut(entity)
|
||||||
.find(|(ce, _, _)| ce.card == *card)
|
|
||||||
{
|
{
|
||||||
transform.translation.z = dragged_card_z(i);
|
transform.translation.z = dragged_card_z(i);
|
||||||
sprite.color.set_alpha(0.85);
|
sprite.color.set_alpha(0.85);
|
||||||
@@ -971,9 +976,8 @@ fn touch_follow_drag(
|
|||||||
let fan = -layout.0.card_size.y * layout.0.tableau_fan_frac;
|
let fan = -layout.0.card_size.y * layout.0.tableau_fan_frac;
|
||||||
|
|
||||||
for (i, card) in drag.cards.iter().enumerate() {
|
for (i, card) in drag.cards.iter().enumerate() {
|
||||||
if let Some((_, mut transform, _)) = card_transforms
|
if let Some(entity) = card_index.get(card)
|
||||||
.iter_mut()
|
&& let Ok((_, mut transform, _)) = card_transforms.get_mut(entity)
|
||||||
.find(|(ce, _, _)| ce.card == *card)
|
|
||||||
{
|
{
|
||||||
transform.translation.x = bottom_pos.x;
|
transform.translation.x = bottom_pos.x;
|
||||||
transform.translation.y = bottom_pos.y + fan * i as f32;
|
transform.translation.y = bottom_pos.y + fan * i as f32;
|
||||||
@@ -998,6 +1002,7 @@ fn touch_end_drag(
|
|||||||
mut changed: MessageWriter<StateChangedEvent>,
|
mut changed: MessageWriter<StateChangedEvent>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
card_entities: Query<(Entity, &CardEntity, &Transform)>,
|
card_entities: Query<(Entity, &CardEntity, &Transform)>,
|
||||||
|
card_index: Res<CardEntityIndex>,
|
||||||
) {
|
) {
|
||||||
let Some(active_id) = drag.active_touch_id else {
|
let Some(active_id) = drag.active_touch_id else {
|
||||||
return; // Mouse drag or idle.
|
return; // Mouse drag or idle.
|
||||||
@@ -1070,9 +1075,8 @@ fn touch_end_drag(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let target_pos = card_position(&game.0, &layout.0, &origin, stack_index);
|
let target_pos = card_position(&game.0, &layout.0, &origin, stack_index);
|
||||||
if let Some((entity, _, transform)) = card_entities
|
if let Some(entity) = card_index.get(card)
|
||||||
.iter()
|
&& let Ok((_, _, transform)) = card_entities.get(entity)
|
||||||
.find(|(_, ce, _)| ce.card == *card)
|
|
||||||
{
|
{
|
||||||
let drag_pos = transform.translation.truncate();
|
let drag_pos = transform.translation.truncate();
|
||||||
let drag_z = transform.translation.z;
|
let drag_z = transform.translation.z;
|
||||||
|
|||||||
Reference in New Issue
Block a user