diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/ai/MemoryModificationCounter.java b/src/main/java/net/gensokyoreimagined/nitori/common/ai/MemoryModificationCounter.java new file mode 100644 index 0000000..e41ac5c --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/ai/MemoryModificationCounter.java @@ -0,0 +1,6 @@ +package net.gensokyoreimagined.nitori.common.ai; + +public interface MemoryModificationCounter { + + long lithium$getModCount(); +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/ai/WeightedListIterable.java b/src/main/java/net/gensokyoreimagined/nitori/common/ai/WeightedListIterable.java new file mode 100644 index 0000000..0bd6619 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/ai/WeightedListIterable.java @@ -0,0 +1,45 @@ +package net.gensokyoreimagined.nitori.common.ai; + +import net.minecraft.world.entity.ai.behavior.ShufflingList; + +import java.util.Iterator; + +public interface WeightedListIterable extends Iterable { + /** + * {@inheritDoc} + */ + Iterator iterator(); + + /** + * Returns an {@link Iterable} over the elements in the {@param list}. This allows code to circumvent the usage + * of streams, providing a speed-up in other areas of the game. + */ + @SuppressWarnings("unchecked") + static Iterable cast(ShufflingList list) { + return ((WeightedListIterable) list); + } + + /** + * A wrapper type for an iterator over the entries of a {@link ShufflingList} which de-references the contained + * values for consumers. + * + * @param The value type stored in each list entry + */ + class ListIterator implements Iterator { + private final Iterator> inner; + + public ListIterator(Iterator> inner) { + this.inner = inner; + } + + @Override + public boolean hasNext() { + return this.inner.hasNext(); + } + + @Override + public U next() { + return this.inner.next().getData(); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/entity/NavigatingEntity.java b/src/main/java/net/gensokyoreimagined/nitori/common/entity/NavigatingEntity.java new file mode 100644 index 0000000..180ba61 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/entity/NavigatingEntity.java @@ -0,0 +1,14 @@ +package net.gensokyoreimagined.nitori.common.entity; + +import net.minecraft.world.entity.ai.navigation.PathNavigation; + +public interface NavigatingEntity { + boolean lithium$isRegisteredToWorld(); + + void lithium$setRegisteredToWorld(PathNavigation navigation); + + PathNavigation lithium$getRegisteredNavigation(); + + void lithium$updateNavigationRegistration(); + +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/util/collections/MaskedList.java b/src/main/java/net/gensokyoreimagined/nitori/common/util/collections/MaskedList.java new file mode 100644 index 0000000..9f99c22 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/util/collections/MaskedList.java @@ -0,0 +1,149 @@ +package net.gensokyoreimagined.nitori.common.util.collections; + +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import java.util.*; +import java.util.function.Consumer; + +public class MaskedList extends AbstractList { + private final ObjectArrayList allElements; + private final BitSet visibleMask; + private final Object2IntOpenHashMap element2Index; + private final boolean defaultVisibility; + private int numCleared; + + public MaskedList(ObjectArrayList allElements, boolean defaultVisibility) { + this.allElements = new ObjectArrayList<>(); + this.visibleMask = new BitSet(); + this.defaultVisibility = defaultVisibility; + this.element2Index = new Object2IntOpenHashMap<>(); + this.element2Index.defaultReturnValue(-1); + + this.addAll(allElements); + } + + public MaskedList() { + this(new ObjectArrayList<>(), true); + } + + public int totalSize() { + return this.allElements.size(); + } + + + public void addOrSet(E element, boolean visible) { + int index = this.element2Index.getInt(element); + if (index != -1) { + this.visibleMask.set(index, visible); + } else { + this.add(element); + this.setVisible(element, visible); + } + } + + public void setVisible(E element, final boolean visible) { + int index = this.element2Index.getInt(element); + if (index != -1) { + this.visibleMask.set(index, visible); + } + //ignore when the element is not in the collection + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + int nextIndex = 0; + int cachedNext = -1; + + @Override + public boolean hasNext() { + return (this.cachedNext = MaskedList.this.visibleMask.nextSetBit(this.nextIndex)) != -1; + } + + @Override + public E next() { + int index = this.cachedNext; + this.cachedNext = -1; + this.nextIndex = index + 1; + return MaskedList.this.allElements.get(index); + } + }; + } + + @Override + public Spliterator spliterator() { + return new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL) { + int nextIndex = 0; + + @Override + public boolean tryAdvance(Consumer action) { + int index = MaskedList.this.visibleMask.nextSetBit(this.nextIndex); + if (index == -1) { + return false; + } + this.nextIndex = index + 1; + action.accept(MaskedList.this.allElements.get(index)); + return true; + } + }; + } + + @Override + public boolean add(E e) { + int oldIndex = this.element2Index.put(e, this.allElements.size()); + if (oldIndex != -1) { + throw new IllegalStateException("MaskedList must not contain duplicates! Trying to add " + e + " but it is already present at index " + oldIndex + ". Current size: " + this.allElements.size()); + } + this.visibleMask.set(this.allElements.size(), this.defaultVisibility); + return this.allElements.add(e); + } + + @Override + public boolean remove(Object o) { + int index = this.element2Index.removeInt(o); + if (index == -1) { + return false; + } + this.visibleMask.clear(index); + this.allElements.set(index, null); + this.numCleared++; + + + if (this.numCleared * 2 > this.allElements.size()) { + ObjectArrayList clonedElements = this.allElements.clone(); + BitSet clonedVisibleMask = (BitSet) this.visibleMask.clone(); + this.allElements.clear(); + this.visibleMask.clear(); + this.element2Index.clear(); + for (int i = 0; i < clonedElements.size(); i++) { + E element = clonedElements.get(i); + int newIndex = this.allElements.size(); + this.allElements.add(element); + this.visibleMask.set(newIndex, clonedVisibleMask.get(i)); + this.element2Index.put(element, newIndex); + } + this.numCleared = 0; + } + return true; + } + + @Override + public E get(int index) { + if (index < 0 || index >= this.size()) { + throw new IndexOutOfBoundsException(index); + } + + int i = 0; + while (index >= 0) { + index--; + i = this.visibleMask.nextSetBit(i + 1); + } + return this.allElements.get(i); + } + + @Override + public int size() { + return this.visibleMask.cardinality(); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/util/deduplication/LithiumInternerWrapper.java b/src/main/java/net/gensokyoreimagined/nitori/common/util/deduplication/LithiumInternerWrapper.java new file mode 100644 index 0000000..1ddb82e --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/util/deduplication/LithiumInternerWrapper.java @@ -0,0 +1,8 @@ +package net.gensokyoreimagined.nitori.common.util.deduplication; + +public interface LithiumInternerWrapper { + + T lithium$getCanonical(T value); + + void lithium$deleteCanonical(T value); +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/world/ServerWorldExtended.java b/src/main/java/net/gensokyoreimagined/nitori/common/world/ServerWorldExtended.java new file mode 100644 index 0000000..d90c604 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/world/ServerWorldExtended.java @@ -0,0 +1,9 @@ +package net.gensokyoreimagined.nitori.common.world; + +import net.minecraft.world.entity.Mob; + +public interface ServerWorldExtended { + void lithium$setNavigationActive(Mob mobEntity); + + void lithium$setNavigationInactive(Mob mobEntity); +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/world/blockview/SingleBlockBlockView.java b/src/main/java/net/gensokyoreimagined/nitori/common/world/blockview/SingleBlockBlockView.java new file mode 100644 index 0000000..fa68b6c --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/world/blockview/SingleBlockBlockView.java @@ -0,0 +1,142 @@ +package net.gensokyoreimagined.nitori.common.world.blockview; + +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.core.BlockPos; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.CollisionGetter; +import net.minecraft.world.level.border.WorldBorder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; + +public record SingleBlockBlockView(BlockState state, BlockPos pos) implements BlockGetter, CollisionGetter { + public static SingleBlockBlockView of(BlockState blockState, BlockPos blockPos) { + return new SingleBlockBlockView(blockState, blockPos.immutable()); + } + + @Override + public BlockState getBlockState(BlockPos pos) { + if (pos.equals(this.pos())) { + return this.state(); + } else { + throw SingleBlockViewException.INSTANCE; + } + } + + @Override + public FluidState getFluidState(BlockPos pos) { + if (pos.equals(this.pos())) { + return this.state().getFluidState(); + } else { + throw SingleBlockViewException.INSTANCE; + } + } + + @Nullable + @Override + public BlockEntity getBlockEntity(BlockPos pos) { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public int getHeight() { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public int getMinBuildHeight() { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public WorldBorder getWorldBorder() { + throw SingleBlockViewException.INSTANCE; + } + + @Nullable + @Override + public BlockGetter getChunkForCollisions(int chunkX, int chunkZ) { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public boolean isUnobstructed(@Nullable Entity except, VoxelShape shape) { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public boolean isUnobstructed(BlockState state, BlockPos pos, CollisionContext context) { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public boolean isUnobstructed(Entity entity) { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public boolean noCollision(@Nullable Entity entity, AABB box) { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public List getEntityCollisions(@Nullable Entity entity, AABB box) { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public Iterable getCollisions(@Nullable Entity entity, AABB box) { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public Iterable getBlockCollisions(@Nullable Entity entity, AABB box) { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public boolean collidesWithSuffocatingBlock(@Nullable Entity entity, AABB box) { + throw SingleBlockViewException.INSTANCE; + } + + @Override + public Optional findFreePosition(@Nullable Entity entity, VoxelShape shape, Vec3 target, double x, double y, double z) { + throw SingleBlockViewException.INSTANCE; + } + + public static class SingleBlockViewException extends RuntimeException { + + public static final SingleBlockViewException INSTANCE = new SingleBlockViewException(); + + private SingleBlockViewException() { + this.setStackTrace(new StackTraceElement[0]); + } + + @Override + public synchronized Throwable fillInStackTrace() { + this.setStackTrace(new StackTraceElement[0]); + return this; + } + } + + @javax.annotation.Nullable + @Override + public BlockState getBlockStateIfLoaded(@NotNull BlockPos block) { + throw SingleBlockViewException.INSTANCE; + } + + @javax.annotation.Nullable + @Override + public FluidState getFluidIfLoaded(@NotNull BlockPos block) { + throw SingleBlockViewException.INSTANCE; + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/world/interests/PointOfInterestSetExtended.java b/src/main/java/net/gensokyoreimagined/nitori/common/world/interests/PointOfInterestSetExtended.java new file mode 100644 index 0000000..20a9327 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/world/interests/PointOfInterestSetExtended.java @@ -0,0 +1,14 @@ +package net.gensokyoreimagined.nitori.common.world.interests; + +import net.minecraft.core.Holder; +import net.minecraft.world.entity.ai.village.poi.PoiRecord; +import net.minecraft.world.entity.ai.village.poi.PoiManager; +import net.minecraft.world.entity.ai.village.poi.PoiType; + +import java.util.function.Consumer; +import java.util.function.Predicate; + +public interface PointOfInterestSetExtended { + void lithium$collectMatchingPoints(Predicate> type, PoiManager.Occupancy status, + Consumer consumer); +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/world/interests/iterator/SinglePointOfInterestTypeFilter.java b/src/main/java/net/gensokyoreimagined/nitori/common/world/interests/iterator/SinglePointOfInterestTypeFilter.java new file mode 100644 index 0000000..c53f4ff --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/world/interests/iterator/SinglePointOfInterestTypeFilter.java @@ -0,0 +1,19 @@ +package net.gensokyoreimagined.nitori.common.world.interests.iterator; + +import net.minecraft.core.Holder; +import net.minecraft.world.entity.ai.village.poi.PoiType; + +import java.util.function.Predicate; + +public record SinglePointOfInterestTypeFilter( + Holder type) implements Predicate> { + + @Override + public boolean test(Holder other) { + return this.type == other; + } + + public Holder getType() { + return this.type; + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/world/interests/types/PointOfInterestTypeHelper.java b/src/main/java/net/gensokyoreimagined/nitori/common/world/interests/types/PointOfInterestTypeHelper.java new file mode 100644 index 0000000..50fa001 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/world/interests/types/PointOfInterestTypeHelper.java @@ -0,0 +1,24 @@ +package net.gensokyoreimagined.nitori.common.world.interests.types; + +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunkSection; + +import java.util.Set; +import java.util.function.Predicate; + +public class PointOfInterestTypeHelper { + private static Predicate POI_BLOCKSTATE_PREDICATE; + + + public static void init(Set types) { + if (POI_BLOCKSTATE_PREDICATE != null) { + throw new IllegalStateException("Already initialized"); + } + + POI_BLOCKSTATE_PREDICATE = types::contains; + } + + public static boolean mayHavePoi(LevelChunkSection chunkSection) { + return chunkSection.maybeHas(POI_BLOCKSTATE_PREDICATE); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/executor/annotation/thread/AssistThreadOnly.java b/src/main/java/net/gensokyoreimagined/nitori/executor/annotation/thread/AssistThreadOnly.java index 829c829..7dba937 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/executor/annotation/thread/AssistThreadOnly.java +++ b/src/main/java/net/gensokyoreimagined/nitori/executor/annotation/thread/AssistThreadOnly.java @@ -1,31 +1,31 @@ package net.gensokyoreimagined.nitori.executor.annotation.thread; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - -/** - * An annotation primarily for methods, identifying methods that can only be called on a thread that is an instance - * of {@link AssistThread}. - *
- * This annotation can also be used on fields or classes, similar to {@link ThreadRestricted}. - *
- * In a method annotated with {@link AssistThreadOnly}, fields and methods annotated with - * {@link AssistThreadOnly}, {@link BaseThreadOnly} or {@link AnyThreadSafe} may be used. - *
- * Methods that are annotated with {@link AssistThreadOnly} must never call methods that are annotated with - * {@link PotentiallyBlocking}. - * - * @author Martijn Muijsers under AGPL-3.0 - */ -@SuppressWarnings("unused") -@Documented -@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) -public @interface AssistThreadOnly { - - /** - * @see ThreadRestricted#fieldAccess() - */ - Access value() default Access.READ_WRITE; - -} \ No newline at end of file +//import java.lang.annotation.Documented; +//import java.lang.annotation.ElementType; +//import java.lang.annotation.Target; +// +///** +// * An annotation primarily for methods, identifying methods that can only be called on a thread that is an instance +// * of {@link AssistThread}. +// *
+// * This annotation can also be used on fields or classes, similar to {@link ThreadRestricted}. +// *
+// * In a method annotated with {@link AssistThreadOnly}, fields and methods annotated with +// * {@link AssistThreadOnly}, {@link BaseThreadOnly} or {@link AnyThreadSafe} may be used. +// *
+// * Methods that are annotated with {@link AssistThreadOnly} must never call methods that are annotated with +// * {@link PotentiallyBlocking}. +// * +// * @author Martijn Muijsers under AGPL-3.0 +// */ +//@SuppressWarnings("unused") +//@Documented +//@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) +//public @interface AssistThreadOnly { +// +// /** +// * @see ThreadRestricted#fieldAccess() +// */ +// Access value() default Access.READ_WRITE; +// +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/executor/queue/BaseTaskQueueTier.java b/src/main/java/net/gensokyoreimagined/nitori/executor/queue/BaseTaskQueueTier.java index e0fa918..6f322fa 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/executor/queue/BaseTaskQueueTier.java +++ b/src/main/java/net/gensokyoreimagined/nitori/executor/queue/BaseTaskQueueTier.java @@ -1,114 +1,114 @@ package net.gensokyoreimagined.nitori.executor.queue; -/** - * A tier for {@link AbstractTaskQueue}s, that indicates the priority of the tasks in the task queues. - * Every tier contains a list of the queues that are part of the tier. - * The tiers are in order of priority, from high to low. - * Similarly, the queues for each tier are in the same order of priority. - * The tasks in each queue should also be in order of priority whenever relevant, but usually there - * is no strong difference in priority between tasks in the same queue, so they typically operate as FIFO queues, - * so that the longest waiting task implicitly has the highest priority within the queue. - *
- * Tasks from queues in the {@link #SERVER} tier can only be run on a {@link ServerThread}. - * Tasks from other tiers can be run on {@link ServerThread}s as well as on {@link AssistThread}s. - * - * @author Martijn Muijsers under AGPL-3.0 - */ -public enum BaseTaskQueueTier { - - /** - * A tier for queues that contain tasks that must be executed on a {@link ServerThread}. - *
- * Some parts of the server can only be safely accessed by one thread at a time. - * If they can not be guarded by a lock (or if this is not desired, - * because if a ticking thread would need to acquire this lock it would block it), - * then these parts of the code are typically deferred to the server thread. - * Based on the current use of the {@link TickThread} class, particularly given the existence of - * {@link TickThread#isTickThreadFor(Entity)} and {@link TickThread#isTickThreadFor(ServerLevel, int, int)}, - * we can deduce that future support for performing some of these actions in parallel is planned. - * In such a case, some server thread tasks may become tasks that must be - * executed on an appropriate {@link TickThread}. - * In that case, the queues below should be changed so that the server thread and any of the - * ticking threads poll from queues that contain tasks appropriate for them. - * For example, {@link BaseTaskQueues#deferredToUniversalTickThread} would be for tasks that can run - * on any ticking thread, and additional queues would need to be added concerning a specific - * subject (like an entity or chunk) with tasks that will be run on whichever ticking thread is the - * ticking thread for that subject at the time of polling. - *
- * Note that a {@link ServerThread} can only yield to {@link TaskSpan#TINY} tasks in other tiers - * (since there are no higher tiers, and threads can only yield to lower tiers when - * the task yielded to is{@link TaskSpan#TINY}, or other non-yielding tasks in its own tier (since it - * has a {@link BaseThread#maximumYieldDepth} of 1). - * Yielding to other tasks in this same tier is somewhat risky, since this means that the tasks that were - * yielded to must assume that although they are running on the server thread, they may be running at - * some unknown point in execution of the main thread. Therefore, scheduling any non-yielding tasks to - * a queue in this tier must be done with the utmost care that the task cannot disrupt, or be disrupted by, - * the surrounding code that yields to it. - */ - SERVER(new AbstractTaskQueue[]{ - BaseTaskQueues.deferredToServerThread, - BaseTaskQueues.serverThreadTick, - BaseTaskQueues.anyTickScheduledServerThread - }, MinecraftServer.SERVER_THREAD_PRIORITY), - /** - * A tier for queues that contain tasks that are part of ticking, - * to assist the main ticking thread(s) in doing so. - */ - TICK_ASSIST(new AbstractTaskQueue[]{ - BaseTaskQueues.tickAssist - }, Integer.getInteger("gale.thread.priority.tick", 7)), - /** - * A tier for queues that contain general tasks that must be performed at some point in time, - * asynchronously with respect to the {@link ServerThread} and the ticking of the server. - * Execution of - */ - ASYNC(new AbstractTaskQueue[0], Integer.getInteger("gale.thread.priority.async", 6)), - /** - * A tier for queues that contain tasks with the same considerations as {@link #ASYNC}, - * but with a low priority. - */ - LOW_PRIORITY_ASYNC(new AbstractTaskQueue[0], Integer.getInteger("gale.thread.priority.async.low", 3)); - - /** - * Equal to {@link #ordinal()}. - */ - public final int ordinal; - - /** - * The task queues that belong to this tier. - */ - public final AbstractTaskQueue[] taskQueues; - - /** - * The priority for threads that are executing a task from this tier. - *
- * If a thread yields to other tasks, the priority it will have is always the highest priority of any task - * on its stack. - */ - public final int threadPriority; - - BaseTaskQueueTier(AbstractTaskQueue[] taskQueues, int threadPriority) { - this.ordinal = this.ordinal(); - this.taskQueues = taskQueues; - for (AbstractTaskQueue queue : this.taskQueues) { - queue.setTier(this); - } - this.threadPriority = threadPriority; - } - - /** - * Equal to {@link #values()}. - */ - public static final BaseTaskQueueTier[] VALUES = values(); - - /** - * Equal to {@link #VALUES}{@code .length}. - */ - public static final int length = VALUES.length; - - /** - * Equal to {@link #VALUES} without {@link #SERVER}. - */ - public static final BaseTaskQueueTier[] VALUES_EXCEPT_SERVER = Arrays.stream(VALUES).filter(tier -> tier != SERVER).toList().toArray(new BaseTaskQueueTier[length - 1]); - -} +///** +// * A tier for {@link AbstractTaskQueue}s, that indicates the priority of the tasks in the task queues. +// * Every tier contains a list of the queues that are part of the tier. +// * The tiers are in order of priority, from high to low. +// * Similarly, the queues for each tier are in the same order of priority. +// * The tasks in each queue should also be in order of priority whenever relevant, but usually there +// * is no strong difference in priority between tasks in the same queue, so they typically operate as FIFO queues, +// * so that the longest waiting task implicitly has the highest priority within the queue. +// *
+// * Tasks from queues in the {@link #SERVER} tier can only be run on a {@link ServerThread}. +// * Tasks from other tiers can be run on {@link ServerThread}s as well as on {@link AssistThread}s. +// * +// * @author Martijn Muijsers under AGPL-3.0 +// */ +//public enum BaseTaskQueueTier { +// +// /** +// * A tier for queues that contain tasks that must be executed on a {@link ServerThread}. +// *
+// * Some parts of the server can only be safely accessed by one thread at a time. +// * If they can not be guarded by a lock (or if this is not desired, +// * because if a ticking thread would need to acquire this lock it would block it), +// * then these parts of the code are typically deferred to the server thread. +// * Based on the current use of the {@link TickThread} class, particularly given the existence of +// * {@link TickThread#isTickThreadFor(Entity)} and {@link TickThread#isTickThreadFor(ServerLevel, int, int)}, +// * we can deduce that future support for performing some of these actions in parallel is planned. +// * In such a case, some server thread tasks may become tasks that must be +// * executed on an appropriate {@link TickThread}. +// * In that case, the queues below should be changed so that the server thread and any of the +// * ticking threads poll from queues that contain tasks appropriate for them. +// * For example, {@link BaseTaskQueues#deferredToUniversalTickThread} would be for tasks that can run +// * on any ticking thread, and additional queues would need to be added concerning a specific +// * subject (like an entity or chunk) with tasks that will be run on whichever ticking thread is the +// * ticking thread for that subject at the time of polling. +// *
+// * Note that a {@link ServerThread} can only yield to {@link TaskSpan#TINY} tasks in other tiers +// * (since there are no higher tiers, and threads can only yield to lower tiers when +// * the task yielded to is{@link TaskSpan#TINY}, or other non-yielding tasks in its own tier (since it +// * has a {@link BaseThread#maximumYieldDepth} of 1). +// * Yielding to other tasks in this same tier is somewhat risky, since this means that the tasks that were +// * yielded to must assume that although they are running on the server thread, they may be running at +// * some unknown point in execution of the main thread. Therefore, scheduling any non-yielding tasks to +// * a queue in this tier must be done with the utmost care that the task cannot disrupt, or be disrupted by, +// * the surrounding code that yields to it. +// */ +// SERVER(new AbstractTaskQueue[]{ +// BaseTaskQueues.deferredToServerThread, +// BaseTaskQueues.serverThreadTick, +// BaseTaskQueues.anyTickScheduledServerThread +// }, MinecraftServer.SERVER_THREAD_PRIORITY), +// /** +// * A tier for queues that contain tasks that are part of ticking, +// * to assist the main ticking thread(s) in doing so. +// */ +// TICK_ASSIST(new AbstractTaskQueue[]{ +// BaseTaskQueues.tickAssist +// }, Integer.getInteger("gale.thread.priority.tick", 7)), +// /** +// * A tier for queues that contain general tasks that must be performed at some point in time, +// * asynchronously with respect to the {@link ServerThread} and the ticking of the server. +// * Execution of +// */ +// ASYNC(new AbstractTaskQueue[0], Integer.getInteger("gale.thread.priority.async", 6)), +// /** +// * A tier for queues that contain tasks with the same considerations as {@link #ASYNC}, +// * but with a low priority. +// */ +// LOW_PRIORITY_ASYNC(new AbstractTaskQueue[0], Integer.getInteger("gale.thread.priority.async.low", 3)); +// +// /** +// * Equal to {@link #ordinal()}. +// */ +// public final int ordinal; +// +// /** +// * The task queues that belong to this tier. +// */ +// public final AbstractTaskQueue[] taskQueues; +// +// /** +// * The priority for threads that are executing a task from this tier. +// *
+// * If a thread yields to other tasks, the priority it will have is always the highest priority of any task +// * on its stack. +// */ +// public final int threadPriority; +// +// BaseTaskQueueTier(AbstractTaskQueue[] taskQueues, int threadPriority) { +// this.ordinal = this.ordinal(); +// this.taskQueues = taskQueues; +// for (AbstractTaskQueue queue : this.taskQueues) { +// queue.setTier(this); +// } +// this.threadPriority = threadPriority; +// } +// +// /** +// * Equal to {@link #values()}. +// */ +// public static final BaseTaskQueueTier[] VALUES = values(); +// +// /** +// * Equal to {@link #VALUES}{@code .length}. +// */ +// public static final int length = VALUES.length; +// +// /** +// * Equal to {@link #VALUES} without {@link #SERVER}. +// */ +// public static final BaseTaskQueueTier[] VALUES_EXCEPT_SERVER = Arrays.stream(VALUES).filter(tier -> tier != SERVER).toList().toArray(new BaseTaskQueueTier[length - 1]); +// +//} diff --git a/src/main/java/net/gensokyoreimagined/nitori/executor/thread/AssistThread.java b/src/main/java/net/gensokyoreimagined/nitori/executor/thread/AssistThread.java index 51a80d5..faf6a98 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/executor/thread/AssistThread.java +++ b/src/main/java/net/gensokyoreimagined/nitori/executor/thread/AssistThread.java @@ -1,69 +1,69 @@ package net.gensokyoreimagined.nitori.executor.thread; -/** - * A thread created by the {@link BaseThreadPool}. - * - * @author Martijn Muijsers under AGPL-3.0 - */ -public class AssistThread extends BaseThread { - - /** - * The maximum yield depth. While an {@link AssistThread} has a yield depth equal to or greater than this value, - * it can not start more potentially yielding tasks. - */ - public static final int MAXIMUM_YIELD_DEPTH = Integer.getInteger("gale.yield.depth.max", 100); - - /** - * The index of this thread, as needed as an argument to - * {@link BaseThreadPool#getThreadByAssistIndex(int)}. - */ - public final int assistThreadIndex; - - /** - * Must only be called from {@link BaseThreadPool#addAssistThread}. - */ - public AssistThread(int assistThreadIndex) { - super(AssistThread::getCurrentAssistThreadAndRunForever, "Assist Thread " + assistThreadIndex, assistThreadIndex + 1, MAXIMUM_YIELD_DEPTH); - this.assistThreadIndex = assistThreadIndex; - } - - /** - * Causes this thread to loop forever, always attempting to find a task to do, and if none is found, - * registering itself with the places where a relevant task may be added in order to be signalled when - * one is actually added. - */ - @ThisThreadOnly - protected void runForever() { - this.runTasksUntil(null, () -> false, null); - } - - /** - * @return The current thread if it is a {@link AssistThread}, or null otherwise. - */ - @SuppressWarnings("unused") - @AnyThreadSafe - @YieldFree - public static @Nullable AssistThread currentAssistThread() { - return Thread.currentThread() instanceof AssistThread assistThread ? assistThread : null; - } - - /** - * @return Whether the current thread is a {@link AssistThread}. - */ - @SuppressWarnings("unused") - @AnyThreadSafe - @YieldFree - public static boolean isAssistThread() { - return Thread.currentThread() instanceof AssistThread; - } - - /** - * A method that simply acquires the {@link AssistThread} that is the current thread, and calls - * {@link #runForever()} on it. - */ - @BaseThreadOnly - protected static void getCurrentAssistThreadAndRunForever() { - ((AssistThread) Thread.currentThread()).runForever(); - } - -} +///** +// * A thread created by the {@link BaseThreadPool}. +// * +// * @author Martijn Muijsers under AGPL-3.0 +// */ +//public class AssistThread extends BaseThread { +// +// /** +// * The maximum yield depth. While an {@link AssistThread} has a yield depth equal to or greater than this value, +// * it can not start more potentially yielding tasks. +// */ +// public static final int MAXIMUM_YIELD_DEPTH = Integer.getInteger("gale.yield.depth.max", 100); +// +// /** +// * The index of this thread, as needed as an argument to +// * {@link BaseThreadPool#getThreadByAssistIndex(int)}. +// */ +// public final int assistThreadIndex; +// +// /** +// * Must only be called from {@link BaseThreadPool#addAssistThread}. +// */ +// public AssistThread(int assistThreadIndex) { +// super(AssistThread::getCurrentAssistThreadAndRunForever, "Assist Thread " + assistThreadIndex, assistThreadIndex + 1, MAXIMUM_YIELD_DEPTH); +// this.assistThreadIndex = assistThreadIndex; +// } +// +// /** +// * Causes this thread to loop forever, always attempting to find a task to do, and if none is found, +// * registering itself with the places where a relevant task may be added in order to be signalled when +// * one is actually added. +// */ +// @ThisThreadOnly +// protected void runForever() { +// this.runTasksUntil(null, () -> false, null); +// } +// +// /** +// * @return The current thread if it is a {@link AssistThread}, or null otherwise. +// */ +// @SuppressWarnings("unused") +// @AnyThreadSafe +// @YieldFree +// public static @Nullable AssistThread currentAssistThread() { +// return Thread.currentThread() instanceof AssistThread assistThread ? assistThread : null; +// } +// +// /** +// * @return Whether the current thread is a {@link AssistThread}. +// */ +// @SuppressWarnings("unused") +// @AnyThreadSafe +// @YieldFree +// public static boolean isAssistThread() { +// return Thread.currentThread() instanceof AssistThread; +// } +// +// /** +// * A method that simply acquires the {@link AssistThread} that is the current thread, and calls +// * {@link #runForever()} on it. +// */ +// @BaseThreadOnly +// protected static void getCurrentAssistThreadAndRunForever() { +// ((AssistThread) Thread.currentThread()).runForever(); +// } +// +//} diff --git a/src/main/java/net/gensokyoreimagined/nitori/executor/thread/BaseThread.java b/src/main/java/net/gensokyoreimagined/nitori/executor/thread/BaseThread.java index c5cfb97..4336fb9 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/executor/thread/BaseThread.java +++ b/src/main/java/net/gensokyoreimagined/nitori/executor/thread/BaseThread.java @@ -1,743 +1,743 @@ package net.gensokyoreimagined.nitori.executor.thread; -import net.gensokyoreimagined.nitori.executor.wrapper.MinecraftServerWrapper; - -/** - * An abstract base class implementing {@link AbstractYieldingThread}, - * that provides implementation that is common between - * {@link TickThread} and {@link AssistThread}. - * - * @author Martijn Muijsers under AGPL-3.0 - */ -public abstract class BaseThread extends Thread implements AbstractYieldingThread { - - /** - * The minimum time to wait as the {@link MinecraftServer#serverThread} when performing a timed wait. - * Given in nanoseconds. - * If a timed wait with a lower time is attempted, the wait is not performed at all. - */ - public static final long SERVER_THREAD_WAIT_NANOS_MINIMUM = 10_000; - - /** - * The time to wait as the {@link MinecraftServer#serverThread} during the oversleep phase, if - * there may be delayed tasks. - * Given in nanoseconds. - */ - public static final long SERVER_THREAD_WAIT_NANOS_DURING_OVERSLEEP_WITH_DELAYED_TASKS = 50_000; - - /** - * The index of this thread, as needed as an argument to - * {@link BaseThreadPool#getThreadByBaseIndex(int)}. - */ - public final int baseThreadIndex; - - /** - * The maximum yield depth for this thread, - * which equals 1 for a {@link ServerThread} - * and {@link AssistThread#MAXIMUM_YIELD_DEPTH} for an {@link AssistThread}. - */ - public final int maximumYieldDepth; - - /** - * The number of times this thread holds a {@link YieldingLock}, - * used in {@link #holdsYieldingLock()}. - * - * @see AbstractYieldingThread#incrementHeldYieldingLockCount() - */ - @ThisThreadOnly - public int heldYieldingLockCount = 0; - - /** - * The current yield depth of this thread. - */ - @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) - public volatile int yieldDepth = 0; - - /** - * Whether this thread can currently start yielding tasks with respect to being restricted - * due to {@link #yieldDepth} being at least {@link #maximumYieldDepth}. - *
- * This is updated using {@link #updateCanStartYieldingTasks()} - * after {@link #yieldDepth} or {@link #heldYieldingLockCount} is changed. - */ - @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) - public volatile boolean canStartYieldingTasks = true; - - /** - * The highest {@link BaseTaskQueueTier} of any task on the yielding execution stack of this thread, - * or null if there is no task being executed on this thread. - */ - @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) - public volatile @Nullable BaseTaskQueueTier highestTierOfTaskOnStack; - - /** - * The {@link BaseTaskQueueTier} that the last non-null return value of {@link #pollTask} was polled from, - * or null if {@link #pollTask} has never been called yet. - */ - @ThisThreadOnly - private @Nullable BaseTaskQueueTier lastPolledTaskTier; - - /** - * The lock to guard this thread's sleeping and waking actions. - */ - private final Lock waitLock = new ReentrantLock(); - - /** - * The condition to wait for a signal, when this thread has to wait for something to do. - */ - private final Condition waitCondition = waitLock.newCondition(); - - /** - * Whether this thread is currently not working on the content of a task, but instead - * attempting to poll a next task to do, checking whether it can accept tasks at all, or - * attempting to acquire a {@link YieldingLock}, or waiting (although the fact that this value is true during - * waiting is irrelevant, because at such a time, {@link #isWaiting} will be true, and this value will no longer - * have any effect due to the implementation of {@link #signal}). - *
- * This value is used to determine whether to set {@link #skipNextWait} when {@link #signal} is called - * and {@link #isWaiting} is false. - */ - @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) - private volatile boolean isPollingTaskOrCheckingStopCondition = true; - - /** - * Whether this thread should not start waiting for something to do the next time no task could be polled, - * but instead try polling a task again. - */ - @AnyThreadSafe - public volatile boolean skipNextWait = false; - - /** - * Whether this thread is currently waiting for something to do. - *
- * This is set to true at some point before actually starting to wait in a blocking fashion, - * and set to false at some point after no longer waiting in a blocking fashion. So, at some point, - * this value may be true while the thread is not blocked yet, or anymore. - * Even more so, extra checks for whether the thread should block will be performed in between - * the moment this value is set to true and the moment the thread potentially blocks. This means that if the - * checks fail, this value may be set to true and then false again, without actually ever blocking. - */ - @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) - @Guarded(value = "#waitLock", fieldAccess = Access.WRITE) - public volatile boolean isWaiting = false; - - /** - * Whether {@link #isWaiting} is irrelevant because this thread has already - * been signalled via {@link #signal} to wake up. - */ - @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) - @Guarded(value = "#waitLock", fieldAccess = Access.WRITE) - public volatile boolean mayBeStillWaitingButHasBeenSignalled = false; - - /** - * The {@link YieldingLock} that this thread is waiting for, - * or null if this thread is not waiting for a {@link YieldingLock}. - * This value only has meaning while {@link #isWaiting} is true. - */ - @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) - @Guarded(value = "#waitLock", fieldAccess = Access.WRITE) - public volatile @Nullable YieldingLock lockWaitingFor = null; - - /** - * The value of {@link #lockWaitingFor} during the last wait (a call to {@link Condition#await}) - * or pre-wait check (while {@link #isNotActuallyWaitingYet} is true). - */ - @ThisThreadOnly - private @Nullable YieldingLock lastLockWaitedFor = null; - - /** - * A special flag, used after changing {@link #isWaiting}, when the lock must be temporarily released to - * call {@link BaseThreadActivation#callForUpdate()} (to avoid deadlocks in {@link #signal} calls), - * and we wish the pool to regard this thread as waiting - * (which it will, because {@link #isWaiting} will be true), but we must still - * know not to signal the underlying {@link #waitCondition}, but set {@link #skipNextWait} to true, - * when {@link #signal} is called at some point during the short release of {@link #waitLock}. - */ - public volatile boolean isNotActuallyWaitingYet = false; - - /** - * The last reason this thread was signalled before the current poll attempt, or null if the current - * poll attempt was not preceded by signalling (but by yielding for example). - */ - public volatile @Nullable SignalReason lastSignalReason = null; - - protected BaseThread(Runnable target, String name, int baseThreadIndex, int maximumYieldDepth) { - super(target, name); - this.baseThreadIndex = baseThreadIndex; - this.maximumYieldDepth = maximumYieldDepth; - } - - @Override - public boolean holdsYieldingLock() { - return this.heldYieldingLockCount > 0; - } - - @Override - public void incrementHeldYieldingLockCount() { - this.heldYieldingLockCount++; - if (this.heldYieldingLockCount == 1) { - this.updateCanStartYieldingTasks(); - } - } - - @Override - public void decrementHeldYieldingLockCount() { - this.heldYieldingLockCount--; - if (this.heldYieldingLockCount == 0) { - this.updateCanStartYieldingTasks(); - } - } - - /** - * Updates {@link #canStartYieldingTasks} according to {@link #yieldDepth} and {@link #heldYieldingLockCount}. - */ - private void updateCanStartYieldingTasks() { - this.canStartYieldingTasks = this.heldYieldingLockCount == 0 && this.yieldDepth < this.maximumYieldDepth; - } - - /** - * This method is based on {@link #signal}. - * {@link #signal} must always return true if this method returns true; - * otherwise {@link BaseThreadActivation} will get stuck while choosing a thread to activate. - * - * @see #signal - */ - @SuppressWarnings("RedundantIfStatement") - public boolean isWaitingAndNeedsSignal() { - if (this.isWaiting) { - if (this.isNotActuallyWaitingYet) { - if (!this.skipNextWait) { - return true; - } - return false; - } - if (!this.mayBeStillWaitingButHasBeenSignalled) { - return true; - } - } - return false; - } - - /** - * Yields to tasks: polls and executes tasks while possible and the stop condition is not met. - * The stop condition is met if {@code stopCondition} is not null and returns true, or alternatively, - * if {@code stopCondition} is null, and {@code yieldingLock} is successfully acquired. - * When no tasks can be polled, this thread will block, waiting for either a task that can be executed by this - * thread to become available, or for the {@code yieldingLock}, if given, to be released. - *
- * Exactly one of {@code stopCondition} and {@code yieldingLock} must be non-null. - */ - public final void yieldUntil(@Nullable Long timeoutTime, @Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock) { - int oldYieldDepth = this.yieldDepth; - int newYieldDepth = oldYieldDepth + 1; - this.yieldDepth = newYieldDepth; - if (newYieldDepth == maximumYieldDepth) { - this.updateCanStartYieldingTasks(); - } - this.runTasksUntil(timeoutTime, stopCondition, yieldingLock); - this.yieldDepth = oldYieldDepth; - if (newYieldDepth == maximumYieldDepth) { - this.updateCanStartYieldingTasks(); - } - } - - /** - * This method will keep attempting to find a task to do, and execute it, and if none is found, start waiting - * until the {@code timeoutTime} is reached (which is compared to {@link System#nanoTime}), - * or the thread is signalled by {@link BaseThreadPool} or by a {@link YieldingLock}. - * The loop is broken as soon as the stop condition becomes true, or the given lock is successfully acquired. - *
- * The above is the same as {@link #yieldUntil}, except it may be called in situations that is not 'yielding', - * for instance the endless loop polling tasks performed by a n{@link AssistThread}. The difference with - * {@link #yieldUntil} is that this method does not increment or decrement things the yield depth of this thread. - *
- * Exactly one of {@code stopCondition} or {@code yieldingLock} must be non-null. - * - * @see #yieldUntil - */ - @ThisThreadOnly - @PotentiallyYielding("may yield further if an executed task is potentially yielding") - public final void runTasksUntil(@Nullable Long timeoutTime, @Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock) { - if (TickThread.isTickThread()) MinecraftServer.THREAD_DEBUG_LOGGER.ifPresent(it -> it.info("running tasks until")); - this.isPollingTaskOrCheckingStopCondition = true; - - /* - Endless loop that attempts to perform a task, and if one is found, tries to perform another again, - but if none is found, starts awaiting such a task to become available, or for the given yielding lock - to be released. - */ - while (true) { - try { - if (timeoutTime != null && System.nanoTime() - timeoutTime >= 0) { - break; - } - if (stopCondition != null) { - if (this == MinecraftServerWrapper.serverThread) { - MinecraftServer.currentManagedBlockStopConditionHasBecomeTrue = false; - } - if (stopCondition.getAsBoolean()) { - if (this == MinecraftServerWrapper.serverThread) { - MinecraftServer.currentManagedBlockStopConditionHasBecomeTrue = true; - } - break; - } - } else { - //noinspection ConstantConditions - if (yieldingLock.tryLock()) { - break; - } - } - } finally { - // Make sure other threads can be signalled for the last waited-for lock again - if (this.lastLockWaitedFor != null) { - this.lastLockWaitedFor.canBeSignalledFor = true; - this.lastLockWaitedFor = null; - } - } - - // If this is the original server thread, update isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking - if (this == MinecraftServerWrapper.serverThread) { - MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking = MinecraftServer.isInSpareTime && MinecraftServer.blockingCount == 0 && !MinecraftServer.SERVER.haveTime(); - } - - // Attempt to poll a task that can be started - Runnable task = this.pollTask(); - - // Run the task if found - if (task != null) { - - // If this is the server thread, potentially set nextTimeAssumeWeMayHaveDelayedTasks to true - if (this == MinecraftServerWrapper.serverThread && !MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks && AbstractTaskQueue.taskQueuesHaveTasks(BaseTaskQueueTier.SERVER.taskQueues)) { - MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks = true; - } - - // Update highestTierOfTaskOnStack and the thread priority - var highestTierBeforeTask = this.highestTierOfTaskOnStack; - var threadPriorityBeforeTask = this.getPriority(); - //noinspection DataFlowIssue - var newHighestTier = highestTierBeforeTask == null ? this.lastPolledTaskTier : highestTierBeforeTask.ordinal < this.lastPolledTaskTier.ordinal ? highestTierBeforeTask : this.lastPolledTaskTier; - //noinspection DataFlowIssue - var newThreadPriority = newHighestTier.threadPriority; - if (newHighestTier != highestTierBeforeTask) { - this.highestTierOfTaskOnStack = newHighestTier; - BaseThreadActivation.callForUpdate(); - if (threadPriorityBeforeTask != newThreadPriority) { - this.setPriority(newThreadPriority); - } - } - - this.isPollingTaskOrCheckingStopCondition = false; - task.run(); - - // If this is the server thread, execute some chunk tasks - if (this == MinecraftServerWrapper.serverThread) { - if (newHighestTier != BaseTaskQueueTier.SERVER) { - newHighestTier = BaseTaskQueueTier.SERVER; - this.highestTierOfTaskOnStack = newHighestTier; - BaseThreadActivation.callForUpdate(); - if (newThreadPriority != newHighestTier.threadPriority) { - newThreadPriority = newHighestTier.threadPriority; - this.setPriority(newThreadPriority); - } - } - MinecraftServer.SERVER.executeMidTickTasks(); // Paper - execute chunk tasks mid tick - } - - // Reset highestTierOfTaskOnStack and the thread priority - if (newHighestTier != highestTierBeforeTask) { - this.highestTierOfTaskOnStack = highestTierBeforeTask; - BaseThreadActivation.callForUpdate(); - if (threadPriorityBeforeTask != newThreadPriority) { - this.setPriority(threadPriorityBeforeTask); - } - } - - this.isPollingTaskOrCheckingStopCondition = true; - continue; - - } - - /* - If no task that can be started by this thread was found, wait for a task that we are allowed - to poll to become available (when that happens, the BaseThreadPool will signal this thread), - or for the given yielding lock to be released. This is the only time we should ever block inside - a potentially yielding procedure. - */ - this.waitUntilSignalled(timeoutTime, yieldingLock); - - } - - this.isPollingTaskOrCheckingStopCondition = false; - - /* - If the thread was signalled for another reason than the lock, but we acquired the lock instead, - another thread should be signalled for that reason. - */ - SignalReason lastSignalReason = this.lastSignalReason; - if (lastSignalReason != null && yieldingLock != null && lastSignalReason != SignalReason.YIELDING_LOCK) { - BaseThreadActivation.callForUpdate(); - } - - } - - /** - * @see #pollTask() - */ - @ThisThreadOnly - @YieldFree - private @Nullable Runnable pollTaskFromTier(BaseTaskQueueTier tier, boolean tinyOnly) { - for (var queue : tier.taskQueues) { - // Check whether we can not yield to the queue, if we are yielding - boolean canQueueBeYieldedTo = queue.canBeYieldedTo(); - if (!canQueueBeYieldedTo && this.yieldDepth > 0) { - continue; - } - Runnable task = tinyOnly ? queue.pollTiny(this) : queue.poll(this); - if (task != null) { - this.lastPolledTaskTier = tier; - return task; - } - /* - Check if the tier has run out of tasks for a span, - in order to update BaseThreadActivation#thereMayBeTasks. - */ - for (int spanI = 0; spanI < TaskSpan.length; spanI++) { - TaskSpan span = TaskSpan.VALUES[spanI]; - if (queue.canHaveTasks(span)) { - int oldTasks = BaseThreadActivation.thereMayBeTasks[tier.ordinal][spanI][canQueueBeYieldedTo ? 1 : 0].get(); - if (oldTasks > 0) { - if (!queue.hasTasks(span)) { - boolean tierHasNoTasksForSpan = true; - for (AbstractTaskQueue otherTierQueue : tier.taskQueues) { - // We already know there are no tasks in this queue - if (otherTierQueue == queue) { - continue; - } - if (otherTierQueue.hasTasks(span)) { - tierHasNoTasksForSpan = false; - break; - } - } - if (tierHasNoTasksForSpan) { - // Set thereMayBeTasks to false, but only if it did not change in the meantime - BaseThreadActivation.thereMayBeTasks[tier.ordinal][spanI][canQueueBeYieldedTo ? 1 : 0].compareAndSet(oldTasks, 0); - } - } - } - } - } - } - return null; - } - - /** - * Polls a task from any queue this thread can currently poll from, and returns it. - * Polling potentially yielding tasks is attempted before yield-free tasks. - * - * @return The task that was polled, or null if no task was found. - */ - @ThisThreadOnly - @YieldFree - private @Nullable Runnable pollTask() { - /* - * If this is a server thread, poll from SERVER, and poll tiny tasks from other tiers. - * Note that when polling on the ServerThread, we do not check whether we would be allowed to do so - * by the BaseThreadPool, as we consider keeping the ServerThread in the Thread.State.RUNNABLE state for - * as long as possible to be more important than the off-chance of for example starting a TINY ASYNC task - * on the server thread while no ASYNC tasks are allowed to be polled by other threads at the moment. - */ - if (this instanceof ServerThread) { - // Poll from the SERVER queues - Runnable task = this.pollTaskFromTier(BaseTaskQueueTier.SERVER, false); - if (task != null) { - return task; - } - // Poll tiny tasks from other tiers - for (var tier : BaseTaskQueueTier.VALUES_EXCEPT_SERVER) { - task = this.pollTaskFromTier(tier, true); - if (task != null) { - return task; - } - } - // We failed to poll any task - return null; - } - // If this is not a server thread, poll from all queues except SERVER - for (var tier : BaseTaskQueueTier.VALUES_EXCEPT_SERVER) { - /* - Make sure that we are allowed to poll from the tier, according to the presence of an excess number of - threads working on tasks from that tier during the last BaseThreadActivation#update call. - In the case this check's result is too optimistic, and a task is started when ideally it wouldn't have been, - then so be it - it is not terrible. Whenever this happens, enough threads will surely be allocated - by the BaseThreadPool for the task tier that is more in demand anyway, so it does not matter much. - In the case this check's result is too pessimistic, the polling fails and this thread will start to sleep, - but before doing this, will make a call to BaseThreadActivation#callForUpdate that re-activated this - thread if necessary, so no harm is done. - In the case this check causes this thread to go to sleep, the call to BaseThreadActivation#callForUpdate - while isWaiting is true will make sure the BaseThreadPool has the ability to correctly activate a - different thread (that is able to start tasks of a higher tier) if needed. - Here, we do not even make an exception for TINY tasks, since there may already be ongoing avoidable - context-switching due to excess threads that we can solve by letting this thread go to sleep. - */ - if (tier.ordinal < BaseThreadActivation.tierInExcessOrdinal) { - /* - Tasks of a certain tier may yield to tasks of the same or a higher - tier, and they may also yield to tiny tasks of a lower tier. - */ - var tierYieldingFrom = this.highestTierOfTaskOnStack; - Runnable task = this.pollTaskFromTier(tier, tierYieldingFrom != null && tier.ordinal > tierYieldingFrom.ordinal); - if (task != null) { - return task; - } - } - } - // We failed to poll any task - return null; - } - - /** - * Starts waiting on something to do. - * - * @param timeoutTime The maximum time to wait until (compared to {@link System#nanoTime}). - * @param yieldingLock A {@link YieldingLock} to register with, or null if this thread is not waiting for - * a yielding lock. - */ - @ThisThreadOnly - @PotentiallyBlocking - private void waitUntilSignalled(@Nullable Long timeoutTime, @Nullable YieldingLock yieldingLock) { - - // Remember whether we registered to wait with the lock, to unregister later - // Register this thread with the lock if necessary - boolean registeredAsWaitingWithLock = false; - if (yieldingLock != null) { - // No point in registering if we're not going to wait anyway - if (!this.skipNextWait) { - yieldingLock.incrementWaitingThreads(); - registeredAsWaitingWithLock = true; - } - } - - /* - Remember whether we changed anything that requires a BaseThreadPool#update call - (after the last call to that method). - */ - boolean mustCallPoolUpdateAtEnd = false; - - /* - If we cannot acquire the lock, we can assume this thread is being signalled, - so there is no reason to start waiting. - */ - waitWithLock: if (this.waitLock.tryLock()) { - try { - - // If it was set that this thread should skip the wait in the meantime, skip it - if (this.skipNextWait) { - break waitWithLock; - } - - // Mark this thread as waiting - this.lockWaitingFor = yieldingLock; - this.mayBeStillWaitingButHasBeenSignalled = false; - this.isWaiting = true; - // But actually we are not waiting yet, signal has no effect yet during the next short lock release - this.isNotActuallyWaitingYet = true; - - } finally { - this.waitLock.unlock(); - } - - // Update the pool - BaseThreadActivation.callForUpdate(); - - /* - If we cannot acquire the lock, we can assume this thread is being signalled, - so there is no reason to start waiting. - */ - if (this.waitLock.tryLock()) { - try { - - // We passed the short lock release - this.isNotActuallyWaitingYet = false; - - // If it was set that this thread should skip the wait in the meantime, skip it - if (this.skipNextWait) { - this.isWaiting = false; - this.lastLockWaitedFor = this.lockWaitingFor; - this.lockWaitingFor = null; - mustCallPoolUpdateAtEnd = true; - break waitWithLock; - } - - // Wait - try { - - // -1 indicates to not use a timeout (this value is not later set to any other negative value) - long waitForNanos = -1; - if (timeoutTime != null) { - waitForNanos = Math.max(timeoutTime - System.nanoTime(), SERVER_THREAD_WAIT_NANOS_MINIMUM); - } else { - /* - Check if we should wait with a tick-based timeout: - this only happens if this thread is the server thread, in - which case we do not want to wait past the start of the next tick. - */ - if (this == MinecraftServerWrapper.serverThread) { - if (MinecraftServer.isWaitingUntilNextTick) { - /* - During waiting until the next tick, we wait until the next tick start. - If it already passed, we do not have to use a timeout, because we will be notified - when the stop condition becomes true. - */ - waitForNanos = MinecraftServer.nextTickStartNanoTime - System.nanoTime(); - if (waitForNanos < 0) { - waitForNanos = -1; - } - } else if (MinecraftServer.SERVER.isOversleep) { - /* - During this phase, MinecraftServer#mayHaveDelayedTasks() is checked, and we may not - be notified when it changes. Therefore, if the next tick start has not passed, we will - wait until then, but if it has, we wait for a short interval to make sure we keep - checking the stop condition (but not for longer than until the last time we can be - executing extra delayed tasks). - */ - waitForNanos = MinecraftServer.nextTickStartNanoTime - System.nanoTime(); - if (waitForNanos < 0) { - waitForNanos = Math.min(Math.max(0, MinecraftServer.delayedTasksMaxNextTickNanoTime - System.nanoTime()), SERVER_THREAD_WAIT_NANOS_DURING_OVERSLEEP_WITH_DELAYED_TASKS); - } - } - } - } - if (waitForNanos >= 0) { - // Set the last signal reason to null in case the timeout elapses without a signal - this.lastSignalReason = null; - // Skip if the time is too short - if (waitForNanos >= SERVER_THREAD_WAIT_NANOS_MINIMUM) { - //noinspection ResultOfMethodCallIgnored - this.waitCondition.await(waitForNanos, TimeUnit.NANOSECONDS); - } - } else { - /* - If we did not wait with a timeout, wait indefinitely. If this thread is the server thread, - and the intended start time of the next tick has already passed, but the stop condition to stop - running tasks is still not true, this thread must be signalled when a change in conditions causes - the stop condition to become true. - */ - this.waitCondition.await(); - } - - } catch (InterruptedException e) { - throw new IllegalStateException(e); - } - - // Unmark this thread as waiting - this.isWaiting = false; - this.lastLockWaitedFor = this.lockWaitingFor; - this.lockWaitingFor = null; - mustCallPoolUpdateAtEnd = true; - - } finally { - this.waitLock.unlock(); - } - } - - } - - // Unregister this thread from the lock if necessary - if (registeredAsWaitingWithLock) { - yieldingLock.decrementWaitingThreads(); - } - - // Reset skipping the next wait - this.skipNextWait = false; - - // Update the pool if necessary - if (mustCallPoolUpdateAtEnd) { - BaseThreadActivation.callForUpdate(); - } - - } - - /** - * An auxiliary method for exclusive use in {@link #signal}, that marks the {@link YieldingLock} - * that this thread is waiting for as having been signalled for, so that no other threads - * are also signalled for it. - *
- * This must be called when {@link #signal} returns true, and must be called before any other - * actions relating to the signalling of this thread are performed. - */ - private void markLockWaitingForAsSignalledFor() { - @Nullable YieldingLock lockWaitingFor = this.lockWaitingFor; - if (lockWaitingFor != null) { - lockWaitingFor.canBeSignalledFor = false; - } - } - - /** - * Signals this thread to wake up, or if it was not sleeping but attempting to poll a task: - * to not go to sleep the next time no task could be polled, and instead try polling a task again. - * - * @param reason The reason why this thread was signalled, or null if it is irrelevant (e.g. when the signal - * will never need to be repeated because there is only thread waiting for this specific event - * to happen). - * @return Whether this thread was sleeping before, and has woken up now, - * or whether {@link #skipNextWait} was set to true. - */ - @AnyThreadSafe - @YieldFree - public final boolean signal(@Nullable SignalReason reason) { - while (!this.waitLock.tryLock()) { // TODO Gale use a wait-free system here by using a sort of leave-a-message-at-the-door Atomic class system - Thread.onSpinWait(); - } - try { - if (this.isWaiting) { - if (this.isNotActuallyWaitingYet) { - if (!this.skipNextWait) { - this.markLockWaitingForAsSignalledFor(); - this.lastSignalReason = reason; - this.skipNextWait = true; - return true; - } - return false; - } - if (!this.mayBeStillWaitingButHasBeenSignalled) { - this.markLockWaitingForAsSignalledFor(); - this.lastSignalReason = reason; - this.mayBeStillWaitingButHasBeenSignalled = true; - this.waitCondition.signal(); - return true; - } - } else if (this.isPollingTaskOrCheckingStopCondition) { - if (!this.skipNextWait) { - this.markLockWaitingForAsSignalledFor(); - this.lastSignalReason = reason; - this.skipNextWait = true; - return true; - } - } - return false; - } finally { - this.waitLock.unlock(); - } - } - - /** - * @return The current thread if it is a {@link BaseThread}, or null otherwise. - */ - @SuppressWarnings("unused") - @AnyThreadSafe - @YieldFree - public static @Nullable BaseThread currentBaseThread() { - return Thread.currentThread() instanceof BaseThread baseThread ? baseThread : null; - } - - /** - * @return Whether the current thread is a {@link BaseThread}. - */ - @SuppressWarnings("unused") - @AnyThreadSafe - @YieldFree - public static boolean isBaseThread() { - return Thread.currentThread() instanceof BaseThread; - } - -} +//import net.gensokyoreimagined.nitori.executor.wrapper.MinecraftServerWrapper; +// +///** +// * An abstract base class implementing {@link AbstractYieldingThread}, +// * that provides implementation that is common between +// * {@link TickThread} and {@link AssistThread}. +// * +// * @author Martijn Muijsers under AGPL-3.0 +// */ +//public abstract class BaseThread extends Thread implements AbstractYieldingThread { +// +// /** +// * The minimum time to wait as the {@link MinecraftServer#serverThread} when performing a timed wait. +// * Given in nanoseconds. +// * If a timed wait with a lower time is attempted, the wait is not performed at all. +// */ +// public static final long SERVER_THREAD_WAIT_NANOS_MINIMUM = 10_000; +// +// /** +// * The time to wait as the {@link MinecraftServer#serverThread} during the oversleep phase, if +// * there may be delayed tasks. +// * Given in nanoseconds. +// */ +// public static final long SERVER_THREAD_WAIT_NANOS_DURING_OVERSLEEP_WITH_DELAYED_TASKS = 50_000; +// +// /** +// * The index of this thread, as needed as an argument to +// * {@link BaseThreadPool#getThreadByBaseIndex(int)}. +// */ +// public final int baseThreadIndex; +// +// /** +// * The maximum yield depth for this thread, +// * which equals 1 for a {@link ServerThread} +// * and {@link AssistThread#MAXIMUM_YIELD_DEPTH} for an {@link AssistThread}. +// */ +// public final int maximumYieldDepth; +// +// /** +// * The number of times this thread holds a {@link YieldingLock}, +// * used in {@link #holdsYieldingLock()}. +// * +// * @see AbstractYieldingThread#incrementHeldYieldingLockCount() +// */ +// @ThisThreadOnly +// public int heldYieldingLockCount = 0; +// +// /** +// * The current yield depth of this thread. +// */ +// @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) +// public volatile int yieldDepth = 0; +// +// /** +// * Whether this thread can currently start yielding tasks with respect to being restricted +// * due to {@link #yieldDepth} being at least {@link #maximumYieldDepth}. +// *
+// * This is updated using {@link #updateCanStartYieldingTasks()} +// * after {@link #yieldDepth} or {@link #heldYieldingLockCount} is changed. +// */ +// @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) +// public volatile boolean canStartYieldingTasks = true; +// +// /** +// * The highest {@link BaseTaskQueueTier} of any task on the yielding execution stack of this thread, +// * or null if there is no task being executed on this thread. +// */ +// @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) +// public volatile @Nullable BaseTaskQueueTier highestTierOfTaskOnStack; +// +// /** +// * The {@link BaseTaskQueueTier} that the last non-null return value of {@link #pollTask} was polled from, +// * or null if {@link #pollTask} has never been called yet. +// */ +// @ThisThreadOnly +// private @Nullable BaseTaskQueueTier lastPolledTaskTier; +// +// /** +// * The lock to guard this thread's sleeping and waking actions. +// */ +// private final Lock waitLock = new ReentrantLock(); +// +// /** +// * The condition to wait for a signal, when this thread has to wait for something to do. +// */ +// private final Condition waitCondition = waitLock.newCondition(); +// +// /** +// * Whether this thread is currently not working on the content of a task, but instead +// * attempting to poll a next task to do, checking whether it can accept tasks at all, or +// * attempting to acquire a {@link YieldingLock}, or waiting (although the fact that this value is true during +// * waiting is irrelevant, because at such a time, {@link #isWaiting} will be true, and this value will no longer +// * have any effect due to the implementation of {@link #signal}). +// *
+// * This value is used to determine whether to set {@link #skipNextWait} when {@link #signal} is called +// * and {@link #isWaiting} is false. +// */ +// @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) +// private volatile boolean isPollingTaskOrCheckingStopCondition = true; +// +// /** +// * Whether this thread should not start waiting for something to do the next time no task could be polled, +// * but instead try polling a task again. +// */ +// @AnyThreadSafe +// public volatile boolean skipNextWait = false; +// +// /** +// * Whether this thread is currently waiting for something to do. +// *
+// * This is set to true at some point before actually starting to wait in a blocking fashion, +// * and set to false at some point after no longer waiting in a blocking fashion. So, at some point, +// * this value may be true while the thread is not blocked yet, or anymore. +// * Even more so, extra checks for whether the thread should block will be performed in between +// * the moment this value is set to true and the moment the thread potentially blocks. This means that if the +// * checks fail, this value may be set to true and then false again, without actually ever blocking. +// */ +// @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) +// @Guarded(value = "#waitLock", fieldAccess = Access.WRITE) +// public volatile boolean isWaiting = false; +// +// /** +// * Whether {@link #isWaiting} is irrelevant because this thread has already +// * been signalled via {@link #signal} to wake up. +// */ +// @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) +// @Guarded(value = "#waitLock", fieldAccess = Access.WRITE) +// public volatile boolean mayBeStillWaitingButHasBeenSignalled = false; +// +// /** +// * The {@link YieldingLock} that this thread is waiting for, +// * or null if this thread is not waiting for a {@link YieldingLock}. +// * This value only has meaning while {@link #isWaiting} is true. +// */ +// @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) +// @Guarded(value = "#waitLock", fieldAccess = Access.WRITE) +// public volatile @Nullable YieldingLock lockWaitingFor = null; +// +// /** +// * The value of {@link #lockWaitingFor} during the last wait (a call to {@link Condition#await}) +// * or pre-wait check (while {@link #isNotActuallyWaitingYet} is true). +// */ +// @ThisThreadOnly +// private @Nullable YieldingLock lastLockWaitedFor = null; +// +// /** +// * A special flag, used after changing {@link #isWaiting}, when the lock must be temporarily released to +// * call {@link BaseThreadActivation#callForUpdate()} (to avoid deadlocks in {@link #signal} calls), +// * and we wish the pool to regard this thread as waiting +// * (which it will, because {@link #isWaiting} will be true), but we must still +// * know not to signal the underlying {@link #waitCondition}, but set {@link #skipNextWait} to true, +// * when {@link #signal} is called at some point during the short release of {@link #waitLock}. +// */ +// public volatile boolean isNotActuallyWaitingYet = false; +// +// /** +// * The last reason this thread was signalled before the current poll attempt, or null if the current +// * poll attempt was not preceded by signalling (but by yielding for example). +// */ +// public volatile @Nullable SignalReason lastSignalReason = null; +// +// protected BaseThread(Runnable target, String name, int baseThreadIndex, int maximumYieldDepth) { +// super(target, name); +// this.baseThreadIndex = baseThreadIndex; +// this.maximumYieldDepth = maximumYieldDepth; +// } +// +// @Override +// public boolean holdsYieldingLock() { +// return this.heldYieldingLockCount > 0; +// } +// +// @Override +// public void incrementHeldYieldingLockCount() { +// this.heldYieldingLockCount++; +// if (this.heldYieldingLockCount == 1) { +// this.updateCanStartYieldingTasks(); +// } +// } +// +// @Override +// public void decrementHeldYieldingLockCount() { +// this.heldYieldingLockCount--; +// if (this.heldYieldingLockCount == 0) { +// this.updateCanStartYieldingTasks(); +// } +// } +// +// /** +// * Updates {@link #canStartYieldingTasks} according to {@link #yieldDepth} and {@link #heldYieldingLockCount}. +// */ +// private void updateCanStartYieldingTasks() { +// this.canStartYieldingTasks = this.heldYieldingLockCount == 0 && this.yieldDepth < this.maximumYieldDepth; +// } +// +// /** +// * This method is based on {@link #signal}. +// * {@link #signal} must always return true if this method returns true; +// * otherwise {@link BaseThreadActivation} will get stuck while choosing a thread to activate. +// * +// * @see #signal +// */ +// @SuppressWarnings("RedundantIfStatement") +// public boolean isWaitingAndNeedsSignal() { +// if (this.isWaiting) { +// if (this.isNotActuallyWaitingYet) { +// if (!this.skipNextWait) { +// return true; +// } +// return false; +// } +// if (!this.mayBeStillWaitingButHasBeenSignalled) { +// return true; +// } +// } +// return false; +// } +// +// /** +// * Yields to tasks: polls and executes tasks while possible and the stop condition is not met. +// * The stop condition is met if {@code stopCondition} is not null and returns true, or alternatively, +// * if {@code stopCondition} is null, and {@code yieldingLock} is successfully acquired. +// * When no tasks can be polled, this thread will block, waiting for either a task that can be executed by this +// * thread to become available, or for the {@code yieldingLock}, if given, to be released. +// *
+// * Exactly one of {@code stopCondition} and {@code yieldingLock} must be non-null. +// */ +// public final void yieldUntil(@Nullable Long timeoutTime, @Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock) { +// int oldYieldDepth = this.yieldDepth; +// int newYieldDepth = oldYieldDepth + 1; +// this.yieldDepth = newYieldDepth; +// if (newYieldDepth == maximumYieldDepth) { +// this.updateCanStartYieldingTasks(); +// } +// this.runTasksUntil(timeoutTime, stopCondition, yieldingLock); +// this.yieldDepth = oldYieldDepth; +// if (newYieldDepth == maximumYieldDepth) { +// this.updateCanStartYieldingTasks(); +// } +// } +// +// /** +// * This method will keep attempting to find a task to do, and execute it, and if none is found, start waiting +// * until the {@code timeoutTime} is reached (which is compared to {@link System#nanoTime}), +// * or the thread is signalled by {@link BaseThreadPool} or by a {@link YieldingLock}. +// * The loop is broken as soon as the stop condition becomes true, or the given lock is successfully acquired. +// *
+// * The above is the same as {@link #yieldUntil}, except it may be called in situations that is not 'yielding', +// * for instance the endless loop polling tasks performed by a n{@link AssistThread}. The difference with +// * {@link #yieldUntil} is that this method does not increment or decrement things the yield depth of this thread. +// *
+// * Exactly one of {@code stopCondition} or {@code yieldingLock} must be non-null. +// * +// * @see #yieldUntil +// */ +// @ThisThreadOnly +// @PotentiallyYielding("may yield further if an executed task is potentially yielding") +// public final void runTasksUntil(@Nullable Long timeoutTime, @Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock) { +// if (TickThread.isTickThread()) MinecraftServer.THREAD_DEBUG_LOGGER.ifPresent(it -> it.info("running tasks until")); +// this.isPollingTaskOrCheckingStopCondition = true; +// +// /* +// Endless loop that attempts to perform a task, and if one is found, tries to perform another again, +// but if none is found, starts awaiting such a task to become available, or for the given yielding lock +// to be released. +// */ +// while (true) { +// try { +// if (timeoutTime != null && System.nanoTime() - timeoutTime >= 0) { +// break; +// } +// if (stopCondition != null) { +// if (this == MinecraftServerWrapper.serverThread) { +// MinecraftServer.currentManagedBlockStopConditionHasBecomeTrue = false; +// } +// if (stopCondition.getAsBoolean()) { +// if (this == MinecraftServerWrapper.serverThread) { +// MinecraftServer.currentManagedBlockStopConditionHasBecomeTrue = true; +// } +// break; +// } +// } else { +// //noinspection ConstantConditions +// if (yieldingLock.tryLock()) { +// break; +// } +// } +// } finally { +// // Make sure other threads can be signalled for the last waited-for lock again +// if (this.lastLockWaitedFor != null) { +// this.lastLockWaitedFor.canBeSignalledFor = true; +// this.lastLockWaitedFor = null; +// } +// } +// +// // If this is the original server thread, update isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking +// if (this == MinecraftServerWrapper.serverThread) { +// MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking = MinecraftServer.isInSpareTime && MinecraftServer.blockingCount == 0 && !MinecraftServer.SERVER.haveTime(); +// } +// +// // Attempt to poll a task that can be started +// Runnable task = this.pollTask(); +// +// // Run the task if found +// if (task != null) { +// +// // If this is the server thread, potentially set nextTimeAssumeWeMayHaveDelayedTasks to true +// if (this == MinecraftServerWrapper.serverThread && !MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks && AbstractTaskQueue.taskQueuesHaveTasks(BaseTaskQueueTier.SERVER.taskQueues)) { +// MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks = true; +// } +// +// // Update highestTierOfTaskOnStack and the thread priority +// var highestTierBeforeTask = this.highestTierOfTaskOnStack; +// var threadPriorityBeforeTask = this.getPriority(); +// //noinspection DataFlowIssue +// var newHighestTier = highestTierBeforeTask == null ? this.lastPolledTaskTier : highestTierBeforeTask.ordinal < this.lastPolledTaskTier.ordinal ? highestTierBeforeTask : this.lastPolledTaskTier; +// //noinspection DataFlowIssue +// var newThreadPriority = newHighestTier.threadPriority; +// if (newHighestTier != highestTierBeforeTask) { +// this.highestTierOfTaskOnStack = newHighestTier; +// BaseThreadActivation.callForUpdate(); +// if (threadPriorityBeforeTask != newThreadPriority) { +// this.setPriority(newThreadPriority); +// } +// } +// +// this.isPollingTaskOrCheckingStopCondition = false; +// task.run(); +// +// // If this is the server thread, execute some chunk tasks +// if (this == MinecraftServerWrapper.serverThread) { +// if (newHighestTier != BaseTaskQueueTier.SERVER) { +// newHighestTier = BaseTaskQueueTier.SERVER; +// this.highestTierOfTaskOnStack = newHighestTier; +// BaseThreadActivation.callForUpdate(); +// if (newThreadPriority != newHighestTier.threadPriority) { +// newThreadPriority = newHighestTier.threadPriority; +// this.setPriority(newThreadPriority); +// } +// } +// MinecraftServer.SERVER.executeMidTickTasks(); // Paper - execute chunk tasks mid tick +// } +// +// // Reset highestTierOfTaskOnStack and the thread priority +// if (newHighestTier != highestTierBeforeTask) { +// this.highestTierOfTaskOnStack = highestTierBeforeTask; +// BaseThreadActivation.callForUpdate(); +// if (threadPriorityBeforeTask != newThreadPriority) { +// this.setPriority(threadPriorityBeforeTask); +// } +// } +// +// this.isPollingTaskOrCheckingStopCondition = true; +// continue; +// +// } +// +// /* +// If no task that can be started by this thread was found, wait for a task that we are allowed +// to poll to become available (when that happens, the BaseThreadPool will signal this thread), +// or for the given yielding lock to be released. This is the only time we should ever block inside +// a potentially yielding procedure. +// */ +// this.waitUntilSignalled(timeoutTime, yieldingLock); +// +// } +// +// this.isPollingTaskOrCheckingStopCondition = false; +// +// /* +// If the thread was signalled for another reason than the lock, but we acquired the lock instead, +// another thread should be signalled for that reason. +// */ +// SignalReason lastSignalReason = this.lastSignalReason; +// if (lastSignalReason != null && yieldingLock != null && lastSignalReason != SignalReason.YIELDING_LOCK) { +// BaseThreadActivation.callForUpdate(); +// } +// +// } +// +// /** +// * @see #pollTask() +// */ +// @ThisThreadOnly +// @YieldFree +// private @Nullable Runnable pollTaskFromTier(BaseTaskQueueTier tier, boolean tinyOnly) { +// for (var queue : tier.taskQueues) { +// // Check whether we can not yield to the queue, if we are yielding +// boolean canQueueBeYieldedTo = queue.canBeYieldedTo(); +// if (!canQueueBeYieldedTo && this.yieldDepth > 0) { +// continue; +// } +// Runnable task = tinyOnly ? queue.pollTiny(this) : queue.poll(this); +// if (task != null) { +// this.lastPolledTaskTier = tier; +// return task; +// } +// /* +// Check if the tier has run out of tasks for a span, +// in order to update BaseThreadActivation#thereMayBeTasks. +// */ +// for (int spanI = 0; spanI < TaskSpan.length; spanI++) { +// TaskSpan span = TaskSpan.VALUES[spanI]; +// if (queue.canHaveTasks(span)) { +// int oldTasks = BaseThreadActivation.thereMayBeTasks[tier.ordinal][spanI][canQueueBeYieldedTo ? 1 : 0].get(); +// if (oldTasks > 0) { +// if (!queue.hasTasks(span)) { +// boolean tierHasNoTasksForSpan = true; +// for (AbstractTaskQueue otherTierQueue : tier.taskQueues) { +// // We already know there are no tasks in this queue +// if (otherTierQueue == queue) { +// continue; +// } +// if (otherTierQueue.hasTasks(span)) { +// tierHasNoTasksForSpan = false; +// break; +// } +// } +// if (tierHasNoTasksForSpan) { +// // Set thereMayBeTasks to false, but only if it did not change in the meantime +// BaseThreadActivation.thereMayBeTasks[tier.ordinal][spanI][canQueueBeYieldedTo ? 1 : 0].compareAndSet(oldTasks, 0); +// } +// } +// } +// } +// } +// } +// return null; +// } +// +// /** +// * Polls a task from any queue this thread can currently poll from, and returns it. +// * Polling potentially yielding tasks is attempted before yield-free tasks. +// * +// * @return The task that was polled, or null if no task was found. +// */ +// @ThisThreadOnly +// @YieldFree +// private @Nullable Runnable pollTask() { +// /* +// * If this is a server thread, poll from SERVER, and poll tiny tasks from other tiers. +// * Note that when polling on the ServerThread, we do not check whether we would be allowed to do so +// * by the BaseThreadPool, as we consider keeping the ServerThread in the Thread.State.RUNNABLE state for +// * as long as possible to be more important than the off-chance of for example starting a TINY ASYNC task +// * on the server thread while no ASYNC tasks are allowed to be polled by other threads at the moment. +// */ +// if (this instanceof ServerThread) { +// // Poll from the SERVER queues +// Runnable task = this.pollTaskFromTier(BaseTaskQueueTier.SERVER, false); +// if (task != null) { +// return task; +// } +// // Poll tiny tasks from other tiers +// for (var tier : BaseTaskQueueTier.VALUES_EXCEPT_SERVER) { +// task = this.pollTaskFromTier(tier, true); +// if (task != null) { +// return task; +// } +// } +// // We failed to poll any task +// return null; +// } +// // If this is not a server thread, poll from all queues except SERVER +// for (var tier : BaseTaskQueueTier.VALUES_EXCEPT_SERVER) { +// /* +// Make sure that we are allowed to poll from the tier, according to the presence of an excess number of +// threads working on tasks from that tier during the last BaseThreadActivation#update call. +// In the case this check's result is too optimistic, and a task is started when ideally it wouldn't have been, +// then so be it - it is not terrible. Whenever this happens, enough threads will surely be allocated +// by the BaseThreadPool for the task tier that is more in demand anyway, so it does not matter much. +// In the case this check's result is too pessimistic, the polling fails and this thread will start to sleep, +// but before doing this, will make a call to BaseThreadActivation#callForUpdate that re-activated this +// thread if necessary, so no harm is done. +// In the case this check causes this thread to go to sleep, the call to BaseThreadActivation#callForUpdate +// while isWaiting is true will make sure the BaseThreadPool has the ability to correctly activate a +// different thread (that is able to start tasks of a higher tier) if needed. +// Here, we do not even make an exception for TINY tasks, since there may already be ongoing avoidable +// context-switching due to excess threads that we can solve by letting this thread go to sleep. +// */ +// if (tier.ordinal < BaseThreadActivation.tierInExcessOrdinal) { +// /* +// Tasks of a certain tier may yield to tasks of the same or a higher +// tier, and they may also yield to tiny tasks of a lower tier. +// */ +// var tierYieldingFrom = this.highestTierOfTaskOnStack; +// Runnable task = this.pollTaskFromTier(tier, tierYieldingFrom != null && tier.ordinal > tierYieldingFrom.ordinal); +// if (task != null) { +// return task; +// } +// } +// } +// // We failed to poll any task +// return null; +// } +// +// /** +// * Starts waiting on something to do. +// * +// * @param timeoutTime The maximum time to wait until (compared to {@link System#nanoTime}). +// * @param yieldingLock A {@link YieldingLock} to register with, or null if this thread is not waiting for +// * a yielding lock. +// */ +// @ThisThreadOnly +// @PotentiallyBlocking +// private void waitUntilSignalled(@Nullable Long timeoutTime, @Nullable YieldingLock yieldingLock) { +// +// // Remember whether we registered to wait with the lock, to unregister later +// // Register this thread with the lock if necessary +// boolean registeredAsWaitingWithLock = false; +// if (yieldingLock != null) { +// // No point in registering if we're not going to wait anyway +// if (!this.skipNextWait) { +// yieldingLock.incrementWaitingThreads(); +// registeredAsWaitingWithLock = true; +// } +// } +// +// /* +// Remember whether we changed anything that requires a BaseThreadPool#update call +// (after the last call to that method). +// */ +// boolean mustCallPoolUpdateAtEnd = false; +// +// /* +// If we cannot acquire the lock, we can assume this thread is being signalled, +// so there is no reason to start waiting. +// */ +// waitWithLock: if (this.waitLock.tryLock()) { +// try { +// +// // If it was set that this thread should skip the wait in the meantime, skip it +// if (this.skipNextWait) { +// break waitWithLock; +// } +// +// // Mark this thread as waiting +// this.lockWaitingFor = yieldingLock; +// this.mayBeStillWaitingButHasBeenSignalled = false; +// this.isWaiting = true; +// // But actually we are not waiting yet, signal has no effect yet during the next short lock release +// this.isNotActuallyWaitingYet = true; +// +// } finally { +// this.waitLock.unlock(); +// } +// +// // Update the pool +// BaseThreadActivation.callForUpdate(); +// +// /* +// If we cannot acquire the lock, we can assume this thread is being signalled, +// so there is no reason to start waiting. +// */ +// if (this.waitLock.tryLock()) { +// try { +// +// // We passed the short lock release +// this.isNotActuallyWaitingYet = false; +// +// // If it was set that this thread should skip the wait in the meantime, skip it +// if (this.skipNextWait) { +// this.isWaiting = false; +// this.lastLockWaitedFor = this.lockWaitingFor; +// this.lockWaitingFor = null; +// mustCallPoolUpdateAtEnd = true; +// break waitWithLock; +// } +// +// // Wait +// try { +// +// // -1 indicates to not use a timeout (this value is not later set to any other negative value) +// long waitForNanos = -1; +// if (timeoutTime != null) { +// waitForNanos = Math.max(timeoutTime - System.nanoTime(), SERVER_THREAD_WAIT_NANOS_MINIMUM); +// } else { +// /* +// Check if we should wait with a tick-based timeout: +// this only happens if this thread is the server thread, in +// which case we do not want to wait past the start of the next tick. +// */ +// if (this == MinecraftServerWrapper.serverThread) { +// if (MinecraftServer.isWaitingUntilNextTick) { +// /* +// During waiting until the next tick, we wait until the next tick start. +// If it already passed, we do not have to use a timeout, because we will be notified +// when the stop condition becomes true. +// */ +// waitForNanos = MinecraftServer.nextTickStartNanoTime - System.nanoTime(); +// if (waitForNanos < 0) { +// waitForNanos = -1; +// } +// } else if (MinecraftServer.SERVER.isOversleep) { +// /* +// During this phase, MinecraftServer#mayHaveDelayedTasks() is checked, and we may not +// be notified when it changes. Therefore, if the next tick start has not passed, we will +// wait until then, but if it has, we wait for a short interval to make sure we keep +// checking the stop condition (but not for longer than until the last time we can be +// executing extra delayed tasks). +// */ +// waitForNanos = MinecraftServer.nextTickStartNanoTime - System.nanoTime(); +// if (waitForNanos < 0) { +// waitForNanos = Math.min(Math.max(0, MinecraftServer.delayedTasksMaxNextTickNanoTime - System.nanoTime()), SERVER_THREAD_WAIT_NANOS_DURING_OVERSLEEP_WITH_DELAYED_TASKS); +// } +// } +// } +// } +// if (waitForNanos >= 0) { +// // Set the last signal reason to null in case the timeout elapses without a signal +// this.lastSignalReason = null; +// // Skip if the time is too short +// if (waitForNanos >= SERVER_THREAD_WAIT_NANOS_MINIMUM) { +// //noinspection ResultOfMethodCallIgnored +// this.waitCondition.await(waitForNanos, TimeUnit.NANOSECONDS); +// } +// } else { +// /* +// If we did not wait with a timeout, wait indefinitely. If this thread is the server thread, +// and the intended start time of the next tick has already passed, but the stop condition to stop +// running tasks is still not true, this thread must be signalled when a change in conditions causes +// the stop condition to become true. +// */ +// this.waitCondition.await(); +// } +// +// } catch (InterruptedException e) { +// throw new IllegalStateException(e); +// } +// +// // Unmark this thread as waiting +// this.isWaiting = false; +// this.lastLockWaitedFor = this.lockWaitingFor; +// this.lockWaitingFor = null; +// mustCallPoolUpdateAtEnd = true; +// +// } finally { +// this.waitLock.unlock(); +// } +// } +// +// } +// +// // Unregister this thread from the lock if necessary +// if (registeredAsWaitingWithLock) { +// yieldingLock.decrementWaitingThreads(); +// } +// +// // Reset skipping the next wait +// this.skipNextWait = false; +// +// // Update the pool if necessary +// if (mustCallPoolUpdateAtEnd) { +// BaseThreadActivation.callForUpdate(); +// } +// +// } +// +// /** +// * An auxiliary method for exclusive use in {@link #signal}, that marks the {@link YieldingLock} +// * that this thread is waiting for as having been signalled for, so that no other threads +// * are also signalled for it. +// *
+// * This must be called when {@link #signal} returns true, and must be called before any other +// * actions relating to the signalling of this thread are performed. +// */ +// private void markLockWaitingForAsSignalledFor() { +// @Nullable YieldingLock lockWaitingFor = this.lockWaitingFor; +// if (lockWaitingFor != null) { +// lockWaitingFor.canBeSignalledFor = false; +// } +// } +// +// /** +// * Signals this thread to wake up, or if it was not sleeping but attempting to poll a task: +// * to not go to sleep the next time no task could be polled, and instead try polling a task again. +// * +// * @param reason The reason why this thread was signalled, or null if it is irrelevant (e.g. when the signal +// * will never need to be repeated because there is only thread waiting for this specific event +// * to happen). +// * @return Whether this thread was sleeping before, and has woken up now, +// * or whether {@link #skipNextWait} was set to true. +// */ +// @AnyThreadSafe +// @YieldFree +// public final boolean signal(@Nullable SignalReason reason) { +// while (!this.waitLock.tryLock()) { // TODO Gale use a wait-free system here by using a sort of leave-a-message-at-the-door Atomic class system +// Thread.onSpinWait(); +// } +// try { +// if (this.isWaiting) { +// if (this.isNotActuallyWaitingYet) { +// if (!this.skipNextWait) { +// this.markLockWaitingForAsSignalledFor(); +// this.lastSignalReason = reason; +// this.skipNextWait = true; +// return true; +// } +// return false; +// } +// if (!this.mayBeStillWaitingButHasBeenSignalled) { +// this.markLockWaitingForAsSignalledFor(); +// this.lastSignalReason = reason; +// this.mayBeStillWaitingButHasBeenSignalled = true; +// this.waitCondition.signal(); +// return true; +// } +// } else if (this.isPollingTaskOrCheckingStopCondition) { +// if (!this.skipNextWait) { +// this.markLockWaitingForAsSignalledFor(); +// this.lastSignalReason = reason; +// this.skipNextWait = true; +// return true; +// } +// } +// return false; +// } finally { +// this.waitLock.unlock(); +// } +// } +// +// /** +// * @return The current thread if it is a {@link BaseThread}, or null otherwise. +// */ +// @SuppressWarnings("unused") +// @AnyThreadSafe +// @YieldFree +// public static @Nullable BaseThread currentBaseThread() { +// return Thread.currentThread() instanceof BaseThread baseThread ? baseThread : null; +// } +// +// /** +// * @return Whether the current thread is a {@link BaseThread}. +// */ +// @SuppressWarnings("unused") +// @AnyThreadSafe +// @YieldFree +// public static boolean isBaseThread() { +// return Thread.currentThread() instanceof BaseThread; +// } +// +//} diff --git a/src/main/java/net/gensokyoreimagined/nitori/executor/thread/OriginalServerThread.java b/src/main/java/net/gensokyoreimagined/nitori/executor/thread/OriginalServerThread.java index 474d108..3296380 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/executor/thread/OriginalServerThread.java +++ b/src/main/java/net/gensokyoreimagined/nitori/executor/thread/OriginalServerThread.java @@ -1,18 +1,18 @@ package net.gensokyoreimagined.nitori.executor.thread; -import net.minecraft.server.MinecraftServer; -import org.spigotmc.WatchdogThread; - -/** - * A type that is unique to {@link MinecraftServer#serverThread}, - * to distinguish it from {@link WatchdogThread#instance}. - * - * @author Martijn Muijsers under AGPL-3.0 - */ -public final class OriginalServerThread extends ServerThread { - - public OriginalServerThread(final Runnable run, final String name) { - super(run, name); - } - -} +//import net.minecraft.server.MinecraftServer; +//import org.spigotmc.WatchdogThread; +// +///** +// * A type that is unique to {@link MinecraftServer#serverThread}, +// * to distinguish it from {@link WatchdogThread#instance}. +// * +// * @author Martijn Muijsers under AGPL-3.0 +// */ +//public final class OriginalServerThread extends ServerThread { +// +// public OriginalServerThread(final Runnable run, final String name) { +// super(run, name); +// } +// +//} diff --git a/src/main/java/net/gensokyoreimagined/nitori/executor/thread/ServerThread.java b/src/main/java/net/gensokyoreimagined/nitori/executor/thread/ServerThread.java index 62e35c4..afeb8ad 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/executor/thread/ServerThread.java +++ b/src/main/java/net/gensokyoreimagined/nitori/executor/thread/ServerThread.java @@ -1,54 +1,54 @@ package net.gensokyoreimagined.nitori.executor.thread; -import io.papermc.paper.util.TickThread; -import net.gensokyoreimagined.nitori.executor.wrapper.MinecraftServerWrapper; -import net.gensokyoreimagined.nitori.mixin.virtual_thread.accessors.MixinMinecraftServer; -import net.gensokyoreimagined.nitori.mixin.virtual_thread.accessors.MixinWatchdogThread; -import net.minecraft.server.MinecraftServer; -import org.gradle.internal.impldep.com.esotericsoftware.kryo.NotNull; -import org.spigotmc.WatchdogThread; - -import javax.annotation.Nullable; - -/** - * A {@link TickThread} that provides an implementation for {@link BaseThread}, - * that is shared between the {@link MinecraftServer#serverThread} and {@link WatchdogThread#instance}. - * - * @author Martijn Muijsers under AGPL-3.0 - */ -public class ServerThread extends TickThread { - - protected ServerThread(final String name) { - super(name); - } - - protected ServerThread(final Runnable run, final String name) { - super(run, name); - } - - /** - * This method must not be called while {@link MinecraftServer#isConstructed} is false. - * - * @return The global {@link ServerThread} instance, which is either - * {@link MinecraftServer#serverThread}, or {@link WatchdogThread#instance} while the server is shutting - * down and the {@link WatchdogThread} was responsible. - */ - public static @NotNull Thread getInstance() { - if (((MixinMinecraftServer) MinecraftServerWrapper.SERVER).hasStopped()) { - if (((MixinMinecraftServer) MinecraftServerWrapper.SERVER).shutdownThread() == MixinWatchdogThread.getInstance()) { - return MixinWatchdogThread.getInstance(); - } - } - - return MinecraftServerWrapper.serverThread; - } - - /** - * @return The same value as {@link #getInstance()} if {@link MinecraftServer#isConstructed} is true, - * or null otherwise. - */ - public static @Nullable Thread getInstanceIfConstructed() { - return MinecraftServerWrapper.isConstructed ? getInstance() : null; - } - -} +//import io.papermc.paper.util.TickThread; +//import net.gensokyoreimagined.nitori.executor.wrapper.MinecraftServerWrapper; +//import net.gensokyoreimagined.nitori.mixin.virtual_thread.accessors.MixinMinecraftServer; +//import net.gensokyoreimagined.nitori.mixin.virtual_thread.accessors.MixinWatchdogThread; +//import net.minecraft.server.MinecraftServer; +//import org.gradle.internal.impldep.com.esotericsoftware.kryo.NotNull; +//import org.spigotmc.WatchdogThread; +// +//import javax.annotation.Nullable; +// +///** +// * A {@link TickThread} that provides an implementation for {@link BaseThread}, +// * that is shared between the {@link MinecraftServer#serverThread} and {@link WatchdogThread#instance}. +// * +// * @author Martijn Muijsers under AGPL-3.0 +// */ +//public class ServerThread extends TickThread { +// +// protected ServerThread(final String name) { +// super(name); +// } +// +// protected ServerThread(final Runnable run, final String name) { +// super(run, name); +// } +// +// /** +// * This method must not be called while {@link MinecraftServer#isConstructed} is false. +// * +// * @return The global {@link ServerThread} instance, which is either +// * {@link MinecraftServer#serverThread}, or {@link WatchdogThread#instance} while the server is shutting +// * down and the {@link WatchdogThread} was responsible. +// */ +// public static @NotNull Thread getInstance() { +// if (((MixinMinecraftServer) MinecraftServerWrapper.SERVER).hasStopped()) { +// if (((MixinMinecraftServer) MinecraftServerWrapper.SERVER).shutdownThread() == MixinWatchdogThread.getInstance()) { +// return MixinWatchdogThread.getInstance(); +// } +// } +// +// return MinecraftServerWrapper.serverThread; +// } +// +// /** +// * @return The same value as {@link #getInstance()} if {@link MinecraftServer#isConstructed} is true, +// * or null otherwise. +// */ +// public static @Nullable Thread getInstanceIfConstructed() { +// return MinecraftServerWrapper.isConstructed ? getInstance() : null; +// } +// +//} diff --git a/src/main/java/net/gensokyoreimagined/nitori/executor/thread/pool/BaseThreadPool.java b/src/main/java/net/gensokyoreimagined/nitori/executor/thread/pool/BaseThreadPool.java index 802260c..1c2f7f3 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/executor/thread/pool/BaseThreadPool.java +++ b/src/main/java/net/gensokyoreimagined/nitori/executor/thread/pool/BaseThreadPool.java @@ -2,207 +2,207 @@ package net.gensokyoreimagined.nitori.executor.thread.pool; -/** - * A pool of threads that can perform tasks to assist the current {@link ServerThread}. These tasks can be of - * different {@linkplain BaseTaskQueueTier tiers}. - *
- * This pool intends to keep {@link #targetParallelism} threads active at any time, - * which includes a potentially active {@link ServerThread}. - *
- * As such, this pool is closely intertwined with the {@link ServerThread}. This pool can not control the - * {@link ServerThread} in any way, but it is responsible for signalling the {@link ServerThread} when tasks become - * available in a {@link BaseTaskQueueTier#SERVER} task queue, and for listening for when the {@link ServerThread} - * becomes (in)active in order to update the number of active {@link AssistThread}s accordingly. - *

- * Updates to the threads in this pool are done in a lock-free manner that attempts to do the right thing with - * the volatile information that is available. In some cases, this may cause a thread to be woken up when it - * should not have been, and so on, but the updates being lock-free is more significant than the updates being - * optimal in a high-contention environment. The environment is not expected to have high enough contention for - * this to have much of an impact. Additionally, the suboptimalities in updates are always optimistic in terms of - * making/keeping threads active rather than inactive, and can not a situation where a thread was intended - * to be active, but ends but not being active. - * - * @author Martijn Muijsers under AGPL-3.0 - */ -public final class BaseThreadPool { - - private BaseThreadPool() {} - - public static final String targetParallelismEnvironmentVariable = "gale.threads.target"; - public static final String maxUndisturbedLowerTierThreadCountEnvironmentVariable = "gale.threads.undisturbed"; - - /** - * The target number of threads that will be actively in use by this pool, - * which includes a potentially active {@link ServerThread}. - *
- * This value is always positive. - *
- * The value is currently automatically determined according to the following table: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
system threadsthreads spared
≤ 30
[4, 14]1
[15, 23]2
[24, 37]3
[38, 54]4
[55, 74]5
[75, 99]6
[100, 127]7
[128, 158]8
[159, 193]9
[194, 232]10
[233, 274]11
≥ 27512
- * Then target parallelism = system threads - threads spared. - *
- * The computed value above can be overridden using the {@link #targetParallelismEnvironmentVariable}. - */ - public static final int targetParallelism; - static { - int parallelismByEnvironmentVariable = Integer.getInteger(targetParallelismEnvironmentVariable, -1); - int targetParallelismBeforeSetAtLeastOne; - if (parallelismByEnvironmentVariable >= 0) { - targetParallelismBeforeSetAtLeastOne = parallelismByEnvironmentVariable; - } else { - int systemThreads = Runtime.getRuntime().availableProcessors(); - int threadsSpared; - if (systemThreads <= 3) { - threadsSpared = 0; - } else if (systemThreads <= 14) { - threadsSpared = 1; - } else if (systemThreads <= 23) { - threadsSpared = 2; - } else if (systemThreads <= 37) { - threadsSpared = 3; - } else if (systemThreads <= 54) { - threadsSpared = 4; - } else if (systemThreads <= 74) { - threadsSpared = 5; - } else if (systemThreads <= 99) { - threadsSpared = 6; - } else if (systemThreads <= 127) { - threadsSpared = 7; - } else if (systemThreads <= 158) { - threadsSpared = 8; - } else if (systemThreads <= 193) { - threadsSpared = 9; - } else if (systemThreads <= 232) { - threadsSpared = 10; - } else if (systemThreads <= 274) { - threadsSpared = 11; - } else { - threadsSpared = 12; - } - targetParallelismBeforeSetAtLeastOne = systemThreads - threadsSpared; - } - targetParallelism = Math.max(1, targetParallelismBeforeSetAtLeastOne); - } - - /** - * The maximum number of threads to be executing tasks, that only have tasks on their thread that are strictly - * below a certain tier, before a thread wishing to execute such tasks gets activated regardless. - * If this threshold of lower tier threads is not exceeded, activating a thread to execute a higher tier task - * will be delayed until one of the active threads finishes execution of their stack or blocks for another - * reason. - *
- * This value is always nonnegative. - *
- * This value is currently automatically determined according to the following rule: - *
    - *
  • 0, if {@link #targetParallelism} = 1
  • - *
  • {@code max(1, floor(2/5 * }{@link #targetParallelism}{@code ))}
  • - *
- * The computed value above can be overridden using the {@link #maxUndisturbedLowerTierThreadCountEnvironmentVariable}. - */ - public static final int maxUndisturbedLowerTierThreadCount; - static { - int maxUndisturbedLowerTierThreadCountByEnvironmentVariable = Integer.getInteger(maxUndisturbedLowerTierThreadCountEnvironmentVariable, -1); - maxUndisturbedLowerTierThreadCount = maxUndisturbedLowerTierThreadCountByEnvironmentVariable >= 0 ? maxUndisturbedLowerTierThreadCountByEnvironmentVariable : targetParallelism == 1 ? 0 : Math.max(1, targetParallelism * 2 / 5); - } - - /** - * An array of the {@link AssistThread}s in this pool, indexed by their {@link AssistThread#assistThreadIndex}. - *
- * This field must only ever be changed from within {@link #addAssistThread}. - */ - private static volatile AssistThread[] assistThreads = new AssistThread[0]; - - /** - * An array of the {@link BaseThread}s in this pool, indexed by their {@link BaseThread#baseThreadIndex}. - *
- * This field must not be referenced anywhere outside {@link #addAssistThread} or {@link #getBaseThreads()}: - * it only holds the last computed value. - */ - private static volatile @Nullable BaseThread @NotNull [] lastComputedBaseThreads = new BaseThread[1]; - - /** - * Creates a new {@link AssistThread}, adds it to this pool and starts it. - *
- * Must only be called from within {@link BaseThreadActivation#update()} while - * {@link BaseThreadActivation#updateOngoingOnThread} is not null. - */ - public static void addAssistThread() { - int oldThreadsLength = assistThreads.length; - int newThreadsLength = oldThreadsLength + 1; - // Expand the thread array - AssistThread[] newAssistThreads = Arrays.copyOf(assistThreads, newThreadsLength); - // Create the new thread - AssistThread newThread = newAssistThreads[oldThreadsLength] = new AssistThread(oldThreadsLength); - // Save the new thread array - assistThreads = newAssistThreads; - // Update the assist threads in baseThreads - @SuppressWarnings("NonAtomicOperationOnVolatileField") - BaseThread[] newLastComputedBaseThreads = lastComputedBaseThreads = Arrays.copyOf(lastComputedBaseThreads, newThreadsLength + 1); - newLastComputedBaseThreads[newThreadsLength] = newThread; - // Start the thread - newThread.start(); - MinecraftServer.THREAD_DEBUG_LOGGER.ifPresent(it -> it.info("Added assist thread " + newAssistThreads.length)); - } - - /** - * The {@link BaseThread}s ({@link ServerThread}s and {@link AssistThread}s) in this thread pool, - * specifically for the purpose of easy iteration. - *
- * Note that the {@link ServerThread} at index 0 may be null if {@link MinecraftServer#isConstructed} is false. - *
- * Must only be called from within {@link BaseThreadActivation#update()} while - * {@link BaseThreadActivation#updateOngoingOnThread} is not null. - */ - static @Nullable BaseThread @NotNull [] getBaseThreads() { - // Store in a non-local volatile - @Nullable BaseThread @NotNull [] baseThreads = lastComputedBaseThreads; - // Update the server thread if necessary - baseThreads[0] = ServerThread.getInstanceIfConstructed(); - // Return the value - return baseThreads; - } - - /** - * This method must not be called with {@code index} = 0 while {@link MinecraftServer#isConstructed} is false. - * - * @return The {@link BaseThread} with the given {@link BaseThread#baseThreadIndex}. - * This must not be called - */ - public static @NotNull BaseThread getThreadByBaseIndex(int index) { - if (index == 0) { - return ServerThread.getInstance(); - } - return assistThreads[index - 1]; - } - - /** - * @return The same value as {@link #getThreadByBaseIndex} if {@link MinecraftServer#isConstructed} is true - * or if the given {@code index} is not 0, - * or null otherwise (i.e. if {@link MinecraftServer#isConstructed} is false and the given {@code index} is 0). - */ - @SuppressWarnings("unused") - public static @Nullable BaseThread getThreadByBaseIndexIfConstructed(int index) { - return index != 0 || MinecraftServer.isConstructed ? getThreadByBaseIndex(index) : null; - } - - public static AssistThread getThreadByAssistIndex(int index) { - return assistThreads[index]; - } - -} \ No newline at end of file +///** +// * A pool of threads that can perform tasks to assist the current {@link ServerThread}. These tasks can be of +// * different {@linkplain BaseTaskQueueTier tiers}. +// *
+// * This pool intends to keep {@link #targetParallelism} threads active at any time, +// * which includes a potentially active {@link ServerThread}. +// *
+// * As such, this pool is closely intertwined with the {@link ServerThread}. This pool can not control the +// * {@link ServerThread} in any way, but it is responsible for signalling the {@link ServerThread} when tasks become +// * available in a {@link BaseTaskQueueTier#SERVER} task queue, and for listening for when the {@link ServerThread} +// * becomes (in)active in order to update the number of active {@link AssistThread}s accordingly. +// *

+// * Updates to the threads in this pool are done in a lock-free manner that attempts to do the right thing with +// * the volatile information that is available. In some cases, this may cause a thread to be woken up when it +// * should not have been, and so on, but the updates being lock-free is more significant than the updates being +// * optimal in a high-contention environment. The environment is not expected to have high enough contention for +// * this to have much of an impact. Additionally, the suboptimalities in updates are always optimistic in terms of +// * making/keeping threads active rather than inactive, and can not a situation where a thread was intended +// * to be active, but ends but not being active. +// * +// * @author Martijn Muijsers under AGPL-3.0 +// */ +//public final class BaseThreadPool { +// +// private BaseThreadPool() {} +// +// public static final String targetParallelismEnvironmentVariable = "gale.threads.target"; +// public static final String maxUndisturbedLowerTierThreadCountEnvironmentVariable = "gale.threads.undisturbed"; +// +// /** +// * The target number of threads that will be actively in use by this pool, +// * which includes a potentially active {@link ServerThread}. +// *
+// * This value is always positive. +// *
+// * The value is currently automatically determined according to the following table: +// * +// * +// * +// * +// * +// * +// * +// * +// * +// * +// * +// * +// * +// * +// * +// *
system threadsthreads spared
≤ 30
[4, 14]1
[15, 23]2
[24, 37]3
[38, 54]4
[55, 74]5
[75, 99]6
[100, 127]7
[128, 158]8
[159, 193]9
[194, 232]10
[233, 274]11
≥ 27512
+// * Then target parallelism = system threads - threads spared. +// *
+// * The computed value above can be overridden using the {@link #targetParallelismEnvironmentVariable}. +// */ +// public static final int targetParallelism; +// static { +// int parallelismByEnvironmentVariable = Integer.getInteger(targetParallelismEnvironmentVariable, -1); +// int targetParallelismBeforeSetAtLeastOne; +// if (parallelismByEnvironmentVariable >= 0) { +// targetParallelismBeforeSetAtLeastOne = parallelismByEnvironmentVariable; +// } else { +// int systemThreads = Runtime.getRuntime().availableProcessors(); +// int threadsSpared; +// if (systemThreads <= 3) { +// threadsSpared = 0; +// } else if (systemThreads <= 14) { +// threadsSpared = 1; +// } else if (systemThreads <= 23) { +// threadsSpared = 2; +// } else if (systemThreads <= 37) { +// threadsSpared = 3; +// } else if (systemThreads <= 54) { +// threadsSpared = 4; +// } else if (systemThreads <= 74) { +// threadsSpared = 5; +// } else if (systemThreads <= 99) { +// threadsSpared = 6; +// } else if (systemThreads <= 127) { +// threadsSpared = 7; +// } else if (systemThreads <= 158) { +// threadsSpared = 8; +// } else if (systemThreads <= 193) { +// threadsSpared = 9; +// } else if (systemThreads <= 232) { +// threadsSpared = 10; +// } else if (systemThreads <= 274) { +// threadsSpared = 11; +// } else { +// threadsSpared = 12; +// } +// targetParallelismBeforeSetAtLeastOne = systemThreads - threadsSpared; +// } +// targetParallelism = Math.max(1, targetParallelismBeforeSetAtLeastOne); +// } +// +// /** +// * The maximum number of threads to be executing tasks, that only have tasks on their thread that are strictly +// * below a certain tier, before a thread wishing to execute such tasks gets activated regardless. +// * If this threshold of lower tier threads is not exceeded, activating a thread to execute a higher tier task +// * will be delayed until one of the active threads finishes execution of their stack or blocks for another +// * reason. +// *
+// * This value is always nonnegative. +// *
+// * This value is currently automatically determined according to the following rule: +// *
    +// *
  • 0, if {@link #targetParallelism} = 1
  • +// *
  • {@code max(1, floor(2/5 * }{@link #targetParallelism}{@code ))}
  • +// *
+// * The computed value above can be overridden using the {@link #maxUndisturbedLowerTierThreadCountEnvironmentVariable}. +// */ +// public static final int maxUndisturbedLowerTierThreadCount; +// static { +// int maxUndisturbedLowerTierThreadCountByEnvironmentVariable = Integer.getInteger(maxUndisturbedLowerTierThreadCountEnvironmentVariable, -1); +// maxUndisturbedLowerTierThreadCount = maxUndisturbedLowerTierThreadCountByEnvironmentVariable >= 0 ? maxUndisturbedLowerTierThreadCountByEnvironmentVariable : targetParallelism == 1 ? 0 : Math.max(1, targetParallelism * 2 / 5); +// } +// +// /** +// * An array of the {@link AssistThread}s in this pool, indexed by their {@link AssistThread#assistThreadIndex}. +// *
+// * This field must only ever be changed from within {@link #addAssistThread}. +// */ +// private static volatile AssistThread[] assistThreads = new AssistThread[0]; +// +// /** +// * An array of the {@link BaseThread}s in this pool, indexed by their {@link BaseThread#baseThreadIndex}. +// *
+// * This field must not be referenced anywhere outside {@link #addAssistThread} or {@link #getBaseThreads()}: +// * it only holds the last computed value. +// */ +// private static volatile @Nullable BaseThread @NotNull [] lastComputedBaseThreads = new BaseThread[1]; +// +// /** +// * Creates a new {@link AssistThread}, adds it to this pool and starts it. +// *
+// * Must only be called from within {@link BaseThreadActivation#update()} while +// * {@link BaseThreadActivation#updateOngoingOnThread} is not null. +// */ +// public static void addAssistThread() { +// int oldThreadsLength = assistThreads.length; +// int newThreadsLength = oldThreadsLength + 1; +// // Expand the thread array +// AssistThread[] newAssistThreads = Arrays.copyOf(assistThreads, newThreadsLength); +// // Create the new thread +// AssistThread newThread = newAssistThreads[oldThreadsLength] = new AssistThread(oldThreadsLength); +// // Save the new thread array +// assistThreads = newAssistThreads; +// // Update the assist threads in baseThreads +// @SuppressWarnings("NonAtomicOperationOnVolatileField") +// BaseThread[] newLastComputedBaseThreads = lastComputedBaseThreads = Arrays.copyOf(lastComputedBaseThreads, newThreadsLength + 1); +// newLastComputedBaseThreads[newThreadsLength] = newThread; +// // Start the thread +// newThread.start(); +// MinecraftServer.THREAD_DEBUG_LOGGER.ifPresent(it -> it.info("Added assist thread " + newAssistThreads.length)); +// } +// +// /** +// * The {@link BaseThread}s ({@link ServerThread}s and {@link AssistThread}s) in this thread pool, +// * specifically for the purpose of easy iteration. +// *
+// * Note that the {@link ServerThread} at index 0 may be null if {@link MinecraftServer#isConstructed} is false. +// *
+// * Must only be called from within {@link BaseThreadActivation#update()} while +// * {@link BaseThreadActivation#updateOngoingOnThread} is not null. +// */ +// static @Nullable BaseThread @NotNull [] getBaseThreads() { +// // Store in a non-local volatile +// @Nullable BaseThread @NotNull [] baseThreads = lastComputedBaseThreads; +// // Update the server thread if necessary +// baseThreads[0] = ServerThread.getInstanceIfConstructed(); +// // Return the value +// return baseThreads; +// } +// +// /** +// * This method must not be called with {@code index} = 0 while {@link MinecraftServer#isConstructed} is false. +// * +// * @return The {@link BaseThread} with the given {@link BaseThread#baseThreadIndex}. +// * This must not be called +// */ +// public static @NotNull BaseThread getThreadByBaseIndex(int index) { +// if (index == 0) { +// return ServerThread.getInstance(); +// } +// return assistThreads[index - 1]; +// } +// +// /** +// * @return The same value as {@link #getThreadByBaseIndex} if {@link MinecraftServer#isConstructed} is true +// * or if the given {@code index} is not 0, +// * or null otherwise (i.e. if {@link MinecraftServer#isConstructed} is false and the given {@code index} is 0). +// */ +// @SuppressWarnings("unused") +// public static @Nullable BaseThread getThreadByBaseIndexIfConstructed(int index) { +// return index != 0 || MinecraftServer.isConstructed ? getThreadByBaseIndex(index) : null; +// } +// +// public static AssistThread getThreadByAssistIndex(int index) { +// return assistThreads[index]; +// } +// +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/AbstractBlockStateMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/AbstractBlockStateMixin.java new file mode 100644 index 0000000..06084d2 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/AbstractBlockStateMixin.java @@ -0,0 +1,105 @@ +package net.gensokyoreimagined.nitori.mixin.ai.pathing; + +import net.gensokyoreimagined.nitori.common.ai.pathing.BlockStatePathingCache; +import net.gensokyoreimagined.nitori.common.world.blockview.SingleBlockBlockView; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; //LandPathNodeMaker +import net.minecraft.world.level.pathfinder.PathfindingContext; +import net.minecraft.world.level.pathfinder.PathType; +import net.minecraft.core.BlockPos; +import org.apache.commons.lang3.Validate; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +@Mixin(BlockBehaviour.BlockStateBase.class) +public abstract class AbstractBlockStateMixin implements BlockStatePathingCache { + private PathType pathNodeType = null; + private PathType pathNodeTypeNeighbor = null; + + @Unique + private static final Method nitori$getPathTypeFromStateMethod; + + static { + try { + nitori$getPathTypeFromStateMethod = WalkNodeEvaluator.class.getDeclaredMethod("getPathTypeFromState", BlockGetter.class, BlockPos.class); + } catch (NoSuchMethodException e) { + throw new AssertionError("Failed to reflect getCommonNodeType method", e); + } + nitori$getPathTypeFromStateMethod.setAccessible(true); + } + + @Unique + private static PathType nitori$getPathTypeFromState(BlockGetter getter, BlockPos position) throws SingleBlockBlockView.SingleBlockViewException, ClassCastException { + try { + return (PathType) nitori$getPathTypeFromStateMethod.invoke(null, getter, position); + } catch (InvocationTargetException e) { + var cause = e.getCause(); + + if (cause instanceof SingleBlockBlockView.SingleBlockViewException) { + throw (SingleBlockBlockView.SingleBlockViewException) cause; + } + + if (cause instanceof ClassCastException) { + throw (ClassCastException) cause; + } + + throw new AssertionError("Get path type failed!", cause); + } catch (IllegalAccessException e) { + throw new AssertionError("Failed to reflect method", e); + } + } + + @Inject(method = "initCache", at = @At("RETURN")) + private void init(CallbackInfo ci) { + // Reset the cached path node types, to ensure they are re-calculated. + this.pathNodeType = null; + this.pathNodeTypeNeighbor = null; + + BlockState state = this.asState(); + + SingleBlockBlockView singleBlockBlockView = SingleBlockBlockView.of(state, BlockPos.ZERO); + try { + this.pathNodeType = Validate.notNull(nitori$getPathTypeFromState(singleBlockBlockView, BlockPos.ZERO), "Block has no common path node type!"); + } catch (SingleBlockBlockView.SingleBlockViewException | ClassCastException e) { + //This is usually hit by shulker boxes, as their hitbox depends on the block entity, and the node type depends on the hitbox + //Also catch ClassCastException in case some modded code casts BlockView to ChunkCache + this.pathNodeType = null; + } + try { + //Passing null as previous node type to the method signals to other lithium mixins that we only want the neighbor behavior of this block and not its neighbors + //Using exceptions for control flow, but this way we do not need to copy the code for the cache initialization, reducing required maintenance and improving mod compatibility + this.pathNodeTypeNeighbor = (WalkNodeEvaluator.checkNeighbourBlocks(new PathfindingContext(singleBlockBlockView, null), 1, 1, 1, null)); + if (this.pathNodeTypeNeighbor == null) { + this.pathNodeTypeNeighbor = PathType.OPEN; + } + } catch (SingleBlockBlockView.SingleBlockViewException | NullPointerException | ClassCastException e) { + this.pathNodeTypeNeighbor = null; + } + } + + @Override + public PathType lithium$getPathNodeType() { + return this.pathNodeType; + } + + @Override + public PathType lithium$getNeighborPathNodeType() { + return this.pathNodeTypeNeighbor; + } + + @Shadow + protected abstract BlockState asState(); + + @Shadow + public abstract Block getBlock(); +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/BirdPathNodeMakerMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/BirdPathNodeMakerMixin.java new file mode 100644 index 0000000..fd7a414 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/BirdPathNodeMakerMixin.java @@ -0,0 +1,22 @@ +package net.gensokyoreimagined.nitori.mixin.ai.pathing; + +//import net.gensokyoreimagined.nitori.common.ai.pathing.PathNodeCache; +//import net.minecraft.world.level.pathfinder.FlyNodeEvaluator; +//import net.minecraft.world.level.pathfinder.PathfindingContext; +//import net.minecraft.world.level.pathfinder.PathType; +//import org.spongepowered.asm.mixin.Mixin; +//import org.spongepowered.asm.mixin.injection.At; +//import org.spongepowered.asm.mixin.injection.Redirect; +// +//@Mixin(FlyNodeEvaluator.class) +//public class BirdPathNodeMakerMixin { +// +// /** +// * @reason Use optimized implementation which avoids scanning blocks for dangers where possible +// * @author JellySquid, 2No2Name +// */ +// @Redirect(method = "getPathType", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/pathfinder/FlyNodeEvaluator;checkNeighbourBlocks(Lnet/minecraft/world/level/pathfinder/PathfindingContext;IIILnet/minecraft/world/level/pathfinder/PathType;)Lnet/minecraft/world/level/pathfinder/PathType;")) +// private PathType getNodeTypeFromNeighbors(PathfindingContext pathContext, int x, int y, int z, PathType pathNodeType) { +// return PathNodeCache.getNodeTypeFromNeighbors(pathContext, x, y, z, pathNodeType); +// } +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/ChunkCacheMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/ChunkCacheMixin.java new file mode 100644 index 0000000..53847ce --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/ChunkCacheMixin.java @@ -0,0 +1,109 @@ +package net.gensokyoreimagined.nitori.mixin.ai.pathing; + +import net.gensokyoreimagined.nitori.common.util.Pos; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.ChunkAccess; //Chunk +import net.minecraft.world.level.PathNavigationRegion; +import net.minecraft.world.level.chunk.LevelChunkSection; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * The hottest part of path-finding is reading blocks out from the world. This patch makes a number of changes to + * avoid slow paths in the game and to better inline code. In testing, it shows a small improvement in path-finding + * code. + */ +@Mixin(PathNavigationRegion.class) +public abstract class ChunkCacheMixin implements BlockGetter { + private static final BlockState DEFAULT_BLOCK = Blocks.AIR.defaultBlockState(); + + @Shadow + @Final + protected ChunkAccess[][] chunks; + + @Shadow + @Final + protected int centerX; + + @Shadow + @Final + protected int centerZ; + + @Shadow + @Final + protected Level level; + // A 1D view of the chunks available to this cache + private ChunkAccess[] chunksFlat; + + // The x/z length of this cache + private int xLen, zLen; + + private int bottomY, topY; + + @Inject(method = "", at = @At("RETURN")) + private void init(Level level, BlockPos minPos, BlockPos maxPos, CallbackInfo ci) { + this.xLen = 1 + (Pos.ChunkCoord.fromBlockCoord(maxPos.getX())) - (Pos.ChunkCoord.fromBlockCoord(minPos.getX())); + this.zLen = 1 + (Pos.ChunkCoord.fromBlockCoord(maxPos.getZ())) - (Pos.ChunkCoord.fromBlockCoord(minPos.getZ())); + + this.chunksFlat = new ChunkAccess[this.xLen * this.zLen]; + + // Flatten the 2D chunk array into our 1D array + for (int x = 0; x < this.xLen; x++) { + System.arraycopy(this.chunks[x], 0, this.chunksFlat, x * this.zLen, this.zLen); + } + + this.bottomY = this.getMinBuildHeight(); + this.topY = this.getMaxBuildHeight(); + } + + /** + * @reason Use optimized function + * @author JellySquid + */ + @Overwrite + public BlockState getBlockState(BlockPos pos) { + int y = pos.getY(); + + if (!(y < this.bottomY || y >= this.topY)) { + int x = pos.getX(); + int z = pos.getZ(); + + int chunkX = (Pos.ChunkCoord.fromBlockCoord(x)) - this.centerX; + int chunkZ = (Pos.ChunkCoord.fromBlockCoord(z)) - this.centerZ; + + if (chunkX >= 0 && chunkX < this.xLen && chunkZ >= 0 && chunkZ < this.zLen) { + ChunkAccess chunk = this.chunksFlat[(chunkX * this.zLen) + chunkZ]; + + // Avoid going through Chunk#getBlockState + if (chunk != null) { + LevelChunkSection section = chunk.getSections()[Pos.SectionYIndex.fromBlockCoord(this, y)]; + + if (section != null) { + return section.getBlockState(x & 15, y & 15, z & 15); + } + } + } + } + + return DEFAULT_BLOCK; + } + + /** + * @reason Use optimized function + * @author JellySquid + */ + @Overwrite + public FluidState getFluidState(BlockPos pos) { + return this.getBlockState(pos).getFluidState(); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/LandPathNodeMakerMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/LandPathNodeMakerMixin.java new file mode 100644 index 0000000..ab9766c --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/LandPathNodeMakerMixin.java @@ -0,0 +1,81 @@ +package net.gensokyoreimagined.nitori.mixin.ai.pathing; + +//import net.gensokyoreimagined.nitori.common.ai.pathing.PathNodeCache; +//import net.minecraft.world.level.block.state.BlockState; +//import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; +//import net.minecraft.world.level.pathfinder.PathfindingContext; +//import net.minecraft.world.level.pathfinder.PathType; +//import net.minecraft.core.BlockPos; +//import net.minecraft.world.level.BlockGetter; +//import org.spongepowered.asm.mixin.Mixin; +//import org.spongepowered.asm.mixin.injection.At; +//import org.spongepowered.asm.mixin.injection.Inject; +//import org.spongepowered.asm.mixin.injection.Redirect; +//import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +//import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +// +///** +// * Determining the type of node offered by a block state is a very slow operation due to the nasty chain of tag, +// * instanceof, and block property checks. Since each blockstate can only map to one type of node, we can create a +// * cache which stores the result of this complicated code path. This provides a significant speed-up in path-finding +// * code and should be relatively safe. +// */ +//@Mixin(value = WalkNodeEvaluator.class, priority = 990) +//public abstract class LandPathNodeMakerMixin { +// /** +// * This mixin requires a priority < 1000 due to fabric api using 1000 and us needing to inject before them. +// * +// * @reason Use optimized implementation +// * @author JellySquid, 2No2Name +// */ +// @Inject(method = "getPathTypeFromState", +// at = @At( +// value = "INVOKE_ASSIGN", +// target = "Lnet/minecraft/world/level/block/state/BlockState;getBlock()Lnet/minecraft/world/level/block/Block;", +// shift = At.Shift.AFTER +// ), +// cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD +// ) +// private static void getLithiumCachedCommonNodeType(BlockGetter world, BlockPos pos, CallbackInfoReturnable cir, BlockState blockState) { +// PathType type = PathNodeCache.getPathType(blockState); +// if (type != null) { +// cir.setReturnValue(type); +// } +// } +// +// /** +// * Modify the method to allow it to just return the behavior of a single block instead of scanning its neighbors. +// * This technique might seem odd, but it allows us to be very mod and fabric-api compatible. +// * If the function is called with usual inputs (nodeType != null), it behaves normally. +// * If the function is called with nodeType == null, only the passed position is checked for its neighbor behavior. +// *

+// * This allows Lithium to call this function to initialize its caches. It also allows using this function as fallback +// * for dynamic blocks (shulker boxes and fabric-api dynamic definitions) +// * +// * @author 2No2Name +// */ +// @Inject( +// method = "checkNeighbourBlocks", locals = LocalCapture.CAPTURE_FAILHARD, +// at = @At( +// value = "INVOKE", shift = At.Shift.BEFORE, +// target = "Lnet/minecraft/world/level/pathfinder/PathfindingContext;getPathTypeFromState(III)Lnet/minecraft/world/level/pathfinder/PathType;" +// ), +// cancellable = true +// ) +// private static void doNotIteratePositionsIfLithiumSinglePosCall(PathfindingContext context, int x, int y, int z, PathType fallback, CallbackInfoReturnable cir, int i, int j, int k) { +// if (fallback == null) { +// if (i != -1 || j != -1 || k != -1) { +// cir.setReturnValue(null); +// } +// } +// } +// +// /** +// * @reason Use optimized implementation which avoids scanning blocks for dangers where possible +// * @author JellySquid, 2No2Name +// */ +// @Redirect(method = "getPathTypeStatic(Lnet/minecraft/world/level/pathfinder/PathfindingContext;Lnet/minecraft/core/BlockPos$MutableBlockPos;)Lnet/minecraft/world/level/pathfinder/PathType;", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/pathfinder/WalkNodeEvaluator;checkNeighbourBlocks(Lnet/minecraft/world/level/pathfinder/PathfindingContext;IIILnet/minecraft/world/level/pathfinder/PathType;)Lnet/minecraft/world/level/pathfinder/PathType;")) +// private static PathType getNodeTypeFromNeighbors(PathfindingContext context, int x, int y, int z, PathType fallback) { +// return PathNodeCache.getNodeTypeFromNeighbors(context, x, y, z, fallback); +// } +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/PathContextMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/PathContextMixin.java new file mode 100644 index 0000000..7ab990d --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/PathContextMixin.java @@ -0,0 +1,36 @@ +package net.gensokyoreimagined.nitori.mixin.ai.pathing; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.world.level.pathfinder.PathfindingContext; +import net.minecraft.world.entity.Mob; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(PathfindingContext.class) +public class PathContextMixin { + + @WrapOperation( + method = "", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Mob;level()Lnet/minecraft/world/level/Level;") + ) + private Level getWorldIfNonNull(Mob instance, Operation original) { + if (instance == null) { + return null; + } + return original.call(instance); + } + + @WrapOperation( + method = "", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Mob;blockPosition()Lnet/minecraft/core/BlockPos;") + ) + private BlockPos getBlockPosIfNonNull(Mob instance, Operation original) { + if (instance == null) { + return null; + } + return original.call(instance); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/TargetPredicateMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/TargetPredicateMixin.java new file mode 100644 index 0000000..6afb743 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/pathing/TargetPredicateMixin.java @@ -0,0 +1,34 @@ +package net.gensokyoreimagined.nitori.mixin.ai.pathing; + +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalDoubleRef; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.targeting.TargetingConditions; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * Credit: Leaves patch #0026 + */ +@Mixin(TargetingConditions.class) +public class TargetPredicateMixin { + @Shadow private double range; + + @Inject(method = "test", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getVisibilityPercent(Lnet/minecraft/world/entity/Entity;)D", shift = At.Shift.BEFORE), cancellable = true) + private void quickCancelPathFinding(LivingEntity baseEntity, LivingEntity targetEntity, CallbackInfoReturnable cir, @Share("dist")LocalDoubleRef doubleRef) { + double f = baseEntity.distanceToSqr(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ()); + doubleRef.set(f); + if (f > this.range * this.range) { + cir.setReturnValue(false); + } + } + + @Redirect(method = "test", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;distanceToSqr(DDD)D")) + private double borrowValueFromOtherMixin(LivingEntity instance, double x, double y, double z, @Share("dist")LocalDoubleRef doubleRef) { + return doubleRef.get(); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/poi/PointOfInterestSetMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/poi/PointOfInterestSetMixin.java new file mode 100644 index 0000000..3886b69 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/poi/PointOfInterestSetMixin.java @@ -0,0 +1,95 @@ +package net.gensokyoreimagined.nitori.mixin.ai.poi; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceMaps; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import net.gensokyoreimagined.nitori.common.world.interests.PointOfInterestSetExtended; +import net.gensokyoreimagined.nitori.common.world.interests.iterator.SinglePointOfInterestTypeFilter; +import net.minecraft.core.Holder; +import net.minecraft.world.entity.ai.village.poi.PoiRecord; +import net.minecraft.world.entity.ai.village.poi.PoiSection; +import net.minecraft.world.entity.ai.village.poi.PoiManager; +import net.minecraft.world.entity.ai.village.poi.PoiType; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +@Mixin(PoiSection.class) +public class PointOfInterestSetMixin implements PointOfInterestSetExtended { + @Mutable + @Shadow + @Final + private Map, Set> byType; + + private static Iterable> getPointsByTypeIterator(Map map) { + if (map instanceof Reference2ReferenceMap) { + return Reference2ReferenceMaps.fastIterable((Reference2ReferenceMap) map); + } else { + return map.entrySet(); + } + } + + @Inject(method = "(Ljava/lang/Runnable;ZLjava/util/List;)V", at = @At("RETURN")) + private void reinit(Runnable updateListener, boolean bl, List list, CallbackInfo ci) { + this.byType = new Reference2ReferenceOpenHashMap<>(this.byType); + } + + @Override + public void lithium$collectMatchingPoints(Predicate> type, PoiManager.Occupancy status, Consumer consumer) { + if (type instanceof SinglePointOfInterestTypeFilter) { + this.getWithSingleTypeFilter(((SinglePointOfInterestTypeFilter) type).getType(), status, consumer); + } else { + this.getWithDynamicTypeFilter(type, status, consumer); + } + } + + private void getWithDynamicTypeFilter(Predicate> type, PoiManager.Occupancy status, Consumer consumer) { + for (Map.Entry, Set> entry : getPointsByTypeIterator(this.byType)) { + if (!type.test(entry.getKey())) { + continue; + } + + if (!entry.getValue().isEmpty()) { + for (PoiRecord poi : entry.getValue()) { + if (status.getTest().test(poi)) { + consumer.accept(poi); + } + } + } + } + } + + private void getWithSingleTypeFilter(Holder type, PoiManager.Occupancy status, Consumer consumer) { + Set entries = this.byType.get(type); + + if (entries == null || entries.isEmpty()) { + return; + } + + for (PoiRecord poi : entries) { + if (status.getTest().test(poi)) { + consumer.accept(poi); + } + } + } + + @SuppressWarnings("unchecked") + @Redirect(method = "add(Lnet/minecraft/world/entity/ai/village/poi/PoiRecord;)Z", + at = @At(value = "INVOKE", target = "Ljava/util/Map;computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;")) + private K computeIfAbsent(Map map, K key, Function mappingFunction) { + return (K) map.computeIfAbsent(key, o -> (V) new ObjectOpenHashSet<>()); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/poi/PointOfInterestTypesMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/poi/PointOfInterestTypesMixin.java new file mode 100644 index 0000000..45e2ad1 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/poi/PointOfInterestTypesMixin.java @@ -0,0 +1,30 @@ +package net.gensokyoreimagined.nitori.mixin.ai.poi; + +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import net.gensokyoreimagined.nitori.common.world.interests.types.PointOfInterestTypeHelper; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.entity.ai.village.poi.PoiType; +import net.minecraft.world.entity.ai.village.poi.PoiTypes; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Map; + +/** + * Replaces the backing map type with a faster collection type which uses reference equality. + */ +@Mixin(PoiTypes.class) +public class PointOfInterestTypesMixin { + @Mutable + @Shadow + @Final + private static Map TYPE_BY_STATE; + + static { + TYPE_BY_STATE = new Reference2ReferenceOpenHashMap<>(TYPE_BY_STATE); + + PointOfInterestTypeHelper.init(TYPE_BY_STATE.keySet()); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/launch/BrainMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/launch/BrainMixin.java new file mode 100644 index 0000000..c0a7249 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/launch/BrainMixin.java @@ -0,0 +1,199 @@ +package net.gensokyoreimagined.nitori.mixin.ai.task.launch; + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.gensokyoreimagined.nitori.common.util.collections.MaskedList; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.schedule.Activity; +import net.minecraft.world.entity.ai.Brain; +import net.minecraft.world.entity.ai.behavior.Behavior; +import net.minecraft.world.entity.ai.behavior.BehaviorControl; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.VisibleForDebug; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.util.*; +import java.util.function.Supplier; + +@Mixin(Brain.class) +public class BrainMixin { + + @Shadow + @Final + private Map>>> availableBehaviorsByPriority; + + @Shadow + @Final + private Set activeActivities; + + private ArrayList> nitori$possibleTasks; + private MaskedList> nitori$runningTasks; + + private void nitori$onTasksChanged() { + this.nitori$runningTasks = null; + this.nitori$onPossibleActivitiesChanged(); + } + + private void nitori$onPossibleActivitiesChanged() { + this.nitori$possibleTasks = null; + } + + private void nitori$initPossibleTasks() { + this.nitori$possibleTasks = new ArrayList<>(); + for (Map>> map : this.availableBehaviorsByPriority.values()) { + for (Map.Entry>> entry : map.entrySet()) { + Activity activity = entry.getKey(); + if (!this.activeActivities.contains(activity)) { + continue; + } + Set> set = entry.getValue(); + for (BehaviorControl BehaviorControl : set) { + //noinspection UseBulkOperation + this.nitori$possibleTasks.add(BehaviorControl); + } + } + } + } + + private ArrayList> nitori$getPossibleTasks() { + if (this.nitori$possibleTasks == null) { + this.nitori$initPossibleTasks(); + } + return this.nitori$possibleTasks; + } + + private MaskedList> nitori$getCurrentlyRunningTasks() { + if (this.nitori$runningTasks == null) { + this.nitori$initCurrentlyRunningTasks(); + } + return this.nitori$runningTasks; + } + + private void nitori$initCurrentlyRunningTasks() { + MaskedList> list = new MaskedList<>(new ObjectArrayList<>(), false); + + for (Map>> map : this.availableBehaviorsByPriority.values()) { + for (Set> set : map.values()) { + for (BehaviorControl BehaviorControl : set) { + list.addOrSet(BehaviorControl, BehaviorControl.getStatus() == Behavior.Status.RUNNING); + } + } + } + this.nitori$runningTasks = list; + } + + /** + * @author 2No2Name + * @reason use optimized cached collection + */ + @Overwrite + private void startEachNonRunningBehavior(ServerLevel level, E entity) { + long startTime = level.getGameTime(); + for (BehaviorControl BehaviorControl : this.nitori$getPossibleTasks()) { + if (BehaviorControl.getStatus() == Behavior.Status.STOPPED) { + BehaviorControl.tryStart(level, entity, startTime); + } + } + } + + /** + * @author 2No2Name + * @reason use optimized cached collection + */ + @Overwrite + @Deprecated + @VisibleForDebug + public List> getRunningBehaviors() { + return this.nitori$getCurrentlyRunningTasks(); + } + + + @Inject( + method = "(Ljava/util/Collection;Ljava/util/Collection;Lcom/google/common/collect/ImmutableList;Ljava/util/function/Supplier;)V", + at = @At("RETURN") + ) + private void reinitializeBrainCollections(Collection memories, Collection sensors, ImmutableList memoryEntries, Supplier codecSupplier, CallbackInfo ci) { + this.nitori$onTasksChanged(); + } + + @Inject( + method = "addActivity(Lnet/minecraft/world/entity/schedule/Activity;ILcom/google/common/collect/ImmutableList;)V", + at = @At("RETURN") + ) + private void reinitializeTasksSorted(Activity activity, int begin, ImmutableList> list, CallbackInfo ci) { + this.nitori$onTasksChanged(); + } + + @Inject( + method = "clearMemories", + at = @At("RETURN") + ) + private void reinitializeTasksSorted(CallbackInfo ci) { + this.nitori$onTasksChanged(); + } + + @Inject( + method = "setActiveActivity", + at = @At( + value = "INVOKE", + target = "Ljava/util/Set;add(Ljava/lang/Object;)Z", + shift = At.Shift.AFTER + ) + ) + private void onPossibleActivitiesChanged(Activity except, CallbackInfo ci) { + this.nitori$onPossibleActivitiesChanged(); + } + + + @Inject( + method = "stopAll", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/ai/behavior/BehaviorControl;doStop(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/LivingEntity;J)V" + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void removeStoppedTask(ServerLevel world, E entity, CallbackInfo ci, long l, Iterator it, BehaviorControl task) { + if (this.nitori$runningTasks != null) { + this.nitori$runningTasks.setVisible(task, false); + } + } + + @Inject( + method = "tickEachRunningBehavior", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/ai/behavior/BehaviorControl;tickOrStop(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/LivingEntity;J)V", + shift = At.Shift.AFTER + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void removeTaskIfStopped(ServerLevel world, E entity, CallbackInfo ci, long l, Iterator it, BehaviorControl BehaviorControl) { + if (this.nitori$runningTasks != null && BehaviorControl.getStatus() != Behavior.Status.RUNNING) { + this.nitori$runningTasks.setVisible(BehaviorControl, false); + } + } + + @ModifyVariable( + method = "startEachNonRunningBehavior", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/ai/behavior/BehaviorControl;tryStart(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/LivingEntity;J)Z", + shift = At.Shift.AFTER + ) + ) + private BehaviorControl addStartedTasks(BehaviorControl BehaviorControl) { + if (this.nitori$runningTasks != null && BehaviorControl.getStatus() == Behavior.Status.RUNNING) { + this.nitori$runningTasks.setVisible(BehaviorControl, true); + } + return BehaviorControl; + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/memory_change_counting/BrainMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/memory_change_counting/BrainMixin.java new file mode 100644 index 0000000..c5273dd --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/memory_change_counting/BrainMixin.java @@ -0,0 +1,52 @@ +package net.gensokyoreimagined.nitori.mixin.ai.task.memory_change_counting; + +import net.gensokyoreimagined.nitori.common.ai.MemoryModificationCounter; +import net.minecraft.world.entity.ai.Brain; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Map; +import java.util.Optional; + +@Mixin(Brain.class) +public class BrainMixin implements MemoryModificationCounter { + + private long nitori$memoryModCount = 1; + + @Redirect( + method = "setMemoryInternal", + at = @At( + value = "INVOKE", + target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" + ) + ) + private Object increaseMemoryModificationCount(Map map, Object key, Object newValue) { + Object oldValue = map.put(key, newValue); + if (oldValue == null || ((Optional) oldValue).isPresent() != ((Optional) newValue).isPresent()) { + this.nitori$memoryModCount++; + } + return oldValue; + } + + @Override + public long lithium$getModCount() { + return nitori$memoryModCount; + } + + + /** + * Fix mod count being reset when villager loses profession due to disappearing workstation. + * Mod count being reset can lead to tasks not running even though they should be! + */ + @Inject( + method = "copyWithoutBehaviors", + at = @At("RETURN") + ) + private void copyModCount(CallbackInfoReturnable> cir) { + Brain newBrain = cir.getReturnValue(); + ((BrainMixin) (Object) newBrain).nitori$memoryModCount = this.nitori$memoryModCount + 1; + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/memory_change_counting/MultiTickTaskMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/memory_change_counting/MultiTickTaskMixin.java new file mode 100644 index 0000000..ac0b19d --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/memory_change_counting/MultiTickTaskMixin.java @@ -0,0 +1,59 @@ +package net.gensokyoreimagined.nitori.mixin.ai.task.memory_change_counting; + +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import net.gensokyoreimagined.nitori.common.ai.MemoryModificationCounter; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.Brain; +import net.minecraft.world.entity.ai.memory.MemoryStatus; +import net.minecraft.world.entity.ai.memory.MemoryModuleType; +import net.minecraft.world.entity.ai.behavior.Behavior; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Map; + +@Mixin(Behavior.class) +public class MultiTickTaskMixin { + @Mutable + @Shadow + @Final + protected Map, MemoryStatus> entryCondition; + + @Unique + private long nitori$cachedMemoryModCount = -1; + @Unique + private boolean nitori$cachedHasRequiredMemoryState; + + @Inject(method = "(Ljava/util/Map;II)V", at = @At("RETURN")) + private void init(Map, MemoryStatus> map, int int_1, int int_2, CallbackInfo ci) { + this.entryCondition = new Reference2ObjectOpenHashMap<>(map); + } + + /** + * @reason Use cached required memory state test result if memory state is unchanged + * @author 2No2Name + */ + @Overwrite + public boolean hasRequiredMemories(E entity) { + Brain brain = entity.getBrain(); + long modCount = ((MemoryModificationCounter) brain).lithium$getModCount(); + if (this.nitori$cachedMemoryModCount == modCount) { + return this.nitori$cachedHasRequiredMemoryState; + } + this.nitori$cachedMemoryModCount = modCount; + + ObjectIterator, MemoryStatus>> fastIterator = ((Reference2ObjectOpenHashMap, MemoryStatus>) this.entryCondition).reference2ObjectEntrySet().fastIterator(); + while (fastIterator.hasNext()) { + Reference2ObjectMap.Entry, MemoryStatus> entry = fastIterator.next(); + if (!brain.checkMemory(entry.getKey(), entry.getValue())) { + return this.nitori$cachedHasRequiredMemoryState = false; + } + } + + return this.nitori$cachedHasRequiredMemoryState = true; + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/replace_streams/WeightedListMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/replace_streams/WeightedListMixin.java new file mode 100644 index 0000000..7600a10 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/ai/task/replace_streams/WeightedListMixin.java @@ -0,0 +1,22 @@ +package net.gensokyoreimagined.nitori.mixin.ai.task.replace_streams; + +import net.gensokyoreimagined.nitori.common.ai.WeightedListIterable; +import net.minecraft.world.entity.ai.behavior.ShufflingList; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Iterator; +import java.util.List; + +@Mixin(ShufflingList.class) +public class WeightedListMixin implements WeightedListIterable { + @Shadow + @Final + protected List> entries; + + @Override + public Iterator iterator() { + return new WeightedListIterable.ListIterator<>(this.entries.iterator()); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/chunk/entity_class_groups/TypeFilterableListMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/chunk/entity_class_groups/TypeFilterableListMixin.java new file mode 100644 index 0000000..250178f --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/chunk/entity_class_groups/TypeFilterableListMixin.java @@ -0,0 +1,86 @@ +package net.gensokyoreimagined.nitori.mixin.chunk.entity_class_groups; + +import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap; +import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import net.gensokyoreimagined.nitori.common.entity.EntityClassGroup; +import net.gensokyoreimagined.nitori.common.world.chunk.ClassGroupFilterableList; +import net.minecraft.world.entity.Entity; +import net.minecraft.util.ClassInstanceMultiMap; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Patches {@link ClassInstanceMultiMap} to allow grouping entities by arbitrary groups of classes instead of one class only. + */ +@Mixin(ClassInstanceMultiMap.class) +public abstract class TypeFilterableListMixin implements ClassGroupFilterableList { + + @Shadow + @Final + private List allInstances; + + private final Reference2ReferenceArrayMap> nitori$entitiesByGroup = + new Reference2ReferenceArrayMap<>(); + + /** + * Update our collections + */ + @ModifyVariable(method = "add(Ljava/lang/Object;)Z", at = @At("HEAD"), argsOnly = true) + public T add(T entity) { + for (Map.Entry> entityGroupAndSet : this.nitori$entitiesByGroup.entrySet()) { + EntityClassGroup entityGroup = entityGroupAndSet.getKey(); + if (entityGroup.contains(((Entity) entity).getClass())) { + entityGroupAndSet.getValue().add((entity)); + } + } + return entity; + } + + /** + * Update our collections + */ + @ModifyVariable(method = "remove(Ljava/lang/Object;)Z", at = @At("HEAD"), argsOnly = true) + public Object remove(Object o) { + for (ReferenceLinkedOpenHashSet entitySet : this.nitori$entitiesByGroup.values()) { + //noinspection SuspiciousMethodCalls + entitySet.remove(o); + } + return o; + } + + /** + * Get entities of a class group + */ + public Collection lithium$getAllOfGroupType(EntityClassGroup type) { + Collection collection = this.nitori$entitiesByGroup.get(type); + + if (collection == null) { + collection = this.nitori$createAllOfGroupType(type); + } + + return collection; + } + + /** + * Start grouping by a new class group + */ + private Collection nitori$createAllOfGroupType(EntityClassGroup type) { + ReferenceLinkedOpenHashSet allOfType = new ReferenceLinkedOpenHashSet<>(); + + for (T entity : this.allInstances) { + if (type.contains(entity.getClass())) { + allOfType.add(entity); + } + } + this.nitori$entitiesByGroup.put(type, allOfType); + + return allOfType; + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/EntityNavigationMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/EntityNavigationMixin.java new file mode 100644 index 0000000..5f9bb3c --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/EntityNavigationMixin.java @@ -0,0 +1,66 @@ +package net.gensokyoreimagined.nitori.mixin.entity.inactive_navigations; + +import net.gensokyoreimagined.nitori.common.entity.NavigatingEntity; +import net.gensokyoreimagined.nitori.common.world.ServerWorldExtended; +import net.minecraft.world.entity.ai.navigation.PathNavigation; +import net.minecraft.world.level.pathfinder.Path; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PathNavigation.class) +public abstract class EntityNavigationMixin { + + @Shadow + @Final + protected Level level; + + @Shadow + protected Path path; + + @Shadow + @Final + protected Mob mob; + + @Inject( + method = "recomputePath", + at = @At( + value = "INVOKE_ASSIGN", + target = "Lnet/minecraft/world/entity/ai/navigation/PathNavigation;createPath(Lnet/minecraft/core/BlockPos;I)Lnet/minecraft/world/level/pathfinder/Path;", + shift = At.Shift.AFTER + ) + ) + private void updateListeningState(CallbackInfo ci) { + if (((NavigatingEntity) this.mob).lithium$isRegisteredToWorld()) { + if (this.path == null) { + ((ServerWorldExtended) this.level).lithium$setNavigationInactive(this.mob); + } else { + ((ServerWorldExtended) this.level).lithium$setNavigationActive(this.mob); + } + } + } + + @Inject(method = "moveTo(Lnet/minecraft/world/level/pathfinder/Path;D)Z", at = @At(value = "RETURN")) + private void updateListeningState2(Path path, double speed, CallbackInfoReturnable cir) { + if (((NavigatingEntity) this.mob).lithium$isRegisteredToWorld()) { + if (this.path == null) { + ((ServerWorldExtended) this.level).lithium$setNavigationInactive(this.mob); + } else { + ((ServerWorldExtended) this.level).lithium$setNavigationActive(this.mob); + } + } + } + + @Inject(method = "stop", at = @At(value = "RETURN")) + private void stopListening(CallbackInfo ci) { + if (((NavigatingEntity) this.mob).lithium$isRegisteredToWorld()) { + ((ServerWorldExtended) this.level).lithium$setNavigationInactive(this.mob); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/MobEntityMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/MobEntityMixin.java new file mode 100644 index 0000000..4baeabe --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/MobEntityMixin.java @@ -0,0 +1,76 @@ +package net.gensokyoreimagined.nitori.mixin.entity.inactive_navigations; + +import net.gensokyoreimagined.nitori.common.entity.NavigatingEntity; +import net.gensokyoreimagined.nitori.common.world.ServerWorldExtended; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.ai.navigation.PathNavigation; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Intrinsic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Mob.class) +public abstract class MobEntityMixin extends Entity implements NavigatingEntity { + private PathNavigation nitori$registeredNavigation; + + public MobEntityMixin(EntityType type, Level world) { + super(type, world); + } + + @Shadow + public abstract PathNavigation getNavigation(); + + @Override + public boolean lithium$isRegisteredToWorld() { + return this.nitori$registeredNavigation != null; + } + + @Override + public void lithium$setRegisteredToWorld(PathNavigation navigation) { + this.nitori$registeredNavigation = navigation; + } + + @Override + public PathNavigation lithium$getRegisteredNavigation() { + return this.nitori$registeredNavigation; + } + + @Inject(method = "startRiding", at = @At("RETURN")) + private void onNavigationReplacement(Entity entity, boolean force, CallbackInfoReturnable cir) { + this.lithium$updateNavigationRegistration(); + } + + @Override + @Intrinsic + public void stopRiding() { + super.stopRiding(); + } + + @SuppressWarnings({"MixinAnnotationTarget", "UnresolvedMixinReference"}) + @Inject(method = "stopRiding()V", at = @At("RETURN")) + private void updateOnStopRiding(CallbackInfo ci) { + this.lithium$updateNavigationRegistration(); + } + + @Override + public void lithium$updateNavigationRegistration() { + if (this.lithium$isRegisteredToWorld()) { + PathNavigation navigation = this.getNavigation(); + if (this.nitori$registeredNavigation != navigation) { + ((ServerWorldExtended) this.level()).lithium$setNavigationInactive((Mob) (Object) this); + this.nitori$registeredNavigation = navigation; + + if (navigation.getPath() != null) { + ((ServerWorldExtended) this.level()).lithium$setNavigationActive((Mob) (Object) this); + } + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/ServerEntityHandlerMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/ServerEntityHandlerMixin.java new file mode 100644 index 0000000..920a5ae --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/ServerEntityHandlerMixin.java @@ -0,0 +1,51 @@ +package net.gensokyoreimagined.nitori.mixin.entity.inactive_navigations; + +//import net.gensokyoreimagined.nitori.common.entity.NavigatingEntity; +//import net.gensokyoreimagined.nitori.common.world.ServerWorldExtended; +//import net.minecraft.world.entity.ai.navigation.PathNavigation; +//import net.minecraft.world.entity.Mob; +//import net.minecraft.server.level.ServerLevel; +//import org.spongepowered.asm.mixin.Mixin; +//import org.spongepowered.asm.mixin.injection.At; +//import org.spongepowered.asm.mixin.injection.Inject; +//import org.spongepowered.asm.mixin.injection.Redirect; +//import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +// +//import java.util.Set; +// +//@Mixin(ServerLevel.ServerLevel.class) +//public class ServerEntityHandlerMixin { +// +// private ServerLevel outer; +// +// @Inject(method = "", at = @At("TAIL")) +// private void inj(ServerLevel outer, CallbackInfo ci) { +// this.outer = outer; +// } +// +// @Redirect(method = "onTrackingStart(Lnet/minecraft/world/entity/Entity;)V", at = @At(value = "INVOKE", target = "Ljava/util/Set;add(Ljava/lang/Object;)Z")) +// private boolean startListeningOnEntityLoad(Set set, Object mobEntityObj) { +// Mob mobEntity = (Mob) mobEntityObj; +// PathNavigation navigation = mobEntity.getNavigation(); +// ((NavigatingEntity) mobEntity).lithium$setRegisteredToWorld(navigation); +// if (navigation.getPath() != null) { +// ((ServerWorldExtended) this.outer).lithium$setNavigationActive(mobEntity); +// } +// return set.add(mobEntity); +// } +// +// @Redirect(method = "onTrackingEnd(Lnet/minecraft/world/entity/Entity;)V", at = @At(value = "INVOKE", target = "Ljava/util/Set;remove(Ljava/lang/Object;)Z")) +// private boolean stopListeningOnEntityUnload(Set set, Object mobEntityObj) { +// Mob mobEntity = (Mob) mobEntityObj; +// NavigatingEntity navigatingEntity = (NavigatingEntity) mobEntity; +// if (navigatingEntity.lithium$isRegisteredToWorld()) { +// PathNavigation registeredNavigation = navigatingEntity.lithium$getRegisteredNavigation(); +// if (registeredNavigation.getPath() != null) { +// ((ServerWorldExtended) this.outer).lithium$setNavigationInactive(mobEntity); +// } +// navigatingEntity.lithium$setRegisteredToWorld(null); +// } +// return set.remove(mobEntity); +// } +// +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/ServerWorldMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/ServerWorldMixin.java new file mode 100644 index 0000000..00cf60e --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/inactive_navigations/ServerWorldMixin.java @@ -0,0 +1,143 @@ +package net.gensokyoreimagined.nitori.mixin.entity.inactive_navigations; + +//import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +//import net.gensokyoreimagined.nitori.common.entity.NavigatingEntity; +//import net.gensokyoreimagined.nitori.common.world.ServerWorldExtended; +//import net.minecraft.world.level.block.state.BlockState; +//import net.minecraft.world.entity.ai.navigation.PathNavigation; +//import net.minecraft.world.entity.Mob; +//import net.minecraft.core.RegistryAccess; +//import net.minecraft.resources.ResourceKey; +//import net.minecraft.core.Holder; +//import net.minecraft.server.MinecraftServer; +//import net.minecraft.server.level.progress.ChunkProgressListener; +//import net.minecraft.server.level.ServerLevel; +//import net.minecraft.core.BlockPos; +//import net.minecraft.world.RandomSequences; +//import net.minecraft.util.profiling.ProfilerFiller; +//import net.minecraft.world.level.storage.PrimaryLevelData; +//import net.minecraft.world.phys.shapes.VoxelShape; +//import net.minecraft.world.level.storage.WritableLevelData; +//import net.minecraft.world.level.Level; +//import net.minecraft.world.level.dimension.LevelStem; +//import net.minecraft.world.level.dimension.DimensionType; +//import net.minecraft.world.level.storage.ServerLevelData; +//import net.minecraft.world.level.storage.LevelStorageSource; +//import org.bukkit.World; +//import org.bukkit.generator.BiomeProvider; +//import org.bukkit.generator.ChunkGenerator; +//import org.spongepowered.asm.mixin.Final; +//import org.spongepowered.asm.mixin.Mixin; +//import org.spongepowered.asm.mixin.Mutable; +//import org.spongepowered.asm.mixin.Shadow; +//import org.spongepowered.asm.mixin.injection.At; +//import org.spongepowered.asm.mixin.injection.Inject; +//import org.spongepowered.asm.mixin.injection.Redirect; +//import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +// +//import java.util.Collections; +//import java.util.Iterator; +//import java.util.List; +//import java.util.Set; +//import java.util.concurrent.Executor; +//import java.util.function.Supplier; +// +///** +// * This patch is supposed to reduce the cost of setblockstate calls that change the collision shape of a block. +// * In vanilla, changing the collision shape of a block will notify *ALL* MobEntities in the world. +// * Instead, we track which EntityNavigation is going to be used by a MobEntity and +// * call the update code on the navigation directly. +// * As EntityNavigations only care about these changes when they actually have a currentPath, we skip the iteration +// * of many EntityNavigations. For that optimization we need to track whether navigations have a path. +// *

+// * Another possible optimization for the future: By using the entity section registration tracking of 1.17, +// * we can partition the active navigation set by region/chunk/etc. to be able to iterate over nearby EntityNavigations. +// * In vanilla the limit calculation includes the path length entity position, which can change very often and force us +// * to update the registration very often, which could cost a lot of performance. +// * As the number of block changes is generally way higher than the number of mobs pathfinding, the update code would +// * need to be triggered by the mobs pathfinding. +// */ +//@Mixin(ServerLevel.class) +//public abstract class ServerWorldMixin extends Level implements ServerWorldExtended { +// @Mutable +// @Shadow +// @Final +// Set navigatingMobs; +// +// private ReferenceOpenHashSet activeNavigations; +// +// protected ServerWorldMixin(WritableLevelData properties, ResourceKey registryRef, RegistryAccess registryManager, Holder dimensionEntry, Supplier profiler, boolean isClient, boolean debugWorld, long biomeAccess, int maxChainedNeighborUpdates) { +// super(properties, registryRef, registryManager, dimensionEntry, profiler, isClient, debugWorld, biomeAccess, maxChainedNeighborUpdates); +// } +// +// +// /** +// * Optimization: Only update listeners that may care about the update. Listeners which have no path +// * never react to the update. +// * With thousands of non-pathfinding mobs in the world, this can be a relevant difference. +// */ +// @Redirect( +// method = "sendBlockUpdated", +// at = @At( +// value = "INVOKE", +// target = "Ljava/util/Set;iterator()Ljava/util/Iterator;" +// ) +// ) +// private Iterator getActiveListeners(Set set) { +// return Collections.emptyIterator(); +// } +// +// @SuppressWarnings("rawtypes") +// @Inject(method = "", at = @At("TAIL")) +// private void init(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, RandomSequences randomsequences, World.Environment env, ChunkGenerator gen, BiomeProvider biomeProvider, CallbackInfo ci) { +// this.navigatingMobs = new ReferenceOpenHashSet<>(this.navigatingMobs); +// this.activeNavigations = new ReferenceOpenHashSet<>(); +// } +// +// @Override +// public void lithium$setNavigationActive(Mob mobEntity) { +// this.activeNavigations.add(((NavigatingEntity) mobEntity).lithium$getRegisteredNavigation()); +// } +// +// @Override +// public void lithium$setNavigationInactive(Mob mobEntity) { +// this.activeNavigations.remove(((NavigatingEntity) mobEntity).lithium$getRegisteredNavigation()); +// } +// +// @Inject( +// method = "sendBlockUpdated", +// at = @At( +// value = "INVOKE", +// target = "Ljava/util/Set;iterator()Ljava/util/Iterator;" +// ), +// locals = LocalCapture.CAPTURE_FAILHARD +// ) +// private void updateActiveListeners(BlockPos pos, BlockState oldState, BlockState newState, int arg3, CallbackInfo ci, VoxelShape string, VoxelShape voxelShape, List list) { +// for (PathNavigation entityNavigation : this.activeNavigations) { +// if (entityNavigation.shouldRecomputePath(pos)) { +// list.add(entityNavigation); +// } +// } +// } +// +// /** +// * Debug function +// * +// * @return whether the activeEntityNavigation set is in the correct state +// */ +// @SuppressWarnings("unused") +// public boolean isConsistent() { +// int i = 0; +// for (Mob mobEntity : this.navigatingMobs) { +// PathNavigation entityNavigation = mobEntity.getNavigation(); +// if ((entityNavigation.getPath() != null && ((NavigatingEntity) mobEntity).lithium$isRegisteredToWorld()) != this.activeNavigations.contains(entityNavigation)) { +// return false; +// } +// if (entityNavigation.getPath() != null) { +// i++; +// } +// } +// return this.activeNavigations.size() == i; +// } +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/pathfinding/TargetPredicateMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/pathfinding/TargetPredicateMixin.java new file mode 100644 index 0000000..5b6c58f --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/pathfinding/TargetPredicateMixin.java @@ -0,0 +1,34 @@ +package net.gensokyoreimagined.nitori.mixin.entity.pathfinding; + +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalDoubleRef; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.targeting.TargetingConditions; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * Credit: Leaves patch #0026 + */ +@Mixin(TargetingConditions.class) +public class TargetPredicateMixin { + @Shadow private double range; + + @Inject(method = "test", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getVisibilityPercent(Lnet/minecraft/world/entity/Entity;)D", shift = At.Shift.BEFORE), cancellable = true) + private void quickCancelPathFinding(LivingEntity baseEntity, LivingEntity targetEntity, CallbackInfoReturnable cir, @Share("dist")LocalDoubleRef doubleRef) { + double f = baseEntity.distanceToSqr(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ()); + doubleRef.set(f); + if (f > this.range * this.range) { + cir.setReturnValue(false); + } + } + + @Redirect(method = "test", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;distanceToSqr(DDD)D")) + private double borrowValueFromOtherMixin(LivingEntity instance, double x, double y, double z, @Share("dist")LocalDoubleRef doubleRef) { + return doubleRef.get(); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/replace_entitytype_predicates/ArmorStandEntityMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/replace_entitytype_predicates/ArmorStandEntityMixin.java index c1601f8..5e5a3a8 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/replace_entitytype_predicates/ArmorStandEntityMixin.java +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/replace_entitytype_predicates/ArmorStandEntityMixin.java @@ -27,11 +27,11 @@ // target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;)Ljava/util/List;" // ) // ) -// private List getMinecartsDirectly(Level level, Entity excluded, AABB box, Predicate predicate) { +// private List getMinecartsDirectly(Level world, Entity excluded, AABB box, Predicate predicate) { // if (predicate == RIDABLE_MINECARTS) { // // Not using MinecartEntity.class and no predicate, because mods may add another minecart that is type rideable without being MinecartEntity // //noinspection unchecked,rawtypes -// return (List) level.getEntitiesByClass(AbstractMinecart.class, box, (Entity e) -> e != excluded && ((AbstractMinecart) e).getMinecartType() == AbstractMinecart.Type.RIDEABLE); +// return (List) world.getEntitiesOfClass(AbstractMinecart.class, box, (Entity e) -> e != excluded && ((AbstractMinecart) e).getMinecartType() == AbstractMinecart.Type.RIDEABLE); // } // // return level.getEntities(excluded, box, predicate); diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/experimental/entity/block_caching/block_support/EntityMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/experimental/entity/block_caching/block_support/EntityMixin.java new file mode 100644 index 0000000..dec8f20 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/experimental/entity/block_caching/block_support/EntityMixin.java @@ -0,0 +1,63 @@ +package net.gensokyoreimagined.nitori.mixin.experimental.entity.block_caching.block_support; + +//import net.gensokyoreimagined.nitori.common.entity.block_tracking.BlockCache; +//import net.gensokyoreimagined.nitori.common.entity.block_tracking.BlockCacheProvider; +//import net.minecraft.world.entity.Entity; +//import net.minecraft.world.phys.Vec3; +//import org.spongepowered.asm.mixin.Mixin; +//import org.spongepowered.asm.mixin.injection.At; +//import org.spongepowered.asm.mixin.injection.Inject; +//import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +// +//@Mixin(Entity.class) +//public abstract class EntityMixin implements BlockCacheProvider { +// @Inject( +// method = "checkSupportingBlock", cancellable = true, +// at = @At( +// value = "INVOKE", shift = At.Shift.BEFORE, +// target = "Lnet/minecraft/world/entity/Entity;getBoundingBox()Lnet/minecraft/world/phys/AABB;" +// ) +// ) +// private void cancelIfSkippable(boolean onGround, Vec3 movement, CallbackInfo ci) { +// if (movement == null || (movement.x == 0 && movement.z == 0)) { +// //noinspection ConstantConditions +// BlockCache bc = this.getUpdatedBlockCache((Entity) (Object) this); +// if (bc.canSkipSupportingBlockSearch()) { +// ci.cancel(); +// } +// } +// } +// +// @Inject( +// method = "checkSupportingBlock", +// at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/world/entity/Entity;getBoundingBox()Lnet/minecraft/world/phys/AABB;") +// ) +// private void cacheSupportingBlockSearch(CallbackInfo ci) { +// BlockCache bc = this.lithium$getBlockCache(); +// if (bc.isTracking()) { +// bc.setCanSkipSupportingBlockSearch(true); +// } +// } +// +// @Inject( +// method = "checkSupportingBlock", +// at = @At(value = "INVOKE", ordinal = 1, target = "Lnet/minecraft/world/World;findSupportingBlockPos(Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/Box;)Ljava/util/Optional;") +// ) +// private void uncacheSupportingBlockSearch(CallbackInfo ci) { +// BlockCache bc = this.lithium$getBlockCache(); +// if (bc.isTracking()) { +// bc.setCanSkipSupportingBlockSearch(false); +// } +// } +// +// @Inject( +// method = "checkSupportingBlock", +// at = @At(value = "INVOKE", target = "Ljava/util/Optional;empty()Ljava/util/Optional;", remap = false) +// ) +// private void uncacheSupportingBlockSearch1(boolean onGround, Vec3 movement, CallbackInfo ci) { +// BlockCache bc = this.lithium$getBlockCache(); +// if (bc.isTracking()) { +// bc.setCanSkipSupportingBlockSearch(false); +// } +// } +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/logic/recipe_manager/RecipeManagerMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/logic/recipe_manager/RecipeManagerMixin.java new file mode 100644 index 0000000..151fa30 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/logic/recipe_manager/RecipeManagerMixin.java @@ -0,0 +1,29 @@ +package net.gensokyoreimagined.nitori.mixin.logic.recipe_manager; + +import net.minecraft.world.Container; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.item.crafting.RecipeType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +@Mixin(RecipeManager.class) +public abstract class RecipeManagerMixin { + + @Shadow protected abstract > Collection> byType(RecipeType type); + + /** + * @author QPCrummer & Leaf Patch #0023 + * @reason Optimize RecipeManager List Creation + */ + @Overwrite + public > List> getAllRecipesFor(RecipeType type) { + return new ArrayList<>(this.byType(type)); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/logic/reduce_ray_casting/BlockViewCastingMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/logic/reduce_ray_casting/BlockViewCastingMixin.java new file mode 100644 index 0000000..3188504 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/logic/reduce_ray_casting/BlockViewCastingMixin.java @@ -0,0 +1,56 @@ +package net.gensokyoreimagined.nitori.mixin.logic.reduce_ray_casting; + +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.ClipContext; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Credit to PaperMC Patch #0687 and #684 + */ +@Mixin(BlockGetter.class) +public interface BlockViewCastingMixin extends LevelHeightAccessor { + @Shadow BlockState getBlockState(BlockPos pos); + + @Shadow @Nullable BlockHitResult clipWithInteractionOverride(Vec3 start, Vec3 end, BlockPos pos, VoxelShape shape, BlockState state); + + @Shadow static T traverseBlocks(Vec3 start, Vec3 end, C context, BiFunction blockHitFactory, Function missFactory){return null;} + + /** + * @author QPCrummer + * @reason Optimize + */ + @Overwrite + default BlockHitResult clip(ClipContext context) { + return traverseBlocks(context.getFrom(), context.getTo(), context, (innerContext, pos) -> { + BlockState blockState = this.getBlockState(pos); + if (blockState.isAir()) return null; + FluidState fluidState = blockState.getFluidState(); + Vec3 Vec3 = innerContext.getFrom(); + Vec3 vec3d2 = innerContext.getTo(); + VoxelShape voxelShape = innerContext.getBlockShape(blockState, (BlockGetter)(Object)this, pos); + BlockHitResult blockHitResult = this.clipWithInteractionOverride(Vec3, vec3d2, pos, voxelShape, blockState); + VoxelShape voxelShape2 = innerContext.getFluidShape(fluidState, (BlockGetter)(Object)this, pos); + BlockHitResult blockHitResult2 = voxelShape2.clip(Vec3, vec3d2, pos); + double d = blockHitResult == null ? Double.MAX_VALUE : innerContext.getFrom().distanceToSqr(blockHitResult.getLocation()); + double e = blockHitResult2 == null ? Double.MAX_VALUE : innerContext.getTo().distanceToSqr(blockHitResult2.getLocation()); + return d <= e ? blockHitResult : blockHitResult2; + }, (innerContext) -> { + Vec3 Vec3 = innerContext.getFrom().subtract(innerContext.getTo()); + return BlockHitResult.miss(innerContext.getTo(), Direction.getNearest(Vec3.x, Vec3.y, Vec3.z), BlockPos.containing(innerContext.getTo())); + }); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/RandomEntityMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/RandomEntityMixin.java new file mode 100644 index 0000000..5956396 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/RandomEntityMixin.java @@ -0,0 +1,15 @@ +package net.gensokyoreimagined.nitori.mixin.math.random; + +//import net.gensokyoreimagined.nitori.common.math.random.RandomGeneratorRandom; +//import net.minecraft.world.entity.Entity; +//import net.minecraft.util.RandomSource; +//import org.spongepowered.asm.mixin.Final; +//import org.spongepowered.asm.mixin.Mixin; +//import org.spongepowered.asm.mixin.Mutable; +//import org.spongepowered.asm.mixin.Shadow; +// +//@Mixin(Entity.class) +//public abstract class RandomEntityMixin { +// @Shadow @Mutable @Final protected RandomSource random = new RandomGeneratorRandom(); +// +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/creation/QueryResponseHandlerMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/creation/QueryResponseHandlerMixin.java new file mode 100644 index 0000000..92f26fc --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/creation/QueryResponseHandlerMixin.java @@ -0,0 +1,18 @@ +package net.gensokyoreimagined.nitori.mixin.math.random.creation; + +//import net.gensokyoreimagined.nitori.mixin.math.random.math.GetRandomInterface; +//import net.minecraft.server.rcon.thread.QueryThreadGs4; +//import net.minecraft.server.rcon.thread.QueryThreadGs4.*; +//import net.minecraft.util.RandomSource; +//import org.spongepowered.asm.mixin.Mixin; +//import org.spongepowered.asm.mixin.injection.At; +//import org.spongepowered.asm.mixin.injection.Redirect; +// +//// Credit to Mirai patch #0015 +//@Mixin(QueryThreadGs4.RequestChallenge.class) +//public class QueryResponseHandlerMixin { +// @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/random/Random;create()Lnet/minecraft/util/math/random/Random;")) +// private RandomSource redirectRandomCreation() { +// return GetRandomInterface.getRandom(); +// } +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/creation/ServerPlayerEntityRandomMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/creation/ServerPlayerEntityRandomMixin.java new file mode 100644 index 0000000..9a98b2d --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/creation/ServerPlayerEntityRandomMixin.java @@ -0,0 +1,20 @@ +package net.gensokyoreimagined.nitori.mixin.math.random.creation; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(ServerPlayer.class) +public abstract class ServerPlayerEntityRandomMixin { + + @Shadow public abstract ServerLevel serverLevel(); + + @Redirect(method = "fudgeSpawnLocation", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/RandomSource;create()Lnet/minecraft/util/RandomSource;")) + private RandomSource redirectCreatedRandom() { + return serverLevel().random; + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/math/GetRandomInterface.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/math/GetRandomInterface.java new file mode 100644 index 0000000..a5bdfe3 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/math/GetRandomInterface.java @@ -0,0 +1,14 @@ +package net.gensokyoreimagined.nitori.mixin.math.random.math; + +//import net.minecraft.util.Mth; +//import net.minecraft.util.RandomSource; +//import org.spongepowered.asm.mixin.Mixin; +//import org.spongepowered.asm.mixin.gen.Accessor; +// +//@Mixin(Mth.class) +//public interface GetRandomInterface { +// @Accessor("RANDOM") +// public static RandomSource getRandom() { +// throw new AssertionError(); +// } +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/math/rounding/FastRoundingPlayerMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/rounding/FastRoundingPlayerMixin.java new file mode 100644 index 0000000..3a9434c --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/rounding/FastRoundingPlayerMixin.java @@ -0,0 +1,20 @@ +package net.gensokyoreimagined.nitori.mixin.math.rounding; + + +import net.gensokyoreimagined.nitori.common.math.FasterMathUtil; +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(Player.class) +public class FastRoundingPlayerMixin { + + @Redirect( + method = "causeFallDamage", + require = 0, + at = @At(value = "INVOKE", target = "Ljava/lang/Math;round(D)J")) + private long fasterRoundFall(double value) { + return FasterMathUtil.round(value); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/math/rounding/FastRoundingServerPlayerMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/rounding/FastRoundingServerPlayerMixin.java new file mode 100644 index 0000000..6df9eea --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/rounding/FastRoundingServerPlayerMixin.java @@ -0,0 +1,18 @@ +package net.gensokyoreimagined.nitori.mixin.math.rounding; + +import net.gensokyoreimagined.nitori.common.math.FasterMathUtil; +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(ServerPlayer.class) +public class FastRoundingServerPlayerMixin { + @Redirect( + method = "checkMovementStatistics", + require = 0, + at = @At(value = "INVOKE", target = "Ljava/lang/Math;round(D)J")) + private long fasterRound(double value) { + return FasterMathUtil.round(value); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/math/rounding/FastRoundingSurfaceBuildMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/rounding/FastRoundingSurfaceBuildMixin.java new file mode 100644 index 0000000..cc5b228 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/rounding/FastRoundingSurfaceBuildMixin.java @@ -0,0 +1,18 @@ +package net.gensokyoreimagined.nitori.mixin.math.rounding; + +import net.gensokyoreimagined.nitori.common.math.FasterMathUtil; +import net.minecraft.world.level.levelgen.SurfaceSystem; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(SurfaceSystem.class) +public class FastRoundingSurfaceBuildMixin { + @Redirect( + method = "getBand", + require = 0, + at = @At(value = "INVOKE", target = "Ljava/lang/Math;round(D)J")) + private long fasterRound(double value) { + return FasterMathUtil.round(value); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/inline_height/WorldChunkMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/inline_height/WorldChunkMixin.java index 9f887dc..0c78040 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/inline_height/WorldChunkMixin.java +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/inline_height/WorldChunkMixin.java @@ -1,62 +1,62 @@ package net.gensokyoreimagined.nitori.mixin.needs_testing.inline_height; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.LevelHeightAccessor; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.chunk.LevelChunk; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -@Mixin(LevelChunk.class) -public abstract class WorldChunkMixin implements LevelHeightAccessor { - - @Shadow - @Final - Level level; - - @Override - public int getMaxBuildHeight() { - return this.level.getMaxBuildHeight(); - } - - @Override - public int getSectionsCount() { - return this.level.getSectionsCount(); - } - - @Override - public int getMinSection() { - return this.level.getMinSection(); - } - - @Override - public int getMaxSection() { - return this.level.getMaxSection(); - } - - @Override - public boolean isOutsideBuildHeight(BlockPos pos) { - return this.level.isOutsideBuildHeight(pos); - } - - @Override - public boolean isOutsideBuildHeight(int y) { - return this.level.isOutsideBuildHeight(y); - } - - @Override - public int getSectionIndex(int y) { - return this.level.getSectionIndex(y); - } - - @Override - public int getSectionIndexFromSectionY(int coord) { - return this.level.getSectionIndexFromSectionY(coord); - } - - @Override - public int getSectionYFromSectionIndex(int index) { - return this.level.getSectionYFromSectionIndex(index); - } -} \ No newline at end of file +//import net.minecraft.core.BlockPos; +//import net.minecraft.world.level.LevelHeightAccessor; +//import net.minecraft.world.level.Level; +//import net.minecraft.world.level.chunk.LevelChunk; +//import org.spongepowered.asm.mixin.Final; +//import org.spongepowered.asm.mixin.Mixin; +//import org.spongepowered.asm.mixin.Shadow; +// +//@Mixin(LevelChunk.class) +//public abstract class WorldChunkMixin implements LevelHeightAccessor { +// +// @Shadow +// @Final +// public Level level; +// +// @Override +// public int getMaxBuildHeight() { +// return this.level.getMaxBuildHeight(); +// } +// +// @Override +// public int getSectionsCount() { +// return this.level.getSectionsCount(); +// } +// +// @Override +// public int getMinSection() { +// return this.level.getMinSection(); +// } +// +// @Override +// public int getMaxSection() { +// return this.level.getMaxSection(); +// } +// +// @Override +// public boolean isOutsideBuildHeight(BlockPos pos) { +// return this.level.isOutsideBuildHeight(pos); +// } +// +// @Override +// public boolean isOutsideBuildHeight(int y) { +// return this.level.isOutsideBuildHeight(y); +// } +// +// @Override +// public int getSectionIndex(int y) { +// return this.level.getSectionIndex(y); +// } +// +// @Override +// public int getSectionIndexFromSectionY(int coord) { +// return this.level.getSectionIndexFromSectionY(coord); +// } +// +// @Override +// public int getSectionYFromSectionIndex(int index) { +// return this.level.getSectionYFromSectionIndex(index); +// } +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/inline_height/WorldMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/inline_height/WorldMixin.java index c748344..e235d79 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/inline_height/WorldMixin.java +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/inline_height/WorldMixin.java @@ -32,9 +32,10 @@ // method = "", // at = @At("RETURN") // ) -// private void initHeightCache(WritableLevelData properties, ResourceKey registryRef, RegistryAccess registryManager, Holder dimensionEntry, Supplier profiler, boolean isClient, boolean debugWorld, long biomeAccess, int maxChainedNeighborUpdates, CallbackInfo ci) { -// this.nitori$getHeight = dimensionEntry.value().height(); -// this.nitori$getMinBuildHeight = dimensionEntry.value().minY(); +// private void initHeightCache(WritableLevelData worldData, ResourceKey registryKey, RegistryAccess registryAccess, Holder dimensionTypeRegistration, Supplier profiler, boolean isClientSide, boolean debug, long biomeManager, int maxChainedNeighborUpdates, CallbackInfo ci) +// { +// this.nitori$getHeight = dimensionTypeRegistration.value().height(); +// this.nitori$getMinBuildHeight = dimensionTypeRegistration.value().minY(); // this.nitori$getSectionsCount = this.nitori$getMinBuildHeight + this.nitori$getHeight - 1; // } // diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_entity_retrieval/WorldMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_entity_retrieval/WorldMixin.java new file mode 100644 index 0000000..08e0030 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_entity_retrieval/WorldMixin.java @@ -0,0 +1,46 @@ +package net.gensokyoreimagined.nitori.mixin.util.block_entity_retrieval; + +import net.gensokyoreimagined.nitori.common.world.blockentity.BlockEntityGetter; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunk; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(Level.class) +public abstract class WorldMixin implements BlockEntityGetter, LevelAccessor { + @Shadow + @Final + public boolean isClientSide; + + @Shadow + @Final + private Thread thread; + + @Shadow + public abstract LevelChunk getChunk(int i, int j); + + @Shadow + @Nullable + public abstract ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create); + + @Override + public BlockEntity lithium$getLoadedExistingBlockEntity(BlockPos pos) { + if (!this.isOutsideBuildHeight(pos)) { + if (this.isClientSide || Thread.currentThread() == this.thread) { + ChunkAccess chunk = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false); + if (chunk != null) { + return chunk.getBlockEntity(pos); + } + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_tracking/block_listening/WorldMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_tracking/block_listening/WorldMixin.java new file mode 100644 index 0000000..25eaddb --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_tracking/block_listening/WorldMixin.java @@ -0,0 +1,22 @@ +package net.gensokyoreimagined.nitori.mixin.util.block_tracking.block_listening; + +import net.gensokyoreimagined.nitori.common.entity.block_tracking.SectionedBlockChangeTracker; +import net.gensokyoreimagined.nitori.common.util.deduplication.LithiumInterner; +import net.gensokyoreimagined.nitori.common.util.deduplication.LithiumInternerWrapper; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Level.class) +public class WorldMixin implements LithiumInternerWrapper { + private final LithiumInterner blockChangeTrackers = new LithiumInterner<>(); + + @Override + public SectionedBlockChangeTracker lithium$getCanonical(SectionedBlockChangeTracker value) { + return this.blockChangeTrackers.getCanonical(value); + } + + @Override + public void lithium$deleteCanonical(SectionedBlockChangeTracker value) { + this.blockChangeTrackers.deleteCanonical(value); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/accessors/MixinMinecraftServer.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/accessors/MixinMinecraftServer.java index f367e2c..064fd11 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/accessors/MixinMinecraftServer.java +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/accessors/MixinMinecraftServer.java @@ -1,35 +1,35 @@ package net.gensokyoreimagined.nitori.mixin.virtual_thread.accessors; -import com.llamalad7.mixinextras.sugar.Local; -import net.gensokyoreimagined.nitori.executor.thread.OriginalServerThread; -import net.gensokyoreimagined.nitori.executor.wrapper.MinecraftServerWrapper; -import net.minecraft.server.MinecraftServer; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MinecraftServer.class) -public interface MixinMinecraftServer { - @Inject(method = "", at = @At("RETURN")) - private void onConstructed(CallbackInfo ci, @Local(argsOnly = true) Thread thread) { - MinecraftServerWrapper.SERVER = (MinecraftServer) this; - MinecraftServerWrapper.isConstructed = true; - - if (thread instanceof OriginalServerThread) { - MinecraftServerWrapper.serverThread = (OriginalServerThread) thread; - return; - } - - throw new AssertionError("Type of serverThread is not OriginalServerThread!"); - } - - @Accessor("hasStopped") - boolean hasStopped(); - - @Accessor("shutdownThread") - Thread shutdownThread(); - - @Accessor("") -} +//import com.llamalad7.mixinextras.sugar.Local; +//import net.gensokyoreimagined.nitori.executor.thread.OriginalServerThread; +//import net.gensokyoreimagined.nitori.executor.wrapper.MinecraftServerWrapper; +//import net.minecraft.server.MinecraftServer; +//import org.spongepowered.asm.mixin.Mixin; +//import org.spongepowered.asm.mixin.gen.Accessor; +//import org.spongepowered.asm.mixin.injection.At; +//import org.spongepowered.asm.mixin.injection.Inject; +//import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +// +//@Mixin(MinecraftServer.class) +//public interface MixinMinecraftServer { +// @Inject(method = "", at = @At("RETURN")) +// private void onConstructed(CallbackInfo ci, @Local(argsOnly = true) Thread thread) { +// MinecraftServerWrapper.SERVER = (MinecraftServer) this; +// MinecraftServerWrapper.isConstructed = true; +// +// if (thread instanceof OriginalServerThread) { +// MinecraftServerWrapper.serverThread = (OriginalServerThread) thread; +// return; +// } +// +// throw new AssertionError("Type of serverThread is not OriginalServerThread!"); +// } +// +// @Accessor("hasStopped") +// boolean hasStopped(); +// +// @Accessor("shutdownThread") +// Thread shutdownThread(); +// +// @Accessor("") +//} diff --git a/src/main/resources/mixins.core.json b/src/main/resources/mixins.core.json index 4dacd0c..6a1940a 100755 --- a/src/main/resources/mixins.core.json +++ b/src/main/resources/mixins.core.json @@ -25,6 +25,18 @@ "MixinServerEntity", "MixinSpongeSIMD", "MixinWorldGenRegion", + "ai.sensor.secondary_poi.SecondaryPointsOfInterestSensorMixin", + "ai.pathing.PathContextAccessor", + "ai.poi.PointOfInterestSetMixin", + "ai.poi.PointOfInterestTypesMixin", + "ai.task.replace_streams.WeightedListMixin", + "ai.task.launch.BrainMixin", + "ai.task.memory_change_counting.BrainMixin", + "ai.task.memory_change_counting.MultiTickTaskMixin", + "ai.pathing.AbstractBlockStateMixin", + "ai.pathing.PathContextMixin", + "ai.pathing.ChunkCacheMixin", + "ai.pathing.TargetPredicateMixin", "alloc.blockstate.StateMixin", "alloc.chunk_ticking.ServerChunkManagerMixin", "alloc.composter.ComposterMixin$ComposterBlockComposterInventoryMixin", @@ -42,6 +54,7 @@ "collections.mob_spawning.SpawnSettingsMixin", "collections.fluid_submersion.EntityMixin", "collections.brain.BrainMixin", + "chunk.entity_class_groups.TypeFilterableListMixin", "entity.fast_hand_swing.LivingEntityMixin", "entity.fast_retrieval.SectionedEntityCacheMixin", "entity.fall_damage.NoFallDamageMixin", @@ -52,8 +65,13 @@ "entity.replace_entitytype_predicates.AbstractMinecartEntityMixin", "entity.replace_entitytype_predicates.AbstractDecorationEntityMixin", "entity.replace_entitytype_predicates.ItemFrameEntityMixin", + "entity.pathfinding.TargetPredicateMixin", + "entity.inactive_navigations.EntityNavigationMixin", + "entity.inactive_navigations.MobEntityMixin", "logic.fast_bits_blockpos.OptimizedBlockPosBitsMixin", "logic.fast_rotations.EulerAngleMixin", + "logic.reduce_ray_casting.BlockViewCastingMixin", + "logic.recipe_manager.RecipeManagerMixin", "math.fast_blockops.BlockPosMixin", "math.fast_blockops.DirectionMixin", "math.fast_util.AxisCycleDirectionMixin$BackwardMixin", @@ -64,9 +82,13 @@ "math.intrinsic.MathHelperIntrinsicMixin", "math.joml.JOMLMixin", "math.rounding.FastRoundingVoxShapeMixin", + "math.rounding.FastRoundingPlayerMixin", + "math.rounding.FastRoundingServerPlayerMixin", + "math.rounding.FastRoundingSurfaceBuildMixin", "math.sine_lut.MixinMth", "math.vec.FastMathVec3DMixin", "math.random.RandomMixin", + "math.random.creation.ServerPlayerEntityRandomMixin", "network.microopt.VarIntsMixin", "network.microopt.StringEncodingMixin", "network.block_breaking.CacheBlockBreakPacketMixin", @@ -85,6 +107,8 @@ "util.accessors.EntityTrackingSectionAccessor", "util.accessors.ServerEntityManagerAccessor", "util.block_tracking.AbstractBlockStateMixin", + "util.block_tracking.block_listening.WorldMixin", + "util.block_entity_retrieval.WorldMixin", "world.block_entity_ticking.sleeping.WrappedBlockEntityTickInvokerAccessor", "world.block_entity_ticking.sleeping.campfire.CampfireBlockEntityMixin", "world.block_entity_ticking.sleeping.campfire.lit.CampfireBlockEntityMixin", @@ -96,8 +120,6 @@ "world.portal_checks.DisablePortalChecksMixin", "world.blending.BlendMixin", "world.farmland.FarmlandBlockMixin", - "world.biome_access.BiomeAccessMixin", - "virtual_thread.accessors.MixinMinecraftServer", - "virtual_thread.accessors.MixinWatchdogThread" + "world.biome_access.BiomeAccessMixin" ] }