Merge pull request #436 from Minestom/feature/thread-bound-tasks

Fix GLFW-based framebuffers not using the proper threads
This commit is contained in:
Xavier Niochaut 2021-08-28 16:50:59 +02:00 committed by GitHub
commit 7b046c2225
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 107 additions and 3 deletions

View File

@ -4,6 +4,7 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.map.Framebuffer;
import net.minestom.server.map.MapColors;
import net.minestom.server.timer.Task;
import net.minestom.server.utils.thread.ThreadBindingExecutor;
import org.lwjgl.BufferUtils;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFWErrorCallback;
@ -27,6 +28,8 @@ public abstract class GLFWCapableBuffer {
private final ByteBuffer colorsBuffer;
private boolean onlyMapColors;
private static ThreadBindingExecutor threadBindingPool;
protected GLFWCapableBuffer(int width, int height) {
this(width, height, GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
}
@ -60,6 +63,12 @@ public abstract class GLFWCapableBuffer {
throw new RuntimeException("("+errcode+") Failed to create GLFW Window.");
}
}
synchronized(GLFWCapableBuffer.class) {
if(threadBindingPool == null) {
threadBindingPool = new ThreadBindingExecutor(MinecraftServer.THREAD_COUNT_SCHEDULER, MinecraftServer.THREAD_NAME_SCHEDULER);
}
}
}
public GLFWCapableBuffer unbindContextFromThread() {
@ -80,14 +89,17 @@ public abstract class GLFWCapableBuffer {
return MinecraftServer.getSchedulerManager()
.buildTask(new Runnable() {
private boolean first = true;
@Override
public void run() {
private final Runnable subAction = () -> {
if(first) {
changeRenderingThreadToCurrent();
first = false;
}
render(rendering);
};
@Override
public void run() {
threadBindingPool.execute(subAction);
}
})
.repeat(period)

View File

@ -0,0 +1,92 @@
package net.minestom.server.utils.thread;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Executor service which will always give the same thread to a given Runnable.
* Uses {@link Runnable#hashCode()} to determine the thread to assign.
*/
public class ThreadBindingExecutor extends AbstractExecutorService {
private MinestomThread[] threadExecutors;
/**
* Creates a non-local thread-binding executor
*
* @param nThreads the number of threads
* @param name the name of the thread pool
*/
public ThreadBindingExecutor(int nThreads, String name) {
this(nThreads, name, false);
}
/**
* @param nThreads the number of threads
* @param name the name of the thread pool
* @param local set to true if this executor is only used inside a method and should *not* be kept in the internal list of executors
*/
public ThreadBindingExecutor(int nThreads, String name, boolean local) {
threadExecutors = new MinestomThread[nThreads];
for (int i = 0; i < nThreads; i++) {
threadExecutors[i] = new MinestomThread(1, name, local);
}
}
@Override
public void shutdown() {
for (MinestomThread t : threadExecutors) {
t.shutdown();
}
}
@NotNull
@Override
public List<Runnable> shutdownNow() {
List<Runnable> allTasks = new LinkedList<>();
for (MinestomThread t : threadExecutors) {
allTasks.addAll(t.shutdownNow());
}
return allTasks;
}
@Override
public boolean isShutdown() {
for (MinestomThread t : threadExecutors) {
if(!t.isShutdown())
return false;
}
return true;
}
@Override
public boolean isTerminated() {
for (MinestomThread t : threadExecutors) {
if(!t.isShutdown())
return false;
}
return true;
}
@Override
public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException {
boolean terminated = true;
for (MinestomThread t : threadExecutors) {
terminated &= t.awaitTermination(timeout, unit);
}
return terminated;
}
@Override
public void execute(@NotNull Runnable command) {
int hash = command.hashCode();
if(hash < 0) hash = -hash;
int bucket = hash % threadExecutors.length;
threadExecutors[bucket].execute(command);
}
}