2021-05-31 02:05:41 +02:00
|
|
|
package com.jozufozu.flywheel.light;
|
2021-03-23 08:08:31 +01:00
|
|
|
|
2021-04-08 19:22:11 +02:00
|
|
|
import java.util.WeakHashMap;
|
|
|
|
import java.util.function.LongConsumer;
|
|
|
|
|
2021-05-11 20:02:43 +02:00
|
|
|
import com.jozufozu.flywheel.util.WeakHashSet;
|
2021-04-08 19:22:11 +02:00
|
|
|
|
2021-03-23 08:08:31 +01:00
|
|
|
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;
|
2021-03-24 14:54:24 +01:00
|
|
|
import net.minecraft.world.IBlockDisplayReader;
|
2021-03-23 08:08:31 +01:00
|
|
|
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;
|
|
|
|
|
|
|
|
public static LightUpdater getInstance() {
|
2021-06-30 21:43:54 +02:00
|
|
|
if (instance == null) instance = new LightUpdater();
|
2021-03-23 08:08:31 +01:00
|
|
|
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
2021-05-31 02:05:41 +02:00
|
|
|
private final Long2ObjectMap<WeakHashSet<ILightUpdateListener>> sections;
|
|
|
|
private final WeakHashMap<ILightUpdateListener, LongRBTreeSet> listenersToSections;
|
2021-03-23 08:08:31 +01:00
|
|
|
|
2021-05-31 02:05:41 +02:00
|
|
|
private final Long2ObjectMap<WeakHashSet<ILightUpdateListener>> chunks;
|
|
|
|
private final WeakHashMap<ILightUpdateListener, LongRBTreeSet> listenersToChunks;
|
2021-03-23 08:08:31 +01:00
|
|
|
|
|
|
|
public LightUpdater() {
|
|
|
|
sections = new Long2ObjectOpenHashMap<>();
|
|
|
|
listenersToSections = new WeakHashMap<>();
|
|
|
|
|
|
|
|
chunks = new Long2ObjectOpenHashMap<>();
|
|
|
|
listenersToChunks = new WeakHashMap<>();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a listener associated with the given {@link BlockPos}.
|
2021-04-30 02:19:08 +02:00
|
|
|
* <p>
|
2021-03-23 08:08:31 +01:00
|
|
|
* When a light update occurs in the chunk the position is contained in,
|
2021-05-31 02:05:41 +02:00
|
|
|
* {@link ILightUpdateListener#onLightUpdate} will be called.
|
2021-03-23 08:08:31 +01:00
|
|
|
*
|
2021-04-30 02:19:08 +02:00
|
|
|
* @param pos The position in the world that the listener cares about.
|
2021-03-23 08:08:31 +01:00
|
|
|
* @param listener The object that wants to receive light update notifications.
|
|
|
|
*/
|
2021-05-31 02:05:41 +02:00
|
|
|
public void startListening(BlockPos pos, ILightUpdateListener listener) {
|
2021-03-23 08:08:31 +01:00
|
|
|
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}.
|
2021-04-30 02:19:08 +02:00
|
|
|
* <p>
|
2021-03-23 08:08:31 +01:00
|
|
|
* When a light update occurs in any chunk spanning the given volume,
|
2021-05-31 02:05:41 +02:00
|
|
|
* {@link ILightUpdateListener#onLightUpdate} will be called.
|
2021-03-23 08:08:31 +01:00
|
|
|
*
|
2021-04-30 02:19:08 +02:00
|
|
|
* @param volume The volume in the world that the listener cares about.
|
2021-03-23 08:08:31 +01:00
|
|
|
* @param listener The object that wants to receive light update notifications.
|
|
|
|
*/
|
2021-05-31 02:05:41 +02:00
|
|
|
public void startListening(GridAlignedBB volume, ILightUpdateListener listener) {
|
2021-03-23 08:08:31 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-05-31 02:05:41 +02:00
|
|
|
* Dispatch light updates to all registered {@link ILightUpdateListener}s.
|
2021-03-23 08:08:31 +01:00
|
|
|
*
|
2021-04-30 02:19:08 +02:00
|
|
|
* @param world The world in which light was updated.
|
|
|
|
* @param type The type of light that changed.
|
2021-03-23 08:08:31 +01:00
|
|
|
* @param sectionPos A long representing the section position where light changed.
|
|
|
|
*/
|
2021-03-24 14:54:24 +01:00
|
|
|
public void onLightUpdate(IBlockDisplayReader world, LightType type, long sectionPos) {
|
2021-05-31 02:05:41 +02:00
|
|
|
WeakHashSet<ILightUpdateListener> set = sections.get(sectionPos);
|
2021-03-23 08:08:31 +01:00
|
|
|
|
|
|
|
if (set == null || set.isEmpty()) return;
|
|
|
|
|
2021-03-23 10:12:09 +01:00
|
|
|
GridAlignedBB chunkBox = GridAlignedBB.from(SectionPos.from(sectionPos));
|
2021-03-23 08:08:31 +01:00
|
|
|
|
2021-03-23 10:12:09 +01:00
|
|
|
set.removeIf(listener -> listener.onLightUpdate(world, type, chunkBox.copy()));
|
2021-03-23 08:08:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-05-31 02:05:41 +02:00
|
|
|
* Dispatch light updates to all registered {@link ILightUpdateListener}s
|
2021-03-23 08:08:31 +01:00
|
|
|
* when the server sends lighting data for an entire chunk.
|
|
|
|
*
|
|
|
|
* @param world The world in which light was updated.
|
|
|
|
*/
|
2021-03-24 14:54:24 +01:00
|
|
|
public void onLightPacket(IBlockDisplayReader world, int chunkX, int chunkZ) {
|
2021-03-23 08:08:31 +01:00
|
|
|
|
|
|
|
long chunkPos = SectionPos.asLong(chunkX, 0, chunkZ);
|
|
|
|
|
2021-05-31 02:05:41 +02:00
|
|
|
WeakHashSet<ILightUpdateListener> set = chunks.get(chunkPos);
|
2021-03-23 08:08:31 +01:00
|
|
|
|
|
|
|
if (set == null || set.isEmpty()) return;
|
|
|
|
|
2021-03-23 10:12:09 +01:00
|
|
|
set.removeIf(listener -> listener.onLightPacket(world, chunkX, chunkZ));
|
2021-03-23 08:08:31 +01:00
|
|
|
}
|
|
|
|
|
2021-05-31 02:05:41 +02:00
|
|
|
private LongRBTreeSet clearChunks(ILightUpdateListener listener) {
|
2021-03-23 08:08:31 +01:00
|
|
|
return clear(listener, listenersToChunks, chunks);
|
|
|
|
}
|
|
|
|
|
2021-05-31 02:05:41 +02:00
|
|
|
private LongRBTreeSet clearSections(ILightUpdateListener listener) {
|
2021-03-23 08:08:31 +01:00
|
|
|
return clear(listener, listenersToSections, sections);
|
|
|
|
}
|
|
|
|
|
2021-05-31 02:05:41 +02:00
|
|
|
private LongRBTreeSet clear(ILightUpdateListener listener, WeakHashMap<ILightUpdateListener, LongRBTreeSet> listeners, Long2ObjectMap<WeakHashSet<ILightUpdateListener>> lookup) {
|
2021-03-23 08:08:31 +01:00
|
|
|
LongRBTreeSet set = listeners.get(listener);
|
|
|
|
|
|
|
|
if (set == null) {
|
|
|
|
set = new LongRBTreeSet();
|
|
|
|
listeners.put(listener, set);
|
2021-06-30 22:03:02 +02:00
|
|
|
} else {
|
2021-03-23 08:08:31 +01:00
|
|
|
set.forEach((LongConsumer) l -> {
|
2021-05-31 02:05:41 +02:00
|
|
|
WeakHashSet<ILightUpdateListener> listeningSections = lookup.get(l);
|
2021-03-23 08:08:31 +01:00
|
|
|
|
|
|
|
if (listeningSections != null) listeningSections.remove(listener);
|
|
|
|
});
|
|
|
|
|
|
|
|
set.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
return set;
|
|
|
|
}
|
|
|
|
|
2021-05-31 02:05:41 +02:00
|
|
|
private void addToSection(long sectionPos, ILightUpdateListener listener) {
|
2021-03-23 08:08:31 +01:00
|
|
|
getOrCreate(sections, sectionPos).add(listener);
|
|
|
|
}
|
|
|
|
|
2021-05-31 02:05:41 +02:00
|
|
|
private void addToChunk(long chunkPos, ILightUpdateListener listener) {
|
2021-03-23 08:08:31 +01:00
|
|
|
getOrCreate(chunks, chunkPos).add(listener);
|
|
|
|
}
|
|
|
|
|
2021-05-31 02:05:41 +02:00
|
|
|
private WeakHashSet<ILightUpdateListener> getOrCreate(Long2ObjectMap<WeakHashSet<ILightUpdateListener>> sections, long chunkPos) {
|
|
|
|
WeakHashSet<ILightUpdateListener> set = sections.get(chunkPos);
|
2021-03-23 08:08:31 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|