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
This commit is contained in:
OLEGSHA 2020-12-19 20:53:43 +03:00
parent 8977f460ca
commit 32ba0c78e5
13 changed files with 397 additions and 57 deletions

View File

@ -30,7 +30,7 @@ public class ClientState {
Client client = new Client(world, channel); Client client = new Client(world, channel);
world.tmp_generate(); // world.tmp_generate();
channel.connect(); channel.connect();

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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<ChunkCodec> CODECS_BY_ID = new TByteObjectHashMap<>();
private static final List<ChunkCodec> 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<ChunkCodec> 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() {}
}

View File

@ -21,26 +21,25 @@ public class DataBuffer {
private int position; private int position;
private final DataInput reader = new DataInputStream( private final InputStream inputStream = new InputStream() {
new InputStream() { @Override
@Override public int read() throws IOException {
public int read() throws IOException { if (DataBuffer.this.position >= buffer.size()) return -1;
if (position >= buffer.size()) return -1; int result = buffer.getQuick(DataBuffer.this.position);
int result = buffer.getQuick(position); ++DataBuffer.this.position;
++position; return result;
return result; }
} };
}
);
private final DataOutput writer = new DataOutputStream( private final OutputStream outputStream = new OutputStream() {
new OutputStream() { @Override
@Override public void write(int b) throws IOException {
public void write(int b) throws IOException { DataBuffer.this.buffer.add((byte) b);
buffer.add((byte) b); }
} };
}
); private final DataInput reader = new DataInputStream(inputStream);
private final DataOutput writer = new DataOutputStream(outputStream);
public DataBuffer(int capacity) { public DataBuffer(int capacity) {
this.buffer = new TByteArrayList(capacity); this.buffer = new TByteArrayList(capacity);
@ -54,6 +53,16 @@ public class DataBuffer {
this.buffer = new TByteArrayList(copyFrom.buffer); this.buffer = new TByteArrayList(copyFrom.buffer);
} }
public InputStream getInputStream() {
position = 0;
return inputStream;
}
public OutputStream getOutputStream() {
buffer.resetQuick();
return outputStream;
}
public DataInput getReader() { public DataInput getReader() {
position = 0; position = 0;
return reader; return reader;

View File

@ -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);
}
}

View File

@ -62,7 +62,6 @@ public class WorldData {
for (cursor.y = -(size / 2); cursor.y <= (size / 2); ++cursor.y) { for (cursor.y = -(size / 2); cursor.y <= (size / 2); ++cursor.y) {
ChunkData chunk = new ChunkData(cursor, this); ChunkData chunk = new ChunkData(cursor, this);
TestContent.generateChunk(chunk); TestContent.generateChunk(chunk);
addChunkListeners(chunk);
addChunk(chunk); addChunk(chunk);
} }
} }
@ -72,8 +71,20 @@ public class WorldData {
getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener)); getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener));
} }
private synchronized void addChunk(ChunkData chunk) { public synchronized void addChunk(ChunkData chunk) {
chunksByPos.put(getChunkKey(chunk), 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 -> chunk.forEachEntity(entity ->
entitiesById.put(entity.getEntityId(), entity) entitiesById.put(entity.getEntityId(), entity)
@ -83,16 +94,16 @@ public class WorldData {
getListeners().forEach(l -> l.onChunkLoaded(this, chunk)); getListeners().forEach(l -> l.onChunkLoaded(this, chunk));
} }
// private synchronized void removeChunk(ChunkData chunk) { public synchronized void removeChunk(ChunkData chunk) {
// getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk)); getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk));
// chunk.beforeUnloaded(); chunk.beforeUnloaded();
//
// chunk.forEachEntity(entity -> chunk.forEachEntity(entity ->
// entitiesById.remove(entity.getEntityId()) entitiesById.remove(entity.getEntityId())
// ); );
//
// chunksByPos.remove(getChunkKey(chunk)); chunksByPos.remove(getChunkKey(chunk));
// } }
private static long getChunkKey(ChunkData chunk) { private static long getChunkKey(ChunkData chunk) {
return CoordinatePacker.pack3IntsIntoLong(chunk.getPosition()); return CoordinatePacker.pack3IntsIntoLong(chunk.getPosition());

View File

@ -1,5 +1,6 @@
package ru.windcorp.progressia.server.comms; package ru.windcorp.progressia.server.comms;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger; 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.Role;
import ru.windcorp.progressia.common.comms.CommsChannel.State; import ru.windcorp.progressia.common.comms.CommsChannel.State;
import ru.windcorp.progressia.common.comms.packets.Packet; 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.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; import ru.windcorp.progressia.server.Server;
public class ClientManager { public class ClientManager {
@ -34,11 +39,29 @@ public class ClientManager {
} }
public void addClient(Client client) { public void addClient(Client client) {
clientsById.put(client.getId(), client); synchronized (client) {
clientsById.put(client.getId(), client);
client.addListener(new DefaultServerCommsListener(this, client));
client.addListener(new DefaultServerCommsListener(this, client));
client.sendPacket(new PacketSetLocalPlayer(0x42));
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) { public void disconnectClient(Client client) {

View File

@ -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);
}
}

View File

@ -7,17 +7,15 @@ import java.util.Map;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.ChunkData; 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.ChunkDataListeners;
import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.WorldDataListener; 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.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.block.BlockLogic; import ru.windcorp.progressia.server.world.block.BlockLogic;
import ru.windcorp.progressia.server.world.tile.TileLogic; import ru.windcorp.progressia.server.world.tile.TileLogic;
import ru.windcorp.progressia.test.TestChunkSender;
public class WorldLogic { public class WorldLogic {
@ -42,22 +40,8 @@ public class WorldLogic {
} }
}); });
data.addListener(ChunkDataListeners.createAdder(new ChunkDataListener() { data.addListener(ChunkDataListeners.createAdder(new UpdateTriggerer(server)));
@Override data.addListener(new TestChunkSender(server));
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);
}
}));
} }
public Server getServer() { public Server getServer() {

View File

@ -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.
}
}

View File

@ -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);
}
}

View File

@ -24,6 +24,7 @@ import ru.windcorp.progressia.client.world.tile.*;
import ru.windcorp.progressia.common.collision.AABB; import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.collision.CollisionModel; import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.comms.controls.*; 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.state.StatefulObjectRegistry.Factory;
import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
@ -41,6 +42,7 @@ public class TestContent {
public static void registerContent() { public static void registerContent() {
registerWorldContent(); registerWorldContent();
regsiterControls(); regsiterControls();
registerMisc();
} }
private static void registerWorldContent() { private static void registerWorldContent() {
@ -328,4 +330,8 @@ public class TestContent {
} }
} }
private static void registerMisc() {
ChunkIO.registerCodec(new TestChunkCodec());
}
} }