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 2993f885f..c901543ab 100644 --- a/src/main/java/com/jozufozu/flywheel/api/task/Plan.java +++ b/src/main/java/com/jozufozu/flywheel/api/task/Plan.java @@ -1,5 +1,7 @@ package com.jozufozu.flywheel.api.task; +import java.util.function.Function; + public interface Plan { /** * Submit this plan for execution. @@ -31,6 +33,16 @@ public interface Plan { */ Plan then(Plan plan); + /** + * Create a new plan that executes this plan, then transforms the context to execute the given plan. + * + * @param map A function that transforms the plan context. + * @param plan The plan to execute after this plan with the transformed context. + * @param The type of the transformed context. + * @return The composed plan. + */ + Plan thenMap(Function map, Plan plan); + /** * Create a new plan that executes this plan and the given plan in parallel. * @@ -39,6 +51,16 @@ public interface Plan { */ Plan and(Plan plan); + /** + * Create a new plan that executes this plan and the given plan in parallel. + * + * @param map A function that transforms the plan context. + * @param plan The plan to execute in parallel with this plan than accepts the transformed context. + * @param The type of the transformed context. + * @return The composed plan. + */ + Plan andMap(Function map, Plan plan); + /** * If possible, create a new plan that accomplishes everything * this plan does but with a simpler execution schedule. diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/DistanceUpdateLimiter.java b/src/main/java/com/jozufozu/flywheel/api/visual/DistanceUpdateLimiter.java similarity index 71% rename from src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/DistanceUpdateLimiter.java rename to src/main/java/com/jozufozu/flywheel/api/visual/DistanceUpdateLimiter.java index d2217b734..25ffccfa6 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/DistanceUpdateLimiter.java +++ b/src/main/java/com/jozufozu/flywheel/api/visual/DistanceUpdateLimiter.java @@ -1,18 +1,13 @@ -package com.jozufozu.flywheel.impl.visualization.ratelimit; +package com.jozufozu.flywheel.api.visual; /** * Interface for rate-limiting updates based on an object's distance from the camera. */ public interface DistanceUpdateLimiter { - /** - * Call this before every update. - */ - void tick(); - /** * Check to see if an object at the given position relative to the camera should be updated. * - * @param distanceSquared + * @param distanceSquared The distance squared from the camera to the object. * @return {@code true} if the object should be updated, {@code false} otherwise. */ boolean shouldUpdate(double distanceSquared); diff --git a/src/main/java/com/jozufozu/flywheel/api/visual/DynamicVisual.java b/src/main/java/com/jozufozu/flywheel/api/visual/DynamicVisual.java index d515024ff..4b783e652 100644 --- a/src/main/java/com/jozufozu/flywheel/api/visual/DynamicVisual.java +++ b/src/main/java/com/jozufozu/flywheel/api/visual/DynamicVisual.java @@ -1,7 +1,5 @@ package com.jozufozu.flywheel.api.visual; -import org.joml.FrustumIntersection; - import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.api.instance.Instancer; @@ -10,39 +8,17 @@ import com.jozufozu.flywheel.api.instance.Instancer; * the start of a frame. By implementing {@link DynamicVisual}, an {@link Visual} * can animate its models in ways that could not be easily achieved by shader attribute * parameterization. - * - *

If your goal is offloading work to shaders, but you're unsure exactly how you need + *

+ * If your goal is offloading work to shaders, but you're unsure exactly how you need * to parameterize the instances, you're encouraged to implement this for prototyping. */ public interface DynamicVisual extends Visual { /** - * Called every frame, and after initialization. - *
- * DISPATCHED IN PARALLEL, don't attempt to mutate anything outside this visual. - *
+ * Called every frame. + *

+ * DISPATCHED IN PARALLEL. Ensure proper synchronization if you need to mutate anything outside this visual. + *

* {@link Instancer}/{@link Instance} creation/acquisition is safe here. */ - void beginFrame(); - - /** - * As a further optimization, dynamic visuals that are far away are updated less often. - * This behavior can be disabled by returning false. - * - *
You might want to opt out of this if you want your animations to remain smooth - * even when far away from the camera. It is recommended to keep this as is, however. - * - * @return {@code true} if your visual should be slow updated. - */ - default boolean decreaseFramerateWithDistance() { - return true; - } - - /** - * Check this visual against a frustum.

- * An implementor may choose to return a constant to skip the frustum check. - * - * @param frustum A frustum intersection tester for the current frame. - * @return {@code true} if this visual should be considered for updates. - */ - boolean isVisible(FrustumIntersection frustum); + void beginFrame(VisualFrameContext context); } diff --git a/src/main/java/com/jozufozu/flywheel/api/visual/TickableVisual.java b/src/main/java/com/jozufozu/flywheel/api/visual/TickableVisual.java index aa691a537..d23f33ee1 100644 --- a/src/main/java/com/jozufozu/flywheel/api/visual/TickableVisual.java +++ b/src/main/java/com/jozufozu/flywheel/api/visual/TickableVisual.java @@ -21,23 +21,11 @@ import com.jozufozu.flywheel.api.instance.Instancer; */ public interface TickableVisual extends Visual { /** - * Called every tick, and after initialization.

- * DISPATCHED IN PARALLEL, don't attempt to mutate anything outside of this visual - * without proper synchronization.

+ * Called every tick. + *

+ * DISPATCHED IN PARALLEL. Ensure proper synchronization if you need to mutate anything outside this visual. + *

* {@link Instancer}/{@link Instance} creation/acquisition is safe here. */ - void tick(); - - /** - * As a further optimization, tickable visuals that are far away are ticked less often. - * This behavior can be disabled by returning false. - * - *
You might want to opt out of this if you want your animations to remain smooth - * even when far away from the camera. It is recommended to keep this as is, however. - * - * @return {@code true} if your visual should be slow ticked. - */ - default boolean decreaseTickRateWithDistance() { - return true; - } + void tick(VisualTickContext c); } diff --git a/src/main/java/com/jozufozu/flywheel/api/visual/Visual.java b/src/main/java/com/jozufozu/flywheel/api/visual/Visual.java index 77aeccb76..b38dc666a 100644 --- a/src/main/java/com/jozufozu/flywheel/api/visual/Visual.java +++ b/src/main/java/com/jozufozu/flywheel/api/visual/Visual.java @@ -2,6 +2,9 @@ package com.jozufozu.flywheel.api.visual; /** * A general interface providing information about any type of thing that could use Flywheel's visualized rendering. + * + * @see DynamicVisual + * @see TickableVisual */ public interface Visual { /** @@ -30,16 +33,6 @@ public interface Visual { */ boolean shouldReset(); - /** - * Calculate the distance squared between this visual and the given world position. - * - * @param x The x coordinate. - * @param y The y coordinate. - * @param z The z coordinate. - * @return The distance squared between this visual and the given position. - */ - double distanceSquared(double x, double y, double z); - /** * Free any acquired resources. */ diff --git a/src/main/java/com/jozufozu/flywheel/api/visual/VisualFrameContext.java b/src/main/java/com/jozufozu/flywheel/api/visual/VisualFrameContext.java new file mode 100644 index 000000000..6778a84b3 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/api/visual/VisualFrameContext.java @@ -0,0 +1,7 @@ +package com.jozufozu.flywheel.api.visual; + +import org.joml.FrustumIntersection; + +public record VisualFrameContext(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum, + DistanceUpdateLimiter limiter) { +} diff --git a/src/main/java/com/jozufozu/flywheel/api/visual/VisualTickContext.java b/src/main/java/com/jozufozu/flywheel/api/visual/VisualTickContext.java new file mode 100644 index 000000000..6185d2dd9 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/api/visual/VisualTickContext.java @@ -0,0 +1,4 @@ +package com.jozufozu.flywheel.api.visual; + +public record VisualTickContext(double cameraX, double cameraY, double cameraZ, DistanceUpdateLimiter limiter) { +} 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 63b5e91a1..8365ab5c2 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.RunOnAllWithContextPlan; +import com.jozufozu.flywheel.lib.task.RunOnAllPlan; import com.jozufozu.flywheel.lib.vertex.VertexTransformations; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Matrix3f; @@ -38,7 +38,7 @@ public class TransformCall { meshVertexCount = mesh.getVertexCount(); boundingSphere = mesh.mesh.getBoundingSphere(); - drawPlan = RunOnAllWithContextPlan.of(instancer::getAll, (instance, ctx) -> { + drawPlan = RunOnAllPlan.of(instancer::getAll, (instance, ctx) -> { var boundingSphere = new Vector4f(this.boundingSphere); boundingSphereTransformer.transform(boundingSphere, instance); 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 d2cd7657b..ea7052a42 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualWorld.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualWorld.java @@ -82,7 +82,7 @@ public class VisualWorld implements AutoCloseable { /** * Tick the visuals after the game has ticked: *

- * Call {@link TickableVisual#tick()} on all visuals in this world. + * Call {@link TickableVisual#tick} on all visuals in this world. *

*/ public void tick(double cameraX, double cameraY, double cameraZ) { @@ -96,7 +96,7 @@ public class VisualWorld implements AutoCloseable { *

* Check and update the render origin. *
- * Call {@link DynamicVisual#beginFrame()} on all visuals in this world. + * Call {@link DynamicVisual#beginFrame} on all visuals in this world. *

*/ public void beginFrame(RenderContext context) { 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 6fe623599..c4a759159 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 @@ -6,23 +6,25 @@ import java.util.concurrent.ConcurrentLinkedQueue; 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.api.visual.VisualFrameContext; +import com.jozufozu.flywheel.api.visual.VisualTickContext; 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.DistanceUpdateLimiterImpl; 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.RunOnAllWithContextPlan; +import com.jozufozu.flywheel.lib.task.RunOnAllPlan; import com.jozufozu.flywheel.lib.task.SimplePlan; import com.jozufozu.flywheel.util.Unit; public abstract class VisualManager { private final Queue> queue = new ConcurrentLinkedQueue<>(); - protected DistanceUpdateLimiter tickLimiter; - protected DistanceUpdateLimiter frameLimiter; + protected DistanceUpdateLimiterImpl tickLimiter; + protected DistanceUpdateLimiterImpl frameLimiter; public VisualManager() { tickLimiter = createUpdateLimiter(); @@ -31,8 +33,9 @@ public abstract class VisualManager { protected abstract Storage getStorage(); - protected DistanceUpdateLimiter createUpdateLimiter() { - if (FlwConfig.get().limitUpdates()) { + protected DistanceUpdateLimiterImpl createUpdateLimiter() { + if (FlwConfig.get() + .limitUpdates()) { return new BandedPrimeLimiter(); } else { return new NonLimiter(); @@ -90,13 +93,7 @@ public abstract class VisualManager { tickLimiter.tick(); processQueue(); }) - .then(RunOnAllWithContextPlan.of(getStorage()::getTickableVisuals, this::tickInstance)); - } - - protected void tickInstance(TickableVisual instance, TickContext c) { - if (!instance.decreaseTickRateWithDistance() || tickLimiter.shouldUpdate(instance.distanceSquared(c.cameraX(), c.cameraY(), c.cameraZ()))) { - instance.tick(); - } + .thenMap(this::createVisualTickContext, RunOnAllPlan.of(getStorage()::getTickableVisuals, TickableVisual::tick)); } public Plan createFramePlan() { @@ -104,14 +101,14 @@ public abstract class VisualManager { frameLimiter.tick(); processQueue(); }) - .then(RunOnAllWithContextPlan.of(getStorage()::getDynamicVisuals, this::updateInstance)); + .thenMap(this::createVisualContext, RunOnAllPlan.of(getStorage()::getDynamicVisuals, DynamicVisual::beginFrame)); } - 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(); - } - } + private VisualFrameContext createVisualContext(FrameContext ctx) { + return new VisualFrameContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), ctx.frustum(), frameLimiter); + } + + private VisualTickContext createVisualTickContext(TickContext ctx) { + return new VisualTickContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), frameLimiter); } } diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/BandedPrimeLimiter.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/BandedPrimeLimiter.java index f311ccc09..640332755 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/BandedPrimeLimiter.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/BandedPrimeLimiter.java @@ -2,9 +2,9 @@ package com.jozufozu.flywheel.impl.visualization.ratelimit; import net.minecraft.util.Mth; -public class BandedPrimeLimiter implements DistanceUpdateLimiter { +public class BandedPrimeLimiter implements DistanceUpdateLimiterImpl { // 1 followed by the prime numbers - private static final int[] DIVISOR_SEQUENCE = new int[] { 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 }; + private static final int[] DIVISOR_SEQUENCE = new int[]{1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31}; private int tickCount = 0; diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/DistanceUpdateLimiterImpl.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/DistanceUpdateLimiterImpl.java new file mode 100644 index 000000000..4a11cb2c1 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/DistanceUpdateLimiterImpl.java @@ -0,0 +1,10 @@ +package com.jozufozu.flywheel.impl.visualization.ratelimit; + +import com.jozufozu.flywheel.api.visual.DistanceUpdateLimiter; + +public interface DistanceUpdateLimiterImpl extends DistanceUpdateLimiter { + /** + * Call this before every update. + */ + void tick(); +} diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/NonLimiter.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/NonLimiter.java index c0d77d88a..c16b722e0 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/NonLimiter.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/ratelimit/NonLimiter.java @@ -1,6 +1,6 @@ package com.jozufozu.flywheel.impl.visualization.ratelimit; -public class NonLimiter implements DistanceUpdateLimiter { +public class NonLimiter implements DistanceUpdateLimiterImpl { @Override public void tick() { } diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/AbstractStorage.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/AbstractStorage.java index 9ed40982e..aa63b40ed 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/AbstractStorage.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/AbstractStorage.java @@ -32,12 +32,10 @@ public abstract class AbstractStorage implements Storage { if (visual instanceof TickableVisual tickable) { tickableVisuals.add(tickable); - tickable.tick(); } if (visual instanceof DynamicVisual dynamic) { dynamicVisuals.add(dynamic); - dynamic.beginFrame(); } } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/MapContextPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/MapContextPlan.java new file mode 100644 index 000000000..f91e02ea5 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/lib/task/MapContextPlan.java @@ -0,0 +1,25 @@ +package com.jozufozu.flywheel.lib.task; + +import java.util.function.Function; + +import com.jozufozu.flywheel.api.task.Plan; +import com.jozufozu.flywheel.api.task.TaskExecutor; + +public record MapContextPlan(Function map, Plan plan) implements SimplyComposedPlan { + @Override + public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) { + D newContext = map.apply(context); + plan.execute(taskExecutor, newContext, onCompletion); + } + + @Override + public Plan maybeSimplify() { + var maybeSimplified = plan.maybeSimplify(); + + if (maybeSimplified instanceof UnitPlan) { + return UnitPlan.of(); + } + + return new MapContextPlan<>(map, maybeSimplified); + } +} 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 e3226657a..cf737c238 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllPlan.java +++ b/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllPlan.java @@ -1,35 +1,36 @@ package com.jozufozu.flywheel.lib.task; import java.util.List; -import java.util.function.Consumer; +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 RunOnAllPlan(Supplier> listSupplier, Consumer action) implements ContextAgnosticPlan { - public static Plan of(Supplier> iterable, Consumer forEach) { - return new RunOnAllPlan<>(iterable, forEach).cast(); +public record RunOnAllPlan(Supplier> listSupplier, + BiConsumer action) implements SimplyComposedPlan { + public static Plan of(Supplier> iterable, BiConsumer forEach) { + return new RunOnAllPlan<>(iterable, forEach); } @Override - public void execute(TaskExecutor taskExecutor, Runnable onCompletion) { + 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, onCompletion); + } else if (size <= getChunkSize(taskExecutor, size)) { + processList(list, context, onCompletion); } else { - dispatchChunks(list, taskExecutor, onCompletion); + dispatchChunks(list, taskExecutor, context, onCompletion); } }); } - private void dispatchChunks(List suppliedList, TaskExecutor taskExecutor, Runnable onCompletion) { + private void dispatchChunks(List suppliedList, TaskExecutor taskExecutor, C context, Runnable onCompletion) { final int size = suppliedList.size(); final int chunkSize = getChunkSize(taskExecutor, size); @@ -42,7 +43,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)); + taskExecutor.execute(() -> processList(subList, context, synchronizer)); } } @@ -50,14 +51,10 @@ public record RunOnAllPlan(Supplier> listSupplier, Consumer action return MoreMath.ceilingDiv(totalSize, taskExecutor.getThreadCount() * 32); } - private void processList(List suppliedList, Runnable onCompletion) { + private void processList(List suppliedList, C context, Runnable onCompletion) { for (T t : suppliedList) { - action.accept(t); + action.accept(t, context); } onCompletion.run(); } - - private static int getChunkingThreshold() { - return 256; - } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllWithContextPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllWithContextPlan.java deleted file mode 100644 index 9b15f20d2..000000000 --- a/src/main/java/com/jozufozu/flywheel/lib/task/RunOnAllWithContextPlan.java +++ /dev/null @@ -1,64 +0,0 @@ -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/SimplyComposedPlan.java b/src/main/java/com/jozufozu/flywheel/lib/task/SimplyComposedPlan.java index 359c18459..b70f406dc 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/task/SimplyComposedPlan.java +++ b/src/main/java/com/jozufozu/flywheel/lib/task/SimplyComposedPlan.java @@ -1,5 +1,7 @@ package com.jozufozu.flywheel.lib.task; +import java.util.function.Function; + import com.jozufozu.flywheel.api.task.Plan; public interface SimplyComposedPlan extends Plan { @@ -8,11 +10,21 @@ public interface SimplyComposedPlan extends Plan { return new BarrierPlan<>(this, plan); } + @Override + default Plan thenMap(Function map, Plan plan) { + return then(new MapContextPlan<>(map, plan)); + } + @Override default Plan and(Plan plan) { return NestedPlan.of(this, plan); } + @Override + default Plan andMap(Function map, Plan plan) { + return and(new MapContextPlan<>(map, plan)); + } + @Override default Plan maybeSimplify() { return this; 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 09d2fd145..66a7d60b8 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/task/UnitPlan.java +++ b/src/main/java/com/jozufozu/flywheel/lib/task/UnitPlan.java @@ -1,5 +1,7 @@ package com.jozufozu.flywheel.lib.task; +import java.util.function.Function; + import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.api.task.TaskExecutor; @@ -24,11 +26,21 @@ public class UnitPlan implements Plan { return plan; } + @Override + public Plan thenMap(Function map, Plan plan) { + return new MapContextPlan<>(map, plan); + } + @Override public Plan and(Plan plan) { return plan; } + @Override + public Plan andMap(Function map, Plan plan) { + return new MapContextPlan<>(map, plan); + } + @Override public Plan maybeSimplify() { return this; diff --git a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractBlockEntityVisual.java b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractBlockEntityVisual.java index 9301285b6..14c16bde7 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractBlockEntityVisual.java +++ b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractBlockEntityVisual.java @@ -5,6 +5,7 @@ import org.joml.FrustumIntersection; import com.jozufozu.flywheel.api.visual.BlockEntityVisual; import com.jozufozu.flywheel.api.visual.DynamicVisual; import com.jozufozu.flywheel.api.visual.TickableVisual; +import com.jozufozu.flywheel.api.visual.VisualFrameContext; import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.impl.visualization.manager.BlockEntityVisualManager; import com.jozufozu.flywheel.lib.box.ImmutableBox; @@ -50,11 +51,6 @@ public abstract class AbstractBlockEntityVisual extends A return blockEntity.getBlockState() != blockState; } - @Override - public double distanceSquared(double x, double y, double z) { - return pos.distToCenterSqr(x, y, z); - } - @Override public ImmutableBox getVolume() { return MutableBox.from(pos); @@ -72,8 +68,24 @@ public abstract class AbstractBlockEntityVisual extends A return visualPos; } - public boolean isVisible(FrustumIntersection frustum) { - return frustum.testAab(visualPos.getX(), visualPos.getY(), visualPos.getZ(), - visualPos.getX() + 1, visualPos.getY() + 1, visualPos.getZ() + 1); + /** + * @param frustum The current frustum. + * @return {@code true} if this visual within the given frustum. + */ + public boolean visible(FrustumIntersection frustum) { + return frustum.testAab(visualPos.getX(), visualPos.getY(), visualPos.getZ(), visualPos.getX() + 1, visualPos.getY() + 1, visualPos.getZ() + 1); + } + + /** + * Limits which frames this visual is updated on based on its distance from the camera. + *

+ * You may optionally do this check to avoid updating your visual every frame when it is far away. + * + * @param context The current frame context. + * @return {@code true} if this visual shouldn't be updated this frame based on its distance from the camera. + */ + public boolean doDistanceLimitThisFrame(VisualFrameContext context) { + return !context.limiter() + .shouldUpdate(pos.distToCenterSqr(context.cameraX(), context.cameraY(), context.cameraZ())); } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractEntityVisual.java b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractEntityVisual.java index 4647bf889..a1ba4030d 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractEntityVisual.java +++ b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractEntityVisual.java @@ -35,16 +35,23 @@ import net.minecraft.world.phys.Vec3; public abstract class AbstractEntityVisual extends AbstractVisual implements EntityVisual, TickingLightListener { protected final T entity; protected final MutableBox bounds; - protected final EntityVisibilityTester boxTracker; + protected final EntityVisibilityTester visibilityTester; public AbstractEntityVisual(VisualizationContext ctx, T entity) { super(ctx, entity.level); this.entity = entity; bounds = MutableBox.from(entity.getBoundingBox()); - boxTracker = new EntityVisibilityTester(entity, ctx.renderOrigin()); + visibilityTester = new EntityVisibilityTester(entity, ctx.renderOrigin()); } - @Override + /** + * Calculate the distance squared between this visual and the given world position. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param z The z coordinate. + * @return The distance squared between this visual and the given position. + */ public double distanceSquared(double x, double y, double z) { return entity.distanceToSqr(x, y, z); } @@ -92,15 +99,10 @@ public abstract class AbstractEntityVisual extends AbstractVis */ public Vector3f getVisualPosition(float partialTicks) { Vec3 pos = entity.position(); - return new Vector3f((float) (Mth.lerp(partialTicks, entity.xOld, pos.x) - renderOrigin.getX()), - (float) (Mth.lerp(partialTicks, entity.yOld, pos.y) - renderOrigin.getY()), - (float) (Mth.lerp(partialTicks, entity.zOld, pos.z) - renderOrigin.getZ())); + return new Vector3f((float) (Mth.lerp(partialTicks, entity.xOld, pos.x) - renderOrigin.getX()), (float) (Mth.lerp(partialTicks, entity.yOld, pos.y) - renderOrigin.getY()), (float) (Mth.lerp(partialTicks, entity.zOld, pos.z) - renderOrigin.getZ())); } - public boolean isVisible(FrustumIntersection frustum) { - if (entity.noCulling) { - return true; - } - return boxTracker.isVisible(frustum); + public boolean visible(FrustumIntersection frustum) { + return entity.noCulling || visibilityTester.check(frustum); } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/visual/EntityVisibilityTester.java b/src/main/java/com/jozufozu/flywheel/lib/visual/EntityVisibilityTester.java index 649e4edae..8dfff1c0d 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/visual/EntityVisibilityTester.java +++ b/src/main/java/com/jozufozu/flywheel/lib/visual/EntityVisibilityTester.java @@ -24,7 +24,13 @@ public class EntityVisibilityTester { this.renderOrigin = renderOrigin; } - public boolean isVisible(FrustumIntersection frustum) { + /** + * Check whether the Entity is visible. + * + * @param frustum The frustum to test against. + * @return {@code true} if the Entity is visible, {@code false} otherwise. + */ + public boolean check(FrustumIntersection frustum) { AABB aabb = entity.getBoundingBoxForCulling(); boolean visible = adjustAndTestAABB(frustum, aabb); diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/BellVisual.java b/src/main/java/com/jozufozu/flywheel/vanilla/BellVisual.java index 414aef3f5..277048736 100644 --- a/src/main/java/com/jozufozu/flywheel/vanilla/BellVisual.java +++ b/src/main/java/com/jozufozu/flywheel/vanilla/BellVisual.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull; import com.jozufozu.flywheel.api.event.RenderStage; import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.api.visual.DynamicVisual; +import com.jozufozu.flywheel.api.visual.VisualFrameContext; import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.lib.instance.InstanceTypes; import com.jozufozu.flywheel.lib.instance.OrientedInstance; @@ -38,11 +39,21 @@ public class BellVisual extends AbstractBlockEntityVisual imple bell = createBellInstance().setPivot(0.5f, 0.75f, 0.5f) .setPosition(getVisualPosition()); + updateRotation(); + super.init(); } @Override - public void beginFrame() { + public void beginFrame(VisualFrameContext context) { + if (doDistanceLimitThisFrame(context) || !visible(context.frustum())) { + return; + } + + updateRotation(); + } + + private void updateRotation() { float ringTime = (float) blockEntity.ticks + AnimationTickHolder.getPartialTicks(); if (ringTime == lastRingTime) { diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/ChestVisual.java b/src/main/java/com/jozufozu/flywheel/vanilla/ChestVisual.java index 365390ee0..d75b21146 100644 --- a/src/main/java/com/jozufozu/flywheel/vanilla/ChestVisual.java +++ b/src/main/java/com/jozufozu/flywheel/vanilla/ChestVisual.java @@ -7,6 +7,7 @@ import java.util.function.BiFunction; import com.jozufozu.flywheel.api.event.RenderStage; import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.api.visual.DynamicVisual; +import com.jozufozu.flywheel.api.visual.VisualFrameContext; import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.lib.instance.InstanceTypes; import com.jozufozu.flywheel.lib.instance.OrientedInstance; @@ -80,7 +81,11 @@ public class ChestVisual extends Abstrac } @Override - public void beginFrame() { + public void beginFrame(VisualFrameContext context) { + if (doDistanceLimitThisFrame(context) || !visible(context.frustum())) { + return; + } + float progress = lidProgress.get(AnimationTickHolder.getPartialTicks()); if (lastProgress == progress) { @@ -96,7 +101,7 @@ public class ChestVisual extends Abstrac lid.loadIdentity() .translate(getVisualPosition()) - .translate(0, 9f/16f, 0) + .translate(0, 9f / 16f, 0) .centre() .multiply(baseRotation) .unCentre() diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/MinecartVisual.java b/src/main/java/com/jozufozu/flywheel/vanilla/MinecartVisual.java index 7329d5988..9636778f1 100644 --- a/src/main/java/com/jozufozu/flywheel/vanilla/MinecartVisual.java +++ b/src/main/java/com/jozufozu/flywheel/vanilla/MinecartVisual.java @@ -5,6 +5,8 @@ import org.jetbrains.annotations.NotNull; import com.jozufozu.flywheel.api.event.RenderStage; import com.jozufozu.flywheel.api.visual.DynamicVisual; import com.jozufozu.flywheel.api.visual.TickableVisual; +import com.jozufozu.flywheel.api.visual.VisualFrameContext; +import com.jozufozu.flywheel.api.visual.VisualTickContext; import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.lib.instance.InstanceTypes; import com.jozufozu.flywheel.lib.instance.TransformedInstance; @@ -44,11 +46,13 @@ public class MinecartVisual extends AbstractEntityVi blockState = entity.getDisplayBlockState(); contents = createContentsInstance(); + updatePosition(); + super.init(); } @Override - public void tick() { + public void tick(VisualTickContext c) { BlockState displayBlockState = entity.getDisplayBlockState(); if (displayBlockState != blockState) { @@ -62,12 +66,19 @@ public class MinecartVisual extends AbstractEntityVi } @Override - public void beginFrame() { + public void beginFrame(VisualFrameContext context) { + if (visible(context.frustum())) { + return; + } // TODO: add proper way to temporarily disable rendering a specific instance if (!active) { return; } + updatePosition(); + } + + private void updatePosition() { TransformStack tstack = TransformStack.cast(stack); stack.setIdentity(); float pt = AnimationTickHolder.getPartialTicks(); @@ -76,7 +87,7 @@ public class MinecartVisual extends AbstractEntityVi float yaw = Mth.lerp(pt, entity.yRotO, entity.getYRot()); - long i = (long)entity.getId() * 493286711L; + long i = (long) entity.getId() * 493286711L; i = i * i * 4392167121L + i * 98761L; float f = (((float)(i >> 16 & 7L) + 0.5F) / 8 - 0.5F) * 0.004F; float f1 = (((float)(i >> 20 & 7L) + 0.5F) / 8 - 0.5F) * 0.004F; @@ -134,11 +145,6 @@ public class MinecartVisual extends AbstractEntityVi body.setTransform(stack); } - @Override - public boolean decreaseFramerateWithDistance() { - return false; - } - @Override public void updateLight() { if (contents == null) { diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/ShulkerBoxVisual.java b/src/main/java/com/jozufozu/flywheel/vanilla/ShulkerBoxVisual.java index 781b16a85..33f26c117 100644 --- a/src/main/java/com/jozufozu/flywheel/vanilla/ShulkerBoxVisual.java +++ b/src/main/java/com/jozufozu/flywheel/vanilla/ShulkerBoxVisual.java @@ -6,6 +6,7 @@ import java.util.function.Function; import com.jozufozu.flywheel.api.event.RenderStage; import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.api.visual.DynamicVisual; +import com.jozufozu.flywheel.api.visual.VisualFrameContext; import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.lib.instance.InstanceTypes; import com.jozufozu.flywheel.lib.instance.TransformedInstance; @@ -81,10 +82,15 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual