Skip to content

refactor: player save logic for async tasks and fmt usage#3720

Open
dudantas wants to merge 3 commits intoopentibiabr:mainfrom
dudantas:refactor-player-save
Open

refactor: player save logic for async tasks and fmt usage#3720
dudantas wants to merge 3 commits intoopentibiabr:mainfrom
dudantas:refactor-player-save

Conversation

@dudantas
Copy link
Member

@dudantas dudantas commented Oct 4, 2025

Description

Refactor player save logic to enable async scheduling and adopt fmt-based SQL construction. Replace stringstream with fmt::format and fmt::memory_buffer across save paths for lower allocations and clearer code. Add SaveManager::addTask to dispatch DB writes asynchronously when TOGGLE_SAVE_ASYNC is true, with a synchronous fallback. Update IOLoginDataSave methods to use the new task path and to build UPDATE SET clauses via pre-sized vectors. Harden DBInsert::execute to trim trailing commas, reserve buffers, clear state after runs, and use g_database(). Add DBResult::getNumColumns with m_numColumns initialized in the constructor for safer column handling. No schema or protocol changes.

This change modernizes SQL construction and introduces optional asynchronous save scheduling.

  • Motivation: Reduce main-thread blocking on player saves and cut stringstream overhead while keeping behavior and data formats stable.
  • Scope:
    • src/database/database.cpp/.hpp
      • DBResult: add getNumColumns(), store m_numColumns from mysql_num_fields().
      • DBInsert::execute(): safe concatenation, trailing-comma handling, buffer reserves, clear internal values, call g_database().
    • src/game/scheduling/save_manager.cpp/.hpp
      • SaveManager::addTask(std::function<void()>, std::string_view): wrap try/catch, log errors, run inline if TOGGLE_SAVE_ASYNC=false, use threadPool.detach_task otherwise.
    • src/io/functions/iologindata_save_player.cpp
      • Replace stringstream with fmt for INSERT rows and UPDATE SET joins.
      • Use g_database(), g_saveManager().addTask where applicable.
      • Ensure DBInsert::execute() is called after addRow loops for items/depot/inbox/rewards, etc.

Dependencies: fmt (already in tree), magic_enum (already in tree). No new external deps.

Behaviour

Actual

  • Player save paths build SQL with stringstream, causing extra allocations and risk of separator bugs.
  • Saves may block the main thread during DB I/O.

Expected

  • SQL built with fmt::format/memory_buffer with pre-reserved capacity.
  • Saves scheduled via SaveManager::addTask when TOGGLE_SAVE_ASYNC=true; synchronous fallback when disabled.
  • DBInsert batches free of trailing commas and cleared after execution.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested

Steps:

  1. Unit: DBResult.getNumColumns returns mysql_num_fields result; constructor sets m_numColumns.
  2. Unit: DBInsert::execute splits batches, trims trailing comma, clears internal buffers; verify queries sent to g_database().
  3. Integration (TOGGLE_SAVE_ASYNC=false): trigger full player save; verify rows in:
    • player_items, player_depotitems, player_rewards, player_inboxitems
    • player_spells, player_kills, forge_history, player_bosstiary
    • player_charms, player_prey, player_taskhunt
    • players (first-phase fields, skills, blessings, timers)
  4. Integration (TOGGLE_SAVE_ASYNC=true): same as above; assert main loop remains responsive; confirm tasks executed and errors logged on simulated DB failure.
  5. Performance: compare save latency and allocations vs baseline on large inventory+depot profile; target <= +5% CPU, equal or lower P95 latency.
  • Test A: Async enabled and disabled parity test on seeded character set
  • Test B: DB failure injection inside addTask path logs error and process remains stable

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I checked the PR checks reports
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

Copilot AI review requested due to automatic review settings October 4, 2025 19:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors player save logic to enable asynchronous task scheduling while replacing stringstream-based SQL construction with fmt-based formatting. The changes modernize SQL generation, reduce memory allocations, and introduce optional async save processing without altering data schemas or protocols.

  • Replaces inefficient stringstream SQL construction with fmt::format and fmt::memory_buffer
  • Adds SaveManager::addTask for async save scheduling with synchronous fallback
  • Improves DBInsert execution with trailing comma handling and buffer management

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/io/functions/iologindata_save_player.cpp Converts SQL generation from stringstream to fmt, adds async task scheduling, implements proper DBInsert::execute calls
src/game/scheduling/save_manager.hpp Adds addTask method declaration for async task scheduling
src/game/scheduling/save_manager.cpp Implements addTask with try/catch wrapper and sync/async execution based on TOGGLE_SAVE_ASYNC
src/database/database.hpp Adds getNumColumns method and m_numColumns member to DBResult
src/database/database.cpp Implements getNumColumns, improves DBInsert::execute with better memory management and trailing comma handling

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +200 to +202
std::vector<std::string> columns;
const size_t expectedColumnCount = std::max<size_t>(result->getNumColumns(), 96);
columns.reserve(expectedColumnCount);
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 96 should be replaced with a named constant that explains why this specific value is expected for the column count.

Copilot uses AI. Check for mistakes.
Comment on lines +574 to +577
if (!itemsQuery.execute()) {
g_logger().warn("[IOLoginData::savePlayer] - Error executing insert query 'player_items' for player: {}", player->getName());
return false;
}
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The execute() call for itemsQuery is now present, but it was missing in the original saveItems function. However, the saveItems function no longer calls execute() internally, which means items won't be saved unless execute() is called after saveItems. This creates a dependency that could lead to data loss if not handled properly in all calling contexts.

Copilot uses AI. Check for mistakes.
Comment on lines +475 to 477
if (!batchValues.empty() && batchValues.back() == ',') {
batchValues.pop_back();
}
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This trailing comma removal logic should be moved earlier in the process to prevent the comma from being added in the first place, rather than removing it after the fact. This would be more efficient and clearer.

Copilot uses AI. Check for mistakes.
Replaces stringstream-based SQL query construction with fmt::format and fmt::memory_buffer for improved performance and readability. Hardens DBInsert::execute to safely join base query, batch values, and ON DUPLICATE KEY UPDATE (no trailing commas), uses g_database(), and clears internal buffers after execution. Introduces SaveManager::addTask to schedule save operations asynchronously with try/catch and a synchronous fallback via TOGGLE_SAVE_ASYNC, reducing blocking on database writes. Updates IOLoginDataSave methods to use the new async task system and modernizes query building throughout player save routines. Adds getNumColumns to DBResult for more robust column handling.

Refactor player login/save logic to repository pattern

Introduces repository interfaces and DB-backed implementations for player login and save operations, as well as VIP account management. IOLoginData, IOLoginDataLoad, and IOLoginDataSave now delegate to these repositories via DI helpers, improving separation of concerns and testability. Build scripts (CMake/MSVC) updated to include new sources; friend declarations added to player.hpp.

Refactor column reserve logic in savePlayerFirst

Uses a static constexpr minimum reserve and a pre-sized vector of SET clauses joined with “, ” for clarity and maintainability; schedules the first-phase UPDATE via SaveManager::addTask when appropriate.
@dudantas dudantas force-pushed the refactor-player-save branch from 8c77979 to ac8dac5 Compare October 4, 2025 20:44
dudantas and others added 2 commits October 4, 2025 18:11
Introduces test override functions for ILoginDataLoadRepository and ILoginDataSaveRepository, allowing injection of test implementations. Adds unit tests to verify IOLoginDataLoad and IOLoginDataSave correctly forward calls to their respective repositories. Updates CMakeLists to include new test sources.
@sonarqubecloud
Copy link

sonarqubecloud bot commented Oct 4, 2025

@github-actions
Copy link
Contributor

github-actions bot commented Nov 4, 2025

This PR is stale because it has been open 45 days with no activity.

@github-actions github-actions bot added the Stale No activity label Nov 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Stale No activity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants