52 changed files

This commit is contained in:
Taiyou06
2024-07-30 20:51:14 +03:00
parent d4ae8a6336
commit eea4f8e0d9
52 changed files with 3414 additions and 1322 deletions

View File

@@ -0,0 +1,6 @@
package net.gensokyoreimagined.nitori.common.ai;
public interface MemoryModificationCounter {
long lithium$getModCount();
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
//
//}

View File

@@ -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]);
//
//}

View File

@@ -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();
// }
//
//}

View File

@@ -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);
// }
//
//}

View File

@@ -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;
// }
//
//}

View File

@@ -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>&#8804; 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>&#8805; 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>&#8804; 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>&#8805; 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];
// }
//
//}

View File

@@ -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();
}

View File

@@ -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);
// }
//}

View File

@@ -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();
}
}

View File

@@ -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);
// }
//}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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<>());
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
// }
//
//}

View File

@@ -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;
// }
//}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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);
// }
// }
//}

View File

@@ -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));
}
}

View File

@@ -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()));
});
}
}

View File

@@ -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();
//
//}

View File

@@ -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();
// }
//}

View File

@@ -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;
}
}

View File

@@ -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();
// }
//}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
// }
//}

View File

@@ -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;
// }
//

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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("")
//}

View File

@@ -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"
]
}