From 3d5706bf305092efdf1f4c031ec8b30667daf3bd Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Fri, 25 Oct 2024 12:24:15 -0700 Subject: [PATCH] Rebase fixups --- patches/server/1039-fixup-MC-Utils.patch | 1357 ---- patches/server/ConcurrentUtil.patch | 4808 ++++++------- .../Improve-and-expand-AsyncCatcher.patch | 4 +- patches/server/MC-Utils.patch | 998 ++- patches/server/More-Teleport-API.patch | 2 +- ...ptimize-BlockPosition-helper-methods.patch | 12 +- patches/server/Paper-config-files.patch | 56 +- patches/server/fixup-ConcurrentUtil.patch | 6177 ----------------- patches/server/fixup-More-Teleport-API.patch | 19 - ...ptimize-BlockPosition-helper-methods.patch | 64 - patches/server/fixup-Paper-config-files.patch | 87 - 11 files changed, 3299 insertions(+), 10285 deletions(-) delete mode 100644 patches/server/1039-fixup-MC-Utils.patch delete mode 100644 patches/server/fixup-ConcurrentUtil.patch delete mode 100644 patches/server/fixup-More-Teleport-API.patch delete mode 100644 patches/server/fixup-Optimize-BlockPosition-helper-methods.patch delete mode 100644 patches/server/fixup-Paper-config-files.patch diff --git a/patches/server/1039-fixup-MC-Utils.patch b/patches/server/1039-fixup-MC-Utils.patch deleted file mode 100644 index 90cd0e9d64..0000000000 --- a/patches/server/1039-fixup-MC-Utils.patch +++ /dev/null @@ -1,1357 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 21 Oct 2024 12:21:54 -0700 -Subject: [PATCH] fixup! MC Utils - - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.moonrise.common; -+ -+import com.mojang.datafixers.DSL; -+import com.mojang.datafixers.DataFixer; -+import net.minecraft.core.BlockPos; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.GenerationChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import net.minecraft.world.level.chunk.storage.SerializableChunkData; -+import net.minecraft.world.level.entity.EntityTypeTest; -+import net.minecraft.world.phys.AABB; -+import java.util.List; -+import java.util.ServiceLoader; -+import java.util.function.Predicate; -+ -+public interface PlatformHooks { -+ public static PlatformHooks get() { -+ return Holder.INSTANCE; -+ } -+ -+ public String getBrand(); -+ -+ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos); -+ -+ public Predicate maybeHasLightEmission(); -+ -+ public boolean hasCurrentlyLoadingChunk(); -+ -+ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder); -+ -+ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk); -+ -+ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original); -+ -+ public boolean allowAsyncTicketUpdates(); -+ -+ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel); -+ -+ public void chunkUnloadFromWorld(final LevelChunk chunk); -+ -+ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data); -+ -+ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player); -+ -+ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player); -+ -+ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, -+ final List into); -+ -+ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, -+ final AABB boundingBox, final Predicate predicate, -+ final List into, final int maxCount); -+ -+ public void entityMove(final Entity entity, final long oldSection, final long newSection); -+ -+ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event); -+ -+ public boolean configFixMC224294(); -+ -+ public boolean configAutoConfigSendDistance(); -+ -+ public double configPlayerMaxLoadRate(); -+ -+ public double configPlayerMaxGenRate(); -+ -+ public double configPlayerMaxSendRate(); -+ -+ public int configPlayerMaxConcurrentLoads(); -+ -+ public int configPlayerMaxConcurrentGens(); -+ -+ public long configAutoSaveInterval(final ServerLevel world); -+ -+ public int configMaxAutoSavePerTick(final ServerLevel world); -+ -+ public boolean configFixMC159283(); -+ -+ // support for CB chunk mustNotSave -+ public boolean forceNoSave(final ChunkAccess chunk); -+ -+ public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, -+ final int fromVersion, final int toVersion); -+ -+ public boolean hasMainChunkLoadHook(); -+ -+ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData); -+ -+ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities); -+ -+ public void unloadEntity(final Entity entity); -+ -+ public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk); -+ -+ public int modifyEntityTrackingRange(final Entity entity, final int currentRange); -+ -+ public static final class Holder { -+ private Holder() { -+ } -+ -+ private static final PlatformHooks INSTANCE; -+ -+ static { -+ INSTANCE = ServiceLoader.load(PlatformHooks.class, PlatformHooks.class.getClassLoader()).findFirst() -+ .orElseThrow(() -> new RuntimeException("Failed to locate PlatformHooks")); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java -@@ -0,0 +0,0 @@ import java.util.NoSuchElementException; - */ - public final class EntityList implements Iterable { - -- protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); -+ private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); - { - this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); - } - -- protected static final Entity[] EMPTY_LIST = new Entity[0]; -+ private static final Entity[] EMPTY_LIST = new Entity[0]; - -- protected Entity[] entities = EMPTY_LIST; -- protected int count; -+ private Entity[] entities = EMPTY_LIST; -+ private int count; - - public int size() { - return this.count; -@@ -0,0 +0,0 @@ public final class EntityList implements Iterable { - - @Override - public Iterator iterator() { -- return new Iterator() { -- -- Entity lastRet; -- int current; -+ return new Iterator<>() { -+ private Entity lastRet; -+ private int current; - - @Override - public boolean hasNext() { -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java -deleted file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java -+++ /dev/null -@@ -0,0 +0,0 @@ --package ca.spottedleaf.moonrise.common.list; -- --import it.unimi.dsi.fastutil.longs.LongIterator; --import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; --import java.util.Arrays; --import net.minecraft.world.level.block.Block; --import net.minecraft.world.level.block.state.BlockState; --import net.minecraft.world.level.chunk.GlobalPalette; -- --public final class IBlockDataList { -- -- private static final GlobalPalette GLOBAL_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY); -- -- // map of location -> (index | (location << 16) | (palette id << 32)) -- private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f); -- { -- this.map.defaultReturnValue(Long.MAX_VALUE); -- } -- -- private static final long[] EMPTY_LIST = new long[0]; -- -- private long[] byIndex = EMPTY_LIST; -- private int size; -- -- public static int getLocationKey(final int x, final int y, final int z) { -- return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4)); -- } -- -- public static BlockState getBlockDataFromRaw(final long raw) { -- return GLOBAL_PALETTE.valueFor((int)(raw >>> 32)); -- } -- -- public static int getIndexFromRaw(final long raw) { -- return (int)(raw & 0xFFFF); -- } -- -- public static int getLocationFromRaw(final long raw) { -- return (int)((raw >>> 16) & 0xFFFF); -- } -- -- public static long getRawFromValues(final int index, final int location, final BlockState data) { -- return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.idFor(data)) << 32); -- } -- -- public static long setIndexRawValues(final long value, final int index) { -- return value & ~(0xFFFF) | (index); -- } -- -- public long add(final int x, final int y, final int z, final BlockState data) { -- return this.add(getLocationKey(x, y, z), data); -- } -- -- public long add(final int location, final BlockState data) { -- final long curr = this.map.get((short)location); -- -- if (curr == Long.MAX_VALUE) { -- final int index = this.size++; -- final long raw = getRawFromValues(index, location, data); -- this.map.put((short)location, raw); -- -- if (index >= this.byIndex.length) { -- this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L)); -- } -- -- this.byIndex[index] = raw; -- return raw; -- } else { -- final int index = getIndexFromRaw(curr); -- final long raw = this.byIndex[index] = getRawFromValues(index, location, data); -- -- this.map.put((short)location, raw); -- -- return raw; -- } -- } -- -- public long remove(final int x, final int y, final int z) { -- return this.remove(getLocationKey(x, y, z)); -- } -- -- public long remove(final int location) { -- final long ret = this.map.remove((short)location); -- final int index = getIndexFromRaw(ret); -- if (ret == Long.MAX_VALUE) { -- return ret; -- } -- -- // move the entry at the end to this index -- final int endIndex = --this.size; -- final long end = this.byIndex[endIndex]; -- if (index != endIndex) { -- // not empty after this call -- this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index)); -- } -- this.byIndex[index] = end; -- this.byIndex[endIndex] = 0L; -- -- return ret; -- } -- -- public int size() { -- return this.size; -- } -- -- public long getRaw(final int index) { -- return this.byIndex[index]; -- } -- -- public int getLocation(final int index) { -- return getLocationFromRaw(this.getRaw(index)); -- } -- -- public BlockState getData(final int index) { -- return getBlockDataFromRaw(this.getRaw(index)); -- } -- -- public void clear() { -- this.size = 0; -- this.map.clear(); -- } -- -- public LongIterator getRawIterator() { -- return this.map.values().iterator(); -- } --} -\ No newline at end of file -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.moonrise.common.list; -+ -+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -+import java.util.Arrays; -+ -+public final class IntList { -+ -+ private final Int2IntOpenHashMap map = new Int2IntOpenHashMap(); -+ { -+ this.map.defaultReturnValue(Integer.MIN_VALUE); -+ } -+ -+ private static final int[] EMPTY_LIST = new int[0]; -+ -+ private int[] byIndex = EMPTY_LIST; -+ private int count; -+ -+ public int size() { -+ return this.count; -+ } -+ -+ public void setMinCapacity(final int len) { -+ final int[] byIndex = this.byIndex; -+ if (byIndex.length < len) { -+ this.byIndex = Arrays.copyOf(byIndex, len); -+ } -+ } -+ -+ public int getRaw(final int index) { -+ return this.byIndex[index]; -+ } -+ -+ public boolean add(final int value) { -+ final int count = this.count; -+ final int currIndex = this.map.putIfAbsent(value, count); -+ -+ if (currIndex != Integer.MIN_VALUE) { -+ return false; // already in this list -+ } -+ -+ int[] list = this.byIndex; -+ -+ if (list.length == count) { -+ // resize required -+ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative -+ } -+ -+ list[count] = value; -+ this.count = count + 1; -+ -+ return true; -+ } -+ -+ public boolean remove(final int value) { -+ final int index = this.map.remove(value); -+ if (index == Integer.MIN_VALUE) { -+ return false; -+ } -+ -+ // move the entry at the end to this index -+ final int endIndex = --this.count; -+ final int end = this.byIndex[endIndex]; -+ if (index != endIndex) { -+ // not empty after this call -+ this.map.put(end, index); -+ } -+ this.byIndex[index] = end; -+ this.byIndex[endIndex] = 0; -+ -+ return true; -+ } -+ -+ public void clear() { -+ this.count = 0; -+ this.map.clear(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.moonrise.common.list; -+ -+import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; -+import java.util.Arrays; -+ -+public final class ShortList { -+ -+ private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(); -+ { -+ this.map.defaultReturnValue(Short.MIN_VALUE); -+ } -+ -+ private static final short[] EMPTY_LIST = new short[0]; -+ -+ private short[] byIndex = EMPTY_LIST; -+ private short count; -+ -+ public int size() { -+ return (int)this.count; -+ } -+ -+ public short getRaw(final int index) { -+ return this.byIndex[index]; -+ } -+ -+ public void setMinCapacity(final int len) { -+ final short[] byIndex = this.byIndex; -+ if (byIndex.length < len) { -+ this.byIndex = Arrays.copyOf(byIndex, len); -+ } -+ } -+ -+ public boolean add(final short value) { -+ final int count = (int)this.count; -+ final short currIndex = this.map.putIfAbsent(value, (short)count); -+ -+ if (currIndex != Short.MIN_VALUE) { -+ return false; // already in this list -+ } -+ -+ short[] list = this.byIndex; -+ -+ if (list.length == count) { -+ // resize required -+ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative -+ } -+ -+ list[count] = value; -+ this.count = (short)(count + 1); -+ -+ return true; -+ } -+ -+ public boolean remove(final short value) { -+ final short index = this.map.remove(value); -+ if (index == Short.MIN_VALUE) { -+ return false; -+ } -+ -+ // move the entry at the end to this index -+ final short endIndex = --this.count; -+ final short end = this.byIndex[endIndex]; -+ if (index != endIndex) { -+ // not empty after this call -+ this.map.put(end, index); -+ } -+ this.byIndex[(int)index] = end; -+ this.byIndex[(int)endIndex] = (short)0; -+ -+ return true; -+ } -+ -+ public void clear() { -+ this.count = (short)0; -+ this.map.clear(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.moonrise.common.misc; -+ -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import java.lang.invoke.VarHandle; -+ -+public final class LazyRunnable implements Runnable { -+ -+ private volatile Runnable toRun; -+ private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(LazyRunnable.class, "toRun", Runnable.class); -+ -+ public void setRunnable(final Runnable run) { -+ final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run); -+ if (prev != null) { -+ throw new IllegalStateException("Runnable already set"); -+ } -+ } -+ -+ @Override -+ public void run() { -+ ((Runnable)TO_RUN_HANDLE.getVolatile(this)).run(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -@@ -0,0 +0,0 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; - import ca.spottedleaf.moonrise.common.util.MoonriseConstants; - import ca.spottedleaf.moonrise.common.util.ChunkSystem; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; - import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants; -+import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel; - import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; - import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; - import net.minecraft.core.BlockPos; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; - import net.minecraft.world.level.ChunkPos; -+import java.util.ArrayList; - - public final class NearbyPlayers { - -@@ -0,0 +0,0 @@ public final class NearbyPlayers { - GENERAL_REALLY_SMALL, - TICK_VIEW_DISTANCE, - VIEW_DISTANCE, -- SPAWN_RANGE, // Moonrise - chunk tick iteration -+ // Moonrise start - chunk tick iteration -+ SPAWN_RANGE { -+ @Override -+ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { -+ ((ChunkTickServerLevel)world).moonrise$addPlayerTickingRequest(chunkX, chunkZ); -+ } -+ -+ @Override -+ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { -+ ((ChunkTickServerLevel)world).moonrise$removePlayerTickingRequest(chunkX, chunkZ); -+ } -+ }; -+ // Moonrise end - chunk tick iteration -+ -+ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { -+ -+ } -+ -+ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { -+ -+ } - } - - private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values(); -@@ -0,0 +0,0 @@ public final class NearbyPlayers { - private final ServerLevel world; - private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); - private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); -+ private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; -+ { -+ for (int i = 0; i < this.directByChunk.length; ++i) { -+ this.directByChunk[i] = new Long2ReferenceOpenHashMap<>(); -+ } -+ } - - public NearbyPlayers(final ServerLevel world) { - this.world = world; -@@ -0,0 +0,0 @@ public final class NearbyPlayers { - } - } - -+ public void clear() { -+ if (this.players.isEmpty()) { -+ return; -+ } -+ -+ for (final ServerPlayer player : new ArrayList<>(this.players.keySet())) { -+ this.removePlayer(player); -+ } -+ } -+ - public void tickPlayer(final ServerPlayer player) { - final TrackedPlayer[] players = this.players.get(player); - if (players == null) { -@@ -0,0 +0,0 @@ public final class NearbyPlayers { - return this.byChunk.get(CoordinateUtils.getChunkKey(pos)); - } - -- public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) { -- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos)); -+ public TrackedChunk getChunk(final int chunkX, final int chunkZ) { -+ return this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } - -- return chunk == null ? null : chunk.players[type.ordinal()]; -+ public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) { -+ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos)); - } - - public ReferenceList getPlayers(final ChunkPos pos, final NearbyMapType type) { -- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos)); -- -- return chunk == null ? null : chunk.players[type.ordinal()]; -+ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos)); - } - - public ReferenceList getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) { -- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- return chunk == null ? null : chunk.players[type.ordinal()]; -+ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); - } - - public ReferenceList getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) { -- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4)); -- -- return chunk == null ? null : chunk.players[type.ordinal()]; -+ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4)); - } - - public static final class TrackedChunk { - - private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0]; - -+ private final long chunkKey; -+ private final NearbyPlayers nearbyPlayers; - private final ReferenceList[] players = new ReferenceList[TOTAL_MAP_TYPES]; - private int nonEmptyLists; - private long updateCount; - -+ public TrackedChunk(final long chunkKey, final NearbyPlayers nearbyPlayers) { -+ this.chunkKey = chunkKey; -+ this.nearbyPlayers = nearbyPlayers; -+ } -+ - public boolean isEmpty() { - return this.nonEmptyLists == 0; - } -@@ -0,0 +0,0 @@ public final class NearbyPlayers { - final ReferenceList list = this.players[idx]; - if (list == null) { - ++this.nonEmptyLists; -- (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player); -+ final ReferenceList players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)); -+ this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players); -+ players.add(player); - return; - } - -@@ -0,0 +0,0 @@ public final class NearbyPlayers { - - if (list.size() == 0) { - this.players[idx] = null; -+ this.nearbyPlayers.directByChunk[idx].remove(this.chunkKey); - --this.nonEmptyLists; - } - } -@@ -0,0 +0,0 @@ public final class NearbyPlayers { - protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) { - final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); - -- NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> { -- return new TrackedChunk(); -- }).addPlayer(parameter, this.type); -+ final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey); -+ final NearbyMapType type = this.type; -+ if (chunk != null) { -+ chunk.addPlayer(parameter, type); -+ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); -+ } else { -+ final TrackedChunk created = new TrackedChunk(chunkKey, NearbyPlayers.this); -+ NearbyPlayers.this.byChunk.put(chunkKey, created); -+ created.addPlayer(parameter, type); -+ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); -+ -+ ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created; -+ } - } - - @Override -@@ -0,0 +0,0 @@ public final class NearbyPlayers { - throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey)); - } - -- chunk.removePlayer(parameter, this.type); -+ final NearbyMapType type = this.type; -+ chunk.removePlayer(parameter, type); -+ type.removeFrom(parameter, NearbyPlayers.this.world, chunkX, chunkZ); - - if (chunk.isEmpty()) { - NearbyPlayers.this.byChunk.remove(chunkKey); -+ final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey); -+ if (chunkData != null) { -+ chunkData.nearbyPlayers = null; -+ } - } - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java -@@ -0,0 +0,0 @@ package ca.spottedleaf.moonrise.common.misc; - - import ca.spottedleaf.concurrentutil.util.IntPairUtil; - import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongSet; - import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; - import it.unimi.dsi.fastutil.objects.ReferenceSet; - -@@ -0,0 +0,0 @@ public final class PositionCountingAreaMap { - return this.counters.keySet(); - } - -+ public LongSet getPositions() { -+ return this.positions.keySet(); -+ } -+ - public int getTotalPositions() { - return this.positions.size(); - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -@@ -0,0 +0,0 @@ - package ca.spottedleaf.moonrise.common.util; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import com.mojang.logging.LogUtils; - import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.FullChunkStatus; -@@ -0,0 +0,0 @@ public final class ChunkSystem { - } - - public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) { -- scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); -+ scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL); - } - -- public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) { -+ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) { - level.chunkSource.mainThreadProcessor.execute(run); - } - - public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen, -- final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, -+ final ChunkStatus toStatus, final boolean addTicket, final Priority priority, - final Consumer onComplete) { - if (gen) { - scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -@@ -0,0 +0,0 @@ public final class ChunkSystem { - - private static long chunkLoadCounter = 0L; - public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, -- final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -+ final boolean addTicket, final Priority priority, final Consumer onComplete) { - if (!org.bukkit.Bukkit.isPrimaryThread()) { - scheduleChunkTask(level, chunkX, chunkZ, () -> { - scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -@@ -0,0 +0,0 @@ public final class ChunkSystem { - } - loadCallback.accept(result.orElse(null)); - }, (final Runnable r) -> { -- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); -+ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); - }); - } - - public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ, - final FullChunkStatus toStatus, final boolean addTicket, -- final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -+ final Priority priority, final Consumer onComplete) { - // This method goes unused until the chunk system rewrite - if (toStatus == FullChunkStatus.INACCESSIBLE) { - throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); -@@ -0,0 +0,0 @@ public final class ChunkSystem { - } - loadCallback.accept(result.orElse(null)); - }, (final Runnable r) -> { -- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); -+ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); - }); - } - -@@ -0,0 +0,0 @@ public final class ChunkSystem { - return getUpdatingChunkHolderCount(level) != 0; - } - -- public static boolean screenEntity(final ServerLevel level, final Entity entity) { -+ public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) { -+ if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) { -+ return false; -+ } - return true; - } - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java -@@ -0,0 +0,0 @@ package ca.spottedleaf.moonrise.common.util; - public final class MixinWorkarounds { - - // mixins tries to find the owner of the clone() method, which doesn't exist and NPEs -+ // https://github.com/FabricMC/Mixin/pull/147 - public static long[] clone(final long[] values) { - return values.clone(); - } - -+ public static byte[] clone(final byte[] values) { -+ return values.clone(); -+ } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -@@ -0,0 +0,0 @@ - package ca.spottedleaf.moonrise.common.util; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; -+import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; --import java.io.File; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.Consumer; - - public final class MoonriseCommon { - - private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseCommon.class); - -- // Paper start -- public static PrioritisedThreadPool WORKER_POOL; -- public static int WORKER_THREADS; -- public static void init(io.papermc.paper.configuration.GlobalConfiguration.ChunkSystem chunkSystem) { -- // Paper end -+ public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool( -+ new Consumer<>() { -+ private final AtomicInteger idGenerator = new AtomicInteger(); -+ -+ @Override -+ public void accept(Thread thread) { -+ thread.setDaemon(true); -+ thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement()); -+ thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { -+ @Override -+ public void uncaughtException(final Thread thread, final Throwable throwable) { -+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); -+ } -+ }); -+ } -+ } -+ ); -+ public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms -+ public static final int CLIENT_DIVISION = 0; -+ public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0); -+ public static final int SERVER_DIVISION = 1; -+ public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); -+ public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); -+ public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); -+ -+ public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { - int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; - if (defaultWorkerThreads <= 4) { - defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; - } else { - defaultWorkerThreads = defaultWorkerThreads / 2; - } -- defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); // Paper -+ defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); - -- int workerThreads = chunkSystem.workerThreads; // Paper -+ int workerThreads = configWorkerThreads; - - if (workerThreads <= 0) { - workerThreads = defaultWorkerThreads; - } - -- WORKER_POOL = new PrioritisedThreadPool( -- "Paper Worker Pool", workerThreads, // Paper -- (final Thread thread, final Integer id) -> { -- thread.setName("Paper Common Worker #" + id.intValue()); // Paper -+ final int ioThreads = Math.max(1, configIoThreads); -+ -+ WORKER_POOL.adjustThreadCount(workerThreads); -+ IO_POOL.adjustThreadCount(ioThreads); -+ -+ LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); -+ } -+ -+ public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool( -+ new Consumer<>() { -+ private final AtomicInteger idGenerator = new AtomicInteger(); -+ -+ @Override -+ public void accept(final Thread thread) { -+ thread.setDaemon(true); -+ thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement()); - thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(final Thread thread, final Throwable throwable) { - LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); - } - }); -- }, (long)(20.0e6)); // 20ms -- WORKER_THREADS = workerThreads; -+ } -+ } -+ ); -+ public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms -+ public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0); -+ public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0); -+ -+ public static void haltExecutors() { -+ MoonriseCommon.WORKER_POOL.shutdown(false); -+ LOGGER.info("Awaiting termination of worker pool for up to 60s..."); -+ if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { -+ LOGGER.error("Worker pool did not shut down in time!"); -+ MoonriseCommon.WORKER_POOL.halt(false); -+ } -+ -+ MoonriseCommon.IO_POOL.shutdown(false); -+ LOGGER.info("Awaiting termination of I/O pool for up to 60s..."); -+ if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { -+ LOGGER.error("I/O pool did not shut down in time!"); -+ MoonriseCommon.IO_POOL.halt(false); -+ } - } - - private MoonriseCommon() {} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java -@@ -0,0 +0,0 @@ - package ca.spottedleaf.moonrise.common.util; - -+import ca.spottedleaf.moonrise.common.PlatformHooks; -+ - public final class MoonriseConstants { - -- public static final int MAX_VIEW_DISTANCE = 32; -+ public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32); - - private MoonriseConstants() {} - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.moonrise.common.util; -+ -+import net.minecraft.world.level.levelgen.LegacyRandomSource; -+ -+/** -+ * Avoid costly CAS of superclass -+ */ -+public final class SimpleRandom extends LegacyRandomSource { -+ -+ private static final long MULTIPLIER = 25214903917L; -+ private static final long ADDEND = 11L; -+ private static final int BITS = 48; -+ private static final long MASK = (1L << BITS) - 1; -+ -+ private long value; -+ -+ public SimpleRandom(final long seed) { -+ super(0L); -+ this.value = seed; -+ } -+ -+ @Override -+ public void setSeed(final long seed) { -+ this.value = (seed ^ MULTIPLIER) & MASK; -+ } -+ -+ private long advanceSeed() { -+ return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK; -+ } -+ -+ @Override -+ public int next(final int bits) { -+ return (int)(this.advanceSeed() >>> (BITS - bits)); -+ } -+ -+ @Override -+ public int nextInt() { -+ final long seed = this.advanceSeed(); -+ return (int)(seed >>> (BITS - Integer.SIZE)); -+ } -+ -+ @Override -+ public int nextInt(final int bound) { -+ if (bound <= 0) { -+ throw new IllegalArgumentException(); -+ } -+ -+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ -+ final long value = this.advanceSeed() >>> (BITS - Integer.SIZE); -+ return (int)((value * (long)bound) >>> Integer.SIZE); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java -@@ -0,0 +0,0 @@ public class TickThread extends Thread { - } - - public TickThread(final Runnable run, final String name) { -- this(run, name, ID_GENERATOR.incrementAndGet()); -+ this(null, run, name); - } - -- private TickThread(final Runnable run, final String name, final int id) { -- super(run, name); -+ public TickThread(final ThreadGroup group, final Runnable run, final String name) { -+ this(group, run, name, ID_GENERATOR.incrementAndGet()); -+ } -+ -+ private TickThread(final ThreadGroup group, final Runnable run, final String name, final int id) { -+ super(group, run, name); - this.id = id; - } - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java -@@ -0,0 +0,0 @@ public final class WorldUtil { - // min, max are inclusive - - public static int getMaxSection(final LevelHeightAccessor world) { -- return world.getMaxSectionY() - 1; // getMaxSection() is exclusive -+ return world.getMaxSectionY(); -+ } -+ -+ public static int getMaxSection(final Level world) { -+ return world.getMaxSectionY(); - } - - public static int getMinSection(final LevelHeightAccessor world) { - return world.getMinSectionY(); - } - -+ public static int getMinSection(final Level world) { -+ return world.getMinSectionY(); -+ } -+ - public static int getMaxLightSection(final LevelHeightAccessor world) { - return getMaxSection(world) + 1; - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.moonrise.paper; -+ -+import ca.spottedleaf.moonrise.common.PlatformHooks; -+import com.mojang.datafixers.DSL; -+import com.mojang.datafixers.DataFixer; -+import com.mojang.serialization.Dynamic; -+import net.minecraft.core.BlockPos; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.NbtOps; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.GenerationChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import net.minecraft.world.level.chunk.storage.SerializableChunkData; -+import net.minecraft.world.level.entity.EntityTypeTest; -+import net.minecraft.world.phys.AABB; -+import java.util.List; -+import java.util.function.Predicate; -+ -+public final class PaperHooks implements PlatformHooks { -+ -+ @Override -+ public String getBrand() { -+ return "Paper"; -+ } -+ -+ @Override -+ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) { -+ return blockState.getLightEmission(); -+ } -+ -+ @Override -+ public Predicate maybeHasLightEmission() { -+ return (final BlockState state) -> { -+ return state.getLightEmission() != 0; -+ }; -+ } -+ -+ @Override -+ public boolean hasCurrentlyLoadingChunk() { -+ return false; -+ } -+ -+ @Override -+ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) { -+ return null; -+ } -+ -+ @Override -+ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) { -+ -+ } -+ -+ @Override -+ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) { -+ -+ } -+ -+ @Override -+ public boolean allowAsyncTicketUpdates() { -+ return true; -+ } -+ -+ @Override -+ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) { -+ -+ } -+ -+ @Override -+ public void chunkUnloadFromWorld(final LevelChunk chunk) { -+ -+ } -+ -+ @Override -+ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) { -+ -+ } -+ -+ @Override -+ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) { -+ -+ } -+ -+ @Override -+ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) { -+ -+ } -+ -+ @Override -+ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, final List into) { -+ -+ } -+ -+ @Override -+ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, final AABB boundingBox, final Predicate predicate, final List into, final int maxCount) { -+ -+ } -+ -+ @Override -+ public void entityMove(final Entity entity, final long oldSection, final long newSection) { -+ -+ } -+ -+ @Override -+ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) { -+ return true; -+ } -+ -+ @Override -+ public boolean configFixMC224294() { -+ return true; -+ } -+ -+ @Override -+ public boolean configAutoConfigSendDistance() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance; -+ } -+ -+ @Override -+ public double configPlayerMaxLoadRate() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; -+ } -+ -+ @Override -+ public double configPlayerMaxGenRate() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; -+ } -+ -+ @Override -+ public double configPlayerMaxSendRate() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; -+ } -+ -+ @Override -+ public int configPlayerMaxConcurrentLoads() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; -+ } -+ -+ @Override -+ public int configPlayerMaxConcurrentGens() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; -+ } -+ -+ @Override -+ public long configAutoSaveInterval(final ServerLevel world) { -+ return world.paperConfig().chunks.autoSaveInterval.value(); -+ } -+ -+ @Override -+ public int configMaxAutoSavePerTick(final ServerLevel world) { -+ return world.paperConfig().chunks.maxAutoSaveChunksPerTick; -+ } -+ -+ @Override -+ public boolean configFixMC159283() { -+ return true; -+ } -+ -+ @Override -+ public boolean forceNoSave(final ChunkAccess chunk) { -+ return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave; -+ } -+ -+ @Override -+ public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, -+ final int fromVersion, final int toVersion) { -+ return (CompoundTag)dataFixer.update( -+ type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion -+ ).getValue(); -+ } -+ -+ @Override -+ public boolean hasMainChunkLoadHook() { -+ return false; -+ } -+ -+ @Override -+ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) { -+ -+ } -+ -+ @Override -+ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities) { -+ return entities; -+ } -+ -+ @Override -+ public void unloadEntity(final Entity entity) { -+ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD); -+ } -+ -+ @Override -+ public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) { -+ net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities()); -+ } -+ -+ @Override -+ public int modifyEntityTrackingRange(final Entity entity, final int currentRange) { -+ return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -0,0 +0,0 @@ public class GlobalConfiguration extends ConfigurationPart { - - @PostProcess - private void postProcess() { -- -+ ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads); - } - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - return true; - } - -- public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority, - java.util.function.Consumer> onLoad) { - if (Thread.currentThread() != this.thread) { - this.getChunkSource().mainThreadProcessor.execute(() -> { -diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -@@ -0,0 +0,0 @@ public class ChunkStatusTasks { - }, context.mainThreadExecutor()); - } - -- private static void postLoadProtoChunk(ServerLevel world, List entities) { -+ public static void postLoadProtoChunk(ServerLevel world, List entities) { // Paper - public - if (!entities.isEmpty()) { - // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities - world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> { -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -0,0 +0,0 @@ public class PersistentEntitySectionManager implements A - private boolean addEntity(T entity, boolean existing) { - org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper - // Paper start - chunk system hooks -- if (existing) { -- // I don't want to know why this is a generic type. -- Entity entityCasted = (Entity)entity; -- boolean wasRemoved = entityCasted.isRemoved(); -- boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted); -- if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { -- // removed by callback -- return false; -- } -+ // I don't want to know why this is a generic type. -+ Entity entityCasted = (Entity)entity; -+ boolean wasRemoved = entityCasted.isRemoved(); -+ boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, true); -+ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { -+ // removed by callback -+ return false; - } - // Paper end - chunk system hooks - if (!this.addEntityUuid(entity)) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - } - -- ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority; -+ ca.spottedleaf.concurrentutil.util.Priority priority; - if (urgent) { -- priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER; -+ priority = ca.spottedleaf.concurrentutil.util.Priority.HIGHER; - } else { -- priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL; -+ priority = ca.spottedleaf.concurrentutil.util.Priority.NORMAL; - } - - java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); -diff --git a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks -@@ -0,0 +1 @@ -+ca.spottedleaf.moonrise.paper.PaperHooks diff --git a/patches/server/ConcurrentUtil.patch b/patches/server/ConcurrentUtil.patch index 5f96c71aad..e330e82b7c 100644 --- a/patches/server/ConcurrentUtil.patch +++ b/patches/server/ConcurrentUtil.patch @@ -1431,166 +1431,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } +} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.collection; -+ -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; -+ -+import java.lang.invoke.VarHandle; -+import java.util.ConcurrentModificationException; -+ -+/** -+ * Single reader thread single writer thread queue. The reader side of the queue is ordered by acquire semantics, -+ * and the writer side of the queue is ordered by release semantics. -+ */ -+// TODO test -+public class SRSWLinkedQueue { -+ -+ // always non-null -+ protected LinkedNode head; -+ -+ // always non-null -+ protected LinkedNode tail; -+ -+ /* IMPL NOTE: Leave hashCode and equals to their defaults */ -+ -+ public SRSWLinkedQueue() { -+ final LinkedNode dummy = new LinkedNode<>(null, null); -+ this.head = this.tail = dummy; -+ } -+ -+ /** -+ * Must be the reader thread. -+ * -+ *

-+ * Returns, without removing, the first element of this queue. -+ *

-+ * @return Returns, without removing, the first element of this queue. -+ */ -+ public E peekFirst() { -+ LinkedNode head = this.head; -+ E ret = head.getElementPlain(); -+ if (ret == null) { -+ head = head.getNextAcquire(); -+ if (head == null) { -+ // empty -+ return null; -+ } -+ // update head reference for next poll() call -+ this.head = head; -+ // guaranteed to be non-null -+ ret = head.getElementPlain(); -+ if (ret == null) { -+ throw new ConcurrentModificationException("Multiple reader threads"); -+ } -+ } -+ -+ return ret; -+ } -+ -+ /** -+ * Must be the reader thread. -+ * -+ *

-+ * Returns and removes the first element of this queue. -+ *

-+ * @return Returns and removes the first element of this queue. -+ */ -+ public E poll() { -+ LinkedNode head = this.head; -+ E ret = head.getElementPlain(); -+ if (ret == null) { -+ head = head.getNextAcquire(); -+ if (head == null) { -+ // empty -+ return null; -+ } -+ // guaranteed to be non-null -+ ret = head.getElementPlain(); -+ if (ret == null) { -+ throw new ConcurrentModificationException("Multiple reader threads"); -+ } -+ } -+ -+ head.setElementPlain(null); -+ LinkedNode next = head.getNextAcquire(); -+ this.head = next == null ? head : next; -+ -+ return ret; -+ } -+ -+ /** -+ * Must be the writer thread. -+ * -+ *

-+ * Adds the element to the end of the queue. -+ *

-+ * -+ * @throws NullPointerException If the provided element is null -+ */ -+ public void addLast(final E element) { -+ Validate.notNull(element, "Provided element cannot be null"); -+ final LinkedNode append = new LinkedNode<>(element, null); -+ -+ this.tail.setNextRelease(append); -+ this.tail = append; -+ } -+ -+ protected static final class LinkedNode { -+ -+ protected volatile Object element; -+ protected volatile LinkedNode next; -+ -+ protected static final VarHandle ELEMENT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "element", Object.class); -+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "next", LinkedNode.class); -+ -+ protected LinkedNode(final Object element, final LinkedNode next) { -+ ELEMENT_HANDLE.set(this, element); -+ NEXT_HANDLE.set(this, next); -+ } -+ -+ /* element */ -+ -+ @SuppressWarnings("unchecked") -+ protected final E getElementPlain() { -+ return (E)ELEMENT_HANDLE.get(this); -+ } -+ -+ protected final void setElementPlain(final E update) { -+ ELEMENT_HANDLE.set(this, (Object)update); -+ } -+ /* next */ -+ -+ @SuppressWarnings("unchecked") -+ protected final LinkedNode getNextPlain() { -+ return (LinkedNode)NEXT_HANDLE.get(this); -+ } -+ -+ @SuppressWarnings("unchecked") -+ protected final LinkedNode getNextAcquire() { -+ return (LinkedNode)NEXT_HANDLE.getAcquire(this); -+ } -+ -+ protected final void setNextPlain(final LinkedNode next) { -+ NEXT_HANDLE.set(this, next); -+ } -+ -+ protected final void setNextRelease(final LinkedNode next) { -+ NEXT_HANDLE.setRelease(this, next); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java ++++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java @@ -0,0 +0,0 @@ +package ca.spottedleaf.concurrentutil.completable; + @@ -1601,9 +1446,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import org.slf4j.LoggerFactory; +import java.util.function.BiConsumer; + -+public final class Completable { ++public final class CallbackCompletable { + -+ private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class); ++ private static final Logger LOGGER = LoggerFactory.getLogger(CallbackCompletable.class); + + private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); + private T result; @@ -1655,8 +1500,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private void completeWaiter(final BiConsumer consumer, final T result, final Throwable throwable) { + try { + consumer.accept(result, throwable); -+ } catch (final ThreadDeath death) { -+ throw death; + } catch (final Throwable throwable2) { + LOGGER.error("Failed to complete callback " + ConcurrentUtil.genericToString(consumer), throwable2); + } @@ -1700,222 +1543,752 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Override + public boolean cancel() { -+ return Completable.this.waiters.remove(this.waiter); ++ return CallbackCompletable.this.waiters.remove(this.waiter); + } + } +} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java +\ No newline at end of file +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java ++++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java @@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.executor; ++package ca.spottedleaf.concurrentutil.completable; + +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import java.util.function.BooleanSupplier; ++import ca.spottedleaf.concurrentutil.util.Validate; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import java.lang.invoke.VarHandle; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CompletionException; ++import java.util.concurrent.CompletionStage; ++import java.util.concurrent.Executor; ++import java.util.concurrent.ForkJoinPool; ++import java.util.concurrent.locks.LockSupport; ++import java.util.function.BiConsumer; ++import java.util.function.BiFunction; ++import java.util.function.Consumer; ++import java.util.function.Function; ++import java.util.function.Supplier; + -+/** -+ * Base implementation for an abstract queue of tasks which are executed either synchronously or asynchronously. -+ * -+ *

-+ * The implementation supports tracking task executions using {@link #getTotalTasksScheduled()} and -+ * {@link #getTotalTasksExecuted()}, and optionally shutting down the executor using {@link #shutdown()} -+ *

-+ * -+ *

-+ * The base implementation does not provide a method to queue a task for execution, rather that is specified in -+ * the specific implementation. However, it is required that a specific implementation provides a method to -+ * queue a task or create a task. A queued task is one which will eventually be executed, -+ * and a created task must be queued to execute via {@link BaseTask#queue()} or be executed manually via -+ * {@link BaseTask#execute()}. This choice of delaying the queueing of a task may be useful to provide a task handle -+ * which may be cancelled or adjusted before the actual real task logic is ready to be executed. -+ *

-+ */ -+public interface BaseExecutor { ++public final class Completable { + -+ /** -+ * Returns whether every task scheduled to this queue has been removed and executed or cancelled. If no tasks have been queued, -+ * returns {@code true}. -+ * -+ * @return {@code true} if all tasks that have been queued have finished executing or no tasks have been queued, {@code false} otherwise. -+ */ -+ public default boolean haveAllTasksExecuted() { -+ // order is important -+ // if new tasks are scheduled between the reading of these variables, scheduled is guaranteed to be higher - -+ // so our check fails, and we try again -+ final long completed = this.getTotalTasksExecuted(); -+ final long scheduled = this.getTotalTasksScheduled(); ++ private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class); ++ private static final Function DEFAULT_EXCEPTION_HANDLER = (final Throwable thr) -> { ++ LOGGER.error("Unhandled exception during Completable operation", thr); ++ return thr; ++ }; + -+ return completed == scheduled; ++ public static Executor getDefaultExecutor() { ++ return ForkJoinPool.commonPool(); + } + -+ /** -+ * Returns the number of tasks that have been scheduled or execute or are pending to be scheduled. -+ */ -+ public long getTotalTasksScheduled(); ++ private static final Transform COMPLETED_STACK = new Transform<>(null, null, null, null) { ++ @Override ++ public void run() {} ++ }; ++ private volatile Transform completeStack; ++ private static final VarHandle COMPLETE_STACK_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "completeStack", Transform.class); + -+ /** -+ * Returns the number of tasks that have fully been executed. -+ */ -+ public long getTotalTasksExecuted(); ++ private static final Object NULL_MASK = new Object(); ++ private volatile Object result; ++ private static final VarHandle RESULT_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "result", Object.class); + -+ /** -+ * Waits until this queue has had all of its tasks executed (NOT removed). See {@link #haveAllTasksExecuted()} -+ *

-+ * This call is most effective after a {@link #shutdown()} call, as the shutdown call guarantees no tasks can -+ * be executed and the waitUntilAllExecuted call makes sure the queue is empty. Effectively, using shutdown then using -+ * waitUntilAllExecuted ensures this queue is empty - and most importantly, will remain empty. -+ *

-+ *

-+ * This method is not guaranteed to be immediately responsive to queue state, so calls may take significantly more -+ * time than expected. Effectively, do not rely on this call being fast - even if there are few tasks scheduled. -+ *

-+ *

-+ * Note: Interruptions to the the current thread have no effect. Interrupt status is also not affected by this call. -+ *

-+ * -+ * @throws IllegalStateException If the current thread is not allowed to wait -+ */ -+ public default void waitUntilAllExecuted() throws IllegalStateException { -+ long failures = 1L; // start at 0.25ms ++ private Object getResultPlain() { ++ return (Object)RESULT_HANDLE.get(this); ++ } + -+ while (!this.haveAllTasksExecuted()) { -+ Thread.yield(); -+ failures = ConcurrentUtil.linearLongBackoff(failures, 250_000L, 5_000_000L); // 500us, 5ms ++ private Object getResultVolatile() { ++ return (Object)RESULT_HANDLE.getVolatile(this); ++ } ++ ++ private void pushStackOrRun(final Transform push) { ++ int failures = 0; ++ for (Transform curr = (Transform)COMPLETE_STACK_HANDLE.getVolatile(this);;) { ++ if (curr == COMPLETED_STACK) { ++ push.execute(); ++ return; ++ } ++ ++ push.next = curr; ++ ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ ++ if (curr == (curr = (Transform)COMPLETE_STACK_HANDLE.compareAndExchange(this, curr, push))) { ++ return; ++ } ++ push.next = null; ++ ++failures; + } + } + -+ /** -+ * Executes the next available task. -+ * -+ * @return {@code true} if a task was executed, {@code false} otherwise -+ * @throws IllegalStateException If the current thread is not allowed to execute a task -+ */ -+ public boolean executeTask() throws IllegalStateException; ++ private void propagateStack() { ++ Transform topStack = (Transform)COMPLETE_STACK_HANDLE.getAndSet(this, COMPLETED_STACK); ++ while (topStack != null) { ++ topStack.execute(); ++ topStack = topStack.next; ++ } ++ } + -+ /** -+ * Executes all queued tasks. -+ * -+ * @return {@code true} if a task was executed, {@code false} otherwise -+ * @throws IllegalStateException If the current thread is not allowed to execute a task -+ */ -+ public default boolean executeAll() { -+ if (!this.executeTask()) { ++ private static Object maskNull(final Object res) { ++ return res == null ? NULL_MASK : res; ++ } ++ ++ private static Object unmaskNull(final Object res) { ++ return res == NULL_MASK ? null : res; ++ } ++ ++ private static Executor checkExecutor(final Executor executor) { ++ return Validate.notNull(executor, "Executor may not be null"); ++ } ++ ++ public Completable() {} ++ ++ private Completable(final Object complete) { ++ COMPLETE_STACK_HANDLE.set(this, COMPLETED_STACK); ++ RESULT_HANDLE.setRelease(this, complete); ++ } ++ ++ public static Completable completed(final T value) { ++ return new Completable<>(maskNull(value)); ++ } ++ ++ public static Completable failed(final Throwable ex) { ++ Validate.notNull(ex, "Exception may not be null"); ++ ++ return new Completable<>(new ExceptionResult(ex)); ++ } ++ ++ public static Completable supplied(final Supplier supplier) { ++ return supplied(supplier, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public static Completable supplied(final Supplier supplier, final Function exceptionHandler) { ++ try { ++ return completed(supplier.get()); ++ } catch (final Throwable throwable) { ++ Throwable complete; ++ try { ++ complete = exceptionHandler.apply(throwable); ++ } catch (final Throwable thr2) { ++ throwable.addSuppressed(thr2); ++ complete = throwable; ++ } ++ return failed(complete); ++ } ++ } ++ ++ public static Completable suppliedAsync(final Supplier supplier, final Executor executor) { ++ return suppliedAsync(supplier, executor, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public static Completable suppliedAsync(final Supplier supplier, final Executor executor, final Function exceptionHandler) { ++ final Completable ret = new Completable<>(); ++ ++ class AsyncSuppliedCompletable implements Runnable, CompletableFuture.AsynchronousCompletionTask { ++ @Override ++ public void run() { ++ try { ++ ret.complete(supplier.get()); ++ } catch (final Throwable throwable) { ++ Throwable complete; ++ try { ++ complete = exceptionHandler.apply(throwable); ++ } catch (final Throwable thr2) { ++ throwable.addSuppressed(thr2); ++ complete = throwable; ++ } ++ ret.completeExceptionally(complete); ++ } ++ } ++ } ++ ++ try { ++ executor.execute(new AsyncSuppliedCompletable()); ++ } catch (final Throwable throwable) { ++ Throwable complete; ++ try { ++ complete = exceptionHandler.apply(throwable); ++ } catch (final Throwable thr2) { ++ throwable.addSuppressed(thr2); ++ complete = throwable; ++ } ++ ret.completeExceptionally(complete); ++ } ++ ++ return ret; ++ } ++ ++ private boolean completeRaw(final Object value) { ++ if ((Object)RESULT_HANDLE.getVolatile(this) != null || !(boolean)RESULT_HANDLE.compareAndSet(this, (Object)null, value)) { + return false; + } + -+ while (this.executeTask()); -+ ++ this.propagateStack(); + return true; + } + -+ /** -+ * Waits and executes tasks until the condition returns {@code true}. -+ *

-+ * WARNING: This function is not suitable for waiting until a deadline! -+ * Use {@link #executeUntil(long)} or {@link #executeConditionally(BooleanSupplier, long)} instead. -+ *

-+ */ -+ public default void executeConditionally(final BooleanSupplier condition) { -+ long failures = 0; -+ while (!condition.getAsBoolean()) { -+ if (this.executeTask()) { -+ failures = failures >>> 2; -+ } else { -+ failures = ConcurrentUtil.linearLongBackoff(failures, 100_000L, 10_000_000L); // 100us, 10ms ++ public boolean complete(final T result) { ++ return this.completeRaw(maskNull(result)); ++ } ++ ++ public boolean completeExceptionally(final Throwable exception) { ++ Validate.notNull(exception, "Exception may not be null"); ++ ++ return this.completeRaw(new ExceptionResult(exception)); ++ } ++ ++ public boolean isDone() { ++ return this.getResultVolatile() != null; ++ } ++ ++ public boolean isNormallyComplete() { ++ return this.getResultVolatile() != null && !(this.getResultVolatile() instanceof ExceptionResult); ++ } ++ ++ public boolean isExceptionallyComplete() { ++ return this.getResultVolatile() instanceof ExceptionResult; ++ } ++ ++ public Throwable getException() { ++ final Object res = this.getResultVolatile(); ++ if (res == null) { ++ return null; ++ } ++ ++ if (!(res instanceof ExceptionResult exRes)) { ++ throw new IllegalStateException("Not completed exceptionally"); ++ } ++ ++ return exRes.ex; ++ } ++ ++ public T getNow(final T dfl) throws CompletionException { ++ final Object res = this.getResultVolatile(); ++ if (res == null) { ++ return dfl; ++ } ++ ++ if (res instanceof ExceptionResult exRes) { ++ throw new CompletionException(exRes.ex); ++ } ++ ++ return (T)unmaskNull(res); ++ } ++ ++ public T join() throws CompletionException { ++ if (this.isDone()) { ++ return this.getNow(null); ++ } ++ ++ final UnparkTransform unparkTransform = new UnparkTransform<>(this, Thread.currentThread()); ++ ++ this.pushStackOrRun(unparkTransform); ++ ++ boolean interuptted = false; ++ while (!unparkTransform.isReleasable()) { ++ try { ++ ForkJoinPool.managedBlock(unparkTransform); ++ } catch (final InterruptedException ex) { ++ interuptted = true; + } + } ++ ++ if (interuptted) { ++ Thread.currentThread().interrupt(); ++ } ++ ++ return this.getNow(null); ++ } ++ ++ public CompletableFuture toFuture() { ++ final Object rawResult = this.getResultVolatile(); ++ if (rawResult != null) { ++ if (rawResult instanceof ExceptionResult exRes) { ++ return CompletableFuture.failedFuture(exRes.ex); ++ } else { ++ return CompletableFuture.completedFuture((T)unmaskNull(rawResult)); ++ } ++ } ++ ++ final CompletableFuture ret = new CompletableFuture<>(); ++ ++ class ToFuture implements BiConsumer { ++ ++ @Override ++ public void accept(final T res, final Throwable ex) { ++ if (ex != null) { ++ ret.completeExceptionally(ex); ++ } else { ++ ret.complete(res); ++ } ++ } ++ } ++ ++ this.whenComplete(new ToFuture()); ++ ++ return ret; ++ } ++ ++ public static Completable fromFuture(final CompletionStage stage) { ++ final Completable ret = new Completable<>(); ++ ++ class FromFuture implements BiConsumer { ++ @Override ++ public void accept(final T res, final Throwable ex) { ++ if (ex != null) { ++ ret.completeExceptionally(ex); ++ } else { ++ ret.complete(res); ++ } ++ } ++ } ++ ++ stage.whenComplete(new FromFuture()); ++ ++ return ret; ++ } ++ ++ ++ public Completable thenApply(final Function function) { ++ return this.thenApply(function, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable thenApply(final Function function, final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new ApplyTransform<>(null, this, ret, exceptionHandler, function)); ++ return ret; ++ } ++ ++ public Completable thenApplyAsync(final Function function) { ++ return this.thenApplyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable thenApplyAsync(final Function function, final Executor executor) { ++ return this.thenApplyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable thenApplyAsync(final Function function, final Executor executor, final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new ApplyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); ++ return ret; ++ } ++ ++ ++ public Completable thenAccept(final Consumer consumer) { ++ return this.thenAccept(consumer, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable thenAccept(final Consumer consumer, final Function exceptionHandler) { ++ Validate.notNull(consumer, "Consumer may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new AcceptTransform<>(null, this, ret, exceptionHandler, consumer)); ++ return ret; ++ } ++ ++ public Completable thenAcceptAsync(final Consumer consumer) { ++ return this.thenAcceptAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable thenAcceptAsync(final Consumer consumer, final Executor executor) { ++ return this.thenAcceptAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable thenAcceptAsync(final Consumer consumer, final Executor executor, final Function exceptionHandler) { ++ Validate.notNull(consumer, "Consumer may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new AcceptTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer)); ++ return ret; ++ } ++ ++ ++ public Completable thenRun(final Runnable run) { ++ return this.thenRun(run, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable thenRun(final Runnable run, final Function exceptionHandler) { ++ Validate.notNull(run, "Run may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new RunTransform<>(null, this, ret, exceptionHandler, run)); ++ return ret; ++ } ++ ++ public Completable thenRunAsync(final Runnable run) { ++ return this.thenRunAsync(run, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable thenRunAsync(final Runnable run, final Executor executor) { ++ return this.thenRunAsync(run, executor, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable thenRunAsync(final Runnable run, final Executor executor, final Function exceptionHandler) { ++ Validate.notNull(run, "Run may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new RunTransform<>(checkExecutor(executor), this, ret, exceptionHandler, run)); ++ return ret; ++ } ++ ++ ++ public Completable handle(final BiFunction function) { ++ return this.handle(function, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable handle(final BiFunction function, ++ final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new HandleTransform<>(null, this, ret, exceptionHandler, function)); ++ return ret; ++ } ++ ++ public Completable handleAsync(final BiFunction function) { ++ return this.handleAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable handleAsync(final BiFunction function, ++ final Executor executor) { ++ return this.handleAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable handleAsync(final BiFunction function, ++ final Executor executor, ++ final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new HandleTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); ++ return ret; ++ } ++ ++ ++ public Completable whenComplete(final BiConsumer consumer) { ++ return this.whenComplete(consumer, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable whenComplete(final BiConsumer consumer, final Function exceptionHandler) { ++ Validate.notNull(consumer, "Consumer may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new WhenTransform<>(null, this, ret, exceptionHandler, consumer)); ++ return ret; ++ } ++ ++ public Completable whenCompleteAsync(final BiConsumer consumer) { ++ return this.whenCompleteAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable whenCompleteAsync(final BiConsumer consumer, final Executor executor) { ++ return this.whenCompleteAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable whenCompleteAsync(final BiConsumer consumer, final Executor executor, ++ final Function exceptionHandler) { ++ Validate.notNull(consumer, "Consumer may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new WhenTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer)); ++ return ret; ++ } ++ ++ ++ public Completable exceptionally(final Function function) { ++ return this.exceptionally(function, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable exceptionally(final Function function, final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new ExceptionallyTransform<>(null, this, ret, exceptionHandler, function)); ++ return ret; ++ } ++ ++ public Completable exceptionallyAsync(final Function function) { ++ return this.exceptionallyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable exceptionallyAsync(final Function function, final Executor executor) { ++ return this.exceptionallyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public Completable exceptionallyAsync(final Function function, final Executor executor, ++ final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new ExceptionallyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); ++ return ret; ++ } ++ ++ private static final class ExceptionResult { ++ public final Throwable ex; ++ ++ public ExceptionResult(final Throwable ex) { ++ this.ex = ex; ++ } + } + -+ /** -+ * Waits and executes tasks until the condition returns {@code true} or {@code System.nanoTime() - deadline >= 0}. -+ */ -+ public default void executeConditionally(final BooleanSupplier condition, final long deadline) { -+ long failures = 0; -+ // double check deadline; we don't know how expensive the condition is -+ while ((System.nanoTime() - deadline < 0L) && !condition.getAsBoolean() && (System.nanoTime() - deadline < 0L)) { -+ if (this.executeTask()) { -+ failures = failures >>> 2; -+ } else { -+ failures = ConcurrentUtil.linearLongBackoffDeadline(failures, 100_000L, 10_000_000L, deadline); // 100us, 10ms -+ } ++ private static abstract class Transform implements Runnable, CompletableFuture.AsynchronousCompletionTask { ++ ++ private Transform next; ++ ++ private final Executor executor; ++ protected final Completable from; ++ protected final Completable to; ++ protected final Function exceptionHandler; ++ ++ protected Transform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler) { ++ this.executor = executor; ++ this.from = from; ++ this.to = to; ++ this.exceptionHandler = exceptionHandler; + } -+ } + -+ /** -+ * Waits and executes tasks until {@code System.nanoTime() - deadline >= 0}. -+ */ -+ public default void executeUntil(final long deadline) { -+ long failures = 0; -+ while (System.nanoTime() - deadline < 0L) { -+ if (this.executeTask()) { -+ failures = failures >>> 2; -+ } else { -+ failures = ConcurrentUtil.linearLongBackoffDeadline(failures, 100_000L, 10_000_000L, deadline); // 100us, 10ms -+ } -+ } -+ } -+ -+ /** -+ * Prevent further additions to this queue. Attempts to add after this call has completed (potentially during) will -+ * result in {@link IllegalStateException} being thrown. -+ *

-+ * This operation is atomic with respect to other shutdown calls -+ *

-+ *

-+ * After this call has completed, regardless of return value, this queue will be shutdown. -+ *

-+ * -+ * @return {@code true} if the queue was shutdown, {@code false} if it has shut down already -+ * @throws UnsupportedOperationException If this queue does not support shutdown -+ * @see #isShutdown() -+ */ -+ public default boolean shutdown() throws UnsupportedOperationException { -+ throw new UnsupportedOperationException(); -+ } -+ -+ /** -+ * Returns whether this queue has shut down. Effectively, whether new tasks will be rejected - this method -+ * does not indicate whether all the tasks scheduled have been executed. -+ * @return Returns whether this queue has shut down. -+ * @see #waitUntilAllExecuted() -+ */ -+ public default boolean isShutdown() { -+ return false; -+ } -+ -+ /** -+ * Task object returned for any {@link BaseExecutor} scheduled task. -+ * @see BaseExecutor -+ */ -+ public static interface BaseTask extends Cancellable { -+ -+ /** -+ * Causes a lazily queued task to become queued or executed -+ * -+ * @throws IllegalStateException If the backing queue has shutdown -+ * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed -+ */ -+ public boolean queue(); -+ -+ /** -+ * Forces this task to be marked as completed. -+ * -+ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed or is being completed. -+ */ ++ // force interface call to become virtual call + @Override -+ public boolean cancel(); ++ public abstract void run(); + -+ /** -+ * Executes this task. This will also mark the task as completing. -+ *

-+ * Exceptions thrown from the runnable will be rethrown. -+ *

-+ * -+ * @return {@code true} if this task was executed, {@code false} if it was already marked as completed. -+ */ -+ public boolean execute(); ++ protected void failed(final Throwable throwable) { ++ Throwable complete; ++ try { ++ complete = this.exceptionHandler.apply(throwable); ++ } catch (final Throwable thr2) { ++ throwable.addSuppressed(thr2); ++ complete = throwable; ++ } ++ this.to.completeExceptionally(complete); ++ } ++ ++ public void execute() { ++ if (this.executor == null) { ++ this.run(); ++ return; ++ } ++ ++ try { ++ this.executor.execute(this); ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } ++ } ++ } ++ ++ private static final class ApplyTransform extends Transform { ++ ++ private final Function function; ++ ++ public ApplyTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final Function function) { ++ super(executor, from, to, exceptionHandler); ++ this.function = function; ++ } ++ ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.to.completeExceptionally(exRes.ex); ++ } else { ++ this.to.complete(this.function.apply((T)unmaskNull(result))); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } ++ } ++ } ++ ++ private static final class AcceptTransform extends Transform { ++ private final Consumer consumer; ++ ++ public AcceptTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final Consumer consumer) { ++ super(executor, from, to, exceptionHandler); ++ this.consumer = consumer; ++ } ++ ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.to.completeExceptionally(exRes.ex); ++ } else { ++ this.consumer.accept((T)unmaskNull(result)); ++ this.to.complete(null); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } ++ } ++ } ++ ++ private static final class RunTransform extends Transform { ++ private final Runnable run; ++ ++ public RunTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final Runnable run) { ++ super(executor, from, to, exceptionHandler); ++ this.run = run; ++ } ++ ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.to.completeExceptionally(exRes.ex); ++ } else { ++ this.run.run(); ++ this.to.complete(null); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } ++ } ++ } ++ ++ private static final class HandleTransform extends Transform { ++ ++ private final BiFunction function; ++ ++ public HandleTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final BiFunction function) { ++ super(executor, from, to, exceptionHandler); ++ this.function = function; ++ } ++ ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.to.complete(this.function.apply(null, exRes.ex)); ++ } else { ++ this.to.complete(this.function.apply((T)unmaskNull(result), null)); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } ++ } ++ } ++ ++ private static final class WhenTransform extends Transform { ++ ++ private final BiConsumer consumer; ++ ++ public WhenTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final BiConsumer consumer) { ++ super(executor, from, to, exceptionHandler); ++ this.consumer = consumer; ++ } ++ ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.consumer.accept(null, exRes.ex); ++ this.to.completeExceptionally(exRes.ex); ++ } else { ++ final T unmasked = (T)unmaskNull(result); ++ this.consumer.accept(unmasked, null); ++ this.to.complete(unmasked); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } ++ } ++ } ++ ++ private static final class ExceptionallyTransform extends Transform { ++ private final Function function; ++ ++ public ExceptionallyTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final Function function) { ++ super(executor, from, to, exceptionHandler); ++ this.function = function; ++ } ++ ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.to.complete(this.function.apply(exRes.ex)); ++ } else { ++ this.to.complete((T)unmaskNull(result)); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } ++ } ++ } ++ ++ private static final class UnparkTransform extends Transform implements ForkJoinPool.ManagedBlocker { ++ ++ private volatile Thread thread; ++ ++ public UnparkTransform(final Completable from, final Thread target) { ++ super(null, from, null, null); ++ this.thread = target; ++ } ++ ++ @Override ++ public void run() { ++ final Thread t = this.thread; ++ this.thread = null; ++ LockSupport.unpark(t); ++ } ++ ++ @Override ++ public boolean block() throws InterruptedException { ++ while (!this.isReleasable()) { ++ if (Thread.interrupted()) { ++ throw new InterruptedException(); ++ } ++ LockSupport.park(this); ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public boolean isReleasable() { ++ return this.thread == null; ++ } + } +} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java @@ -1938,374 +2311,85 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + */ + public boolean cancel(); +} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java ++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java @@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.executor.standard; ++package ca.spottedleaf.concurrentutil.executor; + -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import java.lang.invoke.VarHandle; ++import ca.spottedleaf.concurrentutil.util.Priority; + -+public class DelayedPrioritisedTask { ++public interface PrioritisedExecutor { + -+ protected volatile int priority; -+ protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(DelayedPrioritisedTask.class, "priority", int.class); ++ /** ++ * Returns the number of tasks that have been scheduled are pending to be scheduled. ++ */ ++ public long getTotalTasksScheduled(); + -+ protected static final int PRIORITY_SET = Integer.MIN_VALUE >>> 0; ++ /** ++ * Returns the number of tasks that have been executed. ++ */ ++ public long getTotalTasksExecuted(); + -+ protected final int getPriorityVolatile() { -+ return (int)PRIORITY_HANDLE.getVolatile((DelayedPrioritisedTask)this); -+ } -+ -+ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { -+ return (int)PRIORITY_HANDLE.compareAndExchange((DelayedPrioritisedTask)this, (int)expect, (int)update); -+ } -+ -+ protected final int getAndOrPriorityVolatile(final int val) { -+ return (int)PRIORITY_HANDLE.getAndBitwiseOr((DelayedPrioritisedTask)this, (int)val); -+ } -+ -+ protected final void setPriorityPlain(final int val) { -+ PRIORITY_HANDLE.set((DelayedPrioritisedTask)this, (int)val); -+ } -+ -+ protected volatile PrioritisedExecutor.PrioritisedTask task; -+ protected static final VarHandle TASK_HANDLE = ConcurrentUtil.getVarHandle(DelayedPrioritisedTask.class, "task", PrioritisedExecutor.PrioritisedTask.class); -+ -+ protected PrioritisedExecutor.PrioritisedTask getTaskPlain() { -+ return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.get((DelayedPrioritisedTask)this); -+ } -+ -+ protected PrioritisedExecutor.PrioritisedTask getTaskVolatile() { -+ return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.getVolatile((DelayedPrioritisedTask)this); -+ } -+ -+ protected final PrioritisedExecutor.PrioritisedTask compareAndExchangeTaskVolatile(final PrioritisedExecutor.PrioritisedTask expect, final PrioritisedExecutor.PrioritisedTask update) { -+ return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.compareAndExchange((DelayedPrioritisedTask)this, (PrioritisedExecutor.PrioritisedTask)expect, (PrioritisedExecutor.PrioritisedTask)update); -+ } -+ -+ public DelayedPrioritisedTask(final PrioritisedExecutor.Priority priority) { -+ this.setPriorityPlain(priority.priority); -+ } -+ -+ // only public for debugging -+ public int getPriorityInternal() { -+ return this.getPriorityVolatile(); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask getTask() { -+ return this.getTaskVolatile(); -+ } -+ -+ public void setTask(final PrioritisedExecutor.PrioritisedTask task) { -+ int priority = this.getPriorityVolatile(); -+ -+ if (this.compareAndExchangeTaskVolatile(null, task) != null) { -+ throw new IllegalStateException("setTask() called twice"); -+ } -+ -+ int failures = 0; -+ for (;;) { -+ task.setPriority(PrioritisedExecutor.Priority.getPriority(priority)); -+ -+ if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | PRIORITY_SET))) { -+ return; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public PrioritisedExecutor.Priority getPriority() { -+ final int priority = this.getPriorityVolatile(); -+ if ((priority & PRIORITY_SET) != 0) { -+ return this.task.getPriority(); -+ } -+ -+ return PrioritisedExecutor.Priority.getPriority(priority); -+ } -+ -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_SET) != 0) { -+ this.getTaskPlain().raisePriority(priority); -+ return; -+ } -+ -+ if (!priority.isLowerPriority(curr)) { -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_SET) != 0) { -+ this.getTaskPlain().setPriority(priority); -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_SET) != 0) { -+ this.getTaskPlain().lowerPriority(priority); -+ return; -+ } -+ -+ if (!priority.isHigherPriority(curr)) { -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.executor.standard; -+ -+import ca.spottedleaf.concurrentutil.executor.BaseExecutor; -+ -+/** -+ * Implementation of {@link BaseExecutor} which schedules tasks to be executed by a given priority. -+ * @see BaseExecutor -+ */ -+public interface PrioritisedExecutor extends BaseExecutor { -+ -+ public static enum Priority { -+ -+ /** -+ * Priority value indicating the task has completed or is being completed. -+ * This priority cannot be used to schedule tasks. -+ */ -+ COMPLETING(-1), -+ -+ /** -+ * Absolute highest priority, should only be used for when a task is blocking a time-critical thread. -+ */ -+ BLOCKING(), -+ -+ /** -+ * Should only be used for urgent but not time-critical tasks. -+ */ -+ HIGHEST(), -+ -+ /** -+ * Two priorities above normal. -+ */ -+ HIGHER(), -+ -+ /** -+ * One priority above normal. -+ */ -+ HIGH(), -+ -+ /** -+ * Default priority. -+ */ -+ NORMAL(), -+ -+ /** -+ * One priority below normal. -+ */ -+ LOW(), -+ -+ /** -+ * Two priorities below normal. -+ */ -+ LOWER(), -+ -+ /** -+ * Use for tasks that should eventually execute, but are not needed to. -+ */ -+ LOWEST(), -+ -+ /** -+ * Use for tasks that can be delayed indefinitely. -+ */ -+ IDLE(); -+ -+ // returns whether the priority can be scheduled -+ public static boolean isValidPriority(final Priority priority) { -+ return priority != null && priority != Priority.COMPLETING; -+ } -+ -+ // returns the higher priority of the two -+ public static Priority max(final Priority p1, final Priority p2) { -+ return p1.isHigherOrEqualPriority(p2) ? p1 : p2; -+ } -+ -+ // returns the lower priroity of the two -+ public static Priority min(final Priority p1, final Priority p2) { -+ return p1.isLowerOrEqualPriority(p2) ? p1 : p2; -+ } -+ -+ public boolean isHigherOrEqualPriority(final Priority than) { -+ return this.priority <= than.priority; -+ } -+ -+ public boolean isHigherPriority(final Priority than) { -+ return this.priority < than.priority; -+ } -+ -+ public boolean isLowerOrEqualPriority(final Priority than) { -+ return this.priority >= than.priority; -+ } -+ -+ public boolean isLowerPriority(final Priority than) { -+ return this.priority > than.priority; -+ } -+ -+ public boolean isHigherOrEqualPriority(final int than) { -+ return this.priority <= than; -+ } -+ -+ public boolean isHigherPriority(final int than) { -+ return this.priority < than; -+ } -+ -+ public boolean isLowerOrEqualPriority(final int than) { -+ return this.priority >= than; -+ } -+ -+ public boolean isLowerPriority(final int than) { -+ return this.priority > than; -+ } -+ -+ public static boolean isHigherOrEqualPriority(final int priority, final int than) { -+ return priority <= than; -+ } -+ -+ public static boolean isHigherPriority(final int priority, final int than) { -+ return priority < than; -+ } -+ -+ public static boolean isLowerOrEqualPriority(final int priority, final int than) { -+ return priority >= than; -+ } -+ -+ public static boolean isLowerPriority(final int priority, final int than) { -+ return priority > than; -+ } -+ -+ static final Priority[] PRIORITIES = Priority.values(); -+ -+ /** includes special priorities */ -+ public static final int TOTAL_PRIORITIES = PRIORITIES.length; -+ -+ public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1; -+ -+ public static Priority getPriority(final int priority) { -+ return PRIORITIES[priority + 1]; -+ } -+ -+ private static int priorityCounter; -+ -+ private static int nextCounter() { -+ return priorityCounter++; -+ } -+ -+ public final int priority; -+ -+ Priority() { -+ this(nextCounter()); -+ } -+ -+ Priority(final int priority) { -+ this.priority = priority; -+ } -+ } ++ /** ++ * Generates the next suborder id. ++ * @return The next suborder id. ++ */ ++ public long generateNextSubOrder(); + + /** + * Executes the next available task. + *

-+ * If there is a task with priority {@link PrioritisedExecutor.Priority#BLOCKING} available, then that such task is executed. ++ * If there is a task with priority {@link Priority#BLOCKING} available, then that such task is executed. + *

+ *

-+ * If there is a task with priority {@link PrioritisedExecutor.Priority#IDLE} available then that task is only executed ++ * If there is a task with priority {@link Priority#IDLE} available then that task is only executed + * when there are no other tasks available with a higher priority. + *

+ *

-+ * If there are no tasks that have priority {@link PrioritisedExecutor.Priority#BLOCKING} or {@link PrioritisedExecutor.Priority#IDLE}, then ++ * If there are no tasks that have priority {@link Priority#BLOCKING} or {@link Priority#IDLE}, then + * this function will be biased to execute tasks that have higher priorities. + *

+ * + * @return {@code true} if a task was executed, {@code false} otherwise + * @throws IllegalStateException If the current thread is not allowed to execute a task + */ -+ @Override + public boolean executeTask() throws IllegalStateException; + + /** ++ * Prevent further additions to this executor. Attempts to add after this call has completed (potentially during) will ++ * result in {@link IllegalStateException} being thrown. ++ *

++ * This operation is atomic with respect to other shutdown calls ++ *

++ *

++ * After this call has completed, regardless of return value, this executor will be shutdown. ++ *

++ * ++ * @return {@code true} if the executor was shutdown, {@code false} if it has shut down already ++ * @see #isShutdown() ++ */ ++ public boolean shutdown(); ++ ++ /** ++ * Returns whether this executor has shut down. Effectively, returns whether new tasks will be rejected. ++ * This method does not indicate whether all the tasks scheduled have been executed. ++ * @return Returns whether this executor has shut down. ++ */ ++ public boolean isShutdown(); ++ ++ /** + * Queues or executes a task at {@link Priority#NORMAL} priority. + * @param task The task to run. + * -+ * @throws IllegalStateException If this queue has shutdown. ++ * @throws IllegalStateException If this executor has shutdown. + * @throws NullPointerException If the task is null + * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -+ * associated with the parameter ++ * associated with the parameter + */ -+ public default PrioritisedTask queueRunnable(final Runnable task) { -+ return this.queueRunnable(task, Priority.NORMAL); -+ } ++ public PrioritisedTask queueTask(final Runnable task); + + /** + * Queues or executes a task. @@ -2313,50 +2397,107 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * @param task The task to run. + * @param priority The priority for the task. + * -+ * @throws IllegalStateException If this queue has shutdown. ++ * @throws IllegalStateException If this executor has shutdown. + * @throws NullPointerException If the task is null + * @throws IllegalArgumentException If the priority is invalid. + * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -+ * associated with the parameter ++ * associated with the parameter + */ -+ public PrioritisedTask queueRunnable(final Runnable task, final Priority priority); ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority); + + /** -+ * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}. ++ * Queues or executes a task. + * + * @param task The task to run. ++ * @param priority The priority for the task. ++ * @param subOrder The task's suborder. + * -+ * @throws IllegalStateException If this queue has shutdown. ++ * @throws IllegalStateException If this executor has shutdown. + * @throws NullPointerException If the task is null + * @throws IllegalArgumentException If the priority is invalid. -+ * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks -+ * @return The prioritised task associated with the parameters ++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task ++ * associated with the parameter + */ -+ public default PrioritisedTask createTask(final Runnable task) { -+ return this.createTask(task, Priority.NORMAL); -+ } ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder); + + /** -+ * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}. ++ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. ++ * @param task The task to run. ++ * ++ * @throws NullPointerException If the task is null ++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task ++ * associated with the parameter ++ */ ++ public PrioritisedTask createTask(final Runnable task); ++ ++ /** ++ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. + * + * @param task The task to run. + * @param priority The priority for the task. + * -+ * @throws IllegalStateException If this queue has shutdown. + * @throws NullPointerException If the task is null + * @throws IllegalArgumentException If the priority is invalid. -+ * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks -+ * @return The prioritised task associated with the parameters ++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task ++ * associated with the parameter + */ + public PrioritisedTask createTask(final Runnable task, final Priority priority); + + /** -+ * Extension of {@link ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask} which adds functions -+ * to retrieve and modify the task's associated priority. ++ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. + * -+ * @see ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask ++ * @param task The task to run. ++ * @param priority The priority for the task. ++ * @param subOrder The task's suborder. ++ * ++ * @throws NullPointerException If the task is null ++ * @throws IllegalArgumentException If the priority is invalid. ++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task ++ * associated with the parameter + */ -+ public static interface PrioritisedTask extends BaseTask { ++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder); ++ ++ public static interface PrioritisedTask extends Cancellable { ++ ++ /** ++ * Returns the executor associated with this task. ++ * @return The executor associated with this task. ++ */ ++ public PrioritisedExecutor getExecutor(); ++ ++ /** ++ * Causes a lazily queued task to become queued or executed ++ * ++ * @throws IllegalStateException If the backing executor has shutdown ++ * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed ++ */ ++ public boolean queue(); ++ ++ /** ++ * Returns whether this task has been queued and is not completing. ++ * @return {@code true} If the task has been queued, {@code false} if the task has not been queued or is marked ++ * as completing. ++ */ ++ public boolean isQueued(); ++ ++ /** ++ * Forces this task to be marked as completed. ++ * ++ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed ++ * or is being completed. ++ */ ++ @Override ++ public boolean cancel(); ++ ++ /** ++ * Executes this task. This will also mark the task as completing. ++ *

++ * Exceptions thrown from the runnable will be rethrown. ++ *

++ * ++ * @return {@code true} if this task was executed, {@code false} if it was already marked as completed. ++ */ ++ public boolean execute(); + + /** + * Returns the current priority. Note that {@link Priority#COMPLETING} will be returned @@ -2371,7 +2512,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * + * @throws IllegalArgumentException If the priority is invalid + * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue -+ * this task was scheduled on was shutdown, or if the priority was already at the specified level. ++ * this task was scheduled on was shutdown, or if the priority was already at the specified level. + */ + public boolean setPriority(final Priority priority); + @@ -2381,7 +2522,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * @param priority Priority specified + * + * @throws IllegalArgumentException If the priority is invalid -+ * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the specified level or was already at the specified level or higher. ++ * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the ++ * specified level or was already at the specified level or higher. + */ + public boolean raisePriority(final Priority priority); + @@ -2391,20 +2533,532 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * @param priority Priority specified + * + * @throws IllegalArgumentException If the priority is invalid -+ * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the specified level or was already at the specified level or lower. ++ * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the ++ * specified level or was already at the specified level or lower. + */ + public boolean lowerPriority(final Priority priority); ++ ++ /** ++ * Returns the suborder id associated with this task. ++ * @return The suborder id associated with this task. ++ */ ++ public long getSubOrder(); ++ ++ /** ++ * Sets the suborder id associated with this task. Ths function has no effect when this task ++ * is completing or is completed. ++ * ++ * @param subOrder Specified new sub order. ++ * ++ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue ++ * this task was scheduled on was shutdown, or if the current suborder is the same as the new sub order. ++ */ ++ public boolean setSubOrder(final long subOrder); ++ ++ /** ++ * Attempts to raise the suborder to the suborder specified. ++ * ++ * @param subOrder Specified new sub order. ++ * ++ * @return {@code false} if the current task is completing, {@code true} if the suborder was raised to the ++ * specified suborder or was already at the specified suborder or higher. ++ */ ++ public boolean raiseSubOrder(final long subOrder); ++ ++ /** ++ * Attempts to lower the suborder to the suborder specified. ++ * ++ * @param subOrder Specified new sub order. ++ * ++ * @return {@code false} if the current task is completing, {@code true} if the suborder was lowered to the ++ * specified suborder or was already at the specified suborder or lower. ++ */ ++ public boolean lowerSubOrder(final long subOrder); ++ ++ /** ++ * Sets the priority and suborder id associated with this task. Ths function has no effect when this task ++ * is completing or is completed. ++ * ++ * @param priority Priority specified ++ * @param subOrder Specified new sub order. ++ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue ++ * this task was scheduled on was shutdown, or if the current priority and suborder are the same as ++ * the parameters. ++ */ ++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder); + } +} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java ++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java @@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.executor.standard; ++package ca.spottedleaf.concurrentutil.executor.queue; + ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import java.lang.invoke.VarHandle; ++import java.util.Comparator; ++import java.util.Map; ++import java.util.concurrent.ConcurrentSkipListMap; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.atomic.AtomicLong; ++ ++public final class PrioritisedTaskQueue implements PrioritisedExecutor { ++ ++ /** ++ * Required for tie-breaking in the queue ++ */ ++ private final AtomicLong taskIdGenerator = new AtomicLong(); ++ private final AtomicLong scheduledTasks = new AtomicLong(); ++ private final AtomicLong executedTasks = new AtomicLong(); ++ private final AtomicLong subOrderGenerator = new AtomicLong(); ++ private final AtomicBoolean shutdown = new AtomicBoolean(); ++ private final ConcurrentSkipListMap tasks = new ConcurrentSkipListMap<>(PrioritisedQueuedTask.COMPARATOR); ++ ++ @Override ++ public long getTotalTasksScheduled() { ++ return this.scheduledTasks.get(); ++ } ++ ++ @Override ++ public long getTotalTasksExecuted() { ++ return this.executedTasks.get(); ++ } ++ ++ @Override ++ public long generateNextSubOrder() { ++ return this.subOrderGenerator.getAndIncrement(); ++ } ++ ++ @Override ++ public boolean shutdown() { ++ return !this.shutdown.getAndSet(true); ++ } ++ ++ @Override ++ public boolean isShutdown() { ++ return this.shutdown.get(); ++ } ++ ++ public PrioritisedTask peekFirst() { ++ final Map.Entry firstEntry = this.tasks.firstEntry(); ++ return firstEntry == null ? null : firstEntry.getKey().task; ++ } ++ ++ public Priority getHighestPriority() { ++ final Map.Entry firstEntry = this.tasks.firstEntry(); ++ return firstEntry == null ? null : Priority.getPriority(firstEntry.getKey().priority); ++ } ++ ++ public boolean hasNoScheduledTasks() { ++ final long executedTasks = this.executedTasks.get(); ++ final long scheduledTasks = this.scheduledTasks.get(); ++ ++ return executedTasks == scheduledTasks; ++ } ++ ++ public PrioritySubOrderPair getHighestPrioritySubOrder() { ++ final Map.Entry firstEntry = this.tasks.firstEntry(); ++ if (firstEntry == null) { ++ return null; ++ } ++ ++ final PrioritisedQueuedTask.Holder holder = firstEntry.getKey(); ++ ++ return new PrioritySubOrderPair(Priority.getPriority(holder.priority), holder.subOrder); ++ } ++ ++ public Runnable pollTask() { ++ for (;;) { ++ final Map.Entry firstEntry = this.tasks.pollFirstEntry(); ++ if (firstEntry != null) { ++ final PrioritisedQueuedTask.Holder task = firstEntry.getKey(); ++ task.markRemoved(); ++ if (!task.task.cancel()) { ++ continue; ++ } ++ return task.task.execute; ++ } ++ ++ return null; ++ } ++ } ++ ++ @Override ++ public boolean executeTask() { ++ for (;;) { ++ final Map.Entry firstEntry = this.tasks.pollFirstEntry(); ++ if (firstEntry != null) { ++ final PrioritisedQueuedTask.Holder task = firstEntry.getKey(); ++ task.markRemoved(); ++ if (!task.task.execute()) { ++ continue; ++ } ++ return true; ++ } ++ ++ return false; ++ } ++ } ++ ++ @Override ++ public PrioritisedTask createTask(final Runnable task) { ++ return this.createTask(task, Priority.NORMAL, this.generateNextSubOrder()); ++ } ++ ++ @Override ++ public PrioritisedTask createTask(final Runnable task, final Priority priority) { ++ return this.createTask(task, priority, this.generateNextSubOrder()); ++ } ++ ++ @Override ++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { ++ return new PrioritisedQueuedTask(task, priority, subOrder); ++ } ++ ++ @Override ++ public PrioritisedTask queueTask(final Runnable task) { ++ return this.queueTask(task, Priority.NORMAL, this.generateNextSubOrder()); ++ } ++ ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority) { ++ return this.queueTask(task, priority, this.generateNextSubOrder()); ++ } ++ ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { ++ final PrioritisedQueuedTask ret = new PrioritisedQueuedTask(task, priority, subOrder); ++ ++ ret.queue(); ++ ++ return ret; ++ } ++ ++ private final class PrioritisedQueuedTask implements PrioritisedExecutor.PrioritisedTask { ++ public static final Comparator COMPARATOR = (final PrioritisedQueuedTask.Holder t1, final PrioritisedQueuedTask.Holder t2) -> { ++ final int priorityCompare = t1.priority - t2.priority; ++ if (priorityCompare != 0) { ++ return priorityCompare; ++ } ++ ++ final int subOrderCompare = Long.compare(t1.subOrder, t2.subOrder); ++ if (subOrderCompare != 0) { ++ return subOrderCompare; ++ } ++ ++ return Long.compare(t1.id, t2.id); ++ }; ++ ++ private static final class Holder { ++ private final PrioritisedQueuedTask task; ++ private final int priority; ++ private final long subOrder; ++ private final long id; ++ ++ private volatile boolean removed; ++ private static final VarHandle REMOVED_HANDLE = ConcurrentUtil.getVarHandle(Holder.class, "removed", boolean.class); ++ ++ private Holder(final PrioritisedQueuedTask task, final int priority, final long subOrder, ++ final long id) { ++ this.task = task; ++ this.priority = priority; ++ this.subOrder = subOrder; ++ this.id = id; ++ } ++ ++ /** ++ * Returns true if marked as removed ++ */ ++ public boolean markRemoved() { ++ return !(boolean)REMOVED_HANDLE.getAndSet((Holder)this, (boolean)true); ++ } ++ } ++ ++ private final long id; ++ private final Runnable execute; ++ ++ private Priority priority; ++ private long subOrder; ++ private Holder holder; ++ ++ public PrioritisedQueuedTask(final Runnable execute, final Priority priority, final long subOrder) { ++ if (!Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ ++ this.execute = execute; ++ this.priority = priority; ++ this.subOrder = subOrder; ++ this.id = PrioritisedTaskQueue.this.taskIdGenerator.getAndIncrement(); ++ } ++ ++ @Override ++ public PrioritisedExecutor getExecutor() { ++ return PrioritisedTaskQueue.this; ++ } ++ ++ @Override ++ public boolean queue() { ++ synchronized (this) { ++ if (this.holder != null || this.priority == Priority.COMPLETING) { ++ return false; ++ } ++ ++ if (PrioritisedTaskQueue.this.isShutdown()) { ++ throw new IllegalStateException("Queue is shutdown"); ++ } ++ ++ final Holder holder = new Holder(this, this.priority.priority, this.subOrder, this.id); ++ this.holder = holder; ++ ++ PrioritisedTaskQueue.this.scheduledTasks.getAndIncrement(); ++ PrioritisedTaskQueue.this.tasks.put(holder, Boolean.TRUE); ++ } ++ ++ if (PrioritisedTaskQueue.this.isShutdown()) { ++ this.cancel(); ++ throw new IllegalStateException("Queue is shutdown"); ++ } ++ ++ ++ return true; ++ } ++ ++ @Override ++ public boolean isQueued() { ++ synchronized (this) { ++ return this.holder != null && this.priority != Priority.COMPLETING; ++ } ++ } ++ ++ @Override ++ public boolean cancel() { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING) { ++ return false; ++ } ++ ++ this.priority = Priority.COMPLETING; ++ ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ PrioritisedTaskQueue.this.executedTasks.getAndIncrement(); ++ } ++ ++ return true; ++ } ++ } ++ ++ @Override ++ public boolean execute() { ++ final boolean increaseExecuted; ++ ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING) { ++ return false; ++ } ++ ++ this.priority = Priority.COMPLETING; ++ ++ if (increaseExecuted = (this.holder != null)) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ } ++ } ++ ++ try { ++ this.execute.run(); ++ return true; ++ } finally { ++ if (increaseExecuted) { ++ PrioritisedTaskQueue.this.executedTasks.getAndIncrement(); ++ } ++ } ++ } ++ ++ @Override ++ public Priority getPriority() { ++ synchronized (this) { ++ return this.priority; ++ } ++ } ++ ++ @Override ++ public boolean setPriority(final Priority priority) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.priority == priority) { ++ return false; ++ } ++ ++ this.priority = priority; ++ ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } ++ ++ return true; ++ } ++ } ++ ++ @Override ++ public boolean raisePriority(final Priority priority) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.priority.isHigherOrEqualPriority(priority)) { ++ return false; ++ } ++ ++ this.priority = priority; ++ ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } ++ ++ return true; ++ } ++ } ++ ++ @Override ++ public boolean lowerPriority(Priority priority) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.priority.isLowerOrEqualPriority(priority)) { ++ return false; ++ } ++ ++ this.priority = priority; ++ ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } ++ ++ return true; ++ } ++ } ++ ++ @Override ++ public long getSubOrder() { ++ synchronized (this) { ++ return this.subOrder; ++ } ++ } ++ ++ @Override ++ public boolean setSubOrder(final long subOrder) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.subOrder == subOrder) { ++ return false; ++ } ++ ++ this.subOrder = subOrder; ++ ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } ++ ++ return true; ++ } ++ } ++ ++ @Override ++ public boolean raiseSubOrder(long subOrder) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.subOrder >= subOrder) { ++ return false; ++ } ++ ++ this.subOrder = subOrder; ++ ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } ++ ++ return true; ++ } ++ } ++ ++ @Override ++ public boolean lowerSubOrder(final long subOrder) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.subOrder <= subOrder) { ++ return false; ++ } ++ ++ this.subOrder = subOrder; ++ ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } ++ ++ return true; ++ } ++ } ++ ++ @Override ++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || (this.priority == priority && this.subOrder == subOrder)) { ++ return false; ++ } ++ ++ this.priority = priority; ++ this.subOrder = subOrder; ++ ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } ++ ++ return true; ++ } ++ } ++ } ++ ++ public static record PrioritySubOrderPair(Priority priority, long subOrder) {} ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.concurrentutil.executor.thread; ++ ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.lang.invoke.VarHandle; @@ -2415,8 +3069,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + *

+ * Note: When using this thread, queue additions to the underlying {@link #queue} are not sufficient to get this thread + * to execute the task. The function {@link #notifyTasks()} must be used after scheduling a task. For expected behaviour -+ * of task scheduling (thread wakes up after tasks are scheduled), use the methods provided on {@link PrioritisedExecutor} -+ * methods. ++ * of task scheduling, use the methods provided on this class to schedule tasks. + *

+ */ +public class PrioritisedQueueExecutorThread extends Thread implements PrioritisedExecutor { @@ -2434,7 +3087,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + protected final long spinWaitTime; + -+ static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms ++ protected static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms + + public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue) { + this(queue, DEFAULT_SPINWAIT_TIME); // 0.1ms @@ -2446,7 +3099,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override -+ public void run() { ++ public final void run() { ++ try { ++ this.begin(); ++ this.doRun(); ++ } finally { ++ this.die(); ++ } ++ } ++ ++ public final void doRun() { + final long spinWaitTime = this.spinWaitTime; + + main_loop: @@ -2484,7 +3146,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.setThreadParkedVolatile(true); + + // We need to parse here to avoid a race condition where a thread queues a task before we set parked to true -+ // (i.e it will not notify us) ++ // (i.e. it will not notify us) + if (this.pollTasks()) { + this.setThreadParkedVolatile(false); + continue; @@ -2503,6 +3165,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + ++ protected void begin() {} ++ ++ protected void die() {} ++ + /** + * Attempts to poll as many tasks as possible, returning when finished. + * @return Whether any tasks were executed. @@ -2519,8 +3185,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + break; + } + ret = true; -+ } catch (final ThreadDeath death) { -+ throw death; // goodbye world... + } catch (final Throwable throwable) { + LOGGER.error("Exception thrown from prioritized runnable task in thread '" + this.getName() + "'", throwable); + } @@ -2550,67 +3214,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override -+ public PrioritisedTask createTask(final Runnable task, final Priority priority) { -+ final PrioritisedTask queueTask = this.queue.createTask(task, priority); -+ -+ // need to override queue() to notify us of tasks -+ return new PrioritisedTask() { -+ @Override -+ public Priority getPriority() { -+ return queueTask.getPriority(); -+ } -+ -+ @Override -+ public boolean setPriority(final Priority priority) { -+ return queueTask.setPriority(priority); -+ } -+ -+ @Override -+ public boolean raisePriority(final Priority priority) { -+ return queueTask.raisePriority(priority); -+ } -+ -+ @Override -+ public boolean lowerPriority(final Priority priority) { -+ return queueTask.lowerPriority(priority); -+ } -+ -+ @Override -+ public boolean queue() { -+ final boolean ret = queueTask.queue(); -+ if (ret) { -+ PrioritisedQueueExecutorThread.this.notifyTasks(); -+ } -+ return ret; -+ } -+ -+ @Override -+ public boolean cancel() { -+ return queueTask.cancel(); -+ } -+ -+ @Override -+ public boolean execute() { -+ return queueTask.execute(); -+ } -+ }; -+ } -+ -+ @Override -+ public PrioritisedTask queueRunnable(final Runnable task, final Priority priority) { -+ final PrioritisedTask ret = this.queue.queueRunnable(task, priority); -+ -+ this.notifyTasks(); -+ -+ return ret; -+ } -+ -+ @Override -+ public boolean haveAllTasksExecuted() { -+ return this.queue.haveAllTasksExecuted(); -+ } -+ -+ @Override + public long getTotalTasksExecuted() { + return this.queue.getTotalTasksExecuted(); + } @@ -2620,16 +3223,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.queue.getTotalTasksScheduled(); + } + -+ /** -+ * {@inheritDoc} -+ * @throws IllegalStateException If the current thread is {@code this} thread, or the underlying queue throws this exception. -+ */ + @Override -+ public void waitUntilAllExecuted() throws IllegalStateException { -+ if (Thread.currentThread() == this) { -+ throw new IllegalStateException("Cannot block on our own queue"); -+ } -+ this.queue.waitUntilAllExecuted(); ++ public long generateNextSubOrder() { ++ return this.queue.generateNextSubOrder(); ++ } ++ ++ @Override ++ public boolean shutdown() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean isShutdown() { ++ return false; + } + + /** @@ -2641,12 +3247,61 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + throw new IllegalStateException(); + } + ++ @Override ++ public PrioritisedTask queueTask(final Runnable task) { ++ final PrioritisedTask ret = this.createTask(task); ++ ++ ret.queue(); ++ ++ return ret; ++ } ++ ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority) { ++ final PrioritisedTask ret = this.createTask(task, priority); ++ ++ ret.queue(); ++ ++ return ret; ++ } ++ ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { ++ final PrioritisedTask ret = this.createTask(task, priority, subOrder); ++ ++ ret.queue(); ++ ++ return ret; ++ } ++ ++ ++ @Override ++ public PrioritisedTask createTask(Runnable task) { ++ final PrioritisedTask queueTask = this.queue.createTask(task); ++ ++ return new WrappedTask(queueTask); ++ } ++ ++ @Override ++ public PrioritisedTask createTask(final Runnable task, final Priority priority) { ++ final PrioritisedTask queueTask = this.queue.createTask(task, priority); ++ ++ return new WrappedTask(queueTask); ++ } ++ ++ @Override ++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { ++ final PrioritisedTask queueTask = this.queue.createTask(task, priority, subOrder); ++ ++ return new WrappedTask(queueTask); ++ } ++ + /** + * Closes this queue executor's queue. Optionally waits for all tasks in queue to be executed if {@code wait} is true. + *

+ * This function is MT-Safe. + *

-+ * @param wait If this call is to wait until the queue is empty and there are no tasks executing in the queue. ++ * @param wait If this call is to wait until this thread shuts down. + * @param killQueue Whether to shutdown this thread's queue + * @return whether this thread shut down the queue + * @see #halt(boolean) @@ -2660,7 +3315,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + LockSupport.unpark(this); + + if (wait) { -+ this.waitUntilAllExecuted(); ++ boolean interrupted = false; ++ for (;;) { ++ if (this.isAlive()) { ++ if (interrupted) { ++ Thread.currentThread().interrupt(); ++ } ++ break; ++ } ++ try { ++ this.join(); ++ } catch (final InterruptedException ex) { ++ interrupted = true; ++ } ++ } + } + + return ret; @@ -2702,157 +3370,163 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + protected final void setThreadParkedVolatile(final boolean value) { + THREAD_PARKED_HANDLE.setVolatile(this, value); + } ++ ++ /** ++ * Required so that queue() can notify (unpark) this thread ++ */ ++ private final class WrappedTask implements PrioritisedTask { ++ private final PrioritisedTask queueTask; ++ ++ public WrappedTask(final PrioritisedTask queueTask) { ++ this.queueTask = queueTask; ++ } ++ ++ @Override ++ public PrioritisedExecutor getExecutor() { ++ return PrioritisedQueueExecutorThread.this; ++ } ++ ++ @Override ++ public boolean queue() { ++ final boolean ret = this.queueTask.queue(); ++ if (ret) { ++ PrioritisedQueueExecutorThread.this.notifyTasks(); ++ } ++ return ret; ++ } ++ ++ @Override ++ public boolean isQueued() { ++ return this.queueTask.isQueued(); ++ } ++ ++ @Override ++ public boolean cancel() { ++ return this.queueTask.cancel(); ++ } ++ ++ @Override ++ public boolean execute() { ++ return this.queueTask.execute(); ++ } ++ ++ @Override ++ public Priority getPriority() { ++ return this.queueTask.getPriority(); ++ } ++ ++ @Override ++ public boolean setPriority(final Priority priority) { ++ return this.queueTask.setPriority(priority); ++ } ++ ++ @Override ++ public boolean raisePriority(final Priority priority) { ++ return this.queueTask.raisePriority(priority); ++ } ++ ++ @Override ++ public boolean lowerPriority(final Priority priority) { ++ return this.queueTask.lowerPriority(priority); ++ } ++ ++ @Override ++ public long getSubOrder() { ++ return this.queueTask.getSubOrder(); ++ } ++ ++ @Override ++ public boolean setSubOrder(final long subOrder) { ++ return this.queueTask.setSubOrder(subOrder); ++ } ++ ++ @Override ++ public boolean raiseSubOrder(final long subOrder) { ++ return this.queueTask.raiseSubOrder(subOrder); ++ } ++ ++ @Override ++ public boolean lowerSubOrder(final long subOrder) { ++ return this.queueTask.lowerSubOrder(subOrder); ++ } ++ ++ @Override ++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { ++ return this.queueTask.setPriorityAndSubOrder(priority, subOrder); ++ } ++ } +} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java ++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java @@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.executor.standard; ++package ca.spottedleaf.concurrentutil.executor.thread; + -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.queue.PrioritisedTaskQueue; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.concurrentutil.util.TimeUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -+import java.util.ArrayList; ++import java.lang.reflect.Array; +import java.util.Arrays; -+import java.util.Comparator; -+import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.function.BiConsumer; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.function.Consumer; + +public final class PrioritisedThreadPool { + + private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedThreadPool.class); + -+ private final PrioritisedThread[] threads; -+ private final TreeSet queues = new TreeSet<>(PrioritisedPoolExecutorImpl.comparator()); -+ private final String name; -+ private final long queueMaxHoldTime; ++ private final Consumer threadModifier; ++ private final COWArrayList executors = new COWArrayList<>(ExecutorGroup.class); ++ private final COWArrayList threads = new COWArrayList<>(PrioritisedThread.class); ++ private final COWArrayList aliveThreads = new COWArrayList<>(PrioritisedThread.class); + -+ private final ReferenceOpenHashSet nonShutdownQueues = new ReferenceOpenHashSet<>(); -+ private final ReferenceOpenHashSet activeQueues = new ReferenceOpenHashSet<>(); ++ private static final Priority HIGH_PRIORITY_NOTIFY_THRESHOLD = Priority.HIGH; ++ private static final Priority QUEUE_SHUTDOWN_PRIORITY = Priority.HIGH; + + private boolean shutdown; + -+ private long schedulingIdGenerator; ++ public PrioritisedThreadPool(final Consumer threadModifier) { ++ this.threadModifier = threadModifier; + -+ private static final long DEFAULT_QUEUE_HOLD_TIME = (long)(5.0e6); -+ -+ /** -+ * @param name Specified debug name of this thread pool -+ * @param threads The number of threads to use -+ */ -+ public PrioritisedThreadPool(final String name, final int threads) { -+ this(name, threads, null); -+ } -+ -+ /** -+ * @param name Specified debug name of this thread pool -+ * @param threads The number of threads to use -+ * @param threadModifier Invoked for each created thread with its incremental id before starting them -+ */ -+ public PrioritisedThreadPool(final String name, final int threads, final BiConsumer threadModifier) { -+ this(name, threads, threadModifier, DEFAULT_QUEUE_HOLD_TIME); // 5ms -+ } -+ -+ /** -+ * @param name Specified debug name of this thread pool -+ * @param threads The number of threads to use -+ * @param threadModifier Invoked for each created thread with its incremental id before starting them -+ * @param queueHoldTime The maximum amount of time to spend executing tasks in a specific queue before attempting -+ * to switch to another queue, per thread -+ */ -+ public PrioritisedThreadPool(final String name, final int threads, final BiConsumer threadModifier, -+ final long queueHoldTime) { // in ns -+ if (threads <= 0) { -+ throw new IllegalArgumentException("Thread count must be > 0, not " + threads); -+ } -+ if (name == null) { -+ throw new IllegalArgumentException("Name cannot be null"); -+ } -+ this.name = name; -+ this.queueMaxHoldTime = queueHoldTime; -+ -+ this.threads = new PrioritisedThread[threads]; -+ for (int i = 0; i < threads; ++i) { -+ this.threads[i] = new PrioritisedThread(this); -+ -+ // set default attributes -+ this.threads[i].setName("Prioritised thread for pool '" + name + "' #" + i); -+ this.threads[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { -+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); -+ }); -+ -+ // let thread modifier override defaults -+ if (threadModifier != null) { -+ threadModifier.accept(this.threads[i], Integer.valueOf(i)); -+ } -+ -+ // now the thread can start -+ this.threads[i].start(); ++ if (threadModifier == null) { ++ throw new NullPointerException("Thread factory may not be null"); + } + } + -+ /** -+ * Returns an array representing the threads backing this thread pool. -+ */ -+ public Thread[] getThreads() { -+ return Arrays.copyOf(this.threads, this.threads.length, Thread[].class); ++ public Thread[] getAliveThreads() { ++ final PrioritisedThread[] threads = this.aliveThreads.getArray(); ++ ++ return Arrays.copyOf(threads, threads.length, Thread[].class); + } + -+ /** -+ * Creates and returns a {@link PrioritisedPoolExecutor} to schedule tasks onto. The returned executor will execute -+ * tasks on this thread pool only. -+ * @param name The debug name of the executor. -+ * @param minParallelism The minimum number of threads to be executing tasks from the returned executor -+ * before threads may be allocated to other queues in this thread pool. -+ * @param parallelism The maximum number of threads which may be executing tasks from the returned executor. -+ * @throws IllegalStateException If this thread pool is shut down -+ */ -+ public PrioritisedPoolExecutor createExecutor(final String name, final int minParallelism, final int parallelism) { -+ synchronized (this.nonShutdownQueues) { -+ if (this.shutdown) { -+ throw new IllegalStateException("Queue is shutdown: " + this.toString()); -+ } -+ final PrioritisedPoolExecutorImpl ret = new PrioritisedPoolExecutorImpl( -+ this, name, -+ Math.min(Math.max(1, parallelism), this.threads.length), -+ Math.min(Math.max(0, minParallelism), this.threads.length) -+ ); ++ public Thread[] getCoreThreads() { ++ final PrioritisedThread[] threads = this.threads.getArray(); + -+ this.nonShutdownQueues.add(ret); -+ -+ synchronized (this.activeQueues) { -+ this.activeQueues.add(ret); -+ } -+ -+ return ret; -+ } ++ return Arrays.copyOf(threads, threads.length, Thread[].class); + } + + /** + * Prevents creation of new queues, shutdowns all non-shutdown queues if specified + */ + public void halt(final boolean shutdownQueues) { -+ synchronized (this.nonShutdownQueues) { ++ synchronized (this) { + this.shutdown = true; + } -+ if (shutdownQueues) { -+ final ArrayList queuesToShutdown; -+ synchronized (this.nonShutdownQueues) { -+ this.shutdown = true; -+ queuesToShutdown = new ArrayList<>(this.nonShutdownQueues); -+ } + -+ for (final PrioritisedPoolExecutorImpl queue : queuesToShutdown) { -+ queue.shutdown(); ++ if (shutdownQueues) { ++ for (final ExecutorGroup group : this.executors.getArray()) { ++ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) { ++ executor.shutdown(); ++ } + } + } + -+ -+ for (final PrioritisedThread thread : this.threads) { -+ // can't kill queue, queue is null ++ for (final PrioritisedThread thread : this.threads.getArray()) { + thread.halt(false); + } + } @@ -2886,18 +3560,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + final long deadline = start + nsToWait; + boolean interrupted = false; + try { -+ for (final PrioritisedThread thread : this.threads) { ++ for (final PrioritisedThread thread : this.aliveThreads.getArray()) { + for (;;) { + if (!thread.isAlive()) { + break; + } + final long current = System.nanoTime(); -+ if (current >= deadline) { ++ if (current >= deadline && msToWait > 0L) { + return false; + } + + try { -+ thread.join(Math.max(1L, (deadline - current) / (1000 * 1000))); ++ thread.join(msToWait <= 0L ? 0L : Math.max(1L, (deadline - current) / (1000 * 1000))); + } catch (final InterruptedException ex) { + if (interruptable) { + throw ex; @@ -2917,45 +3591,189 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + /** + * Shuts down this thread pool, optionally waiting for all tasks to be executed. -+ * This function will invoke {@link PrioritisedPoolExecutor#shutdown()} on all created executors on this ++ * This function will invoke {@link PrioritisedExecutor#shutdown()} on all created executors on this + * thread pool. + * @param wait Whether to wait for tasks to be executed + */ + public void shutdown(final boolean wait) { -+ final ArrayList queuesToShutdown; -+ synchronized (this.nonShutdownQueues) { ++ synchronized (this) { + this.shutdown = true; -+ queuesToShutdown = new ArrayList<>(this.nonShutdownQueues); + } + -+ for (final PrioritisedPoolExecutorImpl queue : queuesToShutdown) { -+ queue.shutdown(); ++ for (final ExecutorGroup group : this.executors.getArray()) { ++ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) { ++ executor.shutdown(); ++ } + } + -+ for (final PrioritisedThread thread : this.threads) { ++ ++ for (final PrioritisedThread thread : this.threads.getArray()) { + // none of these can be true or else NPE + thread.close(false, false); + } + + if (wait) { -+ final ArrayList queues; -+ synchronized (this.activeQueues) { -+ queues = new ArrayList<>(this.activeQueues); ++ this.join(0L); ++ } ++ } ++ ++ private void die(final PrioritisedThread thread) { ++ this.aliveThreads.remove(thread); ++ } ++ ++ public void adjustThreadCount(final int threads) { ++ synchronized (this) { ++ if (this.shutdown) { ++ return; + } -+ for (final PrioritisedPoolExecutorImpl queue : queues) { -+ queue.waitUntilAllExecuted(); ++ ++ final PrioritisedThread[] currentThreads = this.threads.getArray(); ++ if (threads == currentThreads.length) { ++ // no adjustment needed ++ return; ++ } ++ ++ if (threads < currentThreads.length) { ++ // we need to trim threads ++ for (int i = 0, difference = currentThreads.length - threads; i < difference; ++i) { ++ final PrioritisedThread remove = currentThreads[currentThreads.length - i - 1]; ++ ++ remove.halt(false); ++ this.threads.remove(remove); ++ } ++ } else { ++ // we need to add threads ++ for (int i = 0, difference = threads - currentThreads.length; i < difference; ++i) { ++ final PrioritisedThread thread = new PrioritisedThread(); ++ ++ this.threadModifier.accept(thread); ++ this.aliveThreads.add(thread); ++ this.threads.add(thread); ++ ++ thread.start(); ++ } + } + } + } + -+ protected static final class PrioritisedThread extends PrioritisedQueueExecutorThread { ++ private static int compareInsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority, ++ final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) { ++ final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal(); ++ if (priorityCompare != 0) { ++ return priorityCompare; ++ } + -+ protected final PrioritisedThreadPool pool; -+ protected final AtomicBoolean alertedHighPriority = new AtomicBoolean(); ++ final int parallelismCompare = src.currentParallelism - dst.currentParallelism; ++ if (parallelismCompare != 0) { ++ return parallelismCompare; ++ } + -+ public PrioritisedThread(final PrioritisedThreadPool pool) { ++ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved); ++ } ++ ++ private static int compareOutsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority, ++ final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) { ++ if (src.getGroup().division == dst.getGroup().division) { ++ // can only compare priorities inside the same division ++ final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal(); ++ if (priorityCompare != 0) { ++ return priorityCompare; ++ } ++ } ++ ++ final int parallelismCompare = src.getGroup().currentParallelism - dst.getGroup().currentParallelism; ++ if (parallelismCompare != 0) { ++ return parallelismCompare; ++ } ++ ++ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved); ++ } ++ ++ private ExecutorGroup.ThreadPoolExecutor obtainQueue() { ++ final long time = System.nanoTime(); ++ synchronized (this) { ++ ExecutorGroup.ThreadPoolExecutor ret = null; ++ Priority retPriority = null; ++ ++ for (final ExecutorGroup executorGroup : this.executors.getArray()) { ++ ExecutorGroup.ThreadPoolExecutor highest = null; ++ Priority highestPriority = null; ++ for (final ExecutorGroup.ThreadPoolExecutor executor : executorGroup.executors.getArray()) { ++ final int maxParallelism = executor.maxParallelism; ++ if (maxParallelism > 0 && executor.currentParallelism >= maxParallelism) { ++ continue; ++ } ++ ++ final Priority priority = executor.getTargetPriority(); ++ ++ if (priority == null) { ++ continue; ++ } ++ ++ if (highestPriority == null || compareInsideGroup(highest, highestPriority, executor, priority) > 0) { ++ highest = executor; ++ highestPriority = priority; ++ } ++ } ++ ++ if (highest == null) { ++ continue; ++ } ++ ++ if (ret == null || compareOutsideGroup(ret, retPriority, highest, highestPriority) > 0) { ++ ret = highest; ++ retPriority = highestPriority; ++ } ++ } ++ ++ if (ret != null) { ++ ret.lastRetrieved = time; ++ ++ret.currentParallelism; ++ ++ret.getGroup().currentParallelism; ++ return ret; ++ } ++ ++ return ret; ++ } ++ } ++ ++ private void returnQueue(final ExecutorGroup.ThreadPoolExecutor executor) { ++ synchronized (this) { ++ --executor.currentParallelism; ++ --executor.getGroup().currentParallelism; ++ } ++ ++ if (executor.isShutdown() && executor.queue.hasNoScheduledTasks()) { ++ executor.getGroup().executors.remove(executor); ++ } ++ } ++ ++ private void notifyAllThreads() { ++ for (final PrioritisedThread thread : this.threads.getArray()) { ++ thread.notifyTasks(); ++ } ++ } ++ ++ public ExecutorGroup createExecutorGroup(final int division, final int flags) { ++ synchronized (this) { ++ if (this.shutdown) { ++ throw new IllegalStateException("Queue is shutdown: " + this.toString()); ++ } ++ ++ final ExecutorGroup ret = new ExecutorGroup(division, flags); ++ ++ this.executors.add(ret); ++ ++ return ret; ++ } ++ } ++ ++ private final class PrioritisedThread extends PrioritisedQueueExecutorThread { ++ ++ private final AtomicBoolean alertedHighPriority = new AtomicBoolean(); ++ ++ public PrioritisedThread() { + super(null); -+ this.pool = pool; + } + + public boolean alertHighPriorityExecutor() { @@ -2974,753 +3792,413 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override -+ protected boolean pollTasks() { -+ final PrioritisedThreadPool pool = this.pool; -+ final TreeSet queues = this.pool.queues; ++ protected void die() { ++ PrioritisedThreadPool.this.die(this); ++ } + ++ @Override ++ protected boolean pollTasks() { + boolean ret = false; ++ + for (;;) { + if (this.halted) { + break; + } -+ // try to find a queue -+ // note that if and ONLY IF the queues set is empty, this means there are no tasks for us to execute. -+ // so we can only break when it's empty -+ final PrioritisedPoolExecutorImpl queue; -+ // select queue -+ synchronized (queues) { -+ queue = queues.pollFirst(); -+ if (queue == null) { -+ // no tasks to execute -+ break; -+ } + -+ queue.schedulingId = ++pool.schedulingIdGenerator; -+ // we own this queue now, so increment the executor count -+ // do we also need to push this queue up for grabs for another executor? -+ if (++queue.concurrentExecutors < queue.maximumExecutors) { -+ // re-add to queues -+ // it's very important this is done in the same synchronised block for polling, as this prevents -+ // us from possibly later adding a queue that should not exist in the set -+ queues.add(queue); -+ queue.isQueued = true; -+ } else { -+ queue.isQueued = false; -+ } -+ // note: we cannot drain entries from the queue while holding this lock, as it will cause deadlock -+ // the queue addition holds the per-queue lock first then acquires the lock we have now, but if we -+ // try to poll now we don't hold the per queue lock but we do hold the global lock... ++ final ExecutorGroup.ThreadPoolExecutor executor = PrioritisedThreadPool.this.obtainQueue(); ++ if (executor == null) { ++ break; + } -+ -+ // parse tasks as long as we are allowed -+ final long start = System.nanoTime(); -+ final long deadline = start + pool.queueMaxHoldTime; ++ final long deadline = System.nanoTime() + executor.queueMaxHoldTime; + do { + try { -+ if (this.halted) { ++ if (this.halted || executor.halt) { + break; + } -+ if (!queue.executeTask()) { ++ if (!executor.executeTask()) { + // no more tasks, try next queue + break; + } + ret = true; -+ } catch (final ThreadDeath death) { -+ throw death; // goodbye world... + } catch (final Throwable throwable) { -+ LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + queue.toString() + "'", throwable); ++ LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + executor.toString() + "'", throwable); + } + } while (!this.isAlertedHighPriority() && System.nanoTime() <= deadline); + -+ synchronized (queues) { -+ // decrement executors, we are no longer executing -+ if (queue.isQueued) { -+ queues.remove(queue); -+ queue.isQueued = false; -+ } -+ if (--queue.concurrentExecutors == 0 && queue.scheduledPriority == null) { -+ // reset scheduling id once the queue is empty again -+ // this will ensure empty queues are not prioritised suddenly over active queues once tasks are -+ // queued -+ queue.schedulingId = 0L; -+ } -+ -+ // ensure the executor is queued for execution again -+ if (!queue.isHalted && queue.scheduledPriority != null) { // make sure it actually has tasks -+ queues.add(queue); -+ queue.isQueued = true; -+ } -+ } ++ PrioritisedThreadPool.this.returnQueue(executor); + } + ++ + return ret; + } + } + -+ public interface PrioritisedPoolExecutor extends PrioritisedExecutor { ++ public final class ExecutorGroup { + -+ /** -+ * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed -+ */ -+ public void halt(); ++ private final AtomicLong subOrderGenerator = new AtomicLong(); ++ private final COWArrayList executors = new COWArrayList<>(ThreadPoolExecutor.class); + -+ /** -+ * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether -+ * this queue is not halted and not shutdown. -+ */ -+ public boolean isActive(); -+ } ++ private final int division; ++ private int currentParallelism; + -+ protected static final class PrioritisedPoolExecutorImpl extends PrioritisedThreadedTaskQueue implements PrioritisedPoolExecutor { -+ -+ protected final PrioritisedThreadPool pool; -+ protected final long[] priorityCounts = new long[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; -+ protected long schedulingId; -+ protected int concurrentExecutors; -+ protected Priority scheduledPriority; -+ -+ protected final String name; -+ protected final int maximumExecutors; -+ protected final int minimumExecutors; -+ protected boolean isQueued; -+ -+ public PrioritisedPoolExecutorImpl(final PrioritisedThreadPool pool, final String name, final int maximumExecutors, final int minimumExecutors) { -+ this.pool = pool; -+ this.name = name; -+ this.maximumExecutors = maximumExecutors; -+ this.minimumExecutors = minimumExecutors; ++ private ExecutorGroup(final int division, final int flags) { ++ this.division = division; + } + -+ public static Comparator comparator() { -+ return (final PrioritisedPoolExecutorImpl p1, final PrioritisedPoolExecutorImpl p2) -> { -+ if (p1 == p2) { -+ return 0; -+ } -+ -+ final int belowMin1 = p1.minimumExecutors - p1.concurrentExecutors; -+ final int belowMin2 = p2.minimumExecutors - p2.concurrentExecutors; -+ -+ // test minimum executors -+ if (belowMin1 > 0 || belowMin2 > 0) { -+ // want the largest belowMin to be first -+ final int minCompare = Integer.compare(belowMin2, belowMin1); -+ -+ if (minCompare != 0) { -+ return minCompare; -+ } -+ } -+ -+ // prefer higher priority -+ final int priorityCompare = p1.scheduledPriority.ordinal() - p2.scheduledPriority.ordinal(); -+ if (priorityCompare != 0) { -+ return priorityCompare; -+ } -+ -+ // try to spread out the executors so that each can have threads executing -+ final int executorCompare = p1.concurrentExecutors - p2.concurrentExecutors; -+ if (executorCompare != 0) { -+ return executorCompare; -+ } -+ -+ // if all else fails here we just choose whichever executor was queued first -+ return Long.compare(p1.schedulingId, p2.schedulingId); -+ }; ++ public ThreadPoolExecutor[] getAllExecutors() { ++ return this.executors.getArray().clone(); + } + -+ private boolean isHalted; ++ private PrioritisedThreadPool getThreadPool() { ++ return PrioritisedThreadPool.this; ++ } + -+ @Override -+ public void halt() { -+ final PrioritisedThreadPool pool = this.pool; -+ final TreeSet queues = pool.queues; -+ synchronized (queues) { -+ if (this.isHalted) { ++ public ThreadPoolExecutor createExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) { ++ synchronized (PrioritisedThreadPool.this) { ++ if (PrioritisedThreadPool.this.shutdown) { ++ throw new IllegalStateException("Queue is shutdown: " + PrioritisedThreadPool.this.toString()); ++ } ++ ++ final ThreadPoolExecutor ret = new ThreadPoolExecutor(maxParallelism, queueMaxHoldTime, flags); ++ ++ this.executors.add(ret); ++ ++ return ret; ++ } ++ } ++ ++ public final class ThreadPoolExecutor implements PrioritisedExecutor { ++ ++ private final PrioritisedTaskQueue queue = new PrioritisedTaskQueue(); ++ ++ private volatile int maxParallelism; ++ private final long queueMaxHoldTime; ++ private volatile int currentParallelism; ++ private volatile boolean halt; ++ private long lastRetrieved = System.nanoTime(); ++ ++ private ThreadPoolExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) { ++ this.maxParallelism = maxParallelism; ++ this.queueMaxHoldTime = queueMaxHoldTime; ++ } ++ ++ private ExecutorGroup getGroup() { ++ return ExecutorGroup.this; ++ } ++ ++ private boolean canNotify() { ++ if (this.halt) { ++ return false; ++ } ++ ++ final int max = this.maxParallelism; ++ return max < 0 || this.currentParallelism < max; ++ } ++ ++ private void notifyHighPriority() { ++ if (!this.canNotify()) { + return; + } -+ this.isHalted = true; -+ if (this.isQueued) { -+ queues.remove(this); -+ this.isQueued = false; ++ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) { ++ if (thread.alertHighPriorityExecutor()) { ++ return; ++ } + } + } -+ synchronized (pool.nonShutdownQueues) { -+ pool.nonShutdownQueues.remove(this); -+ } -+ synchronized (pool.activeQueues) { -+ pool.activeQueues.remove(this); -+ } -+ } + -+ @Override -+ public boolean isActive() { -+ final PrioritisedThreadPool pool = this.pool; -+ final TreeSet queues = pool.queues; -+ -+ synchronized (queues) { -+ if (this.concurrentExecutors != 0) { -+ return true; ++ private void notifyScheduled() { ++ if (!this.canNotify()) { ++ return; + } -+ synchronized (pool.activeQueues) { -+ if (pool.activeQueues.contains(this)) { ++ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) { ++ if (thread.notifyTasks()) { ++ return; ++ } ++ } ++ } ++ ++ /** ++ * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed ++ */ ++ public void halt() { ++ this.halt = true; ++ ++ ExecutorGroup.this.executors.remove(this); ++ } ++ ++ /** ++ * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether ++ * this queue is not halted and not shutdown. ++ */ ++ public boolean isActive() { ++ if (this.halt) { ++ return this.currentParallelism > 0; ++ } else { ++ if (!this.isShutdown()) { + return true; + } ++ ++ return !this.queue.hasNoScheduledTasks(); + } + } + -+ return false; -+ } -+ -+ private long totalQueuedTasks = 0L; -+ -+ @Override -+ protected void priorityChange(final PrioritisedThreadedTaskQueue.PrioritisedTask task, final Priority from, final Priority to) { -+ // Note: The superclass' queue lock is ALWAYS held when inside this method. So we do NOT need to do any additional synchronisation -+ // for accessing this queue's state. -+ final long[] priorityCounts = this.priorityCounts; -+ final boolean shutdown = this.isShutdown(); -+ -+ if (from == null && to == Priority.COMPLETING) { -+ throw new IllegalStateException("Cannot complete task without queueing it first"); -+ } -+ -+ // we should only notify for queueing of tasks, not changing priorities -+ final boolean shouldNotifyTasks = from == null; -+ -+ final Priority scheduledPriority = this.scheduledPriority; -+ if (from != null) { -+ --priorityCounts[from.priority]; -+ } -+ if (to != Priority.COMPLETING) { -+ ++priorityCounts[to.priority]; -+ } -+ final long totalQueuedTasks; -+ if (to == Priority.COMPLETING) { -+ totalQueuedTasks = --this.totalQueuedTasks; -+ } else if (from == null) { -+ totalQueuedTasks = ++this.totalQueuedTasks; -+ } else { -+ totalQueuedTasks = this.totalQueuedTasks; -+ } -+ -+ // find new highest priority -+ int highest = Math.min(to == Priority.COMPLETING ? Priority.IDLE.priority : to.priority, scheduledPriority == null ? Priority.IDLE.priority : scheduledPriority.priority); -+ int lowestPriority = priorityCounts.length; // exclusive -+ for (;highest < lowestPriority; ++highest) { -+ final long count = priorityCounts[highest]; -+ if (count < 0) { -+ throw new IllegalStateException("Priority " + highest + " has " + count + " scheduled tasks"); ++ @Override ++ public boolean shutdown() { ++ if (!this.queue.shutdown()) { ++ return false; + } + -+ if (count != 0) { -+ break; ++ if (this.queue.hasNoScheduledTasks()) { ++ ExecutorGroup.this.executors.remove(this); ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public boolean isShutdown() { ++ return this.queue.isShutdown(); ++ } ++ ++ public void setMaxParallelism(final int maxParallelism) { ++ this.maxParallelism = maxParallelism; ++ // assume that we could have increased the parallelism ++ if (this.getTargetPriority() != null) { ++ ExecutorGroup.this.getThreadPool().notifyAllThreads(); + } + } + -+ final Priority newPriority; -+ if (highest == lowestPriority) { -+ // no tasks left -+ newPriority = null; -+ } else if (shutdown) { -+ // whichever is lower, the actual greatest priority or simply HIGHEST -+ // this is so shutdown automatically gets priority -+ newPriority = Priority.getPriority(Math.min(highest, Priority.HIGHEST.priority)); -+ } else { -+ newPriority = Priority.getPriority(highest); -+ } -+ -+ final int executorsWanted; -+ boolean shouldNotifyHighPriority = false; -+ -+ final PrioritisedThreadPool pool = this.pool; -+ final TreeSet queues = pool.queues; -+ -+ synchronized (queues) { -+ if (!this.isQueued) { -+ // see if we need to be queued -+ if (newPriority != null) { -+ if (this.schedulingId == 0L) { -+ this.schedulingId = ++pool.schedulingIdGenerator; -+ } -+ this.scheduledPriority = newPriority; // must be updated before queue add -+ if (!this.isHalted && this.concurrentExecutors < this.maximumExecutors) { -+ shouldNotifyHighPriority = newPriority.isHigherOrEqualPriority(Priority.HIGH); -+ queues.add(this); -+ this.isQueued = true; -+ } -+ } else { -+ // do not queue -+ this.scheduledPriority = null; -+ } -+ } else { -+ // see if we need to NOT be queued -+ if (newPriority == null) { -+ queues.remove(this); -+ this.scheduledPriority = null; -+ this.isQueued = false; -+ } else if (scheduledPriority != newPriority) { -+ // if our priority changed, we need to update it - which means removing and re-adding into the queue -+ queues.remove(this); -+ // only now can we update scheduledPriority, since we are no longer in queue -+ this.scheduledPriority = newPriority; -+ queues.add(this); -+ shouldNotifyHighPriority = (scheduledPriority == null || scheduledPriority.isLowerPriority(Priority.HIGH)) && newPriority.isHigherOrEqualPriority(Priority.HIGH); -+ } ++ Priority getTargetPriority() { ++ final Priority ret = this.queue.getHighestPriority(); ++ if (!this.isShutdown()) { ++ return ret; + } + -+ if (this.isQueued) { -+ executorsWanted = Math.min(this.maximumExecutors - this.concurrentExecutors, (int)totalQueuedTasks); -+ } else { -+ executorsWanted = 0; -+ } ++ return ret == null ? QUEUE_SHUTDOWN_PRIORITY : Priority.max(ret, QUEUE_SHUTDOWN_PRIORITY); + } + -+ if (newPriority == null && shutdown) { -+ synchronized (pool.activeQueues) { -+ pool.activeQueues.remove(this); -+ } ++ @Override ++ public long getTotalTasksScheduled() { ++ return this.queue.getTotalTasksScheduled(); + } + -+ // Wake up the number of executors we want -+ if (executorsWanted > 0 || (shouldNotifyTasks | shouldNotifyHighPriority)) { -+ int notified = 0; -+ for (final PrioritisedThread thread : pool.threads) { -+ if ((shouldNotifyHighPriority ? thread.alertHighPriorityExecutor() : thread.notifyTasks()) -+ && (++notified >= executorsWanted)) { -+ break; -+ } -+ } ++ @Override ++ public long getTotalTasksExecuted() { ++ return this.queue.getTotalTasksExecuted(); + } -+ } + -+ @Override -+ public boolean shutdown() { -+ final boolean ret = super.shutdown(); -+ if (!ret) { ++ @Override ++ public long generateNextSubOrder() { ++ return ExecutorGroup.this.subOrderGenerator.getAndIncrement(); ++ } ++ ++ @Override ++ public boolean executeTask() { ++ return this.queue.executeTask(); ++ } ++ ++ @Override ++ public PrioritisedTask queueTask(final Runnable task) { ++ final PrioritisedTask ret = this.createTask(task); ++ ++ ret.queue(); ++ + return ret; + } + -+ final PrioritisedThreadPool pool = this.pool; ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority) { ++ final PrioritisedTask ret = this.createTask(task, priority); + -+ // remove from active queues -+ synchronized (pool.nonShutdownQueues) { -+ pool.nonShutdownQueues.remove(this); ++ ret.queue(); ++ ++ return ret; + } + -+ final TreeSet queues = pool.queues; ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { ++ final PrioritisedTask ret = this.createTask(task, priority, subOrder); + -+ // try and shift around our priority -+ synchronized (queues) { -+ if (this.scheduledPriority == null) { -+ // no tasks are queued, ensure we aren't in activeQueues -+ synchronized (pool.activeQueues) { -+ pool.activeQueues.remove(this); ++ ret.queue(); ++ ++ return ret; ++ } ++ ++ @Override ++ public PrioritisedTask createTask(final Runnable task) { ++ return this.createTask(task, Priority.NORMAL); ++ } ++ ++ @Override ++ public PrioritisedTask createTask(final Runnable task, final Priority priority) { ++ return this.createTask(task, priority, this.generateNextSubOrder()); ++ } ++ ++ @Override ++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { ++ return new WrappedTask(this.queue.createTask(task, priority, subOrder)); ++ } ++ ++ private final class WrappedTask implements PrioritisedTask { ++ ++ private final PrioritisedTask wrapped; ++ ++ private WrappedTask(final PrioritisedTask wrapped) { ++ this.wrapped = wrapped; ++ } ++ ++ @Override ++ public PrioritisedExecutor getExecutor() { ++ return ThreadPoolExecutor.this; ++ } ++ ++ @Override ++ public boolean queue() { ++ if (this.wrapped.queue()) { ++ final Priority priority = this.getPriority(); ++ if (priority != Priority.COMPLETING) { ++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { ++ ThreadPoolExecutor.this.notifyHighPriority(); ++ } else { ++ ThreadPoolExecutor.this.notifyScheduled(); ++ } ++ } ++ return true; + } + -+ return ret; ++ return false; + } + -+ // try to set scheduled priority to HIGHEST so it drains faster -+ -+ if (this.scheduledPriority.isHigherOrEqualPriority(Priority.HIGHEST)) { -+ // already at target priority (highest or above) -+ return ret; ++ @Override ++ public boolean isQueued() { ++ return this.wrapped.isQueued(); + } + -+ // shift priority to HIGHEST ++ @Override ++ public boolean cancel() { ++ return this.wrapped.cancel(); ++ } + -+ if (this.isQueued) { -+ queues.remove(this); -+ this.scheduledPriority = Priority.HIGHEST; -+ queues.add(this); -+ } else { -+ this.scheduledPriority = Priority.HIGHEST; ++ @Override ++ public boolean execute() { ++ return this.wrapped.execute(); ++ } ++ ++ @Override ++ public Priority getPriority() { ++ return this.wrapped.getPriority(); ++ } ++ ++ @Override ++ public boolean setPriority(final Priority priority) { ++ if (this.wrapped.setPriority(priority)) { ++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { ++ ThreadPoolExecutor.this.notifyHighPriority(); ++ } ++ return true; ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public boolean raisePriority(final Priority priority) { ++ if (this.wrapped.raisePriority(priority)) { ++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { ++ ThreadPoolExecutor.this.notifyHighPriority(); ++ } ++ return true; ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public boolean lowerPriority(final Priority priority) { ++ return this.wrapped.lowerPriority(priority); ++ } ++ ++ @Override ++ public long getSubOrder() { ++ return this.wrapped.getSubOrder(); ++ } ++ ++ @Override ++ public boolean setSubOrder(final long subOrder) { ++ return this.wrapped.setSubOrder(subOrder); ++ } ++ ++ @Override ++ public boolean raiseSubOrder(final long subOrder) { ++ return this.wrapped.raiseSubOrder(subOrder); ++ } ++ ++ @Override ++ public boolean lowerSubOrder(final long subOrder) { ++ return this.wrapped.lowerSubOrder(subOrder); ++ } ++ ++ @Override ++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { ++ if (this.wrapped.setPriorityAndSubOrder(priority, subOrder)) { ++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { ++ ThreadPoolExecutor.this.notifyHighPriority(); ++ } ++ return true; ++ } ++ ++ return false; + } + } -+ -+ return ret; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.executor.standard; -+ -+import java.util.ArrayDeque; -+import java.util.concurrent.atomic.AtomicLong; -+ -+public class PrioritisedThreadedTaskQueue implements PrioritisedExecutor { -+ -+ protected final ArrayDeque[] queues = new ArrayDeque[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; { -+ for (int i = 0; i < Priority.TOTAL_SCHEDULABLE_PRIORITIES; ++i) { -+ this.queues[i] = new ArrayDeque<>(); + } + } + -+ // Use AtomicLong to separate from the queue field, we don't want false sharing here. -+ protected final AtomicLong totalScheduledTasks = new AtomicLong(); -+ protected final AtomicLong totalCompletedTasks = new AtomicLong(); ++ private static final class COWArrayList { + -+ // this is here to prevent failures to queue stalling flush() calls (as the schedule calls would increment totalScheduledTasks without this check) -+ protected volatile boolean hasShutdown; ++ private volatile E[] array; + -+ protected long taskIdGenerator = 0; -+ -+ @Override -+ public PrioritisedExecutor.PrioritisedTask queueRunnable(final Runnable task, final Priority priority) throws IllegalStateException, IllegalArgumentException { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Priority " + priority + " is invalid"); -+ } -+ if (task == null) { -+ throw new NullPointerException("Task cannot be null"); ++ public COWArrayList(final Class clazz) { ++ this.array = (E[])Array.newInstance(clazz, 0); + } + -+ if (this.hasShutdown) { -+ // prevent us from stalling flush() calls by incrementing scheduled tasks when we really didn't schedule something -+ throw new IllegalStateException("Queue has shutdown"); ++ public E[] getArray() { ++ return this.array; + } + -+ final PrioritisedTask ret; ++ public void add(final E element) { ++ synchronized (this) { ++ final E[] array = this.array; + -+ synchronized (this.queues) { -+ if (this.hasShutdown) { -+ throw new IllegalStateException("Queue has shutdown"); ++ final E[] copy = Arrays.copyOf(array, array.length + 1); ++ copy[array.length] = element; ++ ++ this.array = copy; + } -+ this.getAndAddTotalScheduledTasksVolatile(1L); -+ -+ ret = new PrioritisedTask(this.taskIdGenerator++, task, priority, this); -+ -+ this.queues[ret.priority.priority].add(ret); -+ -+ // call priority change callback (note: only after we successfully queue!) -+ this.priorityChange(ret, null, priority); + } + -+ return ret; -+ } -+ -+ @Override -+ public PrioritisedExecutor.PrioritisedTask createTask(final Runnable task, final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Priority " + priority + " is invalid"); -+ } -+ if (task == null) { -+ throw new NullPointerException("Task cannot be null"); -+ } -+ -+ return new PrioritisedTask(task, priority, this); -+ } -+ -+ @Override -+ public long getTotalTasksScheduled() { -+ return this.totalScheduledTasks.get(); -+ } -+ -+ @Override -+ public long getTotalTasksExecuted() { -+ return this.totalCompletedTasks.get(); -+ } -+ -+ // callback method for subclasses to override -+ // from is null when a task is immediately created -+ protected void priorityChange(final PrioritisedTask task, final Priority from, final Priority to) {} -+ -+ /** -+ * Polls the highest priority task currently available. {@code null} if none. This will mark the -+ * returned task as completed. -+ */ -+ protected PrioritisedTask poll() { -+ return this.poll(Priority.IDLE); -+ } -+ -+ protected PrioritisedTask poll(final Priority minPriority) { -+ final ArrayDeque[] queues = this.queues; -+ synchronized (queues) { -+ final int max = minPriority.priority; -+ for (int i = 0; i <= max; ++i) { -+ final ArrayDeque queue = queues[i]; -+ PrioritisedTask task; -+ while ((task = queue.pollFirst()) != null) { -+ if (task.trySetCompleting(i)) { -+ return task; ++ public boolean remove(final E element) { ++ synchronized (this) { ++ final E[] array = this.array; ++ int index = -1; ++ for (int i = 0, len = array.length; i < len; ++i) { ++ if (array[i] == element) { ++ index = i; ++ break; + } + } -+ } -+ } + -+ return null; -+ } -+ -+ /** -+ * Polls and executes the highest priority task currently available. Exceptions thrown during task execution will -+ * be rethrown. -+ * @return {@code true} if a task was executed, {@code false} otherwise. -+ */ -+ @Override -+ public boolean executeTask() { -+ final PrioritisedTask task = this.poll(); -+ -+ if (task != null) { -+ task.executeInternal(); -+ return true; -+ } -+ -+ return false; -+ } -+ -+ @Override -+ public boolean shutdown() { -+ synchronized (this.queues) { -+ if (this.hasShutdown) { -+ return false; -+ } -+ this.hasShutdown = true; -+ } -+ return true; -+ } -+ -+ @Override -+ public boolean isShutdown() { -+ return this.hasShutdown; -+ } -+ -+ /* totalScheduledTasks */ -+ -+ protected final long getTotalScheduledTasksVolatile() { -+ return this.totalScheduledTasks.get(); -+ } -+ -+ protected final long getAndAddTotalScheduledTasksVolatile(final long value) { -+ return this.totalScheduledTasks.getAndAdd(value); -+ } -+ -+ /* totalCompletedTasks */ -+ -+ protected final long getTotalCompletedTasksVolatile() { -+ return this.totalCompletedTasks.get(); -+ } -+ -+ protected final long getAndAddTotalCompletedTasksVolatile(final long value) { -+ return this.totalCompletedTasks.getAndAdd(value); -+ } -+ -+ protected static final class PrioritisedTask implements PrioritisedExecutor.PrioritisedTask { -+ protected final PrioritisedThreadedTaskQueue queue; -+ protected long id; -+ protected static final long NOT_SCHEDULED_ID = -1L; -+ -+ protected Runnable runnable; -+ protected volatile Priority priority; -+ -+ protected PrioritisedTask(final long id, final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ this.priority = priority; -+ this.runnable = runnable; -+ this.queue = queue; -+ this.id = id; -+ } -+ -+ protected PrioritisedTask(final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ this.priority = priority; -+ this.runnable = runnable; -+ this.queue = queue; -+ this.id = NOT_SCHEDULED_ID; -+ } -+ -+ @Override -+ public boolean queue() { -+ if (this.queue.hasShutdown) { -+ throw new IllegalStateException("Queue has shutdown"); -+ } -+ -+ synchronized (this.queue.queues) { -+ if (this.queue.hasShutdown) { -+ throw new IllegalStateException("Queue has shutdown"); -+ } -+ -+ final Priority priority = this.priority; -+ if (priority == Priority.COMPLETING) { ++ if (index == -1) { + return false; + } + -+ if (this.id != NOT_SCHEDULED_ID) { -+ return false; -+ } ++ final E[] copy = (E[])Array.newInstance(array.getClass().getComponentType(), array.length - 1); + -+ this.queue.getAndAddTotalScheduledTasksVolatile(1L); -+ this.id = this.queue.taskIdGenerator++; -+ this.queue.queues[priority.priority].add(this); ++ System.arraycopy(array, 0, copy, 0, index); ++ System.arraycopy(array, index + 1, copy, index, (array.length - 1) - index); + -+ this.queue.priorityChange(this, null, priority); -+ -+ return true; -+ } -+ } -+ -+ protected boolean trySetCompleting(final int minPriority) { -+ final Priority oldPriority = this.priority; -+ if (oldPriority != Priority.COMPLETING && oldPriority.isHigherOrEqualPriority(minPriority)) { -+ this.priority = Priority.COMPLETING; -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); -+ } -+ return true; ++ this.array = copy; + } + -+ return false; -+ } -+ -+ @Override -+ public Priority getPriority() { -+ return this.priority; -+ } -+ -+ @Override -+ public boolean setPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ synchronized (this.queue.queues) { -+ final Priority curr = this.priority; -+ -+ if (curr == Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (curr == priority) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.queues[priority.priority].add(this); -+ -+ // call priority change callback -+ this.queue.priorityChange(this, curr, priority); -+ } -+ } -+ -+ return true; -+ } -+ -+ @Override -+ public boolean raisePriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ synchronized (this.queue.queues) { -+ final Priority curr = this.priority; -+ -+ if (curr == Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (curr.isHigherOrEqualPriority(priority)) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.queues[priority.priority].add(this); -+ -+ // call priority change callback -+ this.queue.priorityChange(this, curr, priority); -+ } -+ } -+ -+ return true; -+ } -+ -+ @Override -+ public boolean lowerPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ synchronized (this.queue.queues) { -+ final Priority curr = this.priority; -+ -+ if (curr == Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (curr.isLowerOrEqualPriority(priority)) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.queues[priority.priority].add(this); -+ -+ // call priority change callback -+ this.queue.priorityChange(this, curr, priority); -+ } -+ } -+ -+ return true; -+ } -+ -+ @Override -+ public boolean cancel() { -+ final long id; -+ synchronized (this.queue.queues) { -+ final Priority oldPriority = this.priority; -+ if (oldPriority == Priority.COMPLETING) { -+ return false; -+ } -+ -+ this.priority = Priority.COMPLETING; -+ // call priority change callback -+ if ((id = this.id) != NOT_SCHEDULED_ID) { -+ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); -+ } -+ } -+ this.runnable = null; -+ if (id != NOT_SCHEDULED_ID) { -+ this.queue.getAndAddTotalCompletedTasksVolatile(1L); -+ } -+ return true; -+ } -+ -+ protected void executeInternal() { -+ try { -+ final Runnable execute = this.runnable; -+ this.runnable = null; -+ execute.run(); -+ } finally { -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.getAndAddTotalCompletedTasksVolatile(1L); -+ } -+ } -+ } -+ -+ @Override -+ public boolean execute() { -+ synchronized (this.queue.queues) { -+ final Priority oldPriority = this.priority; -+ if (oldPriority == Priority.COMPLETING) { -+ return false; -+ } -+ -+ this.priority = Priority.COMPLETING; -+ // call priority change callback -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); -+ } -+ } -+ -+ this.executeInternal(); + return true; + } + } @@ -4160,7 +4638,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * @param + * @see java.util.concurrent.ConcurrentMap + */ -+public class ConcurrentLong2ReferenceChainedHashTable { ++public class ConcurrentLong2ReferenceChainedHashTable implements Iterable> { + + protected static final int DEFAULT_CAPACITY = 16; + protected static final float DEFAULT_LOAD_FACTOR = 0.75f; @@ -4309,6 +4787,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue; + } @@ -4391,10 +4870,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public int size() { + final long ret = this.size.sum(); + -+ if (ret <= 0L) { ++ if (ret < 0L) { + return 0; + } -+ if (ret >= (long)Integer.MAX_VALUE) { ++ if (ret > (long)Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + @@ -4458,6 +4937,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + // create new table data + ++ // noinspection unchecked + final TableEntry[] newTable = new TableEntry[capacity]; + // noinspection unchecked + final TableEntry resizeNode = new TableEntry<>(0L, (V)newTable, true); @@ -4482,6 +4962,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + throw new IllegalStateException("Resizing to same size"); + } + ++ // noinspection unchecked + final TableEntry[] work = new TableEntry[1 << capDiffShift]; // typically, capDiffShift = 1 + + for (int i = 0, len = oldTable.length; i < len; ++i) { @@ -4655,6 +5136,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } @@ -4716,6 +5198,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } @@ -4770,6 +5253,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } @@ -4821,6 +5305,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } @@ -4889,6 +5374,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } @@ -4965,6 +5451,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } @@ -5061,6 +5548,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } @@ -5175,6 +5663,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } @@ -5243,6 +5732,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } @@ -5317,6 +5807,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + if (node.resize) { ++ // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } @@ -5405,6 +5896,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return new EntryIterator<>(this); + } + ++ @Override ++ public final Iterator> iterator() { ++ return this.entryIterator(); ++ } ++ + /** + * Returns an iterator over the keys in this map. The iterator is only guaranteed to see keys that were + * added before the beginning of this call, but it may see keys added during. @@ -5423,7 +5919,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + protected static final class EntryIterator extends BaseIteratorImpl> { + -+ protected EntryIterator(final ConcurrentLong2ReferenceChainedHashTable map) { ++ public EntryIterator(final ConcurrentLong2ReferenceChainedHashTable map) { + super(map); + } + @@ -5443,7 +5939,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + protected static final class KeyIterator extends BaseIteratorImpl implements PrimitiveIterator.OfLong { + -+ protected KeyIterator(final ConcurrentLong2ReferenceChainedHashTable map) { ++ public KeyIterator(final ConcurrentLong2ReferenceChainedHashTable map) { + super(map); + } + @@ -5482,7 +5978,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + protected static final class ValueIterator extends BaseIteratorImpl { + -+ protected ValueIterator(final ConcurrentLong2ReferenceChainedHashTable map) { ++ public ValueIterator(final ConcurrentLong2ReferenceChainedHashTable map) { + super(map); + } + @@ -5537,7 +6033,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Override + public final void remove() { -+ final TableEntry lastReturned = this.nextToReturn; ++ final TableEntry lastReturned = this.lastReturned; + if (lastReturned == null) { + throw new NoSuchElementException(); + } @@ -5609,6 +6105,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + final ResizeChain chain = this.resizeChain; + + if (chain == null) { ++ // noinspection unchecked + final TableEntry[] nextTable = (TableEntry[])entry.getValuePlain(); + + final ResizeChain oldChain = new ResizeChain<>(table, null, null); @@ -5623,6 +6120,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } else { + ResizeChain currChain = chain.next; + if (currChain == null) { ++ // noinspection unchecked + final TableEntry[] ret = (TableEntry[])entry.getValuePlain(); + currChain = new ResizeChain<>(ret, chain, null); + chain.next = currChain; @@ -5703,11 +6201,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + protected static final class ResizeChain { + -+ protected final TableEntry[] table; -+ protected final ResizeChain prev; -+ protected ResizeChain next; ++ public final TableEntry[] table; ++ public final ResizeChain prev; ++ public ResizeChain next; + -+ protected ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) { ++ public ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) { + this.table = table; + this.prev = prev; + this.next = next; @@ -5717,64 +6215,64 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + public static final class TableEntry { + -+ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); ++ private static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); + -+ protected final boolean resize; ++ private final boolean resize; + -+ protected final long key; ++ private final long key; + -+ protected volatile V value; -+ protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); ++ private volatile V value; ++ private static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); + -+ protected final V getValuePlain() { ++ private V getValuePlain() { + //noinspection unchecked + return (V)VALUE_HANDLE.get(this); + } + -+ protected final V getValueAcquire() { ++ private V getValueAcquire() { + //noinspection unchecked + return (V)VALUE_HANDLE.getAcquire(this); + } + -+ protected final V getValueVolatile() { ++ private V getValueVolatile() { + //noinspection unchecked + return (V)VALUE_HANDLE.getVolatile(this); + } + -+ protected final void setValuePlain(final V value) { ++ private void setValuePlain(final V value) { + VALUE_HANDLE.set(this, (Object)value); + } + -+ protected final void setValueRelease(final V value) { ++ private void setValueRelease(final V value) { + VALUE_HANDLE.setRelease(this, (Object)value); + } + -+ protected final void setValueVolatile(final V value) { ++ private void setValueVolatile(final V value) { + VALUE_HANDLE.setVolatile(this, (Object)value); + } + -+ protected volatile TableEntry next; -+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); ++ private volatile TableEntry next; ++ private static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); + -+ protected final TableEntry getNextPlain() { ++ private TableEntry getNextPlain() { + //noinspection unchecked + return (TableEntry)NEXT_HANDLE.get(this); + } + -+ protected final TableEntry getNextVolatile() { ++ private TableEntry getNextVolatile() { + //noinspection unchecked + return (TableEntry)NEXT_HANDLE.getVolatile(this); + } + -+ protected final void setNextPlain(final TableEntry next) { ++ private void setNextPlain(final TableEntry next) { + NEXT_HANDLE.set(this, next); + } + -+ protected final void setNextRelease(final TableEntry next) { ++ private void setNextRelease(final TableEntry next) { + NEXT_HANDLE.setRelease(this, next); + } + -+ protected final void setNextVolatile(final TableEntry next) { ++ private void setNextVolatile(final TableEntry next) { + NEXT_HANDLE.setVolatile(this, next); + } + @@ -8162,6 +8660,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import java.util.concurrent.locks.LockSupport; +import java.util.function.BooleanSupplier; + ++/** ++ * @deprecated To be replaced ++ */ ++@Deprecated +public class SchedulerThreadPool { + + public static final long DEADLINE_NOT_SET = Long.MIN_VALUE; @@ -8446,7 +8948,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * is invoked for any scheduled task - otherwise, {@link #runTasks(BooleanSupplier)} may not be invoked to + * parse intermediate tasks. + *

++ * @deprecated To be replaced + */ ++ @Deprecated + public static abstract class SchedulableTick { + private static final AtomicLong ID_GENERATOR = new AtomicLong(); + public final long id = ID_GENERATOR.getAndIncrement(); @@ -8721,8 +9225,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + public final Comparator comparator; + -+ protected Link head; -+ protected Link tail; ++ private Link head; ++ private Link tail; + + public LinkedSortedSet() { + this((Comparator)Comparator.naturalOrder()); @@ -8970,8 +9474,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private Link prev; + private Link next; + -+ private Link() {} -+ + private Link(final E element) { + this.element = element; + } @@ -8983,825 +9485,213 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } +} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java ++++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java @@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.util; ++package ca.spottedleaf.concurrentutil.set; + -+import java.lang.invoke.VarHandle; ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++import java.util.Objects; + -+public final class ArrayUtil { ++public final class LinkedUnsortedList implements Iterable { + -+ public static final VarHandle BOOLEAN_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(boolean[].class); ++ private Link head; ++ private Link tail; + -+ public static final VarHandle BYTE_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(byte[].class); ++ public LinkedUnsortedList() {} + -+ public static final VarHandle SHORT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(short[].class); -+ -+ public static final VarHandle INT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(int[].class); -+ -+ public static final VarHandle LONG_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(long[].class); -+ -+ public static final VarHandle OBJECT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(Object[].class); -+ -+ private ArrayUtil() { -+ throw new RuntimeException(); ++ public void clear() { ++ this.head = this.tail = null; + } + -+ /* byte array */ -+ -+ public static byte getPlain(final byte[] array, final int index) { -+ return (byte)BYTE_ARRAY_HANDLE.get(array, index); ++ public boolean isEmpty() { ++ return this.head == null; + } + -+ public static byte getOpaque(final byte[] array, final int index) { -+ return (byte)BYTE_ARRAY_HANDLE.getOpaque(array, index); ++ public E first() { ++ final Link head = this.head; ++ return head == null ? null : head.element; + } + -+ public static byte getAcquire(final byte[] array, final int index) { -+ return (byte)BYTE_ARRAY_HANDLE.getAcquire(array, index); ++ public E last() { ++ final Link tail = this.tail; ++ return tail == null ? null : tail.element; + } + -+ public static byte getVolatile(final byte[] array, final int index) { -+ return (byte)BYTE_ARRAY_HANDLE.getVolatile(array, index); -+ } -+ -+ public static void setPlain(final byte[] array, final int index, final byte value) { -+ BYTE_ARRAY_HANDLE.set(array, index, value); -+ } -+ -+ public static void setOpaque(final byte[] array, final int index, final byte value) { -+ BYTE_ARRAY_HANDLE.setOpaque(array, index, value); -+ } -+ -+ public static void setRelease(final byte[] array, final int index, final byte value) { -+ BYTE_ARRAY_HANDLE.setRelease(array, index, value); -+ } -+ -+ public static void setVolatile(final byte[] array, final int index, final byte value) { -+ BYTE_ARRAY_HANDLE.setVolatile(array, index, value); -+ } -+ -+ public static void setVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; -+ -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); ++ public boolean containsFirst(final E element) { ++ for (Link curr = this.head; curr != null; curr = curr.next) { ++ if (Objects.equals(element, curr.element)) { ++ return true; + } ++ } ++ return false; ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; ++ public boolean containsLast(final E element) { ++ for (Link curr = this.tail; curr != null; curr = curr.prev) { ++ if (Objects.equals(element, curr.element)) { ++ return true; + } + } ++ return false; ++ } ++ ++ private void removeNode(final Link node) { ++ final Link prev = node.prev; ++ final Link next = node.next; ++ ++ // help GC ++ node.element = null; ++ node.prev = null; ++ node.next = null; ++ ++ if (prev == null) { ++ this.head = next; ++ } else { ++ prev.next = next; ++ } ++ ++ if (next == null) { ++ this.tail = prev; ++ } else { ++ next.prev = prev; ++ } + } + -+ public static byte compareAndExchangeVolatile(final byte[] array, final int index, final byte expect, final byte update) { -+ return (byte)BYTE_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); ++ public boolean remove(final Link link) { ++ if (link.element == null) { ++ return false; ++ } ++ ++ this.removeNode(link); ++ return true; + } + -+ public static byte getAndAddVolatile(final byte[] array, final int index, final byte param) { -+ return (byte)BYTE_ARRAY_HANDLE.getAndAdd(array, index, param); ++ public boolean removeFirst(final E element) { ++ for (Link curr = this.head; curr != null; curr = curr.next) { ++ if (Objects.equals(element, curr.element)) { ++ this.removeNode(curr); ++ return true; ++ } ++ } ++ return false; + } + -+ public static byte getAndAndVolatile(final byte[] array, final int index, final byte param) { -+ return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); ++ public boolean removeLast(final E element) { ++ for (Link curr = this.tail; curr != null; curr = curr.prev) { ++ if (Objects.equals(element, curr.element)) { ++ this.removeNode(curr); ++ return true; ++ } ++ } ++ return false; + } + -+ public static byte getAndOrVolatile(final byte[] array, final int index, final byte param) { -+ return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -+ } ++ @Override ++ public Iterator iterator() { ++ return new Iterator<>() { ++ private Link next = LinkedUnsortedList.this.head; + -+ public static byte getAndXorVolatile(final byte[] array, final int index, final byte param) { -+ return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -+ } -+ -+ public static byte getAndSetVolatile(final byte[] array, final int index, final byte param) { -+ return (byte)BYTE_ARRAY_HANDLE.getAndSet(array, index, param); -+ } -+ -+ public static byte compareAndExchangeVolatileContended(final byte[] array, final int index, final byte expect, final byte update) { -+ return (byte)BYTE_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } -+ -+ public static byte getAndAddVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; -+ -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); ++ @Override ++ public boolean hasNext() { ++ return this.next != null; + } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr + param)))) { -+ return curr; ++ @Override ++ public E next() { ++ final Link next = this.next; ++ if (next == null) { ++ throw new NoSuchElementException(); ++ } ++ this.next = next.next; ++ return next.element; + } ++ }; ++ } ++ ++ public E pollFirst() { ++ final Link head = this.head; ++ if (head == null) { ++ return null; ++ } ++ ++ final E ret = head.element; ++ final Link next = head.next; ++ ++ // unlink head ++ this.head = next; ++ if (next == null) { ++ this.tail = null; ++ } else { ++ next.prev = null; ++ } ++ ++ // help GC ++ head.element = null; ++ head.next = null; ++ ++ return ret; ++ } ++ ++ public E pollLast() { ++ final Link tail = this.tail; ++ if (tail == null) { ++ return null; ++ } ++ ++ final E ret = tail.element; ++ final Link prev = tail.prev; ++ ++ // unlink tail ++ this.tail = prev; ++ if (prev == null) { ++ this.head = null; ++ } else { ++ prev.next = null; ++ } ++ ++ // help GC ++ tail.element = null; ++ tail.prev = null; ++ ++ return ret; ++ } ++ ++ public Link addLast(final E element) { ++ final Link curr = this.tail; ++ if (curr != null) { ++ return this.tail = new Link<>(element, curr, null); ++ } else { ++ return this.head = this.tail = new Link<>(element); + } + } + -+ public static byte getAndAndVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; -+ -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr & param)))) { -+ return curr; -+ } ++ public Link addFirst(final E element) { ++ final Link curr = this.head; ++ if (curr != null) { ++ return this.head = new Link<>(element, null, curr); ++ } else { ++ return this.head = this.tail = new Link<>(element); + } + } + -+ public static byte getAndOrVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; ++ public static final class Link { ++ private E element; ++ private Link prev; ++ private Link next; + -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr | param)))) { -+ return curr; -+ } ++ private Link(final E element) { ++ this.element = element; + } -+ } + -+ public static byte getAndXorVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; -+ -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr ^ param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static byte getAndSetVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; -+ -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; -+ } -+ } -+ } -+ -+ /* short array */ -+ -+ public static short getPlain(final short[] array, final int index) { -+ return (short)SHORT_ARRAY_HANDLE.get(array, index); -+ } -+ -+ public static short getOpaque(final short[] array, final int index) { -+ return (short)SHORT_ARRAY_HANDLE.getOpaque(array, index); -+ } -+ -+ public static short getAcquire(final short[] array, final int index) { -+ return (short)SHORT_ARRAY_HANDLE.getAcquire(array, index); -+ } -+ -+ public static short getVolatile(final short[] array, final int index) { -+ return (short)SHORT_ARRAY_HANDLE.getVolatile(array, index); -+ } -+ -+ public static void setPlain(final short[] array, final int index, final short value) { -+ SHORT_ARRAY_HANDLE.set(array, index, value); -+ } -+ -+ public static void setOpaque(final short[] array, final int index, final short value) { -+ SHORT_ARRAY_HANDLE.setOpaque(array, index, value); -+ } -+ -+ public static void setRelease(final short[] array, final int index, final short value) { -+ SHORT_ARRAY_HANDLE.setRelease(array, index, value); -+ } -+ -+ public static void setVolatile(final short[] array, final int index, final short value) { -+ SHORT_ARRAY_HANDLE.setVolatile(array, index, value); -+ } -+ -+ public static void setVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; -+ -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; -+ } -+ } -+ } -+ -+ public static short compareAndExchangeVolatile(final short[] array, final int index, final short expect, final short update) { -+ return (short)SHORT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } -+ -+ public static short getAndAddVolatile(final short[] array, final int index, final short param) { -+ return (short)SHORT_ARRAY_HANDLE.getAndAdd(array, index, param); -+ } -+ -+ public static short getAndAndVolatile(final short[] array, final int index, final short param) { -+ return (short)SHORT_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -+ } -+ -+ public static short getAndOrVolatile(final short[] array, final int index, final short param) { -+ return (short)SHORT_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -+ } -+ -+ public static short getAndXorVolatile(final short[] array, final int index, final short param) { -+ return (short)SHORT_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -+ } -+ -+ public static short getAndSetVolatile(final short[] array, final int index, final short param) { -+ return (short)SHORT_ARRAY_HANDLE.getAndSet(array, index, param); -+ } -+ -+ public static short compareAndExchangeVolatileContended(final short[] array, final int index, final short expect, final short update) { -+ return (short)SHORT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } -+ -+ public static short getAndAddVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; -+ -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr + param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static short getAndAndVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; -+ -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr & param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static short getAndOrVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; -+ -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr | param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static short getAndXorVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; -+ -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr ^ param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static short getAndSetVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; -+ -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; -+ } -+ } -+ } -+ -+ /* int array */ -+ -+ public static int getPlain(final int[] array, final int index) { -+ return (int)INT_ARRAY_HANDLE.get(array, index); -+ } -+ -+ public static int getOpaque(final int[] array, final int index) { -+ return (int)INT_ARRAY_HANDLE.getOpaque(array, index); -+ } -+ -+ public static int getAcquire(final int[] array, final int index) { -+ return (int)INT_ARRAY_HANDLE.getAcquire(array, index); -+ } -+ -+ public static int getVolatile(final int[] array, final int index) { -+ return (int)INT_ARRAY_HANDLE.getVolatile(array, index); -+ } -+ -+ public static void setPlain(final int[] array, final int index, final int value) { -+ INT_ARRAY_HANDLE.set(array, index, value); -+ } -+ -+ public static void setOpaque(final int[] array, final int index, final int value) { -+ INT_ARRAY_HANDLE.setOpaque(array, index, value); -+ } -+ -+ public static void setRelease(final int[] array, final int index, final int value) { -+ INT_ARRAY_HANDLE.setRelease(array, index, value); -+ } -+ -+ public static void setVolatile(final int[] array, final int index, final int value) { -+ INT_ARRAY_HANDLE.setVolatile(array, index, value); -+ } -+ -+ public static void setVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; -+ -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; -+ } -+ } -+ } -+ -+ public static int compareAndExchangeVolatile(final int[] array, final int index, final int expect, final int update) { -+ return (int)INT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } -+ -+ public static int getAndAddVolatile(final int[] array, final int index, final int param) { -+ return (int)INT_ARRAY_HANDLE.getAndAdd(array, index, param); -+ } -+ -+ public static int getAndAndVolatile(final int[] array, final int index, final int param) { -+ return (int)INT_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -+ } -+ -+ public static int getAndOrVolatile(final int[] array, final int index, final int param) { -+ return (int)INT_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -+ } -+ -+ public static int getAndXorVolatile(final int[] array, final int index, final int param) { -+ return (int)INT_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -+ } -+ -+ public static int getAndSetVolatile(final int[] array, final int index, final int param) { -+ return (int)INT_ARRAY_HANDLE.getAndSet(array, index, param); -+ } -+ -+ public static int compareAndExchangeVolatileContended(final int[] array, final int index, final int expect, final int update) { -+ return (int)INT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } -+ -+ public static int getAndAddVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; -+ -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr + param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static int getAndAndVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; -+ -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr & param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static int getAndOrVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; -+ -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr | param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static int getAndXorVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; -+ -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr ^ param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static int getAndSetVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; -+ -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; -+ } -+ } -+ } -+ -+ /* long array */ -+ -+ public static long getPlain(final long[] array, final int index) { -+ return (long)LONG_ARRAY_HANDLE.get(array, index); -+ } -+ -+ public static long getOpaque(final long[] array, final int index) { -+ return (long)LONG_ARRAY_HANDLE.getOpaque(array, index); -+ } -+ -+ public static long getAcquire(final long[] array, final int index) { -+ return (long)LONG_ARRAY_HANDLE.getAcquire(array, index); -+ } -+ -+ public static long getVolatile(final long[] array, final int index) { -+ return (long)LONG_ARRAY_HANDLE.getVolatile(array, index); -+ } -+ -+ public static void setPlain(final long[] array, final int index, final long value) { -+ LONG_ARRAY_HANDLE.set(array, index, value); -+ } -+ -+ public static void setOpaque(final long[] array, final int index, final long value) { -+ LONG_ARRAY_HANDLE.setOpaque(array, index, value); -+ } -+ -+ public static void setRelease(final long[] array, final int index, final long value) { -+ LONG_ARRAY_HANDLE.setRelease(array, index, value); -+ } -+ -+ public static void setVolatile(final long[] array, final int index, final long value) { -+ LONG_ARRAY_HANDLE.setVolatile(array, index, value); -+ } -+ -+ public static void setVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; -+ -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; -+ } -+ } -+ } -+ -+ public static long compareAndExchangeVolatile(final long[] array, final int index, final long expect, final long update) { -+ return (long)LONG_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } -+ -+ public static long getAndAddVolatile(final long[] array, final int index, final long param) { -+ return (long)LONG_ARRAY_HANDLE.getAndAdd(array, index, param); -+ } -+ -+ public static long getAndAndVolatile(final long[] array, final int index, final long param) { -+ return (long)LONG_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -+ } -+ -+ public static long getAndOrVolatile(final long[] array, final int index, final long param) { -+ return (long)LONG_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -+ } -+ -+ public static long getAndXorVolatile(final long[] array, final int index, final long param) { -+ return (long)LONG_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -+ } -+ -+ public static long getAndSetVolatile(final long[] array, final int index, final long param) { -+ return (long)LONG_ARRAY_HANDLE.getAndSet(array, index, param); -+ } -+ -+ public static long compareAndExchangeVolatileContended(final long[] array, final int index, final long expect, final long update) { -+ return (long)LONG_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } -+ -+ public static long getAndAddVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; -+ -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr + param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static long getAndAndVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; -+ -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr & param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static long getAndOrVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; -+ -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr | param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static long getAndXorVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; -+ -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr ^ param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static long getAndSetVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; -+ -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; -+ } -+ } -+ } -+ -+ /* boolean array */ -+ -+ public static boolean getPlain(final boolean[] array, final int index) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.get(array, index); -+ } -+ -+ public static boolean getOpaque(final boolean[] array, final int index) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getOpaque(array, index); -+ } -+ -+ public static boolean getAcquire(final boolean[] array, final int index) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAcquire(array, index); -+ } -+ -+ public static boolean getVolatile(final boolean[] array, final int index) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getVolatile(array, index); -+ } -+ -+ public static void setPlain(final boolean[] array, final int index, final boolean value) { -+ BOOLEAN_ARRAY_HANDLE.set(array, index, value); -+ } -+ -+ public static void setOpaque(final boolean[] array, final int index, final boolean value) { -+ BOOLEAN_ARRAY_HANDLE.setOpaque(array, index, value); -+ } -+ -+ public static void setRelease(final boolean[] array, final int index, final boolean value) { -+ BOOLEAN_ARRAY_HANDLE.setRelease(array, index, value); -+ } -+ -+ public static void setVolatile(final boolean[] array, final int index, final boolean value) { -+ BOOLEAN_ARRAY_HANDLE.setVolatile(array, index, value); -+ } -+ -+ public static void setVolatileContended(final boolean[] array, final int index, final boolean param) { -+ int failures = 0; -+ -+ for (boolean curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; -+ } -+ } -+ } -+ -+ public static boolean compareAndExchangeVolatile(final boolean[] array, final int index, final boolean expect, final boolean update) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } -+ -+ public static boolean getAndOrVolatile(final boolean[] array, final int index, final boolean param) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -+ } -+ -+ public static boolean getAndXorVolatile(final boolean[] array, final int index, final boolean param) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -+ } -+ -+ public static boolean getAndSetVolatile(final boolean[] array, final int index, final boolean param) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAndSet(array, index, param); -+ } -+ -+ public static boolean compareAndExchangeVolatileContended(final boolean[] array, final int index, final boolean expect, final boolean update) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } -+ -+ public static boolean getAndAndVolatileContended(final boolean[] array, final int index, final boolean param) { -+ int failures = 0; -+ -+ for (boolean curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr & param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static boolean getAndOrVolatileContended(final boolean[] array, final int index, final boolean param) { -+ int failures = 0; -+ -+ for (boolean curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr | param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static boolean getAndXorVolatileContended(final boolean[] array, final int index, final boolean param) { -+ int failures = 0; -+ -+ for (boolean curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr ^ param)))) { -+ return curr; -+ } -+ } -+ } -+ -+ public static boolean getAndSetVolatileContended(final boolean[] array, final int index, final boolean param) { -+ int failures = 0; -+ -+ for (boolean curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; -+ } -+ } -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static T getPlain(final T[] array, final int index) { -+ final Object ret = OBJECT_ARRAY_HANDLE.get((Object[])array, index); -+ return (T)ret; -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static T getOpaque(final T[] array, final int index) { -+ final Object ret = OBJECT_ARRAY_HANDLE.getOpaque((Object[])array, index); -+ return (T)ret; -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static T getAcquire(final T[] array, final int index) { -+ final Object ret = OBJECT_ARRAY_HANDLE.getAcquire((Object[])array, index); -+ return (T)ret; -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static T getVolatile(final T[] array, final int index) { -+ final Object ret = OBJECT_ARRAY_HANDLE.getVolatile((Object[])array, index); -+ return (T)ret; -+ } -+ -+ public static void setPlain(final T[] array, final int index, final T value) { -+ OBJECT_ARRAY_HANDLE.set((Object[])array, index, (Object)value); -+ } -+ -+ public static void setOpaque(final T[] array, final int index, final T value) { -+ OBJECT_ARRAY_HANDLE.setOpaque((Object[])array, index, (Object)value); -+ } -+ -+ public static void setRelease(final T[] array, final int index, final T value) { -+ OBJECT_ARRAY_HANDLE.setRelease((Object[])array, index, (Object)value); -+ } -+ -+ public static void setVolatile(final T[] array, final int index, final T value) { -+ OBJECT_ARRAY_HANDLE.setVolatile((Object[])array, index, (Object)value); -+ } -+ -+ public static void setVolatileContended(final T[] array, final int index, final T param) { -+ int failures = 0; -+ -+ for (T curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; -+ } -+ } -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static T compareAndExchangeVolatile(final T[] array, final int index, final T expect, final T update) { -+ final Object ret = OBJECT_ARRAY_HANDLE.compareAndExchange((Object[])array, index, (Object)expect, (Object)update); -+ return (T)ret; -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static T getAndSetVolatile(final T[] array, final int index, final T param) { -+ final Object ret = BYTE_ARRAY_HANDLE.getAndSet((Object[])array, index, (Object)param); -+ return (T)ret; -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static T compareAndExchangeVolatileContended(final T[] array, final int index, final T expect, final T update) { -+ final Object ret = OBJECT_ARRAY_HANDLE.compareAndExchange((Object[])array, index, (Object)expect, (Object)update); -+ return (T)ret; -+ } -+ -+ public static T getAndSetVolatileContended(final T[] array, final int index, final T param) { -+ int failures = 0; -+ -+ for (T curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; -+ } ++ private Link(final E element, final Link prev, final Link next) { ++ this.element = element; ++ this.prev = prev; ++ this.next = next; + } + } +} @@ -10361,11 +10251,183 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 + } + ++ // https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide ++ /** ++ * ++ * Usage: ++ *
++     * {@code
++     *     static final long mult = getSimpleMultiplier(divisor, bits);
++     *     long x = ...;
++     *     long magic = x * mult;
++     *     long divQ = magic >>> bits;
++     *     long divR = ((magic & ((1 << bits) - 1)) * divisor) >>> bits;
++     * }
++     * 
++ * ++ * @param bits The number of bits of precision for the returned result ++ */ ++ public static long getUnsignedDivisorMagic(final long divisor, final int bits) { ++ return (((1L << bits) - 1L) / divisor) + 1; ++ } ++ + private IntegerUtil() { + throw new RuntimeException(); + } +} \ No newline at end of file +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.concurrentutil.util; ++ ++public enum Priority { ++ ++ /** ++ * Priority value indicating the task has completed or is being completed. ++ * This priority cannot be used to schedule tasks. ++ */ ++ COMPLETING(-1), ++ ++ /** ++ * Absolute highest priority, should only be used for when a task is blocking a time-critical thread. ++ */ ++ BLOCKING(), ++ ++ /** ++ * Should only be used for urgent but not time-critical tasks. ++ */ ++ HIGHEST(), ++ ++ /** ++ * Two priorities above normal. ++ */ ++ HIGHER(), ++ ++ /** ++ * One priority above normal. ++ */ ++ HIGH(), ++ ++ /** ++ * Default priority. ++ */ ++ NORMAL(), ++ ++ /** ++ * One priority below normal. ++ */ ++ LOW(), ++ ++ /** ++ * Two priorities below normal. ++ */ ++ LOWER(), ++ ++ /** ++ * Use for tasks that should eventually execute, but are not needed to. ++ */ ++ LOWEST(), ++ ++ /** ++ * Use for tasks that can be delayed indefinitely. ++ */ ++ IDLE(); ++ ++ // returns whether the priority can be scheduled ++ public static boolean isValidPriority(final Priority priority) { ++ return priority != null && priority != priority.COMPLETING; ++ } ++ ++ // returns the higher priority of the two ++ public static Priority max(final Priority p1, final Priority p2) { ++ return p1.isHigherOrEqualPriority(p2) ? p1 : p2; ++ } ++ ++ // returns the lower priroity of the two ++ public static Priority min(final Priority p1, final Priority p2) { ++ return p1.isLowerOrEqualPriority(p2) ? p1 : p2; ++ } ++ ++ public boolean isHigherOrEqualPriority(final Priority than) { ++ return this.priority <= than.priority; ++ } ++ ++ public boolean isHigherPriority(final Priority than) { ++ return this.priority < than.priority; ++ } ++ ++ public boolean isLowerOrEqualPriority(final Priority than) { ++ return this.priority >= than.priority; ++ } ++ ++ public boolean isLowerPriority(final Priority than) { ++ return this.priority > than.priority; ++ } ++ ++ public boolean isHigherOrEqualPriority(final int than) { ++ return this.priority <= than; ++ } ++ ++ public boolean isHigherPriority(final int than) { ++ return this.priority < than; ++ } ++ ++ public boolean isLowerOrEqualPriority(final int than) { ++ return this.priority >= than; ++ } ++ ++ public boolean isLowerPriority(final int than) { ++ return this.priority > than; ++ } ++ ++ public static boolean isHigherOrEqualPriority(final int priority, final int than) { ++ return priority <= than; ++ } ++ ++ public static boolean isHigherPriority(final int priority, final int than) { ++ return priority < than; ++ } ++ ++ public static boolean isLowerOrEqualPriority(final int priority, final int than) { ++ return priority >= than; ++ } ++ ++ public static boolean isLowerPriority(final int priority, final int than) { ++ return priority > than; ++ } ++ ++ static final Priority[] PRIORITIES = Priority.values(); ++ ++ /** includes special priorities */ ++ public static final int TOTAL_PRIORITIES = PRIORITIES.length; ++ ++ public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1; ++ ++ public static Priority getPriority(final int priority) { ++ return PRIORITIES[priority + 1]; ++ } ++ ++ private static int priorityCounter; ++ ++ private static int nextCounter() { ++ return priorityCounter++; ++ } ++ ++ public final int priority; ++ ++ private Priority() { ++ this(nextCounter()); ++ } ++ ++ private Priority(final int priority) { ++ this.priority = priority; ++ } ++} +\ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/ThrowUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/ThrowUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 diff --git a/patches/server/Improve-and-expand-AsyncCatcher.patch b/patches/server/Improve-and-expand-AsyncCatcher.patch index 8dead5ae0f..a78f17a24e 100644 --- a/patches/server/Improve-and-expand-AsyncCatcher.patch +++ b/patches/server/Improve-and-expand-AsyncCatcher.patch @@ -59,8 +59,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private boolean addEntity(T entity, boolean existing) { + org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper // Paper start - chunk system hooks - if (existing) { - // I don't want to know why this is a generic type. + // I don't want to know why this is a generic type. + Entity entityCasted = (Entity)entity; @@ -0,0 +0,0 @@ public class PersistentEntitySectionManager implements A } diff --git a/patches/server/MC-Utils.patch b/patches/server/MC-Utils.patch index 25073ec42d..a24b9c999f 100644 --- a/patches/server/MC-Utils.patch +++ b/patches/server/MC-Utils.patch @@ -12,6 +12,129 @@ public net.minecraft.server.level.ServerChunkCache mainThreadProcessor public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor public net.minecraft.world.level.chunk.LevelChunkSection states +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.common; ++ ++import com.mojang.datafixers.DSL; ++import com.mojang.datafixers.DataFixer; ++import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.GenerationChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import net.minecraft.world.level.chunk.storage.SerializableChunkData; ++import net.minecraft.world.level.entity.EntityTypeTest; ++import net.minecraft.world.phys.AABB; ++import java.util.List; ++import java.util.ServiceLoader; ++import java.util.function.Predicate; ++ ++public interface PlatformHooks { ++ public static PlatformHooks get() { ++ return Holder.INSTANCE; ++ } ++ ++ public String getBrand(); ++ ++ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos); ++ ++ public Predicate maybeHasLightEmission(); ++ ++ public boolean hasCurrentlyLoadingChunk(); ++ ++ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder); ++ ++ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk); ++ ++ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original); ++ ++ public boolean allowAsyncTicketUpdates(); ++ ++ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel); ++ ++ public void chunkUnloadFromWorld(final LevelChunk chunk); ++ ++ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data); ++ ++ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player); ++ ++ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player); ++ ++ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, ++ final List into); ++ ++ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, ++ final AABB boundingBox, final Predicate predicate, ++ final List into, final int maxCount); ++ ++ public void entityMove(final Entity entity, final long oldSection, final long newSection); ++ ++ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event); ++ ++ public boolean configFixMC224294(); ++ ++ public boolean configAutoConfigSendDistance(); ++ ++ public double configPlayerMaxLoadRate(); ++ ++ public double configPlayerMaxGenRate(); ++ ++ public double configPlayerMaxSendRate(); ++ ++ public int configPlayerMaxConcurrentLoads(); ++ ++ public int configPlayerMaxConcurrentGens(); ++ ++ public long configAutoSaveInterval(final ServerLevel world); ++ ++ public int configMaxAutoSavePerTick(final ServerLevel world); ++ ++ public boolean configFixMC159283(); ++ ++ // support for CB chunk mustNotSave ++ public boolean forceNoSave(final ChunkAccess chunk); ++ ++ public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, ++ final int fromVersion, final int toVersion); ++ ++ public boolean hasMainChunkLoadHook(); ++ ++ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData); ++ ++ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities); ++ ++ public void unloadEntity(final Entity entity); ++ ++ public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk); ++ ++ public int modifyEntityTrackingRange(final Entity entity, final int currentRange); ++ ++ public static final class Holder { ++ private Holder() { ++ } ++ ++ private static final PlatformHooks INSTANCE; ++ ++ static { ++ INSTANCE = ServiceLoader.load(PlatformHooks.class, PlatformHooks.class.getClassLoader()).findFirst() ++ .orElseThrow(() -> new RuntimeException("Failed to locate PlatformHooks")); ++ } ++ } ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -33,15 +156,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + */ +public final class EntityList implements Iterable { + -+ protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); ++ private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); + { + this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + -+ protected static final Entity[] EMPTY_LIST = new Entity[0]; ++ private static final Entity[] EMPTY_LIST = new Entity[0]; + -+ protected Entity[] entities = EMPTY_LIST; -+ protected int count; ++ private Entity[] entities = EMPTY_LIST; ++ private int count; + + public int size() { + return this.count; @@ -114,10 +237,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Override + public Iterator iterator() { -+ return new Iterator() { -+ -+ Entity lastRet; -+ int current; ++ return new Iterator<>() { ++ private Entity lastRet; ++ private int current; + + @Override + public boolean hasNext() { @@ -147,138 +269,89 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + }; + } +} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java @@ -0,0 +0,0 @@ +package ca.spottedleaf.moonrise.common.list; + -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; ++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import java.util.Arrays; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.GlobalPalette; + -+public final class IBlockDataList { ++public final class IntList { + -+ private static final GlobalPalette GLOBAL_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY); -+ -+ // map of location -> (index | (location << 16) | (palette id << 32)) -+ private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f); ++ private final Int2IntOpenHashMap map = new Int2IntOpenHashMap(); + { -+ this.map.defaultReturnValue(Long.MAX_VALUE); ++ this.map.defaultReturnValue(Integer.MIN_VALUE); + } + -+ private static final long[] EMPTY_LIST = new long[0]; ++ private static final int[] EMPTY_LIST = new int[0]; + -+ private long[] byIndex = EMPTY_LIST; -+ private int size; -+ -+ public static int getLocationKey(final int x, final int y, final int z) { -+ return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4)); -+ } -+ -+ public static BlockState getBlockDataFromRaw(final long raw) { -+ return GLOBAL_PALETTE.valueFor((int)(raw >>> 32)); -+ } -+ -+ public static int getIndexFromRaw(final long raw) { -+ return (int)(raw & 0xFFFF); -+ } -+ -+ public static int getLocationFromRaw(final long raw) { -+ return (int)((raw >>> 16) & 0xFFFF); -+ } -+ -+ public static long getRawFromValues(final int index, final int location, final BlockState data) { -+ return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.idFor(data)) << 32); -+ } -+ -+ public static long setIndexRawValues(final long value, final int index) { -+ return value & ~(0xFFFF) | (index); -+ } -+ -+ public long add(final int x, final int y, final int z, final BlockState data) { -+ return this.add(getLocationKey(x, y, z), data); -+ } -+ -+ public long add(final int location, final BlockState data) { -+ final long curr = this.map.get((short)location); -+ -+ if (curr == Long.MAX_VALUE) { -+ final int index = this.size++; -+ final long raw = getRawFromValues(index, location, data); -+ this.map.put((short)location, raw); -+ -+ if (index >= this.byIndex.length) { -+ this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L)); -+ } -+ -+ this.byIndex[index] = raw; -+ return raw; -+ } else { -+ final int index = getIndexFromRaw(curr); -+ final long raw = this.byIndex[index] = getRawFromValues(index, location, data); -+ -+ this.map.put((short)location, raw); -+ -+ return raw; -+ } -+ } -+ -+ public long remove(final int x, final int y, final int z) { -+ return this.remove(getLocationKey(x, y, z)); -+ } -+ -+ public long remove(final int location) { -+ final long ret = this.map.remove((short)location); -+ final int index = getIndexFromRaw(ret); -+ if (ret == Long.MAX_VALUE) { -+ return ret; -+ } -+ -+ // move the entry at the end to this index -+ final int endIndex = --this.size; -+ final long end = this.byIndex[endIndex]; -+ if (index != endIndex) { -+ // not empty after this call -+ this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index)); -+ } -+ this.byIndex[index] = end; -+ this.byIndex[endIndex] = 0L; -+ -+ return ret; -+ } ++ private int[] byIndex = EMPTY_LIST; ++ private int count; + + public int size() { -+ return this.size; ++ return this.count; + } + -+ public long getRaw(final int index) { ++ public void setMinCapacity(final int len) { ++ final int[] byIndex = this.byIndex; ++ if (byIndex.length < len) { ++ this.byIndex = Arrays.copyOf(byIndex, len); ++ } ++ } ++ ++ public int getRaw(final int index) { + return this.byIndex[index]; + } + -+ public int getLocation(final int index) { -+ return getLocationFromRaw(this.getRaw(index)); ++ public boolean add(final int value) { ++ final int count = this.count; ++ final int currIndex = this.map.putIfAbsent(value, count); ++ ++ if (currIndex != Integer.MIN_VALUE) { ++ return false; // already in this list ++ } ++ ++ int[] list = this.byIndex; ++ ++ if (list.length == count) { ++ // resize required ++ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative ++ } ++ ++ list[count] = value; ++ this.count = count + 1; ++ ++ return true; + } + -+ public BlockState getData(final int index) { -+ return getBlockDataFromRaw(this.getRaw(index)); ++ public boolean remove(final int value) { ++ final int index = this.map.remove(value); ++ if (index == Integer.MIN_VALUE) { ++ return false; ++ } ++ ++ // move the entry at the end to this index ++ final int endIndex = --this.count; ++ final int end = this.byIndex[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.map.put(end, index); ++ } ++ this.byIndex[index] = end; ++ this.byIndex[endIndex] = 0; ++ ++ return true; + } + + public void clear() { -+ this.size = 0; ++ this.count = 0; + this.map.clear(); + } -+ -+ public LongIterator getRawIterator() { -+ return this.map.values().iterator(); -+ } +} -\ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -745,6 +818,89 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + }; + } +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.common.list; ++ ++import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; ++import java.util.Arrays; ++ ++public final class ShortList { ++ ++ private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(); ++ { ++ this.map.defaultReturnValue(Short.MIN_VALUE); ++ } ++ ++ private static final short[] EMPTY_LIST = new short[0]; ++ ++ private short[] byIndex = EMPTY_LIST; ++ private short count; ++ ++ public int size() { ++ return (int)this.count; ++ } ++ ++ public short getRaw(final int index) { ++ return this.byIndex[index]; ++ } ++ ++ public void setMinCapacity(final int len) { ++ final short[] byIndex = this.byIndex; ++ if (byIndex.length < len) { ++ this.byIndex = Arrays.copyOf(byIndex, len); ++ } ++ } ++ ++ public boolean add(final short value) { ++ final int count = (int)this.count; ++ final short currIndex = this.map.putIfAbsent(value, (short)count); ++ ++ if (currIndex != Short.MIN_VALUE) { ++ return false; // already in this list ++ } ++ ++ short[] list = this.byIndex; ++ ++ if (list.length == count) { ++ // resize required ++ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative ++ } ++ ++ list[count] = value; ++ this.count = (short)(count + 1); ++ ++ return true; ++ } ++ ++ public boolean remove(final short value) { ++ final short index = this.map.remove(value); ++ if (index == Short.MIN_VALUE) { ++ return false; ++ } ++ ++ // move the entry at the end to this index ++ final short endIndex = --this.count; ++ final short end = this.byIndex[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.map.put(end, index); ++ } ++ this.byIndex[(int)index] = end; ++ this.byIndex[(int)endIndex] = (short)0; ++ ++ return true; ++ } ++ ++ public void clear() { ++ this.count = (short)0; ++ this.map.clear(); ++ } ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -2413,6 +2569,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.common.misc; ++ ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import java.lang.invoke.VarHandle; ++ ++public final class LazyRunnable implements Runnable { ++ ++ private volatile Runnable toRun; ++ private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(LazyRunnable.class, "toRun", Runnable.class); ++ ++ public void setRunnable(final Runnable run) { ++ final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run); ++ if (prev != null) { ++ throw new IllegalStateException("Runnable already set"); ++ } ++ } ++ ++ @Override ++ public void run() { ++ ((Runnable)TO_RUN_HANDLE.getVolatile(this)).run(); ++ } ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -2425,13 +2609,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.MoonriseConstants; +import ca.spottedleaf.moonrise.common.util.ChunkSystem; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; +import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants; ++import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel; +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; ++import java.util.ArrayList; + +public final class NearbyPlayers { + @@ -2441,7 +2629,27 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + GENERAL_REALLY_SMALL, + TICK_VIEW_DISTANCE, + VIEW_DISTANCE, -+ SPAWN_RANGE, // Moonrise - chunk tick iteration ++ // Moonrise start - chunk tick iteration ++ SPAWN_RANGE { ++ @Override ++ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ((ChunkTickServerLevel)world).moonrise$addPlayerTickingRequest(chunkX, chunkZ); ++ } ++ ++ @Override ++ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ((ChunkTickServerLevel)world).moonrise$removePlayerTickingRequest(chunkX, chunkZ); ++ } ++ }; ++ // Moonrise end - chunk tick iteration ++ ++ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ++ } ++ ++ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ++ } + } + + private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values(); @@ -2458,6 +2666,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private final ServerLevel world; + private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); + private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); ++ private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; ++ { ++ for (int i = 0; i < this.directByChunk.length; ++i) { ++ this.directByChunk[i] = new Long2ReferenceOpenHashMap<>(); ++ } ++ } + + public NearbyPlayers(final ServerLevel world) { + this.world = world; @@ -2491,6 +2705,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + ++ public void clear() { ++ if (this.players.isEmpty()) { ++ return; ++ } ++ ++ for (final ServerPlayer player : new ArrayList<>(this.players.keySet())) { ++ this.removePlayer(player); ++ } ++ } ++ + public void tickPlayer(final ServerPlayer player) { + final TrackedPlayer[] players = this.players.get(player); + if (players == null) { @@ -2515,38 +2739,41 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.byChunk.get(CoordinateUtils.getChunkKey(pos)); + } + -+ public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) { -+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos)); ++ public TrackedChunk getChunk(final int chunkX, final int chunkZ) { ++ return this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } + -+ return chunk == null ? null : chunk.players[type.ordinal()]; ++ public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) { ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos)); + } + + public ReferenceList getPlayers(final ChunkPos pos, final NearbyMapType type) { -+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos)); -+ -+ return chunk == null ? null : chunk.players[type.ordinal()]; ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos)); + } + + public ReferenceList getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) { -+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ -+ return chunk == null ? null : chunk.players[type.ordinal()]; ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + } + + public ReferenceList getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) { -+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4)); -+ -+ return chunk == null ? null : chunk.players[type.ordinal()]; ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4)); + } + + public static final class TrackedChunk { + + private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0]; + ++ private final long chunkKey; ++ private final NearbyPlayers nearbyPlayers; + private final ReferenceList[] players = new ReferenceList[TOTAL_MAP_TYPES]; + private int nonEmptyLists; + private long updateCount; + ++ public TrackedChunk(final long chunkKey, final NearbyPlayers nearbyPlayers) { ++ this.chunkKey = chunkKey; ++ this.nearbyPlayers = nearbyPlayers; ++ } ++ + public boolean isEmpty() { + return this.nonEmptyLists == 0; + } @@ -2566,7 +2793,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + final ReferenceList list = this.players[idx]; + if (list == null) { + ++this.nonEmptyLists; -+ (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player); ++ final ReferenceList players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)); ++ this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players); ++ players.add(player); + return; + } + @@ -2590,6 +2819,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + if (list.size() == 0) { + this.players[idx] = null; ++ this.nearbyPlayers.directByChunk[idx].remove(this.chunkKey); + --this.nonEmptyLists; + } + } @@ -2608,9 +2838,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) { + final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); + -+ NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> { -+ return new TrackedChunk(); -+ }).addPlayer(parameter, this.type); ++ final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey); ++ final NearbyMapType type = this.type; ++ if (chunk != null) { ++ chunk.addPlayer(parameter, type); ++ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); ++ } else { ++ final TrackedChunk created = new TrackedChunk(chunkKey, NearbyPlayers.this); ++ NearbyPlayers.this.byChunk.put(chunkKey, created); ++ created.addPlayer(parameter, type); ++ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); ++ ++ ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created; ++ } + } + + @Override @@ -2622,10 +2862,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey)); + } + -+ chunk.removePlayer(parameter, this.type); ++ final NearbyMapType type = this.type; ++ chunk.removePlayer(parameter, type); ++ type.removeFrom(parameter, NearbyPlayers.this.world, chunkX, chunkZ); + + if (chunk.isEmpty()) { + NearbyPlayers.this.byChunk.remove(chunkKey); ++ final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey); ++ if (chunkData != null) { ++ chunkData.nearbyPlayers = null; ++ } + } + } + } @@ -2640,6 +2886,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +import ca.spottedleaf.concurrentutil.util.IntPairUtil; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceSet; + @@ -2652,6 +2899,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.counters.keySet(); + } + ++ public LongSet getPositions() { ++ return this.positions.keySet(); ++ } ++ + public int getTotalPositions() { + return this.positions.size(); + } @@ -3066,7 +3317,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +package ca.spottedleaf.moonrise.common.util; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import com.mojang.logging.LogUtils; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.FullChunkStatus; @@ -3090,15 +3342,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) { -+ scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); ++ scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL); + } + -+ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) { ++ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) { + level.chunkSource.mainThreadProcessor.execute(run); + } + + public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen, -+ final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, ++ final ChunkStatus toStatus, final boolean addTicket, final Priority priority, + final Consumer onComplete) { + if (gen) { + scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); @@ -3125,7 +3377,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + private static long chunkLoadCounter = 0L; + public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, -+ final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { ++ final boolean addTicket, final Priority priority, final Consumer onComplete) { + if (!org.bukkit.Bukkit.isPrimaryThread()) { + scheduleChunkTask(level, chunkX, chunkZ, () -> { + scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); @@ -3179,13 +3431,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + loadCallback.accept(result.orElse(null)); + }, (final Runnable r) -> { -+ scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); ++ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); + }); + } + + public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ, + final FullChunkStatus toStatus, final boolean addTicket, -+ final PrioritisedExecutor.Priority priority, final Consumer onComplete) { ++ final Priority priority, final Consumer onComplete) { + // This method goes unused until the chunk system rewrite + if (toStatus == FullChunkStatus.INACCESSIBLE) { + throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); @@ -3262,7 +3514,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + loadCallback.accept(result.orElse(null)); + }, (final Runnable r) -> { -+ scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); ++ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); + }); + } + @@ -3286,7 +3538,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return getUpdatingChunkHolderCount(level) != 0; + } + -+ public static boolean screenEntity(final ServerLevel level, final Entity entity) { ++ public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) { ++ if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) { ++ return false; ++ } + return true; + } + @@ -3649,10 +3904,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +public final class MixinWorkarounds { + + // mixins tries to find the owner of the clone() method, which doesn't exist and NPEs ++ // https://github.com/FabricMC/Mixin/pull/147 + public static long[] clone(final long[] values) { + return values.clone(); + } + ++ public static byte[] clone(final byte[] values) { ++ return values.clone(); ++ } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java new file mode 100644 @@ -3662,46 +3921,101 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +package ca.spottedleaf.moonrise.common.util; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; ++import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -+import java.io.File; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Consumer; + +public final class MoonriseCommon { + + private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseCommon.class); + -+ // Paper start -+ public static PrioritisedThreadPool WORKER_POOL; -+ public static int WORKER_THREADS; -+ public static void init(io.papermc.paper.configuration.GlobalConfiguration.ChunkSystem chunkSystem) { -+ // Paper end -+ int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; -+ if (defaultWorkerThreads <= 4) { -+ defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; -+ } else { -+ defaultWorkerThreads = defaultWorkerThreads / 2; -+ } -+ defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); // Paper ++ public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool( ++ new Consumer<>() { ++ private final AtomicInteger idGenerator = new AtomicInteger(); + -+ int workerThreads = chunkSystem.workerThreads; // Paper -+ -+ if (workerThreads <= 0) { -+ workerThreads = defaultWorkerThreads; -+ } -+ -+ WORKER_POOL = new PrioritisedThreadPool( -+ "Paper Worker Pool", workerThreads, // Paper -+ (final Thread thread, final Integer id) -> { -+ thread.setName("Paper Common Worker #" + id.intValue()); // Paper ++ @Override ++ public void accept(Thread thread) { ++ thread.setDaemon(true); ++ thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement()); + thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(final Thread thread, final Throwable throwable) { + LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); + } + }); -+ }, (long)(20.0e6)); // 20ms -+ WORKER_THREADS = workerThreads; ++ } ++ } ++ ); ++ public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms ++ public static final int CLIENT_DIVISION = 0; ++ public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0); ++ public static final int SERVER_DIVISION = 1; ++ public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ ++ public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { ++ int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; ++ if (defaultWorkerThreads <= 4) { ++ defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; ++ } else { ++ defaultWorkerThreads = defaultWorkerThreads / 2; ++ } ++ defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); ++ ++ int workerThreads = configWorkerThreads; ++ ++ if (workerThreads <= 0) { ++ workerThreads = defaultWorkerThreads; ++ } ++ ++ final int ioThreads = Math.max(1, configIoThreads); ++ ++ WORKER_POOL.adjustThreadCount(workerThreads); ++ IO_POOL.adjustThreadCount(ioThreads); ++ ++ LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); ++ } ++ ++ public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool( ++ new Consumer<>() { ++ private final AtomicInteger idGenerator = new AtomicInteger(); ++ ++ @Override ++ public void accept(final Thread thread) { ++ thread.setDaemon(true); ++ thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement()); ++ thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { ++ @Override ++ public void uncaughtException(final Thread thread, final Throwable throwable) { ++ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); ++ } ++ }); ++ } ++ } ++ ); ++ public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms ++ public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0); ++ public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ ++ public static void haltExecutors() { ++ MoonriseCommon.WORKER_POOL.shutdown(false); ++ LOGGER.info("Awaiting termination of worker pool for up to 60s..."); ++ if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { ++ LOGGER.error("Worker pool did not shut down in time!"); ++ MoonriseCommon.WORKER_POOL.halt(false); ++ } ++ ++ MoonriseCommon.IO_POOL.shutdown(false); ++ LOGGER.info("Awaiting termination of I/O pool for up to 60s..."); ++ if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { ++ LOGGER.error("I/O pool did not shut down in time!"); ++ MoonriseCommon.IO_POOL.halt(false); ++ } + } + + private MoonriseCommon() {} @@ -3714,13 +4028,73 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +package ca.spottedleaf.moonrise.common.util; + ++import ca.spottedleaf.moonrise.common.PlatformHooks; ++ +public final class MoonriseConstants { + -+ public static final int MAX_VIEW_DISTANCE = 32; ++ public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32); + + private MoonriseConstants() {} + +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.common.util; ++ ++import net.minecraft.world.level.levelgen.LegacyRandomSource; ++ ++/** ++ * Avoid costly CAS of superclass ++ */ ++public final class SimpleRandom extends LegacyRandomSource { ++ ++ private static final long MULTIPLIER = 25214903917L; ++ private static final long ADDEND = 11L; ++ private static final int BITS = 48; ++ private static final long MASK = (1L << BITS) - 1; ++ ++ private long value; ++ ++ public SimpleRandom(final long seed) { ++ super(0L); ++ this.value = seed; ++ } ++ ++ @Override ++ public void setSeed(final long seed) { ++ this.value = (seed ^ MULTIPLIER) & MASK; ++ } ++ ++ private long advanceSeed() { ++ return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK; ++ } ++ ++ @Override ++ public int next(final int bits) { ++ return (int)(this.advanceSeed() >>> (BITS - bits)); ++ } ++ ++ @Override ++ public int nextInt() { ++ final long seed = this.advanceSeed(); ++ return (int)(seed >>> (BITS - Integer.SIZE)); ++ } ++ ++ @Override ++ public int nextInt(final int bound) { ++ if (bound <= 0) { ++ throw new IllegalArgumentException(); ++ } ++ ++ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ ++ final long value = this.advanceSeed() >>> (BITS - Integer.SIZE); ++ return (int)((value * (long)bound) >>> Integer.SIZE); ++ } ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -3806,11 +4180,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + public TickThread(final Runnable run, final String name) { -+ this(run, name, ID_GENERATOR.incrementAndGet()); ++ this(null, run, name); + } + -+ private TickThread(final Runnable run, final String name, final int id) { -+ super(run, name); ++ public TickThread(final ThreadGroup group, final Runnable run, final String name) { ++ this(group, run, name, ID_GENERATOR.incrementAndGet()); ++ } ++ ++ private TickThread(final ThreadGroup group, final Runnable run, final String name, final int id) { ++ super(group, run, name); + this.id = id; + } + @@ -3882,13 +4260,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // min, max are inclusive + + public static int getMaxSection(final LevelHeightAccessor world) { -+ return world.getMaxSectionY() - 1; // getMaxSection() is exclusive ++ return world.getMaxSectionY(); ++ } ++ ++ public static int getMaxSection(final Level world) { ++ return world.getMaxSectionY(); + } + + public static int getMinSection(final LevelHeightAccessor world) { + return world.getMinSectionY(); + } + ++ public static int getMinSection(final Level world) { ++ return world.getMinSectionY(); ++ } ++ + public static int getMaxLightSection(final LevelHeightAccessor world) { + return getMaxSection(world) + 1; + } @@ -3926,6 +4312,221 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + throw new RuntimeException(); + } +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.paper; ++ ++import ca.spottedleaf.moonrise.common.PlatformHooks; ++import com.mojang.datafixers.DSL; ++import com.mojang.datafixers.DataFixer; ++import com.mojang.serialization.Dynamic; ++import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.GenerationChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import net.minecraft.world.level.chunk.storage.SerializableChunkData; ++import net.minecraft.world.level.entity.EntityTypeTest; ++import net.minecraft.world.phys.AABB; ++import java.util.List; ++import java.util.function.Predicate; ++ ++public final class PaperHooks implements PlatformHooks { ++ ++ @Override ++ public String getBrand() { ++ return "Paper"; ++ } ++ ++ @Override ++ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) { ++ return blockState.getLightEmission(); ++ } ++ ++ @Override ++ public Predicate maybeHasLightEmission() { ++ return (final BlockState state) -> { ++ return state.getLightEmission() != 0; ++ }; ++ } ++ ++ @Override ++ public boolean hasCurrentlyLoadingChunk() { ++ return false; ++ } ++ ++ @Override ++ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) { ++ return null; ++ } ++ ++ @Override ++ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) { ++ ++ } ++ ++ @Override ++ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) { ++ ++ } ++ ++ @Override ++ public boolean allowAsyncTicketUpdates() { ++ return true; ++ } ++ ++ @Override ++ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) { ++ ++ } ++ ++ @Override ++ public void chunkUnloadFromWorld(final LevelChunk chunk) { ++ ++ } ++ ++ @Override ++ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) { ++ ++ } ++ ++ @Override ++ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) { ++ ++ } ++ ++ @Override ++ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) { ++ ++ } ++ ++ @Override ++ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, final List into) { ++ ++ } ++ ++ @Override ++ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, final AABB boundingBox, final Predicate predicate, final List into, final int maxCount) { ++ ++ } ++ ++ @Override ++ public void entityMove(final Entity entity, final long oldSection, final long newSection) { ++ ++ } ++ ++ @Override ++ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) { ++ return true; ++ } ++ ++ @Override ++ public boolean configFixMC224294() { ++ return true; ++ } ++ ++ @Override ++ public boolean configAutoConfigSendDistance() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance; ++ } ++ ++ @Override ++ public double configPlayerMaxLoadRate() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; ++ } ++ ++ @Override ++ public double configPlayerMaxGenRate() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; ++ } ++ ++ @Override ++ public double configPlayerMaxSendRate() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; ++ } ++ ++ @Override ++ public int configPlayerMaxConcurrentLoads() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; ++ } ++ ++ @Override ++ public int configPlayerMaxConcurrentGens() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; ++ } ++ ++ @Override ++ public long configAutoSaveInterval(final ServerLevel world) { ++ return world.paperConfig().chunks.autoSaveInterval.value(); ++ } ++ ++ @Override ++ public int configMaxAutoSavePerTick(final ServerLevel world) { ++ return world.paperConfig().chunks.maxAutoSaveChunksPerTick; ++ } ++ ++ @Override ++ public boolean configFixMC159283() { ++ return true; ++ } ++ ++ @Override ++ public boolean forceNoSave(final ChunkAccess chunk) { ++ return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave; ++ } ++ ++ @Override ++ public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, ++ final int fromVersion, final int toVersion) { ++ return (CompoundTag)dataFixer.update( ++ type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion ++ ).getValue(); ++ } ++ ++ @Override ++ public boolean hasMainChunkLoadHook() { ++ return false; ++ } ++ ++ @Override ++ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) { ++ ++ } ++ ++ @Override ++ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities) { ++ return entities; ++ } ++ ++ @Override ++ public void unloadEntity(final Entity entity) { ++ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD); ++ } ++ ++ @Override ++ public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) { ++ net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities()); ++ } ++ ++ @Override ++ public int modifyEntityTrackingRange(final Entity entity, final int currentRange) { ++ return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange); ++ } ++} diff --git a/src/main/java/com/mojang/logging/LogUtils.java b/src/main/java/com/mojang/logging/LogUtils.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/com/mojang/logging/LogUtils.java @@ -3940,6 +4541,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end } +diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +@@ -0,0 +0,0 @@ public class GlobalConfiguration extends ConfigurationPart { + + @PostProcess + private void postProcess() { +- ++ ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads); + } + } + diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -4859,7 +5473,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return true; + } + -+ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, ++ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority, + java.util.function.Consumer> onLoad) { + if (Thread.currentThread() != this.thread) { + this.getChunkSource().mainThreadProcessor.execute(() -> { @@ -5513,6 +6127,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @Override public BlockState getBlockState(BlockPos pos) { int i = pos.getY(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java ++++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java +@@ -0,0 +0,0 @@ public class ChunkStatusTasks { + }, context.mainThreadExecutor()); + } + +- private static void postLoadProtoChunk(ServerLevel world, List entities) { ++ public static void postLoadProtoChunk(ServerLevel world, List entities) { // Paper - public + if (!entities.isEmpty()) { + // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities + world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> { diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java @@ -5522,15 +6149,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private boolean addEntity(T entity, boolean existing) { + // Paper start - chunk system hooks -+ if (existing) { -+ // I don't want to know why this is a generic type. -+ Entity entityCasted = (Entity)entity; -+ boolean wasRemoved = entityCasted.isRemoved(); -+ boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted); -+ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { -+ // removed by callback -+ return false; -+ } ++ // I don't want to know why this is a generic type. ++ Entity entityCasted = (Entity)entity; ++ boolean wasRemoved = entityCasted.isRemoved(); ++ boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, true); ++ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { ++ // removed by callback ++ return false; + } + // Paper end - chunk system hooks if (!this.addEntityUuid(entity)) { @@ -5587,11 +6212,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority; ++ ca.spottedleaf.concurrentutil.util.Priority priority; + if (urgent) { -+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER; ++ priority = ca.spottedleaf.concurrentutil.util.Priority.HIGHER; + } else { -+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL; ++ priority = ca.spottedleaf.concurrentutil.util.Priority.NORMAL; + } + + java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); @@ -5774,3 +6399,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 MONSTER, ANIMAL, RAIDER, +diff --git a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks +@@ -0,0 +1 @@ ++ca.spottedleaf.moonrise.paper.PaperHooks diff --git a/patches/server/More-Teleport-API.patch b/patches/server/More-Teleport-API.patch index 7dca058409..bb2235faa3 100644 --- a/patches/server/More-Teleport-API.patch +++ b/patches/server/More-Teleport-API.patch @@ -86,7 +86,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); + + world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), -+ this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER : ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, (list) -> { ++ this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.util.Priority.HIGHER : ca.spottedleaf.concurrentutil.util.Priority.NORMAL, (list) -> { + net.minecraft.server.level.ServerChunkCache chunkProviderServer = world.getChunkSource(); + for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) { + chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); diff --git a/patches/server/Optimize-BlockPosition-helper-methods.patch b/patches/server/Optimize-BlockPosition-helper-methods.patch index 8bac9e96e7..53dc54bc32 100644 --- a/patches/server/Optimize-BlockPosition-helper-methods.patch +++ b/patches/server/Optimize-BlockPosition-helper-methods.patch @@ -19,7 +19,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @Override public BlockPos above(int distance) { - return this.relative(Direction.UP, distance); -+ return distance == 0 ? this : new BlockPos(this.getX(), this.getY() + distance, this.getZ()); // Paper - Perf: Optimize BlockPosition ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() + distance, this.getZ()); // Paper - Perf: Optimize BlockPosition } @Override @@ -31,7 +31,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @Override public BlockPos below(int i) { - return this.relative(Direction.DOWN, i); -+ return i == 0 ? this : new BlockPos(this.getX(), this.getY() - i, this.getZ()); // Paper - Perf: Optimize BlockPosition ++ return i == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() - i, this.getZ()); // Paper - Perf: Optimize BlockPosition } @Override @@ -43,7 +43,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @Override public BlockPos north(int distance) { - return this.relative(Direction.NORTH, distance); -+ return distance == 0 ? this : new BlockPos(this.getX(), this.getY(), this.getZ() - distance); // Paper - Perf: Optimize BlockPosition ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() - distance); // Paper - Perf: Optimize BlockPosition } @Override @@ -55,7 +55,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @Override public BlockPos south(int distance) { - return this.relative(Direction.SOUTH, distance); -+ return distance == 0 ? this : new BlockPos(this.getX(), this.getY(), this.getZ() + distance); // Paper - Perf: Optimize BlockPosition ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() + distance); // Paper - Perf: Optimize BlockPosition } @Override @@ -67,7 +67,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @Override public BlockPos west(int distance) { - return this.relative(Direction.WEST, distance); -+ return distance == 0 ? this : new BlockPos(this.getX() - distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX() - distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition } @Override @@ -79,7 +79,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @Override public BlockPos east(int distance) { - return this.relative(Direction.EAST, distance); -+ return distance == 0 ? this : new BlockPos(this.getX() + distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX() + distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition } @Override diff --git a/patches/server/Paper-config-files.patch b/patches/server/Paper-config-files.patch index d44b5af17c..ad972ff6bc 100644 --- a/patches/server/Paper-config-files.patch +++ b/patches/server/Paper-config-files.patch @@ -523,6 +523,45 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public static GlobalConfiguration get() { + return instance; + } ++ ++ public ChunkLoadingBasic chunkLoadingBasic; ++ ++ public class ChunkLoadingBasic extends ConfigurationPart { ++ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.") ++ public double playerMaxChunkSendRate = 75.0; ++ ++ @Comment( ++ "The maximum rate at which chunks will load for any individual player. " + ++ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" + ++ "chunk is already generated. Set to -1 to disable this limit." ++ ) ++ public double playerMaxChunkLoadRate = 100.0; ++ ++ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.") ++ public double playerMaxChunkGenerateRate = -1.0; ++ } ++ ++ public ChunkLoadingAdvanced chunkLoadingAdvanced; ++ ++ public class ChunkLoadingAdvanced extends ConfigurationPart { ++ @Comment( ++ "Set to true if the server will match the chunk send radius that clients have configured" + ++ "in their view distance settings if the client is less-than the server's send distance." ++ ) ++ public boolean autoConfigSendDistance = true; ++ ++ @Comment( ++ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." + ++ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." ++ ) ++ public int playerMaxConcurrentChunkLoads = 0; ++ ++ @Comment( ++ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." + ++ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." ++ ) ++ public int playerMaxConcurrentChunkGenerates = 0; ++ } + static void set(GlobalConfiguration instance) { + GlobalConfiguration.instance = instance; + } @@ -638,21 +677,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public int incomingPacketThreshold = 300; + } + -+ public ChunkLoading chunkLoading; -+ -+ public class ChunkLoading extends ConfigurationPart { -+ public int minLoadRadius = 2; -+ public int maxConcurrentSends = 2; -+ public boolean autoconfigSendDistance = true; -+ public double targetPlayerChunkSendRate = 100.0; -+ public double globalMaxChunkSendRate = -1.0; -+ public boolean enableFrustumPriority = false; -+ public double globalMaxChunkLoadRate = -1.0; -+ public double playerMaxConcurrentLoads = 20.0; -+ public double globalMaxConcurrentLoads = 500.0; -+ public double playerMaxChunkLoadRate = -1.0; -+ } -+ + public UnsupportedSettings unsupportedSettings; + + public class UnsupportedSettings extends ConfigurationPart { @@ -711,7 +735,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @PostProcess + private void postProcess() { -+ //io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); ++ + } + } + diff --git a/patches/server/fixup-ConcurrentUtil.patch b/patches/server/fixup-ConcurrentUtil.patch deleted file mode 100644 index 640fe960cb..0000000000 --- a/patches/server/fixup-ConcurrentUtil.patch +++ /dev/null @@ -1,6177 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 21 Oct 2024 11:47:34 -0700 -Subject: [PATCH] fixup! ConcurrentUtil - - -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java -deleted file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java -+++ /dev/null -@@ -0,0 +0,0 @@ --package ca.spottedleaf.concurrentutil.collection; -- --import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; --import ca.spottedleaf.concurrentutil.util.Validate; -- --import java.lang.invoke.VarHandle; --import java.util.ConcurrentModificationException; -- --/** -- * Single reader thread single writer thread queue. The reader side of the queue is ordered by acquire semantics, -- * and the writer side of the queue is ordered by release semantics. -- */ --// TODO test --public class SRSWLinkedQueue { -- -- // always non-null -- protected LinkedNode head; -- -- // always non-null -- protected LinkedNode tail; -- -- /* IMPL NOTE: Leave hashCode and equals to their defaults */ -- -- public SRSWLinkedQueue() { -- final LinkedNode dummy = new LinkedNode<>(null, null); -- this.head = this.tail = dummy; -- } -- -- /** -- * Must be the reader thread. -- * -- *

-- * Returns, without removing, the first element of this queue. -- *

-- * @return Returns, without removing, the first element of this queue. -- */ -- public E peekFirst() { -- LinkedNode head = this.head; -- E ret = head.getElementPlain(); -- if (ret == null) { -- head = head.getNextAcquire(); -- if (head == null) { -- // empty -- return null; -- } -- // update head reference for next poll() call -- this.head = head; -- // guaranteed to be non-null -- ret = head.getElementPlain(); -- if (ret == null) { -- throw new ConcurrentModificationException("Multiple reader threads"); -- } -- } -- -- return ret; -- } -- -- /** -- * Must be the reader thread. -- * -- *

-- * Returns and removes the first element of this queue. -- *

-- * @return Returns and removes the first element of this queue. -- */ -- public E poll() { -- LinkedNode head = this.head; -- E ret = head.getElementPlain(); -- if (ret == null) { -- head = head.getNextAcquire(); -- if (head == null) { -- // empty -- return null; -- } -- // guaranteed to be non-null -- ret = head.getElementPlain(); -- if (ret == null) { -- throw new ConcurrentModificationException("Multiple reader threads"); -- } -- } -- -- head.setElementPlain(null); -- LinkedNode next = head.getNextAcquire(); -- this.head = next == null ? head : next; -- -- return ret; -- } -- -- /** -- * Must be the writer thread. -- * -- *

-- * Adds the element to the end of the queue. -- *

-- * -- * @throws NullPointerException If the provided element is null -- */ -- public void addLast(final E element) { -- Validate.notNull(element, "Provided element cannot be null"); -- final LinkedNode append = new LinkedNode<>(element, null); -- -- this.tail.setNextRelease(append); -- this.tail = append; -- } -- -- protected static final class LinkedNode { -- -- protected volatile Object element; -- protected volatile LinkedNode next; -- -- protected static final VarHandle ELEMENT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "element", Object.class); -- protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "next", LinkedNode.class); -- -- protected LinkedNode(final Object element, final LinkedNode next) { -- ELEMENT_HANDLE.set(this, element); -- NEXT_HANDLE.set(this, next); -- } -- -- /* element */ -- -- @SuppressWarnings("unchecked") -- protected final E getElementPlain() { -- return (E)ELEMENT_HANDLE.get(this); -- } -- -- protected final void setElementPlain(final E update) { -- ELEMENT_HANDLE.set(this, (Object)update); -- } -- /* next */ -- -- @SuppressWarnings("unchecked") -- protected final LinkedNode getNextPlain() { -- return (LinkedNode)NEXT_HANDLE.get(this); -- } -- -- @SuppressWarnings("unchecked") -- protected final LinkedNode getNextAcquire() { -- return (LinkedNode)NEXT_HANDLE.getAcquire(this); -- } -- -- protected final void setNextPlain(final LinkedNode next) { -- NEXT_HANDLE.set(this, next); -- } -- -- protected final void setNextRelease(final LinkedNode next) { -- NEXT_HANDLE.setRelease(this, next); -- } -- } --} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.completable; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import org.slf4j.Logger; -+import org.slf4j.LoggerFactory; -+import java.util.function.BiConsumer; -+ -+public final class CallbackCompletable { -+ -+ private static final Logger LOGGER = LoggerFactory.getLogger(CallbackCompletable.class); -+ -+ private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); -+ private T result; -+ private Throwable throwable; -+ private volatile boolean completed; -+ -+ public boolean isCompleted() { -+ return this.completed; -+ } -+ -+ /** -+ * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero -+ * synchronisation -+ */ -+ public T getResult() { -+ return this.result; -+ } -+ -+ /** -+ * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero -+ * synchronisation -+ */ -+ public Throwable getThrowable() { -+ return this.throwable; -+ } -+ -+ /** -+ * Adds a waiter that should only be completed asynchronously by the complete() calls. If complete() -+ * has already been called, returns {@code null} and does not invoke the specified consumer. -+ * @param consumer Consumer to be executed on completion -+ * @throws NullPointerException If consumer is null -+ * @return A cancellable which will control the execution of the specified consumer -+ */ -+ public Cancellable addAsynchronousWaiter(final BiConsumer consumer) { -+ if (this.waiters.add(consumer)) { -+ return new CancellableImpl(consumer); -+ } -+ return null; -+ } -+ -+ private void completeAllWaiters(final T result, final Throwable throwable) { -+ this.completed = true; -+ BiConsumer waiter; -+ while ((waiter = this.waiters.pollOrBlockAdds()) != null) { -+ this.completeWaiter(waiter, result, throwable); -+ } -+ } -+ -+ private void completeWaiter(final BiConsumer consumer, final T result, final Throwable throwable) { -+ try { -+ consumer.accept(result, throwable); -+ } catch (final Throwable throwable2) { -+ LOGGER.error("Failed to complete callback " + ConcurrentUtil.genericToString(consumer), throwable2); -+ } -+ } -+ -+ /** -+ * Adds a waiter that will be completed asynchronously by the complete() calls. If complete() -+ * has already been called, then invokes the consumer synchronously with the completed result. -+ * @param consumer Consumer to be executed on completion -+ * @throws NullPointerException If consumer is null -+ * @return A cancellable which will control the execution of the specified consumer -+ */ -+ public Cancellable addWaiter(final BiConsumer consumer) { -+ if (this.waiters.add(consumer)) { -+ return new CancellableImpl(consumer); -+ } -+ this.completeWaiter(consumer, this.result, this.throwable); -+ return new CancellableImpl(consumer); -+ } -+ -+ public void complete(final T result) { -+ this.result = result; -+ this.completeAllWaiters(result, null); -+ } -+ -+ public void completeWithThrowable(final Throwable throwable) { -+ if (throwable == null) { -+ throw new NullPointerException("Throwable cannot be null"); -+ } -+ this.throwable = throwable; -+ this.completeAllWaiters(null, throwable); -+ } -+ -+ private final class CancellableImpl implements Cancellable { -+ -+ private final BiConsumer waiter; -+ -+ private CancellableImpl(final BiConsumer waiter) { -+ this.waiter = waiter; -+ } -+ -+ @Override -+ public boolean cancel() { -+ return CallbackCompletable.this.waiters.remove(this.waiter); -+ } -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java -+++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java -@@ -0,0 +0,0 @@ - package ca.spottedleaf.concurrentutil.completable; - --import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; --import ca.spottedleaf.concurrentutil.executor.Cancellable; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; -+import java.lang.invoke.VarHandle; -+import java.util.concurrent.CompletableFuture; -+import java.util.concurrent.CompletionException; -+import java.util.concurrent.CompletionStage; -+import java.util.concurrent.Executor; -+import java.util.concurrent.ForkJoinPool; -+import java.util.concurrent.locks.LockSupport; - import java.util.function.BiConsumer; -+import java.util.function.BiFunction; -+import java.util.function.Consumer; -+import java.util.function.Function; -+import java.util.function.Supplier; - - public final class Completable { - - private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class); -+ private static final Function DEFAULT_EXCEPTION_HANDLER = (final Throwable thr) -> { -+ LOGGER.error("Unhandled exception during Completable operation", thr); -+ return thr; -+ }; - -- private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); -- private T result; -- private Throwable throwable; -- private volatile boolean completed; -+ public static Executor getDefaultExecutor() { -+ return ForkJoinPool.commonPool(); -+ } -+ -+ private static final Transform COMPLETED_STACK = new Transform<>(null, null, null, null) { -+ @Override -+ public void run() {} -+ }; -+ private volatile Transform completeStack; -+ private static final VarHandle COMPLETE_STACK_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "completeStack", Transform.class); -+ -+ private static final Object NULL_MASK = new Object(); -+ private volatile Object result; -+ private static final VarHandle RESULT_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "result", Object.class); - -- public boolean isCompleted() { -- return this.completed; -+ private Object getResultPlain() { -+ return (Object)RESULT_HANDLE.get(this); - } - -- /** -- * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero -- * synchronisation -- */ -- public T getResult() { -- return this.result; -+ private Object getResultVolatile() { -+ return (Object)RESULT_HANDLE.getVolatile(this); - } - -- /** -- * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero -- * synchronisation -- */ -- public Throwable getThrowable() { -- return this.throwable; -+ private void pushStackOrRun(final Transform push) { -+ int failures = 0; -+ for (Transform curr = (Transform)COMPLETE_STACK_HANDLE.getVolatile(this);;) { -+ if (curr == COMPLETED_STACK) { -+ push.execute(); -+ return; -+ } -+ -+ push.next = curr; -+ -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = (Transform)COMPLETE_STACK_HANDLE.compareAndExchange(this, curr, push))) { -+ return; -+ } -+ push.next = null; -+ ++failures; -+ } - } - -- /** -- * Adds a waiter that should only be completed asynchronously by the complete() calls. If complete() -- * has already been called, returns {@code null} and does not invoke the specified consumer. -- * @param consumer Consumer to be executed on completion -- * @throws NullPointerException If consumer is null -- * @return A cancellable which will control the execution of the specified consumer -- */ -- public Cancellable addAsynchronousWaiter(final BiConsumer consumer) { -- if (this.waiters.add(consumer)) { -- return new CancellableImpl(consumer); -+ private void propagateStack() { -+ Transform topStack = (Transform)COMPLETE_STACK_HANDLE.getAndSet(this, COMPLETED_STACK); -+ while (topStack != null) { -+ topStack.execute(); -+ topStack = topStack.next; - } -- return null; - } - -- private void completeAllWaiters(final T result, final Throwable throwable) { -- this.completed = true; -- BiConsumer waiter; -- while ((waiter = this.waiters.pollOrBlockAdds()) != null) { -- this.completeWaiter(waiter, result, throwable); -+ private static Object maskNull(final Object res) { -+ return res == null ? NULL_MASK : res; -+ } -+ -+ private static Object unmaskNull(final Object res) { -+ return res == NULL_MASK ? null : res; -+ } -+ -+ private static Executor checkExecutor(final Executor executor) { -+ return Validate.notNull(executor, "Executor may not be null"); -+ } -+ -+ public Completable() {} -+ -+ private Completable(final Object complete) { -+ COMPLETE_STACK_HANDLE.set(this, COMPLETED_STACK); -+ RESULT_HANDLE.setRelease(this, complete); -+ } -+ -+ public static Completable completed(final T value) { -+ return new Completable<>(maskNull(value)); -+ } -+ -+ public static Completable failed(final Throwable ex) { -+ Validate.notNull(ex, "Exception may not be null"); -+ -+ return new Completable<>(new ExceptionResult(ex)); -+ } -+ -+ public static Completable supplied(final Supplier supplier) { -+ return supplied(supplier, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public static Completable supplied(final Supplier supplier, final Function exceptionHandler) { -+ try { -+ return completed(supplier.get()); -+ } catch (final Throwable throwable) { -+ Throwable complete; -+ try { -+ complete = exceptionHandler.apply(throwable); -+ } catch (final Throwable thr2) { -+ throwable.addSuppressed(thr2); -+ complete = throwable; -+ } -+ return failed(complete); - } - } - -- private void completeWaiter(final BiConsumer consumer, final T result, final Throwable throwable) { -+ public static Completable suppliedAsync(final Supplier supplier, final Executor executor) { -+ return suppliedAsync(supplier, executor, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public static Completable suppliedAsync(final Supplier supplier, final Executor executor, final Function exceptionHandler) { -+ final Completable ret = new Completable<>(); -+ -+ class AsyncSuppliedCompletable implements Runnable, CompletableFuture.AsynchronousCompletionTask { -+ @Override -+ public void run() { -+ try { -+ ret.complete(supplier.get()); -+ } catch (final Throwable throwable) { -+ Throwable complete; -+ try { -+ complete = exceptionHandler.apply(throwable); -+ } catch (final Throwable thr2) { -+ throwable.addSuppressed(thr2); -+ complete = throwable; -+ } -+ ret.completeExceptionally(complete); -+ } -+ } -+ } -+ - try { -- consumer.accept(result, throwable); -- } catch (final ThreadDeath death) { -- throw death; -- } catch (final Throwable throwable2) { -- LOGGER.error("Failed to complete callback " + ConcurrentUtil.genericToString(consumer), throwable2); -+ executor.execute(new AsyncSuppliedCompletable()); -+ } catch (final Throwable throwable) { -+ Throwable complete; -+ try { -+ complete = exceptionHandler.apply(throwable); -+ } catch (final Throwable thr2) { -+ throwable.addSuppressed(thr2); -+ complete = throwable; -+ } -+ ret.completeExceptionally(complete); -+ } -+ -+ return ret; -+ } -+ -+ private boolean completeRaw(final Object value) { -+ if ((Object)RESULT_HANDLE.getVolatile(this) != null || !(boolean)RESULT_HANDLE.compareAndSet(this, (Object)null, value)) { -+ return false; -+ } -+ -+ this.propagateStack(); -+ return true; -+ } -+ -+ public boolean complete(final T result) { -+ return this.completeRaw(maskNull(result)); -+ } -+ -+ public boolean completeExceptionally(final Throwable exception) { -+ Validate.notNull(exception, "Exception may not be null"); -+ -+ return this.completeRaw(new ExceptionResult(exception)); -+ } -+ -+ public boolean isDone() { -+ return this.getResultVolatile() != null; -+ } -+ -+ public boolean isNormallyComplete() { -+ return this.getResultVolatile() != null && !(this.getResultVolatile() instanceof ExceptionResult); -+ } -+ -+ public boolean isExceptionallyComplete() { -+ return this.getResultVolatile() instanceof ExceptionResult; -+ } -+ -+ public Throwable getException() { -+ final Object res = this.getResultVolatile(); -+ if (res == null) { -+ return null; -+ } -+ -+ if (!(res instanceof ExceptionResult exRes)) { -+ throw new IllegalStateException("Not completed exceptionally"); -+ } -+ -+ return exRes.ex; -+ } -+ -+ public T getNow(final T dfl) throws CompletionException { -+ final Object res = this.getResultVolatile(); -+ if (res == null) { -+ return dfl; -+ } -+ -+ if (res instanceof ExceptionResult exRes) { -+ throw new CompletionException(exRes.ex); -+ } -+ -+ return (T)unmaskNull(res); -+ } -+ -+ public T join() throws CompletionException { -+ if (this.isDone()) { -+ return this.getNow(null); -+ } -+ -+ final UnparkTransform unparkTransform = new UnparkTransform<>(this, Thread.currentThread()); -+ -+ this.pushStackOrRun(unparkTransform); -+ -+ boolean interuptted = false; -+ while (!unparkTransform.isReleasable()) { -+ try { -+ ForkJoinPool.managedBlock(unparkTransform); -+ } catch (final InterruptedException ex) { -+ interuptted = true; -+ } -+ } -+ -+ if (interuptted) { -+ Thread.currentThread().interrupt(); -+ } -+ -+ return this.getNow(null); -+ } -+ -+ public CompletableFuture toFuture() { -+ final Object rawResult = this.getResultVolatile(); -+ if (rawResult != null) { -+ if (rawResult instanceof ExceptionResult exRes) { -+ return CompletableFuture.failedFuture(exRes.ex); -+ } else { -+ return CompletableFuture.completedFuture((T)unmaskNull(rawResult)); -+ } -+ } -+ -+ final CompletableFuture ret = new CompletableFuture<>(); -+ -+ class ToFuture implements BiConsumer { -+ -+ @Override -+ public void accept(final T res, final Throwable ex) { -+ if (ex != null) { -+ ret.completeExceptionally(ex); -+ } else { -+ ret.complete(res); -+ } -+ } -+ } -+ -+ this.whenComplete(new ToFuture()); -+ -+ return ret; -+ } -+ -+ public static Completable fromFuture(final CompletionStage stage) { -+ final Completable ret = new Completable<>(); -+ -+ class FromFuture implements BiConsumer { -+ @Override -+ public void accept(final T res, final Throwable ex) { -+ if (ex != null) { -+ ret.completeExceptionally(ex); -+ } else { -+ ret.complete(res); -+ } -+ } -+ } -+ -+ stage.whenComplete(new FromFuture()); -+ -+ return ret; -+ } -+ -+ -+ public Completable thenApply(final Function function) { -+ return this.thenApply(function, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable thenApply(final Function function, final Function exceptionHandler) { -+ Validate.notNull(function, "Function may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new ApplyTransform<>(null, this, ret, exceptionHandler, function)); -+ return ret; -+ } -+ -+ public Completable thenApplyAsync(final Function function) { -+ return this.thenApplyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable thenApplyAsync(final Function function, final Executor executor) { -+ return this.thenApplyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable thenApplyAsync(final Function function, final Executor executor, final Function exceptionHandler) { -+ Validate.notNull(function, "Function may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new ApplyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); -+ return ret; -+ } -+ -+ -+ public Completable thenAccept(final Consumer consumer) { -+ return this.thenAccept(consumer, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable thenAccept(final Consumer consumer, final Function exceptionHandler) { -+ Validate.notNull(consumer, "Consumer may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new AcceptTransform<>(null, this, ret, exceptionHandler, consumer)); -+ return ret; -+ } -+ -+ public Completable thenAcceptAsync(final Consumer consumer) { -+ return this.thenAcceptAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable thenAcceptAsync(final Consumer consumer, final Executor executor) { -+ return this.thenAcceptAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable thenAcceptAsync(final Consumer consumer, final Executor executor, final Function exceptionHandler) { -+ Validate.notNull(consumer, "Consumer may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new AcceptTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer)); -+ return ret; -+ } -+ -+ -+ public Completable thenRun(final Runnable run) { -+ return this.thenRun(run, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable thenRun(final Runnable run, final Function exceptionHandler) { -+ Validate.notNull(run, "Run may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new RunTransform<>(null, this, ret, exceptionHandler, run)); -+ return ret; -+ } -+ -+ public Completable thenRunAsync(final Runnable run) { -+ return this.thenRunAsync(run, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable thenRunAsync(final Runnable run, final Executor executor) { -+ return this.thenRunAsync(run, executor, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable thenRunAsync(final Runnable run, final Executor executor, final Function exceptionHandler) { -+ Validate.notNull(run, "Run may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new RunTransform<>(checkExecutor(executor), this, ret, exceptionHandler, run)); -+ return ret; -+ } -+ -+ -+ public Completable handle(final BiFunction function) { -+ return this.handle(function, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable handle(final BiFunction function, -+ final Function exceptionHandler) { -+ Validate.notNull(function, "Function may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new HandleTransform<>(null, this, ret, exceptionHandler, function)); -+ return ret; -+ } -+ -+ public Completable handleAsync(final BiFunction function) { -+ return this.handleAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable handleAsync(final BiFunction function, -+ final Executor executor) { -+ return this.handleAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable handleAsync(final BiFunction function, -+ final Executor executor, -+ final Function exceptionHandler) { -+ Validate.notNull(function, "Function may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new HandleTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); -+ return ret; -+ } -+ -+ -+ public Completable whenComplete(final BiConsumer consumer) { -+ return this.whenComplete(consumer, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable whenComplete(final BiConsumer consumer, final Function exceptionHandler) { -+ Validate.notNull(consumer, "Consumer may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new WhenTransform<>(null, this, ret, exceptionHandler, consumer)); -+ return ret; -+ } -+ -+ public Completable whenCompleteAsync(final BiConsumer consumer) { -+ return this.whenCompleteAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable whenCompleteAsync(final BiConsumer consumer, final Executor executor) { -+ return this.whenCompleteAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable whenCompleteAsync(final BiConsumer consumer, final Executor executor, -+ final Function exceptionHandler) { -+ Validate.notNull(consumer, "Consumer may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new WhenTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer)); -+ return ret; -+ } -+ -+ -+ public Completable exceptionally(final Function function) { -+ return this.exceptionally(function, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable exceptionally(final Function function, final Function exceptionHandler) { -+ Validate.notNull(function, "Function may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new ExceptionallyTransform<>(null, this, ret, exceptionHandler, function)); -+ return ret; -+ } -+ -+ public Completable exceptionallyAsync(final Function function) { -+ return this.exceptionallyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable exceptionallyAsync(final Function function, final Executor executor) { -+ return this.exceptionallyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); -+ } -+ -+ public Completable exceptionallyAsync(final Function function, final Executor executor, -+ final Function exceptionHandler) { -+ Validate.notNull(function, "Function may not be null"); -+ Validate.notNull(exceptionHandler, "Exception handler may not be null"); -+ -+ final Completable ret = new Completable<>(); -+ this.pushStackOrRun(new ExceptionallyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); -+ return ret; -+ } -+ -+ private static final class ExceptionResult { -+ public final Throwable ex; -+ -+ public ExceptionResult(final Throwable ex) { -+ this.ex = ex; - } - } - -- /** -- * Adds a waiter that will be completed asynchronously by the complete() calls. If complete() -- * has already been called, then invokes the consumer synchronously with the completed result. -- * @param consumer Consumer to be executed on completion -- * @throws NullPointerException If consumer is null -- * @return A cancellable which will control the execution of the specified consumer -- */ -- public Cancellable addWaiter(final BiConsumer consumer) { -- if (this.waiters.add(consumer)) { -- return new CancellableImpl(consumer); -+ private static abstract class Transform implements Runnable, CompletableFuture.AsynchronousCompletionTask { -+ -+ private Transform next; -+ -+ private final Executor executor; -+ protected final Completable from; -+ protected final Completable to; -+ protected final Function exceptionHandler; -+ -+ protected Transform(final Executor executor, final Completable from, final Completable to, -+ final Function exceptionHandler) { -+ this.executor = executor; -+ this.from = from; -+ this.to = to; -+ this.exceptionHandler = exceptionHandler; -+ } -+ -+ // force interface call to become virtual call -+ @Override -+ public abstract void run(); -+ -+ protected void failed(final Throwable throwable) { -+ Throwable complete; -+ try { -+ complete = this.exceptionHandler.apply(throwable); -+ } catch (final Throwable thr2) { -+ throwable.addSuppressed(thr2); -+ complete = throwable; -+ } -+ this.to.completeExceptionally(complete); -+ } -+ -+ public void execute() { -+ if (this.executor == null) { -+ this.run(); -+ return; -+ } -+ -+ try { -+ this.executor.execute(this); -+ } catch (final Throwable throwable) { -+ this.failed(throwable); -+ } -+ } -+ } -+ -+ private static final class ApplyTransform extends Transform { -+ -+ private final Function function; -+ -+ public ApplyTransform(final Executor executor, final Completable from, final Completable to, -+ final Function exceptionHandler, -+ final Function function) { -+ super(executor, from, to, exceptionHandler); -+ this.function = function; -+ } -+ -+ @Override -+ public void run() { -+ final Object result = this.from.getResultPlain(); -+ try { -+ if (result instanceof ExceptionResult exRes) { -+ this.to.completeExceptionally(exRes.ex); -+ } else { -+ this.to.complete(this.function.apply((T)unmaskNull(result))); -+ } -+ } catch (final Throwable throwable) { -+ this.failed(throwable); -+ } - } -- this.completeWaiter(consumer, this.result, this.throwable); -- return new CancellableImpl(consumer); - } - -- public void complete(final T result) { -- this.result = result; -- this.completeAllWaiters(result, null); -+ private static final class AcceptTransform extends Transform { -+ private final Consumer consumer; -+ -+ public AcceptTransform(final Executor executor, final Completable from, final Completable to, -+ final Function exceptionHandler, -+ final Consumer consumer) { -+ super(executor, from, to, exceptionHandler); -+ this.consumer = consumer; -+ } -+ -+ @Override -+ public void run() { -+ final Object result = this.from.getResultPlain(); -+ try { -+ if (result instanceof ExceptionResult exRes) { -+ this.to.completeExceptionally(exRes.ex); -+ } else { -+ this.consumer.accept((T)unmaskNull(result)); -+ this.to.complete(null); -+ } -+ } catch (final Throwable throwable) { -+ this.failed(throwable); -+ } -+ } - } - -- public void completeWithThrowable(final Throwable throwable) { -- if (throwable == null) { -- throw new NullPointerException("Throwable cannot be null"); -+ private static final class RunTransform extends Transform { -+ private final Runnable run; -+ -+ public RunTransform(final Executor executor, final Completable from, final Completable to, -+ final Function exceptionHandler, -+ final Runnable run) { -+ super(executor, from, to, exceptionHandler); -+ this.run = run; -+ } -+ -+ @Override -+ public void run() { -+ final Object result = this.from.getResultPlain(); -+ try { -+ if (result instanceof ExceptionResult exRes) { -+ this.to.completeExceptionally(exRes.ex); -+ } else { -+ this.run.run(); -+ this.to.complete(null); -+ } -+ } catch (final Throwable throwable) { -+ this.failed(throwable); -+ } - } -- this.throwable = throwable; -- this.completeAllWaiters(null, throwable); - } - -- private final class CancellableImpl implements Cancellable { -+ private static final class HandleTransform extends Transform { -+ -+ private final BiFunction function; -+ -+ public HandleTransform(final Executor executor, final Completable from, final Completable to, -+ final Function exceptionHandler, -+ final BiFunction function) { -+ super(executor, from, to, exceptionHandler); -+ this.function = function; -+ } - -- private final BiConsumer waiter; -+ @Override -+ public void run() { -+ final Object result = this.from.getResultPlain(); -+ try { -+ if (result instanceof ExceptionResult exRes) { -+ this.to.complete(this.function.apply(null, exRes.ex)); -+ } else { -+ this.to.complete(this.function.apply((T)unmaskNull(result), null)); -+ } -+ } catch (final Throwable throwable) { -+ this.failed(throwable); -+ } -+ } -+ } -+ -+ private static final class WhenTransform extends Transform { -+ -+ private final BiConsumer consumer; -+ -+ public WhenTransform(final Executor executor, final Completable from, final Completable to, -+ final Function exceptionHandler, -+ final BiConsumer consumer) { -+ super(executor, from, to, exceptionHandler); -+ this.consumer = consumer; -+ } -+ -+ @Override -+ public void run() { -+ final Object result = this.from.getResultPlain(); -+ try { -+ if (result instanceof ExceptionResult exRes) { -+ this.consumer.accept(null, exRes.ex); -+ this.to.completeExceptionally(exRes.ex); -+ } else { -+ final T unmasked = (T)unmaskNull(result); -+ this.consumer.accept(unmasked, null); -+ this.to.complete(unmasked); -+ } -+ } catch (final Throwable throwable) { -+ this.failed(throwable); -+ } -+ } -+ } -+ -+ private static final class ExceptionallyTransform extends Transform { -+ private final Function function; -+ -+ public ExceptionallyTransform(final Executor executor, final Completable from, final Completable to, -+ final Function exceptionHandler, -+ final Function function) { -+ super(executor, from, to, exceptionHandler); -+ this.function = function; -+ } -+ -+ @Override -+ public void run() { -+ final Object result = this.from.getResultPlain(); -+ try { -+ if (result instanceof ExceptionResult exRes) { -+ this.to.complete(this.function.apply(exRes.ex)); -+ } else { -+ this.to.complete((T)unmaskNull(result)); -+ } -+ } catch (final Throwable throwable) { -+ this.failed(throwable); -+ } -+ } -+ } -+ -+ private static final class UnparkTransform extends Transform implements ForkJoinPool.ManagedBlocker { -+ -+ private volatile Thread thread; -+ -+ public UnparkTransform(final Completable from, final Thread target) { -+ super(null, from, null, null); -+ this.thread = target; -+ } -+ -+ @Override -+ public void run() { -+ final Thread t = this.thread; -+ this.thread = null; -+ LockSupport.unpark(t); -+ } -+ -+ @Override -+ public boolean block() throws InterruptedException { -+ while (!this.isReleasable()) { -+ if (Thread.interrupted()) { -+ throw new InterruptedException(); -+ } -+ LockSupport.park(this); -+ } - -- private CancellableImpl(final BiConsumer waiter) { -- this.waiter = waiter; -+ return true; - } - - @Override -- public boolean cancel() { -- return Completable.this.waiters.remove(this.waiter); -+ public boolean isReleasable() { -+ return this.thread == null; - } - } - } -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java -deleted file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java -+++ /dev/null -@@ -0,0 +0,0 @@ --package ca.spottedleaf.concurrentutil.executor; -- --import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; --import java.util.function.BooleanSupplier; -- --/** -- * Base implementation for an abstract queue of tasks which are executed either synchronously or asynchronously. -- * -- *

-- * The implementation supports tracking task executions using {@link #getTotalTasksScheduled()} and -- * {@link #getTotalTasksExecuted()}, and optionally shutting down the executor using {@link #shutdown()} -- *

-- * -- *

-- * The base implementation does not provide a method to queue a task for execution, rather that is specified in -- * the specific implementation. However, it is required that a specific implementation provides a method to -- * queue a task or create a task. A queued task is one which will eventually be executed, -- * and a created task must be queued to execute via {@link BaseTask#queue()} or be executed manually via -- * {@link BaseTask#execute()}. This choice of delaying the queueing of a task may be useful to provide a task handle -- * which may be cancelled or adjusted before the actual real task logic is ready to be executed. -- *

-- */ --public interface BaseExecutor { -- -- /** -- * Returns whether every task scheduled to this queue has been removed and executed or cancelled. If no tasks have been queued, -- * returns {@code true}. -- * -- * @return {@code true} if all tasks that have been queued have finished executing or no tasks have been queued, {@code false} otherwise. -- */ -- public default boolean haveAllTasksExecuted() { -- // order is important -- // if new tasks are scheduled between the reading of these variables, scheduled is guaranteed to be higher - -- // so our check fails, and we try again -- final long completed = this.getTotalTasksExecuted(); -- final long scheduled = this.getTotalTasksScheduled(); -- -- return completed == scheduled; -- } -- -- /** -- * Returns the number of tasks that have been scheduled or execute or are pending to be scheduled. -- */ -- public long getTotalTasksScheduled(); -- -- /** -- * Returns the number of tasks that have fully been executed. -- */ -- public long getTotalTasksExecuted(); -- -- /** -- * Waits until this queue has had all of its tasks executed (NOT removed). See {@link #haveAllTasksExecuted()} -- *

-- * This call is most effective after a {@link #shutdown()} call, as the shutdown call guarantees no tasks can -- * be executed and the waitUntilAllExecuted call makes sure the queue is empty. Effectively, using shutdown then using -- * waitUntilAllExecuted ensures this queue is empty - and most importantly, will remain empty. -- *

-- *

-- * This method is not guaranteed to be immediately responsive to queue state, so calls may take significantly more -- * time than expected. Effectively, do not rely on this call being fast - even if there are few tasks scheduled. -- *

-- *

-- * Note: Interruptions to the the current thread have no effect. Interrupt status is also not affected by this call. -- *

-- * -- * @throws IllegalStateException If the current thread is not allowed to wait -- */ -- public default void waitUntilAllExecuted() throws IllegalStateException { -- long failures = 1L; // start at 0.25ms -- -- while (!this.haveAllTasksExecuted()) { -- Thread.yield(); -- failures = ConcurrentUtil.linearLongBackoff(failures, 250_000L, 5_000_000L); // 500us, 5ms -- } -- } -- -- /** -- * Executes the next available task. -- * -- * @return {@code true} if a task was executed, {@code false} otherwise -- * @throws IllegalStateException If the current thread is not allowed to execute a task -- */ -- public boolean executeTask() throws IllegalStateException; -- -- /** -- * Executes all queued tasks. -- * -- * @return {@code true} if a task was executed, {@code false} otherwise -- * @throws IllegalStateException If the current thread is not allowed to execute a task -- */ -- public default boolean executeAll() { -- if (!this.executeTask()) { -- return false; -- } -- -- while (this.executeTask()); -- -- return true; -- } -- -- /** -- * Waits and executes tasks until the condition returns {@code true}. -- *

-- * WARNING: This function is not suitable for waiting until a deadline! -- * Use {@link #executeUntil(long)} or {@link #executeConditionally(BooleanSupplier, long)} instead. -- *

-- */ -- public default void executeConditionally(final BooleanSupplier condition) { -- long failures = 0; -- while (!condition.getAsBoolean()) { -- if (this.executeTask()) { -- failures = failures >>> 2; -- } else { -- failures = ConcurrentUtil.linearLongBackoff(failures, 100_000L, 10_000_000L); // 100us, 10ms -- } -- } -- } -- -- /** -- * Waits and executes tasks until the condition returns {@code true} or {@code System.nanoTime() - deadline >= 0}. -- */ -- public default void executeConditionally(final BooleanSupplier condition, final long deadline) { -- long failures = 0; -- // double check deadline; we don't know how expensive the condition is -- while ((System.nanoTime() - deadline < 0L) && !condition.getAsBoolean() && (System.nanoTime() - deadline < 0L)) { -- if (this.executeTask()) { -- failures = failures >>> 2; -- } else { -- failures = ConcurrentUtil.linearLongBackoffDeadline(failures, 100_000L, 10_000_000L, deadline); // 100us, 10ms -- } -- } -- } -- -- /** -- * Waits and executes tasks until {@code System.nanoTime() - deadline >= 0}. -- */ -- public default void executeUntil(final long deadline) { -- long failures = 0; -- while (System.nanoTime() - deadline < 0L) { -- if (this.executeTask()) { -- failures = failures >>> 2; -- } else { -- failures = ConcurrentUtil.linearLongBackoffDeadline(failures, 100_000L, 10_000_000L, deadline); // 100us, 10ms -- } -- } -- } -- -- /** -- * Prevent further additions to this queue. Attempts to add after this call has completed (potentially during) will -- * result in {@link IllegalStateException} being thrown. -- *

-- * This operation is atomic with respect to other shutdown calls -- *

-- *

-- * After this call has completed, regardless of return value, this queue will be shutdown. -- *

-- * -- * @return {@code true} if the queue was shutdown, {@code false} if it has shut down already -- * @throws UnsupportedOperationException If this queue does not support shutdown -- * @see #isShutdown() -- */ -- public default boolean shutdown() throws UnsupportedOperationException { -- throw new UnsupportedOperationException(); -- } -- -- /** -- * Returns whether this queue has shut down. Effectively, whether new tasks will be rejected - this method -- * does not indicate whether all the tasks scheduled have been executed. -- * @return Returns whether this queue has shut down. -- * @see #waitUntilAllExecuted() -- */ -- public default boolean isShutdown() { -- return false; -- } -- -- /** -- * Task object returned for any {@link BaseExecutor} scheduled task. -- * @see BaseExecutor -- */ -- public static interface BaseTask extends Cancellable { -- -- /** -- * Causes a lazily queued task to become queued or executed -- * -- * @throws IllegalStateException If the backing queue has shutdown -- * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed -- */ -- public boolean queue(); -- -- /** -- * Forces this task to be marked as completed. -- * -- * @return {@code true} if the task was cancelled, {@code false} if the task has already completed or is being completed. -- */ -- @Override -- public boolean cancel(); -- -- /** -- * Executes this task. This will also mark the task as completing. -- *

-- * Exceptions thrown from the runnable will be rethrown. -- *

-- * -- * @return {@code true} if this task was executed, {@code false} if it was already marked as completed. -- */ -- public boolean execute(); -- } --} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.executor; -+ -+import ca.spottedleaf.concurrentutil.util.Priority; -+ -+public interface PrioritisedExecutor { -+ -+ /** -+ * Returns the number of tasks that have been scheduled are pending to be scheduled. -+ */ -+ public long getTotalTasksScheduled(); -+ -+ /** -+ * Returns the number of tasks that have been executed. -+ */ -+ public long getTotalTasksExecuted(); -+ -+ /** -+ * Generates the next suborder id. -+ * @return The next suborder id. -+ */ -+ public long generateNextSubOrder(); -+ -+ /** -+ * Executes the next available task. -+ *

-+ * If there is a task with priority {@link Priority#BLOCKING} available, then that such task is executed. -+ *

-+ *

-+ * If there is a task with priority {@link Priority#IDLE} available then that task is only executed -+ * when there are no other tasks available with a higher priority. -+ *

-+ *

-+ * If there are no tasks that have priority {@link Priority#BLOCKING} or {@link Priority#IDLE}, then -+ * this function will be biased to execute tasks that have higher priorities. -+ *

-+ * -+ * @return {@code true} if a task was executed, {@code false} otherwise -+ * @throws IllegalStateException If the current thread is not allowed to execute a task -+ */ -+ public boolean executeTask() throws IllegalStateException; -+ -+ /** -+ * Prevent further additions to this executor. Attempts to add after this call has completed (potentially during) will -+ * result in {@link IllegalStateException} being thrown. -+ *

-+ * This operation is atomic with respect to other shutdown calls -+ *

-+ *

-+ * After this call has completed, regardless of return value, this executor will be shutdown. -+ *

-+ * -+ * @return {@code true} if the executor was shutdown, {@code false} if it has shut down already -+ * @see #isShutdown() -+ */ -+ public boolean shutdown(); -+ -+ /** -+ * Returns whether this executor has shut down. Effectively, returns whether new tasks will be rejected. -+ * This method does not indicate whether all the tasks scheduled have been executed. -+ * @return Returns whether this executor has shut down. -+ */ -+ public boolean isShutdown(); -+ -+ /** -+ * Queues or executes a task at {@link Priority#NORMAL} priority. -+ * @param task The task to run. -+ * -+ * @throws IllegalStateException If this executor has shutdown. -+ * @throws NullPointerException If the task is null -+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -+ * associated with the parameter -+ */ -+ public PrioritisedTask queueTask(final Runnable task); -+ -+ /** -+ * Queues or executes a task. -+ * -+ * @param task The task to run. -+ * @param priority The priority for the task. -+ * -+ * @throws IllegalStateException If this executor has shutdown. -+ * @throws NullPointerException If the task is null -+ * @throws IllegalArgumentException If the priority is invalid. -+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -+ * associated with the parameter -+ */ -+ public PrioritisedTask queueTask(final Runnable task, final Priority priority); -+ -+ /** -+ * Queues or executes a task. -+ * -+ * @param task The task to run. -+ * @param priority The priority for the task. -+ * @param subOrder The task's suborder. -+ * -+ * @throws IllegalStateException If this executor has shutdown. -+ * @throws NullPointerException If the task is null -+ * @throws IllegalArgumentException If the priority is invalid. -+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -+ * associated with the parameter -+ */ -+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder); -+ -+ /** -+ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. -+ * @param task The task to run. -+ * -+ * @throws NullPointerException If the task is null -+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -+ * associated with the parameter -+ */ -+ public PrioritisedTask createTask(final Runnable task); -+ -+ /** -+ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. -+ * -+ * @param task The task to run. -+ * @param priority The priority for the task. -+ * -+ * @throws NullPointerException If the task is null -+ * @throws IllegalArgumentException If the priority is invalid. -+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -+ * associated with the parameter -+ */ -+ public PrioritisedTask createTask(final Runnable task, final Priority priority); -+ -+ /** -+ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. -+ * -+ * @param task The task to run. -+ * @param priority The priority for the task. -+ * @param subOrder The task's suborder. -+ * -+ * @throws NullPointerException If the task is null -+ * @throws IllegalArgumentException If the priority is invalid. -+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -+ * associated with the parameter -+ */ -+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder); -+ -+ public static interface PrioritisedTask extends Cancellable { -+ -+ /** -+ * Returns the executor associated with this task. -+ * @return The executor associated with this task. -+ */ -+ public PrioritisedExecutor getExecutor(); -+ -+ /** -+ * Causes a lazily queued task to become queued or executed -+ * -+ * @throws IllegalStateException If the backing executor has shutdown -+ * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed -+ */ -+ public boolean queue(); -+ -+ /** -+ * Returns whether this task has been queued and is not completing. -+ * @return {@code true} If the task has been queued, {@code false} if the task has not been queued or is marked -+ * as completing. -+ */ -+ public boolean isQueued(); -+ -+ /** -+ * Forces this task to be marked as completed. -+ * -+ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed -+ * or is being completed. -+ */ -+ @Override -+ public boolean cancel(); -+ -+ /** -+ * Executes this task. This will also mark the task as completing. -+ *

-+ * Exceptions thrown from the runnable will be rethrown. -+ *

-+ * -+ * @return {@code true} if this task was executed, {@code false} if it was already marked as completed. -+ */ -+ public boolean execute(); -+ -+ /** -+ * Returns the current priority. Note that {@link Priority#COMPLETING} will be returned -+ * if this task is completing or has completed. -+ */ -+ public Priority getPriority(); -+ -+ /** -+ * Attempts to set this task's priority level to the level specified. -+ * -+ * @param priority Specified priority level. -+ * -+ * @throws IllegalArgumentException If the priority is invalid -+ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue -+ * this task was scheduled on was shutdown, or if the priority was already at the specified level. -+ */ -+ public boolean setPriority(final Priority priority); -+ -+ /** -+ * Attempts to raise the priority to the priority level specified. -+ * -+ * @param priority Priority specified -+ * -+ * @throws IllegalArgumentException If the priority is invalid -+ * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the -+ * specified level or was already at the specified level or higher. -+ */ -+ public boolean raisePriority(final Priority priority); -+ -+ /** -+ * Attempts to lower the priority to the priority level specified. -+ * -+ * @param priority Priority specified -+ * -+ * @throws IllegalArgumentException If the priority is invalid -+ * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the -+ * specified level or was already at the specified level or lower. -+ */ -+ public boolean lowerPriority(final Priority priority); -+ -+ /** -+ * Returns the suborder id associated with this task. -+ * @return The suborder id associated with this task. -+ */ -+ public long getSubOrder(); -+ -+ /** -+ * Sets the suborder id associated with this task. Ths function has no effect when this task -+ * is completing or is completed. -+ * -+ * @param subOrder Specified new sub order. -+ * -+ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue -+ * this task was scheduled on was shutdown, or if the current suborder is the same as the new sub order. -+ */ -+ public boolean setSubOrder(final long subOrder); -+ -+ /** -+ * Attempts to raise the suborder to the suborder specified. -+ * -+ * @param subOrder Specified new sub order. -+ * -+ * @return {@code false} if the current task is completing, {@code true} if the suborder was raised to the -+ * specified suborder or was already at the specified suborder or higher. -+ */ -+ public boolean raiseSubOrder(final long subOrder); -+ -+ /** -+ * Attempts to lower the suborder to the suborder specified. -+ * -+ * @param subOrder Specified new sub order. -+ * -+ * @return {@code false} if the current task is completing, {@code true} if the suborder was lowered to the -+ * specified suborder or was already at the specified suborder or lower. -+ */ -+ public boolean lowerSubOrder(final long subOrder); -+ -+ /** -+ * Sets the priority and suborder id associated with this task. Ths function has no effect when this task -+ * is completing or is completed. -+ * -+ * @param priority Priority specified -+ * @param subOrder Specified new sub order. -+ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue -+ * this task was scheduled on was shutdown, or if the current priority and suborder are the same as -+ * the parameters. -+ */ -+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.executor.queue; -+ -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import java.lang.invoke.VarHandle; -+import java.util.Comparator; -+import java.util.Map; -+import java.util.concurrent.ConcurrentSkipListMap; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.atomic.AtomicLong; -+ -+public final class PrioritisedTaskQueue implements PrioritisedExecutor { -+ -+ /** -+ * Required for tie-breaking in the queue -+ */ -+ private final AtomicLong taskIdGenerator = new AtomicLong(); -+ private final AtomicLong scheduledTasks = new AtomicLong(); -+ private final AtomicLong executedTasks = new AtomicLong(); -+ private final AtomicLong subOrderGenerator = new AtomicLong(); -+ private final AtomicBoolean shutdown = new AtomicBoolean(); -+ private final ConcurrentSkipListMap tasks = new ConcurrentSkipListMap<>(PrioritisedQueuedTask.COMPARATOR); -+ -+ @Override -+ public long getTotalTasksScheduled() { -+ return this.scheduledTasks.get(); -+ } -+ -+ @Override -+ public long getTotalTasksExecuted() { -+ return this.executedTasks.get(); -+ } -+ -+ @Override -+ public long generateNextSubOrder() { -+ return this.subOrderGenerator.getAndIncrement(); -+ } -+ -+ @Override -+ public boolean shutdown() { -+ return !this.shutdown.getAndSet(true); -+ } -+ -+ @Override -+ public boolean isShutdown() { -+ return this.shutdown.get(); -+ } -+ -+ public PrioritisedTask peekFirst() { -+ final Map.Entry firstEntry = this.tasks.firstEntry(); -+ return firstEntry == null ? null : firstEntry.getKey().task; -+ } -+ -+ public Priority getHighestPriority() { -+ final Map.Entry firstEntry = this.tasks.firstEntry(); -+ return firstEntry == null ? null : Priority.getPriority(firstEntry.getKey().priority); -+ } -+ -+ public boolean hasNoScheduledTasks() { -+ final long executedTasks = this.executedTasks.get(); -+ final long scheduledTasks = this.scheduledTasks.get(); -+ -+ return executedTasks == scheduledTasks; -+ } -+ -+ public PrioritySubOrderPair getHighestPrioritySubOrder() { -+ final Map.Entry firstEntry = this.tasks.firstEntry(); -+ if (firstEntry == null) { -+ return null; -+ } -+ -+ final PrioritisedQueuedTask.Holder holder = firstEntry.getKey(); -+ -+ return new PrioritySubOrderPair(Priority.getPriority(holder.priority), holder.subOrder); -+ } -+ -+ public Runnable pollTask() { -+ for (;;) { -+ final Map.Entry firstEntry = this.tasks.pollFirstEntry(); -+ if (firstEntry != null) { -+ final PrioritisedQueuedTask.Holder task = firstEntry.getKey(); -+ task.markRemoved(); -+ if (!task.task.cancel()) { -+ continue; -+ } -+ return task.task.execute; -+ } -+ -+ return null; -+ } -+ } -+ -+ @Override -+ public boolean executeTask() { -+ for (;;) { -+ final Map.Entry firstEntry = this.tasks.pollFirstEntry(); -+ if (firstEntry != null) { -+ final PrioritisedQueuedTask.Holder task = firstEntry.getKey(); -+ task.markRemoved(); -+ if (!task.task.execute()) { -+ continue; -+ } -+ return true; -+ } -+ -+ return false; -+ } -+ } -+ -+ @Override -+ public PrioritisedTask createTask(final Runnable task) { -+ return this.createTask(task, Priority.NORMAL, this.generateNextSubOrder()); -+ } -+ -+ @Override -+ public PrioritisedTask createTask(final Runnable task, final Priority priority) { -+ return this.createTask(task, priority, this.generateNextSubOrder()); -+ } -+ -+ @Override -+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { -+ return new PrioritisedQueuedTask(task, priority, subOrder); -+ } -+ -+ @Override -+ public PrioritisedTask queueTask(final Runnable task) { -+ return this.queueTask(task, Priority.NORMAL, this.generateNextSubOrder()); -+ } -+ -+ @Override -+ public PrioritisedTask queueTask(final Runnable task, final Priority priority) { -+ return this.queueTask(task, priority, this.generateNextSubOrder()); -+ } -+ -+ @Override -+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { -+ final PrioritisedQueuedTask ret = new PrioritisedQueuedTask(task, priority, subOrder); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ private final class PrioritisedQueuedTask implements PrioritisedExecutor.PrioritisedTask { -+ public static final Comparator COMPARATOR = (final PrioritisedQueuedTask.Holder t1, final PrioritisedQueuedTask.Holder t2) -> { -+ final int priorityCompare = t1.priority - t2.priority; -+ if (priorityCompare != 0) { -+ return priorityCompare; -+ } -+ -+ final int subOrderCompare = Long.compare(t1.subOrder, t2.subOrder); -+ if (subOrderCompare != 0) { -+ return subOrderCompare; -+ } -+ -+ return Long.compare(t1.id, t2.id); -+ }; -+ -+ private static final class Holder { -+ private final PrioritisedQueuedTask task; -+ private final int priority; -+ private final long subOrder; -+ private final long id; -+ -+ private volatile boolean removed; -+ private static final VarHandle REMOVED_HANDLE = ConcurrentUtil.getVarHandle(Holder.class, "removed", boolean.class); -+ -+ private Holder(final PrioritisedQueuedTask task, final int priority, final long subOrder, -+ final long id) { -+ this.task = task; -+ this.priority = priority; -+ this.subOrder = subOrder; -+ this.id = id; -+ } -+ -+ /** -+ * Returns true if marked as removed -+ */ -+ public boolean markRemoved() { -+ return !(boolean)REMOVED_HANDLE.getAndSet((Holder)this, (boolean)true); -+ } -+ } -+ -+ private final long id; -+ private final Runnable execute; -+ -+ private Priority priority; -+ private long subOrder; -+ private Holder holder; -+ -+ public PrioritisedQueuedTask(final Runnable execute, final Priority priority, final long subOrder) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ this.execute = execute; -+ this.priority = priority; -+ this.subOrder = subOrder; -+ this.id = PrioritisedTaskQueue.this.taskIdGenerator.getAndIncrement(); -+ } -+ -+ @Override -+ public PrioritisedExecutor getExecutor() { -+ return PrioritisedTaskQueue.this; -+ } -+ -+ @Override -+ public boolean queue() { -+ synchronized (this) { -+ if (this.holder != null || this.priority == Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (PrioritisedTaskQueue.this.isShutdown()) { -+ throw new IllegalStateException("Queue is shutdown"); -+ } -+ -+ final Holder holder = new Holder(this, this.priority.priority, this.subOrder, this.id); -+ this.holder = holder; -+ -+ PrioritisedTaskQueue.this.scheduledTasks.getAndIncrement(); -+ PrioritisedTaskQueue.this.tasks.put(holder, Boolean.TRUE); -+ } -+ -+ if (PrioritisedTaskQueue.this.isShutdown()) { -+ this.cancel(); -+ throw new IllegalStateException("Queue is shutdown"); -+ } -+ -+ -+ return true; -+ } -+ -+ @Override -+ public boolean isQueued() { -+ synchronized (this) { -+ return this.holder != null && this.priority != Priority.COMPLETING; -+ } -+ } -+ -+ @Override -+ public boolean cancel() { -+ synchronized (this) { -+ if (this.priority == Priority.COMPLETING) { -+ return false; -+ } -+ -+ this.priority = Priority.COMPLETING; -+ -+ if (this.holder != null) { -+ if (this.holder.markRemoved()) { -+ PrioritisedTaskQueue.this.tasks.remove(this.holder); -+ } -+ PrioritisedTaskQueue.this.executedTasks.getAndIncrement(); -+ } -+ -+ return true; -+ } -+ } -+ -+ @Override -+ public boolean execute() { -+ final boolean increaseExecuted; -+ -+ synchronized (this) { -+ if (this.priority == Priority.COMPLETING) { -+ return false; -+ } -+ -+ this.priority = Priority.COMPLETING; -+ -+ if (increaseExecuted = (this.holder != null)) { -+ if (this.holder.markRemoved()) { -+ PrioritisedTaskQueue.this.tasks.remove(this.holder); -+ } -+ } -+ } -+ -+ try { -+ this.execute.run(); -+ return true; -+ } finally { -+ if (increaseExecuted) { -+ PrioritisedTaskQueue.this.executedTasks.getAndIncrement(); -+ } -+ } -+ } -+ -+ @Override -+ public Priority getPriority() { -+ synchronized (this) { -+ return this.priority; -+ } -+ } -+ -+ @Override -+ public boolean setPriority(final Priority priority) { -+ synchronized (this) { -+ if (this.priority == Priority.COMPLETING || this.priority == priority) { -+ return false; -+ } -+ -+ this.priority = priority; -+ -+ if (this.holder != null) { -+ if (this.holder.markRemoved()) { -+ PrioritisedTaskQueue.this.tasks.remove(this.holder); -+ } -+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); -+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); -+ } -+ -+ return true; -+ } -+ } -+ -+ @Override -+ public boolean raisePriority(final Priority priority) { -+ synchronized (this) { -+ if (this.priority == Priority.COMPLETING || this.priority.isHigherOrEqualPriority(priority)) { -+ return false; -+ } -+ -+ this.priority = priority; -+ -+ if (this.holder != null) { -+ if (this.holder.markRemoved()) { -+ PrioritisedTaskQueue.this.tasks.remove(this.holder); -+ } -+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); -+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); -+ } -+ -+ return true; -+ } -+ } -+ -+ @Override -+ public boolean lowerPriority(Priority priority) { -+ synchronized (this) { -+ if (this.priority == Priority.COMPLETING || this.priority.isLowerOrEqualPriority(priority)) { -+ return false; -+ } -+ -+ this.priority = priority; -+ -+ if (this.holder != null) { -+ if (this.holder.markRemoved()) { -+ PrioritisedTaskQueue.this.tasks.remove(this.holder); -+ } -+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); -+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); -+ } -+ -+ return true; -+ } -+ } -+ -+ @Override -+ public long getSubOrder() { -+ synchronized (this) { -+ return this.subOrder; -+ } -+ } -+ -+ @Override -+ public boolean setSubOrder(final long subOrder) { -+ synchronized (this) { -+ if (this.priority == Priority.COMPLETING || this.subOrder == subOrder) { -+ return false; -+ } -+ -+ this.subOrder = subOrder; -+ -+ if (this.holder != null) { -+ if (this.holder.markRemoved()) { -+ PrioritisedTaskQueue.this.tasks.remove(this.holder); -+ } -+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); -+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); -+ } -+ -+ return true; -+ } -+ } -+ -+ @Override -+ public boolean raiseSubOrder(long subOrder) { -+ synchronized (this) { -+ if (this.priority == Priority.COMPLETING || this.subOrder >= subOrder) { -+ return false; -+ } -+ -+ this.subOrder = subOrder; -+ -+ if (this.holder != null) { -+ if (this.holder.markRemoved()) { -+ PrioritisedTaskQueue.this.tasks.remove(this.holder); -+ } -+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); -+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); -+ } -+ -+ return true; -+ } -+ } -+ -+ @Override -+ public boolean lowerSubOrder(final long subOrder) { -+ synchronized (this) { -+ if (this.priority == Priority.COMPLETING || this.subOrder <= subOrder) { -+ return false; -+ } -+ -+ this.subOrder = subOrder; -+ -+ if (this.holder != null) { -+ if (this.holder.markRemoved()) { -+ PrioritisedTaskQueue.this.tasks.remove(this.holder); -+ } -+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); -+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); -+ } -+ -+ return true; -+ } -+ } -+ -+ @Override -+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { -+ synchronized (this) { -+ if (this.priority == Priority.COMPLETING || (this.priority == priority && this.subOrder == subOrder)) { -+ return false; -+ } -+ -+ this.priority = priority; -+ this.subOrder = subOrder; -+ -+ if (this.holder != null) { -+ if (this.holder.markRemoved()) { -+ PrioritisedTaskQueue.this.tasks.remove(this.holder); -+ } -+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); -+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); -+ } -+ -+ return true; -+ } -+ } -+ } -+ -+ public static record PrioritySubOrderPair(Priority priority, long subOrder) {} -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java -deleted file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java -+++ /dev/null -@@ -0,0 +0,0 @@ --package ca.spottedleaf.concurrentutil.executor.standard; -- --import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; --import java.lang.invoke.VarHandle; -- --public class DelayedPrioritisedTask { -- -- protected volatile int priority; -- protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(DelayedPrioritisedTask.class, "priority", int.class); -- -- protected static final int PRIORITY_SET = Integer.MIN_VALUE >>> 0; -- -- protected final int getPriorityVolatile() { -- return (int)PRIORITY_HANDLE.getVolatile((DelayedPrioritisedTask)this); -- } -- -- protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { -- return (int)PRIORITY_HANDLE.compareAndExchange((DelayedPrioritisedTask)this, (int)expect, (int)update); -- } -- -- protected final int getAndOrPriorityVolatile(final int val) { -- return (int)PRIORITY_HANDLE.getAndBitwiseOr((DelayedPrioritisedTask)this, (int)val); -- } -- -- protected final void setPriorityPlain(final int val) { -- PRIORITY_HANDLE.set((DelayedPrioritisedTask)this, (int)val); -- } -- -- protected volatile PrioritisedExecutor.PrioritisedTask task; -- protected static final VarHandle TASK_HANDLE = ConcurrentUtil.getVarHandle(DelayedPrioritisedTask.class, "task", PrioritisedExecutor.PrioritisedTask.class); -- -- protected PrioritisedExecutor.PrioritisedTask getTaskPlain() { -- return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.get((DelayedPrioritisedTask)this); -- } -- -- protected PrioritisedExecutor.PrioritisedTask getTaskVolatile() { -- return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.getVolatile((DelayedPrioritisedTask)this); -- } -- -- protected final PrioritisedExecutor.PrioritisedTask compareAndExchangeTaskVolatile(final PrioritisedExecutor.PrioritisedTask expect, final PrioritisedExecutor.PrioritisedTask update) { -- return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.compareAndExchange((DelayedPrioritisedTask)this, (PrioritisedExecutor.PrioritisedTask)expect, (PrioritisedExecutor.PrioritisedTask)update); -- } -- -- public DelayedPrioritisedTask(final PrioritisedExecutor.Priority priority) { -- this.setPriorityPlain(priority.priority); -- } -- -- // only public for debugging -- public int getPriorityInternal() { -- return this.getPriorityVolatile(); -- } -- -- public PrioritisedExecutor.PrioritisedTask getTask() { -- return this.getTaskVolatile(); -- } -- -- public void setTask(final PrioritisedExecutor.PrioritisedTask task) { -- int priority = this.getPriorityVolatile(); -- -- if (this.compareAndExchangeTaskVolatile(null, task) != null) { -- throw new IllegalStateException("setTask() called twice"); -- } -- -- int failures = 0; -- for (;;) { -- task.setPriority(PrioritisedExecutor.Priority.getPriority(priority)); -- -- if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | PRIORITY_SET))) { -- return; -- } -- -- ++failures; -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- } -- } -- -- public PrioritisedExecutor.Priority getPriority() { -- final int priority = this.getPriorityVolatile(); -- if ((priority & PRIORITY_SET) != 0) { -- return this.task.getPriority(); -- } -- -- return PrioritisedExecutor.Priority.getPriority(priority); -- } -- -- public void raisePriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -- throw new IllegalArgumentException("Invalid priority " + priority); -- } -- -- int failures = 0; -- for (int curr = this.getPriorityVolatile();;) { -- if ((curr & PRIORITY_SET) != 0) { -- this.getTaskPlain().raisePriority(priority); -- return; -- } -- -- if (!priority.isLowerPriority(curr)) { -- return; -- } -- -- if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -- return; -- } -- -- // failed, retry -- -- ++failures; -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- } -- } -- -- public void setPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -- throw new IllegalArgumentException("Invalid priority " + priority); -- } -- -- int failures = 0; -- for (int curr = this.getPriorityVolatile();;) { -- if ((curr & PRIORITY_SET) != 0) { -- this.getTaskPlain().setPriority(priority); -- return; -- } -- -- if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -- return; -- } -- -- // failed, retry -- -- ++failures; -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- } -- } -- -- public void lowerPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -- throw new IllegalArgumentException("Invalid priority " + priority); -- } -- -- int failures = 0; -- for (int curr = this.getPriorityVolatile();;) { -- if ((curr & PRIORITY_SET) != 0) { -- this.getTaskPlain().lowerPriority(priority); -- return; -- } -- -- if (!priority.isHigherPriority(curr)) { -- return; -- } -- -- if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -- return; -- } -- -- // failed, retry -- -- ++failures; -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- } -- } --} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java -deleted file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java -+++ /dev/null -@@ -0,0 +0,0 @@ --package ca.spottedleaf.concurrentutil.executor.standard; -- --import ca.spottedleaf.concurrentutil.executor.BaseExecutor; -- --/** -- * Implementation of {@link BaseExecutor} which schedules tasks to be executed by a given priority. -- * @see BaseExecutor -- */ --public interface PrioritisedExecutor extends BaseExecutor { -- -- public static enum Priority { -- -- /** -- * Priority value indicating the task has completed or is being completed. -- * This priority cannot be used to schedule tasks. -- */ -- COMPLETING(-1), -- -- /** -- * Absolute highest priority, should only be used for when a task is blocking a time-critical thread. -- */ -- BLOCKING(), -- -- /** -- * Should only be used for urgent but not time-critical tasks. -- */ -- HIGHEST(), -- -- /** -- * Two priorities above normal. -- */ -- HIGHER(), -- -- /** -- * One priority above normal. -- */ -- HIGH(), -- -- /** -- * Default priority. -- */ -- NORMAL(), -- -- /** -- * One priority below normal. -- */ -- LOW(), -- -- /** -- * Two priorities below normal. -- */ -- LOWER(), -- -- /** -- * Use for tasks that should eventually execute, but are not needed to. -- */ -- LOWEST(), -- -- /** -- * Use for tasks that can be delayed indefinitely. -- */ -- IDLE(); -- -- // returns whether the priority can be scheduled -- public static boolean isValidPriority(final Priority priority) { -- return priority != null && priority != Priority.COMPLETING; -- } -- -- // returns the higher priority of the two -- public static Priority max(final Priority p1, final Priority p2) { -- return p1.isHigherOrEqualPriority(p2) ? p1 : p2; -- } -- -- // returns the lower priroity of the two -- public static Priority min(final Priority p1, final Priority p2) { -- return p1.isLowerOrEqualPriority(p2) ? p1 : p2; -- } -- -- public boolean isHigherOrEqualPriority(final Priority than) { -- return this.priority <= than.priority; -- } -- -- public boolean isHigherPriority(final Priority than) { -- return this.priority < than.priority; -- } -- -- public boolean isLowerOrEqualPriority(final Priority than) { -- return this.priority >= than.priority; -- } -- -- public boolean isLowerPriority(final Priority than) { -- return this.priority > than.priority; -- } -- -- public boolean isHigherOrEqualPriority(final int than) { -- return this.priority <= than; -- } -- -- public boolean isHigherPriority(final int than) { -- return this.priority < than; -- } -- -- public boolean isLowerOrEqualPriority(final int than) { -- return this.priority >= than; -- } -- -- public boolean isLowerPriority(final int than) { -- return this.priority > than; -- } -- -- public static boolean isHigherOrEqualPriority(final int priority, final int than) { -- return priority <= than; -- } -- -- public static boolean isHigherPriority(final int priority, final int than) { -- return priority < than; -- } -- -- public static boolean isLowerOrEqualPriority(final int priority, final int than) { -- return priority >= than; -- } -- -- public static boolean isLowerPriority(final int priority, final int than) { -- return priority > than; -- } -- -- static final Priority[] PRIORITIES = Priority.values(); -- -- /** includes special priorities */ -- public static final int TOTAL_PRIORITIES = PRIORITIES.length; -- -- public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1; -- -- public static Priority getPriority(final int priority) { -- return PRIORITIES[priority + 1]; -- } -- -- private static int priorityCounter; -- -- private static int nextCounter() { -- return priorityCounter++; -- } -- -- public final int priority; -- -- Priority() { -- this(nextCounter()); -- } -- -- Priority(final int priority) { -- this.priority = priority; -- } -- } -- -- /** -- * Executes the next available task. -- *

-- * If there is a task with priority {@link PrioritisedExecutor.Priority#BLOCKING} available, then that such task is executed. -- *

-- *

-- * If there is a task with priority {@link PrioritisedExecutor.Priority#IDLE} available then that task is only executed -- * when there are no other tasks available with a higher priority. -- *

-- *

-- * If there are no tasks that have priority {@link PrioritisedExecutor.Priority#BLOCKING} or {@link PrioritisedExecutor.Priority#IDLE}, then -- * this function will be biased to execute tasks that have higher priorities. -- *

-- * -- * @return {@code true} if a task was executed, {@code false} otherwise -- * @throws IllegalStateException If the current thread is not allowed to execute a task -- */ -- @Override -- public boolean executeTask() throws IllegalStateException; -- -- /** -- * Queues or executes a task at {@link Priority#NORMAL} priority. -- * @param task The task to run. -- * -- * @throws IllegalStateException If this queue has shutdown. -- * @throws NullPointerException If the task is null -- * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -- * associated with the parameter -- */ -- public default PrioritisedTask queueRunnable(final Runnable task) { -- return this.queueRunnable(task, Priority.NORMAL); -- } -- -- /** -- * Queues or executes a task. -- * -- * @param task The task to run. -- * @param priority The priority for the task. -- * -- * @throws IllegalStateException If this queue has shutdown. -- * @throws NullPointerException If the task is null -- * @throws IllegalArgumentException If the priority is invalid. -- * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -- * associated with the parameter -- */ -- public PrioritisedTask queueRunnable(final Runnable task, final Priority priority); -- -- /** -- * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}. -- * -- * @param task The task to run. -- * -- * @throws IllegalStateException If this queue has shutdown. -- * @throws NullPointerException If the task is null -- * @throws IllegalArgumentException If the priority is invalid. -- * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks -- * @return The prioritised task associated with the parameters -- */ -- public default PrioritisedTask createTask(final Runnable task) { -- return this.createTask(task, Priority.NORMAL); -- } -- -- /** -- * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}. -- * -- * @param task The task to run. -- * @param priority The priority for the task. -- * -- * @throws IllegalStateException If this queue has shutdown. -- * @throws NullPointerException If the task is null -- * @throws IllegalArgumentException If the priority is invalid. -- * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks -- * @return The prioritised task associated with the parameters -- */ -- public PrioritisedTask createTask(final Runnable task, final Priority priority); -- -- /** -- * Extension of {@link ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask} which adds functions -- * to retrieve and modify the task's associated priority. -- * -- * @see ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask -- */ -- public static interface PrioritisedTask extends BaseTask { -- -- /** -- * Returns the current priority. Note that {@link Priority#COMPLETING} will be returned -- * if this task is completing or has completed. -- */ -- public Priority getPriority(); -- -- /** -- * Attempts to set this task's priority level to the level specified. -- * -- * @param priority Specified priority level. -- * -- * @throws IllegalArgumentException If the priority is invalid -- * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue -- * this task was scheduled on was shutdown, or if the priority was already at the specified level. -- */ -- public boolean setPriority(final Priority priority); -- -- /** -- * Attempts to raise the priority to the priority level specified. -- * -- * @param priority Priority specified -- * -- * @throws IllegalArgumentException If the priority is invalid -- * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the specified level or was already at the specified level or higher. -- */ -- public boolean raisePriority(final Priority priority); -- -- /** -- * Attempts to lower the priority to the priority level specified. -- * -- * @param priority Priority specified -- * -- * @throws IllegalArgumentException If the priority is invalid -- * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the specified level or was already at the specified level or lower. -- */ -- public boolean lowerPriority(final Priority priority); -- } --} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java -deleted file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java -+++ /dev/null -@@ -0,0 +0,0 @@ --package ca.spottedleaf.concurrentutil.executor.standard; -- --import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; --import org.slf4j.Logger; --import org.slf4j.LoggerFactory; --import java.util.ArrayList; --import java.util.Arrays; --import java.util.Comparator; --import java.util.TreeSet; --import java.util.concurrent.atomic.AtomicBoolean; --import java.util.function.BiConsumer; -- --public final class PrioritisedThreadPool { -- -- private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedThreadPool.class); -- -- private final PrioritisedThread[] threads; -- private final TreeSet queues = new TreeSet<>(PrioritisedPoolExecutorImpl.comparator()); -- private final String name; -- private final long queueMaxHoldTime; -- -- private final ReferenceOpenHashSet nonShutdownQueues = new ReferenceOpenHashSet<>(); -- private final ReferenceOpenHashSet activeQueues = new ReferenceOpenHashSet<>(); -- -- private boolean shutdown; -- -- private long schedulingIdGenerator; -- -- private static final long DEFAULT_QUEUE_HOLD_TIME = (long)(5.0e6); -- -- /** -- * @param name Specified debug name of this thread pool -- * @param threads The number of threads to use -- */ -- public PrioritisedThreadPool(final String name, final int threads) { -- this(name, threads, null); -- } -- -- /** -- * @param name Specified debug name of this thread pool -- * @param threads The number of threads to use -- * @param threadModifier Invoked for each created thread with its incremental id before starting them -- */ -- public PrioritisedThreadPool(final String name, final int threads, final BiConsumer threadModifier) { -- this(name, threads, threadModifier, DEFAULT_QUEUE_HOLD_TIME); // 5ms -- } -- -- /** -- * @param name Specified debug name of this thread pool -- * @param threads The number of threads to use -- * @param threadModifier Invoked for each created thread with its incremental id before starting them -- * @param queueHoldTime The maximum amount of time to spend executing tasks in a specific queue before attempting -- * to switch to another queue, per thread -- */ -- public PrioritisedThreadPool(final String name, final int threads, final BiConsumer threadModifier, -- final long queueHoldTime) { // in ns -- if (threads <= 0) { -- throw new IllegalArgumentException("Thread count must be > 0, not " + threads); -- } -- if (name == null) { -- throw new IllegalArgumentException("Name cannot be null"); -- } -- this.name = name; -- this.queueMaxHoldTime = queueHoldTime; -- -- this.threads = new PrioritisedThread[threads]; -- for (int i = 0; i < threads; ++i) { -- this.threads[i] = new PrioritisedThread(this); -- -- // set default attributes -- this.threads[i].setName("Prioritised thread for pool '" + name + "' #" + i); -- this.threads[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { -- LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); -- }); -- -- // let thread modifier override defaults -- if (threadModifier != null) { -- threadModifier.accept(this.threads[i], Integer.valueOf(i)); -- } -- -- // now the thread can start -- this.threads[i].start(); -- } -- } -- -- /** -- * Returns an array representing the threads backing this thread pool. -- */ -- public Thread[] getThreads() { -- return Arrays.copyOf(this.threads, this.threads.length, Thread[].class); -- } -- -- /** -- * Creates and returns a {@link PrioritisedPoolExecutor} to schedule tasks onto. The returned executor will execute -- * tasks on this thread pool only. -- * @param name The debug name of the executor. -- * @param minParallelism The minimum number of threads to be executing tasks from the returned executor -- * before threads may be allocated to other queues in this thread pool. -- * @param parallelism The maximum number of threads which may be executing tasks from the returned executor. -- * @throws IllegalStateException If this thread pool is shut down -- */ -- public PrioritisedPoolExecutor createExecutor(final String name, final int minParallelism, final int parallelism) { -- synchronized (this.nonShutdownQueues) { -- if (this.shutdown) { -- throw new IllegalStateException("Queue is shutdown: " + this.toString()); -- } -- final PrioritisedPoolExecutorImpl ret = new PrioritisedPoolExecutorImpl( -- this, name, -- Math.min(Math.max(1, parallelism), this.threads.length), -- Math.min(Math.max(0, minParallelism), this.threads.length) -- ); -- -- this.nonShutdownQueues.add(ret); -- -- synchronized (this.activeQueues) { -- this.activeQueues.add(ret); -- } -- -- return ret; -- } -- } -- -- /** -- * Prevents creation of new queues, shutdowns all non-shutdown queues if specified -- */ -- public void halt(final boolean shutdownQueues) { -- synchronized (this.nonShutdownQueues) { -- this.shutdown = true; -- } -- if (shutdownQueues) { -- final ArrayList queuesToShutdown; -- synchronized (this.nonShutdownQueues) { -- this.shutdown = true; -- queuesToShutdown = new ArrayList<>(this.nonShutdownQueues); -- } -- -- for (final PrioritisedPoolExecutorImpl queue : queuesToShutdown) { -- queue.shutdown(); -- } -- } -- -- -- for (final PrioritisedThread thread : this.threads) { -- // can't kill queue, queue is null -- thread.halt(false); -- } -- } -- -- /** -- * Waits until all threads in this pool have shutdown, or until the specified time has passed. -- * @param msToWait Maximum time to wait. -- * @return {@code false} if the maximum time passed, {@code true} otherwise. -- */ -- public boolean join(final long msToWait) { -- try { -- return this.join(msToWait, false); -- } catch (final InterruptedException ex) { -- throw new IllegalStateException(ex); -- } -- } -- -- /** -- * Waits until all threads in this pool have shutdown, or until the specified time has passed. -- * @param msToWait Maximum time to wait. -- * @return {@code false} if the maximum time passed, {@code true} otherwise. -- * @throws InterruptedException If this thread is interrupted. -- */ -- public boolean joinInterruptable(final long msToWait) throws InterruptedException { -- return this.join(msToWait, true); -- } -- -- protected final boolean join(final long msToWait, final boolean interruptable) throws InterruptedException { -- final long nsToWait = msToWait * (1000 * 1000); -- final long start = System.nanoTime(); -- final long deadline = start + nsToWait; -- boolean interrupted = false; -- try { -- for (final PrioritisedThread thread : this.threads) { -- for (;;) { -- if (!thread.isAlive()) { -- break; -- } -- final long current = System.nanoTime(); -- if (current >= deadline) { -- return false; -- } -- -- try { -- thread.join(Math.max(1L, (deadline - current) / (1000 * 1000))); -- } catch (final InterruptedException ex) { -- if (interruptable) { -- throw ex; -- } -- interrupted = true; -- } -- } -- } -- -- return true; -- } finally { -- if (interrupted) { -- Thread.currentThread().interrupt(); -- } -- } -- } -- -- /** -- * Shuts down this thread pool, optionally waiting for all tasks to be executed. -- * This function will invoke {@link PrioritisedPoolExecutor#shutdown()} on all created executors on this -- * thread pool. -- * @param wait Whether to wait for tasks to be executed -- */ -- public void shutdown(final boolean wait) { -- final ArrayList queuesToShutdown; -- synchronized (this.nonShutdownQueues) { -- this.shutdown = true; -- queuesToShutdown = new ArrayList<>(this.nonShutdownQueues); -- } -- -- for (final PrioritisedPoolExecutorImpl queue : queuesToShutdown) { -- queue.shutdown(); -- } -- -- for (final PrioritisedThread thread : this.threads) { -- // none of these can be true or else NPE -- thread.close(false, false); -- } -- -- if (wait) { -- final ArrayList queues; -- synchronized (this.activeQueues) { -- queues = new ArrayList<>(this.activeQueues); -- } -- for (final PrioritisedPoolExecutorImpl queue : queues) { -- queue.waitUntilAllExecuted(); -- } -- } -- } -- -- protected static final class PrioritisedThread extends PrioritisedQueueExecutorThread { -- -- protected final PrioritisedThreadPool pool; -- protected final AtomicBoolean alertedHighPriority = new AtomicBoolean(); -- -- public PrioritisedThread(final PrioritisedThreadPool pool) { -- super(null); -- this.pool = pool; -- } -- -- public boolean alertHighPriorityExecutor() { -- if (!this.notifyTasks()) { -- if (!this.alertedHighPriority.get()) { -- this.alertedHighPriority.set(true); -- } -- return false; -- } -- -- return true; -- } -- -- private boolean isAlertedHighPriority() { -- return this.alertedHighPriority.get() && this.alertedHighPriority.getAndSet(false); -- } -- -- @Override -- protected boolean pollTasks() { -- final PrioritisedThreadPool pool = this.pool; -- final TreeSet queues = this.pool.queues; -- -- boolean ret = false; -- for (;;) { -- if (this.halted) { -- break; -- } -- // try to find a queue -- // note that if and ONLY IF the queues set is empty, this means there are no tasks for us to execute. -- // so we can only break when it's empty -- final PrioritisedPoolExecutorImpl queue; -- // select queue -- synchronized (queues) { -- queue = queues.pollFirst(); -- if (queue == null) { -- // no tasks to execute -- break; -- } -- -- queue.schedulingId = ++pool.schedulingIdGenerator; -- // we own this queue now, so increment the executor count -- // do we also need to push this queue up for grabs for another executor? -- if (++queue.concurrentExecutors < queue.maximumExecutors) { -- // re-add to queues -- // it's very important this is done in the same synchronised block for polling, as this prevents -- // us from possibly later adding a queue that should not exist in the set -- queues.add(queue); -- queue.isQueued = true; -- } else { -- queue.isQueued = false; -- } -- // note: we cannot drain entries from the queue while holding this lock, as it will cause deadlock -- // the queue addition holds the per-queue lock first then acquires the lock we have now, but if we -- // try to poll now we don't hold the per queue lock but we do hold the global lock... -- } -- -- // parse tasks as long as we are allowed -- final long start = System.nanoTime(); -- final long deadline = start + pool.queueMaxHoldTime; -- do { -- try { -- if (this.halted) { -- break; -- } -- if (!queue.executeTask()) { -- // no more tasks, try next queue -- break; -- } -- ret = true; -- } catch (final ThreadDeath death) { -- throw death; // goodbye world... -- } catch (final Throwable throwable) { -- LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + queue.toString() + "'", throwable); -- } -- } while (!this.isAlertedHighPriority() && System.nanoTime() <= deadline); -- -- synchronized (queues) { -- // decrement executors, we are no longer executing -- if (queue.isQueued) { -- queues.remove(queue); -- queue.isQueued = false; -- } -- if (--queue.concurrentExecutors == 0 && queue.scheduledPriority == null) { -- // reset scheduling id once the queue is empty again -- // this will ensure empty queues are not prioritised suddenly over active queues once tasks are -- // queued -- queue.schedulingId = 0L; -- } -- -- // ensure the executor is queued for execution again -- if (!queue.isHalted && queue.scheduledPriority != null) { // make sure it actually has tasks -- queues.add(queue); -- queue.isQueued = true; -- } -- } -- } -- -- return ret; -- } -- } -- -- public interface PrioritisedPoolExecutor extends PrioritisedExecutor { -- -- /** -- * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed -- */ -- public void halt(); -- -- /** -- * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether -- * this queue is not halted and not shutdown. -- */ -- public boolean isActive(); -- } -- -- protected static final class PrioritisedPoolExecutorImpl extends PrioritisedThreadedTaskQueue implements PrioritisedPoolExecutor { -- -- protected final PrioritisedThreadPool pool; -- protected final long[] priorityCounts = new long[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; -- protected long schedulingId; -- protected int concurrentExecutors; -- protected Priority scheduledPriority; -- -- protected final String name; -- protected final int maximumExecutors; -- protected final int minimumExecutors; -- protected boolean isQueued; -- -- public PrioritisedPoolExecutorImpl(final PrioritisedThreadPool pool, final String name, final int maximumExecutors, final int minimumExecutors) { -- this.pool = pool; -- this.name = name; -- this.maximumExecutors = maximumExecutors; -- this.minimumExecutors = minimumExecutors; -- } -- -- public static Comparator comparator() { -- return (final PrioritisedPoolExecutorImpl p1, final PrioritisedPoolExecutorImpl p2) -> { -- if (p1 == p2) { -- return 0; -- } -- -- final int belowMin1 = p1.minimumExecutors - p1.concurrentExecutors; -- final int belowMin2 = p2.minimumExecutors - p2.concurrentExecutors; -- -- // test minimum executors -- if (belowMin1 > 0 || belowMin2 > 0) { -- // want the largest belowMin to be first -- final int minCompare = Integer.compare(belowMin2, belowMin1); -- -- if (minCompare != 0) { -- return minCompare; -- } -- } -- -- // prefer higher priority -- final int priorityCompare = p1.scheduledPriority.ordinal() - p2.scheduledPriority.ordinal(); -- if (priorityCompare != 0) { -- return priorityCompare; -- } -- -- // try to spread out the executors so that each can have threads executing -- final int executorCompare = p1.concurrentExecutors - p2.concurrentExecutors; -- if (executorCompare != 0) { -- return executorCompare; -- } -- -- // if all else fails here we just choose whichever executor was queued first -- return Long.compare(p1.schedulingId, p2.schedulingId); -- }; -- } -- -- private boolean isHalted; -- -- @Override -- public void halt() { -- final PrioritisedThreadPool pool = this.pool; -- final TreeSet queues = pool.queues; -- synchronized (queues) { -- if (this.isHalted) { -- return; -- } -- this.isHalted = true; -- if (this.isQueued) { -- queues.remove(this); -- this.isQueued = false; -- } -- } -- synchronized (pool.nonShutdownQueues) { -- pool.nonShutdownQueues.remove(this); -- } -- synchronized (pool.activeQueues) { -- pool.activeQueues.remove(this); -- } -- } -- -- @Override -- public boolean isActive() { -- final PrioritisedThreadPool pool = this.pool; -- final TreeSet queues = pool.queues; -- -- synchronized (queues) { -- if (this.concurrentExecutors != 0) { -- return true; -- } -- synchronized (pool.activeQueues) { -- if (pool.activeQueues.contains(this)) { -- return true; -- } -- } -- } -- -- return false; -- } -- -- private long totalQueuedTasks = 0L; -- -- @Override -- protected void priorityChange(final PrioritisedThreadedTaskQueue.PrioritisedTask task, final Priority from, final Priority to) { -- // Note: The superclass' queue lock is ALWAYS held when inside this method. So we do NOT need to do any additional synchronisation -- // for accessing this queue's state. -- final long[] priorityCounts = this.priorityCounts; -- final boolean shutdown = this.isShutdown(); -- -- if (from == null && to == Priority.COMPLETING) { -- throw new IllegalStateException("Cannot complete task without queueing it first"); -- } -- -- // we should only notify for queueing of tasks, not changing priorities -- final boolean shouldNotifyTasks = from == null; -- -- final Priority scheduledPriority = this.scheduledPriority; -- if (from != null) { -- --priorityCounts[from.priority]; -- } -- if (to != Priority.COMPLETING) { -- ++priorityCounts[to.priority]; -- } -- final long totalQueuedTasks; -- if (to == Priority.COMPLETING) { -- totalQueuedTasks = --this.totalQueuedTasks; -- } else if (from == null) { -- totalQueuedTasks = ++this.totalQueuedTasks; -- } else { -- totalQueuedTasks = this.totalQueuedTasks; -- } -- -- // find new highest priority -- int highest = Math.min(to == Priority.COMPLETING ? Priority.IDLE.priority : to.priority, scheduledPriority == null ? Priority.IDLE.priority : scheduledPriority.priority); -- int lowestPriority = priorityCounts.length; // exclusive -- for (;highest < lowestPriority; ++highest) { -- final long count = priorityCounts[highest]; -- if (count < 0) { -- throw new IllegalStateException("Priority " + highest + " has " + count + " scheduled tasks"); -- } -- -- if (count != 0) { -- break; -- } -- } -- -- final Priority newPriority; -- if (highest == lowestPriority) { -- // no tasks left -- newPriority = null; -- } else if (shutdown) { -- // whichever is lower, the actual greatest priority or simply HIGHEST -- // this is so shutdown automatically gets priority -- newPriority = Priority.getPriority(Math.min(highest, Priority.HIGHEST.priority)); -- } else { -- newPriority = Priority.getPriority(highest); -- } -- -- final int executorsWanted; -- boolean shouldNotifyHighPriority = false; -- -- final PrioritisedThreadPool pool = this.pool; -- final TreeSet queues = pool.queues; -- -- synchronized (queues) { -- if (!this.isQueued) { -- // see if we need to be queued -- if (newPriority != null) { -- if (this.schedulingId == 0L) { -- this.schedulingId = ++pool.schedulingIdGenerator; -- } -- this.scheduledPriority = newPriority; // must be updated before queue add -- if (!this.isHalted && this.concurrentExecutors < this.maximumExecutors) { -- shouldNotifyHighPriority = newPriority.isHigherOrEqualPriority(Priority.HIGH); -- queues.add(this); -- this.isQueued = true; -- } -- } else { -- // do not queue -- this.scheduledPriority = null; -- } -- } else { -- // see if we need to NOT be queued -- if (newPriority == null) { -- queues.remove(this); -- this.scheduledPriority = null; -- this.isQueued = false; -- } else if (scheduledPriority != newPriority) { -- // if our priority changed, we need to update it - which means removing and re-adding into the queue -- queues.remove(this); -- // only now can we update scheduledPriority, since we are no longer in queue -- this.scheduledPriority = newPriority; -- queues.add(this); -- shouldNotifyHighPriority = (scheduledPriority == null || scheduledPriority.isLowerPriority(Priority.HIGH)) && newPriority.isHigherOrEqualPriority(Priority.HIGH); -- } -- } -- -- if (this.isQueued) { -- executorsWanted = Math.min(this.maximumExecutors - this.concurrentExecutors, (int)totalQueuedTasks); -- } else { -- executorsWanted = 0; -- } -- } -- -- if (newPriority == null && shutdown) { -- synchronized (pool.activeQueues) { -- pool.activeQueues.remove(this); -- } -- } -- -- // Wake up the number of executors we want -- if (executorsWanted > 0 || (shouldNotifyTasks | shouldNotifyHighPriority)) { -- int notified = 0; -- for (final PrioritisedThread thread : pool.threads) { -- if ((shouldNotifyHighPriority ? thread.alertHighPriorityExecutor() : thread.notifyTasks()) -- && (++notified >= executorsWanted)) { -- break; -- } -- } -- } -- } -- -- @Override -- public boolean shutdown() { -- final boolean ret = super.shutdown(); -- if (!ret) { -- return ret; -- } -- -- final PrioritisedThreadPool pool = this.pool; -- -- // remove from active queues -- synchronized (pool.nonShutdownQueues) { -- pool.nonShutdownQueues.remove(this); -- } -- -- final TreeSet queues = pool.queues; -- -- // try and shift around our priority -- synchronized (queues) { -- if (this.scheduledPriority == null) { -- // no tasks are queued, ensure we aren't in activeQueues -- synchronized (pool.activeQueues) { -- pool.activeQueues.remove(this); -- } -- -- return ret; -- } -- -- // try to set scheduled priority to HIGHEST so it drains faster -- -- if (this.scheduledPriority.isHigherOrEqualPriority(Priority.HIGHEST)) { -- // already at target priority (highest or above) -- return ret; -- } -- -- // shift priority to HIGHEST -- -- if (this.isQueued) { -- queues.remove(this); -- this.scheduledPriority = Priority.HIGHEST; -- queues.add(this); -- } else { -- this.scheduledPriority = Priority.HIGHEST; -- } -- } -- -- return ret; -- } -- } --} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java -deleted file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java -+++ /dev/null -@@ -0,0 +0,0 @@ --package ca.spottedleaf.concurrentutil.executor.standard; -- --import java.util.ArrayDeque; --import java.util.concurrent.atomic.AtomicLong; -- --public class PrioritisedThreadedTaskQueue implements PrioritisedExecutor { -- -- protected final ArrayDeque[] queues = new ArrayDeque[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; { -- for (int i = 0; i < Priority.TOTAL_SCHEDULABLE_PRIORITIES; ++i) { -- this.queues[i] = new ArrayDeque<>(); -- } -- } -- -- // Use AtomicLong to separate from the queue field, we don't want false sharing here. -- protected final AtomicLong totalScheduledTasks = new AtomicLong(); -- protected final AtomicLong totalCompletedTasks = new AtomicLong(); -- -- // this is here to prevent failures to queue stalling flush() calls (as the schedule calls would increment totalScheduledTasks without this check) -- protected volatile boolean hasShutdown; -- -- protected long taskIdGenerator = 0; -- -- @Override -- public PrioritisedExecutor.PrioritisedTask queueRunnable(final Runnable task, final Priority priority) throws IllegalStateException, IllegalArgumentException { -- if (!Priority.isValidPriority(priority)) { -- throw new IllegalArgumentException("Priority " + priority + " is invalid"); -- } -- if (task == null) { -- throw new NullPointerException("Task cannot be null"); -- } -- -- if (this.hasShutdown) { -- // prevent us from stalling flush() calls by incrementing scheduled tasks when we really didn't schedule something -- throw new IllegalStateException("Queue has shutdown"); -- } -- -- final PrioritisedTask ret; -- -- synchronized (this.queues) { -- if (this.hasShutdown) { -- throw new IllegalStateException("Queue has shutdown"); -- } -- this.getAndAddTotalScheduledTasksVolatile(1L); -- -- ret = new PrioritisedTask(this.taskIdGenerator++, task, priority, this); -- -- this.queues[ret.priority.priority].add(ret); -- -- // call priority change callback (note: only after we successfully queue!) -- this.priorityChange(ret, null, priority); -- } -- -- return ret; -- } -- -- @Override -- public PrioritisedExecutor.PrioritisedTask createTask(final Runnable task, final Priority priority) { -- if (!Priority.isValidPriority(priority)) { -- throw new IllegalArgumentException("Priority " + priority + " is invalid"); -- } -- if (task == null) { -- throw new NullPointerException("Task cannot be null"); -- } -- -- return new PrioritisedTask(task, priority, this); -- } -- -- @Override -- public long getTotalTasksScheduled() { -- return this.totalScheduledTasks.get(); -- } -- -- @Override -- public long getTotalTasksExecuted() { -- return this.totalCompletedTasks.get(); -- } -- -- // callback method for subclasses to override -- // from is null when a task is immediately created -- protected void priorityChange(final PrioritisedTask task, final Priority from, final Priority to) {} -- -- /** -- * Polls the highest priority task currently available. {@code null} if none. This will mark the -- * returned task as completed. -- */ -- protected PrioritisedTask poll() { -- return this.poll(Priority.IDLE); -- } -- -- protected PrioritisedTask poll(final Priority minPriority) { -- final ArrayDeque[] queues = this.queues; -- synchronized (queues) { -- final int max = minPriority.priority; -- for (int i = 0; i <= max; ++i) { -- final ArrayDeque queue = queues[i]; -- PrioritisedTask task; -- while ((task = queue.pollFirst()) != null) { -- if (task.trySetCompleting(i)) { -- return task; -- } -- } -- } -- } -- -- return null; -- } -- -- /** -- * Polls and executes the highest priority task currently available. Exceptions thrown during task execution will -- * be rethrown. -- * @return {@code true} if a task was executed, {@code false} otherwise. -- */ -- @Override -- public boolean executeTask() { -- final PrioritisedTask task = this.poll(); -- -- if (task != null) { -- task.executeInternal(); -- return true; -- } -- -- return false; -- } -- -- @Override -- public boolean shutdown() { -- synchronized (this.queues) { -- if (this.hasShutdown) { -- return false; -- } -- this.hasShutdown = true; -- } -- return true; -- } -- -- @Override -- public boolean isShutdown() { -- return this.hasShutdown; -- } -- -- /* totalScheduledTasks */ -- -- protected final long getTotalScheduledTasksVolatile() { -- return this.totalScheduledTasks.get(); -- } -- -- protected final long getAndAddTotalScheduledTasksVolatile(final long value) { -- return this.totalScheduledTasks.getAndAdd(value); -- } -- -- /* totalCompletedTasks */ -- -- protected final long getTotalCompletedTasksVolatile() { -- return this.totalCompletedTasks.get(); -- } -- -- protected final long getAndAddTotalCompletedTasksVolatile(final long value) { -- return this.totalCompletedTasks.getAndAdd(value); -- } -- -- protected static final class PrioritisedTask implements PrioritisedExecutor.PrioritisedTask { -- protected final PrioritisedThreadedTaskQueue queue; -- protected long id; -- protected static final long NOT_SCHEDULED_ID = -1L; -- -- protected Runnable runnable; -- protected volatile Priority priority; -- -- protected PrioritisedTask(final long id, final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) { -- if (!Priority.isValidPriority(priority)) { -- throw new IllegalArgumentException("Invalid priority " + priority); -- } -- -- this.priority = priority; -- this.runnable = runnable; -- this.queue = queue; -- this.id = id; -- } -- -- protected PrioritisedTask(final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) { -- if (!Priority.isValidPriority(priority)) { -- throw new IllegalArgumentException("Invalid priority " + priority); -- } -- -- this.priority = priority; -- this.runnable = runnable; -- this.queue = queue; -- this.id = NOT_SCHEDULED_ID; -- } -- -- @Override -- public boolean queue() { -- if (this.queue.hasShutdown) { -- throw new IllegalStateException("Queue has shutdown"); -- } -- -- synchronized (this.queue.queues) { -- if (this.queue.hasShutdown) { -- throw new IllegalStateException("Queue has shutdown"); -- } -- -- final Priority priority = this.priority; -- if (priority == Priority.COMPLETING) { -- return false; -- } -- -- if (this.id != NOT_SCHEDULED_ID) { -- return false; -- } -- -- this.queue.getAndAddTotalScheduledTasksVolatile(1L); -- this.id = this.queue.taskIdGenerator++; -- this.queue.queues[priority.priority].add(this); -- -- this.queue.priorityChange(this, null, priority); -- -- return true; -- } -- } -- -- protected boolean trySetCompleting(final int minPriority) { -- final Priority oldPriority = this.priority; -- if (oldPriority != Priority.COMPLETING && oldPriority.isHigherOrEqualPriority(minPriority)) { -- this.priority = Priority.COMPLETING; -- if (this.id != NOT_SCHEDULED_ID) { -- this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); -- } -- return true; -- } -- -- return false; -- } -- -- @Override -- public Priority getPriority() { -- return this.priority; -- } -- -- @Override -- public boolean setPriority(final Priority priority) { -- if (!Priority.isValidPriority(priority)) { -- throw new IllegalArgumentException("Invalid priority " + priority); -- } -- synchronized (this.queue.queues) { -- final Priority curr = this.priority; -- -- if (curr == Priority.COMPLETING) { -- return false; -- } -- -- if (curr == priority) { -- return true; -- } -- -- this.priority = priority; -- if (this.id != NOT_SCHEDULED_ID) { -- this.queue.queues[priority.priority].add(this); -- -- // call priority change callback -- this.queue.priorityChange(this, curr, priority); -- } -- } -- -- return true; -- } -- -- @Override -- public boolean raisePriority(final Priority priority) { -- if (!Priority.isValidPriority(priority)) { -- throw new IllegalArgumentException("Invalid priority " + priority); -- } -- -- synchronized (this.queue.queues) { -- final Priority curr = this.priority; -- -- if (curr == Priority.COMPLETING) { -- return false; -- } -- -- if (curr.isHigherOrEqualPriority(priority)) { -- return true; -- } -- -- this.priority = priority; -- if (this.id != NOT_SCHEDULED_ID) { -- this.queue.queues[priority.priority].add(this); -- -- // call priority change callback -- this.queue.priorityChange(this, curr, priority); -- } -- } -- -- return true; -- } -- -- @Override -- public boolean lowerPriority(final Priority priority) { -- if (!Priority.isValidPriority(priority)) { -- throw new IllegalArgumentException("Invalid priority " + priority); -- } -- -- synchronized (this.queue.queues) { -- final Priority curr = this.priority; -- -- if (curr == Priority.COMPLETING) { -- return false; -- } -- -- if (curr.isLowerOrEqualPriority(priority)) { -- return true; -- } -- -- this.priority = priority; -- if (this.id != NOT_SCHEDULED_ID) { -- this.queue.queues[priority.priority].add(this); -- -- // call priority change callback -- this.queue.priorityChange(this, curr, priority); -- } -- } -- -- return true; -- } -- -- @Override -- public boolean cancel() { -- final long id; -- synchronized (this.queue.queues) { -- final Priority oldPriority = this.priority; -- if (oldPriority == Priority.COMPLETING) { -- return false; -- } -- -- this.priority = Priority.COMPLETING; -- // call priority change callback -- if ((id = this.id) != NOT_SCHEDULED_ID) { -- this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); -- } -- } -- this.runnable = null; -- if (id != NOT_SCHEDULED_ID) { -- this.queue.getAndAddTotalCompletedTasksVolatile(1L); -- } -- return true; -- } -- -- protected void executeInternal() { -- try { -- final Runnable execute = this.runnable; -- this.runnable = null; -- execute.run(); -- } finally { -- if (this.id != NOT_SCHEDULED_ID) { -- this.queue.getAndAddTotalCompletedTasksVolatile(1L); -- } -- } -- } -- -- @Override -- public boolean execute() { -- synchronized (this.queue.queues) { -- final Priority oldPriority = this.priority; -- if (oldPriority == Priority.COMPLETING) { -- return false; -- } -- -- this.priority = Priority.COMPLETING; -- // call priority change callback -- if (this.id != NOT_SCHEDULED_ID) { -- this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); -- } -- } -- -- this.executeInternal(); -- return true; -- } -- } --} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java -similarity index 60% -rename from src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java -rename to src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java -@@ -0,0 +0,0 @@ --package ca.spottedleaf.concurrentutil.executor.standard; -+package ca.spottedleaf.concurrentutil.executor.thread; - -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; - import java.lang.invoke.VarHandle; -@@ -0,0 +0,0 @@ import java.util.concurrent.locks.LockSupport; - *

- * Note: When using this thread, queue additions to the underlying {@link #queue} are not sufficient to get this thread - * to execute the task. The function {@link #notifyTasks()} must be used after scheduling a task. For expected behaviour -- * of task scheduling (thread wakes up after tasks are scheduled), use the methods provided on {@link PrioritisedExecutor} -- * methods. -+ * of task scheduling, use the methods provided on this class to schedule tasks. - *

- */ - public class PrioritisedQueueExecutorThread extends Thread implements PrioritisedExecutor { -@@ -0,0 +0,0 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise - - protected final long spinWaitTime; - -- static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms -+ protected static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms - - public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue) { - this(queue, DEFAULT_SPINWAIT_TIME); // 0.1ms -@@ -0,0 +0,0 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise - } - - @Override -- public void run() { -+ public final void run() { -+ try { -+ this.begin(); -+ this.doRun(); -+ } finally { -+ this.die(); -+ } -+ } -+ -+ public final void doRun() { - final long spinWaitTime = this.spinWaitTime; - - main_loop: -@@ -0,0 +0,0 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise - this.setThreadParkedVolatile(true); - - // We need to parse here to avoid a race condition where a thread queues a task before we set parked to true -- // (i.e it will not notify us) -+ // (i.e. it will not notify us) - if (this.pollTasks()) { - this.setThreadParkedVolatile(false); - continue; -@@ -0,0 +0,0 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise - } - } - -+ protected void begin() {} -+ -+ protected void die() {} -+ - /** - * Attempts to poll as many tasks as possible, returning when finished. - * @return Whether any tasks were executed. -@@ -0,0 +0,0 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise - break; - } - ret = true; -- } catch (final ThreadDeath death) { -- throw death; // goodbye world... - } catch (final Throwable throwable) { - LOGGER.error("Exception thrown from prioritized runnable task in thread '" + this.getName() + "'", throwable); - } -@@ -0,0 +0,0 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise - } - - @Override -- public PrioritisedTask createTask(final Runnable task, final Priority priority) { -- final PrioritisedTask queueTask = this.queue.createTask(task, priority); -- -- // need to override queue() to notify us of tasks -- return new PrioritisedTask() { -- @Override -- public Priority getPriority() { -- return queueTask.getPriority(); -- } -- -- @Override -- public boolean setPriority(final Priority priority) { -- return queueTask.setPriority(priority); -- } -+ public long getTotalTasksExecuted() { -+ return this.queue.getTotalTasksExecuted(); -+ } - -- @Override -- public boolean raisePriority(final Priority priority) { -- return queueTask.raisePriority(priority); -- } -+ @Override -+ public long getTotalTasksScheduled() { -+ return this.queue.getTotalTasksScheduled(); -+ } - -- @Override -- public boolean lowerPriority(final Priority priority) { -- return queueTask.lowerPriority(priority); -- } -+ @Override -+ public long generateNextSubOrder() { -+ return this.queue.generateNextSubOrder(); -+ } - -- @Override -- public boolean queue() { -- final boolean ret = queueTask.queue(); -- if (ret) { -- PrioritisedQueueExecutorThread.this.notifyTasks(); -- } -- return ret; -- } -+ @Override -+ public boolean shutdown() { -+ throw new UnsupportedOperationException(); -+ } - -- @Override -- public boolean cancel() { -- return queueTask.cancel(); -- } -+ @Override -+ public boolean isShutdown() { -+ return false; -+ } - -- @Override -- public boolean execute() { -- return queueTask.execute(); -- } -- }; -+ /** -+ * {@inheritDoc} -+ * @throws IllegalStateException Always -+ */ -+ @Override -+ public boolean executeTask() throws IllegalStateException { -+ throw new IllegalStateException(); - } - - @Override -- public PrioritisedTask queueRunnable(final Runnable task, final Priority priority) { -- final PrioritisedTask ret = this.queue.queueRunnable(task, priority); -+ public PrioritisedTask queueTask(final Runnable task) { -+ final PrioritisedTask ret = this.createTask(task); - -- this.notifyTasks(); -+ ret.queue(); - - return ret; - } - - @Override -- public boolean haveAllTasksExecuted() { -- return this.queue.haveAllTasksExecuted(); -+ public PrioritisedTask queueTask(final Runnable task, final Priority priority) { -+ final PrioritisedTask ret = this.createTask(task, priority); -+ -+ ret.queue(); -+ -+ return ret; - } - - @Override -- public long getTotalTasksExecuted() { -- return this.queue.getTotalTasksExecuted(); -+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { -+ final PrioritisedTask ret = this.createTask(task, priority, subOrder); -+ -+ ret.queue(); -+ -+ return ret; - } - -+ - @Override -- public long getTotalTasksScheduled() { -- return this.queue.getTotalTasksScheduled(); -+ public PrioritisedTask createTask(Runnable task) { -+ final PrioritisedTask queueTask = this.queue.createTask(task); -+ -+ return new WrappedTask(queueTask); - } - -- /** -- * {@inheritDoc} -- * @throws IllegalStateException If the current thread is {@code this} thread, or the underlying queue throws this exception. -- */ - @Override -- public void waitUntilAllExecuted() throws IllegalStateException { -- if (Thread.currentThread() == this) { -- throw new IllegalStateException("Cannot block on our own queue"); -- } -- this.queue.waitUntilAllExecuted(); -+ public PrioritisedTask createTask(final Runnable task, final Priority priority) { -+ final PrioritisedTask queueTask = this.queue.createTask(task, priority); -+ -+ return new WrappedTask(queueTask); - } - -- /** -- * {@inheritDoc} -- * @throws IllegalStateException Always -- */ - @Override -- public boolean executeTask() throws IllegalStateException { -- throw new IllegalStateException(); -+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { -+ final PrioritisedTask queueTask = this.queue.createTask(task, priority, subOrder); -+ -+ return new WrappedTask(queueTask); - } - - /** -@@ -0,0 +0,0 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise - *

- * This function is MT-Safe. - *

-- * @param wait If this call is to wait until the queue is empty and there are no tasks executing in the queue. -+ * @param wait If this call is to wait until this thread shuts down. - * @param killQueue Whether to shutdown this thread's queue - * @return whether this thread shut down the queue - * @see #halt(boolean) -@@ -0,0 +0,0 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise - LockSupport.unpark(this); - - if (wait) { -- this.waitUntilAllExecuted(); -+ boolean interrupted = false; -+ for (;;) { -+ if (this.isAlive()) { -+ if (interrupted) { -+ Thread.currentThread().interrupt(); -+ } -+ break; -+ } -+ try { -+ this.join(); -+ } catch (final InterruptedException ex) { -+ interrupted = true; -+ } -+ } - } - - return ret; -@@ -0,0 +0,0 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise - protected final void setThreadParkedVolatile(final boolean value) { - THREAD_PARKED_HANDLE.setVolatile(this, value); - } -+ -+ /** -+ * Required so that queue() can notify (unpark) this thread -+ */ -+ private final class WrappedTask implements PrioritisedTask { -+ private final PrioritisedTask queueTask; -+ -+ public WrappedTask(final PrioritisedTask queueTask) { -+ this.queueTask = queueTask; -+ } -+ -+ @Override -+ public PrioritisedExecutor getExecutor() { -+ return PrioritisedQueueExecutorThread.this; -+ } -+ -+ @Override -+ public boolean queue() { -+ final boolean ret = this.queueTask.queue(); -+ if (ret) { -+ PrioritisedQueueExecutorThread.this.notifyTasks(); -+ } -+ return ret; -+ } -+ -+ @Override -+ public boolean isQueued() { -+ return this.queueTask.isQueued(); -+ } -+ -+ @Override -+ public boolean cancel() { -+ return this.queueTask.cancel(); -+ } -+ -+ @Override -+ public boolean execute() { -+ return this.queueTask.execute(); -+ } -+ -+ @Override -+ public Priority getPriority() { -+ return this.queueTask.getPriority(); -+ } -+ -+ @Override -+ public boolean setPriority(final Priority priority) { -+ return this.queueTask.setPriority(priority); -+ } -+ -+ @Override -+ public boolean raisePriority(final Priority priority) { -+ return this.queueTask.raisePriority(priority); -+ } -+ -+ @Override -+ public boolean lowerPriority(final Priority priority) { -+ return this.queueTask.lowerPriority(priority); -+ } -+ -+ @Override -+ public long getSubOrder() { -+ return this.queueTask.getSubOrder(); -+ } -+ -+ @Override -+ public boolean setSubOrder(final long subOrder) { -+ return this.queueTask.setSubOrder(subOrder); -+ } -+ -+ @Override -+ public boolean raiseSubOrder(final long subOrder) { -+ return this.queueTask.raiseSubOrder(subOrder); -+ } -+ -+ @Override -+ public boolean lowerSubOrder(final long subOrder) { -+ return this.queueTask.lowerSubOrder(subOrder); -+ } -+ -+ @Override -+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { -+ return this.queueTask.setPriorityAndSubOrder(priority, subOrder); -+ } -+ } - } -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.executor.thread; -+ -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.queue.PrioritisedTaskQueue; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.concurrentutil.util.TimeUtil; -+import org.slf4j.Logger; -+import org.slf4j.LoggerFactory; -+import java.lang.reflect.Array; -+import java.util.Arrays; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.Consumer; -+ -+public final class PrioritisedThreadPool { -+ -+ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedThreadPool.class); -+ -+ private final Consumer threadModifier; -+ private final COWArrayList executors = new COWArrayList<>(ExecutorGroup.class); -+ private final COWArrayList threads = new COWArrayList<>(PrioritisedThread.class); -+ private final COWArrayList aliveThreads = new COWArrayList<>(PrioritisedThread.class); -+ -+ private static final Priority HIGH_PRIORITY_NOTIFY_THRESHOLD = Priority.HIGH; -+ private static final Priority QUEUE_SHUTDOWN_PRIORITY = Priority.HIGH; -+ -+ private boolean shutdown; -+ -+ public PrioritisedThreadPool(final Consumer threadModifier) { -+ this.threadModifier = threadModifier; -+ -+ if (threadModifier == null) { -+ throw new NullPointerException("Thread factory may not be null"); -+ } -+ } -+ -+ public Thread[] getAliveThreads() { -+ final PrioritisedThread[] threads = this.aliveThreads.getArray(); -+ -+ return Arrays.copyOf(threads, threads.length, Thread[].class); -+ } -+ -+ public Thread[] getCoreThreads() { -+ final PrioritisedThread[] threads = this.threads.getArray(); -+ -+ return Arrays.copyOf(threads, threads.length, Thread[].class); -+ } -+ -+ /** -+ * Prevents creation of new queues, shutdowns all non-shutdown queues if specified -+ */ -+ public void halt(final boolean shutdownQueues) { -+ synchronized (this) { -+ this.shutdown = true; -+ } -+ -+ if (shutdownQueues) { -+ for (final ExecutorGroup group : this.executors.getArray()) { -+ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) { -+ executor.shutdown(); -+ } -+ } -+ } -+ -+ for (final PrioritisedThread thread : this.threads.getArray()) { -+ thread.halt(false); -+ } -+ } -+ -+ /** -+ * Waits until all threads in this pool have shutdown, or until the specified time has passed. -+ * @param msToWait Maximum time to wait. -+ * @return {@code false} if the maximum time passed, {@code true} otherwise. -+ */ -+ public boolean join(final long msToWait) { -+ try { -+ return this.join(msToWait, false); -+ } catch (final InterruptedException ex) { -+ throw new IllegalStateException(ex); -+ } -+ } -+ -+ /** -+ * Waits until all threads in this pool have shutdown, or until the specified time has passed. -+ * @param msToWait Maximum time to wait. -+ * @return {@code false} if the maximum time passed, {@code true} otherwise. -+ * @throws InterruptedException If this thread is interrupted. -+ */ -+ public boolean joinInterruptable(final long msToWait) throws InterruptedException { -+ return this.join(msToWait, true); -+ } -+ -+ protected final boolean join(final long msToWait, final boolean interruptable) throws InterruptedException { -+ final long nsToWait = msToWait * (1000 * 1000); -+ final long start = System.nanoTime(); -+ final long deadline = start + nsToWait; -+ boolean interrupted = false; -+ try { -+ for (final PrioritisedThread thread : this.aliveThreads.getArray()) { -+ for (;;) { -+ if (!thread.isAlive()) { -+ break; -+ } -+ final long current = System.nanoTime(); -+ if (current >= deadline && msToWait > 0L) { -+ return false; -+ } -+ -+ try { -+ thread.join(msToWait <= 0L ? 0L : Math.max(1L, (deadline - current) / (1000 * 1000))); -+ } catch (final InterruptedException ex) { -+ if (interruptable) { -+ throw ex; -+ } -+ interrupted = true; -+ } -+ } -+ } -+ -+ return true; -+ } finally { -+ if (interrupted) { -+ Thread.currentThread().interrupt(); -+ } -+ } -+ } -+ -+ /** -+ * Shuts down this thread pool, optionally waiting for all tasks to be executed. -+ * This function will invoke {@link PrioritisedExecutor#shutdown()} on all created executors on this -+ * thread pool. -+ * @param wait Whether to wait for tasks to be executed -+ */ -+ public void shutdown(final boolean wait) { -+ synchronized (this) { -+ this.shutdown = true; -+ } -+ -+ for (final ExecutorGroup group : this.executors.getArray()) { -+ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) { -+ executor.shutdown(); -+ } -+ } -+ -+ -+ for (final PrioritisedThread thread : this.threads.getArray()) { -+ // none of these can be true or else NPE -+ thread.close(false, false); -+ } -+ -+ if (wait) { -+ this.join(0L); -+ } -+ } -+ -+ private void die(final PrioritisedThread thread) { -+ this.aliveThreads.remove(thread); -+ } -+ -+ public void adjustThreadCount(final int threads) { -+ synchronized (this) { -+ if (this.shutdown) { -+ return; -+ } -+ -+ final PrioritisedThread[] currentThreads = this.threads.getArray(); -+ if (threads == currentThreads.length) { -+ // no adjustment needed -+ return; -+ } -+ -+ if (threads < currentThreads.length) { -+ // we need to trim threads -+ for (int i = 0, difference = currentThreads.length - threads; i < difference; ++i) { -+ final PrioritisedThread remove = currentThreads[currentThreads.length - i - 1]; -+ -+ remove.halt(false); -+ this.threads.remove(remove); -+ } -+ } else { -+ // we need to add threads -+ for (int i = 0, difference = threads - currentThreads.length; i < difference; ++i) { -+ final PrioritisedThread thread = new PrioritisedThread(); -+ -+ this.threadModifier.accept(thread); -+ this.aliveThreads.add(thread); -+ this.threads.add(thread); -+ -+ thread.start(); -+ } -+ } -+ } -+ } -+ -+ private static int compareInsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority, -+ final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) { -+ final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal(); -+ if (priorityCompare != 0) { -+ return priorityCompare; -+ } -+ -+ final int parallelismCompare = src.currentParallelism - dst.currentParallelism; -+ if (parallelismCompare != 0) { -+ return parallelismCompare; -+ } -+ -+ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved); -+ } -+ -+ private static int compareOutsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority, -+ final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) { -+ if (src.getGroup().division == dst.getGroup().division) { -+ // can only compare priorities inside the same division -+ final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal(); -+ if (priorityCompare != 0) { -+ return priorityCompare; -+ } -+ } -+ -+ final int parallelismCompare = src.getGroup().currentParallelism - dst.getGroup().currentParallelism; -+ if (parallelismCompare != 0) { -+ return parallelismCompare; -+ } -+ -+ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved); -+ } -+ -+ private ExecutorGroup.ThreadPoolExecutor obtainQueue() { -+ final long time = System.nanoTime(); -+ synchronized (this) { -+ ExecutorGroup.ThreadPoolExecutor ret = null; -+ Priority retPriority = null; -+ -+ for (final ExecutorGroup executorGroup : this.executors.getArray()) { -+ ExecutorGroup.ThreadPoolExecutor highest = null; -+ Priority highestPriority = null; -+ for (final ExecutorGroup.ThreadPoolExecutor executor : executorGroup.executors.getArray()) { -+ final int maxParallelism = executor.maxParallelism; -+ if (maxParallelism > 0 && executor.currentParallelism >= maxParallelism) { -+ continue; -+ } -+ -+ final Priority priority = executor.getTargetPriority(); -+ -+ if (priority == null) { -+ continue; -+ } -+ -+ if (highestPriority == null || compareInsideGroup(highest, highestPriority, executor, priority) > 0) { -+ highest = executor; -+ highestPriority = priority; -+ } -+ } -+ -+ if (highest == null) { -+ continue; -+ } -+ -+ if (ret == null || compareOutsideGroup(ret, retPriority, highest, highestPriority) > 0) { -+ ret = highest; -+ retPriority = highestPriority; -+ } -+ } -+ -+ if (ret != null) { -+ ret.lastRetrieved = time; -+ ++ret.currentParallelism; -+ ++ret.getGroup().currentParallelism; -+ return ret; -+ } -+ -+ return ret; -+ } -+ } -+ -+ private void returnQueue(final ExecutorGroup.ThreadPoolExecutor executor) { -+ synchronized (this) { -+ --executor.currentParallelism; -+ --executor.getGroup().currentParallelism; -+ } -+ -+ if (executor.isShutdown() && executor.queue.hasNoScheduledTasks()) { -+ executor.getGroup().executors.remove(executor); -+ } -+ } -+ -+ private void notifyAllThreads() { -+ for (final PrioritisedThread thread : this.threads.getArray()) { -+ thread.notifyTasks(); -+ } -+ } -+ -+ public ExecutorGroup createExecutorGroup(final int division, final int flags) { -+ synchronized (this) { -+ if (this.shutdown) { -+ throw new IllegalStateException("Queue is shutdown: " + this.toString()); -+ } -+ -+ final ExecutorGroup ret = new ExecutorGroup(division, flags); -+ -+ this.executors.add(ret); -+ -+ return ret; -+ } -+ } -+ -+ private final class PrioritisedThread extends PrioritisedQueueExecutorThread { -+ -+ private final AtomicBoolean alertedHighPriority = new AtomicBoolean(); -+ -+ public PrioritisedThread() { -+ super(null); -+ } -+ -+ public boolean alertHighPriorityExecutor() { -+ if (!this.notifyTasks()) { -+ if (!this.alertedHighPriority.get()) { -+ this.alertedHighPriority.set(true); -+ } -+ return false; -+ } -+ -+ return true; -+ } -+ -+ private boolean isAlertedHighPriority() { -+ return this.alertedHighPriority.get() && this.alertedHighPriority.getAndSet(false); -+ } -+ -+ @Override -+ protected void die() { -+ PrioritisedThreadPool.this.die(this); -+ } -+ -+ @Override -+ protected boolean pollTasks() { -+ boolean ret = false; -+ -+ for (;;) { -+ if (this.halted) { -+ break; -+ } -+ -+ final ExecutorGroup.ThreadPoolExecutor executor = PrioritisedThreadPool.this.obtainQueue(); -+ if (executor == null) { -+ break; -+ } -+ final long deadline = System.nanoTime() + executor.queueMaxHoldTime; -+ do { -+ try { -+ if (this.halted || executor.halt) { -+ break; -+ } -+ if (!executor.executeTask()) { -+ // no more tasks, try next queue -+ break; -+ } -+ ret = true; -+ } catch (final Throwable throwable) { -+ LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + executor.toString() + "'", throwable); -+ } -+ } while (!this.isAlertedHighPriority() && System.nanoTime() <= deadline); -+ -+ PrioritisedThreadPool.this.returnQueue(executor); -+ } -+ -+ -+ return ret; -+ } -+ } -+ -+ public final class ExecutorGroup { -+ -+ private final AtomicLong subOrderGenerator = new AtomicLong(); -+ private final COWArrayList executors = new COWArrayList<>(ThreadPoolExecutor.class); -+ -+ private final int division; -+ private int currentParallelism; -+ -+ private ExecutorGroup(final int division, final int flags) { -+ this.division = division; -+ } -+ -+ public ThreadPoolExecutor[] getAllExecutors() { -+ return this.executors.getArray().clone(); -+ } -+ -+ private PrioritisedThreadPool getThreadPool() { -+ return PrioritisedThreadPool.this; -+ } -+ -+ public ThreadPoolExecutor createExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) { -+ synchronized (PrioritisedThreadPool.this) { -+ if (PrioritisedThreadPool.this.shutdown) { -+ throw new IllegalStateException("Queue is shutdown: " + PrioritisedThreadPool.this.toString()); -+ } -+ -+ final ThreadPoolExecutor ret = new ThreadPoolExecutor(maxParallelism, queueMaxHoldTime, flags); -+ -+ this.executors.add(ret); -+ -+ return ret; -+ } -+ } -+ -+ public final class ThreadPoolExecutor implements PrioritisedExecutor { -+ -+ private final PrioritisedTaskQueue queue = new PrioritisedTaskQueue(); -+ -+ private volatile int maxParallelism; -+ private final long queueMaxHoldTime; -+ private volatile int currentParallelism; -+ private volatile boolean halt; -+ private long lastRetrieved = System.nanoTime(); -+ -+ private ThreadPoolExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) { -+ this.maxParallelism = maxParallelism; -+ this.queueMaxHoldTime = queueMaxHoldTime; -+ } -+ -+ private ExecutorGroup getGroup() { -+ return ExecutorGroup.this; -+ } -+ -+ private boolean canNotify() { -+ if (this.halt) { -+ return false; -+ } -+ -+ final int max = this.maxParallelism; -+ return max < 0 || this.currentParallelism < max; -+ } -+ -+ private void notifyHighPriority() { -+ if (!this.canNotify()) { -+ return; -+ } -+ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) { -+ if (thread.alertHighPriorityExecutor()) { -+ return; -+ } -+ } -+ } -+ -+ private void notifyScheduled() { -+ if (!this.canNotify()) { -+ return; -+ } -+ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) { -+ if (thread.notifyTasks()) { -+ return; -+ } -+ } -+ } -+ -+ /** -+ * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed -+ */ -+ public void halt() { -+ this.halt = true; -+ -+ ExecutorGroup.this.executors.remove(this); -+ } -+ -+ /** -+ * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether -+ * this queue is not halted and not shutdown. -+ */ -+ public boolean isActive() { -+ if (this.halt) { -+ return this.currentParallelism > 0; -+ } else { -+ if (!this.isShutdown()) { -+ return true; -+ } -+ -+ return !this.queue.hasNoScheduledTasks(); -+ } -+ } -+ -+ @Override -+ public boolean shutdown() { -+ if (!this.queue.shutdown()) { -+ return false; -+ } -+ -+ if (this.queue.hasNoScheduledTasks()) { -+ ExecutorGroup.this.executors.remove(this); -+ } -+ -+ return true; -+ } -+ -+ @Override -+ public boolean isShutdown() { -+ return this.queue.isShutdown(); -+ } -+ -+ public void setMaxParallelism(final int maxParallelism) { -+ this.maxParallelism = maxParallelism; -+ // assume that we could have increased the parallelism -+ if (this.getTargetPriority() != null) { -+ ExecutorGroup.this.getThreadPool().notifyAllThreads(); -+ } -+ } -+ -+ Priority getTargetPriority() { -+ final Priority ret = this.queue.getHighestPriority(); -+ if (!this.isShutdown()) { -+ return ret; -+ } -+ -+ return ret == null ? QUEUE_SHUTDOWN_PRIORITY : Priority.max(ret, QUEUE_SHUTDOWN_PRIORITY); -+ } -+ -+ @Override -+ public long getTotalTasksScheduled() { -+ return this.queue.getTotalTasksScheduled(); -+ } -+ -+ @Override -+ public long getTotalTasksExecuted() { -+ return this.queue.getTotalTasksExecuted(); -+ } -+ -+ @Override -+ public long generateNextSubOrder() { -+ return ExecutorGroup.this.subOrderGenerator.getAndIncrement(); -+ } -+ -+ @Override -+ public boolean executeTask() { -+ return this.queue.executeTask(); -+ } -+ -+ @Override -+ public PrioritisedTask queueTask(final Runnable task) { -+ final PrioritisedTask ret = this.createTask(task); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ @Override -+ public PrioritisedTask queueTask(final Runnable task, final Priority priority) { -+ final PrioritisedTask ret = this.createTask(task, priority); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ @Override -+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { -+ final PrioritisedTask ret = this.createTask(task, priority, subOrder); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ @Override -+ public PrioritisedTask createTask(final Runnable task) { -+ return this.createTask(task, Priority.NORMAL); -+ } -+ -+ @Override -+ public PrioritisedTask createTask(final Runnable task, final Priority priority) { -+ return this.createTask(task, priority, this.generateNextSubOrder()); -+ } -+ -+ @Override -+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { -+ return new WrappedTask(this.queue.createTask(task, priority, subOrder)); -+ } -+ -+ private final class WrappedTask implements PrioritisedTask { -+ -+ private final PrioritisedTask wrapped; -+ -+ private WrappedTask(final PrioritisedTask wrapped) { -+ this.wrapped = wrapped; -+ } -+ -+ @Override -+ public PrioritisedExecutor getExecutor() { -+ return ThreadPoolExecutor.this; -+ } -+ -+ @Override -+ public boolean queue() { -+ if (this.wrapped.queue()) { -+ final Priority priority = this.getPriority(); -+ if (priority != Priority.COMPLETING) { -+ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { -+ ThreadPoolExecutor.this.notifyHighPriority(); -+ } else { -+ ThreadPoolExecutor.this.notifyScheduled(); -+ } -+ } -+ return true; -+ } -+ -+ return false; -+ } -+ -+ @Override -+ public boolean isQueued() { -+ return this.wrapped.isQueued(); -+ } -+ -+ @Override -+ public boolean cancel() { -+ return this.wrapped.cancel(); -+ } -+ -+ @Override -+ public boolean execute() { -+ return this.wrapped.execute(); -+ } -+ -+ @Override -+ public Priority getPriority() { -+ return this.wrapped.getPriority(); -+ } -+ -+ @Override -+ public boolean setPriority(final Priority priority) { -+ if (this.wrapped.setPriority(priority)) { -+ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { -+ ThreadPoolExecutor.this.notifyHighPriority(); -+ } -+ return true; -+ } -+ -+ return false; -+ } -+ -+ @Override -+ public boolean raisePriority(final Priority priority) { -+ if (this.wrapped.raisePriority(priority)) { -+ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { -+ ThreadPoolExecutor.this.notifyHighPriority(); -+ } -+ return true; -+ } -+ -+ return false; -+ } -+ -+ @Override -+ public boolean lowerPriority(final Priority priority) { -+ return this.wrapped.lowerPriority(priority); -+ } -+ -+ @Override -+ public long getSubOrder() { -+ return this.wrapped.getSubOrder(); -+ } -+ -+ @Override -+ public boolean setSubOrder(final long subOrder) { -+ return this.wrapped.setSubOrder(subOrder); -+ } -+ -+ @Override -+ public boolean raiseSubOrder(final long subOrder) { -+ return this.wrapped.raiseSubOrder(subOrder); -+ } -+ -+ @Override -+ public boolean lowerSubOrder(final long subOrder) { -+ return this.wrapped.lowerSubOrder(subOrder); -+ } -+ -+ @Override -+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { -+ if (this.wrapped.setPriorityAndSubOrder(priority, subOrder)) { -+ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { -+ ThreadPoolExecutor.this.notifyHighPriority(); -+ } -+ return true; -+ } -+ -+ return false; -+ } -+ } -+ } -+ } -+ -+ private static final class COWArrayList { -+ -+ private volatile E[] array; -+ -+ public COWArrayList(final Class clazz) { -+ this.array = (E[])Array.newInstance(clazz, 0); -+ } -+ -+ public E[] getArray() { -+ return this.array; -+ } -+ -+ public void add(final E element) { -+ synchronized (this) { -+ final E[] array = this.array; -+ -+ final E[] copy = Arrays.copyOf(array, array.length + 1); -+ copy[array.length] = element; -+ -+ this.array = copy; -+ } -+ } -+ -+ public boolean remove(final E element) { -+ synchronized (this) { -+ final E[] array = this.array; -+ int index = -1; -+ for (int i = 0, len = array.length; i < len; ++i) { -+ if (array[i] == element) { -+ index = i; -+ break; -+ } -+ } -+ -+ if (index == -1) { -+ return false; -+ } -+ -+ final E[] copy = (E[])Array.newInstance(array.getClass().getComponentType(), array.length - 1); -+ -+ System.arraycopy(array, 0, copy, 0, index); -+ System.arraycopy(array, index + 1, copy, index, (array.length - 1) - index); -+ -+ this.array = copy; -+ } -+ -+ return true; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java -+++ b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java -@@ -0,0 +0,0 @@ import java.util.function.Predicate; - * @param - * @see java.util.concurrent.ConcurrentMap - */ --public class ConcurrentLong2ReferenceChainedHashTable { -+public class ConcurrentLong2ReferenceChainedHashTable implements Iterable> { - - protected static final int DEFAULT_CAPACITY = 16; - protected static final float DEFAULT_LOAD_FACTOR = 0.75f; -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - public int size() { - final long ret = this.size.sum(); - -- if (ret <= 0L) { -+ if (ret < 0L) { - return 0; - } -- if (ret >= (long)Integer.MAX_VALUE) { -+ if (ret > (long)Integer.MAX_VALUE) { - return Integer.MAX_VALUE; - } - -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - - // create new table data - -+ // noinspection unchecked - final TableEntry[] newTable = new TableEntry[capacity]; - // noinspection unchecked - final TableEntry resizeNode = new TableEntry<>(0L, (V)newTable, true); -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - throw new IllegalStateException("Resizing to same size"); - } - -+ // noinspection unchecked - final TableEntry[] work = new TableEntry[1 << capDiffShift]; // typically, capDiffShift = 1 - - for (int i = 0, len = oldTable.length; i < len; ++i) { -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue table_loop; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue table_loop; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue table_loop; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue table_loop; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue table_loop; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue table_loop; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue table_loop; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue table_loop; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue table_loop; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } - - if (node.resize) { -+ // noinspection unchecked - table = (TableEntry[])node.getValuePlain(); - continue table_loop; - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - return new EntryIterator<>(this); - } - -+ @Override -+ public final Iterator> iterator() { -+ return this.entryIterator(); -+ } -+ - /** - * Returns an iterator over the keys in this map. The iterator is only guaranteed to see keys that were - * added before the beginning of this call, but it may see keys added during. -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - - protected static final class EntryIterator extends BaseIteratorImpl> { - -- protected EntryIterator(final ConcurrentLong2ReferenceChainedHashTable map) { -+ public EntryIterator(final ConcurrentLong2ReferenceChainedHashTable map) { - super(map); - } - -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - - protected static final class KeyIterator extends BaseIteratorImpl implements PrimitiveIterator.OfLong { - -- protected KeyIterator(final ConcurrentLong2ReferenceChainedHashTable map) { -+ public KeyIterator(final ConcurrentLong2ReferenceChainedHashTable map) { - super(map); - } - -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - - protected static final class ValueIterator extends BaseIteratorImpl { - -- protected ValueIterator(final ConcurrentLong2ReferenceChainedHashTable map) { -+ public ValueIterator(final ConcurrentLong2ReferenceChainedHashTable map) { - super(map); - } - -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - - @Override - public final void remove() { -- final TableEntry lastReturned = this.nextToReturn; -+ final TableEntry lastReturned = this.lastReturned; - if (lastReturned == null) { - throw new NoSuchElementException(); - } -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - final ResizeChain chain = this.resizeChain; - - if (chain == null) { -+ // noinspection unchecked - final TableEntry[] nextTable = (TableEntry[])entry.getValuePlain(); - - final ResizeChain oldChain = new ResizeChain<>(table, null, null); -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - } else { - ResizeChain currChain = chain.next; - if (currChain == null) { -+ // noinspection unchecked - final TableEntry[] ret = (TableEntry[])entry.getValuePlain(); - currChain = new ResizeChain<>(ret, chain, null); - chain.next = currChain; -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - - protected static final class ResizeChain { - -- protected final TableEntry[] table; -- protected final ResizeChain prev; -- protected ResizeChain next; -+ public final TableEntry[] table; -+ public final ResizeChain prev; -+ public ResizeChain next; - -- protected ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) { -+ public ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) { - this.table = table; - this.prev = prev; - this.next = next; -@@ -0,0 +0,0 @@ public class ConcurrentLong2ReferenceChainedHashTable { - - public static final class TableEntry { - -- protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); -+ private static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); - -- protected final boolean resize; -+ private final boolean resize; - -- protected final long key; -+ private final long key; - -- protected volatile V value; -- protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); -+ private volatile V value; -+ private static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); - -- protected final V getValuePlain() { -+ private V getValuePlain() { - //noinspection unchecked - return (V)VALUE_HANDLE.get(this); - } - -- protected final V getValueAcquire() { -+ private V getValueAcquire() { - //noinspection unchecked - return (V)VALUE_HANDLE.getAcquire(this); - } - -- protected final V getValueVolatile() { -+ private V getValueVolatile() { - //noinspection unchecked - return (V)VALUE_HANDLE.getVolatile(this); - } - -- protected final void setValuePlain(final V value) { -+ private void setValuePlain(final V value) { - VALUE_HANDLE.set(this, (Object)value); - } - -- protected final void setValueRelease(final V value) { -+ private void setValueRelease(final V value) { - VALUE_HANDLE.setRelease(this, (Object)value); - } - -- protected final void setValueVolatile(final V value) { -+ private void setValueVolatile(final V value) { - VALUE_HANDLE.setVolatile(this, (Object)value); - } - -- protected volatile TableEntry next; -- protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); -+ private volatile TableEntry next; -+ private static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); - -- protected final TableEntry getNextPlain() { -+ private TableEntry getNextPlain() { - //noinspection unchecked - return (TableEntry)NEXT_HANDLE.get(this); - } - -- protected final TableEntry getNextVolatile() { -+ private TableEntry getNextVolatile() { - //noinspection unchecked - return (TableEntry)NEXT_HANDLE.getVolatile(this); - } - -- protected final void setNextPlain(final TableEntry next) { -+ private void setNextPlain(final TableEntry next) { - NEXT_HANDLE.set(this, next); - } - -- protected final void setNextRelease(final TableEntry next) { -+ private void setNextRelease(final TableEntry next) { - NEXT_HANDLE.setRelease(this, next); - } - -- protected final void setNextVolatile(final TableEntry next) { -+ private void setNextVolatile(final TableEntry next) { - NEXT_HANDLE.setVolatile(this, next); - } - -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java -+++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java -@@ -0,0 +0,0 @@ import java.util.concurrent.atomic.AtomicLong; - import java.util.concurrent.locks.LockSupport; - import java.util.function.BooleanSupplier; - -+/** -+ * @deprecated To be replaced -+ */ -+@Deprecated - public class SchedulerThreadPool { - - public static final long DEADLINE_NOT_SET = Long.MIN_VALUE; -@@ -0,0 +0,0 @@ public class SchedulerThreadPool { - * is invoked for any scheduled task - otherwise, {@link #runTasks(BooleanSupplier)} may not be invoked to - * parse intermediate tasks. - *

-+ * @deprecated To be replaced - */ -+ @Deprecated - public static abstract class SchedulableTick { - private static final AtomicLong ID_GENERATOR = new AtomicLong(); - public final long id = ID_GENERATOR.getAndIncrement(); -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java -+++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java -@@ -0,0 +0,0 @@ public final class LinkedSortedSet implements Iterable { - - public final Comparator comparator; - -- protected Link head; -- protected Link tail; -+ private Link head; -+ private Link tail; - - public LinkedSortedSet() { - this((Comparator)Comparator.naturalOrder()); -@@ -0,0 +0,0 @@ public final class LinkedSortedSet implements Iterable { - private Link prev; - private Link next; - -- private Link() {} -- - private Link(final E element) { - this.element = element; - } -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.set; -+ -+import java.util.Iterator; -+import java.util.NoSuchElementException; -+import java.util.Objects; -+ -+public final class LinkedUnsortedList implements Iterable { -+ -+ private Link head; -+ private Link tail; -+ -+ public LinkedUnsortedList() {} -+ -+ public void clear() { -+ this.head = this.tail = null; -+ } -+ -+ public boolean isEmpty() { -+ return this.head == null; -+ } -+ -+ public E first() { -+ final Link head = this.head; -+ return head == null ? null : head.element; -+ } -+ -+ public E last() { -+ final Link tail = this.tail; -+ return tail == null ? null : tail.element; -+ } -+ -+ public boolean containsFirst(final E element) { -+ for (Link curr = this.head; curr != null; curr = curr.next) { -+ if (Objects.equals(element, curr.element)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ public boolean containsLast(final E element) { -+ for (Link curr = this.tail; curr != null; curr = curr.prev) { -+ if (Objects.equals(element, curr.element)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ private void removeNode(final Link node) { -+ final Link prev = node.prev; -+ final Link next = node.next; -+ -+ // help GC -+ node.element = null; -+ node.prev = null; -+ node.next = null; -+ -+ if (prev == null) { -+ this.head = next; -+ } else { -+ prev.next = next; -+ } -+ -+ if (next == null) { -+ this.tail = prev; -+ } else { -+ next.prev = prev; -+ } -+ } -+ -+ public boolean remove(final Link link) { -+ if (link.element == null) { -+ return false; -+ } -+ -+ this.removeNode(link); -+ return true; -+ } -+ -+ public boolean removeFirst(final E element) { -+ for (Link curr = this.head; curr != null; curr = curr.next) { -+ if (Objects.equals(element, curr.element)) { -+ this.removeNode(curr); -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ public boolean removeLast(final E element) { -+ for (Link curr = this.tail; curr != null; curr = curr.prev) { -+ if (Objects.equals(element, curr.element)) { -+ this.removeNode(curr); -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ @Override -+ public Iterator iterator() { -+ return new Iterator<>() { -+ private Link next = LinkedUnsortedList.this.head; -+ -+ @Override -+ public boolean hasNext() { -+ return this.next != null; -+ } -+ -+ @Override -+ public E next() { -+ final Link next = this.next; -+ if (next == null) { -+ throw new NoSuchElementException(); -+ } -+ this.next = next.next; -+ return next.element; -+ } -+ }; -+ } -+ -+ public E pollFirst() { -+ final Link head = this.head; -+ if (head == null) { -+ return null; -+ } -+ -+ final E ret = head.element; -+ final Link next = head.next; -+ -+ // unlink head -+ this.head = next; -+ if (next == null) { -+ this.tail = null; -+ } else { -+ next.prev = null; -+ } -+ -+ // help GC -+ head.element = null; -+ head.next = null; -+ -+ return ret; -+ } -+ -+ public E pollLast() { -+ final Link tail = this.tail; -+ if (tail == null) { -+ return null; -+ } -+ -+ final E ret = tail.element; -+ final Link prev = tail.prev; -+ -+ // unlink tail -+ this.tail = prev; -+ if (prev == null) { -+ this.head = null; -+ } else { -+ prev.next = null; -+ } -+ -+ // help GC -+ tail.element = null; -+ tail.prev = null; -+ -+ return ret; -+ } -+ -+ public Link addLast(final E element) { -+ final Link curr = this.tail; -+ if (curr != null) { -+ return this.tail = new Link<>(element, curr, null); -+ } else { -+ return this.head = this.tail = new Link<>(element); -+ } -+ } -+ -+ public Link addFirst(final E element) { -+ final Link curr = this.head; -+ if (curr != null) { -+ return this.head = new Link<>(element, null, curr); -+ } else { -+ return this.head = this.tail = new Link<>(element); -+ } -+ } -+ -+ public static final class Link { -+ private E element; -+ private Link prev; -+ private Link next; -+ -+ private Link(final E element) { -+ this.element = element; -+ } -+ -+ private Link(final E element, final Link prev, final Link next) { -+ this.element = element; -+ this.prev = prev; -+ this.next = next; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java -deleted file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java -+++ /dev/null -@@ -0,0 +0,0 @@ --package ca.spottedleaf.concurrentutil.util; -- --import java.lang.invoke.VarHandle; -- --public final class ArrayUtil { -- -- public static final VarHandle BOOLEAN_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(boolean[].class); -- -- public static final VarHandle BYTE_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(byte[].class); -- -- public static final VarHandle SHORT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(short[].class); -- -- public static final VarHandle INT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(int[].class); -- -- public static final VarHandle LONG_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(long[].class); -- -- public static final VarHandle OBJECT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(Object[].class); -- -- private ArrayUtil() { -- throw new RuntimeException(); -- } -- -- /* byte array */ -- -- public static byte getPlain(final byte[] array, final int index) { -- return (byte)BYTE_ARRAY_HANDLE.get(array, index); -- } -- -- public static byte getOpaque(final byte[] array, final int index) { -- return (byte)BYTE_ARRAY_HANDLE.getOpaque(array, index); -- } -- -- public static byte getAcquire(final byte[] array, final int index) { -- return (byte)BYTE_ARRAY_HANDLE.getAcquire(array, index); -- } -- -- public static byte getVolatile(final byte[] array, final int index) { -- return (byte)BYTE_ARRAY_HANDLE.getVolatile(array, index); -- } -- -- public static void setPlain(final byte[] array, final int index, final byte value) { -- BYTE_ARRAY_HANDLE.set(array, index, value); -- } -- -- public static void setOpaque(final byte[] array, final int index, final byte value) { -- BYTE_ARRAY_HANDLE.setOpaque(array, index, value); -- } -- -- public static void setRelease(final byte[] array, final int index, final byte value) { -- BYTE_ARRAY_HANDLE.setRelease(array, index, value); -- } -- -- public static void setVolatile(final byte[] array, final int index, final byte value) { -- BYTE_ARRAY_HANDLE.setVolatile(array, index, value); -- } -- -- public static void setVolatileContended(final byte[] array, final int index, final byte param) { -- int failures = 0; -- -- for (byte curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return; -- } -- } -- } -- -- public static byte compareAndExchangeVolatile(final byte[] array, final int index, final byte expect, final byte update) { -- return (byte)BYTE_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -- } -- -- public static byte getAndAddVolatile(final byte[] array, final int index, final byte param) { -- return (byte)BYTE_ARRAY_HANDLE.getAndAdd(array, index, param); -- } -- -- public static byte getAndAndVolatile(final byte[] array, final int index, final byte param) { -- return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -- } -- -- public static byte getAndOrVolatile(final byte[] array, final int index, final byte param) { -- return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -- } -- -- public static byte getAndXorVolatile(final byte[] array, final int index, final byte param) { -- return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -- } -- -- public static byte getAndSetVolatile(final byte[] array, final int index, final byte param) { -- return (byte)BYTE_ARRAY_HANDLE.getAndSet(array, index, param); -- } -- -- public static byte compareAndExchangeVolatileContended(final byte[] array, final int index, final byte expect, final byte update) { -- return (byte)BYTE_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -- } -- -- public static byte getAndAddVolatileContended(final byte[] array, final int index, final byte param) { -- int failures = 0; -- -- for (byte curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr + param)))) { -- return curr; -- } -- } -- } -- -- public static byte getAndAndVolatileContended(final byte[] array, final int index, final byte param) { -- int failures = 0; -- -- for (byte curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr & param)))) { -- return curr; -- } -- } -- } -- -- public static byte getAndOrVolatileContended(final byte[] array, final int index, final byte param) { -- int failures = 0; -- -- for (byte curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr | param)))) { -- return curr; -- } -- } -- } -- -- public static byte getAndXorVolatileContended(final byte[] array, final int index, final byte param) { -- int failures = 0; -- -- for (byte curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr ^ param)))) { -- return curr; -- } -- } -- } -- -- public static byte getAndSetVolatileContended(final byte[] array, final int index, final byte param) { -- int failures = 0; -- -- for (byte curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return curr; -- } -- } -- } -- -- /* short array */ -- -- public static short getPlain(final short[] array, final int index) { -- return (short)SHORT_ARRAY_HANDLE.get(array, index); -- } -- -- public static short getOpaque(final short[] array, final int index) { -- return (short)SHORT_ARRAY_HANDLE.getOpaque(array, index); -- } -- -- public static short getAcquire(final short[] array, final int index) { -- return (short)SHORT_ARRAY_HANDLE.getAcquire(array, index); -- } -- -- public static short getVolatile(final short[] array, final int index) { -- return (short)SHORT_ARRAY_HANDLE.getVolatile(array, index); -- } -- -- public static void setPlain(final short[] array, final int index, final short value) { -- SHORT_ARRAY_HANDLE.set(array, index, value); -- } -- -- public static void setOpaque(final short[] array, final int index, final short value) { -- SHORT_ARRAY_HANDLE.setOpaque(array, index, value); -- } -- -- public static void setRelease(final short[] array, final int index, final short value) { -- SHORT_ARRAY_HANDLE.setRelease(array, index, value); -- } -- -- public static void setVolatile(final short[] array, final int index, final short value) { -- SHORT_ARRAY_HANDLE.setVolatile(array, index, value); -- } -- -- public static void setVolatileContended(final short[] array, final int index, final short param) { -- int failures = 0; -- -- for (short curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return; -- } -- } -- } -- -- public static short compareAndExchangeVolatile(final short[] array, final int index, final short expect, final short update) { -- return (short)SHORT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -- } -- -- public static short getAndAddVolatile(final short[] array, final int index, final short param) { -- return (short)SHORT_ARRAY_HANDLE.getAndAdd(array, index, param); -- } -- -- public static short getAndAndVolatile(final short[] array, final int index, final short param) { -- return (short)SHORT_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -- } -- -- public static short getAndOrVolatile(final short[] array, final int index, final short param) { -- return (short)SHORT_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -- } -- -- public static short getAndXorVolatile(final short[] array, final int index, final short param) { -- return (short)SHORT_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -- } -- -- public static short getAndSetVolatile(final short[] array, final int index, final short param) { -- return (short)SHORT_ARRAY_HANDLE.getAndSet(array, index, param); -- } -- -- public static short compareAndExchangeVolatileContended(final short[] array, final int index, final short expect, final short update) { -- return (short)SHORT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -- } -- -- public static short getAndAddVolatileContended(final short[] array, final int index, final short param) { -- int failures = 0; -- -- for (short curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr + param)))) { -- return curr; -- } -- } -- } -- -- public static short getAndAndVolatileContended(final short[] array, final int index, final short param) { -- int failures = 0; -- -- for (short curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr & param)))) { -- return curr; -- } -- } -- } -- -- public static short getAndOrVolatileContended(final short[] array, final int index, final short param) { -- int failures = 0; -- -- for (short curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr | param)))) { -- return curr; -- } -- } -- } -- -- public static short getAndXorVolatileContended(final short[] array, final int index, final short param) { -- int failures = 0; -- -- for (short curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr ^ param)))) { -- return curr; -- } -- } -- } -- -- public static short getAndSetVolatileContended(final short[] array, final int index, final short param) { -- int failures = 0; -- -- for (short curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return curr; -- } -- } -- } -- -- /* int array */ -- -- public static int getPlain(final int[] array, final int index) { -- return (int)INT_ARRAY_HANDLE.get(array, index); -- } -- -- public static int getOpaque(final int[] array, final int index) { -- return (int)INT_ARRAY_HANDLE.getOpaque(array, index); -- } -- -- public static int getAcquire(final int[] array, final int index) { -- return (int)INT_ARRAY_HANDLE.getAcquire(array, index); -- } -- -- public static int getVolatile(final int[] array, final int index) { -- return (int)INT_ARRAY_HANDLE.getVolatile(array, index); -- } -- -- public static void setPlain(final int[] array, final int index, final int value) { -- INT_ARRAY_HANDLE.set(array, index, value); -- } -- -- public static void setOpaque(final int[] array, final int index, final int value) { -- INT_ARRAY_HANDLE.setOpaque(array, index, value); -- } -- -- public static void setRelease(final int[] array, final int index, final int value) { -- INT_ARRAY_HANDLE.setRelease(array, index, value); -- } -- -- public static void setVolatile(final int[] array, final int index, final int value) { -- INT_ARRAY_HANDLE.setVolatile(array, index, value); -- } -- -- public static void setVolatileContended(final int[] array, final int index, final int param) { -- int failures = 0; -- -- for (int curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return; -- } -- } -- } -- -- public static int compareAndExchangeVolatile(final int[] array, final int index, final int expect, final int update) { -- return (int)INT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -- } -- -- public static int getAndAddVolatile(final int[] array, final int index, final int param) { -- return (int)INT_ARRAY_HANDLE.getAndAdd(array, index, param); -- } -- -- public static int getAndAndVolatile(final int[] array, final int index, final int param) { -- return (int)INT_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -- } -- -- public static int getAndOrVolatile(final int[] array, final int index, final int param) { -- return (int)INT_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -- } -- -- public static int getAndXorVolatile(final int[] array, final int index, final int param) { -- return (int)INT_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -- } -- -- public static int getAndSetVolatile(final int[] array, final int index, final int param) { -- return (int)INT_ARRAY_HANDLE.getAndSet(array, index, param); -- } -- -- public static int compareAndExchangeVolatileContended(final int[] array, final int index, final int expect, final int update) { -- return (int)INT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -- } -- -- public static int getAndAddVolatileContended(final int[] array, final int index, final int param) { -- int failures = 0; -- -- for (int curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr + param)))) { -- return curr; -- } -- } -- } -- -- public static int getAndAndVolatileContended(final int[] array, final int index, final int param) { -- int failures = 0; -- -- for (int curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr & param)))) { -- return curr; -- } -- } -- } -- -- public static int getAndOrVolatileContended(final int[] array, final int index, final int param) { -- int failures = 0; -- -- for (int curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr | param)))) { -- return curr; -- } -- } -- } -- -- public static int getAndXorVolatileContended(final int[] array, final int index, final int param) { -- int failures = 0; -- -- for (int curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr ^ param)))) { -- return curr; -- } -- } -- } -- -- public static int getAndSetVolatileContended(final int[] array, final int index, final int param) { -- int failures = 0; -- -- for (int curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return curr; -- } -- } -- } -- -- /* long array */ -- -- public static long getPlain(final long[] array, final int index) { -- return (long)LONG_ARRAY_HANDLE.get(array, index); -- } -- -- public static long getOpaque(final long[] array, final int index) { -- return (long)LONG_ARRAY_HANDLE.getOpaque(array, index); -- } -- -- public static long getAcquire(final long[] array, final int index) { -- return (long)LONG_ARRAY_HANDLE.getAcquire(array, index); -- } -- -- public static long getVolatile(final long[] array, final int index) { -- return (long)LONG_ARRAY_HANDLE.getVolatile(array, index); -- } -- -- public static void setPlain(final long[] array, final int index, final long value) { -- LONG_ARRAY_HANDLE.set(array, index, value); -- } -- -- public static void setOpaque(final long[] array, final int index, final long value) { -- LONG_ARRAY_HANDLE.setOpaque(array, index, value); -- } -- -- public static void setRelease(final long[] array, final int index, final long value) { -- LONG_ARRAY_HANDLE.setRelease(array, index, value); -- } -- -- public static void setVolatile(final long[] array, final int index, final long value) { -- LONG_ARRAY_HANDLE.setVolatile(array, index, value); -- } -- -- public static void setVolatileContended(final long[] array, final int index, final long param) { -- int failures = 0; -- -- for (long curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return; -- } -- } -- } -- -- public static long compareAndExchangeVolatile(final long[] array, final int index, final long expect, final long update) { -- return (long)LONG_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -- } -- -- public static long getAndAddVolatile(final long[] array, final int index, final long param) { -- return (long)LONG_ARRAY_HANDLE.getAndAdd(array, index, param); -- } -- -- public static long getAndAndVolatile(final long[] array, final int index, final long param) { -- return (long)LONG_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -- } -- -- public static long getAndOrVolatile(final long[] array, final int index, final long param) { -- return (long)LONG_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -- } -- -- public static long getAndXorVolatile(final long[] array, final int index, final long param) { -- return (long)LONG_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -- } -- -- public static long getAndSetVolatile(final long[] array, final int index, final long param) { -- return (long)LONG_ARRAY_HANDLE.getAndSet(array, index, param); -- } -- -- public static long compareAndExchangeVolatileContended(final long[] array, final int index, final long expect, final long update) { -- return (long)LONG_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -- } -- -- public static long getAndAddVolatileContended(final long[] array, final int index, final long param) { -- int failures = 0; -- -- for (long curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr + param)))) { -- return curr; -- } -- } -- } -- -- public static long getAndAndVolatileContended(final long[] array, final int index, final long param) { -- int failures = 0; -- -- for (long curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr & param)))) { -- return curr; -- } -- } -- } -- -- public static long getAndOrVolatileContended(final long[] array, final int index, final long param) { -- int failures = 0; -- -- for (long curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr | param)))) { -- return curr; -- } -- } -- } -- -- public static long getAndXorVolatileContended(final long[] array, final int index, final long param) { -- int failures = 0; -- -- for (long curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr ^ param)))) { -- return curr; -- } -- } -- } -- -- public static long getAndSetVolatileContended(final long[] array, final int index, final long param) { -- int failures = 0; -- -- for (long curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return curr; -- } -- } -- } -- -- /* boolean array */ -- -- public static boolean getPlain(final boolean[] array, final int index) { -- return (boolean)BOOLEAN_ARRAY_HANDLE.get(array, index); -- } -- -- public static boolean getOpaque(final boolean[] array, final int index) { -- return (boolean)BOOLEAN_ARRAY_HANDLE.getOpaque(array, index); -- } -- -- public static boolean getAcquire(final boolean[] array, final int index) { -- return (boolean)BOOLEAN_ARRAY_HANDLE.getAcquire(array, index); -- } -- -- public static boolean getVolatile(final boolean[] array, final int index) { -- return (boolean)BOOLEAN_ARRAY_HANDLE.getVolatile(array, index); -- } -- -- public static void setPlain(final boolean[] array, final int index, final boolean value) { -- BOOLEAN_ARRAY_HANDLE.set(array, index, value); -- } -- -- public static void setOpaque(final boolean[] array, final int index, final boolean value) { -- BOOLEAN_ARRAY_HANDLE.setOpaque(array, index, value); -- } -- -- public static void setRelease(final boolean[] array, final int index, final boolean value) { -- BOOLEAN_ARRAY_HANDLE.setRelease(array, index, value); -- } -- -- public static void setVolatile(final boolean[] array, final int index, final boolean value) { -- BOOLEAN_ARRAY_HANDLE.setVolatile(array, index, value); -- } -- -- public static void setVolatileContended(final boolean[] array, final int index, final boolean param) { -- int failures = 0; -- -- for (boolean curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return; -- } -- } -- } -- -- public static boolean compareAndExchangeVolatile(final boolean[] array, final int index, final boolean expect, final boolean update) { -- return (boolean)BOOLEAN_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -- } -- -- public static boolean getAndOrVolatile(final boolean[] array, final int index, final boolean param) { -- return (boolean)BOOLEAN_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -- } -- -- public static boolean getAndXorVolatile(final boolean[] array, final int index, final boolean param) { -- return (boolean)BOOLEAN_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -- } -- -- public static boolean getAndSetVolatile(final boolean[] array, final int index, final boolean param) { -- return (boolean)BOOLEAN_ARRAY_HANDLE.getAndSet(array, index, param); -- } -- -- public static boolean compareAndExchangeVolatileContended(final boolean[] array, final int index, final boolean expect, final boolean update) { -- return (boolean)BOOLEAN_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -- } -- -- public static boolean getAndAndVolatileContended(final boolean[] array, final int index, final boolean param) { -- int failures = 0; -- -- for (boolean curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr & param)))) { -- return curr; -- } -- } -- } -- -- public static boolean getAndOrVolatileContended(final boolean[] array, final int index, final boolean param) { -- int failures = 0; -- -- for (boolean curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr | param)))) { -- return curr; -- } -- } -- } -- -- public static boolean getAndXorVolatileContended(final boolean[] array, final int index, final boolean param) { -- int failures = 0; -- -- for (boolean curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr ^ param)))) { -- return curr; -- } -- } -- } -- -- public static boolean getAndSetVolatileContended(final boolean[] array, final int index, final boolean param) { -- int failures = 0; -- -- for (boolean curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return curr; -- } -- } -- } -- -- @SuppressWarnings("unchecked") -- public static T getPlain(final T[] array, final int index) { -- final Object ret = OBJECT_ARRAY_HANDLE.get((Object[])array, index); -- return (T)ret; -- } -- -- @SuppressWarnings("unchecked") -- public static T getOpaque(final T[] array, final int index) { -- final Object ret = OBJECT_ARRAY_HANDLE.getOpaque((Object[])array, index); -- return (T)ret; -- } -- -- @SuppressWarnings("unchecked") -- public static T getAcquire(final T[] array, final int index) { -- final Object ret = OBJECT_ARRAY_HANDLE.getAcquire((Object[])array, index); -- return (T)ret; -- } -- -- @SuppressWarnings("unchecked") -- public static T getVolatile(final T[] array, final int index) { -- final Object ret = OBJECT_ARRAY_HANDLE.getVolatile((Object[])array, index); -- return (T)ret; -- } -- -- public static void setPlain(final T[] array, final int index, final T value) { -- OBJECT_ARRAY_HANDLE.set((Object[])array, index, (Object)value); -- } -- -- public static void setOpaque(final T[] array, final int index, final T value) { -- OBJECT_ARRAY_HANDLE.setOpaque((Object[])array, index, (Object)value); -- } -- -- public static void setRelease(final T[] array, final int index, final T value) { -- OBJECT_ARRAY_HANDLE.setRelease((Object[])array, index, (Object)value); -- } -- -- public static void setVolatile(final T[] array, final int index, final T value) { -- OBJECT_ARRAY_HANDLE.setVolatile((Object[])array, index, (Object)value); -- } -- -- public static void setVolatileContended(final T[] array, final int index, final T param) { -- int failures = 0; -- -- for (T curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return; -- } -- } -- } -- -- @SuppressWarnings("unchecked") -- public static T compareAndExchangeVolatile(final T[] array, final int index, final T expect, final T update) { -- final Object ret = OBJECT_ARRAY_HANDLE.compareAndExchange((Object[])array, index, (Object)expect, (Object)update); -- return (T)ret; -- } -- -- @SuppressWarnings("unchecked") -- public static T getAndSetVolatile(final T[] array, final int index, final T param) { -- final Object ret = BYTE_ARRAY_HANDLE.getAndSet((Object[])array, index, (Object)param); -- return (T)ret; -- } -- -- @SuppressWarnings("unchecked") -- public static T compareAndExchangeVolatileContended(final T[] array, final int index, final T expect, final T update) { -- final Object ret = OBJECT_ARRAY_HANDLE.compareAndExchange((Object[])array, index, (Object)expect, (Object)update); -- return (T)ret; -- } -- -- public static T getAndSetVolatileContended(final T[] array, final int index, final T param) { -- int failures = 0; -- -- for (T curr = getVolatile(array, index);;++failures) { -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -- -- if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -- return curr; -- } -- } -- } --} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java -+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java -@@ -0,0 +0,0 @@ public final class IntegerUtil { - return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 - } - -+ // https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide -+ /** -+ * -+ * Usage: -+ *
-+     * {@code
-+     *     static final long mult = getSimpleMultiplier(divisor, bits);
-+     *     long x = ...;
-+     *     long magic = x * mult;
-+     *     long divQ = magic >>> bits;
-+     *     long divR = ((magic & ((1 << bits) - 1)) * divisor) >>> bits;
-+     * }
-+     * 
-+ * -+ * @param bits The number of bits of precision for the returned result -+ */ -+ public static long getUnsignedDivisorMagic(final long divisor, final int bits) { -+ return (((1L << bits) - 1L) / divisor) + 1; -+ } -+ - private IntegerUtil() { - throw new RuntimeException(); - } -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java -@@ -0,0 +0,0 @@ -+package ca.spottedleaf.concurrentutil.util; -+ -+public enum Priority { -+ -+ /** -+ * Priority value indicating the task has completed or is being completed. -+ * This priority cannot be used to schedule tasks. -+ */ -+ COMPLETING(-1), -+ -+ /** -+ * Absolute highest priority, should only be used for when a task is blocking a time-critical thread. -+ */ -+ BLOCKING(), -+ -+ /** -+ * Should only be used for urgent but not time-critical tasks. -+ */ -+ HIGHEST(), -+ -+ /** -+ * Two priorities above normal. -+ */ -+ HIGHER(), -+ -+ /** -+ * One priority above normal. -+ */ -+ HIGH(), -+ -+ /** -+ * Default priority. -+ */ -+ NORMAL(), -+ -+ /** -+ * One priority below normal. -+ */ -+ LOW(), -+ -+ /** -+ * Two priorities below normal. -+ */ -+ LOWER(), -+ -+ /** -+ * Use for tasks that should eventually execute, but are not needed to. -+ */ -+ LOWEST(), -+ -+ /** -+ * Use for tasks that can be delayed indefinitely. -+ */ -+ IDLE(); -+ -+ // returns whether the priority can be scheduled -+ public static boolean isValidPriority(final Priority priority) { -+ return priority != null && priority != priority.COMPLETING; -+ } -+ -+ // returns the higher priority of the two -+ public static Priority max(final Priority p1, final Priority p2) { -+ return p1.isHigherOrEqualPriority(p2) ? p1 : p2; -+ } -+ -+ // returns the lower priroity of the two -+ public static Priority min(final Priority p1, final Priority p2) { -+ return p1.isLowerOrEqualPriority(p2) ? p1 : p2; -+ } -+ -+ public boolean isHigherOrEqualPriority(final Priority than) { -+ return this.priority <= than.priority; -+ } -+ -+ public boolean isHigherPriority(final Priority than) { -+ return this.priority < than.priority; -+ } -+ -+ public boolean isLowerOrEqualPriority(final Priority than) { -+ return this.priority >= than.priority; -+ } -+ -+ public boolean isLowerPriority(final Priority than) { -+ return this.priority > than.priority; -+ } -+ -+ public boolean isHigherOrEqualPriority(final int than) { -+ return this.priority <= than; -+ } -+ -+ public boolean isHigherPriority(final int than) { -+ return this.priority < than; -+ } -+ -+ public boolean isLowerOrEqualPriority(final int than) { -+ return this.priority >= than; -+ } -+ -+ public boolean isLowerPriority(final int than) { -+ return this.priority > than; -+ } -+ -+ public static boolean isHigherOrEqualPriority(final int priority, final int than) { -+ return priority <= than; -+ } -+ -+ public static boolean isHigherPriority(final int priority, final int than) { -+ return priority < than; -+ } -+ -+ public static boolean isLowerOrEqualPriority(final int priority, final int than) { -+ return priority >= than; -+ } -+ -+ public static boolean isLowerPriority(final int priority, final int than) { -+ return priority > than; -+ } -+ -+ static final Priority[] PRIORITIES = Priority.values(); -+ -+ /** includes special priorities */ -+ public static final int TOTAL_PRIORITIES = PRIORITIES.length; -+ -+ public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1; -+ -+ public static Priority getPriority(final int priority) { -+ return PRIORITIES[priority + 1]; -+ } -+ -+ private static int priorityCounter; -+ -+ private static int nextCounter() { -+ return priorityCounter++; -+ } -+ -+ public final int priority; -+ -+ private Priority() { -+ this(nextCounter()); -+ } -+ -+ private Priority(final int priority) { -+ this.priority = priority; -+ } -+} -\ No newline at end of file diff --git a/patches/server/fixup-More-Teleport-API.patch b/patches/server/fixup-More-Teleport-API.patch deleted file mode 100644 index 838c928b18..0000000000 --- a/patches/server/fixup-More-Teleport-API.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 24 Oct 2024 11:13:42 -0700 -Subject: [PATCH] fixup! More Teleport API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -0,0 +0,0 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); - - world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), -- this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER : ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, (list) -> { -+ this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.util.Priority.HIGHER : ca.spottedleaf.concurrentutil.util.Priority.NORMAL, (list) -> { - net.minecraft.server.level.ServerChunkCache chunkProviderServer = world.getChunkSource(); - for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) { - chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); diff --git a/patches/server/fixup-Optimize-BlockPosition-helper-methods.patch b/patches/server/fixup-Optimize-BlockPosition-helper-methods.patch deleted file mode 100644 index 5718214103..0000000000 --- a/patches/server/fixup-Optimize-BlockPosition-helper-methods.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 21 Oct 2024 19:13:43 -0700 -Subject: [PATCH] fixup! Optimize BlockPosition helper methods - - -diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/core/BlockPos.java -+++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i { - - @Override - public BlockPos above(int distance) { -- return distance == 0 ? this : new BlockPos(this.getX(), this.getY() + distance, this.getZ()); // Paper - Perf: Optimize BlockPosition -+ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() + distance, this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override -@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i { - - @Override - public BlockPos below(int i) { -- return i == 0 ? this : new BlockPos(this.getX(), this.getY() - i, this.getZ()); // Paper - Perf: Optimize BlockPosition -+ return i == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() - i, this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override -@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i { - - @Override - public BlockPos north(int distance) { -- return distance == 0 ? this : new BlockPos(this.getX(), this.getY(), this.getZ() - distance); // Paper - Perf: Optimize BlockPosition -+ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() - distance); // Paper - Perf: Optimize BlockPosition - } - - @Override -@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i { - - @Override - public BlockPos south(int distance) { -- return distance == 0 ? this : new BlockPos(this.getX(), this.getY(), this.getZ() + distance); // Paper - Perf: Optimize BlockPosition -+ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() + distance); // Paper - Perf: Optimize BlockPosition - } - - @Override -@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i { - - @Override - public BlockPos west(int distance) { -- return distance == 0 ? this : new BlockPos(this.getX() - distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition -+ return distance == 0 ? this.immutable() : new BlockPos(this.getX() - distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override -@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i { - - @Override - public BlockPos east(int distance) { -- return distance == 0 ? this : new BlockPos(this.getX() + distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition -+ return distance == 0 ? this.immutable() : new BlockPos(this.getX() + distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override diff --git a/patches/server/fixup-Paper-config-files.patch b/patches/server/fixup-Paper-config-files.patch deleted file mode 100644 index b7b86b9c36..0000000000 --- a/patches/server/fixup-Paper-config-files.patch +++ /dev/null @@ -1,87 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 24 Oct 2024 08:11:12 -0700 -Subject: [PATCH] fixup! Paper config files - - -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -0,0 +0,0 @@ public class GlobalConfiguration extends ConfigurationPart { - public static GlobalConfiguration get() { - return instance; - } -+ -+ public ChunkLoadingBasic chunkLoadingBasic; -+ -+ public class ChunkLoadingBasic extends ConfigurationPart { -+ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.") -+ public double playerMaxChunkSendRate = 75.0; -+ -+ @Comment( -+ "The maximum rate at which chunks will load for any individual player. " + -+ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" + -+ "chunk is already generated. Set to -1 to disable this limit." -+ ) -+ public double playerMaxChunkLoadRate = 100.0; -+ -+ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.") -+ public double playerMaxChunkGenerateRate = -1.0; -+ } -+ -+ public ChunkLoadingAdvanced chunkLoadingAdvanced; -+ -+ public class ChunkLoadingAdvanced extends ConfigurationPart { -+ @Comment( -+ "Set to true if the server will match the chunk send radius that clients have configured" + -+ "in their view distance settings if the client is less-than the server's send distance." -+ ) -+ public boolean autoConfigSendDistance = true; -+ -+ @Comment( -+ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." + -+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." -+ ) -+ public int playerMaxConcurrentChunkLoads = 0; -+ -+ @Comment( -+ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." + -+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." -+ ) -+ public int playerMaxConcurrentChunkGenerates = 0; -+ } - static void set(GlobalConfiguration instance) { - GlobalConfiguration.instance = instance; - } -@@ -0,0 +0,0 @@ public class GlobalConfiguration extends ConfigurationPart { - public int incomingPacketThreshold = 300; - } - -- public ChunkLoading chunkLoading; -- -- public class ChunkLoading extends ConfigurationPart { -- public int minLoadRadius = 2; -- public int maxConcurrentSends = 2; -- public boolean autoconfigSendDistance = true; -- public double targetPlayerChunkSendRate = 100.0; -- public double globalMaxChunkSendRate = -1.0; -- public boolean enableFrustumPriority = false; -- public double globalMaxChunkLoadRate = -1.0; -- public double playerMaxConcurrentLoads = 20.0; -- public double globalMaxConcurrentLoads = 500.0; -- public double playerMaxChunkLoadRate = -1.0; -- } -- - public UnsupportedSettings unsupportedSettings; - - public class UnsupportedSettings extends ConfigurationPart { -@@ -0,0 +0,0 @@ public class GlobalConfiguration extends ConfigurationPart { - - @PostProcess - private void postProcess() { -- //io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); -+ - } - } -