use bevy::prelude::*;
use crate::components::*;
use crate::events::*;
use crate::world::EcsMetrics;
pub fn connection_management_system(
mut commands: Commands,
mut events: EventReader<ConnectionEstablishedEvent>,
mut metrics: ResMut<EcsMetrics>,
) {
for event in events.read() {
commands.spawn((
PlayerConnection::new(event.connection_id, event.address),
Position::origin(),
Health::full(100.0),
));
metrics.entity_count += 1;
info!("Player connected: {}", event.connection_id);
}
}
pub fn disconnection_system(
mut commands: Commands,
mut events: EventReader<ConnectionClosedEvent>,
query: Query<(Entity, &PlayerConnection)>,
mut metrics: ResMut<EcsMetrics>,
) {
for event in events.read() {
for (entity, conn) in query.iter() {
if conn.connection_id == event.connection_id {
commands.entity(entity).despawn();
metrics.entity_count -= 1;
info!(
"Player disconnected: {} (reason: {})",
event.connection_id, event.reason
);
break;
}
}
}
}
pub fn message_handling_system(
mut events: EventReader<MessageReceivedEvent>,
query: Query<&PlayerConnection>,
mut metrics: ResMut<EcsMetrics>,
) {
for event in events.read() {
for conn in query.iter() {
if conn.connection_id == event.connection_id {
debug!(
"Message from {}: msg_id={}, seq={}, size={}",
event.connection_id,
event.message_id,
event.sequence_id,
event.payload.len()
);
break;
}
}
metrics.events_processed += 1;
}
}
pub fn position_update_system(
mut query: Query<(&mut Position, &Velocity)>,
time: Res<Time>,
) {
for (mut pos, vel) in query.iter_mut() {
let delta = time.delta_seconds();
pos.x += vel.vx * delta;
pos.y += vel.vy * delta;
pos.z += vel.vz * delta;
}
}
pub fn timer_update_system(
mut commands: Commands,
time: Res<Time>,
mut query: Query<(Entity, &mut GameTimer)>,
) {
for (entity, mut timer) in query.iter_mut() {
if timer.tick(time.delta()) {
info!("Timer triggered for entity {:?}", entity);
if !timer.repeating && timer.finished() {
commands.entity(entity).remove::<GameTimer>();
}
}
}
}
#[derive(Component)]
pub struct HealthRegeneration {
pub rate: f32,
}
impl HealthRegeneration {
pub fn new(rate: f32) -> Self {
Self { rate }
}
}
pub fn health_regen_system(
mut query: Query<&mut Health, With<HealthRegeneration>>,
regen_query: Query<&HealthRegeneration>,
time: Res<Time>,
) {
let rate = regen_query.iter().next().map(|r| r.rate).unwrap_or(0.0);
for mut health in query.iter_mut() {
if !health.is_dead() && !health.is_full() {
health.heal(rate * time.delta_seconds());
}
}
}
#[derive(Resource, Clone, Copy)]
pub struct HeartbeatTimeoutThreshold {
pub duration: std::time::Duration,
}
impl Default for HeartbeatTimeoutThreshold {
fn default() -> Self {
Self {
duration: std::time::Duration::from_secs(30),
}
}
}
pub fn heartbeat_detection_system(
mut commands: Commands,
mut events: EventWriter<HeartbeatTimeoutEvent>,
query: Query<(Entity, &PlayerConnection)>,
threshold: Res<HeartbeatTimeoutThreshold>,
) {
for (entity, conn) in query.iter() {
let idle_time = conn.idle_time();
if idle_time > threshold.duration {
events.send(HeartbeatTimeoutEvent {
connection_id: conn.connection_id,
timeout_duration: threshold.duration,
last_activity: conn.last_activity,
});
commands.entity(entity).despawn();
warn!(
"Heartbeat timeout for {}: idle={:?}",
conn.connection_id, idle_time
);
}
}
}
pub fn cleanup_disconnected_system(
query: Query<(Entity, &PlayerConnection)>,
metrics: Res<EcsMetrics>,
) {
let _ = (query, metrics);
}
pub struct GameSystems;
#[cfg(test)]
mod tests {
use super::*;
use crate::world::EcsWorld;
#[test]
fn test_position_update_system() {
let mut world = World::new();
world.insert_resource::<Time>(Time::default());
world.spawn((
Position::origin(),
Velocity::new(1.0, 2.0, 3.0),
));
let mut time = world.resource_mut::<Time>();
time.advance_by(std::time::Duration::from_secs_f32(0.016)); drop(time);
let mut schedule = Schedule::default();
schedule.add_systems(position_update_system);
schedule.run(&mut world);
let pos = world.query::<&Position>().single(&world);
assert!(pos.x > 0.0);
assert!(pos.y > 0.0);
assert!(pos.z > 0.0);
}
#[test]
fn test_health_regen_system() {
let mut world = World::new();
world.spawn((
Health::new(100.0),
HealthRegeneration::new(10.0), ));
world.insert_resource::<Time>(Time::default());
let mut health = world.query::<&mut Health>().single_mut(&mut world);
health.current = 50.0;
drop(health);
let mut time = world.resource_mut::<Time>();
time.advance_by(std::time::Duration::from_secs(1));
drop(time);
let mut schedule = Schedule::default();
schedule.add_systems(health_regen_system);
schedule.run(&mut world);
let health = world.query::<&Health>().single(&world);
assert!((health.current - 60.0).abs() < 0.1); }
#[test]
fn test_timer_system() {
let mut world = World::new();
world.spawn(GameTimer::once(std::time::Duration::from_millis(100)));
world.insert_resource::<Time>(Time::default());
let mut time = world.resource_mut::<Time>();
time.advance_by(std::time::Duration::from_millis(50));
drop(time);
let mut schedule = Schedule::default();
schedule.add_systems(timer_update_system);
schedule.run(&mut world);
let timer = world.query::<&GameTimer>().single(&world);
assert!(!timer.finished());
let mut time = world.resource_mut::<Time>();
time.advance_by(std::time::Duration::from_millis(60));
drop(time);
schedule.run(&mut world);
let timer_count = world.query::<&GameTimer>().iter(&world).count();
assert_eq!(timer_count, 0);
}
}