Light update convergence

- Move light update logic for all instances to use LightUpdater
 - Begin refactor of LightUpdater to account for moving listeners
This commit is contained in:
Jozufozu 2021-08-29 14:40:46 -07:00
parent cb10e4e7d1
commit 7ca4ea5c3e
19 changed files with 358 additions and 340 deletions

View file

@ -0,0 +1,97 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.Arrays;
import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import com.jozufozu.flywheel.backend.material.MaterialManager;
import com.jozufozu.flywheel.core.materials.IFlatLight;
import com.jozufozu.flywheel.light.GridAlignedBB;
import com.jozufozu.flywheel.light.ILightUpdateListener;
import com.jozufozu.flywheel.light.LightUpdater;
import com.jozufozu.flywheel.light.ListenerStatus;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
/**
* A general interface providing information about any type of thing that could use Flywheel's instanced rendering.
* Right now, that's only {@link TileInstanceManager}, but there could be an entity equivalent in the future.
*/
public abstract class AbstractInstance implements IInstance, ILightUpdateListener {
protected final MaterialManager materialManager;
protected final World world;
public AbstractInstance(MaterialManager materialManager, World world) {
this.materialManager = materialManager;
this.world = world;
}
/**
* Free any acquired resources.
*/
public abstract void remove();
/**
* Update instance data here. Good for when data doesn't change very often and when animations are GPU based.
* Don't query lighting data here, that's handled separately in {@link #updateLight()}.
*
* <br><br> If your animations are complex or more CPU driven, see {@link IDynamicInstance} or {@link ITickableInstance}.
*/
public void update() {
}
/**
* Called after construction and when a light update occurs in the world.
*
* <br> If your model needs it, update light here.
*/
public void updateLight() {
}
/**
* When an instance is reset, the instance is deleted and re-created.
*
* <p>
* Just before {@link #update()} would be called, <code>shouldReset()</code> is checked.
* If this function returns <code>true</code>, then this instance will be {@link #remove removed},
* 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</code> if this instance should be discarded and refreshed.
*/
public boolean shouldReset() {
return false;
}
@Override
public ListenerStatus status() {
return ListenerStatus.OKAY;
}
@Override
public void onLightUpdate(IBlockDisplayReader world, LightType type, GridAlignedBB changed) {
updateLight();
}
protected void relight(BlockPos pos, IFlatLight<?>... models) {
relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models);
}
protected <L extends IFlatLight<?>> void relight(BlockPos pos, Stream<L> models) {
relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models);
}
protected void relight(int block, int sky, IFlatLight<?>... models) {
relight(block, sky, Arrays.stream(models));
}
protected <L extends IFlatLight<?>> void relight(int block, int sky, Stream<L> models) {
models.forEach(model -> model.setBlockLight(block)
.setSkyLight(sky));
}
}

View file

@ -1,31 +1,7 @@
package com.jozufozu.flywheel.backend.instancing;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import net.minecraft.util.math.BlockPos;
/**
* A general interface providing information about any type of thing that could use Flywheel's instanced rendering.
* Right now, that's only {@link TileInstanceManager}, but there could be an entity equivalent in the future.
*/
public interface IInstance {
BlockPos getWorldPosition();
void updateLight();
void remove();
/**
* When an instance is reset, the instance is deleted and re-created.
*
* <p>
* This is used to handle things like block state changes.
* </p>
*
* @return true if this instance should be reset
*/
boolean shouldReset();
void update();
BlockPos getWorldPosition();
}

View file

@ -25,7 +25,7 @@ public abstract class InstanceManager<T> implements MaterialManagerImpl.OriginSh
private final Set<T> queuedAdditions;
private final Set<T> queuedUpdates;
protected final Map<T, IInstance> instances;
protected final Map<T, AbstractInstance> instances;
protected final Object2ObjectOpenHashMap<T, ITickableInstance> tickableInstances;
protected final Object2ObjectOpenHashMap<T, IDynamicInstance> dynamicInstances;
@ -63,7 +63,7 @@ public abstract class InstanceManager<T> implements MaterialManagerImpl.OriginSh
protected abstract boolean canCreateInstance(T obj);
@Nullable
protected abstract IInstance createRaw(T obj);
protected abstract AbstractInstance createRaw(T obj);
/**
* Ticks the InstanceManager.
@ -169,7 +169,7 @@ public abstract class InstanceManager<T> implements MaterialManagerImpl.OriginSh
.canUseInstancing()) return;
if (canInstance(obj)) {
IInstance instance = getInstance(obj, false);
AbstractInstance instance = getInstance(obj, false);
if (instance != null) {
@ -186,40 +186,29 @@ public abstract class InstanceManager<T> implements MaterialManagerImpl.OriginSh
}
}
public void onLightUpdate(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
if (canInstance(obj)) {
IInstance instance = getInstance(obj, false);
if (instance != null) instance.updateLight();
}
}
public void remove(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
if (canInstance(obj)) {
IInstance instance = getInstance(obj, false);
AbstractInstance instance = getInstance(obj, false);
if (instance != null) removeInternal(obj, instance);
}
}
public void invalidate() {
instances.values().forEach(IInstance::remove);
instances.values().forEach(AbstractInstance::remove);
instances.clear();
dynamicInstances.clear();
tickableInstances.clear();
}
@Nullable
protected <I extends T> IInstance getInstance(I obj, boolean create) {
protected <I extends T> AbstractInstance getInstance(I obj, boolean create) {
if (!Backend.getInstance()
.canUseInstancing()) return null;
IInstance instance = instances.get(obj);
AbstractInstance instance = instances.get(obj);
if (instance != null) {
return instance;
@ -283,18 +272,20 @@ public abstract class InstanceManager<T> implements MaterialManagerImpl.OriginSh
getInstance(tile, true);
}
protected void removeInternal(T obj, IInstance instance) {
protected void removeInternal(T obj, AbstractInstance instance) {
instance.remove();
instances.remove(obj);
dynamicInstances.remove(obj);
tickableInstances.remove(obj);
}
protected IInstance createInternal(T obj) {
IInstance renderer = createRaw(obj);
@Nullable
protected AbstractInstance createInternal(T obj) {
AbstractInstance renderer = createRaw(obj);
if (renderer != null) {
renderer.updateLight();
renderer.startListening();
instances.put(obj, renderer);
if (renderer instanceof IDynamicInstance) dynamicInstances.put(obj, (IDynamicInstance) renderer);

View file

@ -3,7 +3,6 @@ package com.jozufozu.flywheel.backend.instancing;
import javax.annotation.Nonnull;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.state.RenderLayer;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.event.RenderLayerEvent;
@ -28,7 +27,7 @@ public class InstancedRenderDispatcher {
private static final WorldAttached<InstanceWorld> instanceWorlds = new WorldAttached<>($ -> new InstanceWorld());
/**
* Call this when you want to manually run {@link IInstance#update()}.
* Call this when you want to manually run {@link AbstractInstance#update()}.
* @param te The tile whose instance you want to update.
*/
public static void enqueueUpdate(TileEntity te) {
@ -36,7 +35,7 @@ public class InstancedRenderDispatcher {
}
/**
* Call this when you want to manually run {@link IInstance#update()}.
* Call this when you want to manually run {@link AbstractInstance#update()}.
* @param entity The entity whose instance you want to update.
*/
public static void enqueueUpdate(Entity entity) {

View file

@ -4,15 +4,13 @@ import java.util.Arrays;
import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.instancing.IDynamicInstance;
import com.jozufozu.flywheel.backend.instancing.IInstance;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.ITickableInstance;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import com.jozufozu.flywheel.backend.material.InstanceMaterial;
import com.jozufozu.flywheel.backend.material.MaterialManager;
import com.jozufozu.flywheel.core.Materials;
import com.jozufozu.flywheel.core.materials.IFlatLight;
import com.jozufozu.flywheel.core.materials.ModelData;
import com.jozufozu.flywheel.core.materials.OrientedData;
import com.jozufozu.flywheel.light.ILightUpdateListener;
import com.jozufozu.flywheel.light.ListenerStatus;
import com.jozufozu.flywheel.light.Volume;
import net.minecraft.entity.Entity;
import net.minecraft.tileentity.TileEntity;
@ -21,8 +19,6 @@ import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f;
import net.minecraft.util.math.vector.Vector3i;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
/**
* The layer between a {@link TileEntity} and the Flywheel backend.
@ -39,50 +35,25 @@ import net.minecraft.world.World;
*
* @param <E> The type of {@link Entity} your class is an instance of.
*/
public abstract class EntityInstance<E extends Entity> implements IInstance {
public abstract class EntityInstance<E extends Entity> extends AbstractInstance implements ILightUpdateListener {
protected final MaterialManager materialManager;
protected final E entity;
protected final World world;
public EntityInstance(MaterialManager materialManager, E entity) {
this.materialManager = materialManager;
super(materialManager, entity.level);
this.entity = entity;
this.world = entity.level;
startListening();
}
/**
* Free any acquired resources.
*/
public abstract void remove();
/**
* Update instance data here. Good for when data doesn't change very often and when animations are GPU based.
* Don't query lighting data here, that's handled separately in {@link #updateLight()}.
*
* <br><br> If your animations are complex or more CPU driven, see {@link IDynamicInstance} or {@link ITickableInstance}.
*/
public void update() {
@Override
public Volume.Box getVolume() {
return Volume.box(entity.getBoundingBox());
}
/**
* Called after construction and when a light update occurs in the world.
*
* <br> If your model needs it, update light here.
*/
public void updateLight() {
}
/**
* Just before {@link #update()} would be called, <code>shouldReset()</code> is checked.
* If this function returns <code>true</code>, then this instance will be {@link #remove removed},
* 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.
*
* @return <code>true</code> if this instance should be discarded and refreshed.
*/
public boolean shouldReset() {
return false;
@Override
public ListenerStatus status() {
return ListenerStatus.UPDATE;
}
/**
@ -119,30 +90,4 @@ public abstract class EntityInstance<E extends Entity> implements IInstance {
public BlockPos getWorldPosition() {
return entity.blockPosition();
}
protected void relight(BlockPos pos, IFlatLight<?>... models) {
relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models);
}
protected <L extends IFlatLight<?>> void relight(BlockPos pos, Stream<L> models) {
relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models);
}
protected void relight(int block, int sky, IFlatLight<?>... models) {
relight(block, sky, Arrays.stream(models));
}
protected <L extends IFlatLight<?>> void relight(int block, int sky, Stream<L> models) {
models.forEach(model -> model.setBlockLight(block)
.setSkyLight(sky));
}
protected InstanceMaterial<ModelData> getTransformMaterial() {
return materialManager.defaultSolid().material(Materials.TRANSFORMED);
}
protected InstanceMaterial<OrientedData> getOrientedMaterial() {
return materialManager.defaultSolid().material(Materials.ORIENTED);
}
}

View file

@ -1,7 +1,7 @@
package com.jozufozu.flywheel.backend.instancing.entity;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.IInstance;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
import com.jozufozu.flywheel.backend.material.MaterialManagerImpl;
@ -23,7 +23,7 @@ public class EntityInstanceManager extends InstanceManager<Entity> {
}
@Override
protected IInstance createRaw(Entity obj) {
protected AbstractInstance createRaw(Entity obj) {
return InstancedRenderRegistry.getInstance()
.create(materialManager, obj);
}

View file

@ -1,24 +1,19 @@
package com.jozufozu.flywheel.backend.instancing.tile;
import java.util.Arrays;
import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.instancing.IDynamicInstance;
import com.jozufozu.flywheel.backend.instancing.IInstance;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.ITickableInstance;
import com.jozufozu.flywheel.backend.material.InstanceMaterial;
import com.jozufozu.flywheel.backend.material.MaterialManager;
import com.jozufozu.flywheel.backend.material.MaterialManagerImpl;
import com.jozufozu.flywheel.core.Materials;
import com.jozufozu.flywheel.core.materials.IFlatLight;
import com.jozufozu.flywheel.core.materials.ModelData;
import com.jozufozu.flywheel.core.materials.OrientedData;
import com.jozufozu.flywheel.light.GridAlignedBB;
import com.jozufozu.flywheel.light.Volume;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
/**
* The layer between a {@link TileEntity} and the Flywheel backend.
@ -37,46 +32,21 @@ import net.minecraft.world.World;
*
* @param <T> The type of {@link TileEntity} your class is an instance of.
*/
public abstract class TileEntityInstance<T extends TileEntity> implements IInstance {
public abstract class TileEntityInstance<T extends TileEntity> extends AbstractInstance {
protected final MaterialManager materialManager;
protected final T tile;
protected final World world;
protected final BlockPos pos;
protected final BlockPos instancePos;
protected final BlockState blockState;
public TileEntityInstance(MaterialManager materialManager, T tile) {
this.materialManager = materialManager;
super(materialManager, tile.getLevel());
this.tile = tile;
this.world = tile.getLevel();
this.pos = tile.getBlockPos();
this.blockState = tile.getBlockState();
this.instancePos = pos.subtract(materialManager.getOriginCoordinate());
}
/**
* Update instance data here. Good for when data doesn't change very often and when animations are GPU based.
* Don't query lighting data here, that's handled separately in {@link #updateLight()}.
*
* <br><br> If your animations are complex or more CPU driven, see {@link IDynamicInstance} or {@link ITickableInstance}.
*/
public void update() {
}
/**
* Called after construction and when a light update occurs in the world.
*
* <br> If your model needs it, update light here.
*/
public void updateLight() {
}
/**
* Free any acquired resources.
*/
public abstract void remove();
/**
* Just before {@link #update()} would be called, <code>shouldReset()</code> is checked.
* If this function returns <code>true</code>, then this instance will be {@link #remove removed},
@ -106,23 +76,6 @@ public abstract class TileEntityInstance<T extends TileEntity> implements IInsta
return pos;
}
protected void relight(BlockPos pos, IFlatLight<?>... models) {
relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models);
}
protected <L extends IFlatLight<?>> void relight(BlockPos pos, Stream<L> models) {
relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models);
}
protected void relight(int block, int sky, IFlatLight<?>... models) {
relight(block, sky, Arrays.stream(models));
}
protected <L extends IFlatLight<?>> void relight(int block, int sky, Stream<L> models) {
models.forEach(model -> model.setBlockLight(block)
.setSkyLight(sky));
}
protected InstanceMaterial<ModelData> getTransformMaterial() {
return materialManager.defaultCutout().material(Materials.TRANSFORMED);
}
@ -130,4 +83,9 @@ public abstract class TileEntityInstance<T extends TileEntity> implements IInsta
protected InstanceMaterial<OrientedData> getOrientedMaterial() {
return materialManager.defaultCutout().material(Materials.ORIENTED);
}
@Override
public Volume.Block getVolume() {
return Volume.block(pos);
}
}

View file

@ -1,7 +1,7 @@
package com.jozufozu.flywheel.backend.instancing.tile;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.IInstance;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
import com.jozufozu.flywheel.backend.material.MaterialManagerImpl;
@ -23,7 +23,7 @@ public class TileInstanceManager extends InstanceManager<TileEntity> {
}
@Override
protected IInstance createRaw(TileEntity obj) {
protected AbstractInstance createRaw(TileEntity obj) {
return InstancedRenderRegistry.getInstance()
.create(materialManager, obj);
}

View file

@ -4,12 +4,15 @@ import java.util.ArrayList;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.light.LightUpdater;
import net.minecraft.client.Minecraft;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.world.IWorld;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@ -45,4 +48,10 @@ public class ForgeEvents {
}
}
@SubscribeEvent
public static void rwle(TickEvent.ClientTickEvent e) {
if (e.phase == TickEvent.Phase.END && Backend.isGameActive())
LightUpdater.getInstance().tick();
}
}

View file

@ -53,6 +53,10 @@ public class GridAlignedBB {
return new GridAlignedBB(start.getX(), start.getY(), start.getZ(), end.getX() + 1, end.getY() + 1, end.getZ() + 1);
}
public static GridAlignedBB from(BlockPos pos) {
return new GridAlignedBB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1);
}
public static GridAlignedBB from(int sectionX, int sectionZ) {
int startX = sectionX << 4;
int startZ = sectionZ << 4;

View file

@ -4,28 +4,33 @@ import net.minecraft.world.IBlockDisplayReader;
import net.minecraft.world.LightType;
/**
* Anything can implement this, implementors should call {@link LightUpdater#startListening}
* Anything can implement this, implementors should call {@link #startListening}
* appropriately to make sure they get the updates they want.
*/
public interface ILightUpdateListener {
Volume getVolume();
ListenerStatus status();
default void startListening() {
LightUpdater.getInstance().addListener(this);
}
/**
* Called when a light updates in a chunk the implementor cares about.
*
* @return true if this object is no longer valid and should not receive any more updates.
*/
boolean onLightUpdate(IBlockDisplayReader world, LightType type, GridAlignedBB changed);
void onLightUpdate(IBlockDisplayReader world, LightType type, GridAlignedBB changed);
/**
* Called when the server sends light data to the client.
*
* @return true if this object is no longer valid and should not receive any more updates.
*/
default boolean onLightPacket(IBlockDisplayReader world, int chunkX, int chunkZ) {
default void onLightPacket(IBlockDisplayReader world, int chunkX, int chunkZ) {
GridAlignedBB changedVolume = GridAlignedBB.from(chunkX, chunkZ);
if (onLightUpdate(world, LightType.BLOCK, changedVolume)) return true;
onLightUpdate(world, LightType.BLOCK, changedVolume);
return onLightUpdate(world, LightType.SKY, changedVolume);
onLightUpdate(world, LightType.SKY, changedVolume);
}
}

View file

@ -1,27 +1,16 @@
package com.jozufozu.flywheel.light;
import java.util.WeakHashMap;
import java.util.function.LongConsumer;
import java.util.Set;
import com.jozufozu.flywheel.util.WeakHashSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongRBTreeSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.client.Minecraft;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.SectionPos;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraft.world.LightType;
/**
* By using WeakReferences we can automatically remove listeners when they are garbage collected.
* This allows us to easily be more clever about how we store the listeners. Each listener is associated
* with 2 sets of longs indicating what chunks and sections each listener is in. Additionally, a reverse
* mapping is created to allow for fast lookups when light updates. The reverse mapping is more interesting,
* but {@link #listenersToSections}, and {@link #listenersToChunks} are used to know what sections and
* chunks we need to remove the listeners from if they re-subscribe. Otherwise, listeners could get updates
* they no longer care about. This is done in {@link #clearSections} and {@link #clearChunks}
*/
public class LightUpdater {
private static LightUpdater instance;
@ -32,72 +21,69 @@ public class LightUpdater {
return instance;
}
private final Long2ObjectMap<WeakHashSet<ILightUpdateListener>> sections;
private final WeakHashMap<ILightUpdateListener, LongRBTreeSet> listenersToSections;
private final Long2ObjectMap<WeakHashSet<ILightUpdateListener>> chunks;
private final WeakHashMap<ILightUpdateListener, LongRBTreeSet> listenersToChunks;
private final WeakHashSet<ILightUpdateListener> allListeners;
private final WeakContainmentMultiMap<ILightUpdateListener> sections;
private final WeakContainmentMultiMap<ILightUpdateListener> chunks;
public LightUpdater() {
sections = new Long2ObjectOpenHashMap<>();
listenersToSections = new WeakHashMap<>();
allListeners = new WeakHashSet<>();
sections = new WeakContainmentMultiMap<>();
chunks = new WeakContainmentMultiMap<>();
}
chunks = new Long2ObjectOpenHashMap<>();
listenersToChunks = new WeakHashMap<>();
public void tick() {
for (ILightUpdateListener listener : allListeners) {
if (listener.status() == ListenerStatus.UPDATE) {
addListener(listener);
listener.onLightUpdate(Minecraft.getInstance().level, LightType.BLOCK, null);
}
}
}
/**
* Add a listener associated with the given {@link BlockPos}.
* <p>
* When a light update occurs in the chunk the position is contained in,
* {@link ILightUpdateListener#onLightUpdate} will be called.
*
* @param pos The position in the world that the listener cares about.
* Add a listener.
* @param listener The object that wants to receive light update notifications.
*/
public void startListening(BlockPos pos, ILightUpdateListener listener) {
LongRBTreeSet sections = clearSections(listener);
LongRBTreeSet chunks = clearChunks(listener);
public void addListener(ILightUpdateListener listener) {
allListeners.add(listener);
long sectionPos = worldToSection(pos);
addToSection(sectionPos, listener);
sections.add(sectionPos);
Volume volume = listener.getVolume();
long chunkPos = sectionToChunk(sectionPos);
addToChunk(chunkPos, listener);
chunks.add(chunkPos);
}
LongSet sections = this.sections.getAndResetContainment(listener);
LongSet chunks = this.chunks.getAndResetContainment(listener);
/**
* Add a listener associated with the given {@link GridAlignedBB}.
* <p>
* When a light update occurs in any chunk spanning the given volume,
* {@link ILightUpdateListener#onLightUpdate} will be called.
*
* @param volume The volume in the world that the listener cares about.
* @param listener The object that wants to receive light update notifications.
*/
public void startListening(GridAlignedBB volume, ILightUpdateListener listener) {
LongRBTreeSet sections = clearSections(listener);
LongRBTreeSet chunks = clearSections(listener);
if (volume instanceof Volume.Block) {
BlockPos pos = ((Volume.Block) volume).pos;
long sectionPos = blockToSection(pos);
this.sections.put(sectionPos, listener);
sections.add(sectionPos);
int minX = SectionPos.blockToSectionCoord(volume.minX);
int minY = SectionPos.blockToSectionCoord(volume.minY);
int minZ = SectionPos.blockToSectionCoord(volume.minZ);
int maxX = SectionPos.blockToSectionCoord(volume.maxX);
int maxY = SectionPos.blockToSectionCoord(volume.maxY);
int maxZ = SectionPos.blockToSectionCoord(volume.maxZ);
long chunkPos = sectionToChunk(sectionPos);
this.chunks.put(chunkPos, listener);
chunks.add(chunkPos);
} else if (volume instanceof Volume.Box) {
GridAlignedBB box = ((Volume.Box) volume).box;
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
for (int y = minY; y <= maxY; y++) {
long sectionPos = SectionPos.asLong(x, y, z);
addToSection(sectionPos, listener);
sections.add(sectionPos);
int minX = SectionPos.blockToSectionCoord(box.minX);
int minY = SectionPos.blockToSectionCoord(box.minY);
int minZ = SectionPos.blockToSectionCoord(box.minZ);
int maxX = SectionPos.blockToSectionCoord(box.maxX);
int maxY = SectionPos.blockToSectionCoord(box.maxY);
int maxZ = SectionPos.blockToSectionCoord(box.maxZ);
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
for (int y = minY; y <= maxY; y++) {
long sectionPos = SectionPos.asLong(x, y, z);
this.sections.put(sectionPos, listener);
sections.add(sectionPos);
}
long chunkPos = SectionPos.asLong(x, 0, z);
this.chunks.put(chunkPos, listener);
chunks.add(chunkPos);
}
long chunkPos = SectionPos.asLong(x, 0, z);
addToChunk(chunkPos, listener);
chunks.add(chunkPos);
}
}
}
@ -110,13 +96,17 @@ public class LightUpdater {
* @param sectionPos A long representing the section position where light changed.
*/
public void onLightUpdate(IBlockDisplayReader world, LightType type, long sectionPos) {
WeakHashSet<ILightUpdateListener> set = sections.get(sectionPos);
Set<ILightUpdateListener> set = sections.get(sectionPos);
if (set == null || set.isEmpty()) return;
set.removeIf(l -> l.status().shouldRemove());
GridAlignedBB chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos));
set.removeIf(listener -> listener.onLightUpdate(world, type, chunkBox.copy()));
for (ILightUpdateListener listener : set) {
listener.onLightUpdate(world, type, chunkBox.copy());
}
}
/**
@ -126,63 +116,20 @@ public class LightUpdater {
* @param world The world in which light was updated.
*/
public void onLightPacket(IBlockDisplayReader world, int chunkX, int chunkZ) {
long chunkPos = SectionPos.asLong(chunkX, 0, chunkZ);
WeakHashSet<ILightUpdateListener> set = chunks.get(chunkPos);
Set<ILightUpdateListener> set = chunks.get(chunkPos);
if (set == null || set.isEmpty()) return;
set.removeIf(listener -> listener.onLightPacket(world, chunkX, chunkZ));
}
set.removeIf(l -> l.status().shouldRemove());
private LongRBTreeSet clearChunks(ILightUpdateListener listener) {
return clear(listener, listenersToChunks, chunks);
}
private LongRBTreeSet clearSections(ILightUpdateListener listener) {
return clear(listener, listenersToSections, sections);
}
private LongRBTreeSet clear(ILightUpdateListener listener, WeakHashMap<ILightUpdateListener, LongRBTreeSet> listeners, Long2ObjectMap<WeakHashSet<ILightUpdateListener>> lookup) {
LongRBTreeSet set = listeners.get(listener);
if (set == null) {
set = new LongRBTreeSet();
listeners.put(listener, set);
} else {
set.forEach((LongConsumer) l -> {
WeakHashSet<ILightUpdateListener> listeningSections = lookup.get(l);
if (listeningSections != null) listeningSections.remove(listener);
});
set.clear();
for (ILightUpdateListener listener : set) {
listener.onLightPacket(world, chunkX, chunkZ);
}
return set;
}
private void addToSection(long sectionPos, ILightUpdateListener listener) {
getOrCreate(sections, sectionPos).add(listener);
}
private void addToChunk(long chunkPos, ILightUpdateListener listener) {
getOrCreate(chunks, chunkPos).add(listener);
}
private WeakHashSet<ILightUpdateListener> getOrCreate(Long2ObjectMap<WeakHashSet<ILightUpdateListener>> sections, long chunkPos) {
WeakHashSet<ILightUpdateListener> set = sections.get(chunkPos);
if (set == null) {
set = new WeakHashSet<>();
sections.put(chunkPos, set);
}
return set;
}
public static long worldToSection(BlockPos pos) {
public static long blockToSection(BlockPos pos) {
return SectionPos.asLong(pos.getX(), pos.getY(), pos.getZ());
}

View file

@ -0,0 +1,16 @@
package com.jozufozu.flywheel.light;
public enum ListenerStatus {
OKAY,
REMOVE,
UPDATE,
;
public boolean isOk() {
return this == OKAY;
}
public boolean shouldRemove() {
return this == REMOVE;
}
}

View file

@ -0,0 +1,35 @@
package com.jozufozu.flywheel.light;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
public class Volume {
public static Volume.Block block(BlockPos pos) {
return new Block(pos);
}
public static Volume.Box box(GridAlignedBB box) {
return new Box(box);
}
public static Volume.Box box(AxisAlignedBB box) {
return new Box(GridAlignedBB.from(box));
}
public static class Block extends Volume {
public final BlockPos pos;
public Block(BlockPos pos) {
this.pos = pos;
}
}
public static class Box extends Volume {
public final GridAlignedBB box;
public Box(GridAlignedBB box) {
this.box = box;
}
}
}

View file

@ -0,0 +1,56 @@
package com.jozufozu.flywheel.light;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.LongConsumer;
import com.jozufozu.flywheel.util.WeakHashSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongRBTreeSet;
import it.unimi.dsi.fastutil.longs.LongSet;
public class WeakContainmentMultiMap<T> {
private final Long2ObjectMap<WeakHashSet<T>> forward;
private final WeakHashMap<T, LongSet> reverse;
public WeakContainmentMultiMap() {
forward = new Long2ObjectOpenHashMap<>();
reverse = new WeakHashMap<>();
}
/**
* This is a confusing function, but it maintains the internal state of the chunk/section maps.
*
* <p>
* First, uses the reverse lookup map to remove listener from all sets in the lookup map.<br>
* Then, clears the listeners containment set.
* </p>
*
* @param listener The listener to clean up.
* @return An empty set that should be populated with the chunks/sections the listener is contained in.
*/
public LongSet getAndResetContainment(T listener) {
LongSet containmentSet = reverse.computeIfAbsent(listener, $ -> new LongRBTreeSet());
containmentSet.forEach((LongConsumer) l -> {
WeakHashSet<T> listeners = forward.get(l);
if (listeners != null) listeners.remove(listener);
});
containmentSet.clear();
return containmentSet;
}
public Set<T> get(long l) {
return forward.get(l);
}
public void put(long sectionPos, T listener) {
forward.computeIfAbsent(sectionPos, $ -> new WeakHashSet<>()).add(listener);
}
}

View file

@ -38,25 +38,6 @@ public abstract class LightUpdateMixin extends AbstractChunkProvider {
ClientChunkProvider thi = ((ClientChunkProvider) (Object) this);
ClientWorld world = (ClientWorld) thi.getLevel();
Chunk chunk = thi.getChunk(pos.x(), pos.z(), false);
int sectionY = pos.y();
if (ChunkUtil.isValidSection(chunk, sectionY)) {
InstanceManager<TileEntity> tiles = InstancedRenderDispatcher.getTiles(world);
InstanceManager<Entity> entities = InstancedRenderDispatcher.getEntities(world);
chunk.getBlockEntities()
.entrySet()
.stream()
.filter(entry -> SectionPos.blockToSectionCoord(entry.getKey()
.getY()) == sectionY)
.map(Map.Entry::getValue)
.forEach(tiles::onLightUpdate);
chunk.getEntitySections()[sectionY].forEach(entities::onLightUpdate);
}
LightUpdater.getInstance()
.onLightUpdate(world, type, pos.asLong());
}

View file

@ -34,22 +34,6 @@ public class NetworkLightUpdateMixin {
int chunkX = packet.getX();
int chunkZ = packet.getZ();
Chunk chunk = world.getChunkSource()
.getChunk(chunkX, chunkZ, false);
if (chunk != null) {
InstanceManager<TileEntity> tiles = InstancedRenderDispatcher.getTiles(world);
InstanceManager<Entity> entities = InstancedRenderDispatcher.getEntities(world);
chunk.getBlockEntities()
.values()
.forEach(tiles::onLightUpdate);
Arrays.stream(chunk.getEntitySections())
.flatMap(ClassInheritanceMultiMap::stream)
.forEach(entities::onLightUpdate);
}
LightUpdater.getInstance()
.onLightPacket(world, chunkX, chunkZ);
});

View file

@ -98,7 +98,11 @@ public class WeakHashSet<T> extends AbstractSet<T> {
@Override
public boolean addAll(Collection<? extends T> c) {
return false;
boolean out = false;
for (T t : c) {
out |= add(t);
}
return out;
}
@Override

View file

@ -1,13 +1,12 @@
package com.jozufozu.flywheel.vanilla;
import com.jozufozu.flywheel.backend.instancing.IDynamicInstance;
import com.jozufozu.flywheel.backend.instancing.ITickableInstance;
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstance;
import com.jozufozu.flywheel.backend.material.MaterialManager;
import com.jozufozu.flywheel.backend.model.ModelPool;
import com.jozufozu.flywheel.backend.state.TextureRenderState;
import com.jozufozu.flywheel.core.Materials;
import com.jozufozu.flywheel.core.materials.ModelData;
import com.jozufozu.flywheel.core.model.BlockModel;
import com.jozufozu.flywheel.core.model.IModel;
import com.jozufozu.flywheel.core.model.ModelPart;
import com.jozufozu.flywheel.util.AnimationTickHolder;
@ -21,22 +20,36 @@ import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f;
public class MinecartInstance<T extends AbstractMinecartEntity> extends EntityInstance<T> implements IDynamicInstance {
public class MinecartInstance<T extends AbstractMinecartEntity> extends EntityInstance<T> implements IDynamicInstance, ITickableInstance {
private static final ResourceLocation MINECART_LOCATION = new ResourceLocation("textures/entity/minecart.png");
MatrixTransformStack stack = new MatrixTransformStack();
private final ModelData body;
private final ModelData contents;
private ModelData contents;
private BlockState blockstate;
public MinecartInstance(MaterialManager materialManager, T entity) {
super(materialManager, entity);
blockstate = entity.getDisplayBlockState();
contents = getContents();
body = getBody();
}
@Override
public void tick() {
BlockState displayBlockState = entity.getDisplayBlockState();
if (displayBlockState != blockstate) {
blockstate = displayBlockState;
contents.delete();
contents = getContents();
updateLight();
}
}
@Override
public void beginFrame() {
stack.setIdentity();
@ -124,8 +137,6 @@ public class MinecartInstance<T extends AbstractMinecartEntity> extends EntityIn
}
private ModelData getContents() {
BlockState blockstate = entity.getDisplayBlockState();
if (blockstate.getRenderShape() == BlockRenderType.INVISIBLE)
return null;