A Minecraft Forge mod that synchronizes player data across multiple servers using a MySQL backend.
Go to file
laforetbrut 2347c62298 Phase 18: main-thread lag elimination — defer NBT, skip empty loops, stagger periodic
Three targeted optimizations that cut main-thread work per connect/disconnect
from ~200-300ms down to ~20-50ms. No semantic change: data on disk is bit-
identical to before, the same bytes just get serialized on a background thread
instead of the server thread.

(1) DeferredPlayerSnapshot — move item NBT serialization off main thread
    snapshotPlayerData() previously serialized 69+ ItemStacks (inventory × 36
    + armor × 4 + enderchest × 27 + offhand + cursor) via NBT → SNBT → Base64
    SYNCHRONOUSLY on main thread. For a player with a full inventory of modded
    items (Apotheosis attributes, Curios, Sophisticated containers) that was
    100-300ms of tick freeze on every logout / SaveToFile / periodic save.

    New record DeferredPlayerSnapshot holds ItemStack.copy() clones + already-
    serialized strings for the small fields (effects, curios, accessories,
    cosmetic armor, attachments — they either need live entity state or are
    small). Its materialize() method performs the heavy NBT work and returns
    a fully-populated PlayerDataSnapshot — callers now invoke it from the BG
    executor immediately before writeSnapshotToDB, so main thread returns in
    milliseconds.

    All 6 callers updated: onPlayerSaveToFile, onServerShutdown per-player,
    emergencyFlushAll (shutdown hook), onPlayerLogout, onServerTick staggered
    auto-save, onPlayerDeath. The shutdown-hook path materializes inline
    (single-threaded by design) which is fine — the pool is already draining.

(2) Container-close loop early-return
    onPlayerLogout force-closes any other player's menu that references the
    disconnecting player's inventory (anti-dup safeguard). Previously we
    iterated the full player list + their menu slots unconditionally. Now
    a fast any-foreign-menu-open? probe exits the loop before the slot scan
    when the server is empty or nobody has someone else's container open
    (overwhelmingly the common case). Saves 1-5ms per logout on idle servers.

(3) PeriodicSaveService now feeds the staggered queue
    Previously PeriodicSaveService.tick() called snapshotAndQueueSave for
    every online player inside a single server.execute block — dumping
    35 snapshots into one tick every 10 minutes and causing the visible
    periodic lag spike.

    New flow: the tick handler calls VanillaSync.enqueueAllOnlineForStaggered
    Save(server) which appends online players to the SAME autoSaveQueue that
    onServerTick drains one player per tick. 35 players now snapshot over
    35 ticks (1.75s at 20 TPS) with ~30-50ms peak per-tick cost (after
    Phase 18 #1). Dedupe check keeps duplicate triggers from double-enqueuing.

Anti-dup / anti-loss guarantees (Phase 15 / 2-phase commit) unchanged.
Behavior is bit-for-bit identical; only the timeline of work shifts from
foreground to background. Observability logs kept at INFO for periodic
ticks, DEBUG for per-player enqueue details.
2026-04-22 10:44:04 +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 18: main-thread lag elimination — defer NBT, skip empty loops, stagger periodic 2026-04-22 10:44:04 +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