Non-Saturn players are re-requested from the server every frame #2

Closed
opened 2026-09-25 11:43:38 +00:00 by selimaj-dev · 0 comments
Owner

Problem

SaturnPlayer.get(name, uuid) (common/src/main/java/org/saturnclient/client/player/SaturnPlayer.java) can't tell "not fetched yet" apart from "fetched, not a Saturn user". Both are stored as null:

SaturnPlayer player = PLAYERS.get(uuid);
if (player == null) {
    PLAYERS.put(uuid, null);
    QUEUE.add(uuid);        // re-queued on every call
    startPlayerThread();
}

When get_player returns null (the player isn't running Saturn), the worker stores null. The next call re-queues the player, and so on forever.

It's called from the render path in every versions/* folder:

  • PlayerEntityRendererMixin: every visible player, every frame
  • TabListMixin: every tab-list entry, whenever the tab list renders

With 20 non-Saturn players visible at 60 FPS, that's about 1,200 queue entries per second. The worker sends one blocking get_player per entry to the production server, so:

  • the ConcurrentLinkedQueue grows without bound (client memory)
  • the server receives a constant stream of get_player requests
  • real Saturn players' cosmetics load late, stuck behind the backlog

Fix

  • Record lookups that returned null, for example with containsKey or a "not Saturn" marker, and don't re-queue them.
  • Re-check them only after a cooldown (say 60 s), or when the server announces the player through the player notification.
  • Don't queue a UUID that is already queued or in flight.
## Problem `SaturnPlayer.get(name, uuid)` (`common/src/main/java/org/saturnclient/client/player/SaturnPlayer.java`) can't tell "not fetched yet" apart from "fetched, not a Saturn user". Both are stored as `null`: ```java SaturnPlayer player = PLAYERS.get(uuid); if (player == null) { PLAYERS.put(uuid, null); QUEUE.add(uuid); // re-queued on every call startPlayerThread(); } ``` When `get_player` returns `null` (the player isn't running Saturn), the worker stores `null`. The next call re-queues the player, and so on forever. It's called from the render path in every `versions/*` folder: - `PlayerEntityRendererMixin`: every visible player, every frame - `TabListMixin`: every tab-list entry, whenever the tab list renders With 20 non-Saturn players visible at 60 FPS, that's about 1,200 queue entries per second. The worker sends one blocking `get_player` per entry to the production server, so: - the `ConcurrentLinkedQueue` grows without bound (client memory) - the server receives a constant stream of `get_player` requests - real Saturn players' cosmetics load late, stuck behind the backlog ## Fix - Record lookups that returned `null`, for example with `containsKey` or a "not Saturn" marker, and don't re-queue them. - Re-check them only after a cooldown (say 60 s), or when the server announces the player through the `player` notification. - Don't queue a UUID that is already queued or in flight.
Sign in to join this conversation.