A Minecraft Forge mod that synchronizes player data across multiple servers using a MySQL backend.
Go to file
laforetbrut fa7033fdea Phase 13: batch RS2 disk saves + force-claim ghost sessions after 15s
Two targeted fixes based on the 2026-04-22 06:26+ production log run.

(1) RS2 disk writes: one batched transaction instead of N sequential REPLACE INTOs
    Every logout [perf-logout] line showed the same pattern:
        core=72ms  backpacks=6ms  ss=5ms  rs2=523ms  total=606ms
        core=56ms  backpacks=4ms  ss=1ms  rs2=391ms  total=452ms
        core=77ms  backpacks=3ms  ss=1ms  rs2=409ms  total=490ms
    RS2 dominated the save path. Backpacks + SS were already batched via
    saveBackpackSnapshots since Phase 7, but saveRS2DisksByLevel still
    looped saveStorageContents (one REPLACE INTO per disk).

    Fix: collect every disk's NBT into Map<UUID, CompoundTag> first, then
    delegate to saveBackpackSnapshots (same table, same batched transaction
    path with per-entry fallback on failure). Expected ~10x reduction in
    rs2= duration for players with 3-4 disks.

(2) Ghost-session force-claim: absolute 15s cap instead of stale-heartbeat-only
    Fresh field logs showed the exact scenario Phase 10 left unsolved:
        06:26:43  RESTORE started for 95d0db86
        06:27:44  RESTORE completed in 60627ms   (full poll timeout)
        06:58:16  RESTORE started for 5d582bbc
        06:59:17  RESTORE completed in 61630ms   (full poll timeout)
    The peer's heartbeat was always fresh (age 2-28s, well under the 60s
    stale threshold), so Phase 11's 'only force-claim if stale' gate never
    fired — the loop ran the full 120 attempts. Meanwhile [perf-logout]
    proves real saves commit in < 1s, so a peer that hasn't flushed after
    15s is a ghost session (player disconnected uncleanly, flag stuck at
    online=1). Waiting another 45s for a save that isn't coming is pure
    UX cost.

    Fix: after join_peer_alive_max_wait_seconds (default raised from 5 to
    15), force-claim unconditionally. Safe because:
      - 15s is 15x the max observed save time — real saves are always
        committed to DB by then.
      - Phase 2's last_server guard already blocks any late write from
        the ghost session (the guard logs [GUARD] on the peer's side).
      - Phase 10 duplication scenario (force-claim before peer's async
        save commits) can no longer happen with this safer threshold.

    Peer-truly-stale short-circuit (heartbeat > 60s old) still triggers
    instantly via the isPeerServerStale() check at the top of the loop —
    only the 'peer alive but player ghost' path changed semantics.
2026-04-22 09:04:53 +02:00
.github Bump gradle/actions from 4 to 5 2025-10-14 16:55:21 +08:00
compat-mods Add compat-mods staging folder for mod compatibility analysis 2026-04-22 03:33:11 +02:00
gradle/wrapper migrate from ForgeGradle to ModDevGradle legacy 2025-05-02 22:40:39 +00:00
src/main Phase 13: batch RS2 disk saves + force-claim ghost sessions after 15s 2026-04-22 09:04:53 +02:00
.gitattributes Initial commit 2022-12-08 16:59:20 +08:00
.gitignore Fix backpack/curios dup, perf overhaul, drop chat+cobblemon 2026-04-22 02:50:26 +02:00
build.gradle jarJar: declare version ranges for MySQL + HikariCP 2026-04-22 06:46:24 +02:00
CHANGELOG.md Phase 8: 20+ new config keys + 14 admin commands (/playersync) 2026-04-22 06:34:02 +02:00
docker-compose.yml use volume for docker-compose db to persist data 2025-05-01 18:42:58 +00:00
ERROR_LOG.md Phase 6: docs (CHANGELOG, ERROR_LOG, TEST_PROCEDURE) 2026-04-22 06:09:08 +02:00
gradle.properties perf: zero JDBC on server thread + HikariCP + parallel shutdown + audit fixes 2026-03-29 18:58:27 +02:00
gradlew migrate from ForgeGradle to ModDevGradle legacy 2025-05-02 22:40:39 +00:00
gradlew.bat migrate from ForgeGradle to ModDevGradle legacy 2025-05-02 22:40:39 +00:00
LICENSE Create LICENSE 2022-12-08 17:11:47 +08:00
README.md readme: add section on how to setup a dev env 2025-05-01 16:59:05 +00:00
settings.gradle half done, noticed about advancement can't store normally 2025-06-07 00:55:30 +08:00
TEST_PROCEDURE_v2.1.5.html Phase 6: docs (CHANGELOG, ERROR_LOG, TEST_PROCEDURE) 2026-04-22 06:09:08 +02:00

PlayerSync

PlayerSync is a Minecraft Forge mod that synchronizes player data across multiple servers using a MySQL backend. It allows players to maintain their inventory, equipment, experience, advancements, and more when moving between servers in a network.

Mod Support

Any other mods support is also possible.

Development Setup

Database Setup (Docker)

A docker-compose.yml file is provided for easily setting up a MariaDB database instance for development testing.

  1. Make sure Docker is installed.
  2. Inside your work directory run:
    docker compose up -d
    
    This will download the MariaDB image (if not already present) and start a database container in the background.
  3. Stoppinng the Database
    docker compose down
    

Data Persistence: The database uses a Docker volume, ensuring your data persists even if you stop and restart the containers.

Database Management Tool

The docker-compose.yml also includes an Adminer service, a lightweight database management tool.

For debugging purposes, you can enable use_legacy_serialization to have readable database fields. This can cause crashes and unintended side-effects. Do not enable this on a production server if not absolutely necessary!

Running the Mod

The project uses Gradle for building and running. Use the provided Gradle wrapper (gradlew for Linux/macOS, gradlew.bat for Windows).

  1. Make sure that the MySQL database you configured is running.
  2. Run the Server
    ./gradlew runServer
    
    or on Windows:
    .\gradlew.bat runServer
    
    This task compiles the mod and starts a dedicated Minecraft server instance with the mod loaded in the run directory.
  3. Run the Client
    ./gradlew runClient
    
    or on Windows:
    .\gradlew.bat runClient