From c6677ec8fdee20e286fab309332a961270993f8c Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Fri, 25 Dec 2020 18:12:14 +0300 Subject: [PATCH] Added ChunkMap and ChunkSet --- .../progressia/common/world/WorldData.java | 34 ++- .../common/world/generic/ChunkMap.java | 113 ++++++++++ .../common/world/generic/ChunkSet.java | 212 ++++++++++++++++++ .../world/generic/LongBasedChunkMap.java | 89 ++++++++ .../world/generic/LongBasedChunkSet.java | 152 +++++++++++++ .../progressia/server/ChunkLoadManager.java | 27 +-- 6 files changed, 590 insertions(+), 37 deletions(-) create mode 100644 src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMap.java create mode 100644 src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSet.java create mode 100644 src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkMap.java create mode 100644 src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkSet.java diff --git a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java index 1924487..01b7c1b 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java @@ -25,12 +25,13 @@ import glm.vec._3.i.Vec3i; import gnu.trove.impl.sync.TSynchronizedLongObjectMap; import gnu.trove.map.TLongObjectMap; import gnu.trove.map.hash.TLongObjectHashMap; -import gnu.trove.set.TLongSet; import ru.windcorp.progressia.common.collision.CollisionModel; -import ru.windcorp.progressia.common.util.CoordinatePacker; import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.generic.ChunkMap; +import ru.windcorp.progressia.common.world.generic.ChunkSet; import ru.windcorp.progressia.common.world.generic.GenericWorld; +import ru.windcorp.progressia.common.world.generic.LongBasedChunkMap; import ru.windcorp.progressia.common.world.tile.TileData; import ru.windcorp.progressia.common.world.tile.TileDataStack; import ru.windcorp.progressia.test.TestContent; @@ -44,11 +45,12 @@ implements GenericWorld< EntityData >{ - private final TLongObjectMap chunksByPos = - new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this); + private final ChunkMap chunksByPos = new LongBasedChunkMap<>( + new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this) + ); private final Collection chunks = - Collections.unmodifiableCollection(chunksByPos.valueCollection()); + Collections.unmodifiableCollection(chunksByPos.values()); private final TLongObjectMap entitiesById = new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this); @@ -67,7 +69,7 @@ implements GenericWorld< @Override public ChunkData getChunk(Vec3i pos) { - return chunksByPos.get(CoordinatePacker.pack3IntsIntoLong(pos)); + return chunksByPos.get(pos); } @Override @@ -75,6 +77,10 @@ implements GenericWorld< return chunks; } + public ChunkSet getLoadedChunks() { + return chunksByPos.keys(); + } + @Override public Collection getEntities() { return entities; @@ -102,9 +108,7 @@ implements GenericWorld< public synchronized void addChunk(ChunkData chunk) { addChunkListeners(chunk); - long key = getChunkKey(chunk); - - ChunkData previous = chunksByPos.get(key); + ChunkData previous = chunksByPos.get(chunk); if (previous != null) { throw new IllegalArgumentException(String.format( "Chunk at (%d; %d; %d) already exists", @@ -112,7 +116,7 @@ implements GenericWorld< )); } - chunksByPos.put(key, chunk); + chunksByPos.put(chunk, chunk); chunk.forEachEntity(entity -> entitiesById.put(entity.getEntityId(), entity) @@ -130,11 +134,7 @@ implements GenericWorld< entitiesById.remove(entity.getEntityId()) ); - chunksByPos.remove(getChunkKey(chunk)); - } - - private static long getChunkKey(ChunkData chunk) { - return CoordinatePacker.pack3IntsIntoLong(chunk.getPosition()); + chunksByPos.remove(chunk); } public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) { @@ -149,10 +149,6 @@ implements GenericWorld< chunk.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, notify); } - public TLongSet getChunkKeys() { - return chunksByPos.keySet(); - } - public EntityData getEntity(long entityId) { return entitiesById.get(entityId); } diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMap.java b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMap.java new file mode 100644 index 0000000..ab83f9a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMap.java @@ -0,0 +1,113 @@ +package ru.windcorp.progressia.common.world.generic; + +import java.util.Collection; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import glm.vec._3.i.Vec3i; + +public interface ChunkMap { + + /* + * Size + */ + + int size(); + + default boolean isEmpty() { + return size() == 0; + } + + /* + * Basic operations + */ + + boolean containsKey(Vec3i pos); + + V get(Vec3i pos); + V put(Vec3i pos, V obj); + V remove(Vec3i pos); + + default boolean containsValue(V value) { + return values().contains(value); + } + + default V getOrDefault(Vec3i pos, V def) { + return containsKey(pos) ? def : get(pos); + } + + default V compute(Vec3i pos, BiFunction remappingFunction) { + V newValue = remappingFunction.apply(pos, get(pos)); + + if (newValue == null) { + remove(pos); + } else { + put(pos, newValue); + } + + return newValue; + } + + // TODO implement ALL default methods from Map + + /* + * Basic operation wrappers + */ + + // TODO implement (int, int, int) and GenericChunk versions of all of the above + + default boolean containsChunk(GenericChunk chunk) { + return containsKey(chunk.getPosition()); + } + + default V get(GenericChunk chunk) { + return get(chunk.getPosition()); + } + + default V put(GenericChunk chunk, V obj) { + return put(chunk.getPosition(), obj); + } + + default V remove(GenericChunk chunk) { + return remove(chunk.getPosition()); + } + + default V getOrDefault(GenericChunk chunk, V def) { + return containsChunk(chunk) ? def : get(chunk); + } + + default > V compute(C chunk, BiFunction remappingFunction) { + V newValue = remappingFunction.apply(chunk, get(chunk)); + + if (newValue == null) { + remove(chunk); + } else { + put(chunk, newValue); + } + + return newValue; + } + + /* + * Views + */ + + Collection values(); + ChunkSet keys(); + + /* + * Bulk operations + */ + + boolean removeIf(BiPredicate condition); + void forEach(BiConsumer action); + + default > void forEachIn(GenericWorld world, BiConsumer action) { + forEach((pos, value) -> { + C chunk = world.getChunk(pos); + if (chunk == null) return; + action.accept(chunk, value); + }); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSet.java b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSet.java new file mode 100644 index 0000000..84013ec --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSet.java @@ -0,0 +1,212 @@ +package ru.windcorp.progressia.common.world.generic; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import glm.vec._3.i.Vec3i; +import gnu.trove.set.hash.TLongHashSet; +import ru.windcorp.progressia.common.util.Vectors; + +public interface ChunkSet extends Iterable { + + /* + * Size + */ + + int size(); + + default boolean isEmpty() { + return size() == 0; + } + + /* + * Basic operations + */ + + boolean contains(Vec3i pos); + boolean add(Vec3i pos); + boolean remove(Vec3i pos); + + /* + * Basic operation wrappers + */ + + default boolean contains(int x, int y, int z) { + Vec3i v = Vectors.grab3i(); + boolean result = contains(v); + Vectors.release(v); + return result; + } + + default boolean add(int x, int y, int z) { + Vec3i v = Vectors.grab3i(); + boolean result = add(v); + Vectors.release(v); + return result; + } + + default boolean remove(int x, int y, int z) { + Vec3i v = Vectors.grab3i(); + boolean result = remove(v); + Vectors.release(v); + return result; + } + + default boolean contains(GenericChunk chunk) { + return contains(chunk.getPosition()); + } + + default boolean add(GenericChunk chunk) { + return add(chunk.getPosition()); + } + + default boolean remove(GenericChunk chunk) { + return remove(chunk.getPosition()); + } + + default > void forEachIn(GenericWorld world, Consumer action) { + forEach(position -> { + C chunk = world.getChunk(position); + if (chunk == null) return; + action.accept(chunk); + }); + } + + /* + * Bulk operations on ChunkSets + */ + + boolean containsAll(ChunkSet other); + boolean containsAny(ChunkSet other); + + void addAll(ChunkSet other); + void removeAll(ChunkSet other); + void retainAll(ChunkSet other); + + /* + * Other bulk operations + */ + + void clear(); + + default boolean containsAll(Iterable other) { + boolean[] hasMissing = new boolean[] { false }; + + other.forEach(v -> { + if (!contains(v)) { + hasMissing[0] = true; + } + }); + + return hasMissing[0]; + } + + default boolean containsAny(Iterable other) { + boolean[] hasPresent = new boolean[] { false }; + + other.forEach(v -> { + if (contains(v)) { + hasPresent[0] = true; + } + }); + + return hasPresent[0]; + } + + default void addAll(Iterable other) { + other.forEach(this::add); + } + + default void removeAll(Iterable other) { + other.forEach(this::remove); + } + + default void retainAll(Iterable other) { + if (other instanceof ChunkSet) { + // We shouldn't invoke retainAll(ChunkSet) because we could be the fallback for it + removeIf(v -> !((ChunkSet) other).contains(v)); + return; + } + + final int threshold = 16; // Maximum size of other at which point creating a Set becomes faster than iterating + + Collection collection = null; + int otherSize = -1; + + if (other instanceof Set) { + collection = (Set) other; + } else if (other instanceof Collection) { + Collection otherAsCollection = ((Collection) other); + otherSize = otherAsCollection.size(); + + if (otherSize < threshold) { + collection = otherAsCollection; + } + } + + if (collection != null) { + final Collection c = collection; + removeIf(v -> !c.contains(v)); + return; + } + + if (otherSize < 0) { + otherSize = gnu.trove.impl.Constants.DEFAULT_CAPACITY; + } + + retainAll(new LongBasedChunkSet(new TLongHashSet(otherSize), other)); + return; + } + + default void removeIf(Predicate condition) { + for (Iterator it = iterator(); it.hasNext();) { + if (condition.test(it.next())) { + it.remove(); + } + } + } + + default void retainIf(Predicate condition) { + for (Iterator it = iterator(); it.hasNext();) { + if (!condition.test(it.next())) { + it.remove(); + } + } + } + + default boolean containsAllChunks(Iterable> chunks) { + boolean[] hasMissing = new boolean[] { false }; + + chunks.forEach(c -> { + if (!contains(c.getPosition())) { + hasMissing[0] = true; + } + }); + + return hasMissing[0]; + } + + default boolean containsAnyChunks(Iterable> chunks) { + boolean[] hasPresent = new boolean[] { false }; + + chunks.forEach(c -> { + if (contains(c.getPosition())) { + hasPresent[0] = true; + } + }); + + return hasPresent[0]; + } + + default void addAllChunks(Iterable> chunks) { + chunks.forEach(this::add); + } + + default void removeAllChunks(Iterable> chunks) { + chunks.forEach(this::remove); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkMap.java b/src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkMap.java new file mode 100644 index 0000000..80c7e17 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkMap.java @@ -0,0 +1,89 @@ +package ru.windcorp.progressia.common.world.generic; + +import java.util.Collection; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; + +import glm.vec._3.i.Vec3i; +import gnu.trove.map.TLongObjectMap; +import ru.windcorp.progressia.common.util.CoordinatePacker; +import ru.windcorp.progressia.common.util.Vectors; + +public class LongBasedChunkMap implements ChunkMap { + + protected final TLongObjectMap impl; + private final ChunkSet keys; + + public LongBasedChunkMap(TLongObjectMap impl) { + this.impl = impl; + this.keys = new LongBasedChunkSet(impl.keySet()); + } + + private static long getKey(Vec3i v) { + return CoordinatePacker.pack3IntsIntoLong(v); + } + + private static Vec3i getVector(long key, Vec3i output) { + return CoordinatePacker.unpack3IntsFromLong(key, output); + } + + @Override + public int size() { + return impl.size(); + } + + @Override + public boolean containsKey(Vec3i pos) { + return impl.containsKey(getKey(pos)); + } + + @Override + public V get(Vec3i pos) { + return impl.get(getKey(pos)); + } + + @Override + public V put(Vec3i pos, V obj) { + return impl.put(getKey(pos), obj); + } + + @Override + public V remove(Vec3i pos) { + return impl.remove(getKey(pos)); + } + + @Override + public Collection values() { + return impl.valueCollection(); + } + + @Override + public ChunkSet keys() { + return keys; + } + + @Override + public boolean removeIf(BiPredicate condition) { + Vec3i v = Vectors.grab3i(); + + boolean result = impl.retainEntries((key, value) -> { + return !condition.test(getVector(key, v), value); + }); + + Vectors.release(v); + return result; + } + + @Override + public void forEach(BiConsumer action) { + Vec3i v = Vectors.grab3i(); + + impl.forEachEntry((key, value) -> { + action.accept(getVector(key, v), value); + return true; + }); + + Vectors.release(v); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkSet.java b/src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkSet.java new file mode 100644 index 0000000..ad4c0cb --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkSet.java @@ -0,0 +1,152 @@ +package ru.windcorp.progressia.common.world.generic; + +import java.util.Iterator; +import java.util.function.Consumer; + +import glm.vec._3.i.Vec3i; +import gnu.trove.iterator.TLongIterator; +import gnu.trove.set.TLongSet; +import ru.windcorp.progressia.common.util.CoordinatePacker; +import ru.windcorp.progressia.common.util.Vectors; + +public class LongBasedChunkSet implements ChunkSet { + + protected final TLongSet impl; + + public LongBasedChunkSet(TLongSet impl) { + this.impl = impl; + } + + public LongBasedChunkSet(TLongSet impl, ChunkSet copyFrom) { + this(impl); + addAll(copyFrom); + } + + public LongBasedChunkSet(TLongSet impl, Iterable copyFrom) { + this(impl); + addAll(copyFrom); + } + + public LongBasedChunkSet(TLongSet impl, GenericWorld copyFrom) { + this(impl); + addAllChunks(copyFrom.getChunks()); + } + + private static long getKey(Vec3i v) { + return CoordinatePacker.pack3IntsIntoLong(v); + } + + private static Vec3i getVector(long key, Vec3i output) { + return CoordinatePacker.unpack3IntsFromLong(key, output); + } + + @Override + public Iterator iterator() { + return new IteratorImpl(); + } + + @Override + public int size() { + return impl.size(); + } + + @Override + public boolean contains(Vec3i pos) { + return impl.contains(getKey(pos)); + } + + @Override + public boolean add(Vec3i pos) { + return impl.add(getKey(pos)); + } + + @Override + public boolean remove(Vec3i pos) { + return impl.remove(getKey(pos)); + } + + @Override + public boolean containsAll(ChunkSet other) { + if (other instanceof LongBasedChunkSet) { + return impl.containsAll(((LongBasedChunkSet) other).impl); + } + + return ChunkSet.super.containsAll((Iterable) other); + } + + @Override + public boolean containsAny(ChunkSet other) { + return ChunkSet.super.containsAny((Iterable) other); + } + + @Override + public void addAll(ChunkSet other) { + if (other instanceof LongBasedChunkSet) { + impl.addAll(((LongBasedChunkSet) other).impl); + return; + } + + ChunkSet.super.addAll((Iterable) other); + } + + @Override + public void removeAll(ChunkSet other) { + if (other instanceof LongBasedChunkSet) { + impl.removeAll(((LongBasedChunkSet) other).impl); + return; + } + + ChunkSet.super.removeAll((Iterable) other); + } + + @Override + public void retainAll(ChunkSet other) { + if (other instanceof LongBasedChunkSet) { + impl.retainAll(((LongBasedChunkSet) other).impl); + return; + } + + ChunkSet.super.retainAll((Iterable) other); + } + + @Override + public void clear() { + impl.clear(); + } + + @Override + public void forEach(Consumer action) { + Vec3i v = Vectors.grab3i(); + + impl.forEach(key -> { + getVector(key, v); + action.accept(v); + return true; + }); + + Vectors.release(v); + } + + private class IteratorImpl implements Iterator { + + private final Vec3i vector = new Vec3i(); + private final TLongIterator parent = LongBasedChunkSet.this.impl.iterator(); + + @Override + public boolean hasNext() { + return parent.hasNext(); + } + + @Override + public Vec3i next() { + return getVector(parent.next(), vector); + } + + @Override + public void remove() { + parent.remove(); + } + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/ChunkLoadManager.java b/src/main/java/ru/windcorp/progressia/server/ChunkLoadManager.java index 99493bc..1b2c539 100644 --- a/src/main/java/ru/windcorp/progressia/server/ChunkLoadManager.java +++ b/src/main/java/ru/windcorp/progressia/server/ChunkLoadManager.java @@ -5,10 +5,10 @@ import java.util.Collection; import java.util.Collections; import glm.vec._3.i.Vec3i; -import gnu.trove.set.TLongSet; import gnu.trove.set.hash.TLongHashSet; -import ru.windcorp.progressia.common.util.CoordinatePacker; import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.generic.ChunkSet; +import ru.windcorp.progressia.common.world.generic.LongBasedChunkSet; import ru.windcorp.progressia.test.TestContent; public class ChunkLoadManager { @@ -18,9 +18,9 @@ public class ChunkLoadManager { private final Collection> allChunkLoaders = Collections.synchronizedCollection(new ArrayList<>()); - private final TLongSet requested = new TLongHashSet(); - private final TLongSet toLoad = new TLongHashSet(); - private final TLongSet toUnload = new TLongHashSet(); + private final ChunkSet requested = new LongBasedChunkSet(new TLongHashSet()); + private final ChunkSet toLoad = new LongBasedChunkSet(new TLongHashSet()); + private final ChunkSet toUnload = new LongBasedChunkSet(new TLongHashSet()); public ChunkLoadManager(Server server) { this.server = server; @@ -42,11 +42,11 @@ public class ChunkLoadManager { } private void gatherRequests(ChunkLoader loader) { - loader.requestChunksToLoad(v -> requested.add(CoordinatePacker.pack3IntsIntoLong(v))); + loader.requestChunksToLoad(requested::add); } private void updateQueues() { - TLongSet loaded = getServer().getWorld().getData().getChunkKeys(); + ChunkSet loaded = getServer().getWorld().getData().getLoadedChunks(); toLoad.clear(); toLoad.addAll(requested); @@ -58,17 +58,8 @@ public class ChunkLoadManager { } private void processQueues() { - Vec3i v = new Vec3i(); - - toLoad.forEach(key -> { - loadChunk(CoordinatePacker.unpack3IntsFromLong(key, v)); - return true; - }); - - toUnload.forEach(key -> { - unloadChunk(CoordinatePacker.unpack3IntsFromLong(key, v)); - return true; - }); + toUnload.forEach(this::unloadChunk); + toLoad.forEach(this::loadChunk); } public Server getServer() {