Spicy light update listening api.
- Round 1, no profiling done yet, not everything uses it. - WeakHashSet could be useful elsewhere, too.
This commit is contained in:
parent
1310b88828
commit
20189a86fc
11 changed files with 400 additions and 27 deletions
|
@ -1,10 +1,15 @@
|
||||||
package com.simibubi.create.content.contraptions.components.structureMovement;
|
package com.simibubi.create.content.contraptions.components.structureMovement;
|
||||||
|
|
||||||
|
import net.minecraft.world.ILightReader;
|
||||||
|
import net.minecraft.world.LightType;
|
||||||
|
|
||||||
import com.simibubi.create.content.contraptions.components.structureMovement.render.RenderedContraption;
|
import com.simibubi.create.content.contraptions.components.structureMovement.render.RenderedContraption;
|
||||||
import com.simibubi.create.foundation.render.backend.light.GridAlignedBB;
|
import com.simibubi.create.foundation.render.backend.light.GridAlignedBB;
|
||||||
|
import com.simibubi.create.foundation.render.backend.light.LightUpdateListener;
|
||||||
|
import com.simibubi.create.foundation.render.backend.light.LightUpdater;
|
||||||
import com.simibubi.create.foundation.render.backend.light.LightVolume;
|
import com.simibubi.create.foundation.render.backend.light.LightVolume;
|
||||||
|
|
||||||
public abstract class ContraptionLighter<C extends Contraption> {
|
public abstract class ContraptionLighter<C extends Contraption> implements LightUpdateListener {
|
||||||
protected final C contraption;
|
protected final C contraption;
|
||||||
public final LightVolume lightVolume;
|
public final LightVolume lightVolume;
|
||||||
|
|
||||||
|
@ -21,14 +26,8 @@ public abstract class ContraptionLighter<C extends Contraption> {
|
||||||
|
|
||||||
lightVolume.initialize(contraption.entity.world);
|
lightVolume.initialize(contraption.entity.world);
|
||||||
scheduleRebuild = true;
|
scheduleRebuild = true;
|
||||||
}
|
|
||||||
|
|
||||||
protected GridAlignedBB contraptionBoundsToVolume(GridAlignedBB bounds) {
|
startListening();
|
||||||
bounds.grow(1); // so we have at least enough data on the edges to avoid artifacts and have smooth lighting
|
|
||||||
bounds.minY = Math.max(bounds.minY, 0);
|
|
||||||
bounds.maxY = Math.min(bounds.maxY, 255);
|
|
||||||
|
|
||||||
return bounds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tick(RenderedContraption owner) {
|
public void tick(RenderedContraption owner) {
|
||||||
|
@ -39,4 +38,26 @@ public abstract class ContraptionLighter<C extends Contraption> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract GridAlignedBB getContraptionBounds();
|
public abstract GridAlignedBB getContraptionBounds();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLightUpdate(ILightReader world, LightType type, GridAlignedBB changed) {
|
||||||
|
lightVolume.notifyLightUpdate(world, type, changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLightPacket(ILightReader world, int chunkX, int chunkZ) {
|
||||||
|
lightVolume.notifyLightPacket(world, chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void startListening() {
|
||||||
|
LightUpdater.getInstance().startListening(bounds, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected GridAlignedBB contraptionBoundsToVolume(GridAlignedBB bounds) {
|
||||||
|
bounds.grow(1); // so we have at least enough data on the edges to avoid artifacts and have smooth lighting
|
||||||
|
bounds.minY = Math.max(bounds.minY, 0);
|
||||||
|
bounds.maxY = Math.min(bounds.maxY, 255);
|
||||||
|
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ public class NonStationaryLighter<C extends Contraption> extends ContraptionLigh
|
||||||
if (!contraptionBounds.sameAs(bounds)) {
|
if (!contraptionBounds.sameAs(bounds)) {
|
||||||
lightVolume.move(contraption.entity.world, contraptionBoundsToVolume(contraptionBounds));
|
lightVolume.move(contraption.entity.world, contraptionBoundsToVolume(contraptionBounds));
|
||||||
bounds = contraptionBounds;
|
bounds = contraptionBounds;
|
||||||
|
|
||||||
|
startListening();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,6 @@ public abstract class ActorInstance {
|
||||||
public void beginFrame() { }
|
public void beginFrame() { }
|
||||||
|
|
||||||
protected int localBlockLight() {
|
protected int localBlockLight() {
|
||||||
return modelManager.contraption.renderWorld.getLightLevel(LightType.BLOCK, context.localPos);
|
return modelManager.getContraption().renderWorld.getLightLevel(LightType.BLOCK, context.localPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,16 +20,17 @@ import net.minecraft.world.gen.feature.template.Template;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class ContraptionKineticRenderer extends InstancedTileRenderer<ContraptionProgram> {
|
public class ContraptionKineticRenderer extends InstancedTileRenderer<ContraptionProgram> {
|
||||||
|
|
||||||
protected ArrayList<com.simibubi.create.content.contraptions.components.structureMovement.render.ActorInstance> actors = new ArrayList<>();
|
protected ArrayList<com.simibubi.create.content.contraptions.components.structureMovement.render.ActorInstance> actors = new ArrayList<>();
|
||||||
|
|
||||||
public final RenderedContraption contraption;
|
private final WeakReference<RenderedContraption> contraption;
|
||||||
|
|
||||||
ContraptionKineticRenderer(RenderedContraption contraption) {
|
ContraptionKineticRenderer(RenderedContraption contraption) {
|
||||||
this.contraption = contraption;
|
this.contraption = new WeakReference<>(contraption);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -77,6 +78,10 @@ public class ContraptionKineticRenderer extends InstancedTileRenderer<Contraptio
|
||||||
return getMaterial(KineticRenderMaterials.ACTORS);
|
return getMaterial(KineticRenderMaterials.ACTORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RenderedContraption getContraption() {
|
||||||
|
return contraption.get();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlockPos getOriginCoordinate() {
|
public BlockPos getOriginCoordinate() {
|
||||||
return BlockPos.ZERO;
|
return BlockPos.ZERO;
|
||||||
|
|
|
@ -54,12 +54,6 @@ public class ContraptionRenderDispatcher {
|
||||||
public static final Compartment<Pair<Contraption, Integer>> CONTRAPTION = new Compartment<>();
|
public static final Compartment<Pair<Contraption, Integer>> CONTRAPTION = new Compartment<>();
|
||||||
protected static PlacementSimulationWorld renderWorld;
|
protected static PlacementSimulationWorld renderWorld;
|
||||||
|
|
||||||
public static void notifyLightUpdate(ILightReader world, LightType type, SectionPos pos) {
|
|
||||||
for (RenderedContraption renderer : renderers.values()) {
|
|
||||||
renderer.getLighter().lightVolume.notifyLightUpdate(world, type, pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void notifyLightPacket(ILightReader world, int chunkX, int chunkZ) {
|
public static void notifyLightPacket(ILightReader world, int chunkX, int chunkZ) {
|
||||||
for (RenderedContraption renderer : renderers.values()) {
|
for (RenderedContraption renderer : renderers.values()) {
|
||||||
renderer.getLighter().lightVolume.notifyLightPacket(world, chunkX, chunkZ);
|
renderer.getLighter().lightVolume.notifyLightPacket(world, chunkX, chunkZ);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionRenderDispatcher;
|
import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionRenderDispatcher;
|
||||||
import com.simibubi.create.foundation.render.backend.light.ILightListener;
|
import com.simibubi.create.foundation.render.backend.light.ILightListener;
|
||||||
|
import com.simibubi.create.foundation.render.backend.light.LightUpdater;
|
||||||
|
|
||||||
import net.minecraft.client.multiplayer.ClientChunkProvider;
|
import net.minecraft.client.multiplayer.ClientChunkProvider;
|
||||||
import net.minecraft.util.math.SectionPos;
|
import net.minecraft.util.math.SectionPos;
|
||||||
|
@ -54,6 +55,6 @@ public abstract class LightUpdateMixin extends AbstractChunkProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ContraptionRenderDispatcher.notifyLightUpdate(world, type, pos);
|
LightUpdater.getInstance().onLightUpdate(world, type, pos.asLong());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
package com.simibubi.create.foundation.mixin;
|
package com.simibubi.create.foundation.mixin;
|
||||||
|
|
||||||
|
import com.simibubi.create.CreateClient;
|
||||||
import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionRenderDispatcher;
|
import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionRenderDispatcher;
|
||||||
import com.simibubi.create.foundation.render.backend.RenderWork;
|
import com.simibubi.create.foundation.render.backend.RenderWork;
|
||||||
import com.simibubi.create.foundation.render.backend.light.ILightListener;
|
import com.simibubi.create.foundation.render.backend.light.ILightListener;
|
||||||
|
import com.simibubi.create.foundation.render.backend.light.LightUpdater;
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.network.play.ClientPlayNetHandler;
|
import net.minecraft.client.network.play.ClientPlayNetHandler;
|
||||||
import net.minecraft.client.world.ClientWorld;
|
import net.minecraft.client.world.ClientWorld;
|
||||||
import net.minecraft.network.play.server.SUpdateLightPacket;
|
import net.minecraft.network.play.server.SUpdateLightPacket;
|
||||||
|
import net.minecraft.util.math.SectionPos;
|
||||||
import net.minecraft.world.chunk.Chunk;
|
import net.minecraft.world.chunk.Chunk;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
@ -30,14 +36,16 @@ public class NetworkLightUpdateMixin {
|
||||||
|
|
||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
chunk.getTileEntityMap()
|
chunk.getTileEntityMap()
|
||||||
.values()
|
.values()
|
||||||
.stream()
|
.forEach(tile -> {
|
||||||
.filter(tile -> tile instanceof ILightListener)
|
CreateClient.kineticRenderer.get(world).onLightUpdate(tile);
|
||||||
.map(tile -> (ILightListener) tile)
|
|
||||||
.forEach(ILightListener::onChunkLightUpdate);
|
if (tile instanceof ILightListener)
|
||||||
|
((ILightListener) tile).onChunkLightUpdate();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ContraptionRenderDispatcher.notifyLightPacket(world, chunkX, chunkZ);
|
LightUpdater.getInstance().onLightPacket(world, chunkX, chunkZ);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.simibubi.create.foundation.render.backend.light;
|
||||||
|
|
||||||
|
import net.minecraft.world.ILightReader;
|
||||||
|
import net.minecraft.world.LightType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anything can implement this, implementors should call {@link LightUpdater#startListening}
|
||||||
|
* appropriately to make sure they get the updates they want.
|
||||||
|
*/
|
||||||
|
public interface LightUpdateListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a light updates in a chunk the implementor cares about.
|
||||||
|
*/
|
||||||
|
void onLightUpdate(ILightReader world, LightType type, GridAlignedBB changed);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the server sends light data to the client.
|
||||||
|
*/
|
||||||
|
default void onLightPacket(ILightReader world, int chunkX, int chunkZ) {
|
||||||
|
GridAlignedBB changedVolume = GridAlignedBB.fromChunk(chunkX, chunkZ);
|
||||||
|
|
||||||
|
onLightUpdate(world, LightType.BLOCK, changedVolume);
|
||||||
|
onLightUpdate(world, LightType.SKY, changedVolume);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
package com.simibubi.create.foundation.render.backend.light;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongRBTreeSet;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.SectionPos;
|
||||||
|
import net.minecraft.world.ILightReader;
|
||||||
|
import net.minecraft.world.LightType;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.LongConsumer;
|
||||||
|
import com.simibubi.create.foundation.utility.WeakHashSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public static LightUpdater getInstance() {
|
||||||
|
if (instance == null)
|
||||||
|
instance = new LightUpdater();
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Long2ObjectMap<WeakHashSet<LightUpdateListener>> sections;
|
||||||
|
private final WeakHashMap<LightUpdateListener, LongRBTreeSet> listenersToSections;
|
||||||
|
|
||||||
|
private final Long2ObjectMap<WeakHashSet<LightUpdateListener>> chunks;
|
||||||
|
private final WeakHashMap<LightUpdateListener, LongRBTreeSet> listenersToChunks;
|
||||||
|
|
||||||
|
public LightUpdater() {
|
||||||
|
sections = new Long2ObjectOpenHashMap<>();
|
||||||
|
listenersToSections = new WeakHashMap<>();
|
||||||
|
|
||||||
|
chunks = new Long2ObjectOpenHashMap<>();
|
||||||
|
listenersToChunks = new WeakHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener associated with the given {@link BlockPos}.
|
||||||
|
*
|
||||||
|
* When a light update occurs in the chunk the position is contained in,
|
||||||
|
* {@link LightUpdateListener#onLightUpdate} will be called.
|
||||||
|
*
|
||||||
|
* @param pos The position in the world that the listener cares about.
|
||||||
|
* @param listener The object that wants to receive light update notifications.
|
||||||
|
*/
|
||||||
|
public void startListening(BlockPos pos, LightUpdateListener listener) {
|
||||||
|
LongRBTreeSet sections = clearSections(listener);
|
||||||
|
LongRBTreeSet chunks = clearChunks(listener);
|
||||||
|
|
||||||
|
long sectionPos = worldToSection(pos);
|
||||||
|
addToSection(sectionPos, listener);
|
||||||
|
sections.add(sectionPos);
|
||||||
|
|
||||||
|
long chunkPos = sectionToChunk(sectionPos);
|
||||||
|
addToChunk(chunkPos, listener);
|
||||||
|
chunks.add(chunkPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener associated with the given {@link GridAlignedBB}.
|
||||||
|
*
|
||||||
|
* When a light update occurs in any chunk spanning the given volume,
|
||||||
|
* {@link LightUpdateListener#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, LightUpdateListener listener) {
|
||||||
|
LongRBTreeSet sections = clearSections(listener);
|
||||||
|
LongRBTreeSet chunks = clearSections(listener);
|
||||||
|
|
||||||
|
int minX = SectionPos.toChunk(volume.minX);
|
||||||
|
int minY = SectionPos.toChunk(volume.minY);
|
||||||
|
int minZ = SectionPos.toChunk(volume.minZ);
|
||||||
|
int maxX = SectionPos.toChunk(volume.maxX);
|
||||||
|
int maxY = SectionPos.toChunk(volume.maxY);
|
||||||
|
int maxZ = SectionPos.toChunk(volume.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);
|
||||||
|
addToSection(sectionPos, listener);
|
||||||
|
sections.add(sectionPos);
|
||||||
|
}
|
||||||
|
long chunkPos = SectionPos.asLong(x, 0, z);
|
||||||
|
addToChunk(chunkPos, listener);
|
||||||
|
chunks.add(chunkPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch light updates to all registered {@link LightUpdateListener}s.
|
||||||
|
*
|
||||||
|
* @param world The world in which light was updated.
|
||||||
|
* @param type The type of light that changed.
|
||||||
|
* @param sectionPos A long representing the section position where light changed.
|
||||||
|
*/
|
||||||
|
public void onLightUpdate(ILightReader world, LightType type, long sectionPos) {
|
||||||
|
WeakHashSet<LightUpdateListener> set = sections.get(sectionPos);
|
||||||
|
|
||||||
|
if (set == null || set.isEmpty()) return;
|
||||||
|
|
||||||
|
GridAlignedBB chunkBox = GridAlignedBB.fromSection(SectionPos.from(sectionPos));
|
||||||
|
|
||||||
|
for (LightUpdateListener listener : set) {
|
||||||
|
listener.onLightUpdate(world, type, chunkBox.copy());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch light updates to all registered {@link LightUpdateListener}s
|
||||||
|
* when the server sends lighting data for an entire chunk.
|
||||||
|
*
|
||||||
|
* @param world The world in which light was updated.
|
||||||
|
*/
|
||||||
|
public void onLightPacket(ILightReader world, int chunkX, int chunkZ) {
|
||||||
|
|
||||||
|
long chunkPos = SectionPos.asLong(chunkX, 0, chunkZ);
|
||||||
|
|
||||||
|
WeakHashSet<LightUpdateListener> set = chunks.get(chunkPos);
|
||||||
|
|
||||||
|
if (set == null || set.isEmpty()) return;
|
||||||
|
|
||||||
|
for (LightUpdateListener listener : set) {
|
||||||
|
listener.onLightPacket(world, chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private LongRBTreeSet clearChunks(LightUpdateListener listener) {
|
||||||
|
return clear(listener, listenersToChunks, chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LongRBTreeSet clearSections(LightUpdateListener listener) {
|
||||||
|
return clear(listener, listenersToSections, sections);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LongRBTreeSet clear(LightUpdateListener listener, WeakHashMap<LightUpdateListener, LongRBTreeSet> listeners, Long2ObjectMap<WeakHashSet<LightUpdateListener>> lookup) {
|
||||||
|
LongRBTreeSet set = listeners.get(listener);
|
||||||
|
|
||||||
|
if (set == null) {
|
||||||
|
set = new LongRBTreeSet();
|
||||||
|
listeners.put(listener, set);
|
||||||
|
} else {
|
||||||
|
set.forEach((LongConsumer) l -> {
|
||||||
|
WeakHashSet<LightUpdateListener> listeningSections = lookup.get(l);
|
||||||
|
|
||||||
|
if (listeningSections != null) listeningSections.remove(listener);
|
||||||
|
});
|
||||||
|
|
||||||
|
set.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToSection(long sectionPos, LightUpdateListener listener) {
|
||||||
|
getOrCreate(sections, sectionPos).add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToChunk(long chunkPos, LightUpdateListener listener) {
|
||||||
|
getOrCreate(chunks, chunkPos).add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WeakHashSet<LightUpdateListener> getOrCreate(Long2ObjectMap<WeakHashSet<LightUpdateListener>> sections, long chunkPos) {
|
||||||
|
WeakHashSet<LightUpdateListener> set = sections.get(chunkPos);
|
||||||
|
|
||||||
|
if (set == null) {
|
||||||
|
set = new WeakHashSet<>();
|
||||||
|
sections.put(chunkPos, set);
|
||||||
|
}
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long worldToSection(BlockPos pos) {
|
||||||
|
return SectionPos.asLong(pos.getX(), pos.getY(), pos.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long sectionToChunk(long sectionPos) {
|
||||||
|
return sectionPos & 0xFFFFFFFFFFF_00000L;
|
||||||
|
}
|
||||||
|
}
|
|
@ -123,17 +123,21 @@ public class LightVolume {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyLightUpdate(ILightReader world, LightType type, SectionPos location) {
|
public void notifyLightUpdate(ILightReader world, LightType type, GridAlignedBB changedVolume) {
|
||||||
GridAlignedBB changedVolume = GridAlignedBB.fromSection(location);
|
if (removed)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!changedVolume.intersects(sampleVolume))
|
if (!changedVolume.intersects(sampleVolume))
|
||||||
return;
|
return;
|
||||||
changedVolume.intersectAssign(sampleVolume); // compute the region contained by us that has dirty lighting data.
|
changedVolume = changedVolume.intersect(sampleVolume); // compute the region contained by us that has dirty lighting data.
|
||||||
|
|
||||||
if (type == LightType.BLOCK) copyBlock(world, changedVolume);
|
if (type == LightType.BLOCK) copyBlock(world, changedVolume);
|
||||||
else if (type == LightType.SKY) copySky(world, changedVolume);
|
else if (type == LightType.SKY) copySky(world, changedVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyLightPacket(ILightReader world, int chunkX, int chunkZ) {
|
public void notifyLightPacket(ILightReader world, int chunkX, int chunkZ) {
|
||||||
|
if (removed) return;
|
||||||
|
|
||||||
GridAlignedBB changedVolume = GridAlignedBB.fromChunk(chunkX, chunkZ);
|
GridAlignedBB changedVolume = GridAlignedBB.fromChunk(chunkX, chunkZ);
|
||||||
if (!changedVolume.intersects(sampleVolume))
|
if (!changedVolume.intersects(sampleVolume))
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
package com.simibubi.create.foundation.utility;
|
||||||
|
|
||||||
|
import net.minecraft.util.Unit;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class WeakHashSet<T> extends AbstractSet<T> {
|
||||||
|
|
||||||
|
WeakHashMap<T, Unit> map;
|
||||||
|
|
||||||
|
public WeakHashSet() {
|
||||||
|
map = new WeakHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new set containing the elements in the specified
|
||||||
|
* collection. The <tt>HashMap</tt> is created with default load factor
|
||||||
|
* (0.75) and an initial capacity sufficient to contain the elements in
|
||||||
|
* the specified collection.
|
||||||
|
*
|
||||||
|
* @param c the collection whose elements are to be placed into this set
|
||||||
|
* @throws NullPointerException if the specified collection is null
|
||||||
|
*/
|
||||||
|
public WeakHashSet(Collection<? extends T> c) {
|
||||||
|
map = new WeakHashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
|
||||||
|
addAll(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
|
||||||
|
* the specified initial capacity and the specified load factor.
|
||||||
|
*
|
||||||
|
* @param initialCapacity the initial capacity of the hash map
|
||||||
|
* @param loadFactor the load factor of the hash map
|
||||||
|
* @throws IllegalArgumentException if the initial capacity is less
|
||||||
|
* than zero, or if the load factor is nonpositive
|
||||||
|
*/
|
||||||
|
public WeakHashSet(int initialCapacity, float loadFactor) {
|
||||||
|
map = new WeakHashMap<>(initialCapacity, loadFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
|
||||||
|
* the specified initial capacity and default load factor (0.75).
|
||||||
|
*
|
||||||
|
* @param initialCapacity the initial capacity of the hash table
|
||||||
|
* @throws IllegalArgumentException if the initial capacity is less
|
||||||
|
* than zero
|
||||||
|
*/
|
||||||
|
public WeakHashSet(int initialCapacity) {
|
||||||
|
map = new WeakHashMap<>(initialCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return map.keySet().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return map.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(T t) {
|
||||||
|
return map.put(t, Unit.INSTANCE) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object o) {
|
||||||
|
return map.remove((T) o) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return map.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
return map.containsKey((T) o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
return map.keySet().toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsAll(Collection<?> c) {
|
||||||
|
return c.stream().allMatch(map::containsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(Collection<? extends T> c) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean retainAll(Collection<?> c) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeAll(Collection<?> c) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue