diff --git a/src/main/java/lain/lib/SharedPool.java b/src/main/java/lain/lib/SharedPool.java new file mode 100644 index 0000000..0c078cb --- /dev/null +++ b/src/main/java/lain/lib/SharedPool.java @@ -0,0 +1,21 @@ +package lain.lib; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; + +public final class SharedPool +{ + + private static final ExecutorService thePool = Executors.newWorkStealingPool(ForkJoinPool.getCommonPoolParallelism() + 1); + + public static void execute(Runnable command) + { + thePool.execute(command); + } + + private SharedPool() + { + } + +} diff --git a/src/main/java/lain/mods/skins/api/SkinBundle.java b/src/main/java/lain/mods/skins/api/SkinBundle.java new file mode 100644 index 0000000..37e5b9d --- /dev/null +++ b/src/main/java/lain/mods/skins/api/SkinBundle.java @@ -0,0 +1,105 @@ +package lain.mods.skins.api; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import lain.mods.skins.api.interfaces.ISkin; + +/** + * A special ISkin object that will return first ready ISkin object in a collection.
+ * It supports swapping it's collection reference at runtime. + * + */ +public class SkinBundle implements ISkin +{ + + protected final AtomicReference> ref = new AtomicReference<>(Collections.emptyList()); + protected final Collection> listeners = new CopyOnWriteArrayList<>(); + protected final Collection> filters = new CopyOnWriteArrayList<>(); + + protected Optional find() + { + Collection skins; + if ((skins = ref.get()).isEmpty()) + return Optional.empty(); + return skins.stream().filter(ISkin::isDataReady).findFirst(); + } + + @Override + public ByteBuffer getData() + { + return find().orElse(SkinProviderAPI.DUMMY).getData(); + } + + @Override + public String getSkinType() + { + return find().orElse(SkinProviderAPI.DUMMY).getSkinType(); + } + + @Override + public boolean isDataReady() + { + return find().orElse(SkinProviderAPI.DUMMY).isDataReady(); + } + + @Override + public void onRemoval() + { + set(Collections.emptyList()); + } + + public SkinBundle set(Collection c) + { + Objects.requireNonNull(c); + if (!c.isEmpty()) + { + for (ISkin e : c) + { + listeners.forEach(e::setRemovalListener); + filters.forEach(e::setSkinFilter); + } + } + Collection skins; + if (!(skins = ref.getAndSet(c)).isEmpty()) + skins.forEach(ISkin::onRemoval); + return this; + } + + @Override + public boolean setRemovalListener(Consumer listener) + { + if (listener == null || listeners.contains(listener)) + return false; + if (listeners.add(listener)) + { + Collection skins; + if (!(skins = ref.get()).isEmpty()) + skins.forEach(e -> e.setRemovalListener(listener)); + return true; + } + return false; + } + + @Override + public boolean setSkinFilter(Function filter) + { + if (filter == null || filters.contains(filter)) + return false; + if (filters.add(filter)) + { + Collection skins; + if (!(skins = ref.get()).isEmpty()) + skins.forEach(e -> e.setSkinFilter(filter)); + return true; + } + return false; + } + +} diff --git a/src/main/java/lain/mods/skins/api/SkinProviderAPI.java b/src/main/java/lain/mods/skins/api/SkinProviderAPI.java index d0183e1..bdeee20 100644 --- a/src/main/java/lain/mods/skins/api/SkinProviderAPI.java +++ b/src/main/java/lain/mods/skins/api/SkinProviderAPI.java @@ -4,17 +4,23 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinPool.ManagedBlocker; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lain.lib.SharedPool; import lain.mods.skins.api.interfaces.IPlayerProfile; import lain.mods.skins.api.interfaces.ISkin; import lain.mods.skins.api.interfaces.ISkinProvider; @@ -23,6 +29,46 @@ public class SkinProviderAPI { + public static final ISkin DUMMY = new ISkin() + { + + @Override + public ByteBuffer getData() + { + return null; + } + + @Override + public String getSkinType() + { + return null; + } + + @Override + public boolean isDataReady() + { + return false; + } + + @Override + public void onRemoval() + { + } + + @Override + public boolean setRemovalListener(Consumer listener) + { + return false; + } + + @Override + public boolean setSkinFilter(Function filter) + { + return false; + } + + }; + /** * The service for skins. */ @@ -33,132 +79,110 @@ public class SkinProviderAPI public static final ISkinProviderService CAPE = create(); /** - * @return an empty ISkinProviderService with default implementation, a single ISkin object will be created during runtime with all available ISkin objects bundled in it, if a corresponding profile changes during the lifetime of an ISkin object, the ISkin object will be discarded and a new one will be created. + * @return an empty ISkinProviderService with default implementation.
+ * a SkinBundle will be created with all available ISkin objects for that IPlayerProfile.
+ * if the profile got updated during the lifetime of a SkinBundle, new ISkin objects will be gathered and a thread will be used to monitor those objects to wait their isDataReady for up to 10 seconds before updating the SkinBundle. */ public static ISkinProviderService create() { return new ISkinProviderService() { - private final ISkin DUMMY = new ISkin() + private final LoadingCache> reloading = CacheBuilder.newBuilder().weakKeys().build(new CacheLoader>() { @Override - public ByteBuffer getData() + public AtomicReference load(SkinBundle key) throws Exception { - return null; + return new AtomicReference<>(); } - @Override - public String getSkinType() - { - return null; - } - - @Override - public boolean isDataReady() - { - return false; - } - - @Override - public void onRemoval() - { - } - - @Override - public boolean setRemovalListener(Consumer listener) - { - return false; - } - - @Override - public boolean setSkinFilter(Function filter) - { - return false; - } - - }; - - private final LoadingCache cache = CacheBuilder.newBuilder().expireAfterAccess(15, TimeUnit.SECONDS).removalListener(new RemovalListener() + }); + private final LoadingCache cache = CacheBuilder.newBuilder().expireAfterAccess(15, TimeUnit.SECONDS).removalListener(new RemovalListener() { @Override - public void onRemoval(RemovalNotification notification) + public void onRemoval(RemovalNotification notification) { - ISkin skin = notification.getValue(); + SkinBundle skin = notification.getValue(); if (skin != null) skin.onRemoval(); } - }).build(new CacheLoader() + }).build(new CacheLoader() { @Override - public ISkin load(IPlayerProfile key) throws Exception + public SkinBundle load(IPlayerProfile key) throws Exception { key.setUpdateListener(profileChangeListener); - return new ISkin() - { - - private final Collection skins = providers.stream().map(provider -> { - return provider.getSkin(key); - }).filter(skin -> { - return skin != null; - }).collect(Collectors.toCollection(ArrayList::new)); - - private Optional find() - { - return skins.stream().filter(ISkin::isDataReady).findFirst(); - } - - @Override - public ByteBuffer getData() - { - return find().map(ISkin::getData).orElse(null); - } - - @Override - public String getSkinType() - { - return find().map(ISkin::getSkinType).orElse(null); - } - - @Override - public boolean isDataReady() - { - return find().map(ISkin::isDataReady).orElse(false); - } + return new SkinBundle().set(providers.stream().map(provider -> { + return provider.getSkin(key); + }).filter(skin -> { + return skin != null; + }).collect(Collectors.toCollection(ArrayList::new))); + } - @Override - public void onRemoval() - { - for (ISkin skin : skins) - skin.onRemoval(); - } + @Override + public ListenableFuture reload(IPlayerProfile key, SkinBundle oldValue) throws Exception + { + // Gather new ISkin objects. + Collection skins = providers.stream().map(provider -> { + return provider.getSkin(key); + }).filter(skin -> { + return skin != null; + }).collect(Collectors.toCollection(ArrayList::new)); + // Prepare for monitoring. + Object token; + reloading.getUnchecked(oldValue).set(token = new Object()); + long deadline = System.currentTimeMillis() + 10000; // 10 seconds + Supplier ready = () -> { + return System.currentTimeMillis() - deadline > 0L || reloading.getUnchecked(oldValue).get() != token || skins.stream().filter(ISkin::isDataReady).findAny().isPresent(); + }; + Runnable update = () -> { + if (reloading.getUnchecked(oldValue).compareAndSet(token, null)) + oldValue.set(skins); + }; - @Override - public boolean setRemovalListener(Consumer listener) - { - boolean any = false; - for (ISkin skin : skins) - if (skin.setRemovalListener(listener) && !any) - any = true; - return any; - } - - @Override - public boolean setSkinFilter(Function filter) + if (skins.isEmpty()) + { + update.run(); + } + else + { + ManagedBlocker blocker = new ManagedBlocker() { - boolean any = false; - for (ISkin skin : skins) - if (skin.setSkinFilter(filter) && !any) - any = true; - return any; - } - }; + @Override + public boolean block() throws InterruptedException + { + Thread.sleep(1000); // 1 second + return ready.get(); + } + + @Override + public boolean isReleasable() + { + return ready.get(); + } + + }; + SharedPool.execute(() -> { + try + { + ForkJoinPool.managedBlock(blocker); + } + catch (InterruptedException e) + { + } + finally + { + update.run(); + } + }); + } + return Futures.immediateFuture(oldValue); } }); diff --git a/src/main/java/lain/mods/skins/impl/MojangService.java b/src/main/java/lain/mods/skins/impl/MojangService.java index eb2f080..fd67426 100644 --- a/src/main/java/lain/mods/skins/impl/MojangService.java +++ b/src/main/java/lain/mods/skins/impl/MojangService.java @@ -49,7 +49,7 @@ public ListenableFuture> reload(GameProfile key, Optional< { if (oldValue.isPresent()) // good result, doesn't need refresh. return Futures.immediateFuture(oldValue); - return Shared.pool.submit(() -> { + return Shared.submitTask(() -> { return load(key); }); } @@ -121,7 +121,7 @@ public ListenableFuture> reload(String key, Optional { + return Shared.submitTask(() -> { return load(key); }); } @@ -139,7 +139,7 @@ public static ListenableFuture fillProfile(GameProfile profile) Optional cachedResult; if ((cachedResult = filledProfiles.getIfPresent(profile)) != null) return Futures.immediateFuture(cachedResult.orElse(profile)); - return Shared.pool.submit(() -> { + return Shared.submitTask(() -> { return filledProfiles.getUnchecked(profile).orElse(profile); }); } @@ -155,7 +155,7 @@ public static ListenableFuture getProfile(String username) Optional cachedResult; if ((cachedResult = resolvedProfiles.getIfPresent(username)) != null) return Futures.immediateFuture(cachedResult.orElse(Shared.DUMMY)); - return Shared.pool.submit(() -> { + return Shared.submitTask(() -> { return resolvedProfiles.getUnchecked(username).orElse(Shared.DUMMY); }); } diff --git a/src/main/java/lain/mods/skins/impl/PlayerProfile.java b/src/main/java/lain/mods/skins/impl/PlayerProfile.java index 1856958..24fe375 100644 --- a/src/main/java/lain/mods/skins/impl/PlayerProfile.java +++ b/src/main/java/lain/mods/skins/impl/PlayerProfile.java @@ -158,7 +158,7 @@ public ListenableFuture reload(GameProfile key, PlayerProfile old { if (oldValue == DUMMY) // value for bad profile return Futures.immediateFuture(DUMMY); - return Shared.pool.submit(() -> { + return Shared.submitTask(() -> { PlayerProfile newValue = load(key); if (oldValue.getOriginal() != newValue.getOriginal()) // updated oldValue.set(newValue.getOriginal()); // update old profile diff --git a/src/main/java/lain/mods/skins/impl/Shared.java b/src/main/java/lain/mods/skins/impl/Shared.java index b7d6d48..c2ce574 100644 --- a/src/main/java/lain/mods/skins/impl/Shared.java +++ b/src/main/java/lain/mods/skins/impl/Shared.java @@ -9,15 +9,15 @@ import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; import java.util.concurrent.ForkJoinPool; import java.util.function.Consumer; import java.util.function.Supplier; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListenableFutureTask; import com.mojang.authlib.GameProfile; +import lain.lib.SharedPool; public class Shared { @@ -27,7 +27,6 @@ private static interface SupplierBlocker extends Supplier, ForkJoinPool.Ma } public static final GameProfile DUMMY = new GameProfile(UUID.fromString("ae9460f5-bf72-468e-89b6-4eead59001ad"), ""); - public static final ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newWorkStealingPool()); public static final Map store = new ConcurrentHashMap<>(); private static final Cache offlines = CacheBuilder.newBuilder().weakKeys().build(); @@ -35,38 +34,38 @@ private static interface SupplierBlocker extends Supplier, ForkJoinPool.Ma /** * Call a possibly blocking task in a ManagedBlocker to allow current Thread adjust if it is a ForkJoinWorkerThread. * - * @param task the task to call. + * @param callable the task to call. * @param defaultValue a default value to return if the task failed during the call. - * @param receiver a consumer that will receive a Throwable if the task failed during the call, null is acceptable. + * @param consumer a consumer that will receive a Throwable if the task failed during the call, null is acceptable. * @return the result of the task or defaultValue if it failed during the call. */ - public static V blockyCall(Callable task, V defaultValue, Consumer receiver) + public static T blockyCall(Callable callable, T defaultValue, Consumer consumer) { - if (task == null) + if (callable == null) return defaultValue; - return new SupplierBlocker() + return new SupplierBlocker() { - V result; + T result; @Override public boolean block() throws InterruptedException { try { - result = task.call(); + result = callable.call(); } catch (Throwable t) { - if (receiver != null) - receiver.accept(t); + if (consumer != null) + consumer.accept(t); result = defaultValue; } return true; } @Override - public V get() + public T get() { try { @@ -74,8 +73,8 @@ public V get() } catch (InterruptedException e) { - if (receiver != null) - receiver.accept(e); + if (consumer != null) + consumer.accept(e); } return result; } @@ -94,10 +93,10 @@ public boolean isReleasable() * * @param file the file to read. * @param defaultContents a default value to return if failed during reading the file. - * @param receiver a consumer that will receive a Throwable if failed during reading the file, null is acceptable. + * @param consumer a consumer that will receive a Throwable if failed during reading the file, null is acceptable. * @return the contents of the file or defaultContents if failed during reading the file. */ - public static byte[] blockyReadFile(File file, byte[] defaultContents, Consumer receiver) + public static byte[] blockyReadFile(File file, byte[] defaultContents, Consumer consumer) { if (file == null) return defaultContents; @@ -107,7 +106,7 @@ public static byte[] blockyReadFile(File file, byte[] defaultContents, Consumer< fis.getChannel().transferTo(0, Long.MAX_VALUE, Channels.newChannel(baos)); return baos.toByteArray(); } - }, defaultContents, receiver); + }, defaultContents, consumer); } public static boolean isBlank(CharSequence cs) @@ -150,4 +149,11 @@ public static boolean sleep(long millis) } } + public static ListenableFuture submitTask(Callable callable) + { + ListenableFutureTask future; + SharedPool.execute(future = ListenableFutureTask.create(callable)); + return future; + } + } diff --git a/src/main/java/lain/mods/skins/providers/CrafatarCachedCapeProvider.java b/src/main/java/lain/mods/skins/providers/CrafatarCachedCapeProvider.java index 832fbec..10cf448 100644 --- a/src/main/java/lain/mods/skins/providers/CrafatarCachedCapeProvider.java +++ b/src/main/java/lain/mods/skins/providers/CrafatarCachedCapeProvider.java @@ -5,6 +5,7 @@ import java.nio.file.Path; import java.util.UUID; import java.util.function.Function; +import lain.lib.SharedPool; import lain.mods.skins.api.interfaces.IPlayerProfile; import lain.mods.skins.api.interfaces.ISkin; import lain.mods.skins.api.interfaces.ISkinProvider; @@ -40,7 +41,7 @@ public ISkin getSkin(IPlayerProfile profile) SkinData skin = new SkinData(); if (_filter != null) skin.setSkinFilter(_filter); - Shared.pool.execute(() -> { + SharedPool.execute(() -> { byte[] data = null; UUID uuid = profile.getPlayerID(); if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) diff --git a/src/main/java/lain/mods/skins/providers/CrafatarCachedSkinProvider.java b/src/main/java/lain/mods/skins/providers/CrafatarCachedSkinProvider.java index ea18c11..79c5a05 100644 --- a/src/main/java/lain/mods/skins/providers/CrafatarCachedSkinProvider.java +++ b/src/main/java/lain/mods/skins/providers/CrafatarCachedSkinProvider.java @@ -5,6 +5,7 @@ import java.nio.file.Path; import java.util.UUID; import java.util.function.Function; +import lain.lib.SharedPool; import lain.mods.skins.api.interfaces.IPlayerProfile; import lain.mods.skins.api.interfaces.ISkin; import lain.mods.skins.api.interfaces.ISkinProvider; @@ -40,7 +41,7 @@ public ISkin getSkin(IPlayerProfile profile) SkinData skin = new SkinData(); if (_filter != null) skin.setSkinFilter(_filter); - Shared.pool.execute(() -> { + SharedPool.execute(() -> { byte[] data = null; UUID uuid = profile.getPlayerID(); if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) diff --git a/src/main/java/lain/mods/skins/providers/CustomServerCachedCapeProvider.java b/src/main/java/lain/mods/skins/providers/CustomServerCachedCapeProvider.java index 2443786..f237391 100644 --- a/src/main/java/lain/mods/skins/providers/CustomServerCachedCapeProvider.java +++ b/src/main/java/lain/mods/skins/providers/CustomServerCachedCapeProvider.java @@ -5,6 +5,7 @@ import java.nio.file.Path; import java.util.UUID; import java.util.function.Function; +import lain.lib.SharedPool; import lain.mods.skins.api.interfaces.IPlayerProfile; import lain.mods.skins.api.interfaces.ISkin; import lain.mods.skins.api.interfaces.ISkinProvider; @@ -42,7 +43,7 @@ public ISkin getSkin(IPlayerProfile profile) SkinData skin = new SkinData(); if (_filter != null) skin.setSkinFilter(_filter); - Shared.pool.execute(() -> { + SharedPool.execute(() -> { byte[] data = null; UUID uuid = profile.getPlayerID(); String name = profile.getPlayerName(); diff --git a/src/main/java/lain/mods/skins/providers/CustomServerCachedSkinProvider.java b/src/main/java/lain/mods/skins/providers/CustomServerCachedSkinProvider.java index 8b3cb5f..c428a58 100644 --- a/src/main/java/lain/mods/skins/providers/CustomServerCachedSkinProvider.java +++ b/src/main/java/lain/mods/skins/providers/CustomServerCachedSkinProvider.java @@ -5,6 +5,7 @@ import java.nio.file.Path; import java.util.UUID; import java.util.function.Function; +import lain.lib.SharedPool; import lain.mods.skins.api.interfaces.IPlayerProfile; import lain.mods.skins.api.interfaces.ISkin; import lain.mods.skins.api.interfaces.ISkinProvider; @@ -42,7 +43,7 @@ public ISkin getSkin(IPlayerProfile profile) SkinData skin = new SkinData(); if (_filter != null) skin.setSkinFilter(_filter); - Shared.pool.execute(() -> { + SharedPool.execute(() -> { byte[] data = null; UUID uuid = profile.getPlayerID(); String name = profile.getPlayerName(); diff --git a/src/main/java/lain/mods/skins/providers/MojangCachedCapeProvider.java b/src/main/java/lain/mods/skins/providers/MojangCachedCapeProvider.java index d6ef551..bec5066 100644 --- a/src/main/java/lain/mods/skins/providers/MojangCachedCapeProvider.java +++ b/src/main/java/lain/mods/skins/providers/MojangCachedCapeProvider.java @@ -8,6 +8,7 @@ import java.util.function.Function; import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import lain.lib.SharedPool; import lain.mods.skins.api.interfaces.IPlayerProfile; import lain.mods.skins.api.interfaces.ISkin; import lain.mods.skins.api.interfaces.ISkinProvider; @@ -43,7 +44,7 @@ public ISkin getSkin(IPlayerProfile profile) SkinData skin = new SkinData(); if (_filter != null) skin.setSkinFilter(_filter); - Shared.pool.execute(() -> { + SharedPool.execute(() -> { byte[] data = null; UUID uuid = profile.getPlayerID(); if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) diff --git a/src/main/java/lain/mods/skins/providers/MojangCachedSkinProvider.java b/src/main/java/lain/mods/skins/providers/MojangCachedSkinProvider.java index f9f19cd..829a89a 100644 --- a/src/main/java/lain/mods/skins/providers/MojangCachedSkinProvider.java +++ b/src/main/java/lain/mods/skins/providers/MojangCachedSkinProvider.java @@ -8,6 +8,7 @@ import java.util.function.Function; import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import lain.lib.SharedPool; import lain.mods.skins.api.interfaces.IPlayerProfile; import lain.mods.skins.api.interfaces.ISkin; import lain.mods.skins.api.interfaces.ISkinProvider; @@ -43,7 +44,7 @@ public ISkin getSkin(IPlayerProfile profile) SkinData skin = new SkinData(); if (_filter != null) skin.setSkinFilter(_filter); - Shared.pool.execute(() -> { + SharedPool.execute(() -> { byte[] data = null; UUID uuid = profile.getPlayerID(); if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) diff --git a/src/main/java/lain/mods/skins/providers/UserManagedCapeProvider.java b/src/main/java/lain/mods/skins/providers/UserManagedCapeProvider.java index 0043974..9fad3d3 100644 --- a/src/main/java/lain/mods/skins/providers/UserManagedCapeProvider.java +++ b/src/main/java/lain/mods/skins/providers/UserManagedCapeProvider.java @@ -4,6 +4,7 @@ import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.function.Function; +import lain.lib.SharedPool; import lain.mods.skins.api.interfaces.IPlayerProfile; import lain.mods.skins.api.interfaces.ISkin; import lain.mods.skins.api.interfaces.ISkinProvider; @@ -31,7 +32,7 @@ public ISkin getSkin(IPlayerProfile profile) SkinData skin = new SkinData(); if (_filter != null) skin.setSkinFilter(_filter); - Shared.pool.execute(() -> { + SharedPool.execute(() -> { byte[] data = null; if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) data = readFile(_dirU, "%s.png", profile.getPlayerID().toString().replaceAll("-", "")); diff --git a/src/main/java/lain/mods/skins/providers/UserManagedSkinProvider.java b/src/main/java/lain/mods/skins/providers/UserManagedSkinProvider.java index c6bcc9d..8efcb55 100644 --- a/src/main/java/lain/mods/skins/providers/UserManagedSkinProvider.java +++ b/src/main/java/lain/mods/skins/providers/UserManagedSkinProvider.java @@ -4,6 +4,7 @@ import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.function.Function; +import lain.lib.SharedPool; import lain.mods.skins.api.interfaces.IPlayerProfile; import lain.mods.skins.api.interfaces.ISkin; import lain.mods.skins.api.interfaces.ISkinProvider; @@ -31,7 +32,7 @@ public ISkin getSkin(IPlayerProfile profile) SkinData skin = new SkinData(); if (_filter != null) skin.setSkinFilter(_filter); - Shared.pool.execute(() -> { + SharedPool.execute(() -> { byte[] data = null; if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) data = readFile(_dirU, "%s.png", profile.getPlayerID().toString().replaceAll("-", ""));