mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-23 17:09:29 +00:00
Merge branch 'ver/1.21.4' into dev/1.21.5
This commit is contained in:
@@ -18,7 +18,6 @@ public class AsyncGoalExecutor {
|
||||
protected final SpscIntQueue queue;
|
||||
protected final SpscIntQueue wake;
|
||||
protected final IntArrayList submit;
|
||||
private final AsyncGoalThread thread;
|
||||
private final ServerLevel world;
|
||||
private long midTickCount = 0L;
|
||||
|
||||
@@ -27,7 +26,6 @@ public class AsyncGoalExecutor {
|
||||
this.queue = new SpscIntQueue(AsyncTargetFinding.queueSize);
|
||||
this.wake = new SpscIntQueue(AsyncTargetFinding.queueSize);
|
||||
this.submit = new IntArrayList();
|
||||
this.thread = thread;
|
||||
}
|
||||
|
||||
boolean wake(int id) {
|
||||
@@ -46,7 +44,18 @@ public class AsyncGoalExecutor {
|
||||
|
||||
public final void tick() {
|
||||
batchSubmit();
|
||||
LockSupport.unpark(thread);
|
||||
while (true) {
|
||||
OptionalInt result = this.wake.recv();
|
||||
if (result.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
int id = result.getAsInt();
|
||||
if (poll(id) && !this.queue.send(id)) {
|
||||
do {
|
||||
wake(id);
|
||||
} while (poll(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void batchSubmit() {
|
||||
@@ -67,18 +76,6 @@ public class AsyncGoalExecutor {
|
||||
}
|
||||
|
||||
public final void midTick() {
|
||||
while (true) {
|
||||
OptionalInt result = this.wake.recv();
|
||||
if (result.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
int id = result.getAsInt();
|
||||
if (poll(id) && !this.queue.send(id)) {
|
||||
do {
|
||||
wake(id);
|
||||
} while (poll(id));
|
||||
}
|
||||
}
|
||||
if (AsyncTargetFinding.threshold <= 0L || (midTickCount % AsyncTargetFinding.threshold) == 0L) {
|
||||
batchSubmit();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.dreeam.leaf.async.chunk;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import net.minecraft.Util;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -14,13 +15,13 @@ public class AsyncChunkSend {
|
||||
public static final ExecutorService POOL = new ThreadPoolExecutor(
|
||||
1, 1, 0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new com.google.common.util.concurrent.ThreadFactoryBuilder()
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
new ThreadFactoryBuilder()
|
||||
.setPriority(Thread.NORM_PRIORITY)
|
||||
.setNameFormat("Leaf Async Chunk Send Thread")
|
||||
.setUncaughtExceptionHandler(Util::onThreadException)
|
||||
.setThreadFactory(AsyncChunkSendThread::new)
|
||||
.build(),
|
||||
new ThreadPoolExecutor.DiscardPolicy()
|
||||
new ThreadPoolExecutor.CallerRunsPolicy()
|
||||
);
|
||||
public static final Logger LOGGER = LogManager.getLogger("Leaf Async Chunk Send");
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.pathfinder.Path;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dreeam.leaf.config.modules.async.AsyncPathfinding;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -29,8 +30,8 @@ public class AsyncPathProcessor {
|
||||
private static long lastWarnMillis = System.currentTimeMillis();
|
||||
private static final ThreadPoolExecutor pathProcessingExecutor = new ThreadPoolExecutor(
|
||||
1,
|
||||
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingMaxThreads,
|
||||
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS,
|
||||
AsyncPathfinding.asyncPathfindingMaxThreads,
|
||||
AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS,
|
||||
getQueueImpl(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat(THREAD_PREFIX + " Thread - %d")
|
||||
@@ -44,7 +45,7 @@ public class AsyncPathProcessor {
|
||||
public void rejectedExecution(Runnable rejectedTask, ThreadPoolExecutor executor) {
|
||||
BlockingQueue<Runnable> workQueue = executor.getQueue();
|
||||
if (!executor.isShutdown()) {
|
||||
switch (org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingRejectPolicy) {
|
||||
switch (AsyncPathfinding.asyncPathfindingRejectPolicy) {
|
||||
case FLUSH_ALL -> {
|
||||
if (!workQueue.isEmpty()) {
|
||||
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
|
||||
@@ -98,7 +99,7 @@ public class AsyncPathProcessor {
|
||||
}
|
||||
|
||||
private static BlockingQueue<Runnable> getQueueImpl() {
|
||||
final int queueCapacity = org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingQueueSize;
|
||||
final int queueCapacity = AsyncPathfinding.asyncPathfindingQueueSize;
|
||||
|
||||
return new LinkedBlockingQueue<>(queueCapacity);
|
||||
}
|
||||
|
||||
@@ -32,15 +32,6 @@ public class MultithreadedTracker {
|
||||
private static long lastWarnMillis = System.currentTimeMillis();
|
||||
private static ThreadPoolExecutor TRACKER_EXECUTOR = null;
|
||||
|
||||
private record SendChanges(Object[] entities, int size) implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < size; i++) {
|
||||
((ServerEntity) entities[i]).sendDirtyEntityData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MultithreadedTracker() {
|
||||
}
|
||||
|
||||
@@ -80,7 +71,6 @@ public class MultithreadedTracker {
|
||||
|
||||
// Move tracking to off-main
|
||||
TRACKER_EXECUTOR.execute(() -> {
|
||||
ReferenceArrayList<ServerEntity> sendDirty = new ReferenceArrayList<>();
|
||||
for (final Entity entity : trackerEntitiesRaw) {
|
||||
if (entity == null) continue;
|
||||
|
||||
@@ -88,19 +78,12 @@ public class MultithreadedTracker {
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
// Don't Parallel Tick Tracker of Entity
|
||||
synchronized (tracker.sync) {
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
synchronized (tracker) {
|
||||
var trackedChunk = nearbyPlayers.getChunk(entity.chunkPosition());
|
||||
tracker.moonrise$tick(trackedChunk);
|
||||
tracker.serverEntity.sendChanges();
|
||||
if (tracker.serverEntity.wantSendDirtyEntityData) {
|
||||
tracker.serverEntity.wantSendDirtyEntityData = false;
|
||||
sendDirty.add(tracker.serverEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!sendDirty.isEmpty()) {
|
||||
level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -121,7 +104,7 @@ public class MultithreadedTracker {
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
synchronized (tracker.sync) {
|
||||
synchronized (tracker) {
|
||||
tickTask[index] = tracker.leafTickCompact(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
sendChangesTasks[index] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
|
||||
}
|
||||
@@ -140,22 +123,6 @@ public class MultithreadedTracker {
|
||||
|
||||
sendChanges.run();
|
||||
}
|
||||
|
||||
ReferenceArrayList<ServerEntity> sendDirty = new ReferenceArrayList<>();
|
||||
for (final Entity entity : trackerEntitiesRaw) {
|
||||
if (entity == null) continue;
|
||||
|
||||
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
||||
|
||||
if (tracker == null) continue;
|
||||
if (tracker.serverEntity.wantSendDirtyEntityData) {
|
||||
tracker.serverEntity.wantSendDirtyEntityData = false;
|
||||
sendDirty.add(tracker.serverEntity);
|
||||
}
|
||||
}
|
||||
if (!sendDirty.isEmpty()) {
|
||||
level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -227,8 +227,12 @@ public class LeafConfig {
|
||||
extraConfigs.addAll(Arrays.asList(existing.split(",")));
|
||||
}
|
||||
|
||||
// Use same way in spark's BukkitServerConfigProvider#getNestedFiles to get all world configs
|
||||
// It may spam in the spark profiler, but it's ok, since spark uses YamlConfigParser.INSTANCE to
|
||||
// get configs defined in extra config flag instead of using SplitYamlConfigParser.INSTANCE
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
extraConfigs.add(world.getWorldFolder().getName() + "/gale-world.yml"); // Gale world config
|
||||
Path galeWorldFolder = world.getWorldFolder().toPath().resolve("gale-world.yml");
|
||||
extraConfigs.add(galeWorldFolder.toString().replace("\\", "/").replace("./", "")); // Gale world config
|
||||
}
|
||||
|
||||
return extraConfigs;
|
||||
|
||||
@@ -8,15 +8,18 @@ import org.dreeam.leaf.config.annotations.Experimental;
|
||||
public class SparklyPaperParallelWorldTicking extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-tracking";
|
||||
} // TODO: Correct config key when stable
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-ticking";
|
||||
}
|
||||
|
||||
@Experimental
|
||||
public static boolean enabled = false;
|
||||
public static int threads = 8;
|
||||
public static boolean logContainerCreationStacktraces = false;
|
||||
public static boolean disableHardThrow = false;
|
||||
@Deprecated
|
||||
public static boolean runAsyncTasksSync = false;
|
||||
// STRICT, BUFFERED, DISABLED
|
||||
public static String asyncUnsafeReadHandling = "BUFFERED";
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
@@ -34,15 +37,30 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules {
|
||||
} else {
|
||||
threads = 0;
|
||||
}
|
||||
|
||||
logContainerCreationStacktraces = config.getBoolean(getBasePath() + ".log-container-creation-stacktraces", logContainerCreationStacktraces);
|
||||
logContainerCreationStacktraces = enabled && logContainerCreationStacktraces;
|
||||
disableHardThrow = config.getBoolean(getBasePath() + ".disable-hard-throw", disableHardThrow);
|
||||
disableHardThrow = enabled && disableHardThrow;
|
||||
runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", runAsyncTasksSync);
|
||||
runAsyncTasksSync = enabled && runAsyncTasksSync;
|
||||
asyncUnsafeReadHandling = config.getString(getBasePath() + ".async-unsafe-read-handling", asyncUnsafeReadHandling).toUpperCase();
|
||||
|
||||
if (!asyncUnsafeReadHandling.equals("STRICT") && !asyncUnsafeReadHandling.equals("BUFFERED") && !asyncUnsafeReadHandling.equals("DISABLED")) {
|
||||
LeafConfig.LOGGER.warn("Invalid value for {}.async-unsafe-read-handling: {}, fallback to STRICT.", getBasePath(), asyncUnsafeReadHandling);
|
||||
asyncUnsafeReadHandling = "STRICT";
|
||||
}
|
||||
if (!enabled) {
|
||||
asyncUnsafeReadHandling = "DISABLED";
|
||||
}
|
||||
|
||||
runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", false); // Default to false now
|
||||
if (runAsyncTasksSync) {
|
||||
LeafConfig.LOGGER.warn("The setting '{}.run-async-tasks-sync' is deprecated. Use 'async-unsafe-read-handling: STRICT' for similar safety checks or 'BUFFERED' for buffered reads.", getBasePath());
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
LeafConfig.LOGGER.info("Using {} threads for Parallel World Ticking", threads);
|
||||
}
|
||||
|
||||
runAsyncTasksSync = enabled && runAsyncTasksSync; // Auto-disable if main feature is off
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class OptimizedPoweredRails extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName() + ".optimized-powered-rails";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config().getBoolean(getBasePath(), enabled);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import java.util.*;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
|
||||
// fast array backend map with O(1) get & put & remove
|
||||
public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, AttributeInstance>, Cloneable {
|
||||
public final class AttributeInstanceArrayMap implements Map<Holder<Attribute>, AttributeInstance>, Cloneable {
|
||||
|
||||
private int size = 0;
|
||||
private transient AttributeInstance[] a = new AttributeInstance[32];
|
||||
@@ -46,17 +46,17 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int size() {
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isEmpty() {
|
||||
public boolean isEmpty() {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean containsKey(Object key) {
|
||||
public boolean containsKey(Object key) {
|
||||
if (key instanceof Holder<?> holder && holder.value() instanceof Attribute attribute) {
|
||||
int uid = attribute.uid;
|
||||
return uid >= 0 && uid < a.length && a[uid] != null;
|
||||
@@ -65,22 +65,22 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean containsValue(Object value) {
|
||||
for (final AttributeInstance instance : a) {
|
||||
if (Objects.equals(value, instance)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public boolean containsValue(Object value) {
|
||||
return value instanceof AttributeInstance val && Objects.equals(getInstance(val.getAttribute().value().uid), val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final AttributeInstance get(Object key) {
|
||||
public AttributeInstance get(Object key) {
|
||||
return key instanceof Holder<?> holder && holder.value() instanceof Attribute attribute ? a[attribute.uid] : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AttributeInstance getInstance(int key) {
|
||||
return a[key];
|
||||
}
|
||||
|
||||
@Override
|
||||
public final AttributeInstance put(@NotNull Holder<Attribute> key, AttributeInstance value) {
|
||||
public AttributeInstance put(@NotNull Holder<Attribute> key, AttributeInstance value) {
|
||||
int uid = key.value().uid;
|
||||
AttributeInstance prev = a[uid];
|
||||
setByIndex(uid, value);
|
||||
@@ -88,7 +88,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
|
||||
}
|
||||
|
||||
@Override
|
||||
public final AttributeInstance remove(Object key) {
|
||||
public AttributeInstance remove(Object key) {
|
||||
if (!(key instanceof Holder<?> holder) || !(holder.value() instanceof Attribute attribute)) return null;
|
||||
int uid = attribute.uid;
|
||||
AttributeInstance prev = a[uid];
|
||||
@@ -97,7 +97,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void putAll(@NotNull Map<? extends Holder<Attribute>, ? extends AttributeInstance> m) {
|
||||
public void putAll(@NotNull Map<? extends Holder<Attribute>, ? extends AttributeInstance> m) {
|
||||
for (AttributeInstance e : m.values()) {
|
||||
if (e != null) {
|
||||
setByIndex(e.getAttribute().value().uid, e);
|
||||
@@ -106,13 +106,13 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void clear() {
|
||||
public void clear() {
|
||||
Arrays.fill(a, null);
|
||||
size = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @NotNull Set<Holder<Attribute>> keySet() {
|
||||
public @NotNull Set<Holder<Attribute>> keySet() {
|
||||
if (keys == null) {
|
||||
keys = new KeySet();
|
||||
}
|
||||
@@ -120,7 +120,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @NotNull Collection<AttributeInstance> values() {
|
||||
public @NotNull Collection<AttributeInstance> values() {
|
||||
if (values == null) {
|
||||
values = new Values();
|
||||
}
|
||||
@@ -128,7 +128,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @NotNull Set<Entry<Holder<Attribute>, AttributeInstance>> entrySet() {
|
||||
public @NotNull Set<Entry<Holder<Attribute>, AttributeInstance>> entrySet() {
|
||||
if (entries == null) {
|
||||
entries = new EntrySet();
|
||||
}
|
||||
@@ -136,13 +136,23 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (!(o instanceof AttributeInstanceArrayMap that)) return false;
|
||||
return size == that.size && Arrays.equals(a, that.a);
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (!(o instanceof Map<?, ?> s)) return false;
|
||||
if (s.size() != size()) return false;
|
||||
if (o instanceof AttributeInstanceArrayMap that) {
|
||||
return Arrays.equals(a, that.a);
|
||||
}
|
||||
for (Entry<?, ?> e : s.entrySet()) {
|
||||
if (!Objects.equals(get(e.getKey()), e.getValue())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(a);
|
||||
}
|
||||
|
||||
@@ -192,7 +202,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
currentIndex = nextIndex;
|
||||
nextIndex = findNextOccupied(nextIndex + 1);
|
||||
return BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(currentIndex);
|
||||
return BuiltInRegistries.ATTRIBUTE.get(currentIndex).orElseThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -279,7 +289,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
|
||||
public Entry<Holder<Attribute>, AttributeInstance> next() {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
currentIndex = nextIndex;
|
||||
Holder<Attribute> key = BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(nextIndex);
|
||||
Holder<Attribute> key = BuiltInRegistries.ATTRIBUTE.get(nextIndex).orElseThrow();
|
||||
AttributeInstance value = a[nextIndex];
|
||||
nextIndex = findNextOccupied(nextIndex + 1);
|
||||
return new SimpleEntry<>(key, value) {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.dreeam.leaf.util.map;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArraySet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
|
||||
public final class AttributeInstanceSet extends AbstractCollection<AttributeInstance> implements Set<AttributeInstance> {
|
||||
public final IntSet inner;
|
||||
public final AttributeInstanceArrayMap map;
|
||||
|
||||
public AttributeInstanceSet(AttributeInstanceArrayMap map) {
|
||||
this.map = map;
|
||||
inner = new IntArraySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(AttributeInstance instance) {
|
||||
return inner.add(instance.getAttribute().value().uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return o instanceof AttributeInstance instance && inner.remove(instance.getAttribute().value().uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterator<AttributeInstance> iterator() {
|
||||
return new CloneIterator(inner.toIntArray(), map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return inner.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return inner.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
inner.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (o instanceof AttributeInstance instance) {
|
||||
return inner.contains(instance.getAttribute().value().uid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeInstance @NotNull [] toArray() {
|
||||
int[] innerClone = inner.toIntArray();
|
||||
AttributeInstance[] arr = new AttributeInstance[innerClone.length];
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
arr[i] = map.getInstance(innerClone[i]);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
public <T> T @NotNull [] toArray(T[] a) {
|
||||
if (a == null || (a.getClass() == AttributeInstance[].class && a.length == 0)) {
|
||||
return (T[]) toArray();
|
||||
}
|
||||
if (a.length < size()) {
|
||||
a = (T[]) Array.newInstance(a.getClass().getComponentType(), size());
|
||||
}
|
||||
System.arraycopy((T[]) toArray(), 0, a, 0, size());
|
||||
if (a.length > size()) {
|
||||
a[size()] = null;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static class CloneIterator implements Iterator<AttributeInstance> {
|
||||
private final int[] array;
|
||||
private int index = 0;
|
||||
private final AttributeInstanceArrayMap map;
|
||||
|
||||
CloneIterator(int[] array, AttributeInstanceArrayMap map) {
|
||||
this.array = array;
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < array.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeInstance next() {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
return map.getInstance(array[index++]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (!(o instanceof Set<?> s)) return false;
|
||||
if (s.size() != size()) return false;
|
||||
return containsAll(s);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.dreeam.leaf.util.biome;
|
||||
package org.dreeam.leaf.world.biome;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
@@ -0,0 +1,333 @@
|
||||
package org.dreeam.leaf.world.block;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.PoweredRailBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.RailShape;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import static net.minecraft.world.level.block.Block.*;
|
||||
import static net.minecraft.world.level.block.PoweredRailBlock.POWERED;
|
||||
import static net.minecraft.world.level.block.PoweredRailBlock.SHAPE;
|
||||
|
||||
public class OptimizedPoweredRails {
|
||||
|
||||
private static final Direction[] EAST_WEST_DIR = new Direction[]{Direction.WEST, Direction.EAST};
|
||||
private static final Direction[] NORTH_SOUTH_DIR = new Direction[]{Direction.SOUTH, Direction.NORTH};
|
||||
|
||||
private static final int UPDATE_FORCE_PLACE = UPDATE_MOVE_BY_PISTON | UPDATE_KNOWN_SHAPE | UPDATE_CLIENTS;
|
||||
|
||||
private static int RAIL_POWER_LIMIT = 8;
|
||||
|
||||
private static void giveShapeUpdate(Level level, BlockState state, BlockPos pos, BlockPos fromPos, Direction direction) {
|
||||
BlockState oldState = level.getBlockState(pos);
|
||||
Block.updateOrDestroy(
|
||||
oldState,
|
||||
oldState.updateShape(level, level, pos, direction.getOpposite(), fromPos, state, level.random),
|
||||
level,
|
||||
pos,
|
||||
UPDATE_CLIENTS & -34,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
public static int getRailPowerLimit() {
|
||||
return RAIL_POWER_LIMIT;
|
||||
}
|
||||
|
||||
public static void setRailPowerLimit(int powerLimit) {
|
||||
RAIL_POWER_LIMIT = powerLimit;
|
||||
}
|
||||
|
||||
public static void updateState(PoweredRailBlock self, BlockState state, Level level, BlockPos pos) {
|
||||
boolean shouldBePowered = level.hasNeighborSignal(pos) ||
|
||||
self.findPoweredRailSignal(level, pos, state, true, 0) ||
|
||||
self.findPoweredRailSignal(level, pos, state, false, 0);
|
||||
if (shouldBePowered != state.getValue(POWERED)) {
|
||||
RailShape railShape = state.getValue(SHAPE);
|
||||
if (railShape.isSlope()) {
|
||||
level.setBlock(pos, state.setValue(POWERED, shouldBePowered), 3);
|
||||
level.updateNeighborsAtExceptFromFacing(pos.below(), self, Direction.UP, null);
|
||||
level.updateNeighborsAtExceptFromFacing(pos.above(), self, Direction.DOWN, null); // isSlope
|
||||
} else if (shouldBePowered) {
|
||||
powerLane(self, level, pos, state, railShape);
|
||||
} else {
|
||||
dePowerLane(self, level, pos, state, railShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level world, BlockPos pos,
|
||||
boolean bl, int distance, RailShape shape,
|
||||
HashMap<BlockPos, Boolean> checkedPos) {
|
||||
BlockState blockState = world.getBlockState(pos);
|
||||
boolean speedCheck = checkedPos.containsKey(pos) && checkedPos.get(pos);
|
||||
if (speedCheck) {
|
||||
return world.hasNeighborSignal(pos) ||
|
||||
findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos);
|
||||
} else {
|
||||
if (blockState.is(self)) {
|
||||
RailShape railShape = blockState.getValue(SHAPE);
|
||||
if (shape == RailShape.EAST_WEST && (
|
||||
railShape == RailShape.NORTH_SOUTH ||
|
||||
railShape == RailShape.ASCENDING_NORTH ||
|
||||
railShape == RailShape.ASCENDING_SOUTH
|
||||
) || shape == RailShape.NORTH_SOUTH && (
|
||||
railShape == RailShape.EAST_WEST ||
|
||||
railShape == RailShape.ASCENDING_EAST ||
|
||||
railShape == RailShape.ASCENDING_WEST
|
||||
)) {
|
||||
return false;
|
||||
} else if (blockState.getValue(POWERED)) {
|
||||
return world.hasNeighborSignal(pos) ||
|
||||
findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level level,
|
||||
BlockPos pos, BlockState state, boolean bl, int distance,
|
||||
HashMap<BlockPos, Boolean> checkedPos) {
|
||||
if (distance >= RAIL_POWER_LIMIT - 1) return false;
|
||||
int i = pos.getX();
|
||||
int j = pos.getY();
|
||||
int k = pos.getZ();
|
||||
boolean bl2 = true;
|
||||
RailShape railShape = state.getValue(SHAPE);
|
||||
switch (railShape.ordinal()) {
|
||||
case 0 -> {
|
||||
if (bl) ++k;
|
||||
else --k;
|
||||
}
|
||||
case 1 -> {
|
||||
if (bl) --i;
|
||||
else ++i;
|
||||
}
|
||||
case 2 -> {
|
||||
if (bl) {
|
||||
--i;
|
||||
} else {
|
||||
++i;
|
||||
++j;
|
||||
bl2 = false;
|
||||
}
|
||||
railShape = RailShape.EAST_WEST;
|
||||
}
|
||||
case 3 -> {
|
||||
if (bl) {
|
||||
--i;
|
||||
++j;
|
||||
bl2 = false;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
railShape = RailShape.EAST_WEST;
|
||||
}
|
||||
case 4 -> {
|
||||
if (bl) {
|
||||
++k;
|
||||
} else {
|
||||
--k;
|
||||
++j;
|
||||
bl2 = false;
|
||||
}
|
||||
railShape = RailShape.NORTH_SOUTH;
|
||||
}
|
||||
case 5 -> {
|
||||
if (bl) {
|
||||
++k;
|
||||
++j;
|
||||
bl2 = false;
|
||||
} else {
|
||||
--k;
|
||||
}
|
||||
railShape = RailShape.NORTH_SOUTH;
|
||||
}
|
||||
}
|
||||
return findPoweredRailSignalFaster(
|
||||
self, level, new BlockPos(i, j, k),
|
||||
bl, distance, railShape, checkedPos
|
||||
) ||
|
||||
(bl2 && findPoweredRailSignalFaster(
|
||||
self, level, new BlockPos(i, j - 1, k),
|
||||
bl, distance, railShape, checkedPos
|
||||
));
|
||||
}
|
||||
|
||||
private static void powerLane(PoweredRailBlock self, Level world, BlockPos pos,
|
||||
BlockState mainState, RailShape railShape) {
|
||||
world.setBlock(pos, mainState.setValue(POWERED, true), UPDATE_FORCE_PLACE);
|
||||
HashMap<BlockPos, Boolean> checkedPos = new HashMap<>();
|
||||
checkedPos.put(pos, true);
|
||||
int[] count = new int[2];
|
||||
if (railShape == RailShape.NORTH_SOUTH) { // Order: +z, -z
|
||||
for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) {
|
||||
setRailPositionsPower(self, world, pos, checkedPos, count, i, NORTH_SOUTH_DIR[i]);
|
||||
}
|
||||
updateRails(self, false, world, pos, mainState, count);
|
||||
} else if (railShape == RailShape.EAST_WEST) { // Order: -x, +x
|
||||
for (int i = 0; i < EAST_WEST_DIR.length; ++i) {
|
||||
setRailPositionsPower(self, world, pos, checkedPos, count, i, EAST_WEST_DIR[i]);
|
||||
}
|
||||
updateRails(self, true, world, pos, mainState, count);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dePowerLane(PoweredRailBlock self, Level world, BlockPos pos,
|
||||
BlockState mainState, RailShape railShape) {
|
||||
world.setBlock(pos, mainState.setValue(POWERED, false), UPDATE_FORCE_PLACE);
|
||||
int[] count = new int[2];
|
||||
if (railShape == RailShape.NORTH_SOUTH) { // Order: +z, -z
|
||||
for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) {
|
||||
setRailPositionsDePower(self, world, pos, count, i, NORTH_SOUTH_DIR[i]);
|
||||
}
|
||||
updateRails(self, false, world, pos, mainState, count);
|
||||
} else if (railShape == RailShape.EAST_WEST) { // Order: -x, +x
|
||||
for (int i = 0; i < EAST_WEST_DIR.length; ++i) {
|
||||
setRailPositionsDePower(self, world, pos, count, i, EAST_WEST_DIR[i]);
|
||||
}
|
||||
updateRails(self, true, world, pos, mainState, count);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setRailPositionsPower(PoweredRailBlock self, Level world, BlockPos pos,
|
||||
HashMap<BlockPos, Boolean> checkedPos, int[] count, int i, Direction dir) {
|
||||
for (int z = 1; z < RAIL_POWER_LIMIT; z++) {
|
||||
BlockPos newPos = pos.relative(dir, z);
|
||||
BlockState state = world.getBlockState(newPos);
|
||||
if (checkedPos.containsKey(newPos)) {
|
||||
if (!checkedPos.get(newPos)) break;
|
||||
count[i]++;
|
||||
} else if (!state.is(self) || state.getValue(POWERED) || !(
|
||||
world.hasNeighborSignal(newPos) ||
|
||||
findPoweredRailSignalFaster(self, world, newPos, state, true, 0, checkedPos) ||
|
||||
findPoweredRailSignalFaster(self, world, newPos, state, false, 0, checkedPos)
|
||||
)) {
|
||||
checkedPos.put(newPos, false);
|
||||
break;
|
||||
} else {
|
||||
checkedPos.put(newPos, true);
|
||||
world.setBlock(newPos, state.setValue(POWERED, true), UPDATE_FORCE_PLACE);
|
||||
count[i]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void setRailPositionsDePower(PoweredRailBlock self, Level world, BlockPos pos,
|
||||
int[] count, int i, Direction dir) {
|
||||
for (int z = 1; z < RAIL_POWER_LIMIT; z++) {
|
||||
BlockPos newPos = pos.relative(dir, z);
|
||||
BlockState state = world.getBlockState(newPos);
|
||||
if (!state.is(self) || !state.getValue(POWERED) || world.hasNeighborSignal(newPos) ||
|
||||
self.findPoweredRailSignal(world, newPos, state, true, 0) ||
|
||||
self.findPoweredRailSignal(world, newPos, state, false, 0)) break;
|
||||
world.setBlock(newPos, state.setValue(POWERED, false), UPDATE_FORCE_PLACE);
|
||||
count[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void shapeUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, BlockState mainState,
|
||||
int endPos, Direction direction, int currentPos, BlockPos blockPos) {
|
||||
if (currentPos == endPos) {
|
||||
BlockPos newPos = pos.relative(direction, currentPos + 1);
|
||||
giveShapeUpdate(world, mainState, newPos, pos, direction);
|
||||
BlockState state = world.getBlockState(blockPos);
|
||||
if (state.is(self) && state.getValue(SHAPE).isSlope()) giveShapeUpdate(world, mainState, newPos.above(), pos, direction);
|
||||
}
|
||||
}
|
||||
|
||||
private static void neighborUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, int endPos,
|
||||
Direction direction, Block block, int currentPos, BlockPos blockPos) {
|
||||
if (currentPos == endPos) {
|
||||
BlockPos newPos = pos.relative(direction, currentPos + 1);
|
||||
world.neighborChanged(newPos, block, null);
|
||||
BlockState state = world.getBlockState(blockPos);
|
||||
if (state.is(self) && state.getValue(SHAPE).isSlope()) world.neighborChanged(newPos.above(), block, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateRailsSectionEastWestShape(PoweredRailBlock self, Level world, BlockPos pos,
|
||||
int c, BlockState mainState, Direction dir,
|
||||
int[] count, int countAmt) {
|
||||
BlockPos pos1 = pos.relative(dir, c);
|
||||
if (c == 0 && count[1] == 0) giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite());
|
||||
shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1);
|
||||
giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN);
|
||||
giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP);
|
||||
giveShapeUpdate(world, mainState, pos1.north(), pos, Direction.NORTH);
|
||||
giveShapeUpdate(world, mainState, pos1.south(), pos, Direction.SOUTH);
|
||||
}
|
||||
|
||||
private static void updateRailsSectionNorthSouthShape(PoweredRailBlock self, Level world, BlockPos pos,
|
||||
int c, BlockState mainState, Direction dir,
|
||||
int[] count, int countAmt) {
|
||||
BlockPos pos1 = pos.relative(dir, c);
|
||||
giveShapeUpdate(world, mainState, pos1.west(), pos, Direction.WEST);
|
||||
giveShapeUpdate(world, mainState, pos1.east(), pos, Direction.EAST);
|
||||
giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN);
|
||||
giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP);
|
||||
shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1);
|
||||
if (c == 0 && count[1] == 0) giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite());
|
||||
}
|
||||
|
||||
private static void updateRails(PoweredRailBlock self, boolean eastWest, Level world,
|
||||
BlockPos pos, BlockState mainState, int[] count) {
|
||||
if (eastWest) {
|
||||
for (int i = 0; i < EAST_WEST_DIR.length; ++i) {
|
||||
int countAmt = count[i];
|
||||
if (i == 1 && countAmt == 0) continue;
|
||||
Direction dir = EAST_WEST_DIR[i];
|
||||
Block block = mainState.getBlock();
|
||||
for (int c = countAmt; c >= i; c--) {
|
||||
BlockPos p = pos.relative(dir, c);
|
||||
if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, null);
|
||||
neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p);
|
||||
world.neighborChanged(p.below(), block, null);
|
||||
world.neighborChanged(p.above(), block, null);
|
||||
world.neighborChanged(p.north(), block, null);
|
||||
world.neighborChanged(p.south(), block, null);
|
||||
BlockPos pos2 = pos.relative(dir, c).below();
|
||||
world.neighborChanged(pos2.below(), block, null);
|
||||
world.neighborChanged(pos2.north(), block, null);
|
||||
world.neighborChanged(pos2.south(), block, null);
|
||||
if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, null);
|
||||
if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()).below(), block, null);
|
||||
}
|
||||
for (int c = countAmt; c >= i; c--)
|
||||
updateRailsSectionEastWestShape(self, world, pos, c, mainState, dir, count, countAmt);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) {
|
||||
int countAmt = count[i];
|
||||
if (i == 1 && countAmt == 0) continue;
|
||||
Direction dir = NORTH_SOUTH_DIR[i];
|
||||
Block block = mainState.getBlock();
|
||||
for (int c = countAmt; c >= i; c--) {
|
||||
BlockPos p = pos.relative(dir, c);
|
||||
world.neighborChanged(p.west(), block, null);
|
||||
world.neighborChanged(p.east(), block, null);
|
||||
world.neighborChanged(p.below(), block, null);
|
||||
world.neighborChanged(p.above(), block, null);
|
||||
neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p);
|
||||
if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, null);
|
||||
BlockPos pos2 = pos.relative(dir, c).below();
|
||||
world.neighborChanged(pos2.west(), block, null);
|
||||
world.neighborChanged(pos2.east(), block, null);
|
||||
world.neighborChanged(pos2.below(), block, null);
|
||||
if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, null);
|
||||
if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()).below(), block, null);
|
||||
}
|
||||
for (int c = countAmt; c >= i; c--)
|
||||
updateRailsSectionNorthSouthShape(self, world, pos, c, mainState, dir, count, countAmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user