use bevy_ecs::prelude::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::time::Instant;
use super::types::{
ButtonBuffer, CursorVelocity, DoubleClick, PhysicalPoint, PointerBuffer, PointerButton,
PointerState, PositionSample, WheelBuffer,
};
thread_local! {
pub(crate) static POINTER_BUFFERS: RefCell<HashMap<Entity, PointerBuffer>> = RefCell::new(HashMap::new());
pub(crate) static BUTTON_BUFFERS: RefCell<HashMap<(Entity, PointerButton), ButtonBuffer>> = RefCell::new(HashMap::new());
pub(crate) static WHEEL_BUFFERS: RefCell<HashMap<Entity, WheelBuffer>> = RefCell::new(HashMap::new());
pub(crate) static DOUBLE_CLICK_BUFFERS: RefCell<HashMap<Entity, DoubleClick>> = RefCell::new(HashMap::new());
pub(crate) static MODIFIER_STATE: RefCell<HashMap<Entity, (bool, bool)>> = RefCell::new(HashMap::new());
}
#[inline]
fn wake_pointer() {
crate::ecs::world::tick_wake::mark(crate::ecs::world::tick_wake::POINTER);
}
#[inline]
pub(crate) fn push_pointer_sample(entity: Entity, x: f32, y: f32, timestamp: Instant) {
wake_pointer();
tracing::trace!(
entity = ?entity,
x, y,
thread_id = ?std::thread::current().id(),
"[push_pointer_sample] Sample added"
);
POINTER_BUFFERS.with(|buffers| {
let mut buffers = buffers.borrow_mut();
let buffer = buffers.entry(entity).or_insert_with(PointerBuffer::new);
buffer.push(PositionSample { x, y, timestamp });
});
}
#[inline]
pub(crate) fn record_button_down(entity: Entity, button: PointerButton) {
wake_pointer();
BUTTON_BUFFERS.with(|buffers| {
let mut buffers = buffers.borrow_mut();
let buffer = buffers.entry((entity, button)).or_default();
buffer.record_down();
tracing::info!(
entity = ?entity,
button = ?button,
"[ButtonBuffer] record_button_down"
);
});
}
#[inline]
pub(crate) fn record_button_up(entity: Entity, button: PointerButton) {
wake_pointer();
BUTTON_BUFFERS.with(|buffers| {
let mut buffers = buffers.borrow_mut();
let buffer = buffers.entry((entity, button)).or_default();
buffer.record_up();
tracing::info!(
entity = ?entity,
button = ?button,
"[ButtonBuffer] record_button_up"
);
});
}
#[inline]
pub(crate) fn add_wheel_vertical(entity: Entity, delta: i16) {
wake_pointer();
WHEEL_BUFFERS.with(|buffers| {
let mut buffers = buffers.borrow_mut();
let buffer = buffers.entry(entity).or_default();
buffer.add_vertical(delta);
});
}
#[inline]
pub(crate) fn add_wheel_horizontal(entity: Entity, delta: i16) {
wake_pointer();
WHEEL_BUFFERS.with(|buffers| {
let mut buffers = buffers.borrow_mut();
let buffer = buffers.entry(entity).or_default();
buffer.add_horizontal(delta);
});
}
#[inline]
pub(crate) fn set_modifier_state(entity: Entity, shift: bool, ctrl: bool) {
wake_pointer();
MODIFIER_STATE.with(|state| {
let mut state = state.borrow_mut();
state.insert(entity, (shift, ctrl));
});
}
pub(crate) fn transfer_buffers_to_world(world: &mut World) {
POINTER_BUFFERS.with(|buffers| {
let mut buffers = buffers.borrow_mut();
for (entity, buffer) in buffers.iter_mut() {
if let Some(sample) = buffer.latest() {
let (vx, vy) = buffer.calculate_velocity();
if let Some(mut pointer_state) = world.get_mut::<PointerState>(*entity) {
pointer_state.client_point =
PhysicalPoint::new(sample.x as i32, sample.y as i32);
pointer_state.local_point = pointer_state.client_point;
pointer_state.velocity = CursorVelocity::new(vx, vy);
tracing::trace!(
entity = ?entity,
x = sample.x,
y = sample.y,
"[transfer_buffers_to_world] PointerState updated"
);
}
}
buffer.clear();
}
});
BUTTON_BUFFERS.with(|buffers| {
let buffers = buffers.borrow();
for ((entity, button), buf) in buffers.iter() {
if buf.up_received
&& let Some(mut pointer_state) = world.get_mut::<PointerState>(*entity)
{
match button {
PointerButton::Left => pointer_state.released.left = true,
PointerButton::Right => pointer_state.released.right = true,
PointerButton::Middle => pointer_state.released.middle = true,
PointerButton::XButton1 => pointer_state.released.xbutton1 = true,
PointerButton::XButton2 => pointer_state.released.xbutton2 = true,
}
}
if buf.down_received {
if let Some(mut pointer_state) = world.get_mut::<PointerState>(*entity) {
match button {
PointerButton::Left => pointer_state.left_down = true,
PointerButton::Right => pointer_state.right_down = true,
PointerButton::Middle => pointer_state.middle_down = true,
PointerButton::XButton1 => pointer_state.xbutton1_down = true,
PointerButton::XButton2 => pointer_state.xbutton2_down = true,
}
tracing::trace!(
entity = ?entity,
button = ?button,
"[transfer_buffers_to_world] Button pressed"
);
}
} else if buf.up_received {
if let Some(mut pointer_state) = world.get_mut::<PointerState>(*entity) {
match button {
PointerButton::Left => pointer_state.left_down = false,
PointerButton::Right => pointer_state.right_down = false,
PointerButton::Middle => pointer_state.middle_down = false,
PointerButton::XButton1 => pointer_state.xbutton1_down = false,
PointerButton::XButton2 => pointer_state.xbutton2_down = false,
}
tracing::trace!(
entity = ?entity,
button = ?button,
"[transfer_buffers_to_world] Button released"
);
}
}
}
});
BUTTON_BUFFERS.with(|buffers| {
let mut buffers = buffers.borrow_mut();
for buf in buffers.values_mut() {
buf.reset();
}
});
MODIFIER_STATE.with(|state| {
let state = state.borrow();
for (entity, (shift_down, ctrl_down)) in state.iter() {
if let Some(mut pointer_state) = world.get_mut::<PointerState>(*entity) {
pointer_state.shift_down = *shift_down;
pointer_state.ctrl_down = *ctrl_down;
}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
fn reset_all_buffers() {
POINTER_BUFFERS.with(|b| b.borrow_mut().clear());
BUTTON_BUFFERS.with(|b| b.borrow_mut().clear());
WHEEL_BUFFERS.with(|b| b.borrow_mut().clear());
DOUBLE_CLICK_BUFFERS.with(|b| b.borrow_mut().clear());
MODIFIER_STATE.with(|b| b.borrow_mut().clear());
}
#[test]
fn test_push_pointer_sample_accumulates_in_thread_local() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn_empty().id();
push_pointer_sample(e, 10.0, 20.0, Instant::now());
push_pointer_sample(e, 11.0, 21.0, Instant::now());
POINTER_BUFFERS.with(|buffers| {
let buffers = buffers.borrow();
let buf = buffers.get(&e).expect("buffer created on first push");
assert_eq!(buf.len(), 2, "2 samples accumulated");
let latest = buf.latest().unwrap();
assert_eq!(latest.x, 11.0);
assert_eq!(latest.y, 21.0);
});
}
#[test]
fn test_record_button_down_up_sets_independent_flags() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn_empty().id();
record_button_down(e, PointerButton::Left);
record_button_up(e, PointerButton::Right);
BUTTON_BUFFERS.with(|buffers| {
let buffers = buffers.borrow();
let left = buffers.get(&(e, PointerButton::Left)).unwrap();
assert!(left.down_received, "Left は down のみ");
assert!(!left.up_received);
let right = buffers.get(&(e, PointerButton::Right)).unwrap();
assert!(!right.down_received);
assert!(right.up_received, "Right は up のみ");
});
}
#[test]
fn test_add_wheel_helpers_accumulate_separately() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn_empty().id();
add_wheel_vertical(e, 120);
add_wheel_vertical(e, 120);
add_wheel_horizontal(e, -60);
WHEEL_BUFFERS.with(|buffers| {
let buffers = buffers.borrow();
let buf = buffers.get(&e).unwrap();
assert_eq!(buf.vertical, 240, "垂直は累積");
assert_eq!(buf.horizontal, -60, "水平は独立して累積");
});
}
#[test]
fn test_set_modifier_state_overwrites_latest() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn_empty().id();
set_modifier_state(e, true, false);
set_modifier_state(e, false, true);
MODIFIER_STATE.with(|state| {
let state = state.borrow();
assert_eq!(state.get(&e), Some(&(false, true)));
});
}
#[test]
fn test_transfer_buffers_to_world_updates_position_and_velocity() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn(PointerState::default()).id();
let t0 = Instant::now();
push_pointer_sample(e, 100.0, 200.0, t0);
push_pointer_sample(e, 150.0, 200.0, t0 + std::time::Duration::from_millis(100));
transfer_buffers_to_world(&mut world);
let state = world.get::<PointerState>(e).unwrap();
assert_eq!(state.client_point, PhysicalPoint::new(150, 200));
assert_eq!(state.local_point, PhysicalPoint::new(150, 200));
assert!(state.velocity.x > 0.0, "x 速度は正");
assert_eq!(state.velocity.y, 0.0, "y 速度は 0");
POINTER_BUFFERS.with(|buffers| {
let buffers = buffers.borrow();
assert!(buffers.get(&e).unwrap().is_empty(), "転送後はクリア");
});
}
#[test]
fn test_transfer_buffers_to_world_button_edge_detection_and_reset() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn(PointerState::default()).id();
record_button_down(e, PointerButton::Left);
transfer_buffers_to_world(&mut world);
assert!(
world.get::<PointerState>(e).unwrap().left_down,
"down 受信で押下状態 true"
);
transfer_buffers_to_world(&mut world);
assert!(
world.get::<PointerState>(e).unwrap().left_down,
"イベントなしでは押下状態を維持"
);
record_button_up(e, PointerButton::Left);
transfer_buffers_to_world(&mut world);
assert!(
!world.get::<PointerState>(e).unwrap().left_down,
"up 受信で押下状態 false"
);
}
#[test]
fn test_transfer_buffers_to_world_sets_released_flag_even_with_down() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn(PointerState::default()).id();
record_button_down(e, PointerButton::Right);
record_button_up(e, PointerButton::Right);
transfer_buffers_to_world(&mut world);
let s = world.get::<PointerState>(e).unwrap();
assert!(s.right_down, "押下優先の既存分岐は不変");
assert!(s.released.right, "同 tick でも解放の旗は落ちない");
assert!(s.released.any(), "any() が真");
assert!(!s.released.left, "他のボタンの旗は立たない");
}
#[test]
fn test_transfer_buffers_to_world_released_flag_maps_all_buttons() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn(PointerState::default()).id();
record_button_up(e, PointerButton::Left);
record_button_up(e, PointerButton::Right);
record_button_up(e, PointerButton::Middle);
record_button_up(e, PointerButton::XButton1);
record_button_up(e, PointerButton::XButton2);
transfer_buffers_to_world(&mut world);
let r = world.get::<PointerState>(e).unwrap().released;
assert!(r.left && r.right && r.middle);
assert!(r.xbutton1 && r.xbutton2, "XButton も個別に写像");
}
#[test]
fn test_transfer_buffers_to_world_maps_all_buttons() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn(PointerState::default()).id();
record_button_down(e, PointerButton::Left);
record_button_down(e, PointerButton::Right);
record_button_down(e, PointerButton::Middle);
record_button_down(e, PointerButton::XButton1);
record_button_down(e, PointerButton::XButton2);
transfer_buffers_to_world(&mut world);
let s = world.get::<PointerState>(e).unwrap();
assert!(s.left_down && s.right_down && s.middle_down);
assert!(s.xbutton1_down && s.xbutton2_down, "XButton も個別に写像");
}
#[test]
fn test_transfer_buffers_to_world_applies_modifier_state() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn(PointerState::default()).id();
set_modifier_state(e, true, true);
transfer_buffers_to_world(&mut world);
let s = world.get::<PointerState>(e).unwrap();
assert!(s.shift_down, "Shift 状態が転送される");
assert!(s.ctrl_down, "Ctrl 状態が転送される");
}
#[test]
fn test_transfer_buffers_to_world_i16_extreme_coords_are_exact() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn(PointerState::default()).id();
let xmin = i16::MIN as f32; let ymax = i16::MAX as f32; push_pointer_sample(e, xmin, ymax, Instant::now());
transfer_buffers_to_world(&mut world);
let s = world.get::<PointerState>(e).unwrap();
assert_eq!(
s.client_point,
PhysicalPoint::new(i16::MIN as i32, i16::MAX as i32),
"i16 極値座標は無損失で i32 へ反映される"
);
assert_eq!(s.local_point, s.client_point, "local = client");
}
#[test]
fn test_transfer_buffers_to_world_nonfinite_coords_saturate_without_panic() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn(PointerState::default()).id();
let t0 = Instant::now();
push_pointer_sample(e, 0.0, 0.0, t0);
push_pointer_sample(
e,
f32::NAN,
f32::INFINITY,
t0 + std::time::Duration::from_millis(10),
);
transfer_buffers_to_world(&mut world);
let s = world.get::<PointerState>(e).unwrap();
assert_eq!(s.client_point.x, 0, "NaN は 0 に飽和");
assert_eq!(s.client_point.y, i32::MAX, "+inf は i32::MAX に飽和");
}
#[test]
fn test_transfer_buffers_to_world_skips_entity_without_pointer_state() {
reset_all_buffers();
let mut world = World::new();
let e = world.spawn_empty().id();
push_pointer_sample(e, 5.0, 5.0, Instant::now());
record_button_down(e, PointerButton::Left);
set_modifier_state(e, true, false);
transfer_buffers_to_world(&mut world);
assert!(
world.get::<PointerState>(e).is_none(),
"PointerState は生成されない(転送先がなければ無視)"
);
}
}