keep the path instead of finding it again every tick
the tick was running A* over the whole 36x64 grid for every unit, every tick. catching up ten seconds meant thousands of searches inside the session lock, so the tick loop never finished, snapshots never went out, and the client - which refuses to send a command while isFullUpdatePending is true - sat there showing the connection icon and would not spawn anything. ctrl-c looked like a hang for the same reason: a task stuck in that loop. the client does not do this either. LogicMovementComponent carries a path array precisely so the route is found once and walked. we keep the route and the goal it was found for, drop a node once we are within 250 units of it, and only search again when the goal moves or the route runs out. advance_to also refuses to simulate more than forty ticks in one call, so a late tick can never turn into an unbounded loop under the lock.
This commit is contained in:
parent
f51d76f780
commit
9230a212ef
4 changed files with 89 additions and 8 deletions
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
pub const SNAPSHOT_INTERVAL_TICKS: i32 = 20;
|
||||
pub const MAX_CATCH_UP_TICKS: i32 = 40;
|
||||
use std::time::Instant;
|
||||
use logic::battle::{
|
||||
LogicGameMode, LogicGameObjectEntry, LogicVector2, BATTLE_TICKS_PER_SECOND, BATTLE_TYPE_PVP,
|
||||
|
|
@ -105,6 +106,12 @@ impl BattleSession {
|
|||
fn snapshot_message(&mut self) -> Option<WireMessage> {
|
||||
let snapshot = self.mode.snapshot().ok()?;
|
||||
let report = verify_snapshot(&snapshot);
|
||||
tracing::debug!(
|
||||
tick = self.tick,
|
||||
bytes = snapshot.len(),
|
||||
ok = report.is_ok(),
|
||||
"built a battle snapshot"
|
||||
);
|
||||
if !report.is_ok() {
|
||||
tracing::error!(
|
||||
bytes = snapshot.len(),
|
||||
|
|
@ -174,6 +181,7 @@ impl BattleSession {
|
|||
self.queued.push((at_tick.max(self.tick + 1), entries));
|
||||
}
|
||||
pub fn advance_to(&mut self, tick: i32) {
|
||||
let tick = tick.min(self.tick + MAX_CATCH_UP_TICKS);
|
||||
while self.tick < tick {
|
||||
self.tick += 1;
|
||||
self.mode.time.tick = self.tick;
|
||||
|
|
|
|||
|
|
@ -456,12 +456,17 @@ impl GameApi for GameService {
|
|||
Matched::Waiting => Ok(Vec::new()),
|
||||
};
|
||||
}
|
||||
Ok(self
|
||||
let messages = self
|
||||
.running_battles
|
||||
.tick(account, |card, position, owner, instance| {
|
||||
self.battles.summon(card, position, owner, 0, instance)
|
||||
})
|
||||
.await)
|
||||
.await;
|
||||
if !messages.is_empty() {
|
||||
let kinds: Vec<u16> = messages.iter().map(|message| message.message_type).collect();
|
||||
tracing::debug!(%account, sent = ?kinds, "battle tick");
|
||||
}
|
||||
Ok(messages)
|
||||
}
|
||||
async fn battle_event(&self, account: AccountRef, payload: Vec<u8>) -> RpcResult<()> {
|
||||
let Ok(sent) = SendBattleEventMessage::from_bytes(&payload) else {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ pub struct LogicMovementComponent {
|
|||
pub pushback_speed: i32,
|
||||
pub move_timer: i32,
|
||||
pub jump_distance: i32,
|
||||
pub goal: Option<(i32, i32)>,
|
||||
pub route: Vec<(i32, i32)>,
|
||||
}
|
||||
impl Default for LogicMovementComponent {
|
||||
fn default() -> Self {
|
||||
|
|
@ -89,6 +91,8 @@ impl Default for LogicMovementComponent {
|
|||
pushback_speed: 0,
|
||||
move_timer: 0,
|
||||
jump_distance: 0,
|
||||
goal: None,
|
||||
route: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ pub const COLUMN_TARGET_ONLY_BUILDINGS: &str = "TargetOnlyBuildings";
|
|||
pub const COLUMN_FLYING_HEIGHT: &str = "FlyingHeight";
|
||||
pub const COLUMN_PROJECTILE: &str = "Projectile";
|
||||
pub const DISTANCE_SATURATION: i32 = 46340;
|
||||
pub const WAYPOINT_REACHED: i32 = 250;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct LogicCharacterStats {
|
||||
pub speed: i32,
|
||||
|
|
@ -130,6 +131,23 @@ impl LogicGameObjectEntry {
|
|||
component.hitpoints = (component.hitpoints - amount.max(0)).max(0);
|
||||
}
|
||||
}
|
||||
pub fn movement(&self) -> Option<&crate::battle::logic_component::LogicMovementComponent> {
|
||||
match self.components.get(crate::battle::logic_component::COMPONENT_MOVEMENT)? {
|
||||
Some(LogicComponent::Movement(component)) => Some(component),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn movement_mut(
|
||||
&mut self,
|
||||
) -> Option<&mut crate::battle::logic_component::LogicMovementComponent> {
|
||||
match self
|
||||
.components
|
||||
.get_mut(crate::battle::logic_component::COMPONENT_MOVEMENT)?
|
||||
{
|
||||
Some(LogicComponent::Movement(component)) => Some(component),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn combat_mut(&mut self) -> Option<&mut crate::battle::logic_component::LogicCombatComponent> {
|
||||
match self.components.get_mut(COMPONENT_COMBAT)? {
|
||||
Some(LogicComponent::Combat(component)) => Some(component),
|
||||
|
|
@ -239,6 +257,57 @@ impl LogicBattle {
|
|||
}
|
||||
best.map(|(_, other)| other)
|
||||
}
|
||||
fn next_waypoint(
|
||||
&mut self,
|
||||
index: usize,
|
||||
from: (i32, i32),
|
||||
goal: (i32, i32),
|
||||
flying: bool,
|
||||
) -> (i32, i32) {
|
||||
let stale = match self.objects.objects[index].movement() {
|
||||
Some(movement) => movement.goal != Some(goal) || movement.path.is_empty(),
|
||||
None => return goal,
|
||||
};
|
||||
if stale {
|
||||
let path = self
|
||||
.tilemap
|
||||
.as_ref()
|
||||
.map(|tilemap| crate::battle::find_path(tilemap, from, goal, flying))
|
||||
.unwrap_or_default();
|
||||
if let Some(movement) = self.objects.objects[index].movement_mut() {
|
||||
movement.goal = Some(goal);
|
||||
movement.route = path;
|
||||
}
|
||||
}
|
||||
let reached = self
|
||||
.objects
|
||||
.objects
|
||||
.get(index)
|
||||
.and_then(LogicGameObjectEntry::movement)
|
||||
.and_then(|movement| movement.route.first().copied())
|
||||
.map(|node| {
|
||||
let close = distance_squared(from, node) <= WAYPOINT_REACHED * WAYPOINT_REACHED;
|
||||
(node, close)
|
||||
});
|
||||
match reached {
|
||||
Some((node, true)) => {
|
||||
if let Some(movement) = self.objects.objects[index].movement_mut() {
|
||||
if !movement.route.is_empty() {
|
||||
movement.route.remove(0);
|
||||
}
|
||||
}
|
||||
let _ = node;
|
||||
self.objects
|
||||
.objects
|
||||
.get(index)
|
||||
.and_then(LogicGameObjectEntry::movement)
|
||||
.and_then(|movement| movement.route.first().copied())
|
||||
.unwrap_or(goal)
|
||||
}
|
||||
Some((node, false)) => node,
|
||||
None => goal,
|
||||
}
|
||||
}
|
||||
fn move_objects(&mut self) {
|
||||
let positions: Vec<(i32, i32)> = self
|
||||
.objects
|
||||
|
|
@ -272,12 +341,7 @@ impl LogicBattle {
|
|||
if straight <= stand_off as i64 || straight == 0 {
|
||||
continue;
|
||||
}
|
||||
let waypoint = self
|
||||
.tilemap
|
||||
.as_ref()
|
||||
.map(|tilemap| crate::battle::find_path(tilemap, from, goal, stats.flying))
|
||||
.and_then(|path| path.first().copied())
|
||||
.unwrap_or(goal);
|
||||
let waypoint = self.next_waypoint(index, from, goal, stats.flying);
|
||||
let dx = (waypoint.0 - from.0) as i64;
|
||||
let dy = (waypoint.1 - from.1) as i64;
|
||||
let leg = integer_sqrt(dx * dx + dy * dy);
|
||||
|
|
|
|||
Loading…
Reference in a new issue