Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion crates/world-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4766,6 +4766,11 @@ async fn main() -> Result<ExitCode> {
86_400,
),
},
player_save_interval_ms: world_config_u32(
&world_configs,
"CONFIG_INTERVAL_SAVE",
15 * 60 * 1000,
),
realm_id,
realm_region: active_realm.id.region,
realm_battlegroup: active_realm.id.site,
Expand Down Expand Up @@ -11847,6 +11852,7 @@ async fn create_session(
session.set_chat_flood_config_like_cpp(resources.chat_flood_config);
session.set_socket_timeouts_like_cpp(resources.socket_timeouts);
session.set_packet_spoof_config_like_cpp(resources.packet_spoof_config);
session.set_player_save_interval_ms_like_cpp(resources.player_save_interval_ms);
session.set_legacy_creature_aggro_config_like_cpp(legacy_creature_aggro_config);
session.set_mmap_runtime_config_like_cpp(mmap_runtime_config);
if let Some(pathfinder) = mmap_pathfinder {
Expand Down Expand Up @@ -11919,10 +11925,18 @@ async fn create_session(
info!("Session ready for account {}", account.id);

// Session update loop
let mut last_session_update = Instant::now();
loop {
let (count, disconnecting) = warn_about_sync_queries_scope_like_cpp(async {
let now = Instant::now();
let diff_ms = now
.saturating_duration_since(last_session_update)
.as_millis()
.min(u128::from(u32::MAX)) as u32;
last_session_update = now;

// Process incoming packets
let count = session.update(50);
let count = session.update(diff_ms);

// Dispatch pending packets (async handlers)
session.process_pending().await;
Expand Down
2 changes: 2 additions & 0 deletions crates/wow-network/src/accept.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ pub struct SessionResources {
pub max_overspeed_pings: u32,
pub socket_timeouts: SocketTimeoutsLikeCpp,
pub packet_spoof_config: PacketSpoofConfigLikeCpp,
/// C++ `CONFIG_INTERVAL_SAVE` / `PlayerSaveInterval` in milliseconds.
pub player_save_interval_ms: u32,
pub realm_id: u16,
/// Region from `realmlist.Region`, used in C++ `RealmHandle::GetAddress()`.
pub realm_region: u8,
Expand Down
40 changes: 0 additions & 40 deletions crates/wow-world/src/handlers/character.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3751,46 +3751,6 @@ impl WorldSession {
self.send_packet(&LogoutCancelAck);
}

/// Save accumulated played time (`totaltime` + `leveltime`) back to the
/// characters database. Called on logout so time is not lost.
pub(crate) async fn save_played_time(&self) {
let guid = match self.player_guid() {
Some(g) => g,
None => return,
};

let char_db = match self.char_db() {
Some(db) => Arc::clone(db),
None => return,
};

// Compute current total values: base (from DB at login) + session elapsed.
let session_secs: u32 = self
.login_time
.map(|t| t.elapsed().as_secs() as u32)
.unwrap_or(0);
let total_time = self.total_played_time.saturating_add(session_secs);
let level_time = self.level_played_time.saturating_add(session_secs);

let mut stmt = char_db.prepare(CharStatements::UPD_CHAR_PLAYED_TIME);
stmt.set_u32(0, total_time);
stmt.set_u32(1, level_time);
stmt.set_u32(2, guid.counter() as u32);
if let Err(e) = char_db.execute(&stmt).await {
warn!(
"Failed to save played time for guid {}: {e}",
guid.counter()
);
} else {
info!(
"Saved played time: total={}s level={}s for guid {}",
total_time,
level_time,
guid.counter()
);
}
}

/// Mark the current character as offline in the database.
pub(crate) async fn mark_character_offline(&self) {
let guid = match self.player_guid() {
Expand Down
45 changes: 41 additions & 4 deletions crates/wow-world/src/reputation/mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,12 +584,12 @@ impl ReputationMgrLikeCpp {
}
}

pub fn save_to_db_statement_plan_like_cpp(
&mut self,
pub fn pending_save_to_db_statement_plan_like_cpp(
&self,
player_guid_counter: u64,
) -> Vec<PreparedStatement> {
let mut statements = Vec::new();
for faction in self.factions.values_mut() {
for faction in self.factions.values() {
if !faction.need_save {
continue;
}
Expand All @@ -607,9 +607,24 @@ impl ReputationMgrLikeCpp {
insert.set_i32(2, faction.standing);
insert.set_u16(3, faction.flags.bits());
statements.push(insert);
}
statements
}

faction.need_save = false;
pub fn mark_pending_save_to_db_committed_like_cpp(&mut self) {
for faction in self.factions.values_mut() {
if faction.need_save {
faction.need_save = false;
}
}
}

pub fn save_to_db_statement_plan_like_cpp(
&mut self,
player_guid_counter: u64,
) -> Vec<PreparedStatement> {
let statements = self.pending_save_to_db_statement_plan_like_cpp(player_guid_counter);
self.mark_pending_save_to_db_committed_like_cpp();
statements
}

Expand Down Expand Up @@ -1778,6 +1793,28 @@ mod tests {
assert!(!mgr.get_state(14).expect("saved state").need_save);
}

#[test]
fn pending_save_to_db_statement_plan_like_cpp_does_not_clear_dirty_until_commit() {
let mut mgr = ReputationMgrLikeCpp::new_like_cpp();
mgr.insert_state_for_test_like_cpp(FactionStateLikeCpp {
standing: 456,
flags: ReputationFlagsLikeCpp::VISIBLE,
..FactionStateLikeCpp::new_like_cpp(86, 15, ReputationFlagsLikeCpp::VISIBLE)
});

let statements = mgr.pending_save_to_db_statement_plan_like_cpp(44);

assert_eq!(statements.len(), 2);
assert!(
mgr.get_state(15).expect("pending state").need_save,
"failed Player::SaveToDB transaction must leave reputation dirty for retry"
);

mgr.mark_pending_save_to_db_committed_like_cpp();

assert!(!mgr.get_state(15).expect("committed state").need_save);
}

#[test]
fn set_one_faction_reputation_like_cpp_applies_incremental_rate_and_marks_visible_dirty() {
let mut faction = FactionEntry::for_test_like_cpp(86, 15);
Expand Down
Loading
Loading