All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.minestom.server.thread.ThreadDispatcher Maven / Gradle / Ivy

There is a newer version: 7320437640
Show newest version
package net.minestom.server.thread;

import net.minestom.server.Tickable;
import org.jctools.queues.MessagePassingQueue;
import org.jctools.queues.MpscUnboundedArrayQueue;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.function.IntFunction;

/**
 * ThreadDispatcher can be used to dispatch updates (ticks) across a number of "partitions" (such as chunks) that
 * house {@link Tickable} instances (such as entities). The parallelism of such updates is defined when the dispatcher
 * is constructed.
 * 

* It is recommended that {@link Tickable}s being added to a dispatcher also implement {@link AcquirableSource}, as * doing so will allow the user to synchronize external access to them using the {@link Acquirable} API. *

* Instances of this class can be obtained by calling {@link ThreadDispatcher#of(ThreadProvider, int)}, or a similar * overload. * @see Acquirable * @see AcquirableSource */ public final class ThreadDispatcher

{ private final ThreadProvider

provider; private final List threads; // Partition -> dispatching context // Defines how computation is dispatched to the threads private final Map partitions = new WeakHashMap<>(); // Cache to retrieve the threading context from a tickable element private final Map elements = new WeakHashMap<>(); // Queue to update partition linked thread private final ArrayDeque

partitionUpdateQueue = new ArrayDeque<>(); // Requests consumed at the end of each tick private final MessagePassingQueue> updates = new MpscUnboundedArrayQueue<>(1024); private ThreadDispatcher(ThreadProvider

provider, int threadCount, @NotNull IntFunction threadGenerator) { this.provider = provider; TickThread[] threads = new TickThread[threadCount]; Arrays.setAll(threads, threadGenerator); this.threads = List.of(threads); this.threads.forEach(Thread::start); } /** * Creates a new ThreadDispatcher using default thread names (ex. Ms-Tick-n). * * @param provider the {@link ThreadProvider} instance to be used for defining thread IDs * @param threadCount the number of threads to create for this dispatcher * @return a new ThreadDispatcher instance * @param

the dispatcher partition type */ public static

@NotNull ThreadDispatcher

of(@NotNull ThreadProvider

provider, int threadCount) { return new ThreadDispatcher<>(provider, threadCount, TickThread::new); } /** * Creates a new ThreadDispatcher using the caller-provided thread name generator {@code nameGenerator}. This is * useful to disambiguate custom ThreadDispatcher instances from ones used in core Minestom code. * * @param provider the {@link ThreadProvider} instance to be used for defining thread IDs * @param nameGenerator a function that should return unique names, given a thread index * @param threadCount the number of threads to create for this dispatcher * @return a new ThreadDispatcher instance * @param

the dispatcher partition type */ public static

@NotNull ThreadDispatcher

of(@NotNull ThreadProvider

provider, @NotNull IntFunction nameGenerator, int threadCount) { return new ThreadDispatcher<>(provider, threadCount, index -> new TickThread(nameGenerator.apply(index))); } /** * Creates a single-threaded dispatcher that uses default thread names. * * @return a new ThreadDispatcher instance * @param

the dispatcher partition type */ public static

@NotNull ThreadDispatcher

singleThread() { return of(ThreadProvider.counter(), 1); } /** * Gets the unmodifiable list of TickThreads used to dispatch updates. *

* This method is marked internal to reflect {@link TickThread}s own internal status. * * @return the TickThreads used to dispatch updates */ @Unmodifiable @ApiStatus.Internal public @NotNull List<@NotNull TickThread> threads() { return threads; } /** * Prepares the update by creating the {@link TickThread} tasks. * * @param time the tick time in milliseconds */ public synchronized void updateAndAwait(long time) { // Update dispatcher this.updates.drain(update -> { switch (update) { case DispatchUpdate.PartitionLoad

chunkUpdate -> processLoadedPartition(chunkUpdate.partition()); case DispatchUpdate.PartitionUnload

partitionUnload -> processUnloadedPartition(partitionUnload.partition()); case DispatchUpdate.ElementUpdate

elementUpdate -> processUpdatedElement(elementUpdate.tickable(), elementUpdate.partition()); case DispatchUpdate.ElementRemove

elementRemove -> processRemovedElement(elementRemove.tickable()); case null, default -> throw new IllegalStateException("Unknown update type: " + (update == null ? "null" : update.getClass().getSimpleName())); } }); // Tick all partitions CountDownLatch latch = new CountDownLatch(threads.size()); for (TickThread thread : threads) thread.startTick(latch, time); try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } /** * Called at the end of each tick to clear removed tickables, refresh the partition linked to a tickable, and * partition threads based on {@link ThreadProvider#findThread(Object)}. * * @param nanoTimeout max time in nanoseconds to update partitions */ public void refreshThreads(long nanoTimeout) { switch (provider.refreshType()) { case NEVER -> { // Do nothing } case ALWAYS -> { final long currentTime = System.nanoTime(); int counter = partitionUpdateQueue.size(); while (true) { final P partition = partitionUpdateQueue.pollFirst(); if (partition == null) break; // Update chunk's thread Partition partitionEntry = partitions.get(partition); assert partitionEntry != null; final TickThread previous = partitionEntry.thread; final TickThread next = retrieveThread(partition); if (next != previous) { partitionEntry.thread = next; previous.entries().remove(partitionEntry); next.entries().add(partitionEntry); } this.partitionUpdateQueue.addLast(partition); if (--counter <= 0 || System.nanoTime() - currentTime >= nanoTimeout) { break; } } } } } /** * Refreshes all thread as per {@link ThreadDispatcher#refreshThreads(long)}, with a timeout of * {@link Long#MAX_VALUE}. */ public void refreshThreads() { refreshThreads(Long.MAX_VALUE); } /** * Registers a new partition. * * @param partition the partition to register */ public void createPartition(@NotNull P partition) { signalUpdate(new DispatchUpdate.PartitionLoad<>(partition)); } /** * Deletes an existing partition. * * @param partition the partition to delete */ public void deletePartition(@NotNull P partition) { signalUpdate(new DispatchUpdate.PartitionUnload<>(partition)); } /** * Updates a {@link Tickable}, signalling that it is a part of {@code partition}. * * @param tickable the Tickable to update * @param partition the partition the Tickable is part of */ public void updateElement(@NotNull Tickable tickable, @NotNull P partition) { signalUpdate(new DispatchUpdate.ElementUpdate<>(tickable, partition)); } /** * Removes a {@link Tickable}. * * @param tickable the Tickable to remove */ public void removeElement(@NotNull Tickable tickable) { signalUpdate(new DispatchUpdate.ElementRemove<>(tickable)); } /** * Shutdowns all the {@link TickThread tick threads}. *

* Action is irreversible. */ public void shutdown() { this.threads.forEach(TickThread::shutdown); } private TickThread retrieveThread(P partition) { final int threadId = provider.findThread(partition); final int index = Math.abs(threadId) % threads.size(); return threads.get(index); } private void signalUpdate(@NotNull DispatchUpdate

update) { this.updates.relaxedOffer(update); } private void processLoadedPartition(P partition) { if (partitions.containsKey(partition)) return; final TickThread thread = retrieveThread(partition); final Partition partitionEntry = new Partition(thread); thread.entries().add(partitionEntry); this.partitions.put(partition, partitionEntry); this.partitionUpdateQueue.add(partition); if (partition instanceof Tickable tickable) { processUpdatedElement(tickable, partition); } } private void processUnloadedPartition(P partition) { final Partition partitionEntry = partitions.remove(partition); if (partitionEntry != null) { TickThread thread = partitionEntry.thread; thread.entries().remove(partitionEntry); } this.partitionUpdateQueue.remove(partition); if (partition instanceof Tickable tickable) { processRemovedElement(tickable); } } private void processRemovedElement(Tickable tickable) { Partition partition = elements.get(tickable); if (partition != null) { partition.elements.remove(tickable); } } private void processUpdatedElement(Tickable tickable, P partition) { Partition partitionEntry; partitionEntry = elements.get(tickable); // Remove from previous list if (partitionEntry != null) { partitionEntry.elements.remove(tickable); } // Add to new list partitionEntry = partitions.get(partition); if (partitionEntry != null) { this.elements.put(tickable, partitionEntry); partitionEntry.elements.add(tickable); if (tickable instanceof AcquirableSource acquirableSource) { ((AcquirableImpl) acquirableSource.acquirable()).updateThread(partitionEntry.thread()); } } } /** * A data structure which may contain {@link Tickable}s, and is assigned a single {@link TickThread}. */ public static final class Partition { private TickThread thread; private final List elements = new ArrayList<>(); private Partition(TickThread thread) { this.thread = thread; } /** * The {@link TickThread} used by this partition. *

* This method is marked internal to reflect {@link TickThread}s own internal status. * * @return the TickThread used by this partition */ @ApiStatus.Internal public @NotNull TickThread thread() { return thread; } /** * The {@link Tickable}s assigned to this partition. * * @return the tickables assigned to this partition */ public @NotNull List elements() { return elements; } } @ApiStatus.Internal sealed interface DispatchUpdate

permits DispatchUpdate.PartitionLoad, DispatchUpdate.PartitionUnload, DispatchUpdate.ElementUpdate, DispatchUpdate.ElementRemove { record PartitionLoad

(@NotNull P partition) implements DispatchUpdate

{ } record PartitionUnload

(@NotNull P partition) implements DispatchUpdate

{ } record ElementUpdate

(@NotNull Tickable tickable, P partition) implements DispatchUpdate

{ } record ElementRemove

(@NotNull Tickable tickable) implements DispatchUpdate

{ } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy