mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-13 15:56:07 +01:00
Stick to the plan
- Plans are now generic about a context object - Move default composition impls to SimplyComposedPlan - Remove PlanUtil - Make Synchronizer implement Runnable - Add ContextAgnosticPlan to preserve old behavior - Reduce creation of plan objects - Make BatchingEngine, BatchingStage directly implement plan - Introduce BatchContext for BatchingStage plans - Introduce FrameContext, TickContext for VisualManager plans - Cache separate "recreation plan" for when origin shifts occur
This commit is contained in:
parent
167d417a98
commit
128a77275a
28 changed files with 438 additions and 271 deletions
|
@ -29,5 +29,5 @@ public interface Engine extends InstancerProvider {
|
||||||
|
|
||||||
void delete();
|
void delete();
|
||||||
|
|
||||||
Plan planThisFrame(RenderContext context);
|
Plan<RenderContext> createFramePlan();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,25 @@
|
||||||
package com.jozufozu.flywheel.api.task;
|
package com.jozufozu.flywheel.api.task;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.lib.task.BarrierPlan;
|
public interface Plan<C> {
|
||||||
import com.jozufozu.flywheel.lib.task.NestedPlan;
|
|
||||||
|
|
||||||
public interface Plan {
|
|
||||||
/**
|
/**
|
||||||
* Submit this plan for execution.
|
* Submit this plan for execution.
|
||||||
* <p>
|
* <p>
|
||||||
* You <em>must</em> call {@code onCompletion.run()} when the plan has completed execution.
|
* You <em>must</em> call {@code onCompletion.run()} when the plan has completed execution.
|
||||||
*
|
*
|
||||||
* @param taskExecutor The executor to use for submitting tasks.
|
* @param taskExecutor The executor to use for submitting tasks.
|
||||||
|
* @param context An arbitrary context object that the plan wants to use at runtime.
|
||||||
* @param onCompletion A callback to run when the plan has completed execution, useful for chaining plans.
|
* @param onCompletion A callback to run when the plan has completed execution, useful for chaining plans.
|
||||||
*/
|
*/
|
||||||
void execute(TaskExecutor taskExecutor, Runnable onCompletion);
|
void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion);
|
||||||
|
|
||||||
default void execute(TaskExecutor taskExecutor) {
|
/**
|
||||||
execute(taskExecutor, () -> {
|
* Submit this plan for execution when the caller does not care about the completion of this Plan.
|
||||||
|
*
|
||||||
|
* @param taskExecutor The executor to use for submitting tasks.
|
||||||
|
* @param context An arbitrary context object that the plan wants to use at runtime.
|
||||||
|
*/
|
||||||
|
default void execute(TaskExecutor taskExecutor, C context) {
|
||||||
|
execute(taskExecutor, context, () -> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,10 +29,7 @@ public interface Plan {
|
||||||
* @param plan The plan to execute after this plan.
|
* @param plan The plan to execute after this plan.
|
||||||
* @return The composed plan.
|
* @return The composed plan.
|
||||||
*/
|
*/
|
||||||
default Plan then(Plan plan) {
|
Plan<C> then(Plan<C> plan);
|
||||||
// TODO: AbstractPlan?
|
|
||||||
return new BarrierPlan(this, plan);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new plan that executes this plan and the given plan in parallel.
|
* Create a new plan that executes this plan and the given plan in parallel.
|
||||||
|
@ -36,9 +37,7 @@ public interface Plan {
|
||||||
* @param plan The plan to execute in parallel with this plan.
|
* @param plan The plan to execute in parallel with this plan.
|
||||||
* @return The composed plan.
|
* @return The composed plan.
|
||||||
*/
|
*/
|
||||||
default Plan and(Plan plan) {
|
Plan<C> and(Plan<C> plan);
|
||||||
return NestedPlan.of(this, plan);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If possible, create a new plan that accomplishes everything
|
* If possible, create a new plan that accomplishes everything
|
||||||
|
@ -46,8 +45,5 @@ public interface Plan {
|
||||||
*
|
*
|
||||||
* @return A simplified plan, or this.
|
* @return A simplified plan, or this.
|
||||||
*/
|
*/
|
||||||
default Plan maybeSimplify() {
|
Plan<C> maybeSimplify();
|
||||||
// TODO: plan caching/simplification
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.jozufozu.flywheel.backend.engine.batching;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.joml.FrustumIntersection;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.event.RenderContext;
|
||||||
|
import com.jozufozu.flywheel.lib.math.MatrixUtil;
|
||||||
|
import com.jozufozu.flywheel.util.FlwUtil;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
|
||||||
|
import net.minecraft.client.multiplayer.ClientLevel;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
|
public record BatchContext(ClientLevel level, PoseStack.Pose matrices, FrustumIntersection frustum) {
|
||||||
|
@NotNull
|
||||||
|
static BatchContext create(RenderContext context, BlockPos origin) {
|
||||||
|
Vec3 cameraPos = context.camera()
|
||||||
|
.getPosition();
|
||||||
|
var stack = FlwUtil.copyPoseStack(context.stack());
|
||||||
|
stack.translate(origin.getX() - cameraPos.x, origin.getY() - cameraPos.y, origin.getZ() - cameraPos.z);
|
||||||
|
|
||||||
|
org.joml.Matrix4f proj = MatrixUtil.toJoml(context.viewProjection());
|
||||||
|
proj.translate((float) (origin.getX() - cameraPos.x), (float) (origin.getY() - cameraPos.y), (float) (origin.getZ() - cameraPos.z));
|
||||||
|
|
||||||
|
return new BatchContext(context.level(), stack.last(), new FrustumIntersection(proj));
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,8 +6,6 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.joml.FrustumIntersection;
|
|
||||||
|
|
||||||
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.instance.Instance;
|
import com.jozufozu.flywheel.api.instance.Instance;
|
||||||
|
@ -19,15 +17,13 @@ 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.engine.AbstractEngine;
|
import com.jozufozu.flywheel.backend.engine.AbstractEngine;
|
||||||
import com.jozufozu.flywheel.backend.engine.InstancerKey;
|
import com.jozufozu.flywheel.backend.engine.InstancerKey;
|
||||||
import com.jozufozu.flywheel.lib.math.MatrixUtil;
|
import com.jozufozu.flywheel.lib.task.SimplyComposedPlan;
|
||||||
import com.jozufozu.flywheel.lib.task.NestedPlan;
|
import com.jozufozu.flywheel.lib.task.Synchronizer;
|
||||||
import com.jozufozu.flywheel.util.FlwUtil;
|
|
||||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
|
|
||||||
import net.minecraft.client.renderer.RenderType;
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.world.phys.Vec3;
|
|
||||||
|
|
||||||
public class BatchingEngine extends AbstractEngine {
|
public class BatchingEngine extends AbstractEngine implements SimplyComposedPlan<RenderContext> {
|
||||||
private final BatchingDrawTracker drawTracker = new BatchingDrawTracker();
|
private final BatchingDrawTracker drawTracker = new BatchingDrawTracker();
|
||||||
private final Map<InstancerKey<?>, CPUInstancer<?>> instancers = new HashMap<>();
|
private final Map<InstancerKey<?>, CPUInstancer<?>> instancers = new HashMap<>();
|
||||||
private final List<UninitializedInstancer> uninitializedInstancers = new ArrayList<>();
|
private final List<UninitializedInstancer> uninitializedInstancers = new ArrayList<>();
|
||||||
|
@ -45,33 +41,26 @@ public class BatchingEngine extends AbstractEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Plan planThisFrame(RenderContext context) {
|
public void execute(TaskExecutor taskExecutor, RenderContext context, Runnable onCompletion) {
|
||||||
Vec3 cameraPos = context.camera()
|
|
||||||
.getPosition();
|
|
||||||
var stack = FlwUtil.copyPoseStack(context.stack());
|
|
||||||
stack.translate(renderOrigin.getX() - cameraPos.x, renderOrigin.getY() - cameraPos.y, renderOrigin.getZ() - cameraPos.z);
|
|
||||||
|
|
||||||
org.joml.Matrix4f proj = MatrixUtil.toJoml(context.viewProjection());
|
|
||||||
proj.translate((float) (renderOrigin.getX() - cameraPos.x), (float) (renderOrigin.getY() - cameraPos.y), (float) (renderOrigin.getZ() - cameraPos.z));
|
|
||||||
|
|
||||||
var ctx = new FrameContext(context.level(), stack.last(), new FrustumIntersection(proj));
|
|
||||||
|
|
||||||
flush();
|
flush();
|
||||||
|
|
||||||
var plans = new ArrayList<Plan>();
|
BatchContext ctx = BatchContext.create(context, renderOrigin);
|
||||||
|
|
||||||
|
var sync = new Synchronizer(stages.values()
|
||||||
|
.size(), onCompletion);
|
||||||
|
|
||||||
for (var transformSet : stages.values()) {
|
for (var transformSet : stages.values()) {
|
||||||
plans.add(transformSet.plan(ctx));
|
transformSet.execute(taskExecutor, ctx, sync);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new NestedPlan(plans);
|
@Override
|
||||||
|
public Plan<RenderContext> createFramePlan() {
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) {
|
public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) {
|
||||||
if (!drawTracker.hasStage(stage)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
executor.syncPoint();
|
executor.syncPoint();
|
||||||
drawTracker.draw(stage);
|
drawTracker.draw(stage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,8 @@ import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.event.RenderStage;
|
import com.jozufozu.flywheel.api.event.RenderStage;
|
||||||
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.NestedPlan;
|
import com.jozufozu.flywheel.lib.task.SimplyComposedPlan;
|
||||||
import com.jozufozu.flywheel.lib.task.Synchronizer;
|
import com.jozufozu.flywheel.lib.task.Synchronizer;
|
||||||
|
|
||||||
import net.minecraft.client.renderer.RenderType;
|
import net.minecraft.client.renderer.RenderType;
|
||||||
|
@ -17,7 +16,7 @@ import net.minecraft.client.renderer.RenderType;
|
||||||
/**
|
/**
|
||||||
* All the rendering that happens within a render stage.
|
* All the rendering that happens within a render stage.
|
||||||
*/
|
*/
|
||||||
public class BatchingStage {
|
public class BatchingStage implements SimplyComposedPlan<BatchContext> {
|
||||||
private final RenderStage stage;
|
private final RenderStage stage;
|
||||||
private final BatchingDrawTracker tracker;
|
private final BatchingDrawTracker tracker;
|
||||||
private final Map<RenderType, BufferPlan> buffers = new HashMap<>();
|
private final Map<RenderType, BufferPlan> buffers = new HashMap<>();
|
||||||
|
@ -27,14 +26,20 @@ public class BatchingStage {
|
||||||
this.tracker = tracker;
|
this.tracker = tracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Plan plan(FrameContext ctx) {
|
@Override
|
||||||
var plans = new ArrayList<Plan>();
|
public void execute(TaskExecutor taskExecutor, BatchContext context, Runnable onCompletion) {
|
||||||
|
if (buffers.isEmpty()) {
|
||||||
for (var bufferPlan : buffers.values()) {
|
onCompletion.run();
|
||||||
plans.add(bufferPlan.update(ctx));
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new NestedPlan(plans);
|
taskExecutor.execute(() -> {
|
||||||
|
var sync = new Synchronizer(buffers.size(), onCompletion);
|
||||||
|
|
||||||
|
for (var buffer : buffers.values()) {
|
||||||
|
buffer.execute(taskExecutor, context, sync);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void put(RenderType renderType, TransformCall<?> transformCall) {
|
public void put(RenderType renderType, TransformCall<?> transformCall) {
|
||||||
|
@ -46,40 +51,30 @@ public class BatchingStage {
|
||||||
return buffers.isEmpty();
|
return buffers.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BufferPlan implements Plan {
|
private class BufferPlan implements SimplyComposedPlan<BatchContext> {
|
||||||
private final DrawBuffer buffer;
|
private final DrawBuffer buffer;
|
||||||
private final List<TransformCall<?>> transformCalls = new ArrayList<>();
|
private final List<TransformCall<?>> transformCalls = new ArrayList<>();
|
||||||
private FrameContext ctx;
|
|
||||||
|
|
||||||
public BufferPlan(DrawBuffer drawBuffer) {
|
public BufferPlan(DrawBuffer drawBuffer) {
|
||||||
buffer = drawBuffer;
|
buffer = drawBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Plan update(FrameContext ctx) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
|
|
||||||
// Mark the tracker active by default...
|
|
||||||
tracker.markActive(stage, buffer);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(TransformCall<?> transformCall) {
|
public void add(TransformCall<?> transformCall) {
|
||||||
transformCalls.add(transformCall);
|
transformCalls.add(transformCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
public void execute(TaskExecutor taskExecutor, BatchContext ctx, Runnable onCompletion) {
|
||||||
// Count vertices here to account for instances being added during Visual updates.
|
|
||||||
var vertexCount = setupAndCountVertices();
|
var vertexCount = setupAndCountVertices();
|
||||||
|
|
||||||
if (vertexCount <= 0) {
|
if (vertexCount <= 0) {
|
||||||
// ...then mark it inactive if there's nothing to draw.
|
|
||||||
tracker.markInactive(stage, buffer);
|
|
||||||
onCompletion.run();
|
onCompletion.run();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AtomicInteger vertexCounter = new AtomicInteger(0);
|
tracker.markActive(stage, buffer);
|
||||||
|
|
||||||
|
var vertexCounter = new AtomicInteger(0);
|
||||||
|
|
||||||
buffer.prepare(vertexCount);
|
buffer.prepare(vertexCount);
|
||||||
|
|
||||||
|
@ -88,9 +83,11 @@ public class BatchingStage {
|
||||||
onCompletion.run();
|
onCompletion.run();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var planContext = new TransformCall.PlanContext(ctx, buffer, vertexCounter);
|
||||||
|
|
||||||
for (var transformCall : transformCalls) {
|
for (var transformCall : transformCalls) {
|
||||||
transformCall.plan(ctx, buffer, vertexCounter)
|
transformCall.plan()
|
||||||
.execute(taskExecutor, synchronizer::decrementAndEventuallyRun);
|
.execute(taskExecutor, planContext, synchronizer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package com.jozufozu.flywheel.backend.engine.batching;
|
|
||||||
|
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
|
||||||
|
|
||||||
public record FrameContext(net.minecraft.client.multiplayer.ClientLevel level, PoseStack.Pose matrices,
|
|
||||||
org.joml.FrustumIntersection frustum) {
|
|
||||||
}
|
|
|
@ -12,7 +12,7 @@ import com.jozufozu.flywheel.api.material.Material;
|
||||||
import com.jozufozu.flywheel.api.material.MaterialVertexTransformer;
|
import com.jozufozu.flywheel.api.material.MaterialVertexTransformer;
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.vertex.MutableVertexList;
|
import com.jozufozu.flywheel.api.vertex.MutableVertexList;
|
||||||
import com.jozufozu.flywheel.lib.task.RunOnAllPlan;
|
import com.jozufozu.flywheel.lib.task.RunOnAllWithContextPlan;
|
||||||
import com.jozufozu.flywheel.lib.vertex.VertexTransformations;
|
import com.jozufozu.flywheel.lib.vertex.VertexTransformations;
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import com.mojang.math.Matrix3f;
|
import com.mojang.math.Matrix3f;
|
||||||
|
@ -30,6 +30,8 @@ public class TransformCall<I extends Instance> {
|
||||||
private final InstanceBoundingSphereTransformer<I> boundingSphereTransformer;
|
private final InstanceBoundingSphereTransformer<I> boundingSphereTransformer;
|
||||||
private final Vector4fc boundingSphere;
|
private final Vector4fc boundingSphere;
|
||||||
|
|
||||||
|
private final Plan<PlanContext> drawPlan;
|
||||||
|
|
||||||
public TransformCall(CPUInstancer<I> instancer, Material material, BatchedMeshPool.BufferedMesh mesh) {
|
public TransformCall(CPUInstancer<I> instancer, Material material, BatchedMeshPool.BufferedMesh mesh) {
|
||||||
this.instancer = instancer;
|
this.instancer = instancer;
|
||||||
this.material = material;
|
this.material = material;
|
||||||
|
@ -42,6 +44,32 @@ public class TransformCall<I extends Instance> {
|
||||||
meshVertexCount = mesh.getVertexCount();
|
meshVertexCount = mesh.getVertexCount();
|
||||||
meshByteSize = mesh.size();
|
meshByteSize = mesh.size();
|
||||||
boundingSphere = mesh.mesh.getBoundingSphere();
|
boundingSphere = mesh.mesh.getBoundingSphere();
|
||||||
|
|
||||||
|
drawPlan = RunOnAllWithContextPlan.of(instancer::getAll, (instance, ctx) -> {
|
||||||
|
var boundingSphere = new Vector4f(this.boundingSphere);
|
||||||
|
|
||||||
|
boundingSphereTransformer.transform(boundingSphere, instance);
|
||||||
|
|
||||||
|
if (!ctx.ctx.frustum()
|
||||||
|
.testSphere(boundingSphere.x, boundingSphere.y, boundingSphere.z, boundingSphere.w)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int baseVertex = ctx.vertexCount.getAndAdd(meshVertexCount);
|
||||||
|
|
||||||
|
if (baseVertex + meshVertexCount > ctx.buffer.getVertexCount()) {
|
||||||
|
throw new IndexOutOfBoundsException("Vertex count greater than allocated: " + baseVertex + " + " + meshVertexCount + " > " + ctx.buffer.getVertexCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
var sub = ctx.buffer.slice(baseVertex, meshVertexCount);
|
||||||
|
|
||||||
|
mesh.copyTo(sub.ptr());
|
||||||
|
|
||||||
|
instanceVertexTransformer.transform(sub, instance, ctx.ctx.level());
|
||||||
|
|
||||||
|
materialVertexTransformer.transform(sub, ctx.ctx.level());
|
||||||
|
applyMatrices(sub, ctx.ctx.matrices());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTotalVertexCount() {
|
public int getTotalVertexCount() {
|
||||||
|
@ -52,32 +80,8 @@ public class TransformCall<I extends Instance> {
|
||||||
instancer.update();
|
instancer.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Plan plan(FrameContext ctx, DrawBuffer buffer, final AtomicInteger vertexCount) {
|
public Plan<PlanContext> plan() {
|
||||||
return RunOnAllPlan.of(instancer::getAll, instance -> {
|
return drawPlan;
|
||||||
var boundingSphere = new Vector4f(this.boundingSphere);
|
|
||||||
|
|
||||||
boundingSphereTransformer.transform(boundingSphere, instance);
|
|
||||||
|
|
||||||
if (!ctx.frustum()
|
|
||||||
.testSphere(boundingSphere.x, boundingSphere.y, boundingSphere.z, boundingSphere.w)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int baseVertex = vertexCount.getAndAdd(meshVertexCount);
|
|
||||||
|
|
||||||
if (baseVertex + meshVertexCount > buffer.getVertexCount()) {
|
|
||||||
throw new IndexOutOfBoundsException("Vertex count greater than allocated: " + baseVertex + " + " + meshVertexCount + " > " + buffer.getVertexCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
var sub = buffer.slice(baseVertex, meshVertexCount);
|
|
||||||
|
|
||||||
mesh.copyTo(sub.ptr());
|
|
||||||
|
|
||||||
instanceVertexTransformer.transform(sub, instance, ctx.level());
|
|
||||||
|
|
||||||
materialVertexTransformer.transform(sub, ctx.level());
|
|
||||||
applyMatrices(sub, ctx.matrices());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyMatrices(MutableVertexList vertexList, PoseStack.Pose matrices) {
|
private static void applyMatrices(MutableVertexList vertexList, PoseStack.Pose matrices) {
|
||||||
|
@ -89,4 +93,7 @@ public class TransformCall<I extends Instance> {
|
||||||
VertexTransformations.transformNormal(vertexList, i, normalMatrix);
|
VertexTransformations.transformNormal(vertexList, i, normalMatrix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record PlanContext(BatchContext ctx, DrawBuffer buffer, AtomicInteger vertexCount) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
import com.jozufozu.flywheel.backend.engine.AbstractEngine;
|
import com.jozufozu.flywheel.backend.engine.AbstractEngine;
|
||||||
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.jozufozu.flywheel.lib.task.OnMainThreadPlan;
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
@ -33,8 +33,8 @@ public class IndirectEngine extends AbstractEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Plan planThisFrame(RenderContext context) {
|
public Plan<RenderContext> createFramePlan() {
|
||||||
return PlanUtil.onMainThread(this::flushDrawManager);
|
return OnMainThreadPlan.of(this::flushDrawManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flushDrawManager() {
|
private void flushDrawManager() {
|
||||||
|
|
|
@ -19,7 +19,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.jozufozu.flywheel.lib.task.OnMainThreadPlan;
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
@ -39,8 +39,8 @@ public class InstancingEngine extends AbstractEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Plan planThisFrame(RenderContext context) {
|
public Plan<RenderContext> createFramePlan() {
|
||||||
return PlanUtil.onMainThread(this::flushDrawManager);
|
return OnMainThreadPlan.of(this::flushDrawManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flushDrawManager() {
|
private void flushDrawManager() {
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.jozufozu.flywheel.impl;
|
||||||
|
|
||||||
|
public record TickContext(double cameraX, double cameraY, double cameraZ) {
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.jozufozu.flywheel.impl.visualization;
|
||||||
|
|
||||||
|
import org.joml.FrustumIntersection;
|
||||||
|
|
||||||
|
public record FrameContext(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import com.jozufozu.flywheel.api.backend.Engine;
|
||||||
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.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
import com.jozufozu.flywheel.api.visual.DynamicVisual;
|
import com.jozufozu.flywheel.api.visual.DynamicVisual;
|
||||||
import com.jozufozu.flywheel.api.visual.Effect;
|
import com.jozufozu.flywheel.api.visual.Effect;
|
||||||
import com.jozufozu.flywheel.api.visual.TickableVisual;
|
import com.jozufozu.flywheel.api.visual.TickableVisual;
|
||||||
|
@ -16,12 +17,15 @@ 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;
|
||||||
import com.jozufozu.flywheel.config.FlwConfig;
|
import com.jozufozu.flywheel.config.FlwConfig;
|
||||||
|
import com.jozufozu.flywheel.impl.TickContext;
|
||||||
import com.jozufozu.flywheel.impl.visualization.manager.BlockEntityVisualManager;
|
import com.jozufozu.flywheel.impl.visualization.manager.BlockEntityVisualManager;
|
||||||
import com.jozufozu.flywheel.impl.visualization.manager.EffectVisualManager;
|
import com.jozufozu.flywheel.impl.visualization.manager.EffectVisualManager;
|
||||||
import com.jozufozu.flywheel.impl.visualization.manager.EntityVisualManager;
|
import com.jozufozu.flywheel.impl.visualization.manager.EntityVisualManager;
|
||||||
import com.jozufozu.flywheel.impl.visualization.manager.VisualManager;
|
import com.jozufozu.flywheel.impl.visualization.manager.VisualManager;
|
||||||
import com.jozufozu.flywheel.lib.math.MatrixUtil;
|
import com.jozufozu.flywheel.lib.math.MatrixUtil;
|
||||||
import com.jozufozu.flywheel.lib.task.PlanUtil;
|
import com.jozufozu.flywheel.lib.task.NestedPlan;
|
||||||
|
import com.jozufozu.flywheel.lib.task.SimplyComposedPlan;
|
||||||
|
import com.jozufozu.flywheel.util.Unit;
|
||||||
|
|
||||||
import net.minecraft.core.Vec3i;
|
import net.minecraft.core.Vec3i;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
|
@ -40,13 +44,23 @@ public class VisualWorld implements AutoCloseable {
|
||||||
private final VisualManager<Entity> entities;
|
private final VisualManager<Entity> entities;
|
||||||
private final VisualManager<Effect> effects;
|
private final VisualManager<Effect> effects;
|
||||||
|
|
||||||
|
private final Plan<TickContext> tickPlan;
|
||||||
|
private final Plan<RenderContext> framePlan;
|
||||||
|
|
||||||
public VisualWorld(LevelAccessor level) {
|
public VisualWorld(LevelAccessor level) {
|
||||||
engine = BackendManager.getBackend().createEngine(level);
|
engine = BackendManager.getBackend()
|
||||||
|
.createEngine(level);
|
||||||
taskExecutor = FlwTaskExecutor.get();
|
taskExecutor = FlwTaskExecutor.get();
|
||||||
|
|
||||||
blockEntities = new BlockEntityVisualManager(engine);
|
blockEntities = new BlockEntityVisualManager(engine);
|
||||||
entities = new EntityVisualManager(engine);
|
entities = new EntityVisualManager(engine);
|
||||||
effects = new EffectVisualManager(engine);
|
effects = new EffectVisualManager(engine);
|
||||||
|
|
||||||
|
tickPlan = blockEntities.createTickPlan()
|
||||||
|
.and(entities.createTickPlan())
|
||||||
|
.and(effects.createTickPlan())
|
||||||
|
.maybeSimplify();
|
||||||
|
framePlan = new FramePlan();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Engine getEngine() {
|
public Engine getEngine() {
|
||||||
|
@ -74,11 +88,7 @@ public class VisualWorld implements AutoCloseable {
|
||||||
public void tick(double cameraX, double cameraY, double cameraZ) {
|
public void tick(double cameraX, double cameraY, double cameraZ) {
|
||||||
taskExecutor.syncPoint();
|
taskExecutor.syncPoint();
|
||||||
|
|
||||||
blockEntities.planThisTick(cameraX, cameraY, cameraZ)
|
tickPlan.execute(taskExecutor, new TickContext(cameraX, cameraY, cameraZ));
|
||||||
.and(entities.planThisTick(cameraX, cameraY, cameraZ))
|
|
||||||
.and(effects.planThisTick(cameraX, cameraY, cameraZ))
|
|
||||||
.maybeSimplify()
|
|
||||||
.execute(taskExecutor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,30 +102,7 @@ public class VisualWorld implements AutoCloseable {
|
||||||
public void beginFrame(RenderContext context) {
|
public void beginFrame(RenderContext context) {
|
||||||
taskExecutor.syncPoint();
|
taskExecutor.syncPoint();
|
||||||
|
|
||||||
getManagerPlan(context).then(engine.planThisFrame(context))
|
framePlan.execute(taskExecutor, context);
|
||||||
.maybeSimplify()
|
|
||||||
.execute(taskExecutor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Plan getManagerPlan(RenderContext context) {
|
|
||||||
if (engine.updateRenderOrigin(context.camera())) {
|
|
||||||
return PlanUtil.of(blockEntities::recreateAll, entities::recreateAll, effects::recreateAll);
|
|
||||||
} else {
|
|
||||||
Vec3i renderOrigin = engine.renderOrigin();
|
|
||||||
var cameraPos = context.camera()
|
|
||||||
.getPosition();
|
|
||||||
double cameraX = cameraPos.x;
|
|
||||||
double cameraY = cameraPos.y;
|
|
||||||
double cameraZ = cameraPos.z;
|
|
||||||
|
|
||||||
org.joml.Matrix4f proj = MatrixUtil.toJoml(context.viewProjection());
|
|
||||||
proj.translate((float) (renderOrigin.getX() - cameraX), (float) (renderOrigin.getY() - cameraY), (float) (renderOrigin.getZ() - cameraZ));
|
|
||||||
FrustumIntersection frustum = new FrustumIntersection(proj);
|
|
||||||
|
|
||||||
return blockEntities.planThisFrame(cameraX, cameraY, cameraZ, frustum)
|
|
||||||
.and(entities.planThisFrame(cameraX, cameraY, cameraZ, frustum))
|
|
||||||
.and(effects.planThisFrame(cameraX, cameraY, cameraZ, frustum));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -148,4 +135,37 @@ public class VisualWorld implements AutoCloseable {
|
||||||
public void close() {
|
public void close() {
|
||||||
delete();
|
delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class FramePlan implements SimplyComposedPlan<RenderContext> {
|
||||||
|
private final Plan<Unit> recreationPlan = NestedPlan.of(blockEntities.createRecreationPlan(), entities.createRecreationPlan(), effects.createRecreationPlan());
|
||||||
|
private final Plan<FrameContext> normalPlan = blockEntities.createFramePlan()
|
||||||
|
.and(entities.createFramePlan())
|
||||||
|
.and(effects.createFramePlan());
|
||||||
|
|
||||||
|
private final Plan<RenderContext> enginePlan = engine.createFramePlan();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(TaskExecutor taskExecutor, RenderContext context, Runnable onCompletion) {
|
||||||
|
Runnable then = () -> enginePlan.execute(taskExecutor, context, onCompletion);
|
||||||
|
|
||||||
|
if (engine.updateRenderOrigin(context.camera())) {
|
||||||
|
recreationPlan.execute(taskExecutor, Unit.INSTANCE, then);
|
||||||
|
} else {
|
||||||
|
Vec3i renderOrigin = engine.renderOrigin();
|
||||||
|
var cameraPos = context.camera()
|
||||||
|
.getPosition();
|
||||||
|
double cameraX = cameraPos.x;
|
||||||
|
double cameraY = cameraPos.y;
|
||||||
|
double cameraZ = cameraPos.z;
|
||||||
|
|
||||||
|
org.joml.Matrix4f proj = MatrixUtil.toJoml(context.viewProjection());
|
||||||
|
proj.translate((float) (renderOrigin.getX() - cameraX), (float) (renderOrigin.getY() - cameraY), (float) (renderOrigin.getZ() - cameraZ));
|
||||||
|
FrustumIntersection frustum = new FrustumIntersection(proj);
|
||||||
|
|
||||||
|
var frameContext = new FrameContext(cameraX, cameraY, cameraZ, frustum);
|
||||||
|
|
||||||
|
normalPlan.execute(taskExecutor, frameContext, then);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,20 @@ package com.jozufozu.flywheel.impl.visualization.manager;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
import org.joml.FrustumIntersection;
|
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.visual.DynamicVisual;
|
import com.jozufozu.flywheel.api.visual.DynamicVisual;
|
||||||
import com.jozufozu.flywheel.api.visual.TickableVisual;
|
import com.jozufozu.flywheel.api.visual.TickableVisual;
|
||||||
import com.jozufozu.flywheel.config.FlwConfig;
|
import com.jozufozu.flywheel.config.FlwConfig;
|
||||||
|
import com.jozufozu.flywheel.impl.TickContext;
|
||||||
|
import com.jozufozu.flywheel.impl.visualization.FrameContext;
|
||||||
import com.jozufozu.flywheel.impl.visualization.ratelimit.BandedPrimeLimiter;
|
import com.jozufozu.flywheel.impl.visualization.ratelimit.BandedPrimeLimiter;
|
||||||
import com.jozufozu.flywheel.impl.visualization.ratelimit.DistanceUpdateLimiter;
|
import com.jozufozu.flywheel.impl.visualization.ratelimit.DistanceUpdateLimiter;
|
||||||
import com.jozufozu.flywheel.impl.visualization.ratelimit.NonLimiter;
|
import com.jozufozu.flywheel.impl.visualization.ratelimit.NonLimiter;
|
||||||
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
|
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
|
||||||
import com.jozufozu.flywheel.impl.visualization.storage.Transaction;
|
import com.jozufozu.flywheel.impl.visualization.storage.Transaction;
|
||||||
import com.jozufozu.flywheel.lib.task.RunOnAllPlan;
|
import com.jozufozu.flywheel.lib.task.RunOnAllWithContextPlan;
|
||||||
import com.jozufozu.flywheel.lib.task.SimplePlan;
|
import com.jozufozu.flywheel.lib.task.SimplePlan;
|
||||||
|
import com.jozufozu.flywheel.util.Unit;
|
||||||
|
|
||||||
public abstract class VisualManager<T> {
|
public abstract class VisualManager<T> {
|
||||||
private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>();
|
private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>();
|
||||||
|
@ -67,8 +68,9 @@ public abstract class VisualManager<T> {
|
||||||
queue.add(Transaction.update(obj));
|
queue.add(Transaction.update(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void recreateAll() {
|
public Plan<Unit> createRecreationPlan() {
|
||||||
getStorage().recreateAll();
|
// TODO: parallelize recreation?
|
||||||
|
return SimplePlan.of(getStorage()::recreateAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void invalidate() {
|
public void invalidate() {
|
||||||
|
@ -83,31 +85,31 @@ public abstract class VisualManager<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Plan planThisTick(double cameraX, double cameraY, double cameraZ) {
|
public Plan<TickContext> createTickPlan() {
|
||||||
return SimplePlan.of(() -> {
|
return SimplePlan.<TickContext>of(() -> {
|
||||||
tickLimiter.tick();
|
tickLimiter.tick();
|
||||||
processQueue();
|
processQueue();
|
||||||
})
|
})
|
||||||
.then(RunOnAllPlan.of(getStorage()::getTickableVisuals, instance -> tickInstance(instance, cameraX, cameraY, cameraZ)));
|
.then(RunOnAllWithContextPlan.of(getStorage()::getTickableVisuals, this::tickInstance));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void tickInstance(TickableVisual instance, double cameraX, double cameraY, double cameraZ) {
|
protected void tickInstance(TickableVisual instance, TickContext c) {
|
||||||
if (!instance.decreaseTickRateWithDistance() || tickLimiter.shouldUpdate(instance.distanceSquared(cameraX, cameraY, cameraZ))) {
|
if (!instance.decreaseTickRateWithDistance() || tickLimiter.shouldUpdate(instance.distanceSquared(c.cameraX(), c.cameraY(), c.cameraZ()))) {
|
||||||
instance.tick();
|
instance.tick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Plan planThisFrame(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
|
public Plan<FrameContext> createFramePlan() {
|
||||||
return SimplePlan.of(() -> {
|
return SimplePlan.<FrameContext>of(() -> {
|
||||||
frameLimiter.tick();
|
frameLimiter.tick();
|
||||||
processQueue();
|
processQueue();
|
||||||
})
|
})
|
||||||
.then(RunOnAllPlan.of(getStorage()::getDynamicVisuals, instance -> updateInstance(instance, cameraX, cameraY, cameraZ, frustum)));
|
.then(RunOnAllWithContextPlan.of(getStorage()::getDynamicVisuals, this::updateInstance));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateInstance(DynamicVisual instance, double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
|
protected void updateInstance(DynamicVisual instance, FrameContext c) {
|
||||||
if (!instance.decreaseFramerateWithDistance() || frameLimiter.shouldUpdate(instance.distanceSquared(cameraX, cameraY, cameraZ))) {
|
if (!instance.decreaseFramerateWithDistance() || frameLimiter.shouldUpdate(instance.distanceSquared(c.cameraX(), c.cameraY(), c.cameraZ()))) {
|
||||||
if (instance.isVisible(frustum)) {
|
if (instance.isVisible(c.frustum())) {
|
||||||
instance.beginFrame();
|
instance.beginFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,24 +3,24 @@ package com.jozufozu.flywheel.lib.task;
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
public record BarrierPlan(Plan first, Plan second) implements Plan {
|
public record BarrierPlan<C>(Plan<C> first, Plan<C> second) implements SimplyComposedPlan<C> {
|
||||||
@Override
|
@Override
|
||||||
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) {
|
||||||
first.execute(taskExecutor, () -> second.execute(taskExecutor, onCompletion));
|
first.execute(taskExecutor, context, () -> second.execute(taskExecutor, context, onCompletion));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Plan maybeSimplify() {
|
public Plan<C> maybeSimplify() {
|
||||||
var first = this.first.maybeSimplify();
|
var first = this.first.maybeSimplify();
|
||||||
var second = this.second.maybeSimplify();
|
var second = this.second.maybeSimplify();
|
||||||
|
|
||||||
if (first == UnitPlan.INSTANCE) {
|
if (first == UnitPlan.of()) {
|
||||||
return second;
|
return second;
|
||||||
}
|
}
|
||||||
if (second == UnitPlan.INSTANCE) {
|
if (second == UnitPlan.of()) {
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BarrierPlan(first, second);
|
return new BarrierPlan<>(first, second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
|
public interface ContextAgnosticPlan extends SimplyComposedPlan<Object> {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default <C> Plan<C> cast() {
|
||||||
|
// The context is entirely ignored, so we can safely cast to any context.
|
||||||
|
return (Plan<C>) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void execute(TaskExecutor taskExecutor, Object ignored, Runnable onCompletion) {
|
||||||
|
execute(taskExecutor, onCompletion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute(TaskExecutor taskExecutor, Runnable onCompletion);
|
||||||
|
}
|
|
@ -8,13 +8,14 @@ import com.google.common.collect.ImmutableList;
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
public record NestedPlan(List<Plan> parallelPlans) implements Plan {
|
public record NestedPlan<C>(List<Plan<C>> parallelPlans) implements SimplyComposedPlan<C> {
|
||||||
public static NestedPlan of(Plan... plans) {
|
@SafeVarargs
|
||||||
return new NestedPlan(ImmutableList.copyOf(plans));
|
public static <C> NestedPlan<C> of(Plan<C>... plans) {
|
||||||
|
return new NestedPlan<>(ImmutableList.copyOf(plans));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) {
|
||||||
if (parallelPlans.isEmpty()) {
|
if (parallelPlans.isEmpty()) {
|
||||||
onCompletion.run();
|
onCompletion.run();
|
||||||
return;
|
return;
|
||||||
|
@ -24,28 +25,28 @@ public record NestedPlan(List<Plan> parallelPlans) implements Plan {
|
||||||
|
|
||||||
if (size == 1) {
|
if (size == 1) {
|
||||||
parallelPlans.get(0)
|
parallelPlans.get(0)
|
||||||
.execute(taskExecutor, onCompletion);
|
.execute(taskExecutor, context, onCompletion);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var wait = new Synchronizer(size, onCompletion);
|
var wait = new Synchronizer(size, onCompletion);
|
||||||
for (Plan plan : parallelPlans) {
|
for (var plan : parallelPlans) {
|
||||||
plan.execute(taskExecutor, wait::decrementAndEventuallyRun);
|
plan.execute(taskExecutor, context, wait);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Plan and(Plan plan) {
|
public Plan<C> and(Plan<C> plan) {
|
||||||
return new NestedPlan(ImmutableList.<Plan>builder()
|
return new NestedPlan<>(ImmutableList.<Plan<C>>builder()
|
||||||
.addAll(parallelPlans)
|
.addAll(parallelPlans)
|
||||||
.add(plan)
|
.add(plan)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Plan maybeSimplify() {
|
public Plan<C> maybeSimplify() {
|
||||||
if (parallelPlans.isEmpty()) {
|
if (parallelPlans.isEmpty()) {
|
||||||
return UnitPlan.INSTANCE;
|
return UnitPlan.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parallelPlans.size() == 1) {
|
if (parallelPlans.size() == 1) {
|
||||||
|
@ -54,32 +55,12 @@ public record NestedPlan(List<Plan> parallelPlans) implements Plan {
|
||||||
}
|
}
|
||||||
|
|
||||||
var simplifiedTasks = new ArrayList<Runnable>();
|
var simplifiedTasks = new ArrayList<Runnable>();
|
||||||
var simplifiedPlans = new ArrayList<Plan>();
|
var simplifiedPlans = new ArrayList<Plan<C>>();
|
||||||
|
flattenTasksAndPlans(simplifiedTasks, simplifiedPlans);
|
||||||
var toVisit = new ArrayDeque<>(parallelPlans);
|
|
||||||
while (!toVisit.isEmpty()) {
|
|
||||||
var plan = toVisit.pop()
|
|
||||||
.maybeSimplify();
|
|
||||||
|
|
||||||
if (plan == UnitPlan.INSTANCE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plan instanceof SimplePlan simplePlan) {
|
|
||||||
// merge all simple plans into one
|
|
||||||
simplifiedTasks.addAll(simplePlan.parallelTasks());
|
|
||||||
} else if (plan instanceof NestedPlan nestedPlan) {
|
|
||||||
// inline and re-visit nested plans
|
|
||||||
toVisit.addAll(nestedPlan.parallelPlans());
|
|
||||||
} else {
|
|
||||||
// /shrug
|
|
||||||
simplifiedPlans.add(plan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (simplifiedTasks.isEmpty() && simplifiedPlans.isEmpty()) {
|
if (simplifiedTasks.isEmpty() && simplifiedPlans.isEmpty()) {
|
||||||
// everything got simplified away
|
// everything got simplified away
|
||||||
return UnitPlan.INSTANCE;
|
return UnitPlan.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (simplifiedTasks.isEmpty()) {
|
if (simplifiedTasks.isEmpty()) {
|
||||||
|
@ -88,16 +69,39 @@ public record NestedPlan(List<Plan> parallelPlans) implements Plan {
|
||||||
// we only contained one complex plan, so we can just return that
|
// we only contained one complex plan, so we can just return that
|
||||||
return simplifiedPlans.get(0);
|
return simplifiedPlans.get(0);
|
||||||
}
|
}
|
||||||
return new NestedPlan(simplifiedPlans);
|
return new NestedPlan<>(simplifiedPlans);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (simplifiedPlans.isEmpty()) {
|
if (simplifiedPlans.isEmpty()) {
|
||||||
// we only contained simple plans, so we can just return one
|
// we only contained simple plans, so we can just return one
|
||||||
return new SimplePlan(simplifiedTasks);
|
return SimplePlan.of(simplifiedTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have both simple and complex plans, so we need to create a nested plan
|
// we have both simple and complex plans, so we need to create a nested plan
|
||||||
simplifiedPlans.add(new SimplePlan(simplifiedTasks));
|
simplifiedPlans.add(SimplePlan.of(simplifiedTasks));
|
||||||
return new NestedPlan(simplifiedPlans);
|
return new NestedPlan<>(simplifiedPlans);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flattenTasksAndPlans(List<Runnable> simplifiedTasks, List<Plan<C>> simplifiedPlans) {
|
||||||
|
var toVisit = new ArrayDeque<>(parallelPlans);
|
||||||
|
while (!toVisit.isEmpty()) {
|
||||||
|
var plan = toVisit.pop()
|
||||||
|
.maybeSimplify();
|
||||||
|
|
||||||
|
if (plan == UnitPlan.of()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan instanceof SimplePlan simplePlan) {
|
||||||
|
// merge all simple plans into one
|
||||||
|
simplifiedTasks.addAll(simplePlan.parallelTasks());
|
||||||
|
} else if (plan instanceof NestedPlan<C> nestedPlan) {
|
||||||
|
// inline and re-visit nested plans
|
||||||
|
toVisit.addAll(nestedPlan.parallelPlans());
|
||||||
|
} else {
|
||||||
|
// /shrug
|
||||||
|
simplifiedPlans.add(plan);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,11 @@ package com.jozufozu.flywheel.lib.task;
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
public record OnMainThreadPlan(Runnable task) implements Plan {
|
public record OnMainThreadPlan(Runnable task) implements ContextAgnosticPlan {
|
||||||
|
public static <C> Plan<C> of(Runnable task) {
|
||||||
|
return new OnMainThreadPlan(task).cast();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
||||||
// TODO: detect if we're already on the render thread and just run the task directly
|
// TODO: detect if we're already on the render thread and just run the task directly
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
package com.jozufozu.flywheel.lib.task;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
|
||||||
|
|
||||||
public class PlanUtil {
|
|
||||||
|
|
||||||
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 SimplePlan.of(tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Plan onMainThread(Runnable task) {
|
|
||||||
return new OnMainThreadPlan(task);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,9 +8,9 @@ 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.math.MoreMath;
|
import com.jozufozu.flywheel.lib.math.MoreMath;
|
||||||
|
|
||||||
public record RunOnAllPlan<T>(Supplier<List<T>> listSupplier, Consumer<T> action) implements Plan {
|
public record RunOnAllPlan<T>(Supplier<List<T>> listSupplier, Consumer<T> action) implements ContextAgnosticPlan {
|
||||||
public static <T> Plan of(Supplier<List<T>> iterable, Consumer<T> forEach) {
|
public static <T, C> Plan<C> of(Supplier<List<T>> iterable, Consumer<T> forEach) {
|
||||||
return new RunOnAllPlan<>(iterable, forEach);
|
return new RunOnAllPlan<>(iterable, forEach).cast();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -42,7 +42,7 @@ public record RunOnAllPlan<T>(Supplier<List<T>> listSupplier, Consumer<T> action
|
||||||
int start = Math.max(remaining, 0);
|
int start = Math.max(remaining, 0);
|
||||||
|
|
||||||
var subList = suppliedList.subList(start, end);
|
var subList = suppliedList.subList(start, end);
|
||||||
taskExecutor.execute(() -> processList(subList, synchronizer::decrementAndEventuallyRun));
|
taskExecutor.execute(() -> processList(subList, synchronizer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
import com.jozufozu.flywheel.lib.math.MoreMath;
|
||||||
|
|
||||||
|
public record RunOnAllWithContextPlan<T, C>(Supplier<List<T>> listSupplier,
|
||||||
|
BiConsumer<T, C> action) implements SimplyComposedPlan<C> {
|
||||||
|
public static <T, C> Plan<C> of(Supplier<List<T>> iterable, BiConsumer<T, C> forEach) {
|
||||||
|
return new RunOnAllWithContextPlan<>(iterable, forEach);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) {
|
||||||
|
taskExecutor.execute(() -> {
|
||||||
|
var list = listSupplier.get();
|
||||||
|
final int size = list.size();
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
onCompletion.run();
|
||||||
|
} else if (size <= getChunkingThreshold()) {
|
||||||
|
processList(list, context, onCompletion);
|
||||||
|
} else {
|
||||||
|
dispatchChunks(list, taskExecutor, context, onCompletion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dispatchChunks(List<T> suppliedList, TaskExecutor taskExecutor, C context, Runnable onCompletion) {
|
||||||
|
final int size = suppliedList.size();
|
||||||
|
final int chunkSize = getChunkSize(taskExecutor, size);
|
||||||
|
|
||||||
|
var synchronizer = new Synchronizer(MoreMath.ceilingDiv(size, chunkSize), 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, context, synchronizer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getChunkSize(TaskExecutor taskExecutor, int totalSize) {
|
||||||
|
return MoreMath.ceilingDiv(totalSize, taskExecutor.getThreadCount() * 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processList(List<T> suppliedList, C context, Runnable onCompletion) {
|
||||||
|
for (T t : suppliedList) {
|
||||||
|
action.accept(t, context);
|
||||||
|
}
|
||||||
|
onCompletion.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getChunkingThreshold() {
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,9 +5,13 @@ import java.util.List;
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
public record SimplePlan(List<Runnable> parallelTasks) implements Plan {
|
public record SimplePlan(List<Runnable> parallelTasks) implements ContextAgnosticPlan {
|
||||||
public static Plan of(Runnable... tasks) {
|
public static <C> Plan<C> of(Runnable... tasks) {
|
||||||
return new SimplePlan(List.of(tasks));
|
return new SimplePlan(List.of(tasks)).cast();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <C> Plan<C> of(List<Runnable> tasks) {
|
||||||
|
return new SimplePlan(tasks).cast();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -27,9 +31,9 @@ public record SimplePlan(List<Runnable> parallelTasks) implements Plan {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Plan maybeSimplify() {
|
public Plan<Object> maybeSimplify() {
|
||||||
if (parallelTasks.isEmpty()) {
|
if (parallelTasks.isEmpty()) {
|
||||||
return UnitPlan.INSTANCE;
|
return UnitPlan.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
|
||||||
|
public interface SimplyComposedPlan<C> extends Plan<C> {
|
||||||
|
@Override
|
||||||
|
default Plan<C> then(Plan<C> plan) {
|
||||||
|
return new BarrierPlan<>(this, plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Plan<C> and(Plan<C> plan) {
|
||||||
|
return NestedPlan.of(this, plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Plan<C> maybeSimplify() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package com.jozufozu.flywheel.lib.task;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class Synchronizer {
|
public class Synchronizer implements Runnable {
|
||||||
private final AtomicInteger countDown;
|
private final AtomicInteger countDown;
|
||||||
private final Runnable onCompletion;
|
private final Runnable onCompletion;
|
||||||
|
|
||||||
|
@ -16,4 +16,9 @@ public class Synchronizer {
|
||||||
onCompletion.run();
|
onCompletion.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
decrementAndEventuallyRun();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,34 @@ package com.jozufozu.flywheel.lib.task;
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||||
|
|
||||||
public class UnitPlan implements Plan {
|
public class UnitPlan<C> implements Plan<C> {
|
||||||
public static final UnitPlan INSTANCE = new UnitPlan();
|
private static final UnitPlan<?> INSTANCE = new UnitPlan<>();
|
||||||
|
|
||||||
private UnitPlan() {
|
private UnitPlan() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <C> UnitPlan<C> of() {
|
||||||
|
return (UnitPlan<C>) INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
|
public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) {
|
||||||
onCompletion.run();
|
onCompletion.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Plan<C> then(Plan<C> plan) {
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Plan<C> and(Plan<C> plan) {
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Plan<C> maybeSimplify() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
5
src/main/java/com/jozufozu/flywheel/util/Unit.java
Normal file
5
src/main/java/com/jozufozu/flywheel/util/Unit.java
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package com.jozufozu.flywheel.util;
|
||||||
|
|
||||||
|
public enum Unit {
|
||||||
|
INSTANCE;
|
||||||
|
}
|
|
@ -4,12 +4,13 @@ import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.util.Unit;
|
||||||
|
|
||||||
public class PlanCompositionTest {
|
public class PlanCompositionTest {
|
||||||
|
|
||||||
public static final Runnable NOOP = () -> {
|
public static final Runnable NOOP = () -> {
|
||||||
};
|
};
|
||||||
public static final Plan SIMPLE = SimplePlan.of(NOOP);
|
public static final Plan<Unit> SIMPLE = SimplePlan.of(NOOP);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void nestedPlanAnd() {
|
void nestedPlanAnd() {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
import com.jozufozu.flywheel.backend.task.ParallelTaskExecutor;
|
import com.jozufozu.flywheel.backend.task.ParallelTaskExecutor;
|
||||||
|
import com.jozufozu.flywheel.util.Unit;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ class PlanExecutionTest {
|
||||||
var sequence = new IntArrayList(barriers + 1);
|
var sequence = new IntArrayList(barriers + 1);
|
||||||
var expected = new IntArrayList(barriers + 1);
|
var expected = new IntArrayList(barriers + 1);
|
||||||
|
|
||||||
var plan = SimplePlan.of(() -> sequence.add(1));
|
var plan = SimplePlan.<Unit>of(() -> sequence.add(1));
|
||||||
expected.add(1);
|
expected.add(1);
|
||||||
|
|
||||||
for (int i = 0; i < barriers; i++) {
|
for (int i = 0; i < barriers; i++) {
|
||||||
|
@ -81,7 +82,7 @@ class PlanExecutionTest {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var plan = SimplePlan.of(addOne, addOne, addOne, addOne)
|
var plan = SimplePlan.<Unit>of(addOne, addOne, addOne, addOne)
|
||||||
.then(SimplePlan.of(addTwo, addTwo, addTwo, addTwo));
|
.then(SimplePlan.of(addTwo, addTwo, addTwo, addTwo));
|
||||||
|
|
||||||
runAndWait(plan);
|
runAndWait(plan);
|
||||||
|
@ -92,7 +93,7 @@ class PlanExecutionTest {
|
||||||
@Test
|
@Test
|
||||||
void simpleNestedPlan() {
|
void simpleNestedPlan() {
|
||||||
var sequence = new IntArrayList(2);
|
var sequence = new IntArrayList(2);
|
||||||
var plan = NestedPlan.of(SimplePlan.of(() -> sequence.add(1)));
|
var plan = NestedPlan.of(SimplePlan.<Unit>of(() -> sequence.add(1)));
|
||||||
runAndWait(plan);
|
runAndWait(plan);
|
||||||
assertExpectedSequence(sequence, 1);
|
assertExpectedSequence(sequence, 1);
|
||||||
}
|
}
|
||||||
|
@ -100,7 +101,7 @@ class PlanExecutionTest {
|
||||||
@Test
|
@Test
|
||||||
void manyNestedPlans() {
|
void manyNestedPlans() {
|
||||||
var counter = new AtomicInteger(0);
|
var counter = new AtomicInteger(0);
|
||||||
var count4 = NestedPlan.of(SimplePlan.of(counter::incrementAndGet, counter::incrementAndGet), SimplePlan.of(counter::incrementAndGet, counter::incrementAndGet));
|
var count4 = NestedPlan.<Unit>of(SimplePlan.of(counter::incrementAndGet, counter::incrementAndGet), SimplePlan.of(counter::incrementAndGet, counter::incrementAndGet));
|
||||||
|
|
||||||
runAndWait(count4);
|
runAndWait(count4);
|
||||||
Assertions.assertEquals(4, counter.get());
|
Assertions.assertEquals(4, counter.get());
|
||||||
|
@ -116,7 +117,8 @@ class PlanExecutionTest {
|
||||||
void unitPlan() {
|
void unitPlan() {
|
||||||
var done = new AtomicBoolean(false);
|
var done = new AtomicBoolean(false);
|
||||||
|
|
||||||
UnitPlan.INSTANCE.execute(null, () -> done.set(true));
|
UnitPlan.of()
|
||||||
|
.execute(null, Unit.INSTANCE, () -> done.set(true));
|
||||||
|
|
||||||
Assertions.assertTrue(done.get());
|
Assertions.assertTrue(done.get());
|
||||||
}
|
}
|
||||||
|
@ -126,12 +128,12 @@ class PlanExecutionTest {
|
||||||
var done = new AtomicBoolean(false);
|
var done = new AtomicBoolean(false);
|
||||||
|
|
||||||
SimplePlan.of()
|
SimplePlan.of()
|
||||||
.execute(null, () -> done.set(true));
|
.execute(null, Unit.INSTANCE, () -> done.set(true));
|
||||||
Assertions.assertTrue(done.get());
|
Assertions.assertTrue(done.get());
|
||||||
|
|
||||||
done.set(false);
|
done.set(false);
|
||||||
NestedPlan.of()
|
NestedPlan.of()
|
||||||
.execute(null, () -> done.set(true));
|
.execute(null, Unit.INSTANCE, () -> done.set(true));
|
||||||
Assertions.assertTrue(done.get());
|
Assertions.assertTrue(done.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +142,7 @@ class PlanExecutionTest {
|
||||||
var done = new AtomicBoolean(false);
|
var done = new AtomicBoolean(false);
|
||||||
var plan = new OnMainThreadPlan(() -> done.set(true));
|
var plan = new OnMainThreadPlan(() -> done.set(true));
|
||||||
|
|
||||||
plan.execute(EXECUTOR);
|
plan.execute(EXECUTOR, Unit.INSTANCE);
|
||||||
|
|
||||||
Assertions.assertFalse(done.get());
|
Assertions.assertFalse(done.get());
|
||||||
|
|
||||||
|
@ -153,20 +155,20 @@ class PlanExecutionTest {
|
||||||
Assertions.assertArrayEquals(expected, sequence.toIntArray());
|
Assertions.assertArrayEquals(expected, sequence.toIntArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void runAndWait(Plan plan) {
|
public static void runAndWait(Plan<Unit> plan) {
|
||||||
new TestBarrier(plan).runAndWait();
|
new TestBarrier(plan).runAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class TestBarrier {
|
private static final class TestBarrier {
|
||||||
private final Plan plan;
|
private final Plan<Unit> plan;
|
||||||
private boolean done = false;
|
private boolean done = false;
|
||||||
|
|
||||||
private TestBarrier(Plan plan) {
|
private TestBarrier(Plan<Unit> plan) {
|
||||||
this.plan = plan;
|
this.plan = plan;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runAndWait() {
|
public void runAndWait() {
|
||||||
plan.execute(EXECUTOR, this::doneWithPlan);
|
plan.execute(EXECUTOR, Unit.INSTANCE, this::doneWithPlan);
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
// early exit in case the plan is already done for e.g. UnitPlan
|
// early exit in case the plan is already done for e.g. UnitPlan
|
||||||
|
|
|
@ -4,20 +4,21 @@ import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.task.Plan;
|
import com.jozufozu.flywheel.api.task.Plan;
|
||||||
|
import com.jozufozu.flywheel.util.Unit;
|
||||||
|
|
||||||
public class PlanSimplificationTest {
|
public class PlanSimplificationTest {
|
||||||
|
|
||||||
public static final Runnable NOOP = () -> {
|
public static final Runnable NOOP = () -> {
|
||||||
};
|
};
|
||||||
public static final Plan SIMPLE = SimplePlan.of(NOOP);
|
public static final Plan<Unit> SIMPLE = SimplePlan.of(NOOP);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void emptyPlans() {
|
void emptyPlans() {
|
||||||
var empty = NestedPlan.of();
|
var empty = NestedPlan.of();
|
||||||
Assertions.assertEquals(empty.maybeSimplify(), UnitPlan.INSTANCE);
|
Assertions.assertEquals(empty.maybeSimplify(), UnitPlan.of());
|
||||||
|
|
||||||
var simpleEmpty = SimplePlan.of();
|
var simpleEmpty = SimplePlan.of();
|
||||||
Assertions.assertEquals(simpleEmpty.maybeSimplify(), UnitPlan.INSTANCE);
|
Assertions.assertEquals(simpleEmpty.maybeSimplify(), UnitPlan.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -40,7 +41,7 @@ public class PlanSimplificationTest {
|
||||||
|
|
||||||
Assertions.assertEquals(oneMainThread.maybeSimplify(), mainThreadNoop);
|
Assertions.assertEquals(oneMainThread.maybeSimplify(), mainThreadNoop);
|
||||||
|
|
||||||
var barrier = new BarrierPlan(SIMPLE, SIMPLE);
|
var barrier = new BarrierPlan<>(SIMPLE, SIMPLE);
|
||||||
var oneBarrier = NestedPlan.of(barrier);
|
var oneBarrier = NestedPlan.of(barrier);
|
||||||
|
|
||||||
Assertions.assertEquals(oneBarrier.maybeSimplify(), barrier);
|
Assertions.assertEquals(oneBarrier.maybeSimplify(), barrier);
|
||||||
|
@ -56,29 +57,29 @@ public class PlanSimplificationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void nestedUnitPlan() {
|
void nestedUnitPlan() {
|
||||||
var onlyUnit = NestedPlan.of(UnitPlan.INSTANCE, UnitPlan.INSTANCE, UnitPlan.INSTANCE);
|
var onlyUnit = NestedPlan.of(UnitPlan.of(), UnitPlan.of(), UnitPlan.of());
|
||||||
Assertions.assertEquals(onlyUnit.maybeSimplify(), UnitPlan.INSTANCE);
|
Assertions.assertEquals(onlyUnit.maybeSimplify(), UnitPlan.of());
|
||||||
|
|
||||||
var unitAndSimple = NestedPlan.of(UnitPlan.INSTANCE, UnitPlan.INSTANCE, SIMPLE);
|
var unitAndSimple = NestedPlan.of(UnitPlan.of(), UnitPlan.of(), SIMPLE);
|
||||||
Assertions.assertEquals(unitAndSimple.maybeSimplify(), SIMPLE);
|
Assertions.assertEquals(unitAndSimple.maybeSimplify(), SIMPLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void complexNesting() {
|
void complexNesting() {
|
||||||
var mainThreadNoop = new OnMainThreadPlan(NOOP);
|
var mainThreadNoop = OnMainThreadPlan.<Unit>of(NOOP);
|
||||||
|
|
||||||
var nested = NestedPlan.of(mainThreadNoop, SIMPLE);
|
var nested = NestedPlan.of(mainThreadNoop, SIMPLE);
|
||||||
Assertions.assertEquals(nested.maybeSimplify(), nested); // cannot simplify
|
Assertions.assertEquals(nested.maybeSimplify(), nested); // cannot simplify
|
||||||
|
|
||||||
var barrier = new BarrierPlan(SIMPLE, SIMPLE);
|
var barrier = new BarrierPlan<>(SIMPLE, SIMPLE);
|
||||||
var complex = NestedPlan.of(barrier, nested);
|
var complex = NestedPlan.of(barrier, nested);
|
||||||
Assertions.assertEquals(complex.maybeSimplify(), NestedPlan.of(barrier, mainThreadNoop, SIMPLE));
|
Assertions.assertEquals(complex.maybeSimplify(), NestedPlan.of(barrier, mainThreadNoop, SIMPLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void nestedNoSimple() {
|
void nestedNoSimple() {
|
||||||
var mainThreadNoop = new OnMainThreadPlan(NOOP);
|
var mainThreadNoop = OnMainThreadPlan.<Unit>of(NOOP);
|
||||||
var barrier = new BarrierPlan(SIMPLE, SIMPLE);
|
var barrier = new BarrierPlan<>(SIMPLE, SIMPLE);
|
||||||
var oneMainThread = NestedPlan.of(mainThreadNoop, NestedPlan.of(mainThreadNoop, barrier, barrier));
|
var oneMainThread = NestedPlan.of(mainThreadNoop, NestedPlan.of(mainThreadNoop, barrier, barrier));
|
||||||
|
|
||||||
Assertions.assertEquals(oneMainThread.maybeSimplify(), NestedPlan.of(mainThreadNoop, mainThreadNoop, barrier, barrier));
|
Assertions.assertEquals(oneMainThread.maybeSimplify(), NestedPlan.of(mainThreadNoop, mainThreadNoop, barrier, barrier));
|
||||||
|
@ -86,24 +87,24 @@ public class PlanSimplificationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void manyNestedButJustOneAfterSimplification() {
|
void manyNestedButJustOneAfterSimplification() {
|
||||||
var barrier = new BarrierPlan(SIMPLE, SIMPLE);
|
var barrier = new BarrierPlan<>(SIMPLE, SIMPLE);
|
||||||
var oneMainThread = NestedPlan.of(barrier, NestedPlan.of(UnitPlan.INSTANCE, UnitPlan.INSTANCE));
|
var oneMainThread = NestedPlan.of(barrier, NestedPlan.of(UnitPlan.of(), UnitPlan.of()));
|
||||||
|
|
||||||
Assertions.assertEquals(oneMainThread.maybeSimplify(), barrier);
|
Assertions.assertEquals(oneMainThread.maybeSimplify(), barrier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void barrierPlan() {
|
void barrierPlan() {
|
||||||
var doubleUnit = new BarrierPlan(UnitPlan.INSTANCE, UnitPlan.INSTANCE);
|
var doubleUnit = new BarrierPlan<>(UnitPlan.of(), UnitPlan.of());
|
||||||
Assertions.assertEquals(doubleUnit.maybeSimplify(), UnitPlan.INSTANCE);
|
Assertions.assertEquals(doubleUnit.maybeSimplify(), UnitPlan.of());
|
||||||
|
|
||||||
var simpleThenUnit = new BarrierPlan(SIMPLE, UnitPlan.INSTANCE);
|
var simpleThenUnit = new BarrierPlan<>(SIMPLE, UnitPlan.of());
|
||||||
Assertions.assertEquals(simpleThenUnit.maybeSimplify(), SIMPLE);
|
Assertions.assertEquals(simpleThenUnit.maybeSimplify(), SIMPLE);
|
||||||
|
|
||||||
var unitThenSimple = new BarrierPlan(UnitPlan.INSTANCE, SIMPLE);
|
var unitThenSimple = new BarrierPlan<>(UnitPlan.of(), SIMPLE);
|
||||||
Assertions.assertEquals(unitThenSimple.maybeSimplify(), SIMPLE);
|
Assertions.assertEquals(unitThenSimple.maybeSimplify(), SIMPLE);
|
||||||
|
|
||||||
var simpleThenSimple = new BarrierPlan(SIMPLE, SIMPLE);
|
var simpleThenSimple = new BarrierPlan<>(SIMPLE, SIMPLE);
|
||||||
Assertions.assertEquals(simpleThenSimple.maybeSimplify(), new BarrierPlan(SIMPLE, SIMPLE));
|
Assertions.assertEquals(simpleThenSimple.maybeSimplify(), new BarrierPlan<>(SIMPLE, SIMPLE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue