From 32ba0c78e57270308b43d1a57830dc85b7ecebe0 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Sat, 19 Dec 2020 20:53:43 +0300 Subject: [PATCH] Added chunk transfer concept - Added PacketLoadChunk - Added ChunkIO to handle converting ChunkData to/from bytes - Client no longer generates chunks by default - It still does generate them on request by server - DataBuffer now exposes InputStream and OutputStream --- .../progressia/client/ClientState.java | 2 +- .../common/comms/packets/PacketLoadChunk.java | 38 +++++++ .../progressia/common/io/ChunkCodec.java | 36 +++++++ .../progressia/common/io/ChunkIO.java | 99 +++++++++++++++++++ .../progressia/common/util/DataBuffer.java | 47 +++++---- .../common/world/DecodingException.java | 31 ++++++ .../progressia/common/world/WorldData.java | 37 ++++--- .../server/comms/ClientManager.java | 33 ++++++- .../server/world/UpdateTriggerer.java | 35 +++++++ .../progressia/server/world/WorldLogic.java | 22 +---- .../progressia/test/TestChunkCodec.java | 36 +++++++ .../progressia/test/TestChunkSender.java | 32 ++++++ .../windcorp/progressia/test/TestContent.java | 6 ++ 13 files changed, 397 insertions(+), 57 deletions(-) create mode 100644 src/main/java/ru/windcorp/progressia/common/comms/packets/PacketLoadChunk.java create mode 100644 src/main/java/ru/windcorp/progressia/common/io/ChunkCodec.java create mode 100644 src/main/java/ru/windcorp/progressia/common/io/ChunkIO.java create mode 100644 src/main/java/ru/windcorp/progressia/common/world/DecodingException.java create mode 100644 src/main/java/ru/windcorp/progressia/server/world/UpdateTriggerer.java create mode 100644 src/main/java/ru/windcorp/progressia/test/TestChunkCodec.java create mode 100644 src/main/java/ru/windcorp/progressia/test/TestChunkSender.java diff --git a/src/main/java/ru/windcorp/progressia/client/ClientState.java b/src/main/java/ru/windcorp/progressia/client/ClientState.java index 60d5ca2..a7f9a08 100644 --- a/src/main/java/ru/windcorp/progressia/client/ClientState.java +++ b/src/main/java/ru/windcorp/progressia/client/ClientState.java @@ -30,7 +30,7 @@ public class ClientState { Client client = new Client(world, channel); - world.tmp_generate(); +// world.tmp_generate(); channel.connect(); diff --git a/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketLoadChunk.java b/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketLoadChunk.java new file mode 100644 index 0000000..e44f8da --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketLoadChunk.java @@ -0,0 +1,38 @@ +package ru.windcorp.progressia.common.comms.packets; + +import java.io.IOException; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.io.ChunkIO; +import ru.windcorp.progressia.common.util.DataBuffer; +import ru.windcorp.progressia.common.util.crash.CrashReports; +import ru.windcorp.progressia.common.world.DecodingException; +import ru.windcorp.progressia.common.world.WorldData; + +public class PacketLoadChunk extends PacketWorldChange { + + private final DataBuffer data = new DataBuffer(); + private final Vec3i position = new Vec3i(); + + public PacketLoadChunk(String id) { + super(id); + } + + @Override + public void apply(WorldData world) { + try { + world.addChunk(ChunkIO.load(world, position, data.getInputStream())); + } catch (DecodingException | IOException e) { + CrashReports.report(e, "Could not load chunk"); + } + } + + public Vec3i getPosition() { + return position; + } + + public DataBuffer getData() { + return data; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/io/ChunkCodec.java b/src/main/java/ru/windcorp/progressia/common/io/ChunkCodec.java new file mode 100644 index 0000000..03efab4 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/io/ChunkCodec.java @@ -0,0 +1,36 @@ +package ru.windcorp.progressia.common.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.DecodingException; +import ru.windcorp.progressia.common.world.WorldData; + +public abstract class ChunkCodec extends Namespaced { + + private final byte signature; + + public ChunkCodec(String id, byte signature) { + super(id); + this.signature = signature; + } + + public ChunkCodec(String id, int signature) { + this(id, (byte) signature); + } + + public byte getSignature() { + return signature; + } + + public abstract ChunkData decode(WorldData world, Vec3i position, InputStream data) throws DecodingException, IOException; + + public abstract boolean shouldEncode(ChunkData chunk); + + public abstract void encode(ChunkData chunk, OutputStream output) throws IOException; + +} diff --git a/src/main/java/ru/windcorp/progressia/common/io/ChunkIO.java b/src/main/java/ru/windcorp/progressia/common/io/ChunkIO.java new file mode 100644 index 0000000..5b91762 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/io/ChunkIO.java @@ -0,0 +1,99 @@ +package ru.windcorp.progressia.common.io; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import glm.vec._3.i.Vec3i; +import gnu.trove.map.TByteObjectMap; +import gnu.trove.map.hash.TByteObjectHashMap; +import ru.windcorp.progressia.common.util.crash.CrashReports; +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.DecodingException; +import ru.windcorp.progressia.common.world.WorldData; + +public class ChunkIO { + + private static final TByteObjectMap CODECS_BY_ID = new TByteObjectHashMap<>(); + private static final List CODECS_BY_PRIORITY = new ArrayList<>(); + + public static ChunkData load(WorldData world, Vec3i position, InputStream data) + throws DecodingException, IOException + { + if (CODECS_BY_ID.isEmpty()) throw new IllegalStateException("No codecs registered"); + + int signature = data.read(); + if (signature < 0) throw new EOFException("Expected codec signature, got EOF"); + + ChunkCodec codec = getCodec((byte) signature); + if (codec == null) { + throw new DecodingException("Unknown codec signature " + Integer.toHexString(signature) + "; is it from the future?"); + } + + try { + return codec.decode(world, position, data); + } catch (IOException | DecodingException e) { + throw e; + } catch (Throwable t) { + CrashReports.report( + t, "Codec %s has failed to decode chunk (%d; %d; %d)", + codec.getId(), position.x, position.y, position.z + ); + return null; + } + } + + public static void save(ChunkData chunk, OutputStream output) + throws IOException + { + ChunkCodec codec = getCodec(chunk); + + try { + output.write(codec.getSignature()); + codec.encode(chunk, output); + } catch (IOException e) { + throw e; + } catch (Throwable t) { + CrashReports.report( + t, "Codec %s has failed to encode chunk (%d; %d; %d)", + codec.getId(), chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z + ); + } + } + + public static ChunkCodec getCodec(byte signature) { + if (CODECS_BY_ID.isEmpty()) throw new IllegalStateException("No codecs registered"); + return CODECS_BY_ID.get(signature); + } + + public static ChunkCodec getCodec(ChunkData chunk) { + for (ChunkCodec codec : CODECS_BY_PRIORITY) { + if (codec.shouldEncode(chunk)) { + return codec; + } + } + + if (CODECS_BY_ID.isEmpty()) throw new IllegalStateException("No codecs registered"); + return CODECS_BY_PRIORITY.get(0); + } + + /** + * Sorted is order of decreasing priority + * @return + */ + public static List getCodecs() { + return Collections.unmodifiableList(CODECS_BY_PRIORITY); + } + + public static void registerCodec(ChunkCodec codec) { + CODECS_BY_PRIORITY.add(0, codec); // Add to the front + CODECS_BY_ID.put(codec.getSignature(), codec); + } + + private ChunkIO() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/DataBuffer.java b/src/main/java/ru/windcorp/progressia/common/util/DataBuffer.java index 00f7fd3..e2527ea 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/DataBuffer.java +++ b/src/main/java/ru/windcorp/progressia/common/util/DataBuffer.java @@ -21,26 +21,25 @@ public class DataBuffer { private int position; - private final DataInput reader = new DataInputStream( - new InputStream() { - @Override - public int read() throws IOException { - if (position >= buffer.size()) return -1; - int result = buffer.getQuick(position); - ++position; - return result; - } - } - ); + private final InputStream inputStream = new InputStream() { + @Override + public int read() throws IOException { + if (DataBuffer.this.position >= buffer.size()) return -1; + int result = buffer.getQuick(DataBuffer.this.position); + ++DataBuffer.this.position; + return result; + } + }; - private final DataOutput writer = new DataOutputStream( - new OutputStream() { - @Override - public void write(int b) throws IOException { - buffer.add((byte) b); - } - } - ); + private final OutputStream outputStream = new OutputStream() { + @Override + public void write(int b) throws IOException { + DataBuffer.this.buffer.add((byte) b); + } + }; + + private final DataInput reader = new DataInputStream(inputStream); + private final DataOutput writer = new DataOutputStream(outputStream); public DataBuffer(int capacity) { this.buffer = new TByteArrayList(capacity); @@ -54,6 +53,16 @@ public class DataBuffer { this.buffer = new TByteArrayList(copyFrom.buffer); } + public InputStream getInputStream() { + position = 0; + return inputStream; + } + + public OutputStream getOutputStream() { + buffer.resetQuick(); + return outputStream; + } + public DataInput getReader() { position = 0; return reader; diff --git a/src/main/java/ru/windcorp/progressia/common/world/DecodingException.java b/src/main/java/ru/windcorp/progressia/common/world/DecodingException.java new file mode 100644 index 0000000..0dac53d --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/DecodingException.java @@ -0,0 +1,31 @@ +package ru.windcorp.progressia.common.world; + +/** + * Thrown to indicate that some data could not be properly decoded. + * @author javapony + */ +public class DecodingException extends Exception { + + private static final long serialVersionUID = 3200709153311801198L; + + public DecodingException() { + super(); + } + + public DecodingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public DecodingException(String message, Throwable cause) { + super(message, cause); + } + + public DecodingException(String message) { + super(message); + } + + public DecodingException(Throwable cause) { + super(cause); + } + +} 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 4a50001..6dec431 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java @@ -62,7 +62,6 @@ public class WorldData { for (cursor.y = -(size / 2); cursor.y <= (size / 2); ++cursor.y) { ChunkData chunk = new ChunkData(cursor, this); TestContent.generateChunk(chunk); - addChunkListeners(chunk); addChunk(chunk); } } @@ -72,8 +71,20 @@ public class WorldData { getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener)); } - private synchronized void addChunk(ChunkData chunk) { - chunksByPos.put(getChunkKey(chunk), chunk); + public synchronized void addChunk(ChunkData chunk) { + addChunkListeners(chunk); + + long key = getChunkKey(chunk); + + ChunkData previous = chunksByPos.get(key); + if (previous != null) { + throw new IllegalArgumentException(String.format( + "Chunk at (%d; %d; %d) already exists", + chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z + )); + } + + chunksByPos.put(key, chunk); chunk.forEachEntity(entity -> entitiesById.put(entity.getEntityId(), entity) @@ -83,16 +94,16 @@ public class WorldData { getListeners().forEach(l -> l.onChunkLoaded(this, chunk)); } -// private synchronized void removeChunk(ChunkData chunk) { -// getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk)); -// chunk.beforeUnloaded(); -// -// chunk.forEachEntity(entity -> -// entitiesById.remove(entity.getEntityId()) -// ); -// -// chunksByPos.remove(getChunkKey(chunk)); -// } + public synchronized void removeChunk(ChunkData 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) { return CoordinatePacker.pack3IntsIntoLong(chunk.getPosition()); diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java index 86d5cff..f371c12 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java @@ -1,5 +1,6 @@ package ru.windcorp.progressia.server.comms; +import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.concurrent.atomic.AtomicInteger; @@ -10,7 +11,11 @@ import gnu.trove.map.hash.TIntObjectHashMap; import ru.windcorp.progressia.common.comms.CommsChannel.Role; import ru.windcorp.progressia.common.comms.CommsChannel.State; import ru.windcorp.progressia.common.comms.packets.Packet; +import ru.windcorp.progressia.common.comms.packets.PacketLoadChunk; import ru.windcorp.progressia.common.comms.packets.PacketSetLocalPlayer; +import ru.windcorp.progressia.common.io.ChunkIO; +import ru.windcorp.progressia.common.util.crash.CrashReports; +import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.server.Server; public class ClientManager { @@ -34,11 +39,29 @@ public class ClientManager { } public void addClient(Client client) { - clientsById.put(client.getId(), client); - - client.addListener(new DefaultServerCommsListener(this, client)); - - client.sendPacket(new PacketSetLocalPlayer(0x42)); + synchronized (client) { + clientsById.put(client.getId(), client); + + client.addListener(new DefaultServerCommsListener(this, client)); + + for (ChunkData chunk : server.getWorld().getData().getChunks()) { + PacketLoadChunk packet = new PacketLoadChunk("Core:LoadChunk"); + packet.getPosition().set( + chunk.getPosition().x, + chunk.getPosition().y, + chunk.getPosition().z + ); + + try { + ChunkIO.save(chunk, packet.getData().getOutputStream()); + } catch (IOException e) { + CrashReports.report(e, "ClientManager fjcked up. javahorse stupid"); + } + client.sendPacket(packet); + } + + client.sendPacket(new PacketSetLocalPlayer(0x42)); + } } public void disconnectClient(Client client) { diff --git a/src/main/java/ru/windcorp/progressia/server/world/UpdateTriggerer.java b/src/main/java/ru/windcorp/progressia/server/world/UpdateTriggerer.java new file mode 100644 index 0000000..ffc54f7 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/UpdateTriggerer.java @@ -0,0 +1,35 @@ +package ru.windcorp.progressia.server.world; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.ChunkDataListener; +import ru.windcorp.progressia.common.world.Coordinates; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.tile.TileData; +import ru.windcorp.progressia.server.Server; + +public class UpdateTriggerer implements ChunkDataListener { + + private final Server server; + + public UpdateTriggerer(Server server) { + this.server = server; + } + + @Override + public void onChunkBlockChanged( + ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current + ) { + server.getWorldAccessor().triggerUpdates(Coordinates.getInWorld(chunk.getPosition(), blockInChunk, null)); + } + + @Override + public void onChunkTilesChanged( + ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile, + boolean wasAdded + ) { + server.getWorldAccessor().triggerUpdates(Coordinates.getInWorld(chunk.getPosition(), blockInChunk, null), face); + } + +} 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 2fe9576..ca745a7 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java @@ -7,17 +7,15 @@ import java.util.Map; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.common.world.ChunkDataListener; import ru.windcorp.progressia.common.world.ChunkDataListeners; 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.common.world.block.BlockData; import ru.windcorp.progressia.common.world.block.BlockFace; -import ru.windcorp.progressia.common.world.tile.TileData; import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.world.block.BlockLogic; import ru.windcorp.progressia.server.world.tile.TileLogic; +import ru.windcorp.progressia.test.TestChunkSender; public class WorldLogic { @@ -42,22 +40,8 @@ public class WorldLogic { } }); - data.addListener(ChunkDataListeners.createAdder(new ChunkDataListener() { - @Override - public void onChunkBlockChanged( - ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current - ) { - getServer().getWorldAccessor().triggerUpdates(Coordinates.getInWorld(chunk.getPosition(), blockInChunk, null)); - } - - @Override - public void onChunkTilesChanged( - ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile, - boolean wasAdded - ) { - getServer().getWorldAccessor().triggerUpdates(Coordinates.getInWorld(chunk.getPosition(), blockInChunk, null), face); - } - })); + data.addListener(ChunkDataListeners.createAdder(new UpdateTriggerer(server))); + data.addListener(new TestChunkSender(server)); } public Server getServer() { diff --git a/src/main/java/ru/windcorp/progressia/test/TestChunkCodec.java b/src/main/java/ru/windcorp/progressia/test/TestChunkCodec.java new file mode 100644 index 0000000..5b00683 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/TestChunkCodec.java @@ -0,0 +1,36 @@ +package ru.windcorp.progressia.test; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.io.ChunkCodec; +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.DecodingException; +import ru.windcorp.progressia.common.world.WorldData; + +public class TestChunkCodec extends ChunkCodec { + + public TestChunkCodec() { + super("Test:TestCodec", 0x00); + } + + @Override + public ChunkData decode(WorldData world, Vec3i position, InputStream data) throws DecodingException, IOException { + ChunkData chunk = new ChunkData(position, world); + TestContent.generateChunk(chunk); + return chunk; + } + + @Override + public boolean shouldEncode(ChunkData chunk) { + return true; + } + + @Override + public void encode(ChunkData chunk, OutputStream output) throws IOException { + // Do nothing. Heh. + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestChunkSender.java b/src/main/java/ru/windcorp/progressia/test/TestChunkSender.java new file mode 100644 index 0000000..345d8c5 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/TestChunkSender.java @@ -0,0 +1,32 @@ +package ru.windcorp.progressia.test; + +import java.io.IOException; + +import ru.windcorp.progressia.common.comms.packets.PacketLoadChunk; +import ru.windcorp.progressia.common.io.ChunkIO; +import ru.windcorp.progressia.common.util.crash.CrashReports; +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.WorldDataListener; +import ru.windcorp.progressia.server.Server; + +public class TestChunkSender implements WorldDataListener { + + private final Server server; + + public TestChunkSender(Server server) { + this.server = server; + } + + @Override + public void onChunkLoaded(WorldData world, ChunkData chunk) { + PacketLoadChunk packet = new PacketLoadChunk("Core:LoadChunk"); + try { + ChunkIO.save(chunk, packet.getData().getOutputStream()); + } catch (IOException e) { + CrashReports.report(e, "TestChunkSender fjcked up. javahorse stupid"); + } + server.getClientManager().broadcastGamePacket(packet); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index 5b7c1b2..fdd6374 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -24,6 +24,7 @@ import ru.windcorp.progressia.client.world.tile.*; import ru.windcorp.progressia.common.collision.AABB; import ru.windcorp.progressia.common.collision.CollisionModel; import ru.windcorp.progressia.common.comms.controls.*; +import ru.windcorp.progressia.common.io.ChunkIO; import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory; import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.ChunkData; @@ -41,6 +42,7 @@ public class TestContent { public static void registerContent() { registerWorldContent(); regsiterControls(); + registerMisc(); } private static void registerWorldContent() { @@ -328,4 +330,8 @@ public class TestContent { } } + private static void registerMisc() { + ChunkIO.registerCodec(new TestChunkCodec()); + } + }