Gif loading
This commit is contained in:
@@ -3,9 +3,7 @@ package org.saturnclient.client.player;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
import org.saturnclient.client.ServiceClient;
|
import org.saturnclient.client.ServiceClient;
|
||||||
@@ -15,7 +13,6 @@ public class SaturnPlayer {
|
|||||||
private static final Map<UUID, SaturnPlayer> PLAYERS = new HashMap<>();
|
private static final Map<UUID, SaturnPlayer> PLAYERS = new HashMap<>();
|
||||||
|
|
||||||
private static final Queue<UUID> QUEUE = new ConcurrentLinkedQueue<>();
|
private static final Queue<UUID> QUEUE = new ConcurrentLinkedQueue<>();
|
||||||
private static final Set<UUID> QUEUED = ConcurrentHashMap.newKeySet();
|
|
||||||
|
|
||||||
private static volatile boolean RUNNING = false;
|
private static volatile boolean RUNNING = false;
|
||||||
private static Thread WORKER;
|
private static Thread WORKER;
|
||||||
@@ -45,7 +42,7 @@ public class SaturnPlayer {
|
|||||||
|
|
||||||
SaturnPlayer player = PLAYERS.get(uuid);
|
SaturnPlayer player = PLAYERS.get(uuid);
|
||||||
|
|
||||||
if (player == null && QUEUED.add(uuid)) {
|
if (player == null) {
|
||||||
PLAYERS.put(uuid, null);
|
PLAYERS.put(uuid, null);
|
||||||
QUEUE.add(uuid);
|
QUEUE.add(uuid);
|
||||||
startPlayerThread();
|
startPlayerThread();
|
||||||
@@ -97,8 +94,6 @@ public class SaturnPlayer {
|
|||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Providers.saturn.logError("Failed to fetch player " + uuid, e);
|
Providers.saturn.logError("Failed to fetch player " + uuid, e);
|
||||||
} finally {
|
|
||||||
QUEUED.remove(uuid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastWorkTime = System.currentTimeMillis();
|
lastWorkTime = System.currentTimeMillis();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.io.InputStream;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.saturnclient.cosmetics.utils.AnimatedCloakData;
|
import org.saturnclient.cosmetics.utils.GifFrame;
|
||||||
import org.saturnclient.cosmetics.utils.GifDecoder;
|
import org.saturnclient.cosmetics.utils.GifDecoder;
|
||||||
// import org.saturnclient.cosmetics.utils.IdentifierUtils;
|
// import org.saturnclient.cosmetics.utils.IdentifierUtils;
|
||||||
|
|
||||||
@@ -21,18 +21,20 @@ import org.saturnclient.cosmetics.utils.GifDecoder;
|
|||||||
* Originally created by IIpho3nix and modified for Saturn Client by leo.
|
* Originally created by IIpho3nix and modified for Saturn Client by leo.
|
||||||
*/
|
*/
|
||||||
public class Cloaks {
|
public class Cloaks {
|
||||||
public static final String[] ALL_CLOAKS = { "glitch", "mercedes_flow", "crimson_mark", "bmw", "amg", "amg_petronas", "ferrari",
|
public static final String[] ALL_CLOAKS = { "glitch", "mercedes_flow", "crimson_mark", "bmw", "amg", "amg_petronas",
|
||||||
"redbull", "black_hole_amethyst", "black_hole_flame", "black_hole_white", "albania_mark", "end", "spanish_empire", "spain_flag" };
|
"ferrari",
|
||||||
|
"redbull", "black_hole_amethyst", "black_hole_flame", "black_hole_white", "albania_mark", "end",
|
||||||
|
"spanish_empire", "spain_flag" };
|
||||||
private static final String[] ANIMATED_CLOAKS = { "glitch", "black_hole_amethyst", "black_hole_flame",
|
private static final String[] ANIMATED_CLOAKS = { "glitch", "black_hole_amethyst", "black_hole_flame",
|
||||||
"black_hole_white" };
|
"black_hole_white" };
|
||||||
|
|
||||||
private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/";
|
private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/";
|
||||||
public static final List<String> availableCloaks = new ArrayList<>();
|
public static final List<String> availableCloaks = new ArrayList<>();
|
||||||
public static IdentifierRef cloakCacheIdentifier = null;
|
public static IdentifierRef cloakCacheIdentifier = null;
|
||||||
public static final Map<String, List<AnimatedCloakData>> animatedCloaks = new ConcurrentHashMap<>();
|
public static final Map<String, List<GifFrame>> animatedCloaks = new ConcurrentHashMap<>();
|
||||||
private static final Map<String, Long> lastFrameTime = new ConcurrentHashMap<>();
|
private static final Map<String, Long> lastFrameTime = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private static final ConcurrentHashMap<String, List<AnimatedCloakData>> CLOAK_CACHE = new ConcurrentHashMap<>();
|
private static final ConcurrentHashMap<String, List<GifFrame>> CLOAK_CACHE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the cloak system.
|
* Initializes the cloak system.
|
||||||
@@ -88,7 +90,7 @@ public class Cloaks {
|
|||||||
String fileName = cloakName + ".gif";
|
String fileName = cloakName + ".gif";
|
||||||
|
|
||||||
// Check cache first
|
// Check cache first
|
||||||
List<AnimatedCloakData> cached = CLOAK_CACHE.get(fileName);
|
List<GifFrame> cached = CLOAK_CACHE.get(fileName);
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
animatedCloaks.put(cloakName, cached);
|
animatedCloaks.put(cloakName, cached);
|
||||||
lastFrameTime.put(cloakName, System.currentTimeMillis());
|
lastFrameTime.put(cloakName, System.currentTimeMillis());
|
||||||
@@ -114,7 +116,7 @@ public class Cloaks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pre-allocate collections with known size
|
// Pre-allocate collections with known size
|
||||||
List<AnimatedCloakData> animatedFrames = new ArrayList<>(frameCount);
|
List<GifFrame> animatedFrames = new ArrayList<>(frameCount);
|
||||||
String baseFrameId = fileName.replace(".gif", "");
|
String baseFrameId = fileName.replace(".gif", "");
|
||||||
|
|
||||||
// Process frames in batch
|
// Process frames in batch
|
||||||
@@ -127,7 +129,7 @@ public class Cloaks {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Providers.saturn.registerBufferedImageTexture(frameIdentifier, frame);
|
Providers.saturn.registerBufferedImageTexture(frameIdentifier, frame);
|
||||||
animatedFrames.add(new AnimatedCloakData(frameIdentifier, delay));
|
animatedFrames.add(new GifFrame(frameIdentifier, delay));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Providers.saturn.logError("Failed to register frame " + i + " for cloak: " + fileName, e);
|
Providers.saturn.logError("Failed to register frame " + i + " for cloak: " + fileName, e);
|
||||||
// Continue with other frames instead of failing completely
|
// Continue with other frames instead of failing completely
|
||||||
@@ -155,7 +157,7 @@ public class Cloaks {
|
|||||||
|
|
||||||
public static IdentifierRef getCurrentCloakTexture(String cloakName) {
|
public static IdentifierRef getCurrentCloakTexture(String cloakName) {
|
||||||
if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) {
|
if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) {
|
||||||
List<AnimatedCloakData> frames = animatedCloaks.get(cloakName);
|
List<GifFrame> frames = animatedCloaks.get(cloakName);
|
||||||
if (frames == null || frames.isEmpty()) {
|
if (frames == null || frames.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.saturnclient.cosmetics.utils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GifData {
|
||||||
|
public final List<GifFrame> frames;
|
||||||
|
public final int totalDuration;
|
||||||
|
|
||||||
|
public GifData(List<GifFrame> frames) {
|
||||||
|
this.frames = frames;
|
||||||
|
|
||||||
|
int sum = 0;
|
||||||
|
for (GifFrame f : frames) {
|
||||||
|
sum += f.getDelayMs();
|
||||||
|
}
|
||||||
|
this.totalDuration = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -5,7 +5,7 @@ import org.saturnclient.common.ref.asset.IdentifierRef;
|
|||||||
/**
|
/**
|
||||||
* Represents a single frame of an animated cloak.
|
* Represents a single frame of an animated cloak.
|
||||||
*/
|
*/
|
||||||
public class AnimatedCloakData {
|
public class GifFrame {
|
||||||
|
|
||||||
private final IdentifierRef textureId;
|
private final IdentifierRef textureId;
|
||||||
private final int delayMs;
|
private final int delayMs;
|
||||||
@@ -16,7 +16,7 @@ public class AnimatedCloakData {
|
|||||||
* @param textureId The texture identifier for this frame
|
* @param textureId The texture identifier for this frame
|
||||||
* @param delayMs The delay in milliseconds before the next frame
|
* @param delayMs The delay in milliseconds before the next frame
|
||||||
*/
|
*/
|
||||||
public AnimatedCloakData(IdentifierRef textureId, int delayMs) {
|
public GifFrame(IdentifierRef textureId, int delayMs) {
|
||||||
this.textureId = textureId;
|
this.textureId = textureId;
|
||||||
this.delayMs = delayMs;
|
this.delayMs = delayMs;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package org.saturnclient.cosmetics.utils;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import org.saturnclient.common.provider.Providers;
|
||||||
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
|
|
||||||
|
public class GifUtils {
|
||||||
|
public static Map<String, GifData> loadedCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final Queue<IdentifierRef> QUEUE = new ConcurrentLinkedQueue<>();
|
||||||
|
private static volatile boolean RUNNING = false;
|
||||||
|
private static Thread WORKER;
|
||||||
|
|
||||||
|
public static IdentifierRef get(IdentifierRef gif) {
|
||||||
|
String g = gif.toString();
|
||||||
|
|
||||||
|
if (!loadedCache.containsKey(g)) {
|
||||||
|
loadedCache.put(g, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedCache.get(g) == null) {
|
||||||
|
return IdentifierRef.of(g.replace(".gif", ".png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
GifData data = loadedCache.get(g);
|
||||||
|
|
||||||
|
long t = now % data.totalDuration;
|
||||||
|
|
||||||
|
int accumulated = 0;
|
||||||
|
|
||||||
|
for (GifFrame frame : data.frames) {
|
||||||
|
accumulated += frame.getDelayMs();
|
||||||
|
if (t < accumulated) {
|
||||||
|
return frame.getTextureId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.frames.get(data.frames.size() - 1).getTextureId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GifData getData(IdentifierRef id) {
|
||||||
|
String fileName = id.toString();
|
||||||
|
|
||||||
|
try (InputStream inputStream = Providers.saturn.getClient().getResource(id)) {
|
||||||
|
if (inputStream == null) {
|
||||||
|
Providers.saturn.logError("Cloak resource not found: " + fileName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all bytes at once - more efficient than multiple reads
|
||||||
|
byte[] data = inputStream.readAllBytes();
|
||||||
|
GifDecoder.GifImage gif = GifDecoder.read(data);
|
||||||
|
|
||||||
|
int frameCount = gif.getFrameCount();
|
||||||
|
if (frameCount == 0) {
|
||||||
|
Providers.saturn.logError("No frames found in animated cloak: " + fileName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-allocate collections with known size
|
||||||
|
List<GifFrame> animatedFrames = new ArrayList<>(frameCount);
|
||||||
|
String baseFrameId = fileName.replace(".gif", "");
|
||||||
|
|
||||||
|
// Process frames in batch
|
||||||
|
for (int i = 0; i < frameCount; i++) {
|
||||||
|
BufferedImage frame = gif.getFrame(i);
|
||||||
|
int delay = Math.max(gif.getDelay(i) * 10, 50); // Minimum 50ms delay
|
||||||
|
|
||||||
|
String frameId = baseFrameId + "_frame_" + i;
|
||||||
|
IdentifierRef frameIdentifier = IdentifierRef.ofSaturn("cloaks_" + frameId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Providers.saturn.registerBufferedImageTexture(frameIdentifier, frame);
|
||||||
|
animatedFrames.add(new GifFrame(frameIdentifier, delay));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Providers.saturn.logError("Failed to register frame " + i + " for cloak: " + fileName);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!animatedFrames.isEmpty()) {
|
||||||
|
Providers.saturn.logInfo(
|
||||||
|
"Loaded " + animatedFrames.size() + " frames for animated cloak: " + fileName + " (cached)");
|
||||||
|
|
||||||
|
return new GifData(animatedFrames);
|
||||||
|
} else {
|
||||||
|
Providers.saturn.logError("No valid frames could be loaded for cloak: " + fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Providers.saturn.logError("Failed to load animated cloak from resources: " + fileName, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void startPlayerThread() {
|
||||||
|
if (RUNNING)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RUNNING = true;
|
||||||
|
|
||||||
|
WORKER = new Thread(() -> {
|
||||||
|
long lastWorkTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
IdentifierRef id = QUEUE.poll();
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
try {
|
||||||
|
GifData data = getData(id);
|
||||||
|
|
||||||
|
// Apply on main thread
|
||||||
|
Providers.saturn.getClient().executeOnThread(() -> {
|
||||||
|
loadedCache.put(id.toString(), data);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Providers.saturn.logError("Failed to load gif " + id, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastWorkTime = System.currentTimeMillis();
|
||||||
|
} else {
|
||||||
|
if (System.currentTimeMillis() - lastWorkTime > 5000) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RUNNING = false;
|
||||||
|
}, "SaturnPlayer-Worker");
|
||||||
|
|
||||||
|
WORKER.setDaemon(true);
|
||||||
|
WORKER.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user