diff --git a/src/main/java/com/jozufozu/flywheel/api/backend/Engine.java b/src/main/java/com/jozufozu/flywheel/api/backend/Engine.java index 2d54ca396..e830407b4 100644 --- a/src/main/java/com/jozufozu/flywheel/api/backend/Engine.java +++ b/src/main/java/com/jozufozu/flywheel/api/backend/Engine.java @@ -29,5 +29,5 @@ public interface Engine extends InstancerProvider { void delete(); - Plan planThisFrame(RenderContext context); + Plan createFramePlan(); } diff --git a/src/main/java/com/jozufozu/flywheel/api/task/Plan.java b/src/main/java/com/jozufozu/flywheel/api/task/Plan.java index 5334c454c..2993f885f 100644 --- a/src/main/java/com/jozufozu/flywheel/api/task/Plan.java +++ b/src/main/java/com/jozufozu/flywheel/api/task/Plan.java @@ -1,21 +1,25 @@ package com.jozufozu.flywheel.api.task; -import com.jozufozu.flywheel.lib.task.BarrierPlan; -import com.jozufozu.flywheel.lib.task.NestedPlan; - -public interface Plan { +public interface Plan { /** * Submit this plan for execution. *

* You must call {@code onCompletion.run()} when the plan has completed execution. * * @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. */ - 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. * @return The composed plan. */ - default Plan then(Plan plan) { - // TODO: AbstractPlan? - return new BarrierPlan(this, plan); - } + Plan then(Plan plan); /** * 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. * @return The composed plan. */ - default Plan and(Plan plan) { - return NestedPlan.of(this, plan); - } + Plan and(Plan plan); /** * If possible, create a new plan that accomplishes everything @@ -46,8 +45,5 @@ public interface Plan { * * @return A simplified plan, or this. */ - default Plan maybeSimplify() { - // TODO: plan caching/simplification - return this; - } + Plan maybeSimplify(); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchContext.java b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchContext.java new file mode 100644 index 000000000..206dc04cf --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchContext.java @@ -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)); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingEngine.java index 2e509c9e0..6c1b818af 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingEngine.java @@ -6,8 +6,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.joml.FrustumIntersection; - import com.jozufozu.flywheel.api.event.RenderContext; import com.jozufozu.flywheel.api.event.RenderStage; 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.backend.engine.AbstractEngine; import com.jozufozu.flywheel.backend.engine.InstancerKey; -import com.jozufozu.flywheel.lib.math.MatrixUtil; -import com.jozufozu.flywheel.lib.task.NestedPlan; -import com.jozufozu.flywheel.util.FlwUtil; +import com.jozufozu.flywheel.lib.task.SimplyComposedPlan; +import com.jozufozu.flywheel.lib.task.Synchronizer; import com.mojang.blaze3d.vertex.VertexFormat; import net.minecraft.client.renderer.RenderType; -import net.minecraft.world.phys.Vec3; -public class BatchingEngine extends AbstractEngine { +public class BatchingEngine extends AbstractEngine implements SimplyComposedPlan { private final BatchingDrawTracker drawTracker = new BatchingDrawTracker(); private final Map, CPUInstancer> instancers = new HashMap<>(); private final List uninitializedInstancers = new ArrayList<>(); @@ -45,33 +41,26 @@ public class BatchingEngine extends AbstractEngine { } @Override - public Plan planThisFrame(RenderContext context) { - 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)); - + public void execute(TaskExecutor taskExecutor, RenderContext context, Runnable onCompletion) { flush(); - var plans = new ArrayList(); + BatchContext ctx = BatchContext.create(context, renderOrigin); + + var sync = new Synchronizer(stages.values() + .size(), onCompletion); for (var transformSet : stages.values()) { - plans.add(transformSet.plan(ctx)); + transformSet.execute(taskExecutor, ctx, sync); } + } - return new NestedPlan(plans); + @Override + public Plan createFramePlan() { + return this; } @Override public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) { - if (!drawTracker.hasStage(stage)) { - return; - } executor.syncPoint(); drawTracker.draw(stage); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingStage.java b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingStage.java index 72005e251..e1ace2c86 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingStage.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingStage.java @@ -7,9 +7,8 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; 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.lib.task.NestedPlan; +import com.jozufozu.flywheel.lib.task.SimplyComposedPlan; import com.jozufozu.flywheel.lib.task.Synchronizer; 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. */ -public class BatchingStage { +public class BatchingStage implements SimplyComposedPlan { private final RenderStage stage; private final BatchingDrawTracker tracker; private final Map buffers = new HashMap<>(); @@ -27,14 +26,20 @@ public class BatchingStage { this.tracker = tracker; } - public Plan plan(FrameContext ctx) { - var plans = new ArrayList(); - - for (var bufferPlan : buffers.values()) { - plans.add(bufferPlan.update(ctx)); + @Override + public void execute(TaskExecutor taskExecutor, BatchContext context, Runnable onCompletion) { + if (buffers.isEmpty()) { + onCompletion.run(); + 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) { @@ -46,40 +51,30 @@ public class BatchingStage { return buffers.isEmpty(); } - private class BufferPlan implements Plan { + private class BufferPlan implements SimplyComposedPlan { private final DrawBuffer buffer; private final List> transformCalls = new ArrayList<>(); - private FrameContext ctx; public BufferPlan(DrawBuffer 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) { transformCalls.add(transformCall); } @Override - public void execute(TaskExecutor taskExecutor, Runnable onCompletion) { - // Count vertices here to account for instances being added during Visual updates. + public void execute(TaskExecutor taskExecutor, BatchContext ctx, Runnable onCompletion) { var vertexCount = setupAndCountVertices(); if (vertexCount <= 0) { - // ...then mark it inactive if there's nothing to draw. - tracker.markInactive(stage, buffer); onCompletion.run(); return; } - AtomicInteger vertexCounter = new AtomicInteger(0); + tracker.markActive(stage, buffer); + + var vertexCounter = new AtomicInteger(0); buffer.prepare(vertexCount); @@ -88,9 +83,11 @@ public class BatchingStage { onCompletion.run(); }); + var planContext = new TransformCall.PlanContext(ctx, buffer, vertexCounter); + for (var transformCall : transformCalls) { - transformCall.plan(ctx, buffer, vertexCounter) - .execute(taskExecutor, synchronizer::decrementAndEventuallyRun); + transformCall.plan() + .execute(taskExecutor, planContext, synchronizer); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/FrameContext.java b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/FrameContext.java deleted file mode 100644 index 0b9407d4c..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/FrameContext.java +++ /dev/null @@ -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) { -} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/TransformCall.java b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/TransformCall.java index 9808a3b44..25fb8c94b 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/TransformCall.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/TransformCall.java @@ -12,7 +12,7 @@ import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.MaterialVertexTransformer; import com.jozufozu.flywheel.api.task.Plan; 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.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Matrix3f; @@ -30,6 +30,8 @@ public class TransformCall { private final InstanceBoundingSphereTransformer boundingSphereTransformer; private final Vector4fc boundingSphere; + private final Plan drawPlan; + public TransformCall(CPUInstancer instancer, Material material, BatchedMeshPool.BufferedMesh mesh) { this.instancer = instancer; this.material = material; @@ -42,6 +44,32 @@ public class TransformCall { meshVertexCount = mesh.getVertexCount(); meshByteSize = mesh.size(); 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() { @@ -52,32 +80,8 @@ public class TransformCall { instancer.update(); } - public Plan plan(FrameContext ctx, DrawBuffer buffer, final AtomicInteger vertexCount) { - return RunOnAllPlan.of(instancer::getAll, instance -> { - 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()); - }); + public Plan plan() { + return drawPlan; } private static void applyMatrices(MutableVertexList vertexList, PoseStack.Pose matrices) { @@ -89,4 +93,7 @@ public class TransformCall { VertexTransformations.transformNormal(vertexList, i, normalMatrix); } } + + public record PlanContext(BatchContext ctx, DrawBuffer buffer, AtomicInteger vertexCount) { + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java index 489c465ee..4746f7df9 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java @@ -15,7 +15,7 @@ import com.jozufozu.flywheel.api.task.TaskExecutor; import com.jozufozu.flywheel.backend.engine.AbstractEngine; import com.jozufozu.flywheel.gl.GlStateTracker; 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 net.minecraft.client.Minecraft; @@ -33,8 +33,8 @@ public class IndirectEngine extends AbstractEngine { } @Override - public Plan planThisFrame(RenderContext context) { - return PlanUtil.onMainThread(this::flushDrawManager); + public Plan createFramePlan() { + return OnMainThreadPlan.of(this::flushDrawManager); } private void flushDrawManager() { diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java index e394c23c0..5508b1a88 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java @@ -19,7 +19,7 @@ import com.jozufozu.flywheel.backend.engine.UniformBuffer; import com.jozufozu.flywheel.gl.GlStateTracker; import com.jozufozu.flywheel.gl.GlTextureUnit; 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 net.minecraft.client.Minecraft; @@ -39,8 +39,8 @@ public class InstancingEngine extends AbstractEngine { } @Override - public Plan planThisFrame(RenderContext context) { - return PlanUtil.onMainThread(this::flushDrawManager); + public Plan createFramePlan() { + return OnMainThreadPlan.of(this::flushDrawManager); } private void flushDrawManager() { diff --git a/src/main/java/com/jozufozu/flywheel/impl/TickContext.java b/src/main/java/com/jozufozu/flywheel/impl/TickContext.java new file mode 100644 index 000000000..a2c9d5195 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/impl/TickContext.java @@ -0,0 +1,4 @@ +package com.jozufozu.flywheel.impl; + +public record TickContext(double cameraX, double cameraY, double cameraZ) { +} diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/FrameContext.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/FrameContext.java new file mode 100644 index 000000000..b3dd3ef6d --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/FrameContext.java @@ -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) { +} diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualWorld.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualWorld.java index 6d5074082..d2cd7657b 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualWorld.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualWorld.java @@ -9,6 +9,7 @@ import com.jozufozu.flywheel.api.backend.Engine; import com.jozufozu.flywheel.api.event.RenderContext; 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.visual.DynamicVisual; import com.jozufozu.flywheel.api.visual.Effect; 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.config.FlwCommands; 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.EffectVisualManager; import com.jozufozu.flywheel.impl.visualization.manager.EntityVisualManager; import com.jozufozu.flywheel.impl.visualization.manager.VisualManager; 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.world.entity.Entity; @@ -40,13 +44,23 @@ public class VisualWorld implements AutoCloseable { private final VisualManager entities; private final VisualManager effects; + private final Plan tickPlan; + private final Plan framePlan; + public VisualWorld(LevelAccessor level) { - engine = BackendManager.getBackend().createEngine(level); + engine = BackendManager.getBackend() + .createEngine(level); taskExecutor = FlwTaskExecutor.get(); blockEntities = new BlockEntityVisualManager(engine); entities = new EntityVisualManager(engine); effects = new EffectVisualManager(engine); + + tickPlan = blockEntities.createTickPlan() + .and(entities.createTickPlan()) + .and(effects.createTickPlan()) + .maybeSimplify(); + framePlan = new FramePlan(); } public Engine getEngine() { @@ -74,11 +88,7 @@ public class VisualWorld implements AutoCloseable { public void tick(double cameraX, double cameraY, double cameraZ) { taskExecutor.syncPoint(); - blockEntities.planThisTick(cameraX, cameraY, cameraZ) - .and(entities.planThisTick(cameraX, cameraY, cameraZ)) - .and(effects.planThisTick(cameraX, cameraY, cameraZ)) - .maybeSimplify() - .execute(taskExecutor); + tickPlan.execute(taskExecutor, new TickContext(cameraX, cameraY, cameraZ)); } /** @@ -92,30 +102,7 @@ public class VisualWorld implements AutoCloseable { public void beginFrame(RenderContext context) { taskExecutor.syncPoint(); - getManagerPlan(context).then(engine.planThisFrame(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)); - } + framePlan.execute(taskExecutor, context); } /** @@ -148,4 +135,37 @@ public class VisualWorld implements AutoCloseable { public void close() { delete(); } + + private class FramePlan implements SimplyComposedPlan { + private final Plan recreationPlan = NestedPlan.of(blockEntities.createRecreationPlan(), entities.createRecreationPlan(), effects.createRecreationPlan()); + private final Plan normalPlan = blockEntities.createFramePlan() + .and(entities.createFramePlan()) + .and(effects.createFramePlan()); + + private final Plan 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); + } + } + } } diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/VisualManager.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/VisualManager.java index 25f04ef1f..6fe623599 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/VisualManager.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/VisualManager.java @@ -3,19 +3,20 @@ package com.jozufozu.flywheel.impl.visualization.manager; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import org.joml.FrustumIntersection; - import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.api.visual.DynamicVisual; import com.jozufozu.flywheel.api.visual.TickableVisual; 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.DistanceUpdateLimiter; import com.jozufozu.flywheel.impl.visualization.ratelimit.NonLimiter; import com.jozufozu.flywheel.impl.visualization.storage.Storage; 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.util.Unit; public abstract class VisualManager { private final Queue> queue = new ConcurrentLinkedQueue<>(); @@ -67,8 +68,9 @@ public abstract class VisualManager { queue.add(Transaction.update(obj)); } - public void recreateAll() { - getStorage().recreateAll(); + public Plan createRecreationPlan() { + // TODO: parallelize recreation? + return SimplePlan.of(getStorage()::recreateAll); } public void invalidate() { @@ -83,31 +85,31 @@ public abstract class VisualManager { } } - public Plan planThisTick(double cameraX, double cameraY, double cameraZ) { - return SimplePlan.of(() -> { + public Plan createTickPlan() { + return SimplePlan.of(() -> { tickLimiter.tick(); 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) { - if (!instance.decreaseTickRateWithDistance() || tickLimiter.shouldUpdate(instance.distanceSquared(cameraX, cameraY, cameraZ))) { + protected void tickInstance(TickableVisual instance, TickContext c) { + if (!instance.decreaseTickRateWithDistance() || tickLimiter.shouldUpdate(instance.distanceSquared(c.cameraX(), c.cameraY(), c.cameraZ()))) { instance.tick(); } } - public Plan planThisFrame(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) { - return SimplePlan.of(() -> { + public Plan createFramePlan() { + return SimplePlan.of(() -> { frameLimiter.tick(); 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) { - if (!instance.decreaseFramerateWithDistance() || frameLimiter.shouldUpdate(instance.distanceSquared(cameraX, cameraY, cameraZ))) { - if (instance.isVisible(frustum)) { + protected void updateInstance(DynamicVisual instance, FrameContext c) { + if (!instance.decreaseFramerateWithDistance() || frameLimiter.shouldUpdate(instance.distanceSquared(c.cameraX(), c.cameraY(), c.cameraZ()))) { + if (instance.isVisible(c.frustum())) { instance.beginFrame(); } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/BarrierPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/BarrierPlan.java index c304a0867..4a43b06af 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/task/BarrierPlan.java +++ b/src/main/java/com/jozufozu/flywheel/lib/task/BarrierPlan.java @@ -3,24 +3,24 @@ 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 { +public record BarrierPlan(Plan first, Plan second) implements SimplyComposedPlan { @Override - public void execute(TaskExecutor taskExecutor, Runnable onCompletion) { - first.execute(taskExecutor, () -> second.execute(taskExecutor, onCompletion)); + public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) { + first.execute(taskExecutor, context, () -> second.execute(taskExecutor, context, onCompletion)); } @Override - public Plan maybeSimplify() { + public Plan maybeSimplify() { var first = this.first.maybeSimplify(); var second = this.second.maybeSimplify(); - if (first == UnitPlan.INSTANCE) { + if (first == UnitPlan.of()) { return second; } - if (second == UnitPlan.INSTANCE) { + if (second == UnitPlan.of()) { return first; } - return new BarrierPlan(first, second); + return new BarrierPlan<>(first, second); } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/ContextAgnosticPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/ContextAgnosticPlan.java new file mode 100644 index 000000000..32d7c8044 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/lib/task/ContextAgnosticPlan.java @@ -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 { + @SuppressWarnings("unchecked") + default Plan cast() { + // The context is entirely ignored, so we can safely cast to any context. + return (Plan) this; + } + + @Override + default void execute(TaskExecutor taskExecutor, Object ignored, Runnable onCompletion) { + execute(taskExecutor, onCompletion); + } + + void execute(TaskExecutor taskExecutor, Runnable onCompletion); +} diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/NestedPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/NestedPlan.java index e03bf0f8c..a23012c13 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/task/NestedPlan.java +++ b/src/main/java/com/jozufozu/flywheel/lib/task/NestedPlan.java @@ -8,13 +8,14 @@ import com.google.common.collect.ImmutableList; import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.api.task.TaskExecutor; -public record NestedPlan(List parallelPlans) implements Plan { - public static NestedPlan of(Plan... plans) { - return new NestedPlan(ImmutableList.copyOf(plans)); +public record NestedPlan(List> parallelPlans) implements SimplyComposedPlan { + @SafeVarargs + public static NestedPlan of(Plan... plans) { + return new NestedPlan<>(ImmutableList.copyOf(plans)); } @Override - public void execute(TaskExecutor taskExecutor, Runnable onCompletion) { + public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) { if (parallelPlans.isEmpty()) { onCompletion.run(); return; @@ -24,28 +25,28 @@ public record NestedPlan(List parallelPlans) implements Plan { if (size == 1) { parallelPlans.get(0) - .execute(taskExecutor, onCompletion); + .execute(taskExecutor, context, onCompletion); return; } var wait = new Synchronizer(size, onCompletion); - for (Plan plan : parallelPlans) { - plan.execute(taskExecutor, wait::decrementAndEventuallyRun); + for (var plan : parallelPlans) { + plan.execute(taskExecutor, context, wait); } } @Override - public Plan and(Plan plan) { - return new NestedPlan(ImmutableList.builder() + public Plan and(Plan plan) { + return new NestedPlan<>(ImmutableList.>builder() .addAll(parallelPlans) .add(plan) .build()); } @Override - public Plan maybeSimplify() { + public Plan maybeSimplify() { if (parallelPlans.isEmpty()) { - return UnitPlan.INSTANCE; + return UnitPlan.of(); } if (parallelPlans.size() == 1) { @@ -54,32 +55,12 @@ public record NestedPlan(List parallelPlans) implements Plan { } var simplifiedTasks = new ArrayList(); - var simplifiedPlans = new ArrayList(); - - 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); - } - } + var simplifiedPlans = new ArrayList>(); + flattenTasksAndPlans(simplifiedTasks, simplifiedPlans); if (simplifiedTasks.isEmpty() && simplifiedPlans.isEmpty()) { // everything got simplified away - return UnitPlan.INSTANCE; + return UnitPlan.of(); } if (simplifiedTasks.isEmpty()) { @@ -88,16 +69,39 @@ public record NestedPlan(List parallelPlans) implements Plan { // we only contained one complex plan, so we can just return that return simplifiedPlans.get(0); } - return new NestedPlan(simplifiedPlans); + return new NestedPlan<>(simplifiedPlans); } if (simplifiedPlans.isEmpty()) { // 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 - simplifiedPlans.add(new SimplePlan(simplifiedTasks)); - return new NestedPlan(simplifiedPlans); + simplifiedPlans.add(SimplePlan.of(simplifiedTasks)); + return new NestedPlan<>(simplifiedPlans); + } + + private void flattenTasksAndPlans(List simplifiedTasks, List> 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 nestedPlan) { + // inline and re-visit nested plans + toVisit.addAll(nestedPlan.parallelPlans()); + } else { + // /shrug + simplifiedPlans.add(plan); + } + } } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/OnMainThreadPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/OnMainThreadPlan.java index 9ef4993fc..caf0fa1a7 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/task/OnMainThreadPlan.java +++ b/src/main/java/com/jozufozu/flywheel/lib/task/OnMainThreadPlan.java @@ -3,7 +3,11 @@ 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 { +public record OnMainThreadPlan(Runnable task) implements ContextAgnosticPlan { + public static Plan of(Runnable task) { + return new OnMainThreadPlan(task).cast(); + } + @Override public void execute(TaskExecutor taskExecutor, Runnable onCompletion) { // TODO: detect if we're already on the render thread and just run the task directly diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/PlanUtil.java b/src/main/java/com/jozufozu/flywheel/lib/task/PlanUtil.java deleted file mode 100644 index efa8b8280..000000000 --- a/src/main/java/com/jozufozu/flywheel/lib/task/PlanUtil.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllPlan.java index 359fbc7c6..e3226657a 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllPlan.java +++ b/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllPlan.java @@ -8,9 +8,9 @@ import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.api.task.TaskExecutor; import com.jozufozu.flywheel.lib.math.MoreMath; -public record RunOnAllPlan(Supplier> listSupplier, Consumer action) implements Plan { - public static Plan of(Supplier> iterable, Consumer forEach) { - return new RunOnAllPlan<>(iterable, forEach); +public record RunOnAllPlan(Supplier> listSupplier, Consumer action) implements ContextAgnosticPlan { + public static Plan of(Supplier> iterable, Consumer forEach) { + return new RunOnAllPlan<>(iterable, forEach).cast(); } @Override @@ -42,7 +42,7 @@ public record RunOnAllPlan(Supplier> listSupplier, Consumer action int start = Math.max(remaining, 0); var subList = suppliedList.subList(start, end); - taskExecutor.execute(() -> processList(subList, synchronizer::decrementAndEventuallyRun)); + taskExecutor.execute(() -> processList(subList, synchronizer)); } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllWithContextPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllWithContextPlan.java new file mode 100644 index 000000000..9b15f20d2 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllWithContextPlan.java @@ -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(Supplier> listSupplier, + BiConsumer action) implements SimplyComposedPlan { + public static Plan of(Supplier> iterable, BiConsumer 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 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 suppliedList, C context, Runnable onCompletion) { + for (T t : suppliedList) { + action.accept(t, context); + } + onCompletion.run(); + } + + private static int getChunkingThreshold() { + return 256; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/SimplePlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/SimplePlan.java index e3c68506f..1568cf94e 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/task/SimplePlan.java +++ b/src/main/java/com/jozufozu/flywheel/lib/task/SimplePlan.java @@ -5,9 +5,13 @@ import java.util.List; import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.api.task.TaskExecutor; -public record SimplePlan(List parallelTasks) implements Plan { - public static Plan of(Runnable... tasks) { - return new SimplePlan(List.of(tasks)); +public record SimplePlan(List parallelTasks) implements ContextAgnosticPlan { + public static Plan of(Runnable... tasks) { + return new SimplePlan(List.of(tasks)).cast(); + } + + public static Plan of(List tasks) { + return new SimplePlan(tasks).cast(); } @Override @@ -27,9 +31,9 @@ public record SimplePlan(List parallelTasks) implements Plan { } @Override - public Plan maybeSimplify() { + public Plan maybeSimplify() { if (parallelTasks.isEmpty()) { - return UnitPlan.INSTANCE; + return UnitPlan.of(); } return this; diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/SimplyComposedPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/SimplyComposedPlan.java new file mode 100644 index 000000000..359c18459 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/lib/task/SimplyComposedPlan.java @@ -0,0 +1,20 @@ +package com.jozufozu.flywheel.lib.task; + +import com.jozufozu.flywheel.api.task.Plan; + +public interface SimplyComposedPlan extends Plan { + @Override + default Plan then(Plan plan) { + return new BarrierPlan<>(this, plan); + } + + @Override + default Plan and(Plan plan) { + return NestedPlan.of(this, plan); + } + + @Override + default Plan maybeSimplify() { + return this; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/Synchronizer.java b/src/main/java/com/jozufozu/flywheel/lib/task/Synchronizer.java index 51c9716be..c34d68ef7 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/task/Synchronizer.java +++ b/src/main/java/com/jozufozu/flywheel/lib/task/Synchronizer.java @@ -2,7 +2,7 @@ package com.jozufozu.flywheel.lib.task; import java.util.concurrent.atomic.AtomicInteger; -public class Synchronizer { +public class Synchronizer implements Runnable { private final AtomicInteger countDown; private final Runnable onCompletion; @@ -16,4 +16,9 @@ public class Synchronizer { onCompletion.run(); } } + + @Override + public void run() { + decrementAndEventuallyRun(); + } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/UnitPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/UnitPlan.java index 8894b02dd..09d2fd145 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/task/UnitPlan.java +++ b/src/main/java/com/jozufozu/flywheel/lib/task/UnitPlan.java @@ -3,14 +3,34 @@ 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(); +public class UnitPlan implements Plan { + private static final UnitPlan INSTANCE = new UnitPlan<>(); private UnitPlan() { } + @SuppressWarnings("unchecked") + public static UnitPlan of() { + return (UnitPlan) INSTANCE; + } + @Override - public void execute(TaskExecutor taskExecutor, Runnable onCompletion) { + public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) { onCompletion.run(); } + + @Override + public Plan then(Plan plan) { + return plan; + } + + @Override + public Plan and(Plan plan) { + return plan; + } + + @Override + public Plan maybeSimplify() { + return this; + } } diff --git a/src/main/java/com/jozufozu/flywheel/util/Unit.java b/src/main/java/com/jozufozu/flywheel/util/Unit.java new file mode 100644 index 000000000..694edbbc4 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/util/Unit.java @@ -0,0 +1,5 @@ +package com.jozufozu.flywheel.util; + +public enum Unit { + INSTANCE; +} diff --git a/src/test/java/com/jozufozu/flywheel/lib/task/PlanCompositionTest.java b/src/test/java/com/jozufozu/flywheel/lib/task/PlanCompositionTest.java index 31331c55a..8411e608a 100644 --- a/src/test/java/com/jozufozu/flywheel/lib/task/PlanCompositionTest.java +++ b/src/test/java/com/jozufozu/flywheel/lib/task/PlanCompositionTest.java @@ -4,12 +4,13 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import com.jozufozu.flywheel.api.task.Plan; +import com.jozufozu.flywheel.util.Unit; public class PlanCompositionTest { public static final Runnable NOOP = () -> { }; - public static final Plan SIMPLE = SimplePlan.of(NOOP); + public static final Plan SIMPLE = SimplePlan.of(NOOP); @Test void nestedPlanAnd() { diff --git a/src/test/java/com/jozufozu/flywheel/lib/task/PlanExecutionTest.java b/src/test/java/com/jozufozu/flywheel/lib/task/PlanExecutionTest.java index 6f572dee7..8dbac931f 100644 --- a/src/test/java/com/jozufozu/flywheel/lib/task/PlanExecutionTest.java +++ b/src/test/java/com/jozufozu/flywheel/lib/task/PlanExecutionTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.params.provider.ValueSource; import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.backend.task.ParallelTaskExecutor; +import com.jozufozu.flywheel.util.Unit; import it.unimi.dsi.fastutil.ints.IntArrayList; @@ -51,7 +52,7 @@ class PlanExecutionTest { var sequence = new IntArrayList(barriers + 1); var expected = new IntArrayList(barriers + 1); - var plan = SimplePlan.of(() -> sequence.add(1)); + var plan = SimplePlan.of(() -> sequence.add(1)); expected.add(1); for (int i = 0; i < barriers; i++) { @@ -81,7 +82,7 @@ class PlanExecutionTest { } }; - var plan = SimplePlan.of(addOne, addOne, addOne, addOne) + var plan = SimplePlan.of(addOne, addOne, addOne, addOne) .then(SimplePlan.of(addTwo, addTwo, addTwo, addTwo)); runAndWait(plan); @@ -92,7 +93,7 @@ class PlanExecutionTest { @Test void simpleNestedPlan() { var sequence = new IntArrayList(2); - var plan = NestedPlan.of(SimplePlan.of(() -> sequence.add(1))); + var plan = NestedPlan.of(SimplePlan.of(() -> sequence.add(1))); runAndWait(plan); assertExpectedSequence(sequence, 1); } @@ -100,7 +101,7 @@ class PlanExecutionTest { @Test void manyNestedPlans() { var counter = new AtomicInteger(0); - var count4 = NestedPlan.of(SimplePlan.of(counter::incrementAndGet, counter::incrementAndGet), SimplePlan.of(counter::incrementAndGet, counter::incrementAndGet)); + var count4 = NestedPlan.of(SimplePlan.of(counter::incrementAndGet, counter::incrementAndGet), SimplePlan.of(counter::incrementAndGet, counter::incrementAndGet)); runAndWait(count4); Assertions.assertEquals(4, counter.get()); @@ -116,7 +117,8 @@ class PlanExecutionTest { void unitPlan() { 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()); } @@ -126,12 +128,12 @@ class PlanExecutionTest { var done = new AtomicBoolean(false); SimplePlan.of() - .execute(null, () -> done.set(true)); + .execute(null, Unit.INSTANCE, () -> done.set(true)); Assertions.assertTrue(done.get()); done.set(false); NestedPlan.of() - .execute(null, () -> done.set(true)); + .execute(null, Unit.INSTANCE, () -> done.set(true)); Assertions.assertTrue(done.get()); } @@ -140,7 +142,7 @@ class PlanExecutionTest { var done = new AtomicBoolean(false); var plan = new OnMainThreadPlan(() -> done.set(true)); - plan.execute(EXECUTOR); + plan.execute(EXECUTOR, Unit.INSTANCE); Assertions.assertFalse(done.get()); @@ -153,20 +155,20 @@ class PlanExecutionTest { Assertions.assertArrayEquals(expected, sequence.toIntArray()); } - public static void runAndWait(Plan plan) { + public static void runAndWait(Plan plan) { new TestBarrier(plan).runAndWait(); } private static final class TestBarrier { - private final Plan plan; + private final Plan plan; private boolean done = false; - private TestBarrier(Plan plan) { + private TestBarrier(Plan plan) { this.plan = plan; } public void runAndWait() { - plan.execute(EXECUTOR, this::doneWithPlan); + plan.execute(EXECUTOR, Unit.INSTANCE, this::doneWithPlan); synchronized (this) { // early exit in case the plan is already done for e.g. UnitPlan diff --git a/src/test/java/com/jozufozu/flywheel/lib/task/PlanSimplificationTest.java b/src/test/java/com/jozufozu/flywheel/lib/task/PlanSimplificationTest.java index ec051599d..e85887efd 100644 --- a/src/test/java/com/jozufozu/flywheel/lib/task/PlanSimplificationTest.java +++ b/src/test/java/com/jozufozu/flywheel/lib/task/PlanSimplificationTest.java @@ -4,20 +4,21 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import com.jozufozu.flywheel.api.task.Plan; +import com.jozufozu.flywheel.util.Unit; public class PlanSimplificationTest { public static final Runnable NOOP = () -> { }; - public static final Plan SIMPLE = SimplePlan.of(NOOP); + public static final Plan SIMPLE = SimplePlan.of(NOOP); @Test void emptyPlans() { var empty = NestedPlan.of(); - Assertions.assertEquals(empty.maybeSimplify(), UnitPlan.INSTANCE); + Assertions.assertEquals(empty.maybeSimplify(), UnitPlan.of()); var simpleEmpty = SimplePlan.of(); - Assertions.assertEquals(simpleEmpty.maybeSimplify(), UnitPlan.INSTANCE); + Assertions.assertEquals(simpleEmpty.maybeSimplify(), UnitPlan.of()); } @Test @@ -40,7 +41,7 @@ public class PlanSimplificationTest { Assertions.assertEquals(oneMainThread.maybeSimplify(), mainThreadNoop); - var barrier = new BarrierPlan(SIMPLE, SIMPLE); + var barrier = new BarrierPlan<>(SIMPLE, SIMPLE); var oneBarrier = NestedPlan.of(barrier); Assertions.assertEquals(oneBarrier.maybeSimplify(), barrier); @@ -56,29 +57,29 @@ public class PlanSimplificationTest { @Test void nestedUnitPlan() { - var onlyUnit = NestedPlan.of(UnitPlan.INSTANCE, UnitPlan.INSTANCE, UnitPlan.INSTANCE); - Assertions.assertEquals(onlyUnit.maybeSimplify(), UnitPlan.INSTANCE); + var onlyUnit = NestedPlan.of(UnitPlan.of(), UnitPlan.of(), UnitPlan.of()); + 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); } @Test void complexNesting() { - var mainThreadNoop = new OnMainThreadPlan(NOOP); + var mainThreadNoop = OnMainThreadPlan.of(NOOP); var nested = NestedPlan.of(mainThreadNoop, SIMPLE); 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); Assertions.assertEquals(complex.maybeSimplify(), NestedPlan.of(barrier, mainThreadNoop, SIMPLE)); } @Test void nestedNoSimple() { - var mainThreadNoop = new OnMainThreadPlan(NOOP); - var barrier = new BarrierPlan(SIMPLE, SIMPLE); + var mainThreadNoop = OnMainThreadPlan.of(NOOP); + var barrier = new BarrierPlan<>(SIMPLE, SIMPLE); var oneMainThread = NestedPlan.of(mainThreadNoop, NestedPlan.of(mainThreadNoop, barrier, barrier)); Assertions.assertEquals(oneMainThread.maybeSimplify(), NestedPlan.of(mainThreadNoop, mainThreadNoop, barrier, barrier)); @@ -86,24 +87,24 @@ public class PlanSimplificationTest { @Test void manyNestedButJustOneAfterSimplification() { - var barrier = new BarrierPlan(SIMPLE, SIMPLE); - var oneMainThread = NestedPlan.of(barrier, NestedPlan.of(UnitPlan.INSTANCE, UnitPlan.INSTANCE)); + var barrier = new BarrierPlan<>(SIMPLE, SIMPLE); + var oneMainThread = NestedPlan.of(barrier, NestedPlan.of(UnitPlan.of(), UnitPlan.of())); Assertions.assertEquals(oneMainThread.maybeSimplify(), barrier); } @Test void barrierPlan() { - var doubleUnit = new BarrierPlan(UnitPlan.INSTANCE, UnitPlan.INSTANCE); - Assertions.assertEquals(doubleUnit.maybeSimplify(), UnitPlan.INSTANCE); + var doubleUnit = new BarrierPlan<>(UnitPlan.of(), UnitPlan.of()); + 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); - var unitThenSimple = new BarrierPlan(UnitPlan.INSTANCE, SIMPLE); + var unitThenSimple = new BarrierPlan<>(UnitPlan.of(), SIMPLE); Assertions.assertEquals(unitThenSimple.maybeSimplify(), SIMPLE); - var simpleThenSimple = new BarrierPlan(SIMPLE, SIMPLE); - Assertions.assertEquals(simpleThenSimple.maybeSimplify(), new BarrierPlan(SIMPLE, SIMPLE)); + var simpleThenSimple = new BarrierPlan<>(SIMPLE, SIMPLE); + Assertions.assertEquals(simpleThenSimple.maybeSimplify(), new BarrierPlan<>(SIMPLE, SIMPLE)); } }