9
0
mirror of https://github.com/BX-Team/DivineMC.git synced 2025-12-21 07:49:18 +00:00

rewrite chunk system

This commit is contained in:
NONPLAYT
2025-04-06 20:28:55 +03:00
parent 8c81c1e57f
commit 8c091c59bc
27 changed files with 2132 additions and 404 deletions

View File

@@ -0,0 +1,191 @@
package com.ishland.flowsched.executor;
import com.ishland.flowsched.structs.DynamicPriorityQueue;
import com.ishland.flowsched.util.Assertions;
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
public class ExecutorManager {
public final DynamicPriorityQueue<Task> globalWorkQueue;
protected final ConcurrentMap<LockToken, FreeableTaskList> lockListeners = new ConcurrentHashMap<>();
protected final WorkerThread[] workerThreads;
final Object workerMonitor = new Object();
/**
* Creates a new executor manager.
*
* @param workerThreadCount the number of worker threads.
*/
public ExecutorManager(int workerThreadCount) {
this(workerThreadCount, thread -> { });
}
/**
* Creates a new executor manager.
*
* @param workerThreadCount the number of worker threads.
* @param threadInitializer the thread initializer.
*/
public ExecutorManager(int workerThreadCount, Consumer<Thread> threadInitializer) {
globalWorkQueue = new DynamicPriorityQueue<>();
workerThreads = new WorkerThread[workerThreadCount];
for (int i = 0; i < workerThreadCount; i++) {
final WorkerThread thread = new WorkerThread(this);
threadInitializer.accept(thread);
thread.start();
workerThreads[i] = thread;
}
}
/**
* Attempt to lock the given tokens.
* The caller should discard the task if this method returns false, as it reschedules the task.
*
* @return {@code true} if the lock is acquired, {@code false} otherwise.
*/
boolean tryLock(Task task) {
retry:
while (true) {
final FreeableTaskList listenerSet = new FreeableTaskList();
LockToken[] lockTokens = task.lockTokens();
for (int i = 0; i < lockTokens.length; i++) {
LockToken token = lockTokens[i];
final FreeableTaskList present = this.lockListeners.putIfAbsent(token, listenerSet);
if (present != null) {
for (int j = 0; j < i; j++) {
this.lockListeners.remove(lockTokens[j], listenerSet);
}
callListeners(listenerSet);
synchronized (present) {
if (present.freed) {
continue retry;
} else {
present.add(task);
}
}
return false;
}
}
return true;
}
}
/**
* Release the locks held by the given task.
*
* @param task the task.
*/
void releaseLocks(Task task) {
FreeableTaskList expectedListeners = null;
for (LockToken token : task.lockTokens()) {
final FreeableTaskList listeners = this.lockListeners.remove(token);
if (listeners != null) {
if (expectedListeners == null) {
expectedListeners = listeners;
} else {
Assertions.assertTrue(expectedListeners == listeners, "Inconsistent lock listeners");
}
} else {
throw new IllegalStateException("Lock token " + token + " is not locked");
}
}
if (expectedListeners != null) {
callListeners(expectedListeners); // synchronizes
}
}
private void callListeners(FreeableTaskList listeners) {
synchronized (listeners) {
listeners.freed = true;
if (listeners.isEmpty()) return;
for (Task listener : listeners) {
this.schedule0(listener);
}
}
this.wakeup();
}
/**
* Polls an executable task from the global work queue.
*
* @return the task, or {@code null} if no task is executable.
*/
Task pollExecutableTask() {
Task task;
while ((task = this.globalWorkQueue.dequeue()) != null) {
if (this.tryLock(task)) {
return task;
}
}
return null;
}
/**
* Shuts down the executor manager.
*/
public void shutdown() {
for (WorkerThread workerThread : workerThreads) {
workerThread.shutdown();
}
}
/**
* Schedules a task.
*
* @param task the task.
*/
public void schedule(Task task) {
schedule0(task);
wakeup();
}
private void schedule0(Task task) {
this.globalWorkQueue.enqueue(task, task.priority());
}
public void wakeup() { // Canvas - private -> public
synchronized (this.workerMonitor) {
this.workerMonitor.notify();
}
}
public boolean hasPendingTasks() {
return this.globalWorkQueue.size() != 0;
}
/**
* Schedules a runnable for execution with the given priority.
*
* @param runnable the runnable.
* @param priority the priority.
*/
public void schedule(Runnable runnable, int priority) {
this.schedule(new SimpleTask(runnable, priority));
}
/**
* Creates an executor that schedules runnables with the given priority.
*
* @param priority the priority.
* @return the executor.
*/
public Executor executor(int priority) {
return runnable -> this.schedule(runnable, priority);
}
/**
* Notifies the executor manager that the priority of the given task has changed.
*
* @param task the task.
*/
public void notifyPriorityChange(Task task) {
this.globalWorkQueue.changePriority(task, task.priority());
}
protected static class FreeableTaskList extends ReferenceArrayList<Task> { // Canvas - private -> protected
private boolean freed = false;
}
}

View File

@@ -0,0 +1,3 @@
package com.ishland.flowsched.executor;
public interface LockToken { }

View File

@@ -0,0 +1,37 @@
package com.ishland.flowsched.executor;
import java.util.Objects;
public class SimpleTask implements Task {
private final Runnable wrapped;
private final int priority;
public SimpleTask(Runnable wrapped, int priority) {
this.wrapped = Objects.requireNonNull(wrapped);
this.priority = priority;
}
@Override
public void run(Runnable releaseLocks) {
try {
wrapped.run();
} finally {
releaseLocks.run();
}
}
@Override
public void propagateException(Throwable t) {
t.printStackTrace();
}
@Override
public LockToken[] lockTokens() {
return new LockToken[0];
}
@Override
public int priority() {
return this.priority;
}
}

View File

@@ -0,0 +1,11 @@
package com.ishland.flowsched.executor;
public interface Task {
void run(Runnable releaseLocks);
void propagateException(Throwable t);
LockToken[] lockTokens();
int priority();
}

View File

@@ -0,0 +1,84 @@
package com.ishland.flowsched.executor;
import ca.spottedleaf.moonrise.common.util.TickThread;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WorkerThread extends TickThread {
private static final Logger LOGGER = LoggerFactory.getLogger("FlowSched Executor Worker Thread");
private final ExecutorManager executorManager;
private final AtomicBoolean shutdown = new AtomicBoolean(false);
public volatile boolean active = false;
public WorkerThread(ExecutorManager executorManager) {
super("null_worker");
this.executorManager = executorManager;
}
@Override
public void run() {
main_loop:
while (true) {
if (this.shutdown.get()) {
return;
}
active = true;
if (pollTasks()) {
continue;
}
synchronized (this.executorManager.workerMonitor) {
if (this.executorManager.hasPendingTasks()) continue main_loop;
try {
active = false;
this.executorManager.workerMonitor.wait();
} catch (InterruptedException ignored) {
}
}
}
}
private boolean pollTasks() {
final Task task = executorManager.pollExecutableTask();
try {
if (task != null) {
AtomicBoolean released = new AtomicBoolean(false);
try {
task.run(() -> {
if (released.compareAndSet(false, true)) {
executorManager.releaseLocks(task);
}
});
} catch (Throwable t) {
try {
if (released.compareAndSet(false, true)) {
executorManager.releaseLocks(task);
}
} catch (Throwable t1) {
t.addSuppressed(t1);
LOGGER.error("Exception thrown while releasing locks", t);
}
try {
task.propagateException(t);
} catch (Throwable t1) {
t.addSuppressed(t1);
LOGGER.error("Exception thrown while propagating exception", t);
}
}
return true;
}
return false;
} catch (Throwable t) {
LOGGER.error("Exception thrown while executing task", t);
return true;
}
}
public void shutdown() {
shutdown.set(true);
LockSupport.unpark(this);
}
}

View File

@@ -0,0 +1,85 @@
package com.ishland.flowsched.structs;
import org.bxteam.divinemc.server.chunk.PriorityHandler;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicIntegerArray;
@SuppressWarnings("unchecked")
public class DynamicPriorityQueue<E> {
private final AtomicIntegerArray taskCount;
public final ConcurrentLinkedQueue<E>[] priorities;
private final ConcurrentHashMap<E, Integer> priorityMap = new ConcurrentHashMap<>();
public DynamicPriorityQueue() {
this.taskCount = new AtomicIntegerArray(PriorityHandler.MAX_PRIORITY + 1);
this.priorities = new ConcurrentLinkedQueue[PriorityHandler.MAX_PRIORITY + 1];
for (int i = 0; i < (PriorityHandler.MAX_PRIORITY + 1); i++) {
this.priorities[i] = new ConcurrentLinkedQueue<>();
}
}
public void enqueue(E element, int priority) {
if (this.priorityMap.putIfAbsent(element, priority) != null)
throw new IllegalArgumentException("Element already in queue");
this.priorities[priority].add(element);
this.taskCount.incrementAndGet(priority);
}
public boolean changePriority(E element, int newPriority) {
Integer currentPriority = this.priorityMap.get(element);
if (currentPriority == null || currentPriority == newPriority) {
return false;
}
int currentIndex = currentPriority;
boolean removedFromQueue = this.priorities[currentIndex].remove(element);
if (!removedFromQueue) {
return false;
}
this.taskCount.decrementAndGet(currentIndex);
final boolean changeSuccess = this.priorityMap.replace(element, currentPriority, newPriority);
if (!changeSuccess) {
return false;
}
this.priorities[newPriority].add(element);
this.taskCount.incrementAndGet(newPriority);
return true;
}
public E dequeue() {
for (int i = 0; i < this.priorities.length; i++) {
if (this.taskCount.get(i) == 0) continue;
E element = priorities[i].poll();
if (element != null) {
this.taskCount.decrementAndGet(i);
this.priorityMap.remove(element);
return element;
}
}
return null;
}
public boolean contains(E element) {
return priorityMap.containsKey(element);
}
public void remove(E element) {
Integer priority = this.priorityMap.remove(element);
if (priority == null) return;
boolean removed = this.priorities[priority].remove(element);
if (removed) this.taskCount.decrementAndGet(priority);
}
public int size() {
return priorityMap.size();
}
public boolean isEmpty() {
return size() == 0;
}
}

View File

@@ -0,0 +1,27 @@
package com.ishland.flowsched.util;
public final class Assertions {
public static void assertTrue(boolean value, String message) {
if (!value) {
final AssertionError error = new AssertionError(message);
error.printStackTrace();
throw error;
}
}
public static void assertTrue(boolean state, String format, Object... args) {
if (!state) {
final AssertionError error = new AssertionError(String.format(format, args));
error.printStackTrace();
throw error;
}
}
public static void assertTrue(boolean value) {
if (!value) {
final AssertionError error = new AssertionError();
error.printStackTrace();
throw error;
}
}
}

View File

@@ -0,0 +1,32 @@
package org.bxteam.divinemc.server.chunk;
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
import net.minecraft.server.level.ServerLevel;
import java.lang.invoke.VarHandle;
public class ChunkRunnable implements Runnable {
public final int chunkX;
public final int chunkZ;
public final ServerLevel world;
private volatile Runnable toRun;
private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(ChunkRunnable.class, "toRun", Runnable.class);
public ChunkRunnable(int chunkX, int chunkZ, ServerLevel world, Runnable run) {
this.chunkX = chunkX;
this.chunkZ = chunkZ;
this.world = world;
this.toRun = run;
}
public void setRunnable(final Runnable run) {
final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run);
if (prev != null) {
throw new IllegalStateException("Runnable already set");
}
}
@Override
public void run() {
((Runnable)TO_RUN_HANDLE.getVolatile(this)).run();
}
}

View File

@@ -0,0 +1,428 @@
package org.bxteam.divinemc.server.chunk;
import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor.RadiusAwarePrioritisedExecutor;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkUpgradeGenericStatusTask;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.GenericDataLoadTask;
import java.lang.invoke.VarHandle;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
public final class ChunkSystemTaskQueue implements PrioritisedExecutor {
private final AtomicLong taskIdGenerator = new AtomicLong();
private final AtomicLong scheduledTasks = new AtomicLong();
private final AtomicLong executedTasks = new AtomicLong();
private final AtomicLong subOrderGenerator = new AtomicLong();
private final AtomicBoolean shutdown = new AtomicBoolean();
private final ConcurrentSkipListMap<ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder, Boolean> tasks = new ConcurrentSkipListMap<>(ChunkSystemTaskQueue.PrioritisedQueuedTask.COMPARATOR);
private final TheChunkSystem chunkSystem;
public ChunkSystemTaskQueue(TheChunkSystem chunkSystem) {
this.chunkSystem = chunkSystem;
}
@Override
public long getTotalTasksScheduled() {
return this.scheduledTasks.get();
}
@Override
public long getTotalTasksExecuted() {
return this.executedTasks.get();
}
@Override
public long generateNextSubOrder() {
return this.subOrderGenerator.getAndIncrement();
}
@Override
public boolean shutdown() {
return !this.shutdown.getAndSet(true);
}
@Override
public boolean isShutdown() {
return this.shutdown.get();
}
@Override
public boolean executeTask() {
for (; ; ) {
final Map.Entry<ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder, Boolean> firstEntry = this.tasks.pollFirstEntry();
if (firstEntry != null) {
final ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder task = firstEntry.getKey();
task.markRemoved();
if (!task.task.execute()) {
continue;
}
return true;
}
return false;
}
}
@Override
public PrioritisedTask createTask(final Runnable task) {
return this.createTask(task, Priority.NORMAL, this.generateNextSubOrder());
}
@Override
public PrioritisedTask createTask(final Runnable task, final Priority priority) {
return this.createTask(task, priority, this.generateNextSubOrder());
}
@Override
public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
return new ChunkSystemTaskQueue.PrioritisedQueuedTask(task, priority, subOrder);
}
@Override
public PrioritisedTask queueTask(final Runnable task) {
return this.queueTask(task, Priority.NORMAL, this.generateNextSubOrder());
}
@Override
public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
return this.queueTask(task, priority, this.generateNextSubOrder());
}
@Override
public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
final ChunkSystemTaskQueue.PrioritisedQueuedTask ret = new ChunkSystemTaskQueue.PrioritisedQueuedTask(task, priority, subOrder);
ret.queue();
return ret;
}
private final class PrioritisedQueuedTask implements PrioritisedExecutor.PrioritisedTask {
public static final Comparator<ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder> COMPARATOR = (final ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder t1, final ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder t2) -> {
final int priorityCompare = t1.priority - t2.priority;
if (priorityCompare != 0) {
return priorityCompare;
}
final int subOrderCompare = Long.compare(t1.subOrder, t2.subOrder);
if (subOrderCompare != 0) {
return subOrderCompare;
}
return Long.compare(t1.id, t2.id);
};
private final long id;
private final Runnable execute;
private Priority priority;
private long subOrder;
private ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder holder;
public PrioritisedQueuedTask(final Runnable execute, final Priority priority, final long subOrder) {
if (!Priority.isValidPriority(priority)) {
throw new IllegalArgumentException("Invalid priority " + priority);
}
this.execute = execute;
this.priority = priority;
this.subOrder = subOrder;
this.id = ChunkSystemTaskQueue.this.taskIdGenerator.getAndIncrement();
}
@Override
public PrioritisedExecutor getExecutor() {
return ChunkSystemTaskQueue.this;
}
@Override
public boolean queue() {
synchronized (this) {
if (this.holder != null || this.priority == Priority.COMPLETING) {
return false;
}
if (ChunkSystemTaskQueue.this.isShutdown()) {
throw new IllegalStateException("Queue is shutdown");
}
this.holder = new Holder(this, this.priority.priority, this.subOrder, this.id);
ChunkSystemTaskQueue.this.scheduledTasks.getAndIncrement();
int priority = this.holder.task.priority.priority;
if (this.holder.task.priority.isHigherOrEqualPriority(Priority.BLOCKING)) {
priority = PriorityHandler.BLOCKING;
} else if (this.holder.task.execute instanceof ChunkUpgradeGenericStatusTask upgradeTask) {
int x = upgradeTask.chunkX;
int z = upgradeTask.chunkZ;
priority = upgradeTask.world.chunkSystemPriorities.priority(x, z);
} else if (this.holder.task.execute instanceof RadiusAwarePrioritisedExecutor.Task task) {
int x = task.chunkX;
int z = task.chunkZ;
if (!(x == 0 && z == 0)) {
priority = task.world.chunkSystemPriorities.priority(x, z);
} // else | infinite radius task, ignore.
} else if (this.holder.task.execute instanceof GenericDataLoadTask<?, ?>.ProcessOffMainTask offMainTask) {
int x = offMainTask.loadTask().chunkX;
int z = offMainTask.loadTask().chunkZ;
priority = offMainTask.loadTask().world.chunkSystemPriorities.priority(x, z);
} else if (this.holder.task.execute instanceof ChunkRunnable chunkRunnable) {
int x = chunkRunnable.chunkX;
int z = chunkRunnable.chunkZ;
priority = chunkRunnable.world.chunkSystemPriorities.priority(x, z);
}
ChunkSystemTaskQueue.this.chunkSystem.schedule(this.holder.task.execute, priority);
}
if (ChunkSystemTaskQueue.this.isShutdown()) {
this.cancel();
throw new IllegalStateException("Queue is shutdown");
}
return true;
}
@Override
public boolean isQueued() {
synchronized (this) {
return this.holder != null && this.priority != Priority.COMPLETING;
}
}
@Override
public boolean cancel() {
synchronized (this) {
if (this.priority == Priority.COMPLETING) {
return false;
}
this.priority = Priority.COMPLETING;
if (this.holder != null) {
if (this.holder.markRemoved()) {
ChunkSystemTaskQueue.this.tasks.remove(this.holder);
}
ChunkSystemTaskQueue.this.executedTasks.getAndIncrement();
}
return true;
}
}
@Override
public boolean execute() {
final boolean increaseExecuted;
synchronized (this) {
if (this.priority == Priority.COMPLETING) {
return false;
}
this.priority = Priority.COMPLETING;
if (increaseExecuted = (this.holder != null)) {
if (this.holder.markRemoved()) {
ChunkSystemTaskQueue.this.tasks.remove(this.holder);
}
}
}
try {
this.execute.run();
return true;
} finally {
if (increaseExecuted) {
ChunkSystemTaskQueue.this.executedTasks.getAndIncrement();
}
}
}
@Override
public Priority getPriority() {
synchronized (this) {
return this.priority;
}
}
@Override
public boolean setPriority(final Priority priority) {
synchronized (this) {
if (this.priority == Priority.COMPLETING || this.priority == priority) {
return false;
}
this.priority = priority;
if (this.holder != null) {
if (this.holder.markRemoved()) {
ChunkSystemTaskQueue.this.tasks.remove(this.holder);
}
this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id);
ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
}
return true;
}
}
@Override
public boolean raisePriority(final Priority priority) {
synchronized (this) {
if (this.priority == Priority.COMPLETING || this.priority.isHigherOrEqualPriority(priority)) {
return false;
}
this.priority = priority;
if (this.holder != null) {
if (this.holder.markRemoved()) {
ChunkSystemTaskQueue.this.tasks.remove(this.holder);
}
this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id);
ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
}
return true;
}
}
@Override
public boolean lowerPriority(Priority priority) {
synchronized (this) {
if (this.priority == Priority.COMPLETING || this.priority.isLowerOrEqualPriority(priority)) {
return false;
}
this.priority = priority;
if (this.holder != null) {
if (this.holder.markRemoved()) {
ChunkSystemTaskQueue.this.tasks.remove(this.holder);
}
this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id);
ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
}
return true;
}
}
@Override
public long getSubOrder() {
synchronized (this) {
return this.subOrder;
}
}
@Override
public boolean setSubOrder(final long subOrder) {
synchronized (this) {
if (this.priority == Priority.COMPLETING || this.subOrder == subOrder) {
return false;
}
this.subOrder = subOrder;
if (this.holder != null) {
if (this.holder.markRemoved()) {
ChunkSystemTaskQueue.this.tasks.remove(this.holder);
}
this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id);
ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
}
return true;
}
}
@Override
public boolean raiseSubOrder(long subOrder) {
synchronized (this) {
if (this.priority == Priority.COMPLETING || this.subOrder >= subOrder) {
return false;
}
this.subOrder = subOrder;
if (this.holder != null) {
if (this.holder.markRemoved()) {
ChunkSystemTaskQueue.this.tasks.remove(this.holder);
}
this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id);
ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
}
return true;
}
}
@Override
public boolean lowerSubOrder(final long subOrder) {
synchronized (this) {
if (this.priority == Priority.COMPLETING || this.subOrder <= subOrder) {
return false;
}
this.subOrder = subOrder;
if (this.holder != null) {
if (this.holder.markRemoved()) {
ChunkSystemTaskQueue.this.tasks.remove(this.holder);
}
this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id);
ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
}
return true;
}
}
@Override
public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) {
synchronized (this) {
if (this.priority == Priority.COMPLETING || (this.priority == priority && this.subOrder == subOrder)) {
return false;
}
this.priority = priority;
this.subOrder = subOrder;
if (this.holder != null) {
if (this.holder.markRemoved()) {
ChunkSystemTaskQueue.this.tasks.remove(this.holder);
}
this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id);
ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
}
return true;
}
}
private static final class Holder {
private static final VarHandle REMOVED_HANDLE = ConcurrentUtil.getVarHandle(ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder.class, "removed", boolean.class);
private final ChunkSystemTaskQueue.PrioritisedQueuedTask task;
private final int priority;
private final long subOrder;
private final long id;
private volatile boolean removed;
private Holder(
final ChunkSystemTaskQueue.PrioritisedQueuedTask task, final int priority, final long subOrder,
final long id
) {
this.task = task;
this.priority = priority;
this.subOrder = subOrder;
this.id = id;
}
public boolean markRemoved() {
return !(boolean) REMOVED_HANDLE.getAndSet((ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder) this, (boolean) true);
}
}
}
}

View File

@@ -0,0 +1,32 @@
package org.bxteam.divinemc.server.chunk;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.level.ChunkPos;
import static ca.spottedleaf.moonrise.common.util.MoonriseConstants.MAX_VIEW_DISTANCE;
public class PriorityHandler {
public static final int MAX_PRIORITY = MAX_VIEW_DISTANCE + 2;
public static final int BLOCKING = 0;
public final ServerLevel level;
public PriorityHandler(ServerLevel world) {
this.level = world;
}
public int priority(int chunkX, int chunkZ) {
int priority = MAX_PRIORITY;
for (final ServerPlayer player : this.level.players()) {
ChunkPos playerChunk = player.chunkPosition();
int playerChunkX = playerChunk.x;
int playerChunkZ = playerChunk.z;
int dist = Math.max(Mth.abs(playerChunkX - chunkX), Mth.abs(playerChunkZ - chunkZ));
int distPriority = Math.max(0, MAX_VIEW_DISTANCE - dist);
priority = Math.min(priority, distPriority);
}
return priority;
}
}

View File

@@ -0,0 +1,355 @@
package org.bxteam.divinemc.server.chunk;
import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
import ca.spottedleaf.concurrentutil.util.Priority;
import com.ishland.flowsched.executor.ExecutorManager;
import org.bxteam.divinemc.util.ThreadBuilder;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
public class TheChunkSystem extends ExecutorManager {
protected final Logger LOGGER = LoggerFactory.getLogger("TheChunkSystem");
private final TheChunkSystem.COWArrayList<TheChunkSystem.ExecutorGroup> executors = new TheChunkSystem.COWArrayList<>(TheChunkSystem.ExecutorGroup.class);
private boolean shutdown;
public TheChunkSystem(final int workerThreadCount, final ThreadBuilder threadInitializer) {
super(workerThreadCount, threadInitializer);
LOGGER.info("Initialized new ChunkSystem with {} allocated threads", workerThreadCount);
}
@Override
public void shutdown() {
synchronized (this) {
this.shutdown = true;
}
super.shutdown();
this.wakeup();
for (final TheChunkSystem.ExecutorGroup group : this.executors.getArray()) {
for (final TheChunkSystem.ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) {
executor.shutdown();
}
}
LOGGER.info("ChunkSystem shutdown complete");
}
private void notifyAllThreads() {
this.wakeup();
}
public TheChunkSystem.ExecutorGroup createExecutorGroup() {
synchronized (this) {
if (this.shutdown) {
throw new IllegalStateException("Queue is shutdown: " + this);
}
final TheChunkSystem.ExecutorGroup ret = new TheChunkSystem.ExecutorGroup();
this.executors.add(ret);
return ret;
}
}
private static final class COWArrayList<E> {
private volatile E[] array;
public COWArrayList(final Class<E> clazz) {
this.array = (E[]) Array.newInstance(clazz, 0);
}
public E[] getArray() {
return this.array;
}
public void add(final E element) {
synchronized (this) {
final E[] array = this.array;
final E[] copy = Arrays.copyOf(array, array.length + 1);
copy[array.length] = element;
this.array = copy;
}
}
public boolean remove(final E element) {
synchronized (this) {
final E[] array = this.array;
int index = -1;
for (int i = 0, len = array.length; i < len; ++i) {
if (array[i] == element) {
index = i;
break;
}
}
if (index == -1) {
return false;
}
final E[] copy = (E[]) Array.newInstance(array.getClass().getComponentType(), array.length - 1);
System.arraycopy(array, 0, copy, 0, index);
System.arraycopy(array, index + 1, copy, index, (array.length - 1) - index);
this.array = copy;
}
return true;
}
}
public final class ExecutorGroup {
private final AtomicLong subOrderGenerator = new AtomicLong();
private final TheChunkSystem.COWArrayList<TheChunkSystem.ExecutorGroup.ThreadPoolExecutor> executors = new TheChunkSystem.COWArrayList<>(TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.class);
private ExecutorGroup() { }
public TheChunkSystem.ExecutorGroup.ThreadPoolExecutor[] getAllExecutors() {
return this.executors.getArray().clone();
}
private TheChunkSystem getThreadPool() {
return TheChunkSystem.this;
}
public TheChunkSystem.ExecutorGroup.@NotNull ThreadPoolExecutor createExecutor() {
synchronized (TheChunkSystem.this) {
if (TheChunkSystem.this.shutdown) {
throw new IllegalStateException("Queue is shutdown: " + TheChunkSystem.this);
}
final TheChunkSystem.ExecutorGroup.ThreadPoolExecutor ret = new TheChunkSystem.ExecutorGroup.ThreadPoolExecutor();
this.executors.add(ret);
return ret;
}
}
public final class ThreadPoolExecutor implements PrioritisedExecutor {
private final ChunkSystemTaskQueue taskBuilder = new ChunkSystemTaskQueue(TheChunkSystem.this);
private volatile boolean halt;
private ThreadPoolExecutor() { }
private TheChunkSystem.ExecutorGroup getGroup() {
return TheChunkSystem.ExecutorGroup.this;
}
private void notifyPriorityShift() {
TheChunkSystem.this.notifyAllThreads();
}
private void notifyScheduled() {
TheChunkSystem.this.notifyAllThreads();
}
/**
* Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed
*/
public void halt() {
this.halt = true;
TheChunkSystem.ExecutorGroup.this.executors.remove(this);
}
public boolean isActive() {
if (this.halt) {
return false;
} else {
if (!this.isShutdown()) {
return true;
}
return !TheChunkSystem.this.globalWorkQueue.isEmpty();
}
}
@Override
public boolean shutdown() {
if (TheChunkSystem.this.globalWorkQueue.isEmpty()) {
TheChunkSystem.ExecutorGroup.this.executors.remove(this);
}
return true;
}
@Override
public boolean isShutdown() {
return TheChunkSystem.this.shutdown;
}
@Override
public long getTotalTasksScheduled() {
return 0; // TODO: implement
}
@Override
public long getTotalTasksExecuted() {
return 0; // TODO: implement
}
@Override
public long generateNextSubOrder() {
return TheChunkSystem.ExecutorGroup.this.subOrderGenerator.getAndIncrement();
}
@Override
public boolean executeTask() {
throw new UnsupportedOperationException("Unable to execute task from ThreadPoolExecutor as interface into FlowSched");
}
@Override
public PrioritisedTask queueTask(final Runnable task) {
final PrioritisedTask ret = this.createTask(task);
ret.queue();
return ret;
}
@Override
public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
final PrioritisedTask ret = this.createTask(task, priority);
ret.queue();
return ret;
}
@Override
public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
final PrioritisedTask ret = this.createTask(task, priority, subOrder);
ret.queue();
return ret;
}
@Override
public PrioritisedTask createTask(final Runnable task) {
return this.createTask(task, Priority.NORMAL);
}
@Override
public PrioritisedTask createTask(final Runnable task, final Priority priority) {
return this.createTask(task, priority, this.generateNextSubOrder());
}
@Override
public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
return new TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.WrappedTask(this.taskBuilder.createTask(task, priority, subOrder));
}
private final class WrappedTask implements PrioritisedTask {
private final PrioritisedTask wrapped;
private WrappedTask(final PrioritisedTask wrapped) {
this.wrapped = wrapped;
}
@Override
public PrioritisedExecutor getExecutor() {
return TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.this;
}
@Override
public boolean queue() {
if (this.wrapped.queue()) {
final Priority priority = this.getPriority();
if (priority != Priority.COMPLETING) {
TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.this.notifyScheduled();
}
return true;
}
return false;
}
@Override
public boolean isQueued() {
return this.wrapped.isQueued();
}
@Override
public boolean cancel() {
return this.wrapped.cancel();
}
@Override
public boolean execute() {
return this.wrapped.execute();
}
@Override
public Priority getPriority() {
return this.wrapped.getPriority();
}
@Override
public boolean setPriority(final Priority priority) {
if (this.wrapped.setPriority(priority)) {
TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.this.notifyPriorityShift();
return true;
}
return false;
}
@Override
public boolean raisePriority(final Priority priority) {
if (this.wrapped.raisePriority(priority)) {
TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.this.notifyPriorityShift();
return true;
}
return false;
}
@Override
public boolean lowerPriority(final Priority priority) {
return this.wrapped.lowerPriority(priority);
}
@Override
public long getSubOrder() {
return this.wrapped.getSubOrder();
}
@Override
public boolean setSubOrder(final long subOrder) {
return this.wrapped.setSubOrder(subOrder);
}
@Override
public boolean raiseSubOrder(final long subOrder) {
return this.wrapped.raiseSubOrder(subOrder);
}
@Override
public boolean lowerSubOrder(final long subOrder) {
return this.wrapped.lowerSubOrder(subOrder);
}
@Override
public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) {
if (this.wrapped.setPriorityAndSubOrder(priority, subOrder)) {
TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.this.notifyPriorityShift();
return true;
}
return false;
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
package org.bxteam.divinemc.util;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public interface ThreadBuilder extends Consumer<Thread> {
AtomicInteger id = new AtomicInteger();
default int getAndIncrementId() {
return id.getAndIncrement();
}
}