From 372f173723305dc9c34a2b5d77f16467d2d0fa76 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Fri, 20 Nov 2020 23:54:32 +0300 Subject: [PATCH] Introduced ChunkData and WorldDataListeners - Added ChunkDataListener and WorldDataListener - Moved chunk render update requests to clientside ChunkDataListeners - Moved ChunkRender and ChunkLogic initialization into WDListeners --- .../progressia/client/ClientState.java | 2 + .../comms/DefaultClientCommsListener.java | 10 ---- .../client/world/ChunkUpdateListener.java | 19 +++++++ .../progressia/client/world/WorldRender.java | 17 +++++- .../progressia/common/world/ChunkData.java | 46 ++++++++++++--- .../common/world/ChunkDataListener.java | 51 +++++++++++++++++ .../common/world/ChunkDataListeners.java | 25 ++++++++ .../progressia/common/world/Coordinates.java | 9 ++- .../world/IllegalCoordinatesException.java | 28 +++++++++ .../progressia/common/world/WorldData.java | 57 +++++++++++++++++-- .../common/world/WorldDataListener.java | 34 +++++++++++ .../progressia/server/ServerState.java | 1 + .../progressia/server/world/WorldLogic.java | 37 +++++++++++- ...datableBlock.java => UpdateableBlock.java} | 0 14 files changed, 303 insertions(+), 33 deletions(-) create mode 100644 src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java create mode 100644 src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java create mode 100644 src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java create mode 100644 src/main/java/ru/windcorp/progressia/common/world/IllegalCoordinatesException.java create mode 100644 src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java rename src/main/java/ru/windcorp/progressia/server/world/block/{UpdatableBlock.java => UpdateableBlock.java} (100%) diff --git a/src/main/java/ru/windcorp/progressia/client/ClientState.java b/src/main/java/ru/windcorp/progressia/client/ClientState.java index b812907..60d5ca2 100644 --- a/src/main/java/ru/windcorp/progressia/client/ClientState.java +++ b/src/main/java/ru/windcorp/progressia/client/ClientState.java @@ -30,6 +30,8 @@ public class ClientState { Client client = new Client(world, channel); + world.tmp_generate(); + channel.connect(); setInstance(client); diff --git a/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java b/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java index 4053672..f2eb76a 100644 --- a/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java +++ b/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java @@ -5,14 +5,12 @@ import java.io.IOException; import ru.windcorp.jputil.chars.StringUtil; import ru.windcorp.progressia.client.Client; import ru.windcorp.progressia.client.graphics.world.EntityAnchor; -import ru.windcorp.progressia.client.world.ChunkRender; import ru.windcorp.progressia.common.comms.CommsListener; import ru.windcorp.progressia.common.comms.packets.Packet; import ru.windcorp.progressia.common.comms.packets.PacketSetLocalPlayer; import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.common.world.entity.PacketEntityChange; // TODO refactor with no mercy public class DefaultClientCommsListener implements CommsListener { @@ -29,10 +27,6 @@ public class DefaultClientCommsListener implements CommsListener { ((PacketWorldChange) packet).apply( getClient().getWorld().getData() ); - - if (!(packet instanceof PacketEntityChange)) { - tmp_reassembleWorld(); - } } else if (packet instanceof PacketSetLocalPlayer) { setLocalPlayer((PacketSetLocalPlayer) packet); } @@ -57,10 +51,6 @@ public class DefaultClientCommsListener implements CommsListener { )); } - private void tmp_reassembleWorld() { - getClient().getWorld().getChunks().forEach(ChunkRender::markForUpdate); - } - @Override public void onIOError(IOException reason) { // TODO implement diff --git a/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java b/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java new file mode 100644 index 0000000..d5fd908 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java @@ -0,0 +1,19 @@ +package ru.windcorp.progressia.client.world; + +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.ChunkDataListener; + +class ChunkUpdateListener implements ChunkDataListener { + + private final WorldRender world; + + public ChunkUpdateListener(WorldRender world) { + this.world = world; + } + + @Override + public void onChunkChanged(ChunkData chunk) { + world.getChunk(chunk).markForUpdate(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java b/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java index fc219f4..49b8272 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java +++ b/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java @@ -29,7 +29,9 @@ import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry; import ru.windcorp.progressia.client.world.entity.EntityRenderable; import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.ChunkDataListeners; import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.WorldDataListener; import ru.windcorp.progressia.common.world.entity.EntityData; public class WorldRender { @@ -43,9 +45,18 @@ public class WorldRender { public WorldRender(WorldData data) { this.data = data; - for (ChunkData chunkData : data.getChunks()) { - chunks.put(chunkData, new ChunkRender(this, chunkData)); - } + data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this))); + data.addListener(new WorldDataListener() { + @Override + public void onChunkLoaded(WorldData world, ChunkData chunk) { + chunks.put(chunk, new ChunkRender(WorldRender.this, chunk)); + } + + @Override + public void beforeChunkUnloaded(WorldData world, ChunkData chunk) { + chunks.remove(chunk); + } + }); } public WorldData getData() { diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java index 031be35..d725193 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java @@ -20,6 +20,7 @@ package ru.windcorp.progressia.common.world; import static ru.windcorp.progressia.common.world.block.BlockFace.*; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.BiConsumer; @@ -63,8 +64,11 @@ public class ChunkData { private final List entities = Collections.synchronizedList(new ArrayList<>()); - public ChunkData(int x, int y, int z, WorldData world) { - this.position.set(x, y, z); + private final Collection listeners = + Collections.synchronizedCollection(new ArrayList<>()); + + public ChunkData(Vec3i position, WorldData world) { + this.position.set(position.x, position.y, position.z); this.world = world; tmp_generate(); @@ -92,11 +96,11 @@ public class ChunkData { pos.set(x, y, z); if (f > 17) { - setBlock(pos, stone); + setBlock(pos, stone, false); } else if (f > 14) { - setBlock(pos, dirt); + setBlock(pos, dirt, false); } else { - setBlock(pos, air); + setBlock(pos, air, false); } } @@ -152,8 +156,16 @@ public class ChunkData { return blocks[getBlockIndex(posInChunk)]; } - public void setBlock(Vec3i posInChunk, BlockData block) { + public void setBlock(Vec3i posInChunk, BlockData block, boolean notify) { + BlockData previous = blocks[getBlockIndex(posInChunk)]; blocks[getBlockIndex(posInChunk)] = block; + + if (notify) { + getListeners().forEach(l -> { + l.onChunkBlockChanged(this, posInChunk, previous, block); + l.onChunkChanged(this); + }); + } } public List getTilesOrNull(Vec3i blockInChunk, BlockFace face) { @@ -245,7 +257,7 @@ public class ChunkData { private static void checkLocalCoordinates(Vec3i posInChunk) { if (!isInBounds(posInChunk)) { - throw new IllegalArgumentException( + throw new IllegalCoordinatesException( "Coordinates " + str(posInChunk) + " " + "are not legal chunk coordinates" ); @@ -331,8 +343,28 @@ public class ChunkData { return world; } + public Collection getListeners() { + return listeners; + } + + public void addListener(ChunkDataListener listener) { + this.listeners.add(listener); + } + + public void removeListener(ChunkDataListener listener) { + this.listeners.remove(listener); + } + private static String str(Vec3i v) { return "(" + v.x + "; " + v.y + "; " + v.z + ")"; } + protected void onLoaded() { + getListeners().forEach(l -> l.onChunkLoaded(this)); + } + + protected void beforeUnloaded() { + getListeners().forEach(l -> l.beforeChunkUnloaded(this)); + } + } diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java new file mode 100644 index 0000000..216617e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java @@ -0,0 +1,51 @@ +package ru.windcorp.progressia.common.world; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.tile.TileData; + +public interface ChunkDataListener { + + /** + * Invoked after a block has changed in a chunk. + * This is not triggered when a change is caused by chunk loading or unloading. + * @param chunk the chunk that has changed + * @param blockInChunk the {@linkplain Coordinates#blockInChunk chunk coordinates} of the change + * @param previous the previous occupant of {@code blockInChunk} + * @param current the current (new) occupant of {@code blockInChunk} + */ + default void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {} + + /** + * Invoked after a tile has been added or removed from a chunk. + * This is not triggered when a change is caused by chunk loading or unloading. + * @param chunk the chunk that has changed + * @param blockInChunk the {@linkplain Coordinates#blockInChunk chunk coordinates} of the change + * @param face the face that the changed tile belongs or belonged to + * @param tile the tile that has been added or removed + * @param wasAdded {@code true} iff the tile has been added, {@code false} iff the tile has been removed + */ + default void onChunkTilesChanged(ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile, boolean wasAdded) {} + + /** + * Invoked whenever a chunk changes, loads or unloads. If some other method in this + * {@code ChunkDataListener} are to be invoked, e.g. is the change was caused by a + * block being removed, this method is called last. + * @param chunk the chunk that has changed + */ + default void onChunkChanged(ChunkData chunk) {} + + /** + * Invoked whenever a chunk has been loaded. + * @param chunk the chunk that has loaded + */ + default void onChunkLoaded(ChunkData chunk) {} + + /** + * Invoked whenever a chunk is about to be unloaded. + * @param chunk the chunk that is going to be loaded + */ + default void beforeChunkUnloaded(ChunkData chunk) {} + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java new file mode 100644 index 0000000..e0ca46a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java @@ -0,0 +1,25 @@ +package ru.windcorp.progressia.common.world; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import glm.vec._3.i.Vec3i; + +public class ChunkDataListeners { + + public static WorldDataListener createAdder(Supplier listenerSupplier) { + return new WorldDataListener() { + @Override + public void getChunkListeners(WorldData world, Vec3i chunk, Consumer chunkListenerSink) { + chunkListenerSink.accept(listenerSupplier.get()); + } + }; + } + + public static WorldDataListener createAdder(ChunkDataListener listener) { + return createAdder(() -> listener); + } + + private ChunkDataListeners() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java b/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java index 76a3f95..bfc7773 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java +++ b/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java @@ -11,18 +11,17 @@ import glm.vec._3.i.Vec3i; * Three types of coordinates are used in Progressia: *
    * - *
  • World coordinates, in code referred to as {@code blockInWorld} - + *
  • World coordinates, in code referred to as {@code blockInWorld} - * coordinates relative to world origin. Every block in the world has unique * world coordinates.
  • * - *
  • Chunk coordinates, in code referred to as {@code blockInChunk} - + *
  • Chunk coordinates, in code referred to as {@code blockInChunk} - * coordinates relative some chunk's origin. Every block in the chunk has unique * chunk coordinates, but blocks in different chunks may have identical chunk * coordinates. These coordinates are only useful in combination with a chunk - * reference. Chunk coordinates are always [0; {@link #BLOCKS_PER_CHUNK}) - * .
  • + * reference. Chunk coordinates are always [0; {@link #BLOCKS_PER_CHUNK}). * - *
  • Coordinates of chunk, in code referred to as {@code chunk} - + *
  • Coordinates of chunk, in code referred to as {@code chunk} - * chunk coordinates relative to world origin. Every chunk in the world has * unique coordinates of chunk.
  • * diff --git a/src/main/java/ru/windcorp/progressia/common/world/IllegalCoordinatesException.java b/src/main/java/ru/windcorp/progressia/common/world/IllegalCoordinatesException.java new file mode 100644 index 0000000..aba1390 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/IllegalCoordinatesException.java @@ -0,0 +1,28 @@ +package ru.windcorp.progressia.common.world; + +public class IllegalCoordinatesException extends RuntimeException { + + private static final long serialVersionUID = 1362481281554206710L; + + public IllegalCoordinatesException() { + super(); + } + + public IllegalCoordinatesException(String message) { + super(message); + } + + public IllegalCoordinatesException(Throwable cause) { + super(cause); + } + + public IllegalCoordinatesException(String message, Throwable cause) { + super(message, cause); + } + + public IllegalCoordinatesException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} 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 a8a4966..29e82ce 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java @@ -17,6 +17,7 @@ *******************************************************************************/ package ru.windcorp.progressia.common.world; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -46,30 +47,50 @@ public class WorldData { private float time = 0; + private final Collection listeners = + Collections.synchronizedCollection(new ArrayList<>()); + public WorldData() { - final int size = 1; - for (int x = -(size / 2); x <= (size / 2); ++x) { - for (int y = -(size / 2); y <= (size / 2); ++y) { - addChunk(new ChunkData(x, y, 0, this)); + } + + public void tmp_generate() { + final int size = 1; + Vec3i cursor = new Vec3i(0, 0, 0); + + for (cursor.x = -(size / 2); cursor.x <= (size / 2); ++cursor.x) { + for (cursor.y = -(size / 2); cursor.y <= (size / 2); ++cursor.y) { + ChunkData chunk = new ChunkData(cursor, this); + addChunkListeners(chunk); + addChunk(chunk); } } } + private void addChunkListeners(ChunkData chunk) { + getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener)); + } + private synchronized void addChunk(ChunkData chunk) { chunksByPos.put(getChunkKey(chunk), chunk); chunk.forEachEntity(entity -> entitiesById.put(entity.getEntityId(), entity) ); + + chunk.onLoaded(); + getListeners().forEach(l -> l.onChunkLoaded(this, chunk)); } // private synchronized void removeChunk(ChunkData chunk) { -// chunksByPos.remove(getChunkKey(chunk)); +// getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk)); +// chunk.beforeUnloaded(); // // chunk.forEachEntity(entity -> // entitiesById.remove(entity.getEntityId()) // ); +// +// chunksByPos.remove(getChunkKey(chunk)); // } private static long getChunkKey(ChunkData chunk) { @@ -99,6 +120,20 @@ public class WorldData { return result; } + public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) { + ChunkData chunk = getChunkByBlock(blockInWorld); + if (chunk == null) + throw new IllegalCoordinatesException( + "Coordinates " + + "(" + blockInWorld.x + "; " + blockInWorld.y + "; " + blockInWorld.z + ") " + + "do not belong to a loaded chunk" + ); + + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); + chunk.setBlock(blockInChunk, block, notify); + } + public Collection getChunks() { return chunks; } @@ -132,4 +167,16 @@ public class WorldData { return block.getCollisionModel(); } + public Collection getListeners() { + return listeners; + } + + public void addListener(WorldDataListener e) { + listeners.add(e); + } + + public void removeListener(WorldDataListener o) { + listeners.remove(o); + } + } diff --git a/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java b/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java new file mode 100644 index 0000000..12b3b9f --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java @@ -0,0 +1,34 @@ +package ru.windcorp.progressia.common.world; + +import java.util.function.Consumer; + +import glm.vec._3.i.Vec3i; + +public interface WorldDataListener { + + /** + * Invoked when a new {@link ChunkData} instance is created. This method should be used to add + * {@link ChunkDataListener}s to a new chunk. When listeners are added with this method, + * their {@link ChunkDataListener#onChunkLoaded(ChunkData) onChunkLoaded} methods will be invoked. + * @param world the world instance + * @param chunk the {@linkplain Coordinates#chunk coordinates of chunk} of the chunk about to load + * @param chunkListenerSink a sink for listeners. All listeners passed to its + * {@link Consumer#accept(Object) accept} method will be added to the chunk. + */ + default void getChunkListeners(WorldData world, Vec3i chunk, Consumer chunkListenerSink) {} + + /** + * Invoked whenever a {@link Chunk} has been loaded. + * @param world the world instance + * @param chunk the chunk that has loaded + */ + default void onChunkLoaded(WorldData world, ChunkData chunk) {} + + /** + * Invoked whenever a {@link Chunk} is about to be unloaded. + * @param world the world instance + * @param chunk the chunk that is going to be unloaded + */ + default void beforeChunkUnloaded(WorldData world, ChunkData chunk) {} + +} diff --git a/src/main/java/ru/windcorp/progressia/server/ServerState.java b/src/main/java/ru/windcorp/progressia/server/ServerState.java index d8071f6..aa19807 100644 --- a/src/main/java/ru/windcorp/progressia/server/ServerState.java +++ b/src/main/java/ru/windcorp/progressia/server/ServerState.java @@ -16,6 +16,7 @@ public class ServerState { public static void startServer() { Server server = new Server(new WorldData()); + server.getWorld().getData().tmp_generate(); setInstance(server); server.start(); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java index 2aae605..10e0db8 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java @@ -5,8 +5,12 @@ import java.util.HashMap; import java.util.Map; import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.WorldDataListener; +import ru.windcorp.progressia.server.world.block.BlockLogic; public class WorldLogic { @@ -17,9 +21,17 @@ public class WorldLogic { public WorldLogic(WorldData data) { this.data = data; - for (ChunkData chunkData : data.getChunks()) { - chunks.put(chunkData, new ChunkLogic(this, chunkData)); - } + data.addListener(new WorldDataListener() { + @Override + public void onChunkLoaded(WorldData world, ChunkData chunk) { + chunks.put(chunk, new ChunkLogic(WorldLogic.this, chunk)); + } + + @Override + public void beforeChunkUnloaded(WorldData world, ChunkData chunk) { + chunks.remove(chunk); + } + }); } public WorldData getData() { @@ -34,6 +46,25 @@ public class WorldLogic { return chunks.get(getData().getChunk(pos)); } + public ChunkLogic getChunkByBlock(Vec3i blockInWorld) { + Vec3i chunkPos = Vectors.grab3i(); + Coordinates.convertInWorldToChunk(blockInWorld, chunkPos); + ChunkLogic result = getChunk(chunkPos); + Vectors.release(chunkPos); + return result; + } + + public BlockLogic getBlock(Vec3i blockInWorld) { + ChunkLogic chunk = getChunkByBlock(blockInWorld); + if (chunk == null) return null; + + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); + BlockLogic result = chunk.getBlock(blockInChunk); + + return result; + } + public Collection getChunks() { return chunks.values(); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/UpdatableBlock.java b/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java similarity index 100% rename from src/main/java/ru/windcorp/progressia/server/world/block/UpdatableBlock.java rename to src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java