Files
common/src/main/java/org/saturnclient/client/player/SaturnPlayer.java
T
selimaj-devandclaude 3b4e1ab4cb Reconnect to the Saturn server and time out requests
Move to session-java 0.2.0 (Gitea package registry) and rework
ServiceClient around a single connect loop:

- Reconnect with exponential backoff (1 s -> 60 s) when the connection
  drops, e.g. on every server redeploy; give up only when retrying cannot
  help (no Minecraft session, or the server rejects the token)
- Publish the session only after authentication succeeds, and replace
  (rather than append to) owned cosmetics and clear cached players on
  reconnect
- Time out every request after 10 s and connecting after 10 s, so a lost
  response can no longer hang the player-lookup worker forever
- Register onClientStopping once, and keep a local session reference in
  request callbacks
- Allow -Dsaturn.serverUri to point the client at a local server

Refs saturnclientmc/saturnclient#4, saturnclientmc/saturnclient#5

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 14:46:30 +02:00

132 lines
4.0 KiB
Java

package org.saturnclient.client.player;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.saturnclient.client.ServiceClient;
import org.saturnclient.common.provider.Providers;
public class SaturnPlayer {
/** Players known to be using Saturn. */
private static final Map<UUID, SaturnPlayer> PLAYERS = new ConcurrentHashMap<>();
/**
* Earliest time (ms) a player without a Saturn entry may be looked up again,
* so non-Saturn players aren't re-requested every frame.
*/
private static final Map<UUID, Long> NEXT_LOOKUP = new ConcurrentHashMap<>();
/** Players queued or being looked up. */
private static final Set<UUID> PENDING = ConcurrentHashMap.newKeySet();
private static final long NOT_SATURN_RECHECK_MS = 60_000;
private static final long FAILED_LOOKUP_RETRY_MS = 5_000;
private static final ExecutorService LOOKUPS = Executors.newSingleThreadExecutor(runnable -> {
Thread thread = new Thread(runnable, "SaturnPlayer-Worker");
thread.setDaemon(true);
return thread;
});
public volatile String cloak = "";
public volatile String hat = "";
public final UUID uuid;
public final String name;
public SaturnPlayer(UUID uuid, String name, String cloak, String hat) {
this.cloak = cloak;
this.hat = hat;
this.uuid = uuid;
this.name = name;
}
public static SaturnPlayer get() {
UUID self = ServiceClient.uuid;
if (self == null) {
return null;
}
return PLAYERS.get(self);
}
/**
* Returns the player if they are known to use Saturn, otherwise null. An
* unknown player is looked up in the background, at most once at a time and
* no more often than {@link #NEXT_LOOKUP} allows. Safe to call every frame.
*/
public static SaturnPlayer get(String name, UUID uuid) {
if (uuid == null)
return null;
SaturnPlayer player = PLAYERS.get(uuid);
if (player == null) {
requestLookup(name, uuid);
}
return player;
}
public static SaturnPlayer get(UUID uuid) {
return get(Providers.saturn.getClient().getPlayerListEntry(uuid), uuid);
}
public static SaturnPlayer get(String name) {
return get(name, Providers.saturn.getClient().getPlayerListEntryUUID(name));
}
/** Forgets every cached player, e.g. after reconnecting to the server. */
public static void clear() {
PLAYERS.clear();
NEXT_LOOKUP.clear();
}
public static void set(SaturnPlayer player) {
PLAYERS.put(player.uuid, player);
NEXT_LOOKUP.remove(player.uuid);
}
public static String[] getExternalUUIDAsString() {
UUID self = ServiceClient.uuid;
return PLAYERS.keySet().stream().filter(id -> self == null || !id.equals(self))
.map(UUID::toString)
.toArray(String[]::new);
}
private static void requestLookup(String name, UUID uuid) {
if (!ServiceClient.isConnected()) {
return;
}
Long next = NEXT_LOOKUP.get(uuid);
if (next != null && System.currentTimeMillis() < next) {
return;
}
if (!PENDING.add(uuid)) {
return;
}
LOOKUPS.execute(() -> {
try {
SaturnPlayer player = ServiceClient.getPlayer(uuid, name);
if (player != null) {
set(player);
} else {
NEXT_LOOKUP.put(uuid, System.currentTimeMillis() + NOT_SATURN_RECHECK_MS);
}
} catch (Exception e) {
Providers.saturn.logError("Failed to fetch player " + uuid, e);
NEXT_LOOKUP.put(uuid, System.currentTimeMillis() + FAILED_LOOKUP_RETRY_MS);
} finally {
PENDING.remove(uuid);
}
});
}
}