A Minecraft Forge mod that synchronizes player data across multiple servers using a MySQL backend.
Go to file
laforetbrut 3a53ff2302 Phase 10: real durations in logs + safer Phase 9 (no force-claim before peer flush)
Two critical diagnostic/correctness improvements after user field report:
  - '20s latency between inventory syncs with a full test inventory'
  - 'duplication on throw + deposit in chest'
  - 'bad sync on fast inter-server transfer if disconnect too quickly after modification'

(1) Real durations — 'completed in 0ms' was a lie
    Every SyncLogger.saveCompleted / restoreCompleted call hardcoded 0 for the
    duration field. The log line always showed 'in 0ms' regardless of actual
    latency, making the user's 20s-latency reports impossible to reproduce from
    logs alone. Fixed across all 4 save paths (LOGOUT / SHUTDOWN / DEATH /
    EMERGENCY_FLUSH) and the RESTORE path. Durations are measured from the
    start of the BG task (or the start of the restore lock acquisition) to
    just before the success log line.

    New info log 'Logout save completed for {uuid} in {n}ms'
    New warn log '[perf-restore] slow restore for {uuid} ({n}ms)' above 1s
    New info log '[perf-logout] core=Xms backpacks=Yms ss=Zms rs2=Wms total=Nms'
         above 200 ms — breakdown so we can pinpoint which downstream write
         takes the time in the reported 20s cases.

(2) Phase 9 force-takeover could CAUSE duplication
    Phase 9 aimed to fix 30-60s join waits when the previous server was alive
    but the player was ghost-online there. It force-claimed after 5s. But if
    the peer was mid-way through a LEGITIMATE logout save (which is atomic
    with online=1 -> online=0 via writeSnapshotToDB setOffline=true), force-
    claiming before that commit read STALE DB data and restored the player
    from the PRE-disconnect state — e.g., an item the player dropped just
    before disconnect came back in inventory, duplicating with the ItemEntity
    the peer had already spawned in the world.

    Fix: the wait cap is now ADVISORY, not a hard force-claim. Past the cap,
    we only force-claim when the peer's heartbeat has FROZEN (age > cap ms)
    — meaning the peer's process is actually dead or stuck mid-tick, not
    just slow to flush. If the peer is still heartbeating normally, we keep
    waiting: writeSnapshotToDB + online=0 is an atomic UPDATE, so the flush
    WILL land, we just need to be patient. A warn line every 20 attempts
    (10s at default interval) tells admins the save is taking a long time
    so they can profile the peer's DB connection.

    New helper peerHeartbeatAgeMs(id) returns age in ms, Long.MAX_VALUE if
    the peer has no heartbeat row. Used to decide force-claim vs keep-waiting.
2026-04-22 07:32:44 +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 10: real durations in logs + safer Phase 9 (no force-claim before peer flush) 2026-04-22 07:32:44 +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