52 changed files
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
package net.gensokyoreimagined.nitori.common.ai;
|
||||
|
||||
public interface MemoryModificationCounter {
|
||||
|
||||
long lithium$getModCount();
|
||||
}
|
||||
@@ -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<U> extends Iterable<U> {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
Iterator<U> 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 <T> Iterable<? extends T> cast(ShufflingList<T> list) {
|
||||
return ((WeightedListIterable<T>) list);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper type for an iterator over the entries of a {@link ShufflingList} which de-references the contained
|
||||
* values for consumers.
|
||||
*
|
||||
* @param <U> The value type stored in each list entry
|
||||
*/
|
||||
class ListIterator<U> implements Iterator<U> {
|
||||
private final Iterator<ShufflingList.WeightedEntry<? extends U>> inner;
|
||||
|
||||
public ListIterator(Iterator<ShufflingList.WeightedEntry<? extends U>> inner) {
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.inner.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public U next() {
|
||||
return this.inner.next().getData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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<E> extends AbstractList<E> {
|
||||
private final ObjectArrayList<E> allElements;
|
||||
private final BitSet visibleMask;
|
||||
private final Object2IntOpenHashMap<E> element2Index;
|
||||
private final boolean defaultVisibility;
|
||||
private int numCleared;
|
||||
|
||||
public MaskedList(ObjectArrayList<E> 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<E> 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<E> spliterator() {
|
||||
return new Spliterators.AbstractSpliterator<E>(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL) {
|
||||
int nextIndex = 0;
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(Consumer<? super E> 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<E> 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.gensokyoreimagined.nitori.common.util.deduplication;
|
||||
|
||||
public interface LithiumInternerWrapper<T> {
|
||||
|
||||
T lithium$getCanonical(T value);
|
||||
|
||||
void lithium$deleteCanonical(T value);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB box) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<VoxelShape> getCollisions(@Nullable Entity entity, AABB box) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<VoxelShape> getBlockCollisions(@Nullable Entity entity, AABB box) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean collidesWithSuffocatingBlock(@Nullable Entity entity, AABB box) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Vec3> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Holder<PoiType>> type, PoiManager.Occupancy status,
|
||||
Consumer<PoiRecord> consumer);
|
||||
}
|
||||
@@ -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<PoiType> type) implements Predicate<Holder<PoiType>> {
|
||||
|
||||
@Override
|
||||
public boolean test(Holder<PoiType> other) {
|
||||
return this.type == other;
|
||||
}
|
||||
|
||||
public Holder<PoiType> getType() {
|
||||
return this.type;
|
||||
}
|
||||
}
|
||||
@@ -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<BlockState> POI_BLOCKSTATE_PREDICATE;
|
||||
|
||||
|
||||
public static void init(Set<BlockState> 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);
|
||||
}
|
||||
}
|
||||
@@ -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}.
|
||||
* <br>
|
||||
* This annotation can also be used on fields or classes, similar to {@link ThreadRestricted}.
|
||||
* <br>
|
||||
* In a method annotated with {@link AssistThreadOnly}, fields and methods annotated with
|
||||
* {@link AssistThreadOnly}, {@link BaseThreadOnly} or {@link AnyThreadSafe} may be used.
|
||||
* <br>
|
||||
* 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;
|
||||
|
||||
}
|
||||
//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}.
|
||||
// * <br>
|
||||
// * This annotation can also be used on fields or classes, similar to {@link ThreadRestricted}.
|
||||
// * <br>
|
||||
// * In a method annotated with {@link AssistThreadOnly}, fields and methods annotated with
|
||||
// * {@link AssistThreadOnly}, {@link BaseThreadOnly} or {@link AnyThreadSafe} may be used.
|
||||
// * <br>
|
||||
// * 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;
|
||||
//
|
||||
//}
|
||||
@@ -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.
|
||||
* <br>
|
||||
* 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}.
|
||||
* <br>
|
||||
* 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.
|
||||
* <br>
|
||||
* 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.
|
||||
* <br>
|
||||
* 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.
|
||||
// * <br>
|
||||
// * 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}.
|
||||
// * <br>
|
||||
// * 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.
|
||||
// * <br>
|
||||
// * 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.
|
||||
// * <br>
|
||||
// * 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]);
|
||||
//
|
||||
//}
|
||||
|
||||
@@ -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();
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
@@ -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;
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
@@ -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}.
|
||||
* <br>
|
||||
* This pool intends to keep {@link #targetParallelism} threads active at any time,
|
||||
* which includes a potentially active {@link ServerThread}.
|
||||
* <br>
|
||||
* 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.
|
||||
* <br><br>
|
||||
* 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}.
|
||||
* <br>
|
||||
* This value is always positive.
|
||||
* <br>
|
||||
* The value is currently automatically determined according to the following table:
|
||||
* <table>
|
||||
* <tr><th>system threads</th><th>threads spared</th></tr>
|
||||
* <tr><td>≤ 3</td><td>0</td></tr>
|
||||
* <tr><td>[4, 14]</td><td>1</td></tr>
|
||||
* <tr><td>[15, 23]</td><td>2</td></tr>
|
||||
* <tr><td>[24, 37]</td><td>3</td></tr>
|
||||
* <tr><td>[38, 54]</td><td>4</td></tr>
|
||||
* <tr><td>[55, 74]</td><td>5</td></tr>
|
||||
* <tr><td>[75, 99]</td><td>6</td></tr>
|
||||
* <tr><td>[100, 127]</td><td>7</td></tr>
|
||||
* <tr><td>[128, 158]</td><td>8</td></tr>
|
||||
* <tr><td>[159, 193]</td><td>9</td></tr>
|
||||
* <tr><td>[194, 232]</td><td>10</td></tr>
|
||||
* <tr><td>[233, 274]</td><td>11</td></tr>
|
||||
* <tr><td>≥ 275</td><td>12</td></tr>
|
||||
* </table>
|
||||
* Then <code>target parallelism = system threads - threads spared</code>.
|
||||
* <br>
|
||||
* 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.
|
||||
* <br>
|
||||
* This value is always nonnegative.
|
||||
* <br>
|
||||
* This value is currently automatically determined according to the following rule:
|
||||
* <ul>
|
||||
* <li>0, if {@link #targetParallelism} = 1</li>
|
||||
* <li>{@code max(1, floor(2/5 * }{@link #targetParallelism}{@code ))}</li>
|
||||
* </ul>
|
||||
* 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}.
|
||||
* <br>
|
||||
* 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}.
|
||||
* <br>
|
||||
* 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.
|
||||
* <br>
|
||||
* 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.
|
||||
* <br>
|
||||
* Note that the {@link ServerThread} at index 0 may be null if {@link MinecraftServer#isConstructed} is false.
|
||||
* <br>
|
||||
* 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];
|
||||
}
|
||||
|
||||
}
|
||||
///**
|
||||
// * A pool of threads that can perform tasks to assist the current {@link ServerThread}. These tasks can be of
|
||||
// * different {@linkplain BaseTaskQueueTier tiers}.
|
||||
// * <br>
|
||||
// * This pool intends to keep {@link #targetParallelism} threads active at any time,
|
||||
// * which includes a potentially active {@link ServerThread}.
|
||||
// * <br>
|
||||
// * 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.
|
||||
// * <br><br>
|
||||
// * 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}.
|
||||
// * <br>
|
||||
// * This value is always positive.
|
||||
// * <br>
|
||||
// * The value is currently automatically determined according to the following table:
|
||||
// * <table>
|
||||
// * <tr><th>system threads</th><th>threads spared</th></tr>
|
||||
// * <tr><td>≤ 3</td><td>0</td></tr>
|
||||
// * <tr><td>[4, 14]</td><td>1</td></tr>
|
||||
// * <tr><td>[15, 23]</td><td>2</td></tr>
|
||||
// * <tr><td>[24, 37]</td><td>3</td></tr>
|
||||
// * <tr><td>[38, 54]</td><td>4</td></tr>
|
||||
// * <tr><td>[55, 74]</td><td>5</td></tr>
|
||||
// * <tr><td>[75, 99]</td><td>6</td></tr>
|
||||
// * <tr><td>[100, 127]</td><td>7</td></tr>
|
||||
// * <tr><td>[128, 158]</td><td>8</td></tr>
|
||||
// * <tr><td>[159, 193]</td><td>9</td></tr>
|
||||
// * <tr><td>[194, 232]</td><td>10</td></tr>
|
||||
// * <tr><td>[233, 274]</td><td>11</td></tr>
|
||||
// * <tr><td>≥ 275</td><td>12</td></tr>
|
||||
// * </table>
|
||||
// * Then <code>target parallelism = system threads - threads spared</code>.
|
||||
// * <br>
|
||||
// * 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.
|
||||
// * <br>
|
||||
// * This value is always nonnegative.
|
||||
// * <br>
|
||||
// * This value is currently automatically determined according to the following rule:
|
||||
// * <ul>
|
||||
// * <li>0, if {@link #targetParallelism} = 1</li>
|
||||
// * <li>{@code max(1, floor(2/5 * }{@link #targetParallelism}{@code ))}</li>
|
||||
// * </ul>
|
||||
// * 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}.
|
||||
// * <br>
|
||||
// * 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}.
|
||||
// * <br>
|
||||
// * 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.
|
||||
// * <br>
|
||||
// * 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.
|
||||
// * <br>
|
||||
// * Note that the {@link ServerThread} at index 0 may be null if {@link MinecraftServer#isConstructed} is false.
|
||||
// * <br>
|
||||
// * 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];
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
//}
|
||||
@@ -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 = "<init>", 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();
|
||||
}
|
||||
}
|
||||
@@ -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<PathType> 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.
|
||||
// * <p>
|
||||
// * 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<PathType> 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);
|
||||
// }
|
||||
//}
|
||||
@@ -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 = "<init>",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Mob;level()Lnet/minecraft/world/level/Level;")
|
||||
)
|
||||
private Level getWorldIfNonNull(Mob instance, Operation<Level> original) {
|
||||
if (instance == null) {
|
||||
return null;
|
||||
}
|
||||
return original.call(instance);
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
method = "<init>",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Mob;blockPosition()Lnet/minecraft/core/BlockPos;")
|
||||
)
|
||||
private BlockPos getBlockPosIfNonNull(Mob instance, Operation<BlockPos> original) {
|
||||
if (instance == null) {
|
||||
return null;
|
||||
}
|
||||
return original.call(instance);
|
||||
}
|
||||
}
|
||||
@@ -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<Boolean> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<Holder<PoiType>, Set<PoiRecord>> byType;
|
||||
|
||||
private static <K, V> Iterable<? extends Map.Entry<K, V>> getPointsByTypeIterator(Map<K, V> map) {
|
||||
if (map instanceof Reference2ReferenceMap) {
|
||||
return Reference2ReferenceMaps.fastIterable((Reference2ReferenceMap<K, V>) map);
|
||||
} else {
|
||||
return map.entrySet();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "<init>(Ljava/lang/Runnable;ZLjava/util/List;)V", at = @At("RETURN"))
|
||||
private void reinit(Runnable updateListener, boolean bl, List<PoiRecord> list, CallbackInfo ci) {
|
||||
this.byType = new Reference2ReferenceOpenHashMap<>(this.byType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lithium$collectMatchingPoints(Predicate<Holder<PoiType>> type, PoiManager.Occupancy status, Consumer<PoiRecord> consumer) {
|
||||
if (type instanceof SinglePointOfInterestTypeFilter) {
|
||||
this.getWithSingleTypeFilter(((SinglePointOfInterestTypeFilter) type).getType(), status, consumer);
|
||||
} else {
|
||||
this.getWithDynamicTypeFilter(type, status, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
private void getWithDynamicTypeFilter(Predicate<Holder<PoiType>> type, PoiManager.Occupancy status, Consumer<PoiRecord> consumer) {
|
||||
for (Map.Entry<Holder<PoiType>, Set<PoiRecord>> 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<PoiType> type, PoiManager.Occupancy status, Consumer<PoiRecord> consumer) {
|
||||
Set<PoiRecord> 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, V> K computeIfAbsent(Map<K, V> map, K key, Function<? super K, ? extends V> mappingFunction) {
|
||||
return (K) map.computeIfAbsent(key, o -> (V) new ObjectOpenHashSet<>());
|
||||
}
|
||||
}
|
||||
@@ -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<BlockState, PoiType> TYPE_BY_STATE;
|
||||
|
||||
static {
|
||||
TYPE_BY_STATE = new Reference2ReferenceOpenHashMap<>(TYPE_BY_STATE);
|
||||
|
||||
PointOfInterestTypeHelper.init(TYPE_BY_STATE.keySet());
|
||||
}
|
||||
}
|
||||
@@ -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<E extends LivingEntity> {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<Integer, Map<Activity, Set<BehaviorControl<? super E>>>> availableBehaviorsByPriority;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Set<Activity> activeActivities;
|
||||
|
||||
private ArrayList<BehaviorControl<? super E>> nitori$possibleTasks;
|
||||
private MaskedList<BehaviorControl<? super E>> 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<Activity, Set<BehaviorControl<? super E>>> map : this.availableBehaviorsByPriority.values()) {
|
||||
for (Map.Entry<Activity, Set<BehaviorControl<? super E>>> entry : map.entrySet()) {
|
||||
Activity activity = entry.getKey();
|
||||
if (!this.activeActivities.contains(activity)) {
|
||||
continue;
|
||||
}
|
||||
Set<BehaviorControl<? super E>> set = entry.getValue();
|
||||
for (BehaviorControl<? super E> BehaviorControl : set) {
|
||||
//noinspection UseBulkOperation
|
||||
this.nitori$possibleTasks.add(BehaviorControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<BehaviorControl<? super E>> nitori$getPossibleTasks() {
|
||||
if (this.nitori$possibleTasks == null) {
|
||||
this.nitori$initPossibleTasks();
|
||||
}
|
||||
return this.nitori$possibleTasks;
|
||||
}
|
||||
|
||||
private MaskedList<BehaviorControl<? super E>> nitori$getCurrentlyRunningTasks() {
|
||||
if (this.nitori$runningTasks == null) {
|
||||
this.nitori$initCurrentlyRunningTasks();
|
||||
}
|
||||
return this.nitori$runningTasks;
|
||||
}
|
||||
|
||||
private void nitori$initCurrentlyRunningTasks() {
|
||||
MaskedList<BehaviorControl<? super E>> list = new MaskedList<>(new ObjectArrayList<>(), false);
|
||||
|
||||
for (Map<Activity, Set<BehaviorControl<? super E>>> map : this.availableBehaviorsByPriority.values()) {
|
||||
for (Set<BehaviorControl<? super E>> set : map.values()) {
|
||||
for (BehaviorControl<? super E> 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<? super E> 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<BehaviorControl<? super E>> getRunningBehaviors() {
|
||||
return this.nitori$getCurrentlyRunningTasks();
|
||||
}
|
||||
|
||||
|
||||
@Inject(
|
||||
method = "<init>(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<? extends BehaviorControl<? super E>> 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<? super E> 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<? super E> 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<? super E> addStartedTasks(BehaviorControl<? super E> BehaviorControl) {
|
||||
if (this.nitori$runningTasks != null && BehaviorControl.getStatus() == Behavior.Status.RUNNING) {
|
||||
this.nitori$runningTasks.setVisible(BehaviorControl, true);
|
||||
}
|
||||
return BehaviorControl;
|
||||
}
|
||||
}
|
||||
@@ -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<Object, Object> 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<Brain<?>> cir) {
|
||||
Brain<?> newBrain = cir.getReturnValue();
|
||||
((BrainMixin) (Object) newBrain).nitori$memoryModCount = this.nitori$memoryModCount + 1;
|
||||
}
|
||||
}
|
||||
@@ -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<E extends LivingEntity> {
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
protected Map<MemoryModuleType<?>, MemoryStatus> entryCondition;
|
||||
|
||||
@Unique
|
||||
private long nitori$cachedMemoryModCount = -1;
|
||||
@Unique
|
||||
private boolean nitori$cachedHasRequiredMemoryState;
|
||||
|
||||
@Inject(method = "<init>(Ljava/util/Map;II)V", at = @At("RETURN"))
|
||||
private void init(Map<MemoryModuleType<?>, 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<Reference2ObjectMap.Entry<MemoryModuleType<?>, MemoryStatus>> fastIterator = ((Reference2ObjectOpenHashMap<MemoryModuleType<?>, MemoryStatus>) this.entryCondition).reference2ObjectEntrySet().fastIterator();
|
||||
while (fastIterator.hasNext()) {
|
||||
Reference2ObjectMap.Entry<MemoryModuleType<?>, MemoryStatus> entry = fastIterator.next();
|
||||
if (!brain.checkMemory(entry.getKey(), entry.getValue())) {
|
||||
return this.nitori$cachedHasRequiredMemoryState = false;
|
||||
}
|
||||
}
|
||||
|
||||
return this.nitori$cachedHasRequiredMemoryState = true;
|
||||
}
|
||||
}
|
||||
@@ -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<U> implements WeightedListIterable<U> {
|
||||
@Shadow
|
||||
@Final
|
||||
protected List<ShufflingList.WeightedEntry<? extends U>> entries;
|
||||
|
||||
@Override
|
||||
public Iterator<U> iterator() {
|
||||
return new WeightedListIterable.ListIterator<>(this.entries.iterator());
|
||||
}
|
||||
}
|
||||
@@ -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<T> implements ClassGroupFilterableList<T> {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private List<T> allInstances;
|
||||
|
||||
private final Reference2ReferenceArrayMap<EntityClassGroup, ReferenceLinkedOpenHashSet<T>> 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<EntityClassGroup, ReferenceLinkedOpenHashSet<T>> 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<T> entitySet : this.nitori$entitiesByGroup.values()) {
|
||||
//noinspection SuspiciousMethodCalls
|
||||
entitySet.remove(o);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entities of a class group
|
||||
*/
|
||||
public Collection<T> lithium$getAllOfGroupType(EntityClassGroup type) {
|
||||
Collection<T> 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<T> nitori$createAllOfGroupType(EntityClassGroup type) {
|
||||
ReferenceLinkedOpenHashSet<T> allOfType = new ReferenceLinkedOpenHashSet<>();
|
||||
|
||||
for (T entity : this.allInstances) {
|
||||
if (type.contains(entity.getClass())) {
|
||||
allOfType.add(entity);
|
||||
}
|
||||
}
|
||||
this.nitori$entitiesByGroup.put(type, allOfType);
|
||||
|
||||
return allOfType;
|
||||
}
|
||||
}
|
||||
@@ -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<Boolean> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Boolean> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 = "<init>", 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<Mob> 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<Mob> 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);
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -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.
|
||||
// * <p>
|
||||
// * 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<Mob> navigatingMobs;
|
||||
//
|
||||
// private ReferenceOpenHashSet<PathNavigation> activeNavigations;
|
||||
//
|
||||
// protected ServerWorldMixin(WritableLevelData properties, ResourceKey<World> registryRef, RegistryAccess registryManager, Holder<DimensionType> dimensionEntry, Supplier<ProfilerFiller> 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<Mob> getActiveListeners(Set<Mob> set) {
|
||||
// return Collections.emptyIterator();
|
||||
// }
|
||||
//
|
||||
// @SuppressWarnings("rawtypes")
|
||||
// @Inject(method = "<init>", 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<PathNavigation> 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;
|
||||
// }
|
||||
//}
|
||||
@@ -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<Boolean> 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();
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,11 @@ package net.gensokyoreimagined.nitori.mixin.entity.replace_entitytype_predicates
|
||||
// target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;)Ljava/util/List;"
|
||||
// )
|
||||
// )
|
||||
// private List<Entity> getMinecartsDirectly(Level level, Entity excluded, AABB box, Predicate<? super Entity> predicate) {
|
||||
// private List<Entity> getMinecartsDirectly(Level world, Entity excluded, AABB box, Predicate<? super Entity> 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);
|
||||
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -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 <C extends Container, T extends Recipe<C>> Collection<RecipeHolder<T>> byType(RecipeType<T> type);
|
||||
|
||||
/**
|
||||
* @author QPCrummer & Leaf Patch #0023
|
||||
* @reason Optimize RecipeManager List Creation
|
||||
*/
|
||||
@Overwrite
|
||||
public <C extends Container, T extends Recipe<C>> List<RecipeHolder<T>> getAllRecipesFor(RecipeType<T> type) {
|
||||
return new ArrayList<>(this.byType(type));
|
||||
}
|
||||
}
|
||||
@@ -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, C> T traverseBlocks(Vec3 start, Vec3 end, C context, BiFunction<C, BlockPos, T> blockHitFactory, Function<C, T> 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()));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
//
|
||||
//}
|
||||
@@ -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 = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/random/Random;create()Lnet/minecraft/util/math/random/Random;"))
|
||||
// private RandomSource redirectRandomCreation() {
|
||||
// return GetRandomInterface.getRandom();
|
||||
// }
|
||||
//}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
// }
|
||||
//}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
//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);
|
||||
// }
|
||||
//}
|
||||
@@ -32,9 +32,10 @@ package net.gensokyoreimagined.nitori.mixin.needs_testing.inline_height;
|
||||
// method = "<init>",
|
||||
// at = @At("RETURN")
|
||||
// )
|
||||
// private void initHeightCache(WritableLevelData properties, ResourceKey<?> registryRef, RegistryAccess registryManager, Holder<DimensionType> 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<DimensionType> 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;
|
||||
// }
|
||||
//
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<SectionedBlockChangeTracker> {
|
||||
private final LithiumInterner<SectionedBlockChangeTracker> 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);
|
||||
}
|
||||
}
|
||||
@@ -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 = "<init>", 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 = "<init>", 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("")
|
||||
//}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user