Flywheel's Visualized Rendering

- Rename Instance -> Visual (and all related elements)
- Rename InstancePart -> Instance (and all related elements)
- Move classes in package lib.format -> lib.vertex
- Rename Formats -> VertexTypes
- Remove SimpleLazyModel#setMaterial
- Remove unnecessary newlines in some files
This commit is contained in:
PepperCode1 2023-04-09 12:00:03 -07:00
parent 6648751ef7
commit 3b62a4d721
151 changed files with 2056 additions and 2106 deletions

View file

@ -15,18 +15,18 @@ import com.jozufozu.flywheel.handler.ForgeEvents;
import com.jozufozu.flywheel.impl.BackendManagerImpl;
import com.jozufozu.flywheel.impl.IdRegistryImpl;
import com.jozufozu.flywheel.impl.RegistryImpl;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.impl.visualization.VisualizedRenderDispatcher;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.format.Formats;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.material.MaterialIndices;
import com.jozufozu.flywheel.lib.material.Materials;
import com.jozufozu.flywheel.lib.model.Models;
import com.jozufozu.flywheel.lib.model.PartialModel;
import com.jozufozu.flywheel.lib.struct.StructTypes;
import com.jozufozu.flywheel.lib.util.QuadConverter;
import com.jozufozu.flywheel.lib.util.ShadersModHandler;
import com.jozufozu.flywheel.lib.vertex.VertexTypes;
import com.jozufozu.flywheel.mixin.PausedPartialTickAccessor;
import com.jozufozu.flywheel.vanilla.VanillaInstances;
import com.jozufozu.flywheel.vanilla.VanillaVisuals;
import com.mojang.logging.LogUtils;
import net.minecraft.commands.synchronization.ArgumentTypes;
@ -82,9 +82,9 @@ public class Flywheel {
forgeEventBus.addListener(Models::onReloadRenderers);
forgeEventBus.addListener(DrawBuffer::onReloadRenderers);
forgeEventBus.addListener(InstancedRenderDispatcher::onRenderStage);
forgeEventBus.addListener(InstancedRenderDispatcher::onBeginFrame);
forgeEventBus.addListener(InstancedRenderDispatcher::tick);
forgeEventBus.addListener(VisualizedRenderDispatcher::onRenderStage);
forgeEventBus.addListener(VisualizedRenderDispatcher::onBeginFrame);
forgeEventBus.addListener(VisualizedRenderDispatcher::tick);
forgeEventBus.addListener(EntityWorldHandler::onEntityJoinWorld);
forgeEventBus.addListener(EntityWorldHandler::onEntityLeaveWorld);
@ -107,14 +107,14 @@ public class Flywheel {
ShadersModHandler.init();
Formats.init();
StructTypes.init();
VertexTypes.init();
InstanceTypes.init();
Materials.init();
Contexts.init();
MaterialIndices.init();
VanillaInstances.init();
VanillaVisuals.init();
// https://github.com/Jozufozu/Flywheel/issues/69
// Weird issue with accessor loading.

View file

@ -4,7 +4,7 @@ import java.util.List;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancerProvider;
import com.jozufozu.flywheel.api.instance.InstancerProvider;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import net.minecraft.client.Camera;

View file

@ -14,7 +14,6 @@ import net.minecraft.client.renderer.RenderBuffers;
public record RenderContext(LevelRenderer renderer, ClientLevel level, PoseStack stack, Matrix4f viewProjection,
Matrix4f projection, RenderBuffers buffers, Camera camera, FrustumIntersection culler) {
@NotNull
public static Matrix4f createViewProjection(PoseStack view, Matrix4f projection) {
var viewProjection = projection.copy();

View file

@ -1,11 +0,0 @@
package com.jozufozu.flywheel.api.instance;
import java.util.List;
import com.jozufozu.flywheel.api.struct.InstancePart;
import net.minecraft.world.level.block.entity.BlockEntity;
public interface BlockEntityInstance<T extends BlockEntity> extends Instance {
List<InstancePart> getCrumblingParts();
}

View file

@ -1,6 +0,0 @@
package com.jozufozu.flywheel.api.instance;
import com.jozufozu.flywheel.api.instance.effect.Effect;
public interface EffectInstance<T extends Effect> extends Instance {
}

View file

@ -1,6 +0,0 @@
package com.jozufozu.flywheel.api.instance;
import net.minecraft.world.entity.Entity;
public interface EntityInstance<T extends Entity> extends Instance {
}

View file

@ -1,48 +1,8 @@
package com.jozufozu.flywheel.api.instance;
/**
* A general interface providing information about any type of thing that could use Flywheel's instanced rendering.
*/
public interface Instance {
InstanceType<?> type();
/**
* Initialize parts here.
*/
void init();
/**
* Update instance data here. Good for when data doesn't change very often and when animations are GPU based.
*
* <br><br> If your animations are complex or more CPU driven, see {@link DynamicInstance} or {@link TickableInstance}.
*/
void update();
/**
* When an instance is reset, the instance is deleted and re-created.
*
* <p>
* Just before {@link #update()} would be called, {@code shouldReset()} is checked.
* If this function returns {@code true}, then this instance will be {@link #delete deleted},
* and another instance will be constructed to replace it. This allows for more sane resource
* acquisition compared to trying to update everything within the lifetime of an instance.
* </p>
*
* @return {@code true} if this instance should be discarded and refreshed.
*/
boolean shouldReset();
/**
* Calculate the distance squared between this instance and the given <em>world</em> position.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param z The z coordinate.
* @return The distance squared between this instance and the given position.
*/
double distanceSquared(double x, double y, double z);
/**
* Free any acquired resources.
*/
void delete();
@Deprecated
Instance copy(InstanceHandle handle);
}

View file

@ -0,0 +1,7 @@
package com.jozufozu.flywheel.api.instance;
public interface InstanceHandle {
void setChanged();
void setDeleted();
}

View file

@ -0,0 +1,33 @@
package com.jozufozu.flywheel.api.instance;
import com.jozufozu.flywheel.api.layout.BufferLayout;
import com.jozufozu.flywheel.api.registry.Registry;
import com.jozufozu.flywheel.impl.RegistryImpl;
import net.minecraft.resources.ResourceLocation;
/**
* An InstanceType contains metadata for a specific instance that Flywheel can interface with.
*
* @param <I> The java representation of the instance.
*/
public interface InstanceType<I extends Instance> {
static Registry<InstanceType<?>> REGISTRY = RegistryImpl.create();
/**
* @param handle A handle that allows you to mark the instance as dirty or deleted.
* @return A new, zeroed instance of I.
*/
I create(InstanceHandle handle);
/**
* @return The layout of I when buffered.
*/
BufferLayout getLayout();
InstanceWriter<I> getWriter();
ResourceLocation instanceShader();
InstanceVertexTransformer<I> getVertexTransformer();
}

View file

@ -0,0 +1,9 @@
package com.jozufozu.flywheel.api.instance;
import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import net.minecraft.client.multiplayer.ClientLevel;
public interface InstanceVertexTransformer<I extends Instance> {
void transform(MutableVertexList vertexList, I instance, ClientLevel level);
}

View file

@ -0,0 +1,11 @@
package com.jozufozu.flywheel.api.instance;
/**
* InstanceWriters can quickly consume many instances and write them to some memory address.
*/
public interface InstanceWriter<I extends Instance> {
/**
* Write the given instance to the given memory address.
*/
void write(final long ptr, final I instance);
}

View file

@ -1,37 +1,35 @@
package com.jozufozu.flywheel.api.instancer;
import com.jozufozu.flywheel.api.struct.InstancePart;
package com.jozufozu.flywheel.api.instance;
/**
* An instancer is how you interact with an instanced model.
* <p>
* Instanced models can have many copies, and on most systems it's very fast to draw all of the copies at once.
* There is no limit to how many copies an instanced model can have.
* Each copy is represented by an InstanceData object.
* Each copy is represented by an Instance object.
* </p>
* <p>
* When you call {@link #createInstance()} you are given an InstanceData object that you can manipulate however
* you want. The changes you make to the InstanceData object are automatically made visible, and persistent.
* Changing the position of your InstanceData object every frame means that that copy of the model will be in a
* different position in the world each frame. Setting the position of your InstanceData once and not touching it
* When you call {@link #createInstance()} you are given an Instance object that you can manipulate however
* you want. The changes you make to the Instance object are automatically made visible, and persistent.
* Changing the position of your Instance object every frame means that that copy of the model will be in a
* different position in the world each frame. Setting the position of your Instance once and not touching it
* again means that your model will be in the same position in the world every frame. This persistence is useful
* because it means the properties of your model don't have to be re-evaluated every frame.
* </p>
*
* @param <P> the data that represents a copy of the instanced model.
* @param <I> the data that represents a copy of the instanced model.
*/
public interface Instancer<P extends InstancePart> {
public interface Instancer<I extends Instance> {
/**
* @return a handle to a new copy of this model.
*/
P createInstance();
I createInstance();
/**
* Populate arr with new instances of this model.
*
* @param arr An array to fill.
*/
default void createInstances(P[] arr) {
default void createInstances(I[] arr) {
for (int i = 0; i < arr.length; i++) {
arr[i] = createInstance();
}

View file

@ -0,0 +1,13 @@
package com.jozufozu.flywheel.api.instance;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.model.Model;
public interface InstancerProvider {
/**
* Get an instancer for the given instance type, model, and render stage. Calling this method twice with the same arguments will return the same instancer.
*
* @return An instancer for the given instance type, model, and render stage.
*/
<I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, RenderStage stage);
}

View file

@ -1,27 +0,0 @@
package com.jozufozu.flywheel.api.instance.controller;
import com.jozufozu.flywheel.api.instance.BlockEntityInstance;
import net.minecraft.world.level.block.entity.BlockEntity;
/**
* An instancing controller that will be keyed to a block entity type.
* @param <T> The block entity type.
*/
public interface BlockEntityInstancingController<T extends BlockEntity> {
/**
* Given a block entity and context, constructs an instance for the block entity.
*
* @param ctx Context for creating an Instance.
* @param blockEntity The block entity to construct an instance for.
* @return The instance.
*/
BlockEntityInstance<? super T> createInstance(InstanceContext ctx, T blockEntity);
/**
* Checks if the given block entity should not be rendered normally.
* @param blockEntity The block entity to check.
* @return {@code true} if the block entity should not be rendered normally, {@code false} if it should.
*/
boolean shouldSkipRender(T blockEntity);
}

View file

@ -1,27 +0,0 @@
package com.jozufozu.flywheel.api.instance.controller;
import com.jozufozu.flywheel.api.instance.EntityInstance;
import net.minecraft.world.entity.Entity;
/**
* An instancing controller that will be keyed to an entity type.
* @param <T> The entity type.
*/
public interface EntityInstancingController<T extends Entity> {
/**
* Given an entity and context, constructs an instance for the entity.
*
* @param ctx Context for creating an Instance.
* @param entity The entity to construct an instance for.
* @return The instance.
*/
EntityInstance<? super T> createInstance(InstanceContext ctx, T entity);
/**
* Checks if the given entity should not render normally.
* @param entity The entity to check.
* @return {@code true} if the entity should not render normally, {@code false} if it should.
*/
boolean shouldSkipRender(T entity);
}

View file

@ -1,15 +0,0 @@
package com.jozufozu.flywheel.api.instance.controller;
import com.jozufozu.flywheel.api.instancer.InstancerProvider;
import net.minecraft.core.Vec3i;
/**
* A context object passed on Instance creation.
*
* @param instancerProvider The {@link InstancerProvider} that the instance can use to get instancers to render models.
* @param renderOrigin The origin of the renderer as a world position.
* All models render as if this position is (0, 0, 0).
*/
public record InstanceContext(InstancerProvider instancerProvider, Vec3i renderOrigin) {
}

View file

@ -1,60 +0,0 @@
package com.jozufozu.flywheel.api.instance.controller;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.impl.InstancingControllerRegistryImpl;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
/**
* A utility class for registering and retrieving {@code InstancingController}s.
*/
public final class InstancingControllerRegistry {
/**
* Gets the instancing controller for the given block entity type, if one exists.
* @param type The block entity type to get the instancing controller for.
* @param <T> The type of the block entity.
* @return The instancing controller for the given block entity type, or {@code null} if none exists.
*/
@Nullable
public static <T extends BlockEntity> BlockEntityInstancingController<? super T> getController(BlockEntityType<T> type) {
return InstancingControllerRegistryImpl.getController(type);
}
/**
* Gets the instancing controller for the given entity type, if one exists.
* @param type The entity type to get the instancing controller for.
* @param <T> The type of the entity.
* @return The instancing controller for the given entity type, or {@code null} if none exists.
*/
@Nullable
public static <T extends Entity> EntityInstancingController<? super T> getController(EntityType<T> type) {
return InstancingControllerRegistryImpl.getController(type);
}
/**
* Sets the instancing controller for the given block entity type.
* @param type The block entity type to set the instancing controller for.
* @param instancingController The instancing controller to set.
* @param <T> The type of the block entity.
*/
public static <T extends BlockEntity> void setController(BlockEntityType<T> type, BlockEntityInstancingController<? super T> instancingController) {
InstancingControllerRegistryImpl.setController(type, instancingController);
}
/**
* Sets the instancing controller for the given entity type.
* @param type The entity type to set the instancing controller for.
* @param instancingController The instancing controller to set.
* @param <T> The type of the entity.
*/
public static <T extends Entity> void setController(EntityType<T> type, EntityInstancingController<? super T> instancingController) {
InstancingControllerRegistryImpl.setController(type, instancingController);
}
private InstancingControllerRegistry() {
}
}

View file

@ -1,12 +0,0 @@
package com.jozufozu.flywheel.api.instance.effect;
import java.util.Collection;
import com.jozufozu.flywheel.api.instance.EffectInstance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
// TODO: add level getter?
// TODO: return single instance instead of many?
public interface Effect {
Collection<EffectInstance<?>> createInstances(InstanceContext ctx);
}

View file

@ -1,15 +0,0 @@
package com.jozufozu.flywheel.api.instancer;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
public interface InstancerProvider {
/**
* Get an instancer for the given struct type, model, and render stage. Calling this method twice with the same arguments will return the same instancer.
*
* @return An instancer for the given struct type, model, and render stage.
*/
<P extends InstancePart> Instancer<P> instancer(StructType<P> type, Model model, RenderStage stage);
}

View file

@ -7,13 +7,13 @@ import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.gl.array.VertexAttribute;
/**
* Classic Vertex Format struct with a clever name.
* Classic Vertex Format with a clever name.
*
* <p>
* Used for vertices and instances. Describes the layout of a datatype in a buffer object.
* </p>
*
* @see com.jozufozu.flywheel.api.struct.StructType
* @see com.jozufozu.flywheel.api.instance.InstanceType
* @see VertexType
*/
public class BufferLayout {

View file

@ -1,6 +1,6 @@
package com.jozufozu.flywheel.api.pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.gl.GLSLVersion;
import com.jozufozu.flywheel.glsl.ShaderSources;
@ -9,7 +9,6 @@ import com.jozufozu.flywheel.glsl.SourceComponent;
import net.minecraft.resources.ResourceLocation;
public interface Pipeline {
GLSLVersion glslVersion();
ResourceLocation vertexShader();
@ -17,12 +16,12 @@ public interface Pipeline {
ResourceLocation fragmentShader();
/**
* Generate the source component necessary to convert a packed {@link StructType} into its shader representation.
* Generate the source component necessary to convert a packed {@link InstanceType} into its shader representation.
*
* @return A source component defining functions that unpack a representation of the given struct type.
* @return A source component defining functions that unpack a representation of the given instance type.
*/
SourceComponent assemble(InstanceAssemblerContext context);
record InstanceAssemblerContext(ShaderSources sources, VertexType vertexType, StructType<?> structType) {
record InstanceAssemblerContext(ShaderSources sources, VertexType vertexType, InstanceType<?> instanceType) {
}
}

View file

@ -1,7 +0,0 @@
package com.jozufozu.flywheel.api.struct;
public interface Handle {
void setChanged();
void setDeleted();
}

View file

@ -1,8 +0,0 @@
package com.jozufozu.flywheel.api.struct;
public interface InstancePart {
StructType<?> type();
@Deprecated
InstancePart copy(Handle handle);
}

View file

@ -1,33 +0,0 @@
package com.jozufozu.flywheel.api.struct;
import com.jozufozu.flywheel.api.layout.BufferLayout;
import com.jozufozu.flywheel.api.registry.Registry;
import com.jozufozu.flywheel.impl.RegistryImpl;
import net.minecraft.resources.ResourceLocation;
/**
* A StructType contains metadata for a specific instance struct that Flywheel can interface with.
*
* @param <P> The java representation of the instance struct.
*/
public interface StructType<P extends InstancePart> {
static Registry<StructType<?>> REGISTRY = RegistryImpl.create();
/**
* @param handle A handle that allows you to mark the instance as dirty or deleted.
* @return A new, zeroed instance of S.
*/
P create(Handle handle);
/**
* @return The layout of S when buffered.
*/
BufferLayout getLayout();
StructWriter<P> getWriter();
ResourceLocation instanceShader();
StructVertexTransformer<P> getVertexTransformer();
}

View file

@ -1,9 +0,0 @@
package com.jozufozu.flywheel.api.struct;
import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import net.minecraft.client.multiplayer.ClientLevel;
public interface StructVertexTransformer<P extends InstancePart> {
void transform(MutableVertexList vertexList, P struct, ClientLevel level);
}

View file

@ -1,11 +0,0 @@
package com.jozufozu.flywheel.api.struct;
/**
* StructWriters can quickly consume many instances and write them to some memory address.
*/
public interface StructWriter<P extends InstancePart> {
/**
* Write the given struct to the given memory address.
*/
void write(final long ptr, final P struct);
}

View file

@ -0,0 +1,11 @@
package com.jozufozu.flywheel.api.visual;
import java.util.List;
import com.jozufozu.flywheel.api.instance.Instance;
import net.minecraft.world.level.block.entity.BlockEntity;
public interface BlockEntityVisual<T extends BlockEntity> extends Visual {
List<Instance> getCrumblingInstances();
}

View file

@ -1,48 +1,48 @@
package com.jozufozu.flywheel.api.instance;
package com.jozufozu.flywheel.api.visual;
import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.Instancer;
/**
* An interface giving {@link Instance}s a hook to have a function called at
* the start of a frame. By implementing {@link DynamicInstance}, an {@link Instance}
* An interface giving {@link Visual}s a hook to have a function called at
* 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.
*
* <br><br> 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 DynamicInstance extends Instance {
public interface DynamicVisual extends Visual {
/**
* Called every frame, and after initialization.
* <br>
* <em>DISPATCHED IN PARALLEL</em>, don't attempt to mutate anything outside this instance.
* <em>DISPATCHED IN PARALLEL</em>, don't attempt to mutate anything outside this visual.
* <br>
* {@link Instancer}/{@link InstancePart} creation/acquisition is safe here.
* {@link Instancer}/{@link Instance} creation/acquisition is safe here.
*/
void beginFrame();
/**
* As a further optimization, dynamic instances that are far away are ticked less often.
* As a further optimization, dynamic visuals that are far away are updated less often.
* This behavior can be disabled by returning false.
*
* <br> 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 instance should be slow ticked.
* @return {@code true} if your visual should be slow updated.
*/
default boolean decreaseFramerateWithDistance() {
return true;
}
/**
* Check this instance against a frustum.<p>
* Check this visual against a frustum.<p>
* 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 instance should be considered for updates.
* @return {@code true} if this visual should be considered for updates.
*/
boolean isVisible(FrustumIntersection frustum);
}

View file

@ -0,0 +1,11 @@
package com.jozufozu.flywheel.api.visual;
import java.util.Collection;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
// TODO: add level getter?
// TODO: return single visual instead of many?
public interface Effect {
Collection<EffectVisual<?>> createVisuals(VisualizationContext ctx);
}

View file

@ -0,0 +1,4 @@
package com.jozufozu.flywheel.api.visual;
public interface EffectVisual<T extends Effect> extends Visual {
}

View file

@ -0,0 +1,6 @@
package com.jozufozu.flywheel.api.visual;
import net.minecraft.world.entity.Entity;
public interface EntityVisual<T extends Entity> extends Visual {
}

View file

@ -1,17 +1,17 @@
package com.jozufozu.flywheel.api.instance;
package com.jozufozu.flywheel.api.visual;
import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.Instancer;
/**
* An interface giving {@link Instance}s a hook to have a function called at
* the end of every tick. By implementing {@link TickableInstance}, an {@link Instance}
* An interface giving {@link Visual}s a hook to have a function called at
* the end of every tick. By implementing {@link TickableVisual}, an {@link Visual}
* can update frequently, but not every frame.
* <br> There are a few cases in which this should be considered over {@link DynamicInstance}:
* <br> There are a few cases in which this should be considered over {@link DynamicVisual}:
* <ul>
* <li>
* You'd like to change something about the instance every now and then.
* eg. adding or removing parts, snapping to a different rotation, etc.
* You'd like to change something about the visual every now and then.
* eg. adding or removing instances, snapping to a different rotation, etc.
* </li>
* <li>
* Your BlockEntity does animate, but the animation doesn't have
@ -19,24 +19,23 @@ import com.jozufozu.flywheel.api.struct.InstancePart;
* </li>
* </ul>
*/
public interface TickableInstance extends Instance {
public interface TickableVisual extends Visual {
/**
* Called every tick, and after initialization.<p>
* <em>DISPATCHED IN PARALLEL</em>, don't attempt to mutate anything outside of this instance
* <em>DISPATCHED IN PARALLEL</em>, don't attempt to mutate anything outside of this visual
* without proper synchronization.<p>
* {@link Instancer}/{@link InstancePart} creation/acquisition is safe here.
* {@link Instancer}/{@link Instance} creation/acquisition is safe here.
*/
void tick();
/**
* As a further optimization, tickable instances that are far away are ticked less often.
* As a further optimization, tickable visuals that are far away are ticked less often.
* This behavior can be disabled by returning false.
*
* <br> 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 instance should be slow ticked.
* @return {@code true} if your visual should be slow ticked.
*/
default boolean decreaseTickRateWithDistance() {
return true;

View file

@ -0,0 +1,47 @@
package com.jozufozu.flywheel.api.visual;
/**
* A general interface providing information about any type of thing that could use Flywheel's visualized rendering.
*/
public interface Visual {
/**
* Initialize instances here.
*/
void init();
/**
* Update instances here. Good for when instances don't change very often and when animations are GPU based.
*
* <br><br> If your animations are complex or more CPU driven, see {@link DynamicVisual} or {@link TickableVisual}.
*/
void update();
/**
* When a visual is reset, the visual is deleted and re-created.
*
* <p>
* Just before {@link #update()} would be called, {@code shouldReset()} is checked.
* If this function returns {@code true}, then this visual will be {@link #delete deleted},
* and another visual will be constructed to replace it. This allows for more sane resource
* acquisition compared to trying to update everything within the lifetime of a visual.
* </p>
*
* @return {@code true} if this visual should be discarded and refreshed.
*/
boolean shouldReset();
/**
* Calculate the distance squared between this visual and the given <em>world</em> 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.
*/
void delete();
}

View file

@ -0,0 +1,27 @@
package com.jozufozu.flywheel.api.visualization;
import com.jozufozu.flywheel.api.visual.BlockEntityVisual;
import net.minecraft.world.level.block.entity.BlockEntity;
/**
* A visualizer that will be keyed to a block entity type.
* @param <T> The block entity type.
*/
public interface BlockEntityVisualizer<T extends BlockEntity> {
/**
* Given a block entity and context, constructs a visual for the block entity.
*
* @param ctx Context for creating a visual.
* @param blockEntity The block entity to construct a visual for.
* @return The visual.
*/
BlockEntityVisual<? super T> createVisual(VisualizationContext ctx, T blockEntity);
/**
* Checks if the given block entity should not be rendered normally.
* @param blockEntity The block entity to check.
* @return {@code true} if the block entity should not be rendered normally, {@code false} if it should.
*/
boolean shouldSkipRender(T blockEntity);
}

View file

@ -0,0 +1,27 @@
package com.jozufozu.flywheel.api.visualization;
import com.jozufozu.flywheel.api.visual.EntityVisual;
import net.minecraft.world.entity.Entity;
/**
* A visualizer that will be keyed to an entity type.
* @param <T> The entity type.
*/
public interface EntityVisualizer<T extends Entity> {
/**
* Given an entity and context, constructs a visual for the entity.
*
* @param ctx Context for creating a visual.
* @param entity The entity to construct a visual for.
* @return The visual.
*/
EntityVisual<? super T> createVisual(VisualizationContext ctx, T entity);
/**
* Checks if the given entity should not render normally.
* @param entity The entity to check.
* @return {@code true} if the entity should not render normally, {@code false} if it should.
*/
boolean shouldSkipRender(T entity);
}

View file

@ -0,0 +1,15 @@
package com.jozufozu.flywheel.api.visualization;
import com.jozufozu.flywheel.api.instance.InstancerProvider;
import net.minecraft.core.Vec3i;
/**
* A context object passed on visual creation.
*
* @param instancerProvider The {@link InstancerProvider} that the visual can use to get instancers to render models.
* @param renderOrigin The origin of the renderer as a world position.
* All models render as if this position is (0, 0, 0).
*/
public record VisualizationContext(InstancerProvider instancerProvider, Vec3i renderOrigin) {
}

View file

@ -0,0 +1,60 @@
package com.jozufozu.flywheel.api.visualization;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.impl.VisualizerRegistryImpl;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
/**
* A utility class for registering and retrieving {@code Visualizer}s.
*/
public final class VisualizerRegistry {
/**
* Gets the visualizer for the given block entity type, if one exists.
* @param type The block entity type to get the visualizer for.
* @param <T> The type of the block entity.
* @return The visualizer for the given block entity type, or {@code null} if none exists.
*/
@Nullable
public static <T extends BlockEntity> BlockEntityVisualizer<? super T> getVisualizer(BlockEntityType<T> type) {
return VisualizerRegistryImpl.getVisualizer(type);
}
/**
* Gets the visualizer for the given entity type, if one exists.
* @param type The entity type to get the visualizer for.
* @param <T> The type of the entity.
* @return The visualizer for the given entity type, or {@code null} if none exists.
*/
@Nullable
public static <T extends Entity> EntityVisualizer<? super T> getVisualizer(EntityType<T> type) {
return VisualizerRegistryImpl.getVisualizer(type);
}
/**
* Sets the visualizer for the given block entity type.
* @param type The block entity type to set the visualizer for.
* @param visualizer The visualizer to set.
* @param <T> The type of the block entity.
*/
public static <T extends BlockEntity> void setVisualizer(BlockEntityType<T> type, BlockEntityVisualizer<? super T> visualizer) {
VisualizerRegistryImpl.setVisualizer(type, visualizer);
}
/**
* Sets the visualizer for the given entity type.
* @param type The entity type to set the visualizer for.
* @param visualizer The visualizer to set.
* @param <T> The type of the entity.
*/
public static <T extends Entity> void setVisualizer(EntityType<T> type, EntityVisualizer<? super T> visualizer) {
VisualizerRegistryImpl.setVisualizer(type, visualizer);
}
private VisualizerRegistry() {
}
}

View file

@ -1,6 +1,6 @@
package com.jozufozu.flywheel.backend.compile;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.instance.InstanceType;
public record CullingContext(StructType<?> structType) {
public record CullingContext(InstanceType<?> instanceType) {
}

View file

@ -4,13 +4,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.instance.InstanceType;
public class CullingContextSet {
static CullingContextSet create() {
var builder = new CullingContextSet();
for (StructType<?> structType : StructType.REGISTRY) {
builder.add(structType);
for (InstanceType<?> instanceType : InstanceType.REGISTRY) {
builder.add(instanceType);
}
return builder;
}
@ -29,8 +29,8 @@ public class CullingContextSet {
return contexts.size();
}
private void add(StructType<?> structType) {
var ctx = new CullingContext(structType);
private void add(InstanceType<?> instanceType) {
var ctx = new CullingContext(instanceType);
contexts.add(ctx);
}

View file

@ -13,8 +13,8 @@ import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.uniform.ShaderUniforms;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.Pipelines;
@ -45,7 +45,7 @@ public class FlwCompiler {
final ShaderCompiler shaderCompiler;
final Map<PipelineContext, GlProgram> pipelinePrograms = new HashMap<>();
final Map<StructType<?>, GlProgram> cullingPrograms = new HashMap<>();
final Map<InstanceType<?>, GlProgram> cullingPrograms = new HashMap<>();
final List<FailedCompilation> errors = new ArrayList<>();
public FlwCompiler(ShaderSources sources) {
@ -101,7 +101,7 @@ public class FlwCompiler {
private void finish() {
long compileEnd = System.nanoTime();
int programCount = pipelineContexts.size() + StructType.REGISTRY.getAll().size();
int programCount = pipelineContexts.size() + InstanceType.REGISTRY.getAll().size();
int shaderCount = shaderCompiler.shaderCount();
int errorCount = errors.size();
var elapsed = StringUtil.formatTime(compileEnd - compileStart);
@ -124,12 +124,12 @@ public class FlwCompiler {
shaderCompiler.delete();
}
public GlProgram getPipelineProgram(VertexType vertexType, StructType<?> structType, Context contextShader, Pipeline pipelineShader) {
return pipelinePrograms.get(new PipelineContext(vertexType, structType, contextShader, pipelineShader));
public GlProgram getPipelineProgram(VertexType vertexType, InstanceType<?> instanceType, Context contextShader, Pipeline pipelineShader) {
return pipelinePrograms.get(new PipelineContext(vertexType, instanceType, contextShader, pipelineShader));
}
public GlProgram getCullingProgram(StructType<?> structType) {
return cullingPrograms.get(structType);
public GlProgram getCullingProgram(InstanceType<?> instanceType) {
return cullingPrograms.get(instanceType);
}
private void compilePipelineContext(PipelineContext ctx) {
@ -150,14 +150,14 @@ public class FlwCompiler {
}
private void compileComputeCuller(CullingContext ctx) {
var computeComponents = getComputeComponents(ctx.structType());
var computeComponents = getComputeComponents(ctx.instanceType());
var result = shaderCompiler.compile(GLSLVersion.V460, ShaderType.COMPUTE, computeComponents);
if (result == null) {
return;
}
cullingPrograms.put(ctx.structType(), link(result.handle()));
cullingPrograms.put(ctx.instanceType(), link(result.handle()));
}
private GlProgram link(int... shaders) {
@ -172,11 +172,11 @@ public class FlwCompiler {
private ImmutableList<SourceComponent> getVertexComponents(PipelineContext ctx) {
var instanceAssembly = ctx.pipelineShader()
.assemble(new Pipeline.InstanceAssemblerContext(sources, ctx.vertexType(), ctx.structType()));
.assemble(new Pipeline.InstanceAssemblerContext(sources, ctx.vertexType(), ctx.instanceType()));
var layout = sources.find(ctx.vertexType()
.layoutShader());
var instance = sources.find(ctx.structType()
var instance = sources.find(ctx.instanceType()
.instanceShader());
var context = sources.find(ctx.contextShader()
.vertexShader());
@ -194,9 +194,9 @@ public class FlwCompiler {
return ImmutableList.of(uniformComponent, fragmentMaterialComponent, context, pipeline);
}
private ImmutableList<SourceComponent> getComputeComponents(StructType<?> structType) {
var instanceAssembly = new IndirectComponent(sources, structType);
var instance = sources.find(structType.instanceShader());
private ImmutableList<SourceComponent> getComputeComponents(InstanceType<?> instanceType) {
var instanceAssembly = new IndirectComponent(sources, instanceType);
var instance = sources.find(instanceType.instanceShader());
var pipeline = sources.find(Pipelines.Files.INDIRECT_CULL);
return ImmutableList.of(uniformComponent, instanceAssembly, instance, pipeline);

View file

@ -1,17 +1,17 @@
package com.jozufozu.flywheel.backend.compile;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
/**
* Represents the entire context of a program's usage.
*
* @param vertexType The vertexType the program should be adapted for.
* @param structType The instance shader to use.
* @param instanceType The instance shader to use.
* @param contextShader The context shader to use.
*/
public record PipelineContext(VertexType vertexType, StructType<?> structType, Context contextShader,
public record PipelineContext(VertexType vertexType, InstanceType<?> instanceType, Context contextShader,
Pipeline pipelineShader) {
}

View file

@ -9,8 +9,8 @@ import java.util.stream.Collectors;
import com.jozufozu.flywheel.api.backend.Backend;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.lib.context.Contexts;
@ -24,9 +24,9 @@ public class PipelineContextSet {
static PipelineContextSet create() {
var builder = new PipelineContextSet();
for (Pipeline pipelineShader : availablePipelineShaders()) {
for (StructType<?> structType : StructType.REGISTRY) {
for (InstanceType<?> instanceType : InstanceType.REGISTRY) {
for (VertexType vertexType : VertexType.REGISTRY) {
builder.add(vertexType, structType, Contexts.WORLD, pipelineShader);
builder.add(vertexType, instanceType, Contexts.WORLD, pipelineShader);
}
}
}
@ -50,8 +50,8 @@ public class PipelineContextSet {
return contexts.size();
}
private void add(VertexType vertexType, StructType<?> structType, Context world, Pipeline pipelineShader) {
var ctx = new PipelineContext(vertexType, structType, world, pipelineShader);
private void add(VertexType vertexType, InstanceType<?> instanceType, Context world, Pipeline pipelineShader) {
var ctx = new PipelineContext(vertexType, instanceType, world, pipelineShader);
contexts.add(ctx);
}

View file

@ -3,45 +3,42 @@ package com.jozufozu.flywheel.backend.engine;
import java.util.ArrayList;
import java.util.BitSet;
import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
public abstract class AbstractInstancer<P extends InstancePart> implements Instancer<P> {
public final StructType<P> type;
public abstract class AbstractInstancer<I extends Instance> implements Instancer<I> {
public final InstanceType<I> type;
// Lock for all instance data, only needs to be used in methods that may run on the TaskExecutor.
// Lock for all instances, only needs to be used in methods that may run on the TaskExecutor.
protected final Object lock = new Object();
protected final ArrayList<P> data = new ArrayList<>();
protected final ArrayList<HandleImpl> handles = new ArrayList<>();
protected final ArrayList<I> instances = new ArrayList<>();
protected final ArrayList<InstanceHandleImpl> handles = new ArrayList<>();
// TODO: atomic bitset?
protected final BitSet changed = new BitSet();
protected final BitSet deleted = new BitSet();
protected AbstractInstancer(StructType<P> type) {
protected AbstractInstancer(InstanceType<I> type) {
this.type = type;
}
/**
* @return a handle to a new copy of this model.
*/
@Override
public P createInstance() {
public I createInstance() {
synchronized (lock) {
var i = data.size();
var handle = new HandleImpl(this, i);
P instanceData = type.create(handle);
var i = instances.size();
var handle = new InstanceHandleImpl(this, i);
I instance = type.create(handle);
data.add(instanceData);
instances.add(instance);
handles.add(handle);
changed.set(i);
return instanceData;
return instance;
}
}
public int getInstanceCount() {
return data.size();
return instances.size();
}
public void notifyDirty(int index) {
@ -68,7 +65,7 @@ public abstract class AbstractInstancer<P extends InstancePart> implements Insta
}
// Figure out which elements are to be removed.
final int oldSize = this.data.size();
final int oldSize = this.instances.size();
int removeCount = deleted.cardinality();
final int newSize = oldSize - removeCount;
@ -79,10 +76,10 @@ public abstract class AbstractInstancer<P extends InstancePart> implements Insta
if (i != j) {
var handle = handles.get(i);
P element = data.get(i);
I instance = instances.get(i);
handles.set(j, handle);
data.set(j, element);
instances.set(j, instance);
handle.setIndex(j);
changed.set(j);
@ -90,18 +87,18 @@ public abstract class AbstractInstancer<P extends InstancePart> implements Insta
}
deleted.clear();
data.subList(newSize, oldSize)
instances.subList(newSize, oldSize)
.clear();
handles.subList(newSize, oldSize)
.clear();
}
/**
* Clear all instance data without freeing resources.
* Clear all instances without freeing resources.
*/
public void clear() {
handles.forEach(HandleImpl::clear);
data.clear();
handles.forEach(InstanceHandleImpl::clear);
instances.clear();
handles.clear();
changed.clear();
deleted.clear();

View file

@ -1,12 +1,12 @@
package com.jozufozu.flywheel.backend.engine;
import com.jozufozu.flywheel.api.struct.Handle;
import com.jozufozu.flywheel.api.instance.InstanceHandle;
public class HandleImpl implements Handle {
public class InstanceHandleImpl implements InstanceHandle {
private final AbstractInstancer<?> instancer;
private int index;
public HandleImpl(AbstractInstancer<?> instancer, int index) {
public InstanceHandleImpl(AbstractInstancer<?> instancer, int index) {
this.instancer = instancer;
this.index = index;
}

View file

@ -1,9 +1,9 @@
package com.jozufozu.flywheel.backend.engine;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
public record InstancerKey<P extends InstancePart>(StructType<P> type, Model model, RenderStage stage) {
public record InstancerKey<I extends Instance>(InstanceType<I> type, Model model, RenderStage stage) {
}

View file

@ -5,10 +5,10 @@ import java.util.List;
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.instancer.Instancer;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.util.FlwUtil;
import com.mojang.blaze3d.vertex.PoseStack;
@ -24,7 +24,7 @@ public class BatchingEngine implements Engine {
private final BatchingDrawTracker drawTracker = new BatchingDrawTracker();
@Override
public <P extends InstancePart> Instancer<P> instancer(StructType<P> type, Model model, RenderStage stage) {
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, RenderStage stage) {
return transformManager.getInstancer(type, model, stage);
}

View file

@ -15,11 +15,11 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.mojang.blaze3d.vertex.VertexFormat;
@ -42,9 +42,9 @@ public class BatchingTransformManager {
}
@SuppressWarnings("unchecked")
public <P extends InstancePart> Instancer<P> getInstancer(StructType<P> type, Model model, RenderStage stage) {
InstancerKey<P> key = new InstancerKey<>(type, model, stage);
CPUInstancer<P> instancer = (CPUInstancer<P>) instancers.get(key);
public <I extends Instance> Instancer<I> getInstancer(InstanceType<I> type, Model model, RenderStage stage) {
InstancerKey<I> key = new InstancerKey<>(type, model, stage);
CPUInstancer<I> instancer = (CPUInstancer<I>) instancers.get(key);
if (instancer == null) {
instancer = new CPUInstancer<>(type);
instancers.put(key, instancer);

View file

@ -2,21 +2,21 @@ package com.jozufozu.flywheel.backend.engine.batching;
import java.util.List;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
public class CPUInstancer<P extends InstancePart> extends AbstractInstancer<P> {
public CPUInstancer(StructType<P> type) {
public class CPUInstancer<I extends Instance> extends AbstractInstancer<I> {
public CPUInstancer(InstanceType<I> type) {
super(type);
}
public List<P> getRange(int start, int end) {
return data.subList(start, end);
public List<I> getRange(int start, int end) {
return instances.subList(start, end);
}
public List<P> getAll() {
return data;
public List<I> getAll() {
return instances;
}
public void update() {

View file

@ -2,9 +2,9 @@ package com.jozufozu.flywheel.backend.engine.batching;
import java.util.List;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceVertexTransformer;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructVertexTransformer;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import com.jozufozu.flywheel.api.vertex.ReusableVertexList;
@ -15,15 +15,15 @@ import com.mojang.math.Matrix4f;
import net.minecraft.client.multiplayer.ClientLevel;
public class TransformCall<P extends InstancePart> {
private final CPUInstancer<P> instancer;
public class TransformCall<I extends Instance> {
private final CPUInstancer<I> instancer;
private final Material material;
private final BatchedMeshPool.BufferedMesh mesh;
private final int meshVertexCount;
private final int meshByteSize;
public TransformCall(CPUInstancer<P> instancer, Material material, BatchedMeshPool.BufferedMesh mesh) {
public TransformCall(CPUInstancer<I> instancer, Material material, BatchedMeshPool.BufferedMesh mesh) {
this.instancer = instancer;
this.material = material;
this.mesh = mesh;
@ -64,18 +64,18 @@ public class TransformCall<P extends InstancePart> {
transformList(vertexList, instancer.getAll(), matrices, level);
}
public void transformList(ReusableVertexList vertexList, List<P> parts, PoseStack.Pose matrices, ClientLevel level) {
public void transformList(ReusableVertexList vertexList, List<I> instances, PoseStack.Pose matrices, ClientLevel level) {
long anchorPtr = vertexList.ptr();
int totalVertexCount = vertexList.vertexCount();
vertexList.vertexCount(meshVertexCount);
StructVertexTransformer<P> structVertexTransformer = instancer.type.getVertexTransformer();
InstanceVertexTransformer<I> instanceVertexTransformer = instancer.type.getVertexTransformer();
for (P p : parts) {
for (I instance : instances) {
mesh.copyTo(vertexList.ptr());
structVertexTransformer.transform(vertexList, p, level);
instanceVertexTransformer.transform(vertexList, instance, level);
vertexList.ptr(vertexList.ptr() + meshByteSize);
}

View file

@ -5,9 +5,9 @@ import java.util.List;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.layout.LayoutItem;
import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.Pipelines;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.glsl.SourceComponent;
@ -30,11 +30,11 @@ public class IndirectComponent implements SourceComponent {
private final ImmutableList<SourceFile> included;
public IndirectComponent(Pipeline.InstanceAssemblerContext ctx) {
this(ctx.sources(), ctx.structType());
this(ctx.sources(), ctx.instanceType());
}
public IndirectComponent(ShaderSources sources, StructType<?> structType) {
this.layoutItems = structType.getLayout().layoutItems;
public IndirectComponent(ShaderSources sources, InstanceType<?> instanceType) {
this.layoutItems = instanceType.getLayout().layoutItems;
included = ImmutableList.of(sources.find(Pipelines.Files.UTIL_TYPES));
}

View file

@ -12,8 +12,8 @@ import static org.lwjgl.opengl.GL45.glVertexArrayElementBuffer;
import static org.lwjgl.opengl.GL45.glVertexArrayVertexBuffer;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.Pipelines;
import com.jozufozu.flywheel.backend.compile.FlwCompiler;
@ -22,14 +22,14 @@ import com.jozufozu.flywheel.gl.shader.GlProgram;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.util.QuadConverter;
public class IndirectCullingGroup<P extends InstancePart> {
public class IndirectCullingGroup<I extends Instance> {
private static final int BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT;
final GlProgram compute;
final GlProgram draw;
private final VertexType vertexType;
private final long objectStride;
private final long instanceStride;
final IndirectBuffers buffers;
@ -38,18 +38,18 @@ public class IndirectCullingGroup<P extends InstancePart> {
int vertexArray;
final IndirectDrawSet<P> drawSet = new IndirectDrawSet<>();
final IndirectDrawSet<I> drawSet = new IndirectDrawSet<>();
private boolean hasCulledThisFrame;
private boolean needsMemoryBarrier;
private int instanceCountThisFrame;
IndirectCullingGroup(StructType<P> structType, VertexType vertexType) {
IndirectCullingGroup(InstanceType<I> instanceType, VertexType vertexType) {
this.vertexType = vertexType;
objectStride = structType.getLayout()
instanceStride = instanceType.getLayout()
.getStride();
buffers = new IndirectBuffers(objectStride);
buffers = new IndirectBuffers(instanceStride);
buffers.createBuffers();
buffers.createObjectStorage(128);
buffers.createDrawStorage(2);
@ -62,8 +62,8 @@ public class IndirectCullingGroup<P extends InstancePart> {
.quads2Tris(2048).glBuffer;
setupVertexArray();
compute = FlwCompiler.INSTANCE.getCullingProgram(structType);
draw = FlwCompiler.INSTANCE.getPipelineProgram(vertexType, structType, Contexts.WORLD, Pipelines.INDIRECT);
compute = FlwCompiler.INSTANCE.getCullingProgram(instanceType);
draw = FlwCompiler.INSTANCE.getPipelineProgram(vertexType, instanceType, Contexts.WORLD, Pipelines.INDIRECT);
}
private void setupVertexArray() {
@ -110,7 +110,7 @@ public class IndirectCullingGroup<P extends InstancePart> {
buffers.updateCounts(instanceCountThisFrame, drawSet.size());
meshPool.flush();
uploadInstanceData();
uploadInstances();
uploadIndirectCommands();
UniformBuffer.syncAndBind(compute);
@ -143,7 +143,7 @@ public class IndirectCullingGroup<P extends InstancePart> {
}
}
private void uploadInstanceData() {
private void uploadInstances() {
long objectPtr = buffers.objectPtr;
long batchIDPtr = buffers.batchPtr;
@ -153,7 +153,7 @@ public class IndirectCullingGroup<P extends InstancePart> {
.getInstanceCount();
batch.writeObjects(objectPtr, batchIDPtr, i);
objectPtr += instanceCount * objectStride;
objectPtr += instanceCount * instanceStride;
batchIDPtr += instanceCount * IndirectBuffers.INT_SIZE;
}

View file

@ -3,12 +3,12 @@ package com.jozufozu.flywheel.backend.engine.indirect;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.lib.material.MaterialIndices;
public class IndirectDraw<P extends InstancePart> {
private final IndirectInstancer<P> instancer;
public class IndirectDraw<I extends Instance> {
private final IndirectInstancer<I> instancer;
private final IndirectMeshPool.BufferedMesh mesh;
private final Material material;
private final RenderStage stage;
@ -19,7 +19,7 @@ public class IndirectDraw<P extends InstancePart> {
private int baseInstance = -1;
private boolean needsFullWrite = true;
public IndirectDraw(IndirectInstancer<P> instancer, Material material, IndirectMeshPool.BufferedMesh mesh, RenderStage stage) {
public IndirectDraw(IndirectInstancer<I> instancer, Material material, IndirectMeshPool.BufferedMesh mesh, RenderStage stage) {
this.instancer = instancer;
this.material = material;
this.mesh = mesh;
@ -29,7 +29,7 @@ public class IndirectDraw<P extends InstancePart> {
this.fragmentMaterialID = MaterialIndices.getFragmentShaderIndex(material);
}
public IndirectInstancer<P> instancer() {
public IndirectInstancer<I> instancer() {
return instancer;
}

View file

@ -6,10 +6,10 @@ import java.util.List;
import java.util.Map;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.util.Pair;
@ -18,12 +18,12 @@ public class IndirectDrawManager {
private final Map<InstancerKey<?>, IndirectInstancer<?>> instancers = new HashMap<>();
private final List<UninitializedInstancer> uninitializedInstancers = new ArrayList<>();
private final List<IndirectInstancer<?>> initializedInstancers = new ArrayList<>();
public final Map<Pair<StructType<?>, VertexType>, IndirectCullingGroup<?>> renderLists = new HashMap<>();
public final Map<Pair<InstanceType<?>, VertexType>, IndirectCullingGroup<?>> renderLists = new HashMap<>();
@SuppressWarnings("unchecked")
public <P extends InstancePart> Instancer<P> getInstancer(StructType<P> type, Model model, RenderStage stage) {
InstancerKey<P> key = new InstancerKey<>(type, model, stage);
IndirectInstancer<P> instancer = (IndirectInstancer<P>) instancers.get(key);
public <I extends Instance> Instancer<I> getInstancer(InstanceType<I> type, Model model, RenderStage stage) {
InstancerKey<I> key = new InstancerKey<>(type, model, stage);
IndirectInstancer<I> instancer = (IndirectInstancer<I>) instancers.get(key);
if (instancer == null) {
instancer = new IndirectInstancer<>(type);
instancers.put(key, instancer);
@ -58,13 +58,13 @@ public class IndirectDrawManager {
}
@SuppressWarnings("unchecked")
private <P extends InstancePart> void add(IndirectInstancer<P> instancer, Model model, RenderStage stage) {
private <I extends Instance> void add(IndirectInstancer<I> instancer, Model model, RenderStage stage) {
var meshes = model.getMeshes();
for (var entry : meshes.entrySet()) {
var material = entry.getKey();
var mesh = entry.getValue();
var indirectList = (IndirectCullingGroup<P>) renderLists.computeIfAbsent(Pair.of(instancer.type, mesh.getVertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second()));
var indirectList = (IndirectCullingGroup<I>) renderLists.computeIfAbsent(Pair.of(instancer.type, mesh.getVertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second()));
indirectList.drawSet.add(instancer, material, stage, indirectList.meshPool.alloc(mesh));

View file

@ -11,13 +11,13 @@ import java.util.List;
import java.util.Map;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.lib.material.MaterialIndices;
public class IndirectDrawSet<P extends InstancePart> {
public class IndirectDrawSet<I extends Instance> {
final List<IndirectDraw<P>> indirectDraws = new ArrayList<>();
final List<IndirectDraw<I>> indirectDraws = new ArrayList<>();
final Map<RenderStage, List<MultiDraw>> multiDraws = new EnumMap<>(RenderStage.class);
@ -29,7 +29,7 @@ public class IndirectDrawSet<P extends InstancePart> {
return indirectDraws.size();
}
public void add(IndirectInstancer<P> instancer, Material material, RenderStage stage, IndirectMeshPool.BufferedMesh bufferedMesh) {
public void add(IndirectInstancer<I> instancer, Material material, RenderStage stage, IndirectMeshPool.BufferedMesh bufferedMesh) {
indirectDraws.add(new IndirectDraw<>(instancer, material, bufferedMesh, stage));
determineMultiDraws();
}
@ -48,7 +48,7 @@ public class IndirectDrawSet<P extends InstancePart> {
// TODO: Better material equality. Really we only need to bin by the results of the setup method.
multiDraws.clear();
// sort by stage, then material
indirectDraws.sort(Comparator.comparing(IndirectDraw<P>::stage)
indirectDraws.sort(Comparator.comparing(IndirectDraw<I>::stage)
.thenComparing(draw -> MaterialIndices.getMaterialIndex(draw.material())));
for (int start = 0, i = 0; i < indirectDraws.size(); i++) {

View file

@ -7,10 +7,10 @@ import org.lwjgl.opengl.GL32;
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.instancer.Instancer;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.gl.GlStateTracker;
import com.jozufozu.flywheel.gl.GlTextureUnit;
@ -34,7 +34,7 @@ public class IndirectEngine implements Engine {
}
@Override
public <P extends InstancePart> Instancer<P> instancer(StructType<P> type, Model model, RenderStage stage) {
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, RenderStage stage) {
return drawManager.getInstancer(type, model, stage);
}

View file

@ -2,15 +2,15 @@ package com.jozufozu.flywheel.backend.engine.indirect;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.struct.StructWriter;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.InstanceWriter;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
public class IndirectInstancer<P extends InstancePart> extends AbstractInstancer<P> {
public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I> {
private final long instanceStride;
public IndirectInstancer(StructType<P> type) {
public IndirectInstancer(InstanceType<I> type) {
super(type);
this.instanceStride = type.getLayout()
.getStride();
@ -21,11 +21,11 @@ public class IndirectInstancer<P extends InstancePart> extends AbstractInstancer
}
public void writeSparse(long objectPtr, long batchIDPtr, int batchID) {
int count = data.size();
StructWriter<P> writer = type.getWriter();
int count = instances.size();
InstanceWriter<I> writer = type.getWriter();
for (int i = changed.nextSetBit(0); i >= 0 && i < count; i = changed.nextSetBit(i + 1)) {
// write object
writer.write(objectPtr + instanceStride * i, data.get(i));
writer.write(objectPtr + instanceStride * i, instances.get(i));
// write batchID
MemoryUtil.memPutInt(batchIDPtr + IndirectBuffers.INT_SIZE * i, batchID);
@ -34,8 +34,8 @@ public class IndirectInstancer<P extends InstancePart> extends AbstractInstancer
}
public void writeFull(long objectPtr, long batchIDPtr, int batchID) {
StructWriter<P> writer = type.getWriter();
for (var object : data) {
InstanceWriter<I> writer = type.getWriter();
for (I object : instances) {
// write object
writer.write(objectPtr, object);
objectPtr += instanceStride;

View file

@ -4,10 +4,10 @@ import java.util.HashSet;
import java.util.Set;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.InstanceWriter;
import com.jozufozu.flywheel.api.layout.BufferLayout;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.struct.StructWriter;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
import com.jozufozu.flywheel.gl.array.GlVertexArray;
import com.jozufozu.flywheel.gl.buffer.GlBuffer;
@ -15,14 +15,14 @@ import com.jozufozu.flywheel.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.gl.buffer.GlBufferUsage;
import com.jozufozu.flywheel.gl.buffer.MappedBuffer;
public class GPUInstancer<P extends InstancePart> extends AbstractInstancer<P> {
public class GPUInstancer<I extends Instance> extends AbstractInstancer<I> {
private final BufferLayout instanceFormat;
private final int instanceStride;
private final Set<GlVertexArray> boundTo = new HashSet<>();
private GlBuffer vbo;
public GPUInstancer(StructType<P> type) {
public GPUInstancer(InstanceType<I> type) {
super(type);
instanceFormat = type.getLayout();
instanceStride = instanceFormat.getStride();
@ -52,7 +52,7 @@ public class GPUInstancer<P extends InstancePart> extends AbstractInstancer<P> {
}
private void ensureBufferCapacity() {
int count = data.size();
int count = instances.size();
int byteSize = instanceStride * count;
if (vbo.ensureCapacity(byteSize)) {
// The vbo has moved, so we need to re-bind attributes
@ -65,7 +65,7 @@ public class GPUInstancer<P extends InstancePart> extends AbstractInstancer<P> {
return;
}
int count = data.size();
int count = instances.size();
long clearStart = instanceStride * (long) count;
long clearLength = vbo.getSize() - clearStart;
@ -73,10 +73,10 @@ public class GPUInstancer<P extends InstancePart> extends AbstractInstancer<P> {
buf.clear(clearStart, clearLength);
long ptr = buf.getPtr();
StructWriter<P> writer = type.getWriter();
InstanceWriter<I> writer = type.getWriter();
for (int i = changed.nextSetBit(0); i >= 0 && i < count; i = changed.nextSetBit(i + 1)) {
writer.write(ptr + instanceStride * i, data.get(i));
writer.write(ptr + instanceStride * i, instances.get(i));
}
changed.clear();

View file

@ -23,7 +23,7 @@ public class InstancedArraysComponent implements SourceComponent {
private final int baseIndex;
public InstancedArraysComponent(Pipeline.InstanceAssemblerContext ctx) {
this.layoutItems = ctx.structType()
this.layoutItems = ctx.instanceType()
.getLayout().layoutItems;
this.baseIndex = ctx.vertexType()
.getLayout()

View file

@ -14,11 +14,11 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
@ -34,9 +34,9 @@ public class InstancingDrawManager {
}
@SuppressWarnings("unchecked")
public <P extends InstancePart> Instancer<P> getInstancer(StructType<P> type, Model model, RenderStage stage) {
InstancerKey<P> key = new InstancerKey<>(type, model, stage);
GPUInstancer<P> instancer = (GPUInstancer<P>) instancers.get(key);
public <I extends Instance> Instancer<I> getInstancer(InstanceType<I> type, Model model, RenderStage stage) {
InstancerKey<I> key = new InstancerKey<>(type, model, stage);
GPUInstancer<I> instancer = (GPUInstancer<I>) instancers.get(key);
if (instancer == null) {
instancer = new GPUInstancer<>(type);
instancers.put(key, instancer);

View file

@ -8,10 +8,10 @@ import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.Pipelines;
import com.jozufozu.flywheel.backend.compile.FlwCompiler;
@ -41,7 +41,7 @@ public class InstancingEngine implements Engine {
}
@Override
public <P extends InstancePart> Instancer<P> instancer(StructType<P> type, Model model, RenderStage stage) {
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, RenderStage stage) {
return drawManager.getInstancer(type, model, stage);
}
@ -104,9 +104,9 @@ public class InstancingEngine implements Engine {
private void setup(ShaderState desc) {
var material = desc.material();
var vertexType = desc.vertexType();
var structType = desc.instanceType();
var instanceType = desc.instanceType();
var program = FlwCompiler.INSTANCE.getPipelineProgram(vertexType, structType, context, Pipelines.INSTANCED_ARRAYS);
var program = FlwCompiler.INSTANCE.getPipelineProgram(vertexType, instanceType, context, Pipelines.INSTANCED_ARRAYS);
UniformBuffer.syncAndBind(program);
var uniformLocation = program.getUniformLocation("_flw_materialID_instancing");

View file

@ -1,8 +1,8 @@
package com.jozufozu.flywheel.backend.engine.instancing;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
public record ShaderState(Material material, VertexType vertexType, StructType<?> instanceType) {
public record ShaderState(Material material, VertexType vertexType, InstanceType<?> instanceType) {
}

View file

@ -2,13 +2,13 @@ package com.jozufozu.flywheel.extension;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.instance.controller.BlockEntityInstancingController;
import com.jozufozu.flywheel.api.visualization.BlockEntityVisualizer;
import net.minecraft.world.level.block.entity.BlockEntity;
public interface BlockEntityTypeExtension<T extends BlockEntity> {
@Nullable
BlockEntityInstancingController<? super T> flywheel$getInstancingController();
BlockEntityVisualizer<? super T> flywheel$getVisualizer();
void flywheel$setInstancingController(@Nullable BlockEntityInstancingController<? super T> instancingController);
void flywheel$setVisualizer(@Nullable BlockEntityVisualizer<? super T> visualizer);
}

View file

@ -2,13 +2,13 @@ package com.jozufozu.flywheel.extension;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.instance.controller.EntityInstancingController;
import com.jozufozu.flywheel.api.visualization.EntityVisualizer;
import net.minecraft.world.entity.Entity;
public interface EntityTypeExtension<T extends Entity> {
@Nullable
EntityInstancingController<? super T> flywheel$getInstancingController();
EntityVisualizer<? super T> flywheel$getVisualizer();
void flywheel$setInstancingController(@Nullable EntityInstancingController<? super T> instancingController);
void flywheel$setVisualizer(@Nullable EntityVisualizer<? super T> visualizer);
}

View file

@ -3,7 +3,6 @@ package com.jozufozu.flywheel.gl.buffer;
import com.mojang.blaze3d.vertex.VertexFormat;
public class ElementBuffer {
protected final int elementCount;
protected final VertexFormat.IndexType eboIndexType;
public final int glBuffer;

View file

@ -112,5 +112,4 @@ public class ErrorReporter {
LOGGER.error(builder.toString());
}
}

View file

@ -1,6 +1,6 @@
package com.jozufozu.flywheel.handler;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.impl.visualization.VisualizedRenderDispatcher;
import com.jozufozu.flywheel.util.FlwUtil;
import net.minecraft.world.level.Level;
@ -14,8 +14,8 @@ public class EntityWorldHandler {
return;
}
if (FlwUtil.canUseInstancing(level)) {
InstancedRenderDispatcher.getEntities(level)
if (FlwUtil.canUseVisualization(level)) {
VisualizedRenderDispatcher.getEntities(level)
.queueAdd(event.getEntity());
}
}
@ -26,8 +26,8 @@ public class EntityWorldHandler {
return;
}
if (FlwUtil.canUseInstancing(level)) {
InstancedRenderDispatcher.getEntities(level)
if (FlwUtil.canUseVisualization(level)) {
VisualizedRenderDispatcher.getEntities(level)
.remove(event.getEntity());
}
}

View file

@ -3,7 +3,7 @@ package com.jozufozu.flywheel.handler;
import java.util.ArrayList;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.impl.visualization.VisualizedRenderDispatcher;
import com.jozufozu.flywheel.lib.light.LightUpdater;
import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker;
import com.jozufozu.flywheel.util.FlwUtil;
@ -22,7 +22,7 @@ public class ForgeEvents {
debug.add("");
debug.add("Flywheel: " + Flywheel.getVersion());
InstancedRenderDispatcher.addDebugInfo(debug);
VisualizedRenderDispatcher.addDebugInfo(debug);
debug.add("Memory Usage: CPU: " + StringUtil.formatBytes(FlwMemoryTracker.getCPUMemory()) + ", GPU: " + StringUtil.formatBytes(FlwMemoryTracker.getGPUMemory()));
}

View file

@ -8,7 +8,7 @@ import com.jozufozu.flywheel.api.backend.Backend;
import com.jozufozu.flywheel.api.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.backend.Backends;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.impl.visualization.VisualizedRenderDispatcher;
import com.jozufozu.flywheel.lib.backend.SimpleBackend;
import com.mojang.logging.LogUtils;
@ -63,7 +63,7 @@ public final class BackendManagerImpl {
backend = chooseBackend();
if (level != null) {
InstancedRenderDispatcher.resetInstanceWorld(level);
VisualizedRenderDispatcher.resetVisualWorld(level);
}
}

View file

@ -1,38 +0,0 @@
package com.jozufozu.flywheel.impl;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.instance.controller.BlockEntityInstancingController;
import com.jozufozu.flywheel.api.instance.controller.EntityInstancingController;
import com.jozufozu.flywheel.extension.BlockEntityTypeExtension;
import com.jozufozu.flywheel.extension.EntityTypeExtension;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
//TODO: Add freezing
@SuppressWarnings("unchecked")
public final class InstancingControllerRegistryImpl {
@Nullable
public static <T extends BlockEntity> BlockEntityInstancingController<? super T> getController(BlockEntityType<T> type) {
return ((BlockEntityTypeExtension<T>) type).flywheel$getInstancingController();
}
@Nullable
public static <T extends Entity> EntityInstancingController<? super T> getController(EntityType<T> type) {
return ((EntityTypeExtension<T>) type).flywheel$getInstancingController();
}
public static <T extends BlockEntity> void setController(BlockEntityType<T> type, BlockEntityInstancingController<? super T> instancingController) {
((BlockEntityTypeExtension<T>) type).flywheel$setInstancingController(instancingController);
}
public static <T extends Entity> void setController(EntityType<T> type, EntityInstancingController<? super T> instancingController) {
((EntityTypeExtension<T>) type).flywheel$setInstancingController(instancingController);
}
private InstancingControllerRegistryImpl() {
}
}

View file

@ -0,0 +1,38 @@
package com.jozufozu.flywheel.impl;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.visualization.BlockEntityVisualizer;
import com.jozufozu.flywheel.api.visualization.EntityVisualizer;
import com.jozufozu.flywheel.extension.BlockEntityTypeExtension;
import com.jozufozu.flywheel.extension.EntityTypeExtension;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
//TODO: Add freezing
@SuppressWarnings("unchecked")
public final class VisualizerRegistryImpl {
@Nullable
public static <T extends BlockEntity> BlockEntityVisualizer<? super T> getVisualizer(BlockEntityType<T> type) {
return ((BlockEntityTypeExtension<T>) type).flywheel$getVisualizer();
}
@Nullable
public static <T extends Entity> EntityVisualizer<? super T> getVisualizer(EntityType<T> type) {
return ((EntityTypeExtension<T>) type).flywheel$getVisualizer();
}
public static <T extends BlockEntity> void setVisualizer(BlockEntityType<T> type, BlockEntityVisualizer<? super T> visualizer) {
((BlockEntityTypeExtension<T>) type).flywheel$setVisualizer(visualizer);
}
public static <T extends Entity> void setVisualizer(EntityType<T> type, EntityVisualizer<? super T> visualizer) {
((EntityTypeExtension<T>) type).flywheel$setVisualizer(visualizer);
}
private VisualizerRegistryImpl() {
}
}

View file

@ -1,77 +0,0 @@
package com.jozufozu.flywheel.impl.instancing;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.instance.controller.BlockEntityInstancingController;
import com.jozufozu.flywheel.api.instance.controller.EntityInstancingController;
import com.jozufozu.flywheel.api.instance.controller.InstancingControllerRegistry;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
public final class InstancingControllerHelper {
@SuppressWarnings("unchecked")
@Nullable
public static <T extends BlockEntity> BlockEntityInstancingController<? super T> getController(T blockEntity) {
return InstancingControllerRegistry.getController((BlockEntityType<? super T>) blockEntity.getType());
}
@SuppressWarnings("unchecked")
@Nullable
public static <T extends Entity> EntityInstancingController<? super T> getController(T entity) {
return InstancingControllerRegistry.getController((EntityType<? super T>) entity.getType());
}
/**
* Checks if the given block entity can be instanced.
* @param type The block entity to check.
* @param <T> The block entity.
* @return {@code true} if the block entity can be instanced.
*/
public static <T extends BlockEntity> boolean canInstance(T blockEntity) {
return getController(blockEntity) != null;
}
/**
* Checks if the given entity can be instanced.
* @param type The entity to check.
* @param <T> The entity.
* @return {@code true} if the entity can be instanced.
*/
public static <T extends Entity> boolean canInstance(T entity) {
return getController(entity) != null;
}
/**
* Checks if the given block entity is instanced and should not be rendered normally.
* @param blockEntity The block entity to check.
* @param <T> The type of the block entity.
* @return {@code true} if the block entity is instanced and should not be rendered normally.
*/
public static <T extends BlockEntity> boolean shouldSkipRender(T blockEntity) {
BlockEntityInstancingController<? super T> controller = getController(blockEntity);
if (controller == null) {
return false;
}
return controller.shouldSkipRender(blockEntity);
}
/**
* Checks if the given entity is instanced and should not be rendered normally.
* @param entity The entity to check.
* @param <T> The type of the entity.
* @return {@code true} if the entity is instanced and should not be rendered normally.
*/
public static <T extends Entity> boolean shouldSkipRender(T entity) {
EntityInstancingController<? super T> controller = getController(entity);
if (controller == null) {
return false;
}
return controller.shouldSkipRender(entity);
}
private InstancingControllerHelper() {
}
}

View file

@ -1,39 +0,0 @@
package com.jozufozu.flywheel.impl.instancing.manager;
import java.util.Collection;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.api.instance.effect.Effect;
import com.jozufozu.flywheel.impl.instancing.storage.One2ManyStorage;
import com.jozufozu.flywheel.impl.instancing.storage.Storage;
public class EffectInstanceManager extends InstanceManager<Effect> {
private final EffectStorage storage;
public EffectInstanceManager(Engine engine) {
storage = new EffectStorage(engine);
}
@Override
protected Storage<Effect> getStorage() {
return storage;
}
private static class EffectStorage extends One2ManyStorage<Effect> {
public EffectStorage(Engine engine) {
super(engine);
}
@Override
protected Collection<? extends Instance> createRaw(Effect obj) {
return obj.createInstances(new InstanceContext(engine, engine.renderOrigin()));
}
@Override
public boolean willAccept(Effect obj) {
return true;
}
}
}

View file

@ -1,59 +0,0 @@
package com.jozufozu.flywheel.impl.instancing.manager;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.impl.instancing.InstancingControllerHelper;
import com.jozufozu.flywheel.impl.instancing.storage.One2OneStorage;
import com.jozufozu.flywheel.impl.instancing.storage.Storage;
import com.jozufozu.flywheel.util.FlwUtil;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
public class EntityInstanceManager extends InstanceManager<Entity> {
private final EntityStorage storage;
public EntityInstanceManager(Engine engine) {
storage = new EntityStorage(engine);
}
@Override
protected Storage<Entity> getStorage() {
return storage;
}
private static class EntityStorage extends One2OneStorage<Entity> {
public EntityStorage(Engine engine) {
super(engine);
}
@Override
@Nullable
protected Instance createRaw(Entity obj) {
var controller = InstancingControllerHelper.getController(obj);
if (controller == null) {
return null;
}
return controller.createInstance(new InstanceContext(engine, engine.renderOrigin()), obj);
}
@Override
public boolean willAccept(Entity entity) {
if (!entity.isAlive()) {
return false;
}
if (!InstancingControllerHelper.canInstance(entity)) {
return false;
}
Level level = entity.level;
return FlwUtil.isFlywheelLevel(level);
}
}
}

View file

@ -1,173 +0,0 @@
package com.jozufozu.flywheel.impl.instancing.manager;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.impl.instancing.ratelimit.BandedPrimeLimiter;
import com.jozufozu.flywheel.impl.instancing.ratelimit.DistanceUpdateLimiter;
import com.jozufozu.flywheel.impl.instancing.ratelimit.NonLimiter;
import com.jozufozu.flywheel.impl.instancing.storage.Storage;
import com.jozufozu.flywheel.impl.instancing.storage.Transaction;
public abstract class InstanceManager<T> {
private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>();
protected DistanceUpdateLimiter tickLimiter;
protected DistanceUpdateLimiter frameLimiter;
public InstanceManager() {
tickLimiter = createUpdateLimiter();
frameLimiter = createUpdateLimiter();
}
protected abstract Storage<T> getStorage();
protected DistanceUpdateLimiter createUpdateLimiter() {
if (FlwConfig.get().limitUpdates()) {
return new BandedPrimeLimiter();
} else {
return new NonLimiter();
}
}
/**
* Get the number of game objects that are currently being instanced.
*
* @return The object count.
*/
public int getInstanceCount() {
return getStorage().getAllInstances().size();
}
public void add(T obj) {
if (!getStorage().willAccept(obj)) {
return;
}
getStorage().add(obj);
}
public void queueAdd(T obj) {
if (!getStorage().willAccept(obj)) {
return;
}
queue.add(Transaction.add(obj));
}
public void remove(T obj) {
getStorage().remove(obj);
}
public void queueRemove(T obj) {
queue.add(Transaction.remove(obj));
}
/**
* Update the instance associated with an object.
*
* <p>
* By default this is the only hook an {@link Instance} has to change its internal state. This is the lowest frequency
* update hook {@link Instance} gets. For more frequent updates, see {@link TickableInstance} and
* {@link DynamicInstance}.
* </p>
*
* @param obj the object to update.
*/
public void update(T obj) {
if (!getStorage().willAccept(obj)) {
return;
}
getStorage().update(obj);
}
public void queueUpdate(T obj) {
if (!getStorage().willAccept(obj)) {
return;
}
queue.add(Transaction.update(obj));
}
public void recreateAll() {
getStorage().recreateAll();
}
public void invalidate() {
getStorage().invalidate();
}
protected void processQueue() {
var storage = getStorage();
Transaction<T> transaction;
while ((transaction = queue.poll()) != null) {
transaction.apply(storage);
}
}
/**
* Ticks the InstanceManager.
*
* <p>
* {@link TickableInstance}s get ticked.
* <br>
* Queued updates are processed.
* </p>
*/
public void tick(TaskExecutor executor, double cameraX, double cameraY, double cameraZ) {
tickLimiter.tick();
processQueue();
var instances = getStorage().getTickableInstances();
distributeWork(executor, instances, instance -> tickInstance(instance, cameraX, cameraY, cameraZ));
}
protected void tickInstance(TickableInstance instance, double cameraX, double cameraY, double cameraZ) {
if (!instance.decreaseTickRateWithDistance() || tickLimiter.shouldUpdate(instance.distanceSquared(cameraX, cameraY, cameraZ))) {
instance.tick();
}
}
public void beginFrame(TaskExecutor executor, double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
frameLimiter.tick();
processQueue();
var instances = getStorage().getDynamicInstances();
distributeWork(executor, instances, instance -> updateInstance(instance, cameraX, cameraY, cameraZ, frustum));
}
protected void updateInstance(DynamicInstance instance, double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
if (!instance.decreaseFramerateWithDistance() || frameLimiter.shouldUpdate(instance.distanceSquared(cameraX, cameraY, cameraZ))) {
if (instance.isVisible(frustum)) {
instance.beginFrame();
}
}
}
private static <I> void distributeWork(TaskExecutor executor, List<I> instances, Consumer<I> action) {
final int size = instances.size();
final int threadCount = executor.getThreadCount();
if (threadCount == 1) {
executor.execute(() -> instances.forEach(action));
} else {
final int stride = Math.max(size / (threadCount * 2), 1);
for (int start = 0; start < size; start += stride) {
int end = Math.min(start + stride, size);
var sub = instances.subList(start, end);
executor.execute(() -> sub.forEach(action));
}
}
}
}

View file

@ -1,43 +0,0 @@
package com.jozufozu.flywheel.impl.instancing.storage;
import java.util.ArrayList;
import java.util.List;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
public abstract class AbstractStorage<T> implements Storage<T> {
protected final Engine engine;
protected final List<TickableInstance> tickableInstances = new ArrayList<>();
protected final List<DynamicInstance> dynamicInstances = new ArrayList<>();
protected AbstractStorage(Engine engine) {
this.engine = engine;
}
@Override
public List<TickableInstance> getTickableInstances() {
return tickableInstances;
}
@Override
public List<DynamicInstance> getDynamicInstances() {
return dynamicInstances;
}
protected void setup(Instance instance) {
instance.init();
if (instance instanceof TickableInstance tickable) {
tickableInstances.add(tickable);
tickable.tick();
}
if (instance instanceof DynamicInstance dynamic) {
dynamicInstances.add(dynamic);
dynamic.beginFrame();
}
}
}

View file

@ -1,7 +0,0 @@
package com.jozufozu.flywheel.impl.instancing.storage;
public enum Action {
ADD,
REMOVE,
UPDATE,
}

View file

@ -1,86 +0,0 @@
package com.jozufozu.flywheel.impl.instancing.storage;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.Instance;
public abstract class One2ManyStorage<T> extends AbstractStorage<T> {
private final Multimap<T, Instance> allInstances = HashMultimap.create();
public One2ManyStorage(Engine engine) {
super(engine);
}
@Override
public Collection<Instance> getAllInstances() {
return allInstances.values();
}
@Override
public void add(T obj) {
Collection<Instance> instances = allInstances.get(obj);
if (instances.isEmpty()) {
create(obj);
}
}
@Override
public void remove(T obj) {
Collection<Instance> instances = allInstances.removeAll(obj);
if (instances.isEmpty()) {
return;
}
tickableInstances.removeAll(instances);
dynamicInstances.removeAll(instances);
instances.forEach(Instance::delete);
}
@Override
public void update(T obj) {
Collection<Instance> instances = allInstances.get(obj);
if (instances.isEmpty()) {
return;
}
// TODO: shouldReset cannot be checked here because all instances are created at once
instances.forEach(Instance::update);
}
@Override
public void recreateAll() {
tickableInstances.clear();
dynamicInstances.clear();
allInstances.values().forEach(Instance::delete);
List<T> objects = List.copyOf(allInstances.keySet());
allInstances.clear();
objects.forEach(this::create);
}
@Override
public void invalidate() {
tickableInstances.clear();
dynamicInstances.clear();
allInstances.values().forEach(Instance::delete);
allInstances.clear();
}
private void create(T obj) {
Collection<? extends Instance> instances = createRaw(obj);
if (!instances.isEmpty()) {
instances.forEach(this::setup);
allInstances.putAll(obj, instances);
}
}
protected abstract Collection<? extends Instance> createRaw(T obj);
}

View file

@ -1,101 +0,0 @@
package com.jozufozu.flywheel.impl.instancing.storage;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.Instance;
public abstract class One2OneStorage<T> extends AbstractStorage<T> {
private final Map<T, Instance> instances = new HashMap<>();
public One2OneStorage(Engine engine) {
super(engine);
}
@Override
public Collection<Instance> getAllInstances() {
return instances.values();
}
@Override
public void add(T obj) {
Instance instance = instances.get(obj);
if (instance == null) {
create(obj);
}
}
@Override
public void remove(T obj) {
Instance instance = instances.remove(obj);
if (instance == null) {
return;
}
tickableInstances.remove(instance);
dynamicInstances.remove(instance);
instance.delete();
}
@Override
public void update(T obj) {
Instance instance = instances.get(obj);
if (instance == null) {
return;
}
// resetting instances is by default used to handle block state changes.
if (instance.shouldReset()) {
// delete and re-create the instance.
// resetting an instance supersedes updating it.
remove(obj);
create(obj);
} else {
instance.update();
}
}
@Override
public void recreateAll() {
tickableInstances.clear();
dynamicInstances.clear();
instances.replaceAll((obj, instance) -> {
instance.delete();
Instance out = createRaw(obj);
if (out != null) {
setup(out);
}
return out;
});
}
@Override
public void invalidate() {
tickableInstances.clear();
dynamicInstances.clear();
instances.values().forEach(Instance::delete);
instances.clear();
}
private void create(T obj) {
Instance instance = createRaw(obj);
if (instance != null) {
setup(instance);
instances.put(obj, instance);
}
}
@Nullable
protected abstract Instance createRaw(T obj);
}

View file

@ -1,33 +0,0 @@
package com.jozufozu.flywheel.impl.instancing.storage;
import java.util.Collection;
import java.util.List;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
public interface Storage<T> {
Collection<Instance> getAllInstances();
List<TickableInstance> getTickableInstances();
List<DynamicInstance> getDynamicInstances();
/**
* Is the given object currently capable of being added?
*
* @return true if the object is currently capable of being instanced.
*/
boolean willAccept(T obj);
void add(T obj);
void remove(T obj);
void update(T obj);
void recreateAll();
void invalidate();
}

View file

@ -3,8 +3,8 @@ package com.jozufozu.flywheel.impl.vertex;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.vertex.ReusableVertexList;
import com.jozufozu.flywheel.lib.format.AbstractVertexList;
import com.jozufozu.flywheel.lib.math.RenderMath;
import com.jozufozu.flywheel.lib.vertex.AbstractVertexList;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.renderer.LightTexture;

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.impl.instancing;
package com.jozufozu.flywheel.impl.visualization;
import java.util.List;
@ -8,17 +8,17 @@ import com.jozufozu.flywheel.api.backend.BackendManager;
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.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.api.instance.effect.Effect;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.Effect;
import com.jozufozu.flywheel.api.visual.TickableVisual;
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.instancing.manager.BlockEntityInstanceManager;
import com.jozufozu.flywheel.impl.instancing.manager.EffectInstanceManager;
import com.jozufozu.flywheel.impl.instancing.manager.EntityInstanceManager;
import com.jozufozu.flywheel.impl.instancing.manager.InstanceManager;
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 net.minecraft.world.entity.Entity;
import net.minecraft.world.level.LevelAccessor;
@ -28,43 +28,43 @@ import net.minecraft.world.level.block.entity.BlockEntity;
* A manager class for a single world where instancing is supported.
*/
// AutoCloseable is implemented to prevent leaking this object from WorldAttached
public class InstanceWorld implements AutoCloseable {
public class VisualWorld implements AutoCloseable {
private final Engine engine;
private final ParallelTaskExecutor taskExecutor;
private final InstanceManager<BlockEntity> blockEntities;
private final InstanceManager<Entity> entities;
private final InstanceManager<Effect> effects;
private final VisualManager<BlockEntity> blockEntities;
private final VisualManager<Entity> entities;
private final VisualManager<Effect> effects;
public InstanceWorld(LevelAccessor level) {
public VisualWorld(LevelAccessor level) {
engine = BackendManager.getBackend().createEngine(level);
taskExecutor = FlwTaskExecutor.get();
blockEntities = new BlockEntityInstanceManager(engine);
entities = new EntityInstanceManager(engine);
effects = new EffectInstanceManager(engine);
blockEntities = new BlockEntityVisualManager(engine);
entities = new EntityVisualManager(engine);
effects = new EffectVisualManager(engine);
}
public Engine getEngine() {
return engine;
}
public InstanceManager<BlockEntity> getBlockEntities() {
public VisualManager<BlockEntity> getBlockEntities() {
return blockEntities;
}
public InstanceManager<Entity> getEntities() {
public VisualManager<Entity> getEntities() {
return entities;
}
public InstanceManager<Effect> getEffects() {
public VisualManager<Effect> getEffects() {
return effects;
}
/**
* Tick the instances after the game has ticked:
* Tick the visuals after the game has ticked:
* <p>
* Call {@link TickableInstance#tick()} on all instances in this world.
* Call {@link TickableVisual#tick()} on all visuals in this world.
* </p>
*/
public void tick(double cameraX, double cameraY, double cameraZ) {
@ -78,7 +78,7 @@ public class InstanceWorld implements AutoCloseable {
* <p>
* Check and update the render origin.
* <br>
* Call {@link DynamicInstance#beginFrame()} on all instances in this world.
* Call {@link DynamicVisual#beginFrame()} on all visuals in this world.
* </p>
*/
public void beginFrame(RenderContext context) {
@ -109,7 +109,7 @@ public class InstanceWorld implements AutoCloseable {
}
/**
* Draw all instances for the given stage.
* Draw all visuals for the given stage.
*/
public void renderStage(RenderContext context, RenderStage stage) {
taskExecutor.syncPoint();
@ -117,15 +117,15 @@ public class InstanceWorld implements AutoCloseable {
}
public void addDebugInfo(List<String> info) {
info.add("B: " + blockEntities.getInstanceCount()
+ ", E: " + entities.getInstanceCount()
+ ", F: " + effects.getInstanceCount());
info.add("B: " + blockEntities.getVisualCount()
+ ", E: " + entities.getVisualCount()
+ ", F: " + effects.getVisualCount());
info.add("Update limiting: " + FlwCommands.boolToText(FlwConfig.get().limitUpdates()).getString());
engine.addDebugInfo(info);
}
/**
* Free all acquired resources and invalidate this instance world.
* Free all acquired resources and invalidate this visual world.
*/
public void delete() {
taskExecutor.discardAndAwait();

View file

@ -0,0 +1,77 @@
package com.jozufozu.flywheel.impl.visualization;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.visualization.BlockEntityVisualizer;
import com.jozufozu.flywheel.api.visualization.EntityVisualizer;
import com.jozufozu.flywheel.api.visualization.VisualizerRegistry;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
public final class VisualizationHelper {
@SuppressWarnings("unchecked")
@Nullable
public static <T extends BlockEntity> BlockEntityVisualizer<? super T> getVisualizer(T blockEntity) {
return VisualizerRegistry.getVisualizer((BlockEntityType<? super T>) blockEntity.getType());
}
@SuppressWarnings("unchecked")
@Nullable
public static <T extends Entity> EntityVisualizer<? super T> getVisualizer(T entity) {
return VisualizerRegistry.getVisualizer((EntityType<? super T>) entity.getType());
}
/**
* Checks if the given block entity can be visualized.
* @param type The block entity to check.
* @param <T> The block entity.
* @return {@code true} if the block entity can be visualized.
*/
public static <T extends BlockEntity> boolean canVisualize(T blockEntity) {
return getVisualizer(blockEntity) != null;
}
/**
* Checks if the given entity can be visualized.
* @param type The entity to check.
* @param <T> The entity.
* @return {@code true} if the entity can be visualized.
*/
public static <T extends Entity> boolean canVisualize(T entity) {
return getVisualizer(entity) != null;
}
/**
* Checks if the given block entity is visualized and should not be rendered normally.
* @param blockEntity The block entity to check.
* @param <T> The type of the block entity.
* @return {@code true} if the block entity is visualized and should not be rendered normally.
*/
public static <T extends BlockEntity> boolean shouldSkipRender(T blockEntity) {
BlockEntityVisualizer<? super T> visualizer = getVisualizer(blockEntity);
if (visualizer == null) {
return false;
}
return visualizer.shouldSkipRender(blockEntity);
}
/**
* Checks if the given entity is visualized and should not be rendered normally.
* @param entity The entity to check.
* @param <T> The type of the entity.
* @return {@code true} if the entity is visualized and should not be rendered normally.
*/
public static <T extends Entity> boolean shouldSkipRender(T entity) {
EntityVisualizer<? super T> visualizer = getVisualizer(entity);
if (visualizer == null) {
return false;
}
return visualizer.shouldSkipRender(entity);
}
private VisualizationHelper() {
}
}

View file

@ -1,13 +1,13 @@
package com.jozufozu.flywheel.impl.instancing;
package com.jozufozu.flywheel.impl.visualization;
import java.util.List;
import com.jozufozu.flywheel.api.event.BeginFrameEvent;
import com.jozufozu.flywheel.api.event.RenderStageEvent;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.effect.Effect;
import com.jozufozu.flywheel.api.visual.Effect;
import com.jozufozu.flywheel.api.visual.Visual;
import com.jozufozu.flywheel.extension.ClientLevelExtension;
import com.jozufozu.flywheel.impl.instancing.manager.InstanceManager;
import com.jozufozu.flywheel.impl.visualization.manager.VisualManager;
import com.jozufozu.flywheel.lib.util.AnimationTickHolder;
import com.jozufozu.flywheel.util.FlwUtil;
import com.jozufozu.flywheel.util.WorldAttached;
@ -21,81 +21,81 @@ import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.event.TickEvent;
public class InstancedRenderDispatcher {
private static final WorldAttached<InstanceWorld> INSTANCE_WORLDS = new WorldAttached<>(InstanceWorld::new);
public class VisualizedRenderDispatcher {
private static final WorldAttached<VisualWorld> VISUAL_WORLDS = new WorldAttached<>(VisualWorld::new);
/**
* Call this when you want to run {@link Instance#update()}.
* @param blockEntity The block entity whose instance you want to update.
* Call this when you want to run {@link Visual#update()}.
* @param blockEntity The block entity whose visual you want to update.
*/
public static void queueUpdate(BlockEntity blockEntity) {
if (!(blockEntity.getLevel() instanceof ClientLevel level)) {
return;
}
if (!FlwUtil.canUseInstancing(level)) {
if (!FlwUtil.canUseVisualization(level)) {
return;
}
INSTANCE_WORLDS.get(level)
VISUAL_WORLDS.get(level)
.getBlockEntities()
.queueUpdate(blockEntity);
}
/**
* Call this when you want to run {@link Instance#update()}.
* @param entity The entity whose instance you want to update.
* Call this when you want to run {@link Visual#update()}.
* @param entity The entity whose visual you want to update.
*/
public static void queueUpdate(Entity entity) {
Level level = entity.level;
if (!FlwUtil.canUseInstancing(level)) {
if (!FlwUtil.canUseVisualization(level)) {
return;
}
INSTANCE_WORLDS.get(level)
VISUAL_WORLDS.get(level)
.getEntities()
.queueUpdate(entity);
}
/**
* Call this when you want to run {@link Instance#update()}.
* @param effect The effect whose instance you want to update.
* Call this when you want to run {@link Visual#update()}.
* @param effect The effect whose visual you want to update.
*/
public static void queueUpdate(LevelAccessor level, Effect effect) {
if (!FlwUtil.canUseInstancing(level)) {
if (!FlwUtil.canUseVisualization(level)) {
return;
}
INSTANCE_WORLDS.get(level)
VISUAL_WORLDS.get(level)
.getEffects()
.queueUpdate(effect);
}
/**
* Get or create the {@link InstanceWorld} for the given world.
* Get or create the {@link VisualWorld} for the given world.
* @throws IllegalStateException if the backend is off
*/
private static InstanceWorld getInstanceWorld(LevelAccessor level) {
if (!FlwUtil.canUseInstancing(level)) {
throw new IllegalStateException("Cannot retrieve instance world when backend is off!");
private static VisualWorld getVisualWorld(LevelAccessor level) {
if (!FlwUtil.canUseVisualization(level)) {
throw new IllegalStateException("Cannot retrieve visual world when backend is off!");
}
return INSTANCE_WORLDS.get(level);
return VISUAL_WORLDS.get(level);
}
public static InstanceManager<BlockEntity> getBlockEntities(LevelAccessor level) {
return getInstanceWorld(level).getBlockEntities();
public static VisualManager<BlockEntity> getBlockEntities(LevelAccessor level) {
return getVisualWorld(level).getBlockEntities();
}
public static InstanceManager<Entity> getEntities(LevelAccessor level) {
return getInstanceWorld(level).getEntities();
public static VisualManager<Entity> getEntities(LevelAccessor level) {
return getVisualWorld(level).getEntities();
}
public static InstanceManager<Effect> getEffects(LevelAccessor level) {
return getInstanceWorld(level).getEffects();
public static VisualManager<Effect> getEffects(LevelAccessor level) {
return getVisualWorld(level).getEffects();
}
public static Vec3i getRenderOrigin(LevelAccessor level) {
return getInstanceWorld(level).getEngine().renderOrigin();
return getVisualWorld(level).getEngine().renderOrigin();
}
public static void tick(TickEvent.ClientTickEvent event) {
@ -116,7 +116,7 @@ public class InstancedRenderDispatcher {
}
Level level = cameraEntity.level;
if (!FlwUtil.canUseInstancing(level)) {
if (!FlwUtil.canUseVisualization(level)) {
return;
}
@ -124,7 +124,7 @@ public class InstancedRenderDispatcher {
double cameraY = cameraEntity.getEyeY();
double cameraZ = cameraEntity.getZ();
INSTANCE_WORLDS.get(level).tick(cameraX, cameraY, cameraZ);
VISUAL_WORLDS.get(level).tick(cameraX, cameraY, cameraZ);
}
public static void onBeginFrame(BeginFrameEvent event) {
@ -133,30 +133,30 @@ public class InstancedRenderDispatcher {
}
ClientLevel level = event.getContext().level();
if (!FlwUtil.canUseInstancing(level)) {
if (!FlwUtil.canUseVisualization(level)) {
return;
}
INSTANCE_WORLDS.get(level).beginFrame(event.getContext());
VISUAL_WORLDS.get(level).beginFrame(event.getContext());
}
public static void onRenderStage(RenderStageEvent event) {
ClientLevel level = event.getContext().level();
if (!FlwUtil.canUseInstancing(level)) {
if (!FlwUtil.canUseVisualization(level)) {
return;
}
INSTANCE_WORLDS.get(level).renderStage(event.getContext(), event.getStage());
VISUAL_WORLDS.get(level).renderStage(event.getContext(), event.getStage());
}
public static void resetInstanceWorld(ClientLevel level) {
INSTANCE_WORLDS.remove(level, InstanceWorld::delete);
public static void resetVisualWorld(ClientLevel level) {
VISUAL_WORLDS.remove(level, VisualWorld::delete);
if (!FlwUtil.canUseInstancing(level)) {
if (!FlwUtil.canUseVisualization(level)) {
return;
}
InstanceWorld world = INSTANCE_WORLDS.get(level);
VisualWorld world = VISUAL_WORLDS.get(level);
// Block entities are loaded while chunks are baked.
// Entities are loaded with the level, so when chunks are reloaded they need to be re-added.
ClientLevelExtension.getAllLoadedEntities(level)
@ -165,8 +165,8 @@ public class InstancedRenderDispatcher {
public static void addDebugInfo(List<String> info) {
ClientLevel level = Minecraft.getInstance().level;
if (FlwUtil.canUseInstancing(level)) {
INSTANCE_WORLDS.get(level).addDebugInfo(info);
if (FlwUtil.canUseVisualization(level)) {
VISUAL_WORLDS.get(level).addDebugInfo(info);
} else {
info.add("Disabled");
}

View file

@ -1,16 +1,16 @@
package com.jozufozu.flywheel.impl.instancing.manager;
package com.jozufozu.flywheel.impl.visualization.manager;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.BlockEntityInstance;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.impl.instancing.InstancingControllerHelper;
import com.jozufozu.flywheel.impl.instancing.storage.One2OneStorage;
import com.jozufozu.flywheel.impl.instancing.storage.Storage;
import com.jozufozu.flywheel.api.visual.BlockEntityVisual;
import com.jozufozu.flywheel.api.visual.Visual;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.impl.visualization.VisualizationHelper;
import com.jozufozu.flywheel.impl.visualization.storage.One2OneStorage;
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
import com.jozufozu.flywheel.util.FlwUtil;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
@ -20,10 +20,10 @@ import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
public class BlockEntityVisualManager extends VisualManager<BlockEntity> {
private final BlockEntityStorage storage;
public BlockEntityInstanceManager(Engine engine) {
public BlockEntityVisualManager(Engine engine) {
storage = new BlockEntityStorage(engine);
}
@ -32,15 +32,15 @@ public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
return storage;
}
public void getCrumblingInstances(long pos, List<BlockEntityInstance<?>> data) {
BlockEntityInstance<?> instance = storage.posLookup.get(pos);
if (instance != null) {
data.add(instance);
public void getCrumblingVisuals(long pos, List<BlockEntityVisual<?>> visuals) {
BlockEntityVisual<?> visual = storage.posLookup.get(pos);
if (visual != null) {
visuals.add(visual);
}
}
private static class BlockEntityStorage extends One2OneStorage<BlockEntity> {
private final Long2ObjectMap<BlockEntityInstance<?>> posLookup = new Long2ObjectOpenHashMap<>();
private final Long2ObjectMap<BlockEntityVisual<?>> posLookup = new Long2ObjectOpenHashMap<>();
public BlockEntityStorage(Engine engine) {
super(engine);
@ -52,7 +52,7 @@ public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
return false;
}
if (!InstancingControllerHelper.canInstance(blockEntity)) {
if (!VisualizationHelper.canVisualize(blockEntity)) {
return false;
}
@ -79,18 +79,18 @@ public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
@Override
@Nullable
protected Instance createRaw(BlockEntity obj) {
var controller = InstancingControllerHelper.getController(obj);
if (controller == null) {
protected Visual createRaw(BlockEntity obj) {
var visualizer = VisualizationHelper.getVisualizer(obj);
if (visualizer == null) {
return null;
}
var instance = controller.createInstance(new InstanceContext(engine, engine.renderOrigin()), obj);
var visual = visualizer.createVisual(new VisualizationContext(engine, engine.renderOrigin()), obj);
BlockPos blockPos = obj.getBlockPos();
posLookup.put(blockPos.asLong(), instance);
posLookup.put(blockPos.asLong(), visual);
return instance;
return visual;
}
@Override

View file

@ -0,0 +1,39 @@
package com.jozufozu.flywheel.impl.visualization.manager;
import java.util.Collection;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.visual.Effect;
import com.jozufozu.flywheel.api.visual.Visual;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.impl.visualization.storage.One2ManyStorage;
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
public class EffectVisualManager extends VisualManager<Effect> {
private final EffectStorage storage;
public EffectVisualManager(Engine engine) {
storage = new EffectStorage(engine);
}
@Override
protected Storage<Effect> getStorage() {
return storage;
}
private static class EffectStorage extends One2ManyStorage<Effect> {
public EffectStorage(Engine engine) {
super(engine);
}
@Override
protected Collection<? extends Visual> createRaw(Effect obj) {
return obj.createVisuals(new VisualizationContext(engine, engine.renderOrigin()));
}
@Override
public boolean willAccept(Effect obj) {
return true;
}
}
}

View file

@ -0,0 +1,59 @@
package com.jozufozu.flywheel.impl.visualization.manager;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.visual.Visual;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.impl.visualization.VisualizationHelper;
import com.jozufozu.flywheel.impl.visualization.storage.One2OneStorage;
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
import com.jozufozu.flywheel.util.FlwUtil;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
public class EntityVisualManager extends VisualManager<Entity> {
private final EntityStorage storage;
public EntityVisualManager(Engine engine) {
storage = new EntityStorage(engine);
}
@Override
protected Storage<Entity> getStorage() {
return storage;
}
private static class EntityStorage extends One2OneStorage<Entity> {
public EntityStorage(Engine engine) {
super(engine);
}
@Override
@Nullable
protected Visual createRaw(Entity obj) {
var visualizer = VisualizationHelper.getVisualizer(obj);
if (visualizer == null) {
return null;
}
return visualizer.createVisual(new VisualizationContext(engine, engine.renderOrigin()), obj);
}
@Override
public boolean willAccept(Entity entity) {
if (!entity.isAlive()) {
return false;
}
if (!VisualizationHelper.canVisualize(entity)) {
return false;
}
Level level = entity.level;
return FlwUtil.isFlywheelLevel(level);
}
}
}

View file

@ -0,0 +1,173 @@
package com.jozufozu.flywheel.impl.visualization.manager;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.TickableVisual;
import com.jozufozu.flywheel.api.visual.Visual;
import com.jozufozu.flywheel.config.FlwConfig;
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;
public abstract class VisualManager<T> {
private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>();
protected DistanceUpdateLimiter tickLimiter;
protected DistanceUpdateLimiter frameLimiter;
public VisualManager() {
tickLimiter = createUpdateLimiter();
frameLimiter = createUpdateLimiter();
}
protected abstract Storage<T> getStorage();
protected DistanceUpdateLimiter createUpdateLimiter() {
if (FlwConfig.get().limitUpdates()) {
return new BandedPrimeLimiter();
} else {
return new NonLimiter();
}
}
/**
* Get the number of game objects that are currently being visualized.
*
* @return The object count.
*/
public int getVisualCount() {
return getStorage().getAllVisuals().size();
}
public void add(T obj) {
if (!getStorage().willAccept(obj)) {
return;
}
getStorage().add(obj);
}
public void queueAdd(T obj) {
if (!getStorage().willAccept(obj)) {
return;
}
queue.add(Transaction.add(obj));
}
public void remove(T obj) {
getStorage().remove(obj);
}
public void queueRemove(T obj) {
queue.add(Transaction.remove(obj));
}
/**
* Update the visual associated with an object.
*
* <p>
* By default this is the only hook a {@link Visual} has to change its internal state. This is the lowest frequency
* update hook {@link Visual} gets. For more frequent updates, see {@link TickableVisual} and
* {@link DynamicVisual}.
* </p>
*
* @param obj the object whose visual will be updated.
*/
public void update(T obj) {
if (!getStorage().willAccept(obj)) {
return;
}
getStorage().update(obj);
}
public void queueUpdate(T obj) {
if (!getStorage().willAccept(obj)) {
return;
}
queue.add(Transaction.update(obj));
}
public void recreateAll() {
getStorage().recreateAll();
}
public void invalidate() {
getStorage().invalidate();
}
protected void processQueue() {
var storage = getStorage();
Transaction<T> transaction;
while ((transaction = queue.poll()) != null) {
transaction.apply(storage);
}
}
/**
* Ticks the VisualManager.
*
* <p>
* {@link TickableVisual}s get ticked.
* <br>
* Queued updates are processed.
* </p>
*/
public void tick(TaskExecutor executor, double cameraX, double cameraY, double cameraZ) {
tickLimiter.tick();
processQueue();
var visuals = getStorage().getTickableVisuals();
distributeWork(executor, visuals, visual -> tickVisual(visual, cameraX, cameraY, cameraZ));
}
protected void tickVisual(TickableVisual visual, double cameraX, double cameraY, double cameraZ) {
if (!visual.decreaseTickRateWithDistance() || tickLimiter.shouldUpdate(visual.distanceSquared(cameraX, cameraY, cameraZ))) {
visual.tick();
}
}
public void beginFrame(TaskExecutor executor, double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
frameLimiter.tick();
processQueue();
var visuals = getStorage().getDynamicVisuals();
distributeWork(executor, visuals, visual -> updateVisual(visual, cameraX, cameraY, cameraZ, frustum));
}
protected void updateVisual(DynamicVisual visual, double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
if (!visual.decreaseFramerateWithDistance() || frameLimiter.shouldUpdate(visual.distanceSquared(cameraX, cameraY, cameraZ))) {
if (visual.isVisible(frustum)) {
visual.beginFrame();
}
}
}
private static <V> void distributeWork(TaskExecutor executor, List<V> visuals, Consumer<V> action) {
final int amount = visuals.size();
final int threadCount = executor.getThreadCount();
if (threadCount == 1) {
executor.execute(() -> visuals.forEach(action));
} else {
final int stride = Math.max(amount / (threadCount * 2), 1);
for (int start = 0; start < amount; start += stride) {
int end = Math.min(start + stride, amount);
var sub = visuals.subList(start, end);
executor.execute(() -> sub.forEach(action));
}
}
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.impl.instancing.ratelimit;
package com.jozufozu.flywheel.impl.visualization.ratelimit;
import net.minecraft.util.Mth;

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.impl.instancing.ratelimit;
package com.jozufozu.flywheel.impl.visualization.ratelimit;
/**
* Interface for rate-limiting updates based on an object's distance from the camera.

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.impl.instancing.ratelimit;
package com.jozufozu.flywheel.impl.visualization.ratelimit;
public class NonLimiter implements DistanceUpdateLimiter {
@Override

View file

@ -0,0 +1,43 @@
package com.jozufozu.flywheel.impl.visualization.storage;
import java.util.ArrayList;
import java.util.List;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.TickableVisual;
import com.jozufozu.flywheel.api.visual.Visual;
public abstract class AbstractStorage<T> implements Storage<T> {
protected final Engine engine;
protected final List<TickableVisual> tickableVisuals = new ArrayList<>();
protected final List<DynamicVisual> dynamicVisuals = new ArrayList<>();
protected AbstractStorage(Engine engine) {
this.engine = engine;
}
@Override
public List<TickableVisual> getTickableVisuals() {
return tickableVisuals;
}
@Override
public List<DynamicVisual> getDynamicVisuals() {
return dynamicVisuals;
}
protected void setup(Visual visual) {
visual.init();
if (visual instanceof TickableVisual tickable) {
tickableVisuals.add(tickable);
tickable.tick();
}
if (visual instanceof DynamicVisual dynamic) {
dynamicVisuals.add(dynamic);
dynamic.beginFrame();
}
}
}

View file

@ -0,0 +1,7 @@
package com.jozufozu.flywheel.impl.visualization.storage;
public enum Action {
ADD,
REMOVE,
UPDATE,
}

View file

@ -0,0 +1,86 @@
package com.jozufozu.flywheel.impl.visualization.storage;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.visual.Visual;
public abstract class One2ManyStorage<T> extends AbstractStorage<T> {
private final Multimap<T, Visual> allVisuals = HashMultimap.create();
public One2ManyStorage(Engine engine) {
super(engine);
}
@Override
public Collection<Visual> getAllVisuals() {
return allVisuals.values();
}
@Override
public void add(T obj) {
Collection<Visual> visuals = allVisuals.get(obj);
if (visuals.isEmpty()) {
create(obj);
}
}
@Override
public void remove(T obj) {
Collection<Visual> visuals = allVisuals.removeAll(obj);
if (visuals.isEmpty()) {
return;
}
tickableVisuals.removeAll(visuals);
dynamicVisuals.removeAll(visuals);
visuals.forEach(Visual::delete);
}
@Override
public void update(T obj) {
Collection<Visual> visuals = allVisuals.get(obj);
if (visuals.isEmpty()) {
return;
}
// TODO: shouldReset cannot be checked here because all visuals are created at once
visuals.forEach(Visual::update);
}
@Override
public void recreateAll() {
tickableVisuals.clear();
dynamicVisuals.clear();
allVisuals.values().forEach(Visual::delete);
List<T> objects = List.copyOf(allVisuals.keySet());
allVisuals.clear();
objects.forEach(this::create);
}
@Override
public void invalidate() {
tickableVisuals.clear();
dynamicVisuals.clear();
allVisuals.values().forEach(Visual::delete);
allVisuals.clear();
}
private void create(T obj) {
Collection<? extends Visual> visuals = createRaw(obj);
if (!visuals.isEmpty()) {
visuals.forEach(this::setup);
allVisuals.putAll(obj, visuals);
}
}
protected abstract Collection<? extends Visual> createRaw(T obj);
}

View file

@ -0,0 +1,101 @@
package com.jozufozu.flywheel.impl.visualization.storage;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.visual.Visual;
public abstract class One2OneStorage<T> extends AbstractStorage<T> {
private final Map<T, Visual> visuals = new HashMap<>();
public One2OneStorage(Engine engine) {
super(engine);
}
@Override
public Collection<Visual> getAllVisuals() {
return visuals.values();
}
@Override
public void add(T obj) {
Visual visual = visuals.get(obj);
if (visual == null) {
create(obj);
}
}
@Override
public void remove(T obj) {
Visual visual = visuals.remove(obj);
if (visual == null) {
return;
}
tickableVisuals.remove(visual);
dynamicVisuals.remove(visual);
visual.delete();
}
@Override
public void update(T obj) {
Visual visual = visuals.get(obj);
if (visual == null) {
return;
}
// resetting visuals is by default used to handle block state changes.
if (visual.shouldReset()) {
// delete and re-create the visual.
// resetting a visual supersedes updating it.
remove(obj);
create(obj);
} else {
visual.update();
}
}
@Override
public void recreateAll() {
tickableVisuals.clear();
dynamicVisuals.clear();
visuals.replaceAll((obj, visual) -> {
visual.delete();
Visual out = createRaw(obj);
if (out != null) {
setup(out);
}
return out;
});
}
@Override
public void invalidate() {
tickableVisuals.clear();
dynamicVisuals.clear();
visuals.values().forEach(Visual::delete);
visuals.clear();
}
private void create(T obj) {
Visual visual = createRaw(obj);
if (visual != null) {
setup(visual);
visuals.put(obj, visual);
}
}
@Nullable
protected abstract Visual createRaw(T obj);
}

View file

@ -0,0 +1,33 @@
package com.jozufozu.flywheel.impl.visualization.storage;
import java.util.Collection;
import java.util.List;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.TickableVisual;
import com.jozufozu.flywheel.api.visual.Visual;
public interface Storage<T> {
Collection<Visual> getAllVisuals();
List<TickableVisual> getTickableVisuals();
List<DynamicVisual> getDynamicVisuals();
/**
* Is the given object currently capable of being added?
*
* @return true if the object is currently capable of being visualized.
*/
boolean willAccept(T obj);
void add(T obj);
void remove(T obj);
void update(T obj);
void recreateAll();
void invalidate();
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.impl.instancing.storage;
package com.jozufozu.flywheel.impl.visualization.storage;
public record Transaction<T>(T obj, Action action) {
public static <T> Transaction<T> add(T obj) {

View file

@ -1,93 +1,28 @@
package com.jozufozu.flywheel.lib.instance;
import java.util.stream.Stream;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.api.instancer.InstancerProvider;
import com.jozufozu.flywheel.lib.light.LightListener;
import com.jozufozu.flywheel.lib.light.LightUpdater;
import com.jozufozu.flywheel.lib.struct.FlatLit;
import com.jozufozu.flywheel.api.instance.InstanceHandle;
import com.jozufozu.flywheel.api.instance.InstanceType;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LightLayer;
public abstract class AbstractInstance implements Instance {
protected final InstanceType<?> type;
protected final InstanceHandle handle;
public abstract class AbstractInstance implements Instance, LightListener {
protected final InstancerProvider instancerProvider;
protected final Vec3i renderOrigin;
protected final Level level;
protected boolean deleted = false;
public AbstractInstance(InstanceContext ctx, Level level) {
this.instancerProvider = ctx.instancerProvider();
this.renderOrigin = ctx.renderOrigin();
this.level = level;
protected AbstractInstance(InstanceType<?> type, InstanceHandle handle) {
this.type = type;
this.handle = handle;
}
@Override
public void init() {
LightUpdater.get(level).addListener(this);
updateLight();
public InstanceType<?> type() {
return type;
}
@Override
public void update() {
public final void setChanged() {
handle.setChanged();
}
@Override
public boolean shouldReset() {
return false;
}
/**
* Called after construction and when a light update occurs in the world.
*
* <br> If your model needs it, update light here.
*/
public void updateLight() {
}
protected abstract void _delete();
@Override
public final void delete() {
if (deleted) {
return;
}
_delete();
deleted = true;
}
@Override
public void onLightUpdate(LightLayer type, SectionPos pos) {
updateLight();
}
@Override
public boolean isInvalid() {
return deleted;
}
protected void relight(BlockPos pos, FlatLit<?>... parts) {
relight(level.getBrightness(LightLayer.BLOCK, pos), level.getBrightness(LightLayer.SKY, pos), parts);
}
protected void relight(int block, int sky, FlatLit<?>... parts) {
for (FlatLit<?> part : parts) {
part.setLight(block, sky);
}
}
protected <L extends FlatLit<?>> void relight(BlockPos pos, Stream<L> parts) {
relight(level.getBrightness(LightLayer.BLOCK, pos), level.getBrightness(LightLayer.SKY, pos), parts);
}
protected <L extends FlatLit<?>> void relight(int block, int sky, Stream<L> parts) {
parts.forEach(model -> model.setLight(block, sky));
handle.setDeleted();
}
}

View file

@ -1,11 +1,11 @@
package com.jozufozu.flywheel.lib.struct;
package com.jozufozu.flywheel.lib.instance;
import com.jozufozu.flywheel.api.struct.Handle;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.instance.InstanceHandle;
import com.jozufozu.flywheel.api.instance.InstanceType;
import net.minecraft.client.renderer.LightTexture;
public abstract class ColoredLitPart extends AbstractInstancePart implements FlatLit<ColoredLitPart> {
public abstract class ColoredLitInstance extends AbstractInstance implements FlatLit<ColoredLitInstance> {
public byte blockLight;
public byte skyLight;
@ -14,19 +14,19 @@ public abstract class ColoredLitPart extends AbstractInstancePart implements Fla
public byte b = (byte) 0xFF;
public byte a = (byte) 0xFF;
public ColoredLitPart(StructType<? extends ColoredLitPart> type, Handle handle) {
public ColoredLitInstance(InstanceType<? extends ColoredLitInstance> type, InstanceHandle handle) {
super(type, handle);
}
@Override
public ColoredLitPart setBlockLight(int blockLight) {
public ColoredLitInstance setBlockLight(int blockLight) {
this.blockLight = (byte) blockLight;
setChanged();
return this;
}
@Override
public ColoredLitPart setSkyLight(int skyLight) {
public ColoredLitInstance setSkyLight(int skyLight) {
this.skyLight = (byte) skyLight;
setChanged();
return this;
@ -37,11 +37,11 @@ public abstract class ColoredLitPart extends AbstractInstancePart implements Fla
return LightTexture.pack(blockLight, skyLight);
}
public ColoredLitPart setColor(int color) {
public ColoredLitInstance setColor(int color) {
return setColor(color, false);
}
public ColoredLitPart setColor(int color, boolean alpha) {
public ColoredLitInstance setColor(int color, boolean alpha) {
byte r = (byte) ((color >> 16) & 0xFF);
byte g = (byte) ((color >> 8) & 0xFF);
byte b = (byte) (color & 0xFF);
@ -54,11 +54,11 @@ public abstract class ColoredLitPart extends AbstractInstancePart implements Fla
}
}
public ColoredLitPart setColor(int r, int g, int b) {
public ColoredLitInstance setColor(int r, int g, int b) {
return setColor((byte) r, (byte) g, (byte) b);
}
public ColoredLitPart setColor(byte r, byte g, byte b) {
public ColoredLitInstance setColor(byte r, byte g, byte b) {
this.r = r;
this.g = g;
this.b = b;
@ -66,7 +66,7 @@ public abstract class ColoredLitPart extends AbstractInstancePart implements Fla
return this;
}
public ColoredLitPart setColor(byte r, byte g, byte b, byte a) {
public ColoredLitInstance setColor(byte r, byte g, byte b, byte a) {
this.r = r;
this.g = g;
this.b = b;

View file

@ -0,0 +1,17 @@
package com.jozufozu.flywheel.lib.instance;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.instance.InstanceWriter;
public abstract class ColoredLitWriter<I extends ColoredLitInstance> implements InstanceWriter<I> {
@Override
public void write(final long ptr, final I instance) {
MemoryUtil.memPutShort(ptr, instance.blockLight);
MemoryUtil.memPutShort(ptr + 2, instance.skyLight);
MemoryUtil.memPutByte(ptr + 4, instance.r);
MemoryUtil.memPutByte(ptr + 5, instance.g);
MemoryUtil.memPutByte(ptr + 6, instance.b);
MemoryUtil.memPutByte(ptr + 7, instance.a);
}
}

View file

@ -1,39 +1,39 @@
package com.jozufozu.flywheel.lib.struct;
package com.jozufozu.flywheel.lib.instance;
import com.jozufozu.flywheel.api.struct.InstancePart;
import com.jozufozu.flywheel.api.instance.Instance;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.LightLayer;
/**
* An interface that implementors of {@link InstancePart} should also implement
* An interface that implementors of {@link Instance} should also implement
* if they wish to make use of Flywheel's provided light update methods.
* <p>
* This only covers flat lighting, smooth lighting is still TODO.
*
* @param <P> The name of the class that implements this interface.
* @param <I> The name of the class that implements this interface.
*/
public interface FlatLit<P extends InstancePart & FlatLit<P>> {
public interface FlatLit<I extends Instance & FlatLit<I>> {
/**
* @param blockLight An integer in the range [0, 15] representing the
* amount of block light this instance should receive.
* @return {@code this}
*/
P setBlockLight(int blockLight);
I setBlockLight(int blockLight);
/**
* @param skyLight An integer in the range [0, 15] representing the
* amount of sky light this instance should receive.
* @return {@code this}
*/
P setSkyLight(int skyLight);
I setSkyLight(int skyLight);
default P setLight(int blockLight, int skyLight) {
default I setLight(int blockLight, int skyLight) {
return setBlockLight(blockLight).setSkyLight(skyLight);
}
default P updateLight(BlockAndTintGetter level, BlockPos pos) {
default I updateLight(BlockAndTintGetter level, BlockPos pos) {
return setLight(level.getBrightness(LightLayer.BLOCK, pos), level.getBrightness(LightLayer.SKY, pos));
}

Some files were not shown because too many files have changed in this diff Show more