mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-07 12:56:31 +01:00
Parallel planning primitives
- Engines/InstanceManagers now provide Plans for frames/ticks - TODO: batching is broken - TODO: plan caching - Add Plan interface which can be used to compose complex systems of parallel execution - Add Synchronizer to facilitate barriering - Remove WorkGroup - Summary of different primitives below, names need work :ioa: - BarrierPlan: executes one plan then another - NestedPlan: executes many plans at once - OnMainThreadPlan: waits for a syncPoint to execute - RunOnAllPlan: dispatches work over a Supplier<List> - SimplePlan: executes many Runnables at once - UnitPlan: does nothing
This commit is contained in:
parent
72b5968305
commit
fb11f29010
20 changed files with 344 additions and 150 deletions
|
@ -5,6 +5,7 @@ import java.util.List;
|
||||||
import com.jozufozu.flywheel.api.event.RenderContext;
|
import com.jozufozu.flywheel.api.event.RenderContext;
|
||||||
import com.jozufozu.flywheel.api.event.RenderStage;
|
import com.jozufozu.flywheel.api.event.RenderStage;
|
||||||
import com.jozufozu.flywheel.api.instancer.InstancerProvider;
|
import com.jozufozu.flywheel.api.instancer.InstancerProvider;
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
import net.minecraft.client.Camera;
|
import net.minecraft.client.Camera;
|
||||||
|
@ -28,4 +29,6 @@ public interface Engine extends InstancerProvider {
|
||||||
void addDebugInfo(List<String> info);
|
void addDebugInfo(List<String> info);
|
||||||
|
|
||||||
void delete();
|
void delete();
|
||||||
|
|
||||||
|
Plan planThisFrame(RenderContext context);
|
||||||
}
|
}
|
||||||
|
|
42
src/main/java/com/jozufozu/flywheel/api/task/Plan.java
Normal file
42
src/main/java/com/jozufozu/flywheel/api/task/Plan.java
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package com.jozufozu.flywheel.api.task;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.lib.task.BarrierPlan;
|
||||||
|
|
||||||
|
public interface Plan {
|
||||||
|
/**
|
||||||
|
* Submit this plan for execution.
|
||||||
|
* <p>
|
||||||
|
* You <em>must</em> call {@code onCompletion.run()} when the plan has completed execution.
|
||||||
|
*
|
||||||
|
* @param taskExecutor The executor to use for submitting tasks.
|
||||||
|
* @param onCompletion A callback to run when the plan has completed execution, useful for chaining plans.
|
||||||
|
*/
|
||||||
|
void execute(TaskExecutor taskExecutor, Runnable onCompletion);
|
||||||
|
|
||||||
|
default void execute(TaskExecutor taskExecutor) {
|
||||||
|
execute(taskExecutor, () -> {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new plan that executes this plan, then the given plan.
|
||||||
|
*
|
||||||
|
* @param plan The plan to execute after this plan.
|
||||||
|
* @return The new, composed plan.
|
||||||
|
*/
|
||||||
|
default Plan then(Plan plan) {
|
||||||
|
// TODO: AbstractPlan?
|
||||||
|
return new BarrierPlan(this, plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If possible, create a new plan that accomplishes everything
|
||||||
|
* this plan does but with a simpler execution schedule.
|
||||||
|
*
|
||||||
|
* @return A simplified plan, or this.
|
||||||
|
*/
|
||||||
|
default Plan maybeSimplify() {
|
||||||
|
// TODO: plan caching/simplification
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,4 +9,6 @@ public interface TaskExecutor extends Executor {
|
||||||
void syncPoint();
|
void syncPoint();
|
||||||
|
|
||||||
int getThreadCount();
|
int getThreadCount();
|
||||||
|
|
||||||
|
void scheduleForMainThread(Runnable runnable);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ import com.jozufozu.flywheel.api.instancer.Instancer;
|
||||||
import com.jozufozu.flywheel.api.model.Model;
|
import com.jozufozu.flywheel.api.model.Model;
|
||||||
import com.jozufozu.flywheel.api.struct.InstancePart;
|
import com.jozufozu.flywheel.api.struct.InstancePart;
|
||||||
import com.jozufozu.flywheel.api.struct.StructType;
|
import com.jozufozu.flywheel.api.struct.StructType;
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
import com.jozufozu.flywheel.lib.task.PlanUtil;
|
||||||
import com.jozufozu.flywheel.util.FlwUtil;
|
import com.jozufozu.flywheel.util.FlwUtil;
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
|
||||||
|
@ -32,17 +34,34 @@ public class BatchingEngine implements Engine {
|
||||||
public void beginFrame(TaskExecutor executor, RenderContext context) {
|
public void beginFrame(TaskExecutor executor, RenderContext context) {
|
||||||
transformManager.flush();
|
transformManager.flush();
|
||||||
|
|
||||||
Vec3 cameraPos = context.camera().getPosition();
|
Vec3 cameraPos = context.camera()
|
||||||
|
.getPosition();
|
||||||
var stack = FlwUtil.copyPoseStack(context.stack());
|
var stack = FlwUtil.copyPoseStack(context.stack());
|
||||||
stack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
|
stack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
|
||||||
|
|
||||||
// TODO: async task executor barriers
|
|
||||||
executor.syncPoint();
|
executor.syncPoint();
|
||||||
submitTasks(executor, stack.last(), context.level());
|
submitTasks(executor, stack.last(), context.level());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Plan planThisFrame(RenderContext context) {
|
||||||
|
return PlanUtil.of(transformManager::flush)
|
||||||
|
.then(planTransformers(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Plan planTransformers(RenderContext context) {
|
||||||
|
Vec3 cameraPos = context.camera()
|
||||||
|
.getPosition();
|
||||||
|
var stack = FlwUtil.copyPoseStack(context.stack());
|
||||||
|
stack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
|
||||||
|
|
||||||
|
// TODO: planify batching engine
|
||||||
|
return PlanUtil.of();
|
||||||
|
}
|
||||||
|
|
||||||
private void submitTasks(TaskExecutor executor, PoseStack.Pose matrices, ClientLevel level) {
|
private void submitTasks(TaskExecutor executor, PoseStack.Pose matrices, ClientLevel level) {
|
||||||
for (var transformSetEntry : transformManager.getTransformSetsView().entrySet()) {
|
for (var transformSetEntry : transformManager.getTransformSetsView()
|
||||||
|
.entrySet()) {
|
||||||
var stage = transformSetEntry.getKey();
|
var stage = transformSetEntry.getKey();
|
||||||
var transformSet = transformSetEntry.getValue();
|
var transformSet = transformSetEntry.getValue();
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,11 @@ import com.jozufozu.flywheel.api.instancer.Instancer;
|
||||||
import com.jozufozu.flywheel.api.model.Model;
|
import com.jozufozu.flywheel.api.model.Model;
|
||||||
import com.jozufozu.flywheel.api.struct.InstancePart;
|
import com.jozufozu.flywheel.api.struct.InstancePart;
|
||||||
import com.jozufozu.flywheel.api.struct.StructType;
|
import com.jozufozu.flywheel.api.struct.StructType;
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
import com.jozufozu.flywheel.gl.GlStateTracker;
|
import com.jozufozu.flywheel.gl.GlStateTracker;
|
||||||
import com.jozufozu.flywheel.gl.GlTextureUnit;
|
import com.jozufozu.flywheel.gl.GlTextureUnit;
|
||||||
|
import com.jozufozu.flywheel.lib.task.PlanUtil;
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
|
||||||
import net.minecraft.client.Camera;
|
import net.minecraft.client.Camera;
|
||||||
|
@ -40,7 +42,16 @@ public class IndirectEngine implements Engine {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beginFrame(TaskExecutor executor, RenderContext context) {
|
public void beginFrame(TaskExecutor executor, RenderContext context) {
|
||||||
try (var restoreState = GlStateTracker.getRestoreState()) {
|
flushDrawManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Plan planThisFrame(RenderContext context) {
|
||||||
|
return PlanUtil.onMainThread(this::flushDrawManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushDrawManager() {
|
||||||
|
try (var state = GlStateTracker.getRestoreState()) {
|
||||||
drawManager.flush();
|
drawManager.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.jozufozu.flywheel.api.instancer.Instancer;
|
||||||
import com.jozufozu.flywheel.api.model.Model;
|
import com.jozufozu.flywheel.api.model.Model;
|
||||||
import com.jozufozu.flywheel.api.struct.InstancePart;
|
import com.jozufozu.flywheel.api.struct.InstancePart;
|
||||||
import com.jozufozu.flywheel.api.struct.StructType;
|
import com.jozufozu.flywheel.api.struct.StructType;
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
import com.jozufozu.flywheel.backend.Pipelines;
|
import com.jozufozu.flywheel.backend.Pipelines;
|
||||||
import com.jozufozu.flywheel.backend.compile.FlwCompiler;
|
import com.jozufozu.flywheel.backend.compile.FlwCompiler;
|
||||||
|
@ -19,6 +20,7 @@ import com.jozufozu.flywheel.backend.engine.UniformBuffer;
|
||||||
import com.jozufozu.flywheel.gl.GlStateTracker;
|
import com.jozufozu.flywheel.gl.GlStateTracker;
|
||||||
import com.jozufozu.flywheel.gl.GlTextureUnit;
|
import com.jozufozu.flywheel.gl.GlTextureUnit;
|
||||||
import com.jozufozu.flywheel.lib.material.MaterialIndices;
|
import com.jozufozu.flywheel.lib.material.MaterialIndices;
|
||||||
|
import com.jozufozu.flywheel.lib.task.PlanUtil;
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
|
||||||
import net.minecraft.client.Camera;
|
import net.minecraft.client.Camera;
|
||||||
|
@ -47,7 +49,16 @@ public class InstancingEngine implements Engine {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beginFrame(TaskExecutor executor, RenderContext context) {
|
public void beginFrame(TaskExecutor executor, RenderContext context) {
|
||||||
try (var state = GlStateTracker.getRestoreState()) {
|
flushDrawManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Plan planThisFrame(RenderContext context) {
|
||||||
|
return PlanUtil.onMainThread(this::flushDrawManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushDrawManager() {
|
||||||
|
try (var restoreState = GlStateTracker.getRestoreState()) {
|
||||||
drawManager.flush();
|
drawManager.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ package com.jozufozu.flywheel.backend.task;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
@ -34,6 +36,7 @@ public class ParallelTaskExecutor implements TaskExecutor {
|
||||||
|
|
||||||
private final List<WorkerThread> threads = new ArrayList<>();
|
private final List<WorkerThread> threads = new ArrayList<>();
|
||||||
private final Deque<Runnable> taskQueue = new ConcurrentLinkedDeque<>();
|
private final Deque<Runnable> taskQueue = new ConcurrentLinkedDeque<>();
|
||||||
|
private final Queue<Runnable> mainThreadQueue = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
private final Object taskNotifier = new Object();
|
private final Object taskNotifier = new Object();
|
||||||
private final Object tasksCompletedNotifier = new Object();
|
private final Object tasksCompletedNotifier = new Object();
|
||||||
|
@ -118,10 +121,19 @@ public class ParallelTaskExecutor implements TaskExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (taskNotifier) {
|
synchronized (taskNotifier) {
|
||||||
taskNotifier.notify();
|
taskNotifier.notifyAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scheduleForMainThread(Runnable runnable) {
|
||||||
|
if (!running.get()) {
|
||||||
|
throw new IllegalStateException("Executor is stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
mainThreadQueue.add(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for all running tasks to finish.
|
* Wait for all running tasks to finish.
|
||||||
*/
|
*/
|
||||||
|
@ -130,7 +142,7 @@ public class ParallelTaskExecutor implements TaskExecutor {
|
||||||
Runnable task;
|
Runnable task;
|
||||||
|
|
||||||
// Finish everyone else's work...
|
// Finish everyone else's work...
|
||||||
while ((task = taskQueue.pollLast()) != null) {
|
while ((task = pollForSyncPoint()) != null) {
|
||||||
processTask(task);
|
processTask(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +158,15 @@ public class ParallelTaskExecutor implements TaskExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Runnable pollForSyncPoint() {
|
||||||
|
Runnable task = mainThreadQueue.poll();
|
||||||
|
if (task != null) {
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
return taskQueue.pollLast();
|
||||||
|
}
|
||||||
|
|
||||||
public void discardAndAwait() {
|
public void discardAndAwait() {
|
||||||
// Discard everyone else's work...
|
// Discard everyone else's work...
|
||||||
while (taskQueue.pollLast() != null) {
|
while (taskQueue.pollLast() != null) {
|
||||||
|
|
|
@ -13,6 +13,11 @@ public class SerialTaskExecutor implements TaskExecutor {
|
||||||
task.run();
|
task.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scheduleForMainThread(Runnable runnable) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void syncPoint() {
|
public void syncPoint() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import com.jozufozu.flywheel.api.event.RenderStage;
|
||||||
import com.jozufozu.flywheel.api.instance.DynamicInstance;
|
import com.jozufozu.flywheel.api.instance.DynamicInstance;
|
||||||
import com.jozufozu.flywheel.api.instance.TickableInstance;
|
import com.jozufozu.flywheel.api.instance.TickableInstance;
|
||||||
import com.jozufozu.flywheel.api.instance.effect.Effect;
|
import com.jozufozu.flywheel.api.instance.effect.Effect;
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.backend.task.FlwTaskExecutor;
|
import com.jozufozu.flywheel.backend.task.FlwTaskExecutor;
|
||||||
import com.jozufozu.flywheel.backend.task.ParallelTaskExecutor;
|
import com.jozufozu.flywheel.backend.task.ParallelTaskExecutor;
|
||||||
import com.jozufozu.flywheel.config.FlwCommands;
|
import com.jozufozu.flywheel.config.FlwCommands;
|
||||||
|
@ -19,6 +20,7 @@ import com.jozufozu.flywheel.impl.instancing.manager.BlockEntityInstanceManager;
|
||||||
import com.jozufozu.flywheel.impl.instancing.manager.EffectInstanceManager;
|
import com.jozufozu.flywheel.impl.instancing.manager.EffectInstanceManager;
|
||||||
import com.jozufozu.flywheel.impl.instancing.manager.EntityInstanceManager;
|
import com.jozufozu.flywheel.impl.instancing.manager.EntityInstanceManager;
|
||||||
import com.jozufozu.flywheel.impl.instancing.manager.InstanceManager;
|
import com.jozufozu.flywheel.impl.instancing.manager.InstanceManager;
|
||||||
|
import com.jozufozu.flywheel.lib.task.PlanUtil;
|
||||||
|
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.world.level.LevelAccessor;
|
import net.minecraft.world.level.LevelAccessor;
|
||||||
|
@ -68,9 +70,12 @@ public class InstanceWorld implements AutoCloseable {
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public void tick(double cameraX, double cameraY, double cameraZ) {
|
public void tick(double cameraX, double cameraY, double cameraZ) {
|
||||||
blockEntities.tick(taskExecutor, cameraX, cameraY, cameraZ);
|
var blockEntityPlan = blockEntities.planThisTick(cameraX, cameraY, cameraZ);
|
||||||
entities.tick(taskExecutor, cameraX, cameraY, cameraZ);
|
var entityPlan = entities.planThisTick(cameraX, cameraY, cameraZ);
|
||||||
effects.tick(taskExecutor, cameraX, cameraY, cameraZ);
|
var effectPlan = effects.planThisTick(cameraX, cameraY, cameraZ);
|
||||||
|
|
||||||
|
PlanUtil.of(blockEntityPlan, entityPlan, effectPlan)
|
||||||
|
.execute(taskExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,17 +87,16 @@ public class InstanceWorld implements AutoCloseable {
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public void beginFrame(RenderContext context) {
|
public void beginFrame(RenderContext context) {
|
||||||
boolean originChanged = engine.updateRenderOrigin(context.camera());
|
|
||||||
|
|
||||||
if (originChanged) {
|
|
||||||
blockEntities.recreateAll();
|
|
||||||
entities.recreateAll();
|
|
||||||
effects.recreateAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
taskExecutor.syncPoint();
|
taskExecutor.syncPoint();
|
||||||
|
|
||||||
if (!originChanged) {
|
getManagerPlan(context).then(engine.planThisFrame(context))
|
||||||
|
.execute(taskExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Plan getManagerPlan(RenderContext context) {
|
||||||
|
if (engine.updateRenderOrigin(context.camera())) {
|
||||||
|
return PlanUtil.of(blockEntities::recreateAll, entities::recreateAll, effects::recreateAll);
|
||||||
|
} else {
|
||||||
var cameraPos = context.camera()
|
var cameraPos = context.camera()
|
||||||
.getPosition();
|
.getPosition();
|
||||||
double cameraX = cameraPos.x;
|
double cameraX = cameraPos.x;
|
||||||
|
@ -100,12 +104,8 @@ public class InstanceWorld implements AutoCloseable {
|
||||||
double cameraZ = cameraPos.z;
|
double cameraZ = cameraPos.z;
|
||||||
FrustumIntersection culler = context.culler();
|
FrustumIntersection culler = context.culler();
|
||||||
|
|
||||||
blockEntities.beginFrame(taskExecutor, cameraX, cameraY, cameraZ, culler);
|
return PlanUtil.of(blockEntities.planThisFrame(cameraX, cameraY, cameraZ, culler), entities.planThisFrame(cameraX, cameraY, cameraZ, culler), effects.planThisFrame(cameraX, cameraY, cameraZ, culler));
|
||||||
entities.beginFrame(taskExecutor, cameraX, cameraY, cameraZ, culler);
|
|
||||||
effects.beginFrame(taskExecutor, cameraX, cameraY, cameraZ, culler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.beginFrame(taskExecutor, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
package com.jozufozu.flywheel.impl.instancing.manager;
|
package com.jozufozu.flywheel.impl.instancing.manager;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import org.joml.FrustumIntersection;
|
import org.joml.FrustumIntersection;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.instance.DynamicInstance;
|
import com.jozufozu.flywheel.api.instance.DynamicInstance;
|
||||||
import com.jozufozu.flywheel.api.instance.Instance;
|
import com.jozufozu.flywheel.api.instance.Instance;
|
||||||
import com.jozufozu.flywheel.api.instance.TickableInstance;
|
import com.jozufozu.flywheel.api.instance.TickableInstance;
|
||||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.config.FlwConfig;
|
import com.jozufozu.flywheel.config.FlwConfig;
|
||||||
import com.jozufozu.flywheel.impl.instancing.ratelimit.BandedPrimeLimiter;
|
import com.jozufozu.flywheel.impl.instancing.ratelimit.BandedPrimeLimiter;
|
||||||
import com.jozufozu.flywheel.impl.instancing.ratelimit.DistanceUpdateLimiter;
|
import com.jozufozu.flywheel.impl.instancing.ratelimit.DistanceUpdateLimiter;
|
||||||
import com.jozufozu.flywheel.impl.instancing.ratelimit.NonLimiter;
|
import com.jozufozu.flywheel.impl.instancing.ratelimit.NonLimiter;
|
||||||
import com.jozufozu.flywheel.impl.instancing.storage.Storage;
|
import com.jozufozu.flywheel.impl.instancing.storage.Storage;
|
||||||
import com.jozufozu.flywheel.impl.instancing.storage.Transaction;
|
import com.jozufozu.flywheel.impl.instancing.storage.Transaction;
|
||||||
|
import com.jozufozu.flywheel.lib.task.PlanUtil;
|
||||||
|
|
||||||
public abstract class InstanceManager<T> {
|
public abstract class InstanceManager<T> {
|
||||||
private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>();
|
private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>();
|
||||||
|
@ -115,21 +114,10 @@ public abstract class InstanceManager<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Plan planThisTick(double cameraX, double cameraY, double cameraZ) {
|
||||||
* Ticks the InstanceManager.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* {@link TickableInstance}s get ticked.
|
|
||||||
* <br>
|
|
||||||
* Queued updates are processed.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public void tick(TaskExecutor executor, double cameraX, double cameraY, double cameraZ) {
|
|
||||||
tickLimiter.tick();
|
tickLimiter.tick();
|
||||||
processQueue();
|
processQueue();
|
||||||
|
return PlanUtil.runOnAll(getStorage()::getTickableInstances, instance -> tickInstance(instance, cameraX, cameraY, cameraZ));
|
||||||
var instances = getStorage().getTickableInstances();
|
|
||||||
distributeWork(executor, instances, instance -> tickInstance(instance, cameraX, cameraY, cameraZ));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void tickInstance(TickableInstance instance, double cameraX, double cameraY, double cameraZ) {
|
protected void tickInstance(TickableInstance instance, double cameraX, double cameraY, double cameraZ) {
|
||||||
|
@ -138,12 +126,10 @@ public abstract class InstanceManager<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void beginFrame(TaskExecutor executor, double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
|
public Plan planThisFrame(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
|
||||||
frameLimiter.tick();
|
frameLimiter.tick();
|
||||||
processQueue();
|
processQueue();
|
||||||
|
return PlanUtil.runOnAll(getStorage()::getDynamicInstances, instance -> updateInstance(instance, cameraX, cameraY, cameraZ, frustum));
|
||||||
var instances = getStorage().getDynamicInstances();
|
|
||||||
distributeWork(executor, instances, instance -> updateInstance(instance, cameraX, cameraY, cameraZ, frustum));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateInstance(DynamicInstance instance, double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
|
protected void updateInstance(DynamicInstance instance, double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
|
||||||
|
@ -153,21 +139,4 @@ public abstract class InstanceManager<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <I> void distributeWork(TaskExecutor executor, List<I> instances, Consumer<I> action) {
|
|
||||||
final int size = instances.size();
|
|
||||||
final int threadCount = executor.getThreadCount();
|
|
||||||
|
|
||||||
if (threadCount == 1) {
|
|
||||||
executor.execute(() -> instances.forEach(action));
|
|
||||||
} else {
|
|
||||||
final int stride = Math.max(size / (threadCount * 2), 1);
|
|
||||||
for (int start = 0; start < size; start += stride) {
|
|
||||||
int end = Math.min(start + stride, size);
|
|
||||||
|
|
||||||
var sub = instances.subList(start, end);
|
|
||||||
executor.execute(() -> sub.forEach(action));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
package com.jozufozu.flywheel.lib.light;
|
package com.jozufozu.flywheel.lib.light;
|
||||||
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.backend.task.FlwTaskExecutor;
|
|
||||||
import com.jozufozu.flywheel.lib.box.ImmutableBox;
|
import com.jozufozu.flywheel.lib.box.ImmutableBox;
|
||||||
import com.jozufozu.flywheel.lib.task.WorkGroup;
|
|
||||||
import com.jozufozu.flywheel.util.FlwUtil;
|
import com.jozufozu.flywheel.util.FlwUtil;
|
||||||
import com.jozufozu.flywheel.util.WorldAttached;
|
import com.jozufozu.flywheel.util.WorldAttached;
|
||||||
|
|
||||||
|
@ -57,19 +53,6 @@ public class LightUpdater {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tickParallel() {
|
|
||||||
Queue<LightListener> listeners = new ConcurrentLinkedQueue<>();
|
|
||||||
|
|
||||||
WorkGroup.builder()
|
|
||||||
.addTasks(tickingListeners.stream(), listener -> {
|
|
||||||
if (listener.tickLightListener()) {
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onComplete(() -> listeners.forEach(this::addListener))
|
|
||||||
.execute(FlwTaskExecutor.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a listener.
|
* Add a listener.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
|
public record BarrierPlan(Plan first, Plan second) implements Plan {
|
||||||
|
@Override
|
||||||
|
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
||||||
|
first.execute(taskExecutor, () -> second.execute(taskExecutor, onCompletion));
|
||||||
|
}
|
||||||
|
}
|
29
src/main/java/com/jozufozu/flywheel/lib/task/NestedPlan.java
Normal file
29
src/main/java/com/jozufozu/flywheel/lib/task/NestedPlan.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
|
public record NestedPlan(List<Plan> parallelPlans) implements Plan {
|
||||||
|
@Override
|
||||||
|
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
||||||
|
if (parallelPlans.isEmpty()) {
|
||||||
|
onCompletion.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = parallelPlans.size();
|
||||||
|
|
||||||
|
if (size == 1) {
|
||||||
|
parallelPlans.get(0)
|
||||||
|
.execute(taskExecutor, onCompletion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wait = new Synchronizer(size, onCompletion);
|
||||||
|
for (Plan plan : parallelPlans) {
|
||||||
|
plan.execute(taskExecutor, wait::decrementAndEventuallyRun);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
|
public record OnMainThreadPlan(Runnable task) implements Plan {
|
||||||
|
@Override
|
||||||
|
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
||||||
|
// TODO: detect if we're already on the render thread and just run the task directly
|
||||||
|
taskExecutor.scheduleForMainThread(() -> {
|
||||||
|
task.run();
|
||||||
|
onCompletion.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
29
src/main/java/com/jozufozu/flywheel/lib/task/PlanUtil.java
Normal file
29
src/main/java/com/jozufozu/flywheel/lib/task/PlanUtil.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
|
||||||
|
public class PlanUtil {
|
||||||
|
public static <T> Plan runOnAll(Supplier<List<T>> iterable, Consumer<T> forEach) {
|
||||||
|
return new RunOnAllPlan<>(iterable, forEach);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Plan of() {
|
||||||
|
return UnitPlan.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Plan of(Plan... plans) {
|
||||||
|
return new NestedPlan(List.of(plans));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Plan of(Runnable... tasks) {
|
||||||
|
return new SimplePlan(List.of(tasks));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Plan onMainThread(Runnable task) {
|
||||||
|
return new OnMainThreadPlan(task);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
|
public record RunOnAllPlan<T>(Supplier<List<T>> listSupplier, Consumer<T> action) implements Plan {
|
||||||
|
@Override
|
||||||
|
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
||||||
|
taskExecutor.execute(() -> {
|
||||||
|
var list = listSupplier.get();
|
||||||
|
final int size = list.size();
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
onCompletion.run();
|
||||||
|
} else if (size <= getChunkingThreshold(taskExecutor)) {
|
||||||
|
processList(list, onCompletion);
|
||||||
|
} else {
|
||||||
|
dispatchChunks(list, taskExecutor, onCompletion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dispatchChunks(List<T> suppliedList, TaskExecutor taskExecutor, Runnable onCompletion) {
|
||||||
|
final int size = suppliedList.size();
|
||||||
|
final int threadCount = taskExecutor.getThreadCount();
|
||||||
|
|
||||||
|
final int chunkSize = (size + threadCount - 1) / threadCount; // ceiling division
|
||||||
|
final int chunkCount = (size + chunkSize - 1) / chunkSize; // ceiling division
|
||||||
|
|
||||||
|
var synchronizer = new Synchronizer(chunkCount, onCompletion);
|
||||||
|
int remaining = size;
|
||||||
|
|
||||||
|
while (remaining > 0) {
|
||||||
|
int end = remaining;
|
||||||
|
remaining -= chunkSize;
|
||||||
|
int start = Math.max(remaining, 0);
|
||||||
|
|
||||||
|
var subList = suppliedList.subList(start, end);
|
||||||
|
taskExecutor.execute(() -> processList(subList, synchronizer::decrementAndEventuallyRun));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processList(List<T> suppliedList, Runnable onCompletion) {
|
||||||
|
for (T t : suppliedList) {
|
||||||
|
action.accept(t);
|
||||||
|
}
|
||||||
|
onCompletion.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getChunkingThreshold(TaskExecutor taskExecutor) {
|
||||||
|
return 512;
|
||||||
|
}
|
||||||
|
}
|
24
src/main/java/com/jozufozu/flywheel/lib/task/SimplePlan.java
Normal file
24
src/main/java/com/jozufozu/flywheel/lib/task/SimplePlan.java
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
|
public record SimplePlan(List<Runnable> parallelTasks) implements Plan {
|
||||||
|
@Override
|
||||||
|
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
||||||
|
if (parallelTasks.isEmpty()) {
|
||||||
|
onCompletion.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var synchronizer = new Synchronizer(parallelTasks.size(), onCompletion);
|
||||||
|
for (Runnable task : parallelTasks) {
|
||||||
|
taskExecutor.execute(() -> {
|
||||||
|
task.run();
|
||||||
|
synchronizer.decrementAndEventuallyRun();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class Synchronizer {
|
||||||
|
private final AtomicInteger countDown;
|
||||||
|
private final Runnable onCompletion;
|
||||||
|
|
||||||
|
public Synchronizer(int countDown, Runnable onCompletion) {
|
||||||
|
this.countDown = new AtomicInteger(countDown);
|
||||||
|
this.onCompletion = onCompletion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decrementAndEventuallyRun() {
|
||||||
|
if (countDown.decrementAndGet() == 0) {
|
||||||
|
onCompletion.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/main/java/com/jozufozu/flywheel/lib/task/UnitPlan.java
Normal file
16
src/main/java/com/jozufozu/flywheel/lib/task/UnitPlan.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
|
public class UnitPlan implements Plan {
|
||||||
|
public static final UnitPlan INSTANCE = new UnitPlan();
|
||||||
|
|
||||||
|
private UnitPlan() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
||||||
|
onCompletion.run();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,72 +0,0 @@
|
||||||
package com.jozufozu.flywheel.lib.task;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
public final class WorkGroup {
|
|
||||||
public static void run(Iterator<Runnable> tasks, Executor executor) {
|
|
||||||
tasks.forEachRemaining(executor::execute);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void run(Iterator<Runnable> tasks, @Nullable Runnable finalizer, Executor executor) {
|
|
||||||
if (finalizer == null) {
|
|
||||||
run(tasks, executor);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AtomicInteger incompleteTaskCounter = new AtomicInteger(0);
|
|
||||||
tasks.forEachRemaining(task -> {
|
|
||||||
incompleteTaskCounter.incrementAndGet();
|
|
||||||
executor.execute(() -> {
|
|
||||||
task.run();
|
|
||||||
if (incompleteTaskCounter.decrementAndGet() == 0) {
|
|
||||||
executor.execute(finalizer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
@Nullable
|
|
||||||
private Runnable finalizer;
|
|
||||||
private Stream<Runnable> tasks;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> Builder addTasks(Stream<T> iterable, Consumer<T> consumer) {
|
|
||||||
return addTasks(iterable.map(it -> () -> consumer.accept(it)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addTasks(Stream<Runnable> tasks) {
|
|
||||||
if (this.tasks == null) {
|
|
||||||
this.tasks = tasks;
|
|
||||||
} else {
|
|
||||||
this.tasks = Stream.concat(this.tasks, tasks);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder onComplete(Runnable runnable) {
|
|
||||||
this.finalizer = runnable;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void execute(Executor executor) {
|
|
||||||
if (tasks == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
run(tasks.iterator(), finalizer, executor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue