From 794274b629685afe455756402e0ce72ddd915093 Mon Sep 17 00:00:00 2001 From: Timon Blauensteiner <2065342+darkclouder@users.noreply.github.com> Date: Sun, 10 Dec 2023 16:02:16 +0100 Subject: [PATCH 1/4] Refactor policy evaluation --- game_server/src/game/async_connector.rs | 2 +- game_server/src/game/events.rs | 66 ++++ game_server/src/game/game_board/mod.rs | 4 - game_server/src/game/minions/actions.rs | 143 +++++--- .../src/game/minions/chunk_visibility.rs | 4 +- game_server/src/game/minions/minion.rs | 47 +-- .../src/game/minions/minion_manager.rs | 335 +++++++----------- game_server/src/game/minions/mod.rs | 1 + game_server/src/game/minions/policies.rs | 155 ++++---- .../src/game/minions/policy_evaluator.rs | 286 +++++++++++++++ game_server/src/game/mod.rs | 1 + .../src/game/notifiers/minion_update.rs | 2 +- .../src/game/players/player_spawn/mod.rs | 4 +- game_server/src/game/world.rs | 26 +- 14 files changed, 673 insertions(+), 403 deletions(-) create mode 100644 game_server/src/game/events.rs create mode 100644 game_server/src/game/minions/policy_evaluator.rs diff --git a/game_server/src/game/async_connector.rs b/game_server/src/game/async_connector.rs index d4b26c5..573cce6 100644 --- a/game_server/src/game/async_connector.rs +++ b/game_server/src/game/async_connector.rs @@ -87,7 +87,7 @@ struct ActivateTemporaryPolicyMessage { impl ExecuteMessage> for ActivateTemporaryPolicyMessage { fn execute(self, world: &mut World) -> Result<(), ()> { - world.activate_temporary_policy(&self.player_id, &self.minion_id, &self.policy) + world.activate_temporary_policy(&self.player_id, self.minion_id, &self.policy) } } diff --git a/game_server/src/game/events.rs b/game_server/src/game/events.rs new file mode 100644 index 0000000..22d2705 --- /dev/null +++ b/game_server/src/game/events.rs @@ -0,0 +1,66 @@ +use super::minions::minion_manager::MinionManager; +use super::minions::policy_evaluator::PolicyEvaluator; +use game_shared::game::minion::{Damage, MinionId}; +use game_shared::game::tiles::TileIndex; +use std::ops::AddAssign; +use std::time::Instant; + +pub(crate) enum GameEvent { + MinionSetPosition { + position: TileIndex, + target: MinionId, + }, + MinionTakeDamage { + damage: Damage, + target: MinionId, + }, +} + +impl GameEvent { + pub fn execute( + self, + minion_manager: &mut MinionManager, + policy_evaluator: &mut PolicyEvaluator, + now: Instant, + ) { + match self { + GameEvent::MinionSetPosition { position, target } => { + minion_manager.set_position(&target, position, policy_evaluator, now); + }, + GameEvent::MinionTakeDamage { damage, target } => { + minion_manager.take_damage(&target, damage, policy_evaluator, now); + }, + } + } +} + +pub(crate) struct GameEvents { + inner: Vec, +} + +impl GameEvents { + pub fn empty() -> Self { + Self { inner: Vec::new() } + } +} + +impl IntoIterator for GameEvents { + type IntoIter = std::vec::IntoIter; + type Item = GameEvent; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +impl AddAssign for GameEvents { + fn add_assign(&mut self, rhs: Self) { + self.inner.extend(rhs.inner) + } +} + +impl From> for GameEvents { + fn from(inner: Vec) -> Self { + Self { inner } + } +} diff --git a/game_server/src/game/game_board/mod.rs b/game_server/src/game/game_board/mod.rs index b267a44..0b9a07c 100644 --- a/game_server/src/game/game_board/mod.rs +++ b/game_server/src/game/game_board/mod.rs @@ -49,10 +49,6 @@ impl GameBoard { self.chunk_manager.chunk(chunk_index) } - pub fn try_chunk(&self, chunk_index: &ChunkIndex) -> Option<&Chunk> { - self.chunk_manager.try_chunk(chunk_index) - } - pub fn terminate(&mut self) { self.chunk_manager.terminate(); } diff --git a/game_server/src/game/minions/actions.rs b/game_server/src/game/minions/actions.rs index a3a136e..53e1bc5 100644 --- a/game_server/src/game/minions/actions.rs +++ b/game_server/src/game/minions/actions.rs @@ -1,47 +1,60 @@ use super::minion::Minion; use super::policies::Context; +use crate::game::events::{GameEvent, GameEvents}; use crate::game::game_board::path_finding::{ find_shortest_path, minion_walk_tile_cost, TilePathProgression, MINION_PATH_CONFIG, }; use game_shared::game::minion::Damage; use game_shared::game::policies::PolicyAction; use game_shared::game::tiles::TileIndex; +use log::debug; use serde::{Deserialize, Serialize}; use std::time::{Duration, Instant}; -#[derive(Serialize, Deserialize)] -pub(super) enum Action { +pub(super) struct StepResult { + pub next_step: Option<(Instant, ActionStep)>, + pub game_events: GameEvents, +} + +impl StepResult { + fn completed() -> Self { + Self { + next_step: None, + game_events: GameEvents::empty(), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub(super) enum ActionStep { Move(MoveAction), Wait(WaitAction), Suicide(SuicideAction), } -impl Action { - pub fn update( - &mut self, - context: &mut Context, - ) -> Option { +impl ActionStep { + pub fn step(self, context: &mut Context) -> StepResult { match self { - Action::Move(inner) => inner.update(context), - Action::Wait(inner) => inner.update(context), - Action::Suicide(inner) => inner.update(context), + Self::Move(inner) => inner.step(context), + Self::Wait(inner) => inner.step(context), + Self::Suicide(inner) => inner.step(context), } } } -pub(super) fn activate_action(policy: &PolicyAction, minion: &Minion) -> Action { - match policy { +pub(super) fn activate_action(policy_action: &PolicyAction, minion: &Minion) -> ActionStep { + match policy_action { PolicyAction::Move(destination) => { - Action::Move(MoveAction::new(destination.as_absolute(minion.position()))) + ActionStep::Move(MoveAction::new(destination.as_absolute(&minion.position))) }, - PolicyAction::Wait(duration) => Action::Wait(WaitAction { + PolicyAction::Wait(duration) => ActionStep::Wait(WaitAction { duration: Some(*duration), }), - PolicyAction::Suicide => Action::Suicide(SuicideAction), + PolicyAction::Suicide => ActionStep::Suicide(SuicideAction), } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub(super) struct MoveAction { path: Option, destination: TileIndex, @@ -57,76 +70,90 @@ impl MoveAction { } } - fn update( - &mut self, - context: &mut Context, - ) -> Option { - if self.path.is_none() { - let path = find_shortest_path( - *context.minion.position(), + fn step(self, context: &mut Context) -> StepResult { + let Some(mut path) = self.path.or_else(|| { + find_shortest_path( + context.minion.position, self.destination, context.game_board, MINION_PATH_CONFIG, - ); - self.path = path.map(|p| TilePathProgression::from(&p)); - }; - - let Some(path) = self.path.as_mut() else { - return None; + ) + .map(|p| TilePathProgression::from(&p)) + }) else { + return StepResult::completed(); }; let Some(&step_position) = path.get_next() else { - return None; + return StepResult::completed(); }; - context - .minion - .set_position(step_position, context.manager, context.now); path.step(); - let Some(next_position) = path.get_next() else { - return None; + let Some(next_position) = path.get_next().cloned() else { + return StepResult { + next_step: None, + game_events: GameEvents::from(vec![GameEvent::MinionSetPosition { + position: step_position, + target: *context.minion.id(), + }]), + }; }; - let tile = context.game_board.tile(next_position); + let tile = context.game_board.tile(&next_position); let walk_cost = minion_walk_tile_cost(tile); - if walk_cost >= f32::INFINITY { - return None; - } + let next_step = if walk_cost < f32::INFINITY { + let distance = step_position.diagonal_step_distance(&next_position); + let next_completion = context.now + self.unit_speed.mul_f32(walk_cost * distance); + Some(( + next_completion, + ActionStep::Move(MoveAction { + path: Some(path), + destination: self.destination, + unit_speed: self.unit_speed, + }), + )) + } else { + None + }; - let distance = step_position.diagonal_step_distance(next_position); - Some(context.now + self.unit_speed.mul_f32(walk_cost * distance)) + StepResult { + next_step, + game_events: GameEvents::from(vec![GameEvent::MinionSetPosition { + position: step_position, + target: *context.minion.id(), + }]), + } } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub(super) struct WaitAction { duration: Option, } impl WaitAction { - fn update( - &mut self, - context: &mut Context, - ) -> Option { - let next_tick = self.duration.map(|dur| context.now + dur); - self.duration = None; - next_tick + fn step(self, context: &Context) -> StepResult { + StepResult { + next_step: self + .duration + .map(|dur| (context.now + dur, ActionStep::Wait(Self { duration: None }))), + game_events: GameEvents::empty(), + } } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub(super) struct SuicideAction; impl SuicideAction { - fn update( - &mut self, - context: &mut Context, - ) -> Option { - context - .minion - .take_damage(Damage::max(), context.manager, context.now); - None + fn step(self, context: &Context) -> StepResult { + StepResult { + next_step: None, + game_events: GameEvents::from(vec![GameEvent::MinionTakeDamage { + damage: Damage::max(), + target: *context.minion.id(), + }]), + } } } diff --git a/game_server/src/game/minions/chunk_visibility.rs b/game_server/src/game/minions/chunk_visibility.rs index b30380d..c0f3bf5 100644 --- a/game_server/src/game/minions/chunk_visibility.rs +++ b/game_server/src/game/minions/chunk_visibility.rs @@ -37,7 +37,7 @@ impl PlayerMinions { } #[derive(Clone, Copy, PartialEq, Eq)] -pub(super) struct MinionState { +pub(crate) struct MinionState { pub minion_id: MinionId, pub position: TileIndex, pub visiblity_range: u32, @@ -48,7 +48,7 @@ impl From<&Minion> for MinionState { fn from(value: &Minion) -> Self { Self { minion_id: *value.id(), - position: *value.position(), + position: value.position, visiblity_range: value.visibility_range(), owner: *value.owner(), } diff --git a/game_server/src/game/minions/minion.rs b/game_server/src/game/minions/minion.rs index 6ebde9b..31b9f32 100644 --- a/game_server/src/game/minions/minion.rs +++ b/game_server/src/game/minions/minion.rs @@ -1,23 +1,21 @@ -use super::minion_manager::MinionManager; -use game_shared::game::minion::{Damage, Health, MinionId}; +use game_shared::game::minion::{Health, MinionId}; use game_shared::game::tiles::TileIndex; use game_shared::network::messages::PlayerId; use serde::{Deserialize, Serialize}; -use std::time::Instant; #[derive(Clone, Copy, Serialize, Deserialize)] pub(crate) struct Minion { id: MinionId, owner: PlayerId, - position: TileIndex, - health: Health, + pub position: TileIndex, + pub health: Health, } impl Minion { - pub fn new(minion_id: MinionId, player_id: PlayerId, position: TileIndex) -> Minion { + pub fn new(id: MinionId, owner: PlayerId, position: TileIndex) -> Minion { Minion { - id: minion_id, - owner: player_id, + id, + owner, position, health: Health::default(), } @@ -31,39 +29,6 @@ impl Minion { &self.owner } - pub fn position(&self) -> &TileIndex { - &self.position - } - - pub fn set_position( - &mut self, - value: TileIndex, - manager: &mut MinionManager, - now: Instant, - ) { - self.position = value; - manager.update(self, now); - } - - pub fn health(&self) -> &Health { - &self.health - } - - pub fn take_damage( - &mut self, - value: Damage, - manager: &mut MinionManager, - now: Instant, - ) { - self.health.take_damage(value); - - if self.health.is_dead() { - manager.despawn(self); - } else { - manager.update(self, now); - } - } - pub fn visibility_range(&self) -> u32 { 8 } diff --git a/game_server/src/game/minions/minion_manager.rs b/game_server/src/game/minions/minion_manager.rs index 346766d..61c149d 100644 --- a/game_server/src/game/minions/minion_manager.rs +++ b/game_server/src/game/minions/minion_manager.rs @@ -1,65 +1,34 @@ -use super::chunk_visibility::ChunkVisibility; -use super::event_queue::{EventQueue, ExportedEventQueue}; +use super::chunk_visibility::{ChunkVisibility, MinionState}; use super::minion::Minion; -use super::policies::{Context, PolicyAgent, StepId}; +use super::policies::PolicyAgent; +use super::policy_evaluator::PolicyEvaluator; use super::LoadDataError; use crate::game::game_board::chunks::visibility_mask::VisibilityChunkMask; -use crate::game::game_board::GameBoard; use crate::game::notifiers::chunk_update::PlayersChunkUpdateNotifier; use crate::game::notifiers::minion_update::PlayersMinionUpdateNotifier; use game_shared::game::chunk::ChunkIndex; -use game_shared::game::minion::MinionId; -use game_shared::game::policies::Policy; +use game_shared::game::minion::{Damage, MinionId}; use game_shared::game::tiles::TileIndex; use game_shared::network::messages::PlayerId; -use itertools::Itertools; -use log::{debug, error, info}; +use log::{error, info}; use serde::{Deserialize, Serialize}; -use std::cell::{Ref, RefCell}; -use std::collections::HashMap; -use std::ops::DerefMut; +use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; -use std::rc::Rc; use std::sync::Arc; use std::time::Instant; use tokio::fs::{File, OpenOptions}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -type Cursor = Rc>; - #[derive(Serialize, Deserialize)] pub(crate) struct MinionWithAgency { pub(crate) minion: Minion, pub(crate) policy_agent: PolicyAgent, } -impl MinionWithAgency { - fn activate_policy_agent( - &mut self, - manager: &mut MinionManager, - game_board: &mut GameBoard, - now: Instant, - ) { - self.policy_agent.activate_highest_policy(&mut Context { - minion: &mut self.minion, - manager, - game_board, - now, - }); - } -} - -#[derive(Serialize, Deserialize)] -enum EventItem { - PolicyTrigger(MinionId), - ActionStep(MinionId, StepId), -} - pub(crate) struct MinionManager { next_minion_id: MinionId, - minions: HashMap>>, + minions: HashMap>, chunk_visibility: ChunkVisibility, - event_queue: EventQueue, // Player notifiers chunk_update_notifier: Arc, @@ -77,22 +46,18 @@ impl MinionManager { let mut chunk_visibility = ChunkVisibility::default(); let minions_file_path = data_path.join("minions.rmp"); - let now = Instant::now(); - match ExportedMinionManager::load(&minions_file_path).await { Ok(Some(exported)) => { - for minion in &exported.minions { - chunk_visibility.update(&minion.borrow().minion); + for minion_with_agency in &exported.minions { + chunk_visibility.update(&minion_with_agency.minion); } Ok(Self { next_minion_id: exported.next_minion_id, - minions: HashMap::from_iter(exported.minions.into_iter().map(|m| { - let minion_id = *m.borrow().minion.id(); - (minion_id, m) - })), + minions: HashMap::from_iter( + exported.minions.into_iter().map(|m| (*m.minion.id(), m)), + ), chunk_visibility, - event_queue: exported.event_queue.now_into(now), minion_update_notifier, chunk_update_notifier, minions_file_path, @@ -102,7 +67,6 @@ impl MinionManager { next_minion_id: Default::default(), minions: Default::default(), chunk_visibility, - event_queue: Default::default(), minion_update_notifier, chunk_update_notifier, minions_file_path, @@ -114,116 +78,93 @@ impl MinionManager { } } - pub fn spawn(&mut self, player_id: PlayerId, position: TileIndex, now: Instant) -> MinionId { + pub fn spawn( + &mut self, + owner: PlayerId, + position: TileIndex, + policy_evaluator: &mut PolicyEvaluator, + now: Instant, + ) { let minion_id = self.next_minion_id; self.next_minion_id = minion_id.next(); - self.insert_minion(Minion::new(minion_id, player_id, position), now); - - minion_id - } - - pub fn update(&mut self, updated_minion: &Minion, now: Instant) { - let updated_chunks = self.chunk_visibility.update(updated_minion); - - for chunk_index in updated_chunks.iter() { - self.chunk_update_notifier - .add(*updated_minion.owner(), *chunk_index); - } + self.minions.insert( + minion_id, + MinionWithAgency { + minion: Minion::new(minion_id, owner, position), + policy_agent: PolicyAgent::default(), + }, + ); - self.chunk_visibility - .minions_with_visiblity(updated_minion.position()) - .map(|m| { - self.event_queue - .insert(now, EventItem::PolicyTrigger(m.minion_id)); - m.owner - }) - .unique() - .for_each(|player_id| { - self.minion_update_notifier.add(player_id, updated_minion); - }); + self.update_visiblity(&minion_id); + self.notify_minion_update(&minion_id, policy_evaluator, now); } - pub fn despawn(&mut self, minion: &Minion) { - let minion_id = minion.id(); - let owner = minion.owner(); - - self.minions.remove(minion_id); - let updated_chunks = self.chunk_visibility.remove(minion_id); - - for chunk_index in updated_chunks.iter() { - self.chunk_update_notifier - .add(*minion.owner(), *chunk_index); - } - - self.minion_update_notifier.add(*owner, minion); - - for player_id in self - .chunk_visibility - .players_with_visibility(minion.position()) - { - if &player_id != owner { - self.minion_update_notifier.add(player_id, minion) - } - } + pub fn get(&self, minion_id: &MinionId) -> Option<&MinionWithAgency> { + self.minions.get(minion_id) } - pub fn get(&self, minion_id: &MinionId) -> Option>> { - Some(self.minions.get(minion_id)?.borrow()) + pub fn get_mut( + &mut self, + minion_id: &MinionId, + ) -> Option<(&Minion, &mut PolicyAgent)> { + self.minions + .get_mut(minion_id) + .map(|ma| (&ma.minion, &mut ma.policy_agent)) } - pub fn try_activate_policy( + pub fn set_position( &mut self, - game_board: &mut GameBoard, minion_id: &MinionId, - policy: &Policy, + position: TileIndex, + policy_evaluator: &mut PolicyEvaluator, now: Instant, - ) -> Result<(), ()> { - let cursor = self.minions.get(minion_id).ok_or(())?.clone(); - let mut minion_with_agency = cursor.borrow_mut(); - - let MinionWithAgency { - ref mut minion, - ref mut policy_agent, - } = minion_with_agency.deref_mut(); - - let mut context = Context { - minion, - manager: self, - game_board, - now, + ) { + let Some(minion_with_agency) = self.minions.get_mut(minion_id) else { + return; }; - policy_agent.try_activate_temporary_policy(&mut context, policy) + minion_with_agency.minion.position = position; + self.update_visiblity(minion_id); + self.notify_minion_update(minion_id, policy_evaluator, now); } - pub(super) fn enqueue_step( + pub fn take_damage( &mut self, - minion_id: MinionId, - step_id: StepId, - completion: Instant, + minion_id: &MinionId, + damage: Damage, + policy_evaluator: &mut PolicyEvaluator, + now: Instant, ) { - self.event_queue - .insert(completion, EventItem::ActionStep(minion_id, step_id)) + let Some(minion_with_agency) = self.minions.get_mut(minion_id) else { + return; + }; + + let minion = &mut minion_with_agency.minion; + + minion.health.take_damage(damage); + + if minion.health.is_dead() { + self.despawn(minion_id, policy_evaluator, now); + } } - pub fn process_complete_events( + pub fn despawn( &mut self, - game_board: &mut GameBoard, - ) -> Option { - let now = Instant::now(); + minion_id: &MinionId, + policy_evaluator: &mut PolicyEvaluator, + now: Instant, + ) { + let Some(minion_with_agency) = self.minions.remove(minion_id) else { + return; + }; + let updated_chunks = self.chunk_visibility.remove(minion_id); + let owner = *minion_with_agency.minion.owner(); - while let Some((completion, event_item)) = self.event_queue.pop_complete(&now) { - match event_item { - EventItem::PolicyTrigger(minion_id) => { - self.process_policy_trigger(game_board, &minion_id, completion) - }, - EventItem::ActionStep(minion_id, step_id) => { - self.process_action_step(game_board, &minion_id, &step_id, completion) - }, - }; + for chunk_index in updated_chunks.iter() { + self.chunk_update_notifier.add(owner, *chunk_index); } - self.event_queue.get_next_completion() + self.notify_minion_update(minion_id, policy_evaluator, now); } pub fn visibility_chunk_mask( @@ -234,6 +175,49 @@ impl MinionManager { self.chunk_visibility.chunk_mask(chunk_index, player_id) } + pub fn minions_with_visiblity<'a>( + &'a self, + at: &'a TileIndex, + ) -> impl Iterator { + self.chunk_visibility.minions_with_visiblity(at) + } + + fn notify_minion_update( + &mut self, + minion_id: &MinionId, + policy_evaluator: &mut PolicyEvaluator, + now: Instant, + ) { + // TODO: send minion disappeared message to old_position owner's that are not in target + let Some(minion_with_agency) = self.minions.get(minion_id) else { + return; + }; + let minion = &minion_with_agency.minion; + let minion_position = minion.position; + + let target_observer_minions: Box<[MinionState]> = self + .minions_with_visiblity(&minion_position) + .copied() + .collect(); + let target_observer_players = + HashSet::::from_iter(target_observer_minions.iter().map(|ms| ms.owner)); + + for player_id in target_observer_players { + self.minion_update_notifier.add(player_id, minion); + } + + for minion_state in target_observer_minions.iter() { + let Some(minion_with_agency) = self.minions.get_mut(&minion_state.minion_id) else { + continue; + }; + policy_evaluator.trigger_policy( + &minion_with_agency.minion, + &mut minion_with_agency.policy_agent, + now, + ); + } + } + pub fn notify_tile_update(&self, at: &TileIndex) { let chunk_index = at.get_chunk_index(CHUNK_SIZE); @@ -258,11 +242,11 @@ impl MinionManager { }; for minion_id in minions { - let Some(cursor) = self.minions.get(&minion_id) else { + let Some(minion_with_agency) = self.minions.get(&minion_id) else { continue; }; - let minion = &cursor.borrow().minion; - self.minion_update_notifier.add(player_id, minion); + self.minion_update_notifier + .add(player_id, &minion_with_agency.minion); } } @@ -275,74 +259,24 @@ impl MinionManager { Ok(()) } - fn insert_minion(&mut self, minion: Minion, now: Instant) { - let minion_with_agency = MinionWithAgency { - minion, - policy_agent: PolicyAgent::default(), - }; - - self.update(&minion, now); - self.minions - .insert(*minion.id(), Rc::new(RefCell::new(minion_with_agency))); - } - - fn process_policy_trigger( - &mut self, - game_board: &mut GameBoard, - minion_id: &MinionId, - now: Instant, - ) { - let Some(cursor) = self.minions.get(minion_id).cloned() else { - debug!("Minion {minion_id:?} does not exist any more"); + fn update_visiblity(&mut self, minion_id: &MinionId) { + let Some(minion_with_agency) = self.minions.get(minion_id) else { return; }; - let Ok(mut minion_with_agency) = cursor.try_borrow_mut() else { - error!("Minion {minion_id:?} is locked during step"); - return; - }; + let player_id = *minion_with_agency.minion.owner(); + let updated_chunks = self.chunk_visibility.update(&minion_with_agency.minion); - minion_with_agency.activate_policy_agent(self, game_board, now); - } - - fn process_action_step( - &mut self, - game_board: &mut GameBoard, - minion_id: &MinionId, - step_id: &StepId, - now: Instant, - ) { - let Some(cursor) = self.minions.get(minion_id).cloned() else { - debug!("Minion {minion_id:?} does not exist any more"); - return; - }; - - let Ok(mut minion_with_agency) = cursor.try_borrow_mut() else { - error!("Minion {minion_id:?} is locked during step"); - return; - }; - - let MinionWithAgency { - ref mut minion, - ref mut policy_agent, - } = minion_with_agency.deref_mut(); - - let mut context = Context { - minion, - manager: self, - game_board, - now, - }; - - policy_agent.complete_step(step_id, &mut context); + for chunk_index in updated_chunks.iter() { + self.chunk_update_notifier.add(player_id, *chunk_index); + } } } #[derive(Serialize, Deserialize)] struct ExportedMinionManager { next_minion_id: MinionId, - minions: Vec>>, - event_queue: ExportedEventQueue, + minions: Vec>, } impl ExportedMinionManager { @@ -350,17 +284,11 @@ impl ExportedMinionManager { Self { next_minion_id: value.next_minion_id, minions: value.minions.into_values().collect(), - event_queue: ExportedEventQueue::now_from(value.event_queue, now), } } async fn save(self, file_path: &Path) -> Result<(), ()> { - info!( - "Saving {} minions and {} event queue items: {:?}", - self.minions.len(), - self.event_queue.len(), - file_path - ); + info!("Saving {} minions: {:?}", self.minions.len(), file_path); let mut file = OpenOptions::new() .create(true) .write(true) @@ -392,9 +320,8 @@ impl ExportedMinionManager { let exported: Self = rmp_serde::from_slice(&serialized)?; info!( - "Loaded {} minions and {} event queue items from {:?}", + "Loaded {} minions from {:?}", exported.minions.len(), - exported.event_queue.len(), file_path ); diff --git a/game_server/src/game/minions/mod.rs b/game_server/src/game/minions/mod.rs index bb00f6a..accc512 100644 --- a/game_server/src/game/minions/mod.rs +++ b/game_server/src/game/minions/mod.rs @@ -7,6 +7,7 @@ mod event_queue; pub(super) mod minion; pub(super) mod minion_manager; mod policies; +pub(super) mod policy_evaluator; #[derive(Debug, Error)] pub(crate) enum LoadDataError { diff --git a/game_server/src/game/minions/policies.rs b/game_server/src/game/minions/policies.rs index 98f8f31..433e1a2 100644 --- a/game_server/src/game/minions/policies.rs +++ b/game_server/src/game/minions/policies.rs @@ -1,18 +1,15 @@ -use super::actions::{activate_action, Action}; use super::minion::Minion; -use super::minion_manager::MinionManager; use crate::game::game_board::GameBoard; use game_shared::game::policies::{ MovePolicyDestination, Policy, PolicyAction, PolicyPriority, PolicyTrigger, }; use game_shared::game::tiles::TileIndex; -use log::debug; use serde::{Deserialize, Serialize}; +use std::mem; use std::time::Instant; pub(super) struct Context<'a, const CHUNK_SIZE: usize> { - pub minion: &'a mut Minion, - pub manager: &'a mut MinionManager, + pub minion: &'a Minion, pub game_board: &'a mut GameBoard, pub now: Instant, } @@ -20,7 +17,6 @@ pub(super) struct Context<'a, const CHUNK_SIZE: usize> { #[derive(Serialize, Deserialize)] pub(crate) struct PolicyAgent { policies: Vec, - active_step_id: StepId, active_policy: Option>, } @@ -28,78 +24,66 @@ impl Default for PolicyAgent { fn default() -> Self { Self { policies: vec![create_dummy_policy()], - active_step_id: StepId::default(), active_policy: None, } } } impl PolicyAgent { - pub(super) fn activate_highest_policy(&mut self, context: &mut Context) { + pub fn is_active(&self, activation_id: &ActivationId) -> bool { + self.active_policy + .as_ref() + .map(|p| &p.activation_id == activation_id) + .unwrap_or(false) + } + + pub(super) fn advance_to_next_action(&mut self) -> Option<(ActivationId, &PolicyAction)> { + let mut active_policy = self.active_policy.take()?; + + if active_policy.advance_to_next_action() { + self.active_policy = Some(active_policy); + self.current_action() + } else { + None + } + } + + pub(super) fn activate_highest_policy( + &mut self, + activation_id_generator: &mut ActivationIdGenerator, + ) -> Option<(ActivationId, &PolicyAction)> { // TODO: sort policies by priority for policy in self.policies.iter() { if !self.has_precedence_over_active(policy) { - return; + return None; } - // TODO: finish current action if there is one - - let Some(mut next_policy) = try_activate(policy, context) else { - continue; - }; - - if let Some(completion) = next_policy.step(context) { - debug!("Activating {:?} for {:?}", policy.name, context.minion.id()); + if let Some(next_policy) = try_activate(policy, activation_id_generator) { self.active_policy = Some(next_policy); - self.enqueue_next_step(context, completion); - break; + return self.current_action(); } } + + None } - pub(super) fn try_activate_temporary_policy( + pub(super) fn activate_policy( &mut self, - context: &mut Context, policy: &Policy, - ) -> Result<(), ()> { - let Some(mut next_policy) = try_activate(policy, context) else { - return Err(()); - }; - - let Some(completion) = next_policy.step(context) else { - return Err(()); - }; - - debug!("Activating {:?} for {:?}", policy.name, context.minion.id()); - self.active_policy = Some(next_policy); - self.enqueue_next_step(context, completion); - Ok(()) - } - - pub(super) fn complete_step(&mut self, step_id: &StepId, context: &mut Context) { - if &self.active_step_id != step_id { - // this is not the active step for this minion any more - return; + activation_id_generator: &mut ActivationIdGenerator, + ) -> Option<(ActivationId, &PolicyAction)> { + if let Some(next_policy) = try_activate(policy, activation_id_generator) { + self.active_policy = Some(next_policy); + self.current_action() + } else { + None } - - // If a step was queued, and the step ids match, there should never not be an active policy - let active_policy = self.active_policy.as_mut().unwrap(); - - match active_policy.step(context) { - Some(completion) => self.enqueue_next_step(context, completion), - None => { - self.active_policy = None; - self.activate_highest_policy(context); - }, - }; } - fn enqueue_next_step(&mut self, context: &mut Context, completion: Instant) { - self.active_step_id = self.active_step_id.next(); - - context - .manager - .enqueue_step(*context.minion.id(), self.active_step_id, completion); + fn current_action(&self) -> Option<(ActivationId, &PolicyAction)> { + let active_policy = self.active_policy.as_ref()?; + let current_action = active_policy.current_action()?; + Some((active_policy.activation_id, current_action)) } fn has_precedence_over_active(&self, policy: &Policy) -> bool { @@ -112,21 +96,20 @@ impl PolicyAgent { fn try_activate( policy: &Policy, - context: &Context, + activation_id_generator: &mut ActivationIdGenerator, ) -> Option> { if !policy.trigger.is_triggered() { return None; } + let activation_id = activation_id_generator.next(); let actions: Box<[PolicyAction]> = policy.actions.iter().cloned().collect(); - let first = actions.first()?; - Some(ActivePolicy { priority: policy.priority, - active_action: activate_action(first, context.minion), actions, - next_action: 1, + current_action: 0, + activation_id, }) } @@ -146,42 +129,48 @@ fn create_dummy_policy() -> Policy { } } -#[derive(Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub(super) struct StepId(u64); +#[derive(Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Debug)] +pub(crate) struct ActivationId(u64); + +#[derive(Serialize, Deserialize, Default)] +pub(crate) struct ActivationIdGenerator { + next_id: ActivationId, +} + +impl ActivationIdGenerator { + pub fn next(&mut self) -> ActivationId { + let next_id = self.next_id.next(); + mem::replace(&mut self.next_id, next_id) + } +} -impl StepId { +impl ActivationId { pub fn next(&self) -> Self { Self(self.0 + 1) } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub(super) struct ActivePolicy { priority: PolicyPriority, - active_action: Action, actions: Box<[PolicyAction]>, - next_action: usize, + current_action: usize, + pub(super) activation_id: ActivationId, } impl ActivePolicy { - pub fn step(&mut self, context: &mut Context) -> Option { - loop { - if let Some(step_completion) = self.active_action.update(context) { - return Some(step_completion); - } - - let next = self.next_action()?; - self.active_action = activate_action(next, context.minion); - } + pub fn current_action(&self) -> Option<&PolicyAction> { + self.actions.get(self.current_action) } - fn next_action(&mut self) -> Option<&PolicyAction> { - let action = self.actions.get(self.next_action); + pub fn advance_to_next_action(&mut self) -> bool { + let next_action = self.actions.get(self.current_action + 1); - if action.is_some() { - self.next_action += 1; + if next_action.is_some() { + self.current_action += 1; + true + } else { + false } - - action } } diff --git a/game_server/src/game/minions/policy_evaluator.rs b/game_server/src/game/minions/policy_evaluator.rs new file mode 100644 index 0000000..e7a457d --- /dev/null +++ b/game_server/src/game/minions/policy_evaluator.rs @@ -0,0 +1,286 @@ +use super::actions::{activate_action, ActionStep}; +use super::event_queue::{EventQueue, ExportedEventQueue}; +use super::minion::Minion; +use super::minion_manager::MinionManager; +use super::policies::{ActivationId, ActivationIdGenerator, Context, PolicyAgent}; +use super::LoadDataError; +use crate::game::game_board::GameBoard; +use game_shared::game::minion::MinionId; +use game_shared::game::policies::Policy; +use log::{error, info, warn}; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::path::{Path, PathBuf}; +use std::time::Instant; +use tokio::fs::{File, OpenOptions}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[derive(Serialize, Deserialize, Debug)] +pub(super) struct ActionStepEventItem { + minion_id: MinionId, + activation_id: ActivationId, + action_step: ActionStep, +} + +pub(crate) struct PolicyEvaluator { + action_step_queue: EventQueue, + pending_policy_triggers: HashSet, + evaluator_file_path: PathBuf, + activation_id_generator: ActivationIdGenerator, +} + +impl PolicyEvaluator { + pub async fn load(data_path: &Path) -> Result { + let evaluator_file_path = data_path.join("policy_evaluator.rmp"); + + let now = Instant::now(); + + match ExportedPolicyEvaluator::load(&evaluator_file_path).await { + Ok(Some(exported)) => Ok(Self { + action_step_queue: exported.action_step_queue.now_into(now), + pending_policy_triggers: exported.pending_policy_triggers, + evaluator_file_path, + activation_id_generator: exported.activation_id_generator, + }), + Ok(None) => Ok(Self { + action_step_queue: Default::default(), + pending_policy_triggers: Default::default(), + evaluator_file_path, + activation_id_generator: Default::default(), + }), + Err(err) => { + error!("Could not load policy evaluator: {err}"); + Err(()) + }, + } + } + + pub fn process_complete_events( + &mut self, + minion_manager: &mut MinionManager, + game_board: &mut GameBoard, + ) -> Option { + let now = Instant::now(); + + while let Some((completion, event_item)) = self.action_step_queue.pop_complete(&now) { + self.process_complete_event(game_board, minion_manager, event_item, completion); + } + + self.action_step_queue.get_next_completion() + } + + pub fn try_activate_policy( + &mut self, + minion_manager: &mut MinionManager, + minion_id: MinionId, + policy: &Policy, + now: Instant, + ) -> Result<(), ()> { + let (minion, policy_agent) = minion_manager.get_mut(&minion_id).ok_or(())?; + + if let Some((activation_id, next_action)) = + policy_agent.activate_policy(policy, &mut self.activation_id_generator) + { + let action_step = activate_action(next_action, minion); + + self.action_step_queue.insert( + now, + ActionStepEventItem { + minion_id, + activation_id, + action_step, + }, + ); + } + + Ok(()) + } + + pub fn trigger_policy( + &mut self, + minion: &Minion, + policy_agent: &mut PolicyAgent, + now: Instant, + ) { + // Try to activate a higher policy + if let Some((activation_id, next_action)) = + policy_agent.activate_highest_policy(&mut self.activation_id_generator) + { + let action_step = activate_action(next_action, minion); + + self.action_step_queue.insert( + now, + ActionStepEventItem { + minion_id: *minion.id(), + activation_id, + action_step, + }, + ); + } + } + + pub async fn terminate(self) -> Result<(), ()> { + let now = Instant::now(); + let path = self.evaluator_file_path.clone(); + ExportedPolicyEvaluator::now_from(self, now) + .save(&path) + .await?; + Ok(()) + } + + fn process_complete_event( + &mut self, + game_board: &mut GameBoard, + minion_manager: &mut MinionManager, + event_item: ActionStepEventItem, + completion: Instant, + ) { + let minion_id = event_item.minion_id; + + let Some((minion, policy_agent)) = minion_manager.get_mut(&minion_id) else { + warn!("Minion {minion_id:?} does not exist any more"); + return; + }; + + if !policy_agent.is_active(&event_item.activation_id) { + return; + } + + let mut context = Context { + minion, + game_board, + now: completion, + }; + + let step_result = event_item.action_step.step(&mut context); + + for game_event in step_result.game_events { + game_event.execute(minion_manager, self, completion); + } + + // Current action still has another step + if let Some((next_completion, next_step)) = step_result.next_step { + self.action_step_queue.insert( + next_completion, + ActionStepEventItem { + minion_id, + activation_id: event_item.activation_id, + action_step: next_step, + }, + ); + } else { + let Some((minion, policy_agent)) = minion_manager.get_mut(&minion_id) else { + warn!("Minion {minion_id:?} does not exist any more"); + return; + }; + + self.try_activate_highest_policy_or_next_action(minion, policy_agent, completion); + } + } + + fn try_activate_highest_policy_or_next_action( + &mut self, + minion: &Minion, + policy_agent: &mut PolicyAgent, + now: Instant, + ) { + // First try to activate higher policy + let mut next_action = + policy_agent.activate_highest_policy(&mut self.activation_id_generator); + + // If we did not successfully activate higher policy try next action in current policy + if next_action.is_none() { + next_action = policy_agent.advance_to_next_action(); + } + + // If this fails then try activating the highest policy again now that + // there is no active policy any more + if next_action.is_none() { + next_action = policy_agent.activate_highest_policy(&mut self.activation_id_generator); + } + + if let Some((activation_id, next_action)) = next_action { + let action_step = activate_action(next_action, minion); + + self.action_step_queue.insert( + now, + ActionStepEventItem { + minion_id: *minion.id(), + activation_id, + action_step, + }, + ); + } + } +} + +#[derive(Serialize, Deserialize)] +enum EventItem { + PolicyTrigger(MinionId), + ActionStep(MinionId, ActivationId), +} + +#[derive(Serialize, Deserialize)] +struct ExportedPolicyEvaluator { + action_step_queue: ExportedEventQueue, + pending_policy_triggers: HashSet, + activation_id_generator: ActivationIdGenerator, +} + +impl ExportedPolicyEvaluator { + fn now_from(value: PolicyEvaluator, now: Instant) -> Self { + Self { + action_step_queue: ExportedEventQueue::now_from(value.action_step_queue, now), + pending_policy_triggers: value.pending_policy_triggers, + activation_id_generator: value.activation_id_generator, + } + } + + async fn save(self, file_path: &Path) -> Result<(), ()> { + info!( + "Saving {} action step queue items: {:?}", + self.action_step_queue.len(), + file_path + ); + let mut file = OpenOptions::new() + .create(true) + .write(true) + .open(file_path) + .await + .map_err(|err| { + error!("Could not open event queue file: {err}"); + })?; + + let mut serialized = vec![]; + + self.serialize(&mut rmp_serde::Serializer::new(&mut serialized)) + .map_err(|err| { + error!("Could not serialize minion manager: {err}"); + })?; + + file.write_all(&serialized).await.map_err(|err| { + error!("Could not write: {err}"); + })?; + + Ok(()) + } + + async fn load(file_path: &Path) -> Result, LoadDataError> { + if file_path.exists() { + let mut file = File::open(&file_path).await?; + let mut serialized = vec![]; + file.read_to_end(&mut serialized).await?; + let exported: Self = rmp_serde::from_slice(&serialized)?; + + info!( + "Loaded {} event queue items from {:?}", + exported.action_step_queue.len(), + file_path + ); + + Ok(Some(exported)) + } else { + Ok(None) + } + } +} diff --git a/game_server/src/game/mod.rs b/game_server/src/game/mod.rs index 5bfff27..1b56759 100644 --- a/game_server/src/game/mod.rs +++ b/game_server/src/game/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod async_connector; mod database; +mod events; mod game_board; pub(crate) mod game_instance; pub(crate) mod game_loop; diff --git a/game_server/src/game/notifiers/minion_update.rs b/game_server/src/game/notifiers/minion_update.rs index d55f90a..efd4a10 100644 --- a/game_server/src/game/notifiers/minion_update.rs +++ b/game_server/src/game/notifiers/minion_update.rs @@ -24,6 +24,6 @@ impl PlayersMinionUpdateNotifier { pub fn add(&self, receiver: PlayerId, minion: &Minion) { let mut pending = self.pending.write().unwrap(); - pending.push((receiver, *minion.id(), *minion.position(), *minion.health())); + pending.push((receiver, *minion.id(), minion.position, minion.health)); } } diff --git a/game_server/src/game/players/player_spawn/mod.rs b/game_server/src/game/players/player_spawn/mod.rs index 52b1dc6..23432f7 100644 --- a/game_server/src/game/players/player_spawn/mod.rs +++ b/game_server/src/game/players/player_spawn/mod.rs @@ -4,6 +4,7 @@ use self::circle_spawn::get_spawn_position; use crate::game::game_board::path_finding::{find_shortest_path, HAS_ANY_PATH_CONFIG}; use crate::game::game_board::GameBoard; use crate::game::minions::minion_manager::MinionManager; +use crate::game::minions::policy_evaluator::PolicyEvaluator; use game_shared::game::tiles::{PlayerBuildingType, TileBase, TileEntity, TileIndex}; use game_shared::network::messages::PlayerId; use game_shared::PosIndex; @@ -69,6 +70,7 @@ impl PlayerSpawn { player_id: PlayerId, minion_manager: &mut MinionManager, game_board: &mut GameBoard, + policy_evaluator: &mut PolicyEvaluator, ) -> TileIndex { let spawn_position = self.find_next_spawn(game_board); @@ -85,7 +87,7 @@ impl PlayerSpawn { minion_manager, ); - minion_manager.spawn(player_id, spawn_position, Instant::now()); + minion_manager.spawn(player_id, spawn_position, policy_evaluator, Instant::now()); spawn_position } diff --git a/game_server/src/game/world.rs b/game_server/src/game/world.rs index 17610bd..a164142 100644 --- a/game_server/src/game/world.rs +++ b/game_server/src/game/world.rs @@ -1,5 +1,6 @@ use super::game_board::GameBoard; use super::minions::minion_manager::MinionManager; +use super::minions::policy_evaluator::PolicyEvaluator; use super::notifiers::chunk_update::PlayersChunkUpdateNotifier; use super::notifiers::minion_update::PlayersMinionUpdateNotifier; use super::players::connected_players::ConnectedPlayers; @@ -20,6 +21,7 @@ use tokio::fs::create_dir_all; pub(crate) struct World { pub(super) game_board: GameBoard, minion_manager: MinionManager, + policy_evaluator: PolicyEvaluator, player_manager: ConnectedPlayers, player_spawn: PlayerSpawn, chunk_update_notifier: Arc, @@ -46,9 +48,12 @@ impl World { ) .await?; + let policy_evaluator = PolicyEvaluator::load(data_path).await?; + Ok(Self { game_board: GameBoard::new(data_path), minion_manager, + policy_evaluator, player_spawn: PlayerSpawn::new(), player_manager, chunk_update_notifier, @@ -58,8 +63,12 @@ impl World { pub fn spawn_player(&mut self, player_id: PlayerId) -> TileIndex { // TODO: check if player is already spawned - self.player_spawn - .spawn(player_id, &mut self.minion_manager, &mut self.game_board) + self.player_spawn.spawn( + player_id, + &mut self.minion_manager, + &mut self.game_board, + &mut self.policy_evaluator, + ) } pub fn join_game( @@ -79,11 +88,11 @@ impl World { pub fn activate_temporary_policy( &mut self, player_id: &PlayerId, - minion_id: &MinionId, + minion_id: MinionId, policy: &Policy, ) -> Result<(), ()> { { - let minion_with_agency = self.minion_manager.get(minion_id).ok_or(())?; + let minion_with_agency = self.minion_manager.get(&minion_id).ok_or(())?; if minion_with_agency.minion.owner() != player_id { return Err(()); @@ -91,8 +100,8 @@ impl World { }; let now = Instant::now(); - self.minion_manager - .try_activate_policy(&mut self.game_board, minion_id, policy, now) + self.policy_evaluator + .try_activate_policy(&mut self.minion_manager, minion_id, policy, now) } pub fn disconnect(&mut self, player_id: &PlayerId) { @@ -104,6 +113,7 @@ impl World { info!("Terminating game"); self.game_board.terminate(); self.minion_manager.terminate().await.unwrap(); + self.policy_evaluator.terminate().await.unwrap(); info!("Terminating game complete"); } @@ -117,8 +127,8 @@ impl World { pub fn tick(&mut self) -> Instant { let next_complete_minion_event = self - .minion_manager - .process_complete_events(&mut self.game_board) + .policy_evaluator + .process_complete_events(&mut self.minion_manager, &mut self.game_board) .unwrap_or_else(|| Instant::now() + Duration::from_millis(100)); self.notify_pending_minion_updates(); From 59c9c2e14983439dd04acb3283978d7725426b44 Mon Sep 17 00:00:00 2001 From: Timon Blauensteiner <2065342+darkclouder@users.noreply.github.com> Date: Fri, 29 Dec 2023 12:12:36 +0100 Subject: [PATCH 2/4] Fix bug where minion update is not sent after minion despawn --- game_client/src/gameboard/minions.rs | 3 +- game_server/src/game/events.rs | 6 +- game_server/src/game/minions/actions.rs | 3 +- .../src/game/minions/minion_manager.rs | 95 ++++++++----------- game_server/src/game/minions/policies.rs | 1 + .../src/game/minions/policy_evaluator.rs | 54 ++++++----- .../src/game/notifiers/minion_update.rs | 4 +- .../src/game/players/player_spawn/mod.rs | 3 +- 8 files changed, 83 insertions(+), 86 deletions(-) diff --git a/game_client/src/gameboard/minions.rs b/game_client/src/gameboard/minions.rs index 603fa5d..6e0dde7 100644 --- a/game_client/src/gameboard/minions.rs +++ b/game_client/src/gameboard/minions.rs @@ -9,7 +9,7 @@ use bevy::prelude::{ use bevy::sprite::{SpriteSheetBundle, TextureAtlasSprite}; use game_shared::game::minion::{Health, MinionId}; use game_shared::game::tiles::TileIndex; -use log::debug; +use log::{debug, info}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; #[derive(Resource)] @@ -60,6 +60,7 @@ fn receive_minions( while let Ok((minion_id, tile_index, health)) = received_minions_channel.receiver.try_recv() { debug!("Minion system update: {minion_id:?} {tile_index:?}"); + info!("Minion update: {minion_id:?} {tile_index:?} {health:?}"); let position = Vec2::new(tile_index.x as f32, tile_index.y as f32); diff --git a/game_server/src/game/events.rs b/game_server/src/game/events.rs index 22d2705..846c435 100644 --- a/game_server/src/game/events.rs +++ b/game_server/src/game/events.rs @@ -3,7 +3,6 @@ use super::minions::policy_evaluator::PolicyEvaluator; use game_shared::game::minion::{Damage, MinionId}; use game_shared::game::tiles::TileIndex; use std::ops::AddAssign; -use std::time::Instant; pub(crate) enum GameEvent { MinionSetPosition { @@ -21,14 +20,13 @@ impl GameEvent { self, minion_manager: &mut MinionManager, policy_evaluator: &mut PolicyEvaluator, - now: Instant, ) { match self { GameEvent::MinionSetPosition { position, target } => { - minion_manager.set_position(&target, position, policy_evaluator, now); + minion_manager.set_position(&target, position, policy_evaluator); }, GameEvent::MinionTakeDamage { damage, target } => { - minion_manager.take_damage(&target, damage, policy_evaluator, now); + minion_manager.take_damage(&target, damage, policy_evaluator); }, } } diff --git a/game_server/src/game/minions/actions.rs b/game_server/src/game/minions/actions.rs index 53e1bc5..dce9220 100644 --- a/game_server/src/game/minions/actions.rs +++ b/game_server/src/game/minions/actions.rs @@ -7,7 +7,6 @@ use crate::game::game_board::path_finding::{ use game_shared::game::minion::Damage; use game_shared::game::policies::PolicyAction; use game_shared::game::tiles::TileIndex; -use log::debug; use serde::{Deserialize, Serialize}; use std::time::{Duration, Instant}; @@ -66,7 +65,7 @@ impl MoveAction { Self { path: None, destination, - unit_speed: Duration::from_millis(1000), + unit_speed: Duration::from_millis(500), } } diff --git a/game_server/src/game/minions/minion_manager.rs b/game_server/src/game/minions/minion_manager.rs index 61c149d..980b615 100644 --- a/game_server/src/game/minions/minion_manager.rs +++ b/game_server/src/game/minions/minion_manager.rs @@ -83,7 +83,6 @@ impl MinionManager { owner: PlayerId, position: TileIndex, policy_evaluator: &mut PolicyEvaluator, - now: Instant, ) { let minion_id = self.next_minion_id; self.next_minion_id = minion_id.next(); @@ -96,8 +95,7 @@ impl MinionManager { }, ); - self.update_visiblity(&minion_id); - self.notify_minion_update(&minion_id, policy_evaluator, now); + self.set_position(&minion_id, position, policy_evaluator); } pub fn get(&self, minion_id: &MinionId) -> Option<&MinionWithAgency> { @@ -118,14 +116,20 @@ impl MinionManager { minion_id: &MinionId, position: TileIndex, policy_evaluator: &mut PolicyEvaluator, - now: Instant, ) { let Some(minion_with_agency) = self.minions.get_mut(minion_id) else { return; }; minion_with_agency.minion.position = position; - self.update_visiblity(minion_id); - self.notify_minion_update(minion_id, policy_evaluator, now); + + update_visiblity( + &minion_with_agency.minion, + &mut self.chunk_visibility, + &self.chunk_update_notifier, + ); + + let minion = self.minions.get(minion_id).unwrap().minion; + self.notify_minion_update(&minion, policy_evaluator); } pub fn take_damage( @@ -133,27 +137,27 @@ impl MinionManager { minion_id: &MinionId, damage: Damage, policy_evaluator: &mut PolicyEvaluator, - now: Instant, ) { - let Some(minion_with_agency) = self.minions.get_mut(minion_id) else { - return; + let is_dead = { + let Some(minion_with_agency) = self.minions.get_mut(minion_id) else { + return; + }; + + let minion = &mut minion_with_agency.minion; + + minion.health.take_damage(damage); + minion.health.is_dead() }; - let minion = &mut minion_with_agency.minion; - - minion.health.take_damage(damage); - - if minion.health.is_dead() { - self.despawn(minion_id, policy_evaluator, now); + if is_dead { + self.despawn(minion_id, policy_evaluator); + } else { + let minion = self.minions.get(minion_id).unwrap().minion; + self.notify_minion_update(&minion, policy_evaluator); } } - pub fn despawn( - &mut self, - minion_id: &MinionId, - policy_evaluator: &mut PolicyEvaluator, - now: Instant, - ) { + pub fn despawn(&mut self, minion_id: &MinionId, policy_evaluator: &mut PolicyEvaluator) { let Some(minion_with_agency) = self.minions.remove(minion_id) else { return; }; @@ -164,7 +168,7 @@ impl MinionManager { self.chunk_update_notifier.add(owner, *chunk_index); } - self.notify_minion_update(minion_id, policy_evaluator, now); + self.notify_minion_update(&minion_with_agency.minion, policy_evaluator); } pub fn visibility_chunk_mask( @@ -182,39 +186,22 @@ impl MinionManager { self.chunk_visibility.minions_with_visiblity(at) } - fn notify_minion_update( - &mut self, - minion_id: &MinionId, - policy_evaluator: &mut PolicyEvaluator, - now: Instant, - ) { + fn notify_minion_update(&self, minion: &Minion, policy_evaluator: &mut PolicyEvaluator) { // TODO: send minion disappeared message to old_position owner's that are not in target - let Some(minion_with_agency) = self.minions.get(minion_id) else { - return; - }; - let minion = &minion_with_agency.minion; - let minion_position = minion.position; - let target_observer_minions: Box<[MinionState]> = self - .minions_with_visiblity(&minion_position) + .minions_with_visiblity(&minion.position) .copied() .collect(); - let target_observer_players = + let mut target_observer_players = HashSet::::from_iter(target_observer_minions.iter().map(|ms| ms.owner)); + target_observer_players.insert(*minion.owner()); for player_id in target_observer_players { self.minion_update_notifier.add(player_id, minion); } for minion_state in target_observer_minions.iter() { - let Some(minion_with_agency) = self.minions.get_mut(&minion_state.minion_id) else { - continue; - }; - policy_evaluator.trigger_policy( - &minion_with_agency.minion, - &mut minion_with_agency.policy_agent, - now, - ); + policy_evaluator.trigger_policy(minion_state.minion_id); } } @@ -258,18 +245,18 @@ impl MinionManager { .await?; Ok(()) } +} - fn update_visiblity(&mut self, minion_id: &MinionId) { - let Some(minion_with_agency) = self.minions.get(minion_id) else { - return; - }; - - let player_id = *minion_with_agency.minion.owner(); - let updated_chunks = self.chunk_visibility.update(&minion_with_agency.minion); +fn update_visiblity( + minion: &Minion, + chunk_visibility: &mut ChunkVisibility, + chunk_update_notifier: &PlayersChunkUpdateNotifier, +) { + let player_id = *minion.owner(); + let updated_chunks = chunk_visibility.update(minion); - for chunk_index in updated_chunks.iter() { - self.chunk_update_notifier.add(player_id, *chunk_index); - } + for chunk_index in updated_chunks.iter() { + chunk_update_notifier.add(player_id, *chunk_index); } } diff --git a/game_server/src/game/minions/policies.rs b/game_server/src/game/minions/policies.rs index 433e1a2..8c1c4af 100644 --- a/game_server/src/game/minions/policies.rs +++ b/game_server/src/game/minions/policies.rs @@ -119,6 +119,7 @@ fn create_dummy_policy() -> Policy { PolicyAction::Move(MovePolicyDestination::Relative(TileIndex { x: 0, y: 5 })), PolicyAction::Move(MovePolicyDestination::Relative(TileIndex { x: -5, y: 0 })), PolicyAction::Move(MovePolicyDestination::Relative(TileIndex { x: 0, y: -5 })), + PolicyAction::Suicide, ]; Policy { diff --git a/game_server/src/game/minions/policy_evaluator.rs b/game_server/src/game/minions/policy_evaluator.rs index e7a457d..bd248ea 100644 --- a/game_server/src/game/minions/policy_evaluator.rs +++ b/game_server/src/game/minions/policy_evaluator.rs @@ -64,6 +64,7 @@ impl PolicyEvaluator { while let Some((completion, event_item)) = self.action_step_queue.pop_complete(&now) { self.process_complete_event(game_board, minion_manager, event_item, completion); + self.process_pending_policy_triggers(completion, minion_manager); } self.action_step_queue.get_next_completion() @@ -96,27 +97,8 @@ impl PolicyEvaluator { Ok(()) } - pub fn trigger_policy( - &mut self, - minion: &Minion, - policy_agent: &mut PolicyAgent, - now: Instant, - ) { - // Try to activate a higher policy - if let Some((activation_id, next_action)) = - policy_agent.activate_highest_policy(&mut self.activation_id_generator) - { - let action_step = activate_action(next_action, minion); - - self.action_step_queue.insert( - now, - ActionStepEventItem { - minion_id: *minion.id(), - activation_id, - action_step, - }, - ); - } + pub fn trigger_policy(&mut self, minion_id: MinionId) { + self.pending_policy_triggers.insert(minion_id); } pub async fn terminate(self) -> Result<(), ()> { @@ -128,6 +110,34 @@ impl PolicyEvaluator { Ok(()) } + fn process_pending_policy_triggers( + &mut self, + now: Instant, + minion_manager: &mut MinionManager, + ) { + for minion_id in self.pending_policy_triggers.drain() { + let Some((minion, policy_agent)) = minion_manager.get_mut(&minion_id) else { + continue; + }; + + // Try to activate a higher policy + if let Some((activation_id, next_action)) = + policy_agent.activate_highest_policy(&mut self.activation_id_generator) + { + let action_step = activate_action(next_action, minion); + + self.action_step_queue.insert( + now, + ActionStepEventItem { + minion_id, + activation_id, + action_step, + }, + ); + } + } + } + fn process_complete_event( &mut self, game_board: &mut GameBoard, @@ -155,7 +165,7 @@ impl PolicyEvaluator { let step_result = event_item.action_step.step(&mut context); for game_event in step_result.game_events { - game_event.execute(minion_manager, self, completion); + game_event.execute(minion_manager, self); } // Current action still has another step diff --git a/game_server/src/game/notifiers/minion_update.rs b/game_server/src/game/notifiers/minion_update.rs index efd4a10..2de7ec1 100644 --- a/game_server/src/game/notifiers/minion_update.rs +++ b/game_server/src/game/notifiers/minion_update.rs @@ -3,7 +3,7 @@ use crate::game::players::connected_players::ConnectedPlayers; use game_shared::game::minion::{Health, MinionId}; use game_shared::game::tiles::TileIndex; use game_shared::network::messages::PlayerId; -use log::error; +use log::{debug, error}; use std::sync::RwLock; #[derive(Default)] @@ -16,6 +16,8 @@ impl PlayersMinionUpdateNotifier { let mut pending = self.pending.write().unwrap(); for (receiver, minion_id, position, health) in pending.drain(..) { + debug!("Notify: {minion_id:?} {position:?} {health:?}"); + if let Err(err) = player_manager.send_minion(&receiver, minion_id, position, health) { error!("Could not send minion {minion_id:?}: {err:?}"); } diff --git a/game_server/src/game/players/player_spawn/mod.rs b/game_server/src/game/players/player_spawn/mod.rs index 23432f7..11742ca 100644 --- a/game_server/src/game/players/player_spawn/mod.rs +++ b/game_server/src/game/players/player_spawn/mod.rs @@ -9,7 +9,6 @@ use game_shared::game::tiles::{PlayerBuildingType, TileBase, TileEntity, TileInd use game_shared::network::messages::PlayerId; use game_shared::PosIndex; use log::info; -use std::time::Instant; const MIN_SPAWN_DISTANCE: f32 = 64.0; @@ -87,7 +86,7 @@ impl PlayerSpawn { minion_manager, ); - minion_manager.spawn(player_id, spawn_position, policy_evaluator, Instant::now()); + minion_manager.spawn(player_id, spawn_position, policy_evaluator); spawn_position } From 7a91eb293e6cc826abff97cb1553fdcadef3c892 Mon Sep 17 00:00:00 2001 From: Timon Blauensteiner <2065342+darkclouder@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:01:32 +0100 Subject: [PATCH 3/4] Improve path finding code --- .../src/game/game_board/path_finding.rs | 121 +++++++++--------- 1 file changed, 63 insertions(+), 58 deletions(-) diff --git a/game_server/src/game/game_board/path_finding.rs b/game_server/src/game/game_board/path_finding.rs index e642dad..757a93d 100644 --- a/game_server/src/game/game_board/path_finding.rs +++ b/game_server/src/game/game_board/path_finding.rs @@ -123,64 +123,7 @@ pub(crate) fn find_shortest_path( let cost_to_current = *cost_from_start.get(¤t_index).unwrap(); - let neighbors = [ - ( - TileIndex { - x: current_index.x + 1, - y: current_index.y, - }, - 1.0, - ), - ( - TileIndex { - x: current_index.x, - y: current_index.y + 1, - }, - 1.0, - ), - ( - TileIndex { - x: current_index.x - 1, - y: current_index.y, - }, - 1.0, - ), - ( - TileIndex { - x: current_index.x, - y: current_index.y - 1, - }, - 1.0, - ), - ( - TileIndex { - x: current_index.x + 1, - y: current_index.y + 1, - }, - consts::SQRT_2, - ), - ( - TileIndex { - x: current_index.x + 1, - y: current_index.y - 1, - }, - consts::SQRT_2, - ), - ( - TileIndex { - x: current_index.x - 1, - y: current_index.y + 1, - }, - consts::SQRT_2, - ), - ( - TileIndex { - x: current_index.x - 1, - y: current_index.y - 1, - }, - consts::SQRT_2, - ), - ]; + let neighbors = get_neighbors(¤t_index); for (neighbor_index, cost_factor) in neighbors { let tentative_cost = cost_from_start @@ -220,6 +163,68 @@ pub(crate) fn find_shortest_path( None } +fn get_neighbors(position: &TileIndex) -> impl Iterator { + [ + ( + TileIndex { + x: position.x + 1, + y: position.y, + }, + 1.0, + ), + ( + TileIndex { + x: position.x, + y: position.y + 1, + }, + 1.0, + ), + ( + TileIndex { + x: position.x - 1, + y: position.y, + }, + 1.0, + ), + ( + TileIndex { + x: position.x, + y: position.y - 1, + }, + 1.0, + ), + ( + TileIndex { + x: position.x + 1, + y: position.y + 1, + }, + consts::SQRT_2, + ), + ( + TileIndex { + x: position.x + 1, + y: position.y - 1, + }, + consts::SQRT_2, + ), + ( + TileIndex { + x: position.x - 1, + y: position.y + 1, + }, + consts::SQRT_2, + ), + ( + TileIndex { + x: position.x - 1, + y: position.y - 1, + }, + consts::SQRT_2, + ), + ] + .into_iter() +} + #[inline] fn to_priority(value: Cost) -> CostPriority { Reverse(NotNan::new(value).unwrap()) From 33ff09a461671836eaab629461550657b9a79daa Mon Sep 17 00:00:00 2001 From: Timon Blauensteiner <2065342+darkclouder@users.noreply.github.com> Date: Sat, 6 Jan 2024 16:42:41 +0100 Subject: [PATCH 4/4] Add login arguments --- game_client/src/gameboard/mod.rs | 6 ++++- game_client/src/main.rs | 8 ++++++- game_server/src/game/game_loop.rs | 7 +++++- .../src/game/minions/minion_manager.rs | 4 ++-- game_server/src/game/user_database.rs | 24 +++++++------------ game_shared/src/password.rs | 8 +++++++ 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/game_client/src/gameboard/mod.rs b/game_client/src/gameboard/mod.rs index 9af5dab..ece29d6 100644 --- a/game_client/src/gameboard/mod.rs +++ b/game_client/src/gameboard/mod.rs @@ -26,9 +26,11 @@ use game_shared::game::policies::{ }; use game_shared::game::tiles::TileIndex; use game_shared::network::serialization::SerializationFormat; +use game_shared::password::Password; use log::{error, info}; use ordered_float::NotNan; use std::ops::Deref; +use std::sync::Arc; use std::time::Duration; use url::Url; @@ -44,6 +46,8 @@ const ARROW_MOVE_STEP_SIZE: f32 = 256.0; pub struct GameConfig { pub serializer: SerializationFormat, pub server_url: Url, + pub username: Arc, + pub password: Password, } #[derive(Resource, Default, Clone)] @@ -208,7 +212,7 @@ fn setup_network( info!("Authenticating"); client - .authenticate("player".into(), "secret".into()) + .authenticate(config.username.clone(), config.password.clone()) .await .expect("Authentication should work"); info!("Authenticated"); diff --git a/game_client/src/main.rs b/game_client/src/main.rs index 00d36a7..444b05d 100644 --- a/game_client/src/main.rs +++ b/game_client/src/main.rs @@ -8,7 +8,7 @@ mod network; mod ui; use bevy::prelude::{default, App, PluginGroup, PreStartup}; -use bevy::window::{Window, WindowPlugin}; +use bevy::window::{PresentMode, Window, WindowPlugin}; use bevy::DefaultPlugins; use bevy_egui::EguiPlugin; use clap::{arg, Parser}; @@ -25,6 +25,10 @@ use ui::GameUiPlugin; struct Args { #[arg(long)] serializer: Option, + #[arg(long)] + username: Option, + #[arg(long)] + password: Option, } fn main() { @@ -52,6 +56,8 @@ fn main() { GameBoardPlugin::new(GameConfig { serializer, server_url: "ws://127.0.0.1:8080".try_into().unwrap(), + username: args.username.unwrap_or("player".into()).into(), + password: args.password.unwrap_or("secret".into()).into(), }), GameUiPlugin, )) diff --git a/game_server/src/game/game_loop.rs b/game_server/src/game/game_loop.rs index a9d07db..e8ac7e3 100644 --- a/game_server/src/game/game_loop.rs +++ b/game_server/src/game/game_loop.rs @@ -33,7 +33,12 @@ impl GameLoop { rt.block_on(async move { let player_database = Arc::new(PlayerDatabase::new(database.clone())); let user_database = Arc::new(UserDatabase::new(database.clone())); - let _ = user_database.create_dummy_user().await; + user_database + .create_user("player".into(), "secret".into()) + .await; + user_database + .create_user("second".into(), "second".into()) + .await; info!("Initializing world"); let mut world = World::new(&data_path).await.unwrap(); diff --git a/game_server/src/game/minions/minion_manager.rs b/game_server/src/game/minions/minion_manager.rs index 980b615..dacb9b2 100644 --- a/game_server/src/game/minions/minion_manager.rs +++ b/game_server/src/game/minions/minion_manager.rs @@ -142,9 +142,9 @@ impl MinionManager { let Some(minion_with_agency) = self.minions.get_mut(minion_id) else { return; }; - + let minion = &mut minion_with_agency.minion; - + minion.health.take_damage(damage); minion.health.is_dead() }; diff --git a/game_server/src/game/user_database.rs b/game_server/src/game/user_database.rs index 74676eb..62aebc6 100644 --- a/game_server/src/game/user_database.rs +++ b/game_server/src/game/user_database.rs @@ -47,30 +47,22 @@ impl UserDatabase { Self { database } } - pub async fn create_dummy_user(&self) -> Result { - let name: Arc = "player".into(); - let password = Password::from("secret"); // TODO: remove - - let password_salt = Salt::default(); - let password_hash = password.hash(&password_salt).map_err(|err| { - UserDatabaseError::InternalError( - format!("Could not create password hash: {err:?}").into(), - ) - })?; - - self.create_user(name, &password_hash, &password_salt).await - } - pub async fn create_user( &self, name: Arc, - password_hash: &PasswordHash, - password_salt: &Salt, + password: Password, ) -> Result { if let Some(user) = self.find_by_name(&name).await? { return Err(UserDatabaseError::UserAlreadyExists(name.clone(), user.id)); } + let password_salt = Salt::default(); + let password_hash = password.hash(&password_salt).map_err(|err| { + UserDatabaseError::InternalError( + format!("Could not create password hash: {err:?}").into(), + ) + })?; + info!("Creating user {name}"); let result = sqlx::query_as::<_, (i64,)>( diff --git a/game_shared/src/password.rs b/game_shared/src/password.rs index 110ba59..5a1d24c 100644 --- a/game_shared/src/password.rs +++ b/game_shared/src/password.rs @@ -77,6 +77,14 @@ impl From<&'static str> for Password { } } +impl From for Password { + fn from(value: String) -> Self { + Self { + value: value.into(), + } + } +} + impl Password { pub fn hash(&self, salt: &Salt) -> Result { let raw_salt: RawSalt = salt.try_into()?;