Merge branch 'multichunk'

This commit is contained in:
OLEGSHA 2020-12-30 14:49:59 +03:00
commit 6e6701d2e5
87 changed files with 3262 additions and 827 deletions

View File

@ -3,6 +3,7 @@ package ru.windcorp.progressia.client;
import ru.windcorp.progressia.client.comms.DefaultClientCommsListener;
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.client.graphics.world.Camera;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.world.WorldData;
@ -11,14 +12,14 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
public class Client {
private final WorldRender world;
private LocalPlayer localPlayer;
private final LocalPlayer localPlayer = new LocalPlayer(this);
private final Camera camera = new Camera((float) Math.toRadians(70));
private final ServerCommsChannel comms;
public Client(WorldData world, ServerCommsChannel comms) {
this.world = new WorldRender(world);
this.world = new WorldRender(world, this);
this.comms = comms;
comms.addListener(new DefaultClientCommsListener(this));
@ -32,8 +33,8 @@ public class Client {
return localPlayer;
}
public void setLocalPlayer(EntityData localPlayer) {
this.localPlayer = new LocalPlayer(localPlayer);
public boolean isReady() {
return localPlayer.hasEntity();
}
public Camera getCamera() {
@ -44,4 +45,15 @@ public class Client {
return comms;
}
public void onLocalPlayerEntityChanged(EntityData entity, EntityData lastKnownEntity) {
if (entity == null) {
getCamera().setAnchor(null);
return;
}
getCamera().setAnchor(new EntityAnchor(
getWorld().getEntityRenderable(entity)
));
}
}

View File

@ -2,15 +2,12 @@ package ru.windcorp.progressia.client.comms;
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.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.PacketSetLocalPlayer;
import ru.windcorp.progressia.common.world.PacketWorldChange;
// TODO refactor with no mercy
public class DefaultClientCommsListener implements CommsListener {
@ -33,26 +30,12 @@ public class DefaultClientCommsListener implements CommsListener {
}
private void setLocalPlayer(PacketSetLocalPlayer packet) {
EntityData entity = getClient().getWorld().getData().getEntity(
packet.getLocalPlayerEntityId()
);
if (entity == null) {
CrashReports.report(
null,
"Player entity with ID %s not found",
new String(StringUtil.toFullHex(packet.getLocalPlayerEntityId()))
);
}
getClient().setLocalPlayer(entity);
getClient().getCamera().setAnchor(new EntityAnchor(
getClient().getWorld().getEntityRenderable(entity)
));
getClient().getLocalPlayer().setEntityId(packet.getEntityId());
}
@Override
public void onIOError(IOException reason) {
CrashReports.report(reason, "An IOException has occurred in communications");
// TODO implement
}

View File

@ -192,6 +192,7 @@ public class VertexBufferObject implements OpenGLDeletable {
return usage;
}
@Override
public int getHandle() {
return handle;
}

View File

@ -91,6 +91,7 @@ public class Shader implements OpenGLDeletable {
);
}
@Override
public int getHandle() {
return handle;
}

View File

@ -24,6 +24,7 @@ import ru.windcorp.progressia.client.graphics.input.bus.Input;
public abstract class GUILayer extends AssembledFlatLayer {
private final Component root = new Component("Root") {
@Override
protected void handleReassemblyRequest() {
GUILayer.this.invalidate();
}

View File

@ -264,6 +264,7 @@ public class ShapeRenderProgram extends Program {
private final List<Vertex> vertices = new ArrayList<>();
@Override
public VertexBuilder addVertex(
float x, float y, float z,
float r, float g, float b,
@ -278,6 +279,7 @@ public class ShapeRenderProgram extends Program {
return this;
}
@Override
public VertexBuilder addVertex(
Vec3 position,
Vec3 colorMultiplier,
@ -292,6 +294,7 @@ public class ShapeRenderProgram extends Program {
return this;
}
@Override
public ByteBuffer assemble() {
ByteBuffer result = BufferUtils.createByteBuffer(
DEFAULT_BYTES_PER_VERTEX * vertices.size()

View File

@ -75,7 +75,9 @@ public class LayerWorld extends Layer {
renderWorld();
}
if (client.getLocalPlayer() != null) {
client.getLocalPlayer().getEntity();
if (client.isReady()) {
client.getLocalPlayer().update(client.getWorld());
}
}
@ -139,10 +141,9 @@ public class LayerWorld extends Layer {
private static final Renderable SELECTION_BOX = tmp_createSelectionBox();
private void tmp_drawSelectionBox() {
LocalPlayer player = client.getLocalPlayer();
if (player == null) return;
if (!client.isReady()) return;
Vec3i selection = player.getSelection().getBlock();
Vec3i selection = client.getLocalPlayer().getSelection().getBlock();
if (selection == null) return;
helper.pushTransform().translate(selection.x, selection.y, selection.z);

View File

@ -1,16 +1,59 @@
package ru.windcorp.progressia.client.graphics.world;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.common.world.PlayerData;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class LocalPlayer extends PlayerData {
public class LocalPlayer {
private final Client client;
private long entityId = EntityData.NULL_ENTITY_ID;
private EntityData lastKnownEntity = null;
private final Selection selection = new Selection();
public LocalPlayer(EntityData entity) {
super(entity);
public LocalPlayer(Client client) {
this.client = client;
}
public Client getClient() {
return client;
}
public long getEntityId() {
return entityId;
}
public void setEntityId(long entityId) {
this.entityId = entityId;
this.lastKnownEntity = null;
getEntity();
}
public boolean hasEntityId() {
return entityId != EntityData.NULL_ENTITY_ID;
}
public boolean hasEntity() {
return getEntity() != null;
}
public EntityData getEntity() {
if (!hasEntityId()) {
return null;
}
EntityData entity = getClient().getWorld().getData().getEntity(getEntityId());
if (entity != lastKnownEntity) {
getClient().onLocalPlayerEntityChanged(entity, lastKnownEntity);
this.lastKnownEntity = entity;
}
return entity;
}
public Selection getSelection() {

View File

@ -55,6 +55,7 @@ public abstract class MutableString {
return data;
}
@Override
public String toString() {
return get();
}

View File

@ -18,8 +18,11 @@
package ru.windcorp.progressia.client.world;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import glm.mat._4.Mat4;
@ -38,23 +41,63 @@ import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSupplier;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizers;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericChunk;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
public class ChunkRender {
public class ChunkRender
implements GenericChunk<
ChunkRender,
BlockRender,
TileRender,
TileRenderStack
> {
private final WorldRender world;
private final ChunkData data;
private boolean needsUpdate;
private Model model = null;
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists =
Collections.synchronizedMap(new WeakHashMap<>());
public ChunkRender(WorldRender world, ChunkData data) {
this.world = world;
this.data = data;
}
@Override
public Vec3i getPosition() {
return getData().getPosition();
}
@Override
public BlockRender getBlock(Vec3i posInChunk) {
return BlockRenderRegistry.getInstance().get(
getData().getBlock(posInChunk).getId()
);
}
@Override
public TileRenderStack getTiles(Vec3i blockInChunk, BlockFace face) {
return getTileStackWrapper(getData().getTiles(blockInChunk, face));
}
@Override
public boolean hasTiles(Vec3i blockInChunk, BlockFace face) {
return getData().hasTiles(blockInChunk, face);
}
private TileRenderStack getTileStackWrapper(TileDataStack tileDataList) {
return tileRenderLists.computeIfAbsent(
tileDataList,
TileRenderStackImpl::new
);
}
public WorldRender getWorld() {
return world;
}
@ -63,23 +106,13 @@ public class ChunkRender {
return data;
}
public void markForUpdate() {
this.needsUpdate = true;
public synchronized void markForUpdate() {
getWorld().markChunkForUpdate(getPosition());
}
public boolean needsUpdate() {
return needsUpdate;
}
public BlockRender getBlock(Vec3i posInChunk) {
return BlockRenderRegistry.getInstance().get(
getData().getBlock(posInChunk).getId()
);
}
public void render(ShapeRenderHelper renderer) {
if (model == null || needsUpdate()) {
buildModel();
public synchronized void render(ShapeRenderHelper renderer) {
if (model == null) {
return;
}
renderer.pushTransform().translate(
@ -93,7 +126,7 @@ public class ChunkRender {
renderer.popTransform();
}
private void buildModel() {
public synchronized void update() {
Collection<ChunkRenderOptimizer> optimizers =
ChunkRenderOptimizers.getAllSuppliers().stream()
.map(ChunkRenderOptimizerSupplier::createOptimizer)
@ -121,7 +154,6 @@ public class ChunkRender {
.forEach(builder::addPart);
model = new StaticModel(builder);
needsUpdate = false;
}
private void buildBlock(
@ -233,4 +265,44 @@ public class ChunkRender {
);
}
private class TileRenderStackImpl extends TileRenderStack {
private final TileDataStack parent;
public TileRenderStackImpl(TileDataStack parent) {
this.parent = parent;
}
@Override
public Vec3i getBlockInChunk(Vec3i output) {
return parent.getBlockInChunk(output);
}
@Override
public ChunkRender getChunk() {
return ChunkRender.this;
}
@Override
public BlockFace getFace() {
return parent.getFace();
}
@Override
public TileRender get(int index) {
return TileRenderRegistry.getInstance().get(parent.get(index).getId());
}
@Override
public int size() {
return parent.size();
}
@Override
public TileDataStack getData() {
return parent;
}
}
}

View File

@ -20,79 +20,172 @@ package ru.windcorp.progressia.client.world;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.util.VectorUtil;
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;
import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.common.world.generic.GenericWorld;
public class WorldRender {
public class WorldRender
implements GenericWorld<
BlockRender,
TileRender,
TileRenderStack,
ChunkRender,
EntityRenderable
> {
private final WorldData data;
private final Client client;
private final Map<ChunkData, ChunkRender> chunks = new HashMap<>();
private final Map<ChunkData, ChunkRender> chunks =
Collections.synchronizedMap(new HashMap<>());
private final Map<EntityData, EntityRenderable> entityModels =
Collections.synchronizedMap(new WeakHashMap<>());
public WorldRender(WorldData data) {
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
public WorldRender(WorldData data, Client client) {
this.data = data;
this.client = client;
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));
addChunk(chunk);
}
@Override
public void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
chunks.remove(chunk);
removeChunk(chunk);
}
});
}
protected void addChunk(ChunkData chunk) {
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
markChunkForUpdate(chunk.getPosition());
}
protected void removeChunk(ChunkData chunk) {
chunks.remove(chunk);
}
public WorldData getData() {
return data;
}
public Client getClient() {
return client;
}
public ChunkRender getChunk(ChunkData chunkData) {
return chunks.get(chunkData);
}
@Override
public ChunkRender getChunk(Vec3i pos) {
return chunks.get(getData().getChunk(pos));
}
@Override
public Collection<ChunkRender> getChunks() {
return chunks.values();
}
public void render(ShapeRenderHelper renderer) {
for (ChunkRender chunk : getChunks()) {
chunk.render(renderer);
}
@Override
public Collection<EntityRenderable> getEntities() {
return entityModels.values();
}
public void render(ShapeRenderHelper renderer) {
updateChunks();
getChunks().forEach(chunk -> chunk.render(renderer));
renderEntities(renderer);
}
private void updateChunks() {
synchronized (chunksToUpdate) {
if (chunksToUpdate.isEmpty()) return;
int updates = updateChunksNearLocalPlayer();
int maximumUpdates = getMaximumChunkUpdatesPerFrame();
updateRandomChunks(maximumUpdates - updates);
}
}
private int updateChunksNearLocalPlayer() {
EntityData entity = getClient().getLocalPlayer().getEntity();
if (entity == null) return 0;
int[] updates = new int[] { 0 };
VectorUtil.iterateCuboidAround(entity.getChunkCoords(null), 3, chunkPos -> {
if (chunksToUpdate.contains(chunkPos)) {
getChunk(chunkPos).update();
chunksToUpdate.remove(chunkPos);
updates[0]++;
}
});
return updates[0];
}
private void updateRandomChunks(int allowedUpdates) {
if (allowedUpdates <= 0) return;
for (Iterator<Vec3i> it = chunksToUpdate.iterator(); it.hasNext();) {
Vec3i chunkPos = it.next();
ChunkRender chunk = getChunk(chunkPos);
if (chunk != null) {
chunk.update();
allowedUpdates--;
}
it.remove();
if (allowedUpdates <= 0) return;
}
}
private int getMaximumChunkUpdatesPerFrame() {
return 1;
}
public int getPendingChunkUpdates() {
return chunksToUpdate.size();
}
private void renderEntities(ShapeRenderHelper renderer) {
FaceCulling.push(false);
for (ChunkRender chunk : getChunks()) {
chunk.getData().forEachEntity(entity -> {
renderer.pushTransform().translate(entity.getPosition());
getEntityRenderable(entity).render(renderer);
renderer.popTransform();
});
}
getData().forEachEntity(entity -> {
renderer.pushTransform().translate(entity.getPosition());
getEntityRenderable(entity).render(renderer);
renderer.popTransform();
});
FaceCulling.pop();
}
@ -109,4 +202,10 @@ public class WorldRender {
.createRenderable(entity);
}
public void markChunkForUpdate(Vec3i chunkPos) {
if (getData().getChunk(chunkPos) != null) {
chunksToUpdate.add(chunkPos);
}
}
}

View File

@ -19,9 +19,10 @@ package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.generic.GenericBlock;
import ru.windcorp.progressia.client.graphics.model.Renderable;
public abstract class BlockRender extends Namespaced {
public abstract class BlockRender extends Namespaced implements GenericBlock {
public BlockRender(String id) {
super(id);

View File

@ -18,8 +18,8 @@
package ru.windcorp.progressia.client.world.cro;
import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
import static ru.windcorp.progressia.common.world.tile.GenericTileStack.TILES_PER_FACE;
import static ru.windcorp.progressia.common.world.block.BlockFace.BLOCK_FACE_COUNT;
import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE;
import java.util.ArrayList;
import java.util.Collection;

View File

@ -3,8 +3,9 @@ package ru.windcorp.progressia.client.world.entity;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.GenericEntity;
public abstract class EntityRenderable implements Renderable {
public abstract class EntityRenderable implements Renderable, GenericEntity {
private final EntityData data;
@ -16,6 +17,16 @@ public abstract class EntityRenderable implements Renderable {
return data;
}
@Override
public Vec3 getPosition() {
return getData().getPosition();
}
@Override
public String getId() {
return getData().getId();
}
public void getViewPoint(Vec3 output) {
output.set(0, 0, 0);
}

View File

@ -116,6 +116,8 @@ public abstract class NPedModel extends EntityRenderable {
this.body = body;
this.head = head;
this.scale = scale;
evaluateAngles();
}
@Override

View File

@ -5,8 +5,9 @@ import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericTile;
public class TileRender extends Namespaced {
public class TileRender extends Namespaced implements GenericTile {
public TileRender(String id) {
super(id);

View File

@ -0,0 +1,16 @@
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
public abstract class TileRenderStack
extends GenericTileStack<
TileRenderStack,
TileRender,
ChunkRender
> {
public abstract TileDataStack getData();
}

View File

@ -1,6 +1,11 @@
package ru.windcorp.progressia.common.comms.controls;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.common.world.DecodingException;
public class PacketControl extends Packet {
@ -15,4 +20,14 @@ public class PacketControl extends Packet {
return control;
}
@Override
public void read(DataInput input) throws IOException, DecodingException {
// TODO implement controls
}
@Override
public void write(DataOutput output) throws IOException {
// implement controls
}
}

View File

@ -1,11 +1,19 @@
package ru.windcorp.progressia.common.comms.packets;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class Packet extends Namespaced {
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.DecodingException;
public abstract class Packet extends Namespaced {
public Packet(String id) {
super(id);
}
public abstract void read(DataInput input) throws IOException, DecodingException;
public abstract void write(DataOutput output) throws IOException;
}

View File

@ -1,38 +0,0 @@
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

@ -1,24 +0,0 @@
package ru.windcorp.progressia.common.comms.packets;
public class PacketSetLocalPlayer extends Packet {
private long localPlayerEntityId;
public PacketSetLocalPlayer(long entityId) {
this("Core:SetLocalPlayer", entityId);
}
protected PacketSetLocalPlayer(String id, long entityId) {
super(id);
this.localPlayerEntityId = entityId;
}
public long getLocalPlayerEntityId() {
return localPlayerEntityId;
}
public void setLocalPlayerEntityId(long localPlayerEntityId) {
this.localPlayerEntityId = localPlayerEntityId;
}
}

View File

@ -8,10 +8,12 @@ public class OptimizedStateStorage extends StateStorage {
this.ints = new int[sizes.getInts()];
}
@Override
public int getInt(int index) {
return ints[index];
}
@Override
public void setInt(int index, int value) {
ints[index] = value;
}

View File

@ -32,6 +32,7 @@ public class ByteBufferInputStream extends InputStream {
this.buffer = buffer;
}
@Override
public int read() {
if (!buffer.hasRemaining()) {
return -1;
@ -39,6 +40,7 @@ public class ByteBufferInputStream extends InputStream {
return buffer.get() & 0xFF;
}
@Override
public int read(byte[] bytes, int off, int len) {
if (!buffer.hasRemaining()) {
return -1;

View File

@ -30,6 +30,7 @@ public class ByteBufferOutputStream extends OutputStream {
this.buffer = buffer;
}
@Override
public void write(int b) throws IOException {
try {
buffer.put((byte) b);
@ -38,6 +39,7 @@ public class ByteBufferOutputStream extends OutputStream {
}
}
@Override
public void write(byte[] bytes, int off, int len) throws IOException {
try {
buffer.put(bytes, off, len);

View File

@ -25,7 +25,7 @@ public class DataBuffer {
@Override
public int read() throws IOException {
if (DataBuffer.this.position >= buffer.size()) return -1;
int result = buffer.getQuick(DataBuffer.this.position);
int result = buffer.getQuick(DataBuffer.this.position) & 0xFF;
++DataBuffer.this.position;
return result;
}

View File

@ -44,6 +44,7 @@ public class SizeLimitedList<E> extends ForwardingList<E> {
return standardAddAll(index, elements);
}
@Override
public boolean add(E e) {
checkMaxSize();
return delegate().add(e);

View File

@ -19,10 +19,10 @@ public class VectorUtil {
X, Y, Z, W;
}
public static void forEachVectorInCuboid(
public static void iterateCuboid(
int x0, int y0, int z0,
int x1, int y1, int z1,
Consumer<Vec3i> action
Consumer<? super Vec3i> action
) {
Vec3i cursor = Vectors.grab3i();
@ -38,6 +38,57 @@ public class VectorUtil {
Vectors.release(cursor);
}
public static void iterateCuboid(
Vec3i vMin, Vec3i vMax,
Consumer<? super Vec3i> action
) {
iterateCuboid(vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z, action);
}
public static void iterateCuboidAround(
int cx, int cy, int cz,
int dx, int dy, int dz,
Consumer<? super Vec3i> action
) {
if (dx < 0) throw new IllegalArgumentException("dx " + dx + " is negative");
if (dy < 0) throw new IllegalArgumentException("dy " + dx + " is negative");
if (dz < 0) throw new IllegalArgumentException("dz " + dx + " is negative");
if (dx % 2 == 0) throw new IllegalArgumentException("dx " + dx + " is even, only odd accepted");
if (dy % 2 == 0) throw new IllegalArgumentException("dy " + dy + " is even, only odd accepted");
if (dz % 2 == 0) throw new IllegalArgumentException("dz " + dz + " is even, only odd accepted");
dx /= 2;
dy /= 2;
dz /= 2;
iterateCuboid(cx - dx, cy - dy, cz - dz, cx + dx + 1, cy + dy + 1, cz + dz + 1, action);
}
public static void iterateCuboidAround(
Vec3i center,
Vec3i diameters,
Consumer<? super Vec3i> action
) {
iterateCuboidAround(center.x, center.y, center.z, diameters.x, diameters.y, diameters.z, action);
}
public static void iterateCuboidAround(
int cx, int cy, int cz,
int diameter,
Consumer<? super Vec3i> action
) {
iterateCuboidAround(cx, cy, cz, diameter, diameter, diameter, action);
}
public static void iterateCuboidAround(
Vec3i center,
int diameter,
Consumer<? super Vec3i> action
) {
iterateCuboidAround(center.x, center.y, center.z, diameter, action);
}
public static void applyMat4(Vec3 in, Mat4 mat, Vec3 out) {
Vec4 vec4 = Vectors.grab4();
vec4.set(in, 1f);

View File

@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@ -32,13 +31,19 @@ import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.GenericChunk;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.common.world.tile.TileReference;
import ru.windcorp.progressia.common.world.tile.TileStackIsFullException;
public class ChunkData {
public class ChunkData
implements GenericChunk<
ChunkData,
BlockData,
TileData,
TileDataStack
> {
public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
@ -54,9 +59,6 @@ public class ChunkData {
BLOCK_FACE_COUNT
];
private final List<EntityData> entities =
Collections.synchronizedList(new ArrayList<>());
private final Collection<ChunkDataListener> listeners =
Collections.synchronizedCollection(new ArrayList<>());
@ -65,6 +67,12 @@ public class ChunkData {
this.world = world;
}
@Override
public Vec3i getPosition() {
return position;
}
@Override
public BlockData getBlock(Vec3i posInChunk) {
return blocks[getBlockIndex(posInChunk)];
}
@ -81,6 +89,7 @@ public class ChunkData {
}
}
@Override
public TileDataStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) {
return tiles[getTileIndex(blockInChunk, face)];
}
@ -98,10 +107,12 @@ public class ChunkData {
this.tiles[getTileIndex(blockInChunk, face)] = tiles;
}
@Override
public boolean hasTiles(Vec3i blockInChunk, BlockFace face) {
return getTilesOrNull(blockInChunk, face) != null;
}
@Override
public TileDataStack getTiles(Vec3i blockInChunk, BlockFace face) {
int index = getTileIndex(blockInChunk, face);
@ -144,10 +155,6 @@ public class ChunkData {
face.getId();
}
public List<EntityData> getEntities() {
return entities;
}
private static void checkLocalCoordinates(Vec3i posInChunk) {
if (!isInBounds(posInChunk)) {
throw new IllegalCoordinatesException(
@ -176,7 +183,7 @@ public class ChunkData {
}
public void forEachBlock(Consumer<Vec3i> action) {
VectorUtil.forEachVectorInCuboid(
VectorUtil.iterateCuboid(
0, 0, 0,
BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK,
action
@ -203,26 +210,6 @@ public class ChunkData {
forEachTileStack(stack -> stack.forEach(tileData -> action.accept(stack, tileData)));
}
public void forEachEntity(Consumer<EntityData> action) {
getEntities().forEach(action);
}
public int getX() {
return position.x;
}
public int getY() {
return position.y;
}
public int getZ() {
return position.z;
}
public Vec3i getPosition() {
return position;
}
public WorldData getWorld() {
return world;
}
@ -407,6 +394,24 @@ public class ChunkData {
report(null, tile);
}
@Override
public void load(TileData tile, int tag) {
addFarthest(tile);
int assignedTag = getIndexByTag(tag);
if (assignedTag == tag) return;
if (assignedTag == -1) {
throw new IllegalArgumentException("Tag " + tag + " already used by tile at index " + getIndexByTag(tag));
}
indicesByTag[tagsByIndex[size() - 1]] = -1;
tagsByIndex[size() - 1] = tag;
indicesByTag[tag] = size() - 1;
assert checkConsistency();
}
@Override
public TileData remove(int index) {
TileData previous = get(index); // checks index

View File

@ -0,0 +1,13 @@
package ru.windcorp.progressia.common.world;
import glm.vec._3.i.Vec3i;
public abstract class PacketChunkChange extends PacketWorldChange {
public PacketChunkChange(String id) {
super(id);
}
public abstract void getAffectedChunk(Vec3i output);
}

View File

@ -0,0 +1,56 @@
package ru.windcorp.progressia.common.world;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import glm.vec._3.i.Vec3i;
public class PacketRevokeChunk extends PacketChunkChange {
private final Vec3i position = new Vec3i();
public PacketRevokeChunk() {
this("Core:RevokeChunk");
}
protected PacketRevokeChunk(String id) {
super(id);
}
public void set(Vec3i chunkPos) {
this.position.set(chunkPos.x, chunkPos.y, chunkPos.z);
}
@Override
public void read(DataInput input) throws IOException {
this.position.set(input.readInt(), input.readInt(), input.readInt());
}
@Override
public void write(DataOutput output) throws IOException {
output.writeInt(this.position.x);
output.writeInt(this.position.y);
output.writeInt(this.position.z);
}
@Override
public void apply(WorldData world) {
synchronized (world) {
ChunkData chunk = world.getChunk(position);
if (chunk != null) {
world.removeChunk(chunk);
}
}
}
@Override
public void getAffectedChunk(Vec3i output) {
output.set(getPosition().x, getPosition().y, getPosition().z);
}
public Vec3i getPosition() {
return position;
}
}

View File

@ -0,0 +1,72 @@
package ru.windcorp.progressia.common.world;
import java.io.DataInput;
import java.io.DataOutput;
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;
public class PacketSendChunk extends PacketChunkChange {
private final DataBuffer data = new DataBuffer();
private final Vec3i position = new Vec3i();
public PacketSendChunk() {
this("Core:SendChunk");
}
protected PacketSendChunk(String id) {
super(id);
}
public void set(ChunkData chunk) {
this.position.set(chunk.getX(), chunk.getY(), chunk.getZ());
try {
ChunkIO.save(chunk, this.data.getOutputStream());
} catch (IOException e) {
// Impossible
}
}
@Override
public void read(DataInput input) throws IOException {
this.position.set(input.readInt(), input.readInt(), input.readInt());
this.data.fill(input, input.readInt());
}
@Override
public void write(DataOutput output) throws IOException {
output.writeInt(this.position.x);
output.writeInt(this.position.y);
output.writeInt(this.position.z);
output.writeInt(this.data.getSize());
this.data.flush(output);
}
@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");
}
}
@Override
public void getAffectedChunk(Vec3i output) {
output.set(getPosition().x, getPosition().y, getPosition().z);
}
public Vec3i getPosition() {
return position;
}
public DataBuffer getData() {
return data;
}
}

View File

@ -0,0 +1,39 @@
package ru.windcorp.progressia.common.world;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import ru.windcorp.progressia.common.comms.packets.Packet;
public class PacketSetLocalPlayer extends Packet {
private long entityId;
public PacketSetLocalPlayer() {
this("Core:SetLocalPlayer");
}
protected PacketSetLocalPlayer(String id) {
super(id);
}
public void set(long entityId) {
this.entityId = entityId;
}
@Override
public void read(DataInput input) throws IOException, DecodingException {
this.entityId = input.readLong();
}
@Override
public void write(DataOutput output) throws IOException {
output.writeLong(this.entityId);
}
public long getEntityId() {
return entityId;
}
}

View File

@ -1,6 +1,6 @@
package ru.windcorp.progressia.common.comms.packets;
package ru.windcorp.progressia.common.world;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.comms.packets.Packet;
public abstract class PacketWorldChange extends Packet {

View File

@ -20,28 +20,42 @@ package ru.windcorp.progressia.common.world;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import gnu.trove.impl.sync.TSynchronizedLongObjectMap;
import gnu.trove.TCollections;
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.test.TestContent;
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;
public class WorldData {
public class WorldData
implements GenericWorld<
BlockData,
TileData,
TileDataStack,
ChunkData,
EntityData
> {
private final TLongObjectMap<ChunkData> chunksByPos =
new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this);
private final ChunkMap<ChunkData> chunksByPos = new LongBasedChunkMap<>(
TCollections.synchronizedMap(new TLongObjectHashMap<>())
);
private final Collection<ChunkData> chunks =
Collections.unmodifiableCollection(chunksByPos.valueCollection());
Collections.unmodifiableCollection(chunksByPos.values());
private final TLongObjectMap<EntityData> entitiesById =
new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this);
TCollections.synchronizedMap(new TLongObjectHashMap<>());
private final Collection<EntityData> entities =
Collections.unmodifiableCollection(entitiesById.valueCollection());
@ -55,21 +69,37 @@ public class WorldData {
}
public void tmp_generate() {
final int size = 10;
Vec3i cursor = new Vec3i(0, 0, 0);
@Override
public ChunkData getChunk(Vec3i pos) {
return chunksByPos.get(pos);
}
for (cursor.x = -(size / 2); cursor.x <= (size / 2); ++cursor.x) {
for (cursor.y = -(size / 2); cursor.y <= (size / 2); ++cursor.y) {
for (cursor.z = -(size / 2); cursor.z <= (size / 2); ++cursor.z) {
ChunkData chunk = new ChunkData(cursor, this);
TestContent.generateChunk(chunk);
addChunk(chunk);
}
}
@Override
public Collection<ChunkData> getChunks() {
return chunks;
}
public ChunkSet getLoadedChunks() {
return chunksByPos.keys();
}
@Override
public Collection<EntityData> getEntities() {
return entities;
}
@Override
public void forEachEntity(Consumer<? super EntityData> action) {
synchronized (entitiesById) { // TODO HORRIBLY MUTILATE THE CORPSE OF TROVE4J so that gnu.trove.impl.sync.SynchronizedCollection.forEach is synchronized
getEntities().forEach(action);
}
}
public TLongSet getLoadedEntities() {
return entitiesById.keySet();
}
private void addChunkListeners(ChunkData chunk) {
getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener));
}
@ -77,9 +107,7 @@ public class WorldData {
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",
@ -87,11 +115,7 @@ public class WorldData {
));
}
chunksByPos.put(key, chunk);
chunk.forEachEntity(entity ->
entitiesById.put(entity.getEntityId(), entity)
);
chunksByPos.put(chunk, chunk);
chunk.onLoaded();
getListeners().forEach(l -> l.onChunkLoaded(this, chunk));
@ -101,30 +125,7 @@ public class WorldData {
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());
}
public ChunkData getChunk(Vec3i pos) {
return chunksByPos.get(CoordinatePacker.pack3IntsIntoLong(pos));
}
public ChunkData getChunkByBlock(Vec3i blockInWorld) {
return getChunk(Coordinates.convertInWorldToChunk(blockInWorld, null));
}
public BlockData getBlock(Vec3i blockInWorld) {
ChunkData chunk = getChunkByBlock(blockInWorld);
if (chunk == null) return null;
return chunk.getBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null));
chunksByPos.remove(chunk);
}
public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) {
@ -139,20 +140,47 @@ public class WorldData {
chunk.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, notify);
}
public Collection<ChunkData> getChunks() {
return chunks;
}
public TLongSet getChunkKeys() {
return chunksByPos.keySet();
}
public EntityData getEntity(long entityId) {
return entitiesById.get(entityId);
}
public Collection<EntityData> getEntities() {
return entities;
public void addEntity(EntityData entity) {
Objects.requireNonNull(entity, "entity");
EntityData previous = entitiesById.putIfAbsent(entity.getEntityId(), entity);
if (previous != null) {
String message = "Cannot add entity " + entity + ": ";
if (previous == entity) {
message += "already present";
} else {
message += "entity with the same EntityID already present (" + previous + ")";
}
throw new IllegalStateException(message);
}
getListeners().forEach(l -> l.onEntityAdded(this, entity));
}
public void removeEntity(long entityId) {
synchronized (entitiesById) {
EntityData entity = entitiesById.get(entityId);
if (entity == null) {
throw new IllegalArgumentException("Entity with EntityID " + EntityData.formatEntityId(entityId) + " not present");
} else {
removeEntity(entity);
}
}
}
public void removeEntity(EntityData entity) {
Objects.requireNonNull(entity, "entity");
getListeners().forEach(l -> l.beforeEntityRemoved(this, entity));
entitiesById.remove(entity.getEntityId());
}
public float getTime() {

View File

@ -3,6 +3,7 @@ package ru.windcorp.progressia.common.world;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.entity.EntityData;
public interface WorldDataListener {
@ -31,4 +32,18 @@ public interface WorldDataListener {
*/
default void beforeChunkUnloaded(WorldData world, ChunkData chunk) {}
/**
* Invoked whenever an {@link EntityData} has been added.
* @param world the world instance
* @param entity the entity that has been added
*/
default void onEntityAdded(WorldData world, EntityData entity) {}
/**
* Invoked whenever an {@link EntityData} is about to be removed.
* @param world the world instance
* @param entity the entity that is going to be removed
*/
default void beforeEntityRemoved(WorldData world, EntityData entity) {}
}

View File

@ -20,8 +20,9 @@ package ru.windcorp.progressia.common.world.block;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.generic.GenericBlock;
public class BlockData extends Namespaced {
public class BlockData extends Namespaced implements GenericBlock {
public BlockData(String id) {
super(id);

View File

@ -0,0 +1,56 @@
package ru.windcorp.progressia.common.world.block;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.PacketChunkChange;
import ru.windcorp.progressia.common.world.WorldData;
public class PacketSetBlock extends PacketChunkChange {
private String id;
private final Vec3i blockInWorld = new Vec3i();
public PacketSetBlock() {
this("Core:SetBlock");
}
protected PacketSetBlock(String id) {
super(id);
}
public void set(BlockData block, Vec3i blockInWorld) {
this.id = block.getId();
this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z);
}
@Override
public void read(DataInput input) throws IOException, DecodingException {
this.id = input.readUTF();
this.blockInWorld.set(input.readInt(), input.readInt(), input.readInt());
}
@Override
public void write(DataOutput output) throws IOException {
output.writeUTF(this.id);
output.writeInt(this.blockInWorld.x);
output.writeInt(this.blockInWorld.y);
output.writeInt(this.blockInWorld.z);
}
@Override
public void apply(WorldData world) {
BlockData block = BlockDataRegistry.getInstance().get(id);
world.setBlock(blockInWorld, block, true);
}
@Override
public void getAffectedChunk(Vec3i output) {
Coordinates.convertInWorldToChunk(this.blockInWorld, output);
}
}

View File

@ -2,19 +2,25 @@ package ru.windcorp.progressia.common.world.entity;
import glm.vec._2.Vec2;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.jputil.chars.StringUtil;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.state.StatefulObject;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.generic.GenericEntity;
public class EntityData extends StatefulObject implements Collideable {
public class EntityData extends StatefulObject implements Collideable, GenericEntity {
private final Vec3 position = new Vec3();
private final Vec3 velocity = new Vec3();
private final Vec2 direction = new Vec2();
/**
* The unique {@code long} value guaranteed to never be assigned to an entity as its entity ID.
* This can safely be used as a placeholder or a sentinel value.
*/
public static final long NULL_ENTITY_ID = 0x0000_0000_0000_0000;
private long entityId;
private CollisionModel collisionModel = null;
@ -25,20 +31,11 @@ public class EntityData extends StatefulObject implements Collideable {
super(EntityDataRegistry.getInstance(), id);
}
@Override
public Vec3 getPosition() {
return position;
}
public Vec3i getBlockInWorld(Vec3i output) {
if (output == null) output = new Vec3i();
return position.round(output);
}
public Vec3i getChunkCoords(Vec3i output) {
output = getBlockInWorld(output);
return Coordinates.convertInWorldToChunk(output, output);
}
public void setPosition(Vec3 position) {
move(position.sub_(getPosition()));
}
@ -79,6 +76,9 @@ public class EntityData extends StatefulObject implements Collideable {
}
public void setEntityId(long entityId) {
if (entityId == NULL_ENTITY_ID) {
throw new IllegalArgumentException("Attempted to set entity ID to NULL_ENTITY_ID (" + entityId + ")");
}
this.entityId = entityId;
}
@ -138,4 +138,17 @@ public class EntityData extends StatefulObject implements Collideable {
return output;
}
@Override
public String toString() {
return new StringBuilder(super.toString())
.append(" (EntityID ")
.append(StringUtil.toFullHex(getEntityId()))
.append(")")
.toString();
}
public static String formatEntityId(long entityId) {
return new String(StringUtil.toFullHex(entityId));
}
}

View File

@ -4,10 +4,11 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import ru.windcorp.progressia.common.comms.packets.PacketWorldChange;
import ru.windcorp.progressia.common.state.IOContext;
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.PacketWorldChange;
import ru.windcorp.progressia.common.world.WorldData;
public class PacketEntityChange extends PacketWorldChange {
@ -43,6 +44,28 @@ public class PacketEntityChange extends PacketWorldChange {
return buffer.getWriter();
}
public void set(EntityData entity) {
this.entityId = entity.getEntityId();
try {
entity.write(this.buffer.getWriter(), IOContext.COMMS);
} catch (IOException e) {
CrashReports.report(e, "Entity could not be written");
}
}
@Override
public void read(DataInput input) throws IOException, DecodingException {
this.entityId = input.readLong();
this.buffer.fill(input, input.readInt());
}
@Override
public void write(DataOutput output) throws IOException {
output.writeLong(this.entityId);
output.writeInt(this.buffer.getSize());
this.buffer.flush(output);
}
@Override
public void apply(WorldData world) {
EntityData entity = world.getEntity(getEntityId());

View File

@ -0,0 +1,41 @@
package ru.windcorp.progressia.common.world.entity;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import ru.windcorp.progressia.common.world.PacketWorldChange;
import ru.windcorp.progressia.common.world.WorldData;
public class PacketRevokeEntity extends PacketWorldChange {
private long entityId;
public PacketRevokeEntity() {
this("Core:RevokeEntity");
}
protected PacketRevokeEntity(String id) {
super(id);
}
public void set(long entityId) {
this.entityId = entityId;
}
@Override
public void read(DataInput input) throws IOException {
this.entityId = input.readLong();
}
@Override
public void write(DataOutput output) throws IOException {
output.writeLong(this.entityId);
}
@Override
public void apply(WorldData world) {
world.removeEntity(this.entityId);
}
}

View File

@ -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<V> {
/*
* 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<? super Vec3i, ? super V, ? extends V> 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 <C extends GenericChunk<C, ?, ?, ?>> V compute(C chunk, BiFunction<? super C, ? super V, ? extends V> remappingFunction) {
V newValue = remappingFunction.apply(chunk, get(chunk));
if (newValue == null) {
remove(chunk);
} else {
put(chunk, newValue);
}
return newValue;
}
/*
* Views
*/
Collection<V> values();
ChunkSet keys();
/*
* Bulk operations
*/
boolean removeIf(BiPredicate<? super Vec3i, ? super V> condition);
void forEach(BiConsumer<? super Vec3i, ? super V> action);
default <C extends GenericChunk<C, ?, ?, ?>> void forEachIn(GenericWorld<?, ?, ?, C, ?> world, BiConsumer<? super C, ? super V> action) {
forEach((pos, value) -> {
C chunk = world.getChunk(pos);
if (chunk == null) return;
action.accept(chunk, value);
});
}
}

View File

@ -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<Vec3i> {
/*
* 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 <C extends GenericChunk<C, ?, ?, ?>> void forEachIn(GenericWorld<?, ?, ?, C, ?> world, Consumer<? super C> 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<? extends Vec3i> other) {
boolean[] hasMissing = new boolean[] { false };
other.forEach(v -> {
if (!contains(v)) {
hasMissing[0] = true;
}
});
return hasMissing[0];
}
default boolean containsAny(Iterable<? extends Vec3i> other) {
boolean[] hasPresent = new boolean[] { false };
other.forEach(v -> {
if (contains(v)) {
hasPresent[0] = true;
}
});
return hasPresent[0];
}
default void addAll(Iterable<? extends Vec3i> other) {
other.forEach(this::add);
}
default void removeAll(Iterable<? extends Vec3i> other) {
other.forEach(this::remove);
}
default void retainAll(Iterable<? extends Vec3i> 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<? extends Vec3i> collection = null;
int otherSize = -1;
if (other instanceof Set<?>) {
collection = (Set<? extends Vec3i>) other;
} else if (other instanceof Collection<?>) {
Collection<? extends Vec3i> otherAsCollection = ((Collection<? extends Vec3i>) other);
otherSize = otherAsCollection.size();
if (otherSize < threshold) {
collection = otherAsCollection;
}
}
if (collection != null) {
final Collection<? extends Vec3i> 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<? super Vec3i> condition) {
for (Iterator<? extends Vec3i> it = iterator(); it.hasNext();) {
if (condition.test(it.next())) {
it.remove();
}
}
}
default void retainIf(Predicate<? super Vec3i> condition) {
for (Iterator<? extends Vec3i> it = iterator(); it.hasNext();) {
if (!condition.test(it.next())) {
it.remove();
}
}
}
default boolean containsAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
boolean[] hasMissing = new boolean[] { false };
chunks.forEach(c -> {
if (!contains(c.getPosition())) {
hasMissing[0] = true;
}
});
return hasMissing[0];
}
default boolean containsAnyChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
boolean[] hasPresent = new boolean[] { false };
chunks.forEach(c -> {
if (contains(c.getPosition())) {
hasPresent[0] = true;
}
});
return hasPresent[0];
}
default void addAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
chunks.forEach(this::add);
}
default void removeAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
chunks.forEach(this::remove);
}
}

View File

@ -0,0 +1,270 @@
package ru.windcorp.progressia.common.world.generic;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import glm.vec._3.i.Vec3i;
import gnu.trove.set.hash.TLongHashSet;
public class ChunkSets {
public static ChunkSet newHashSet() {
return new LongBasedChunkSet(new TLongHashSet());
}
public static ChunkSet newSyncHashSet(Object mutex) {
return new SynchronizedChunkSet(new LongBasedChunkSet(new TLongHashSet()), mutex);
}
public static ChunkSet newSyncHashSet() {
return newSyncHashSet(null);
}
public static ChunkSet empty() {
return EMPTY_SET;
}
private ChunkSets() {}
private final static ChunkSet EMPTY_SET = new ChunkSet() {
@Override
public Iterator<Vec3i> iterator() {
return new Iterator<Vec3i>() {
@Override
public boolean hasNext() {
return false;
}
@Override
public Vec3i next() {
throw new NoSuchElementException();
}
};
}
@Override
public int size() {
return 0;
}
@Override
public boolean contains(Vec3i pos) {
return false;
}
@Override
public boolean add(Vec3i pos) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Vec3i pos) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(ChunkSet other) {
return false;
}
@Override
public boolean containsAny(ChunkSet other) {
return false;
}
@Override
public void addAll(ChunkSet other) {
throw new UnsupportedOperationException();
}
@Override
public void removeAll(ChunkSet other) {
throw new UnsupportedOperationException();
}
@Override
public void retainAll(ChunkSet other) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
};
private static class SynchronizedChunkSet implements ChunkSet {
private final ChunkSet parent;
private final Object mutex;
public SynchronizedChunkSet(ChunkSet parent, Object mutex) {
Objects.requireNonNull(parent, "parent");
this.parent = parent;
this.mutex = mutex == null ? this : mutex;
}
@Override
public Iterator<Vec3i> iterator() {
return parent.iterator(); // Must be synchronized manually by user!
}
@Override
public void forEach(Consumer<? super Vec3i> action) {
synchronized (mutex) { parent.forEach(action); }
}
@Override
public int size() {
synchronized (mutex) { return parent.size(); }
}
@Override
public boolean isEmpty() {
synchronized (mutex) { return parent.isEmpty(); }
}
@Override
public boolean contains(Vec3i pos) {
synchronized (mutex) { return parent.contains(pos); }
}
@Override
public boolean add(Vec3i pos) {
synchronized (mutex) { return parent.add(pos); }
}
@Override
public boolean remove(Vec3i pos) {
synchronized (mutex) { return parent.remove(pos); }
}
@Override
public boolean contains(int x, int y, int z) {
synchronized (mutex) { return parent.contains(x, y, z); }
}
@Override
public boolean add(int x, int y, int z) {
synchronized (mutex) { return parent.add(x, y, z); }
}
@Override
public boolean remove(int x, int y, int z) {
synchronized (mutex) { return parent.remove(x, y, z); }
}
@Override
public boolean contains(GenericChunk<?, ?, ?, ?> chunk) {
synchronized (mutex) { return parent.contains(chunk); }
}
@Override
public boolean add(GenericChunk<?, ?, ?, ?> chunk) {
synchronized (mutex) { return parent.add(chunk); }
}
@Override
public boolean remove(GenericChunk<?, ?, ?, ?> chunk) {
synchronized (mutex) { return parent.remove(chunk); }
}
@Override
public <C extends GenericChunk<C, ?, ?, ?>> void forEachIn(GenericWorld<?, ?, ?, C, ?> world,
Consumer<? super C> action) {
synchronized (mutex) { parent.forEachIn(world, action); }
}
@Override
public boolean containsAll(ChunkSet other) {
synchronized (mutex) { return parent.containsAll(other); }
}
@Override
public boolean containsAny(ChunkSet other) {
synchronized (mutex) { return parent.containsAny(other); }
}
@Override
public void addAll(ChunkSet other) {
synchronized (mutex) { parent.addAll(other); }
}
@Override
public void removeAll(ChunkSet other) {
synchronized (mutex) { parent.removeAll(other); }
}
@Override
public void retainAll(ChunkSet other) {
synchronized (mutex) { parent.retainAll(other); }
}
@Override
public void clear() {
synchronized (mutex) { parent.clear(); }
}
@Override
public boolean containsAll(Iterable<? extends Vec3i> other) {
synchronized (mutex) { return parent.containsAll(other); }
}
@Override
public boolean containsAny(Iterable<? extends Vec3i> other) {
synchronized (mutex) { return parent.containsAny(other); }
}
@Override
public void addAll(Iterable<? extends Vec3i> other) {
synchronized (mutex) { parent.addAll(other); }
}
@Override
public void removeAll(Iterable<? extends Vec3i> other) {
synchronized (mutex) { parent.removeAll(other); }
}
@Override
public void retainAll(Iterable<? extends Vec3i> other) {
synchronized (mutex) { parent.retainAll(other); }
}
@Override
public void removeIf(Predicate<? super Vec3i> condition) {
synchronized (mutex) { parent.removeIf(condition); }
}
@Override
public void retainIf(Predicate<? super Vec3i> condition) {
synchronized (mutex) { parent.retainIf(condition); }
}
@Override
public boolean containsAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
synchronized (mutex) { return parent.containsAllChunks(chunks); }
}
@Override
public boolean containsAnyChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
synchronized (mutex) { return parent.containsAnyChunks(chunks); }
}
@Override
public void addAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
synchronized (mutex) { parent.addAllChunks(chunks); }
}
@Override
public void removeAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
synchronized (mutex) { parent.removeAllChunks(chunks); }
}
}
}

View File

@ -0,0 +1,7 @@
package ru.windcorp.progressia.common.world.generic;
public interface GenericBlock {
String getId();
}

View File

@ -0,0 +1,85 @@
package ru.windcorp.progressia.common.world.generic;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.BlockFace;
public interface GenericChunk<
Self extends GenericChunk<Self, B, T, TS>,
B extends GenericBlock,
T extends GenericTile,
TS extends GenericTileStack<TS, T, Self>
> {
public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
Vec3i getPosition();
B getBlock(Vec3i blockInChunk);
TS getTiles(Vec3i blockInChunk, BlockFace face);
boolean hasTiles(Vec3i blockInChunk, BlockFace face);
default int getX() {
return getPosition().x;
}
default int getY() {
return getPosition().y;
}
default int getZ() {
return getPosition().z;
}
default boolean containsBiC(Vec3i blockInChunk) {
return
blockInChunk.x >= 0 && blockInChunk.x < BLOCKS_PER_CHUNK &&
blockInChunk.y >= 0 && blockInChunk.y < BLOCKS_PER_CHUNK &&
blockInChunk.z >= 0 && blockInChunk.z < BLOCKS_PER_CHUNK;
}
default boolean containsBiW(Vec3i blockInWorld) {
Vec3i v = Vectors.grab3i();
v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v);
v = blockInWorld.sub(v, v);
boolean result = containsBiC(v);
Vectors.release(v);
return result;
}
default void forEachBiC(Consumer<? super Vec3i> action) {
VectorUtil.iterateCuboid(
0, 0, 0,
BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK,
action
);
}
default void forEachBiW(Consumer<? super Vec3i> action) {
VectorUtil.iterateCuboid(
Coordinates.getInWorld(getX(), 0),
Coordinates.getInWorld(getY(), 0),
Coordinates.getInWorld(getZ(), 0),
BLOCKS_PER_CHUNK,
BLOCKS_PER_CHUNK,
BLOCKS_PER_CHUNK,
action
);
}
default TS getTilesOrNull(Vec3i blockInChunk, BlockFace face) {
if (hasTiles(blockInChunk, face)) {
return getTiles(blockInChunk, face);
}
return null;
}
}

View File

@ -0,0 +1,22 @@
package ru.windcorp.progressia.common.world.generic;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.Coordinates;
public interface GenericEntity {
String getId();
Vec3 getPosition();
default Vec3i getBlockInWorld(Vec3i output) {
if (output == null) output = new Vec3i();
return getPosition().round(output);
}
default Vec3i getChunkCoords(Vec3i output) {
output = getBlockInWorld(output);
return Coordinates.convertInWorldToChunk(output, output);
}
}

View File

@ -0,0 +1,7 @@
package ru.windcorp.progressia.common.world.generic;
public interface GenericTile {
String getId();
}

View File

@ -1,4 +1,4 @@
package ru.windcorp.progressia.common.world.tile;
package ru.windcorp.progressia.common.world.generic;
import java.util.AbstractList;
import java.util.Objects;
@ -6,11 +6,14 @@ import java.util.RandomAccess;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.BlockFace;
public abstract class GenericTileStack<T extends Namespaced, C>
public abstract class GenericTileStack<
Self extends GenericTileStack<Self, T, C>,
T extends GenericTile,
C extends GenericChunk<C, ?, T, Self>
>
extends AbstractList<T>
implements RandomAccess {
@ -21,13 +24,12 @@ implements RandomAccess {
public static final int TILES_PER_FACE = 8;
public abstract Vec3i getBlockInChunk(Vec3i output);
protected abstract Vec3i getChunkPos();
public abstract C getChunk();
public abstract BlockFace getFace();
public Vec3i getBlockInWorld(Vec3i output) {
// This is safe
return Coordinates.getInWorld(getChunkPos(), getBlockInChunk(output), output);
return Coordinates.getInWorld(getChunk().getPosition(), getBlockInChunk(output), output);
}
public boolean isFull() {

View File

@ -0,0 +1,176 @@
package ru.windcorp.progressia.common.world.generic;
import java.util.Collection;
import java.util.function.Consumer;
import glm.Glm;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.BlockFace;
public interface GenericWorld<
B extends GenericBlock,
T extends GenericTile,
TS extends GenericTileStack<TS, T, C>,
C extends GenericChunk<C, B, T, TS>,
E extends GenericEntity
> {
Collection<C> getChunks();
C getChunk(Vec3i pos);
Collection<E> getEntities();
/*
* Chunks
*/
default C getChunkByBlock(Vec3i blockInWorld) {
Vec3i chunkCoords = Vectors.grab3i();
chunkCoords = Coordinates.convertInWorldToChunk(blockInWorld, chunkCoords);
C result = getChunk(chunkCoords);
Vectors.release(chunkCoords);
return result;
}
default B getBlock(Vec3i blockInWorld) {
Vec3i v = Vectors.grab3i();
B result;
C chunk = getChunk(Coordinates.convertInWorldToChunk(blockInWorld, v));
if (chunk == null) {
result = null;
} else {
result = chunk.getBlock(Coordinates.convertInWorldToInChunk(blockInWorld, v));
}
Vectors.release(v);
return result;
}
default TS getTiles(Vec3i blockInWorld, BlockFace face) {
Vec3i v = Vectors.grab3i();
TS result;
C chunk = getChunk(Coordinates.convertInWorldToChunk(blockInWorld, v));
if (chunk == null) {
result = null;
} else {
result = chunk.getTiles(Coordinates.convertInWorldToInChunk(blockInWorld, v), face);
}
Vectors.release(v);
return result;
}
default TS getTilesOrNull(Vec3i blockInWorld, BlockFace face) {
Vec3i v = Vectors.grab3i();
TS result;
C chunk = getChunk(Coordinates.convertInWorldToChunk(blockInWorld, v));
if (chunk == null) {
result = null;
} else {
result = chunk.getTilesOrNull(Coordinates.convertInWorldToInChunk(blockInWorld, v), face);
}
Vectors.release(v);
return result;
}
default boolean hasTiles(Vec3i blockInWorld, BlockFace face) {
Vec3i v = Vectors.grab3i();
boolean result;
C chunk = getChunk(Coordinates.convertInWorldToChunk(blockInWorld, v));
if (chunk == null) {
result = false;
} else {
result = chunk.hasTiles(Coordinates.convertInWorldToInChunk(blockInWorld, v), face);
}
Vectors.release(v);
return result;
}
default T getTile(Vec3i blockInWorld, BlockFace face, int layer) {
TS stack = getTilesOrNull(blockInWorld, face);
if (stack == null || stack.size() <= layer) return null;
return stack.get(layer);
}
default boolean isChunkLoaded(Vec3i pos) {
return getChunk(pos) != null;
}
default boolean isBlockLoaded(Vec3i blockInWorld) {
return getChunkByBlock(blockInWorld) != null;
}
default void forEachChunk(Consumer<? super C> action) {
getChunks().forEach(action);
}
/*
* Entities
*/
default void forEachEntity(Consumer<? super E> action) {
getEntities().forEach(action);
}
default void forEachEntityIn(Vec3i min, Vec3i max, Consumer<? super E> action) {
forEachEntity(e -> {
Vec3 pos = e.getPosition();
if (pos.x < min.x || pos.y < min.y || pos.z < min.z || pos.x > max.x || pos.y > max.y || pos.z > max.z) {
action.accept(e);
}
});
}
default void forEachEntityIn(Vec3 min, Vec3 max, Consumer<? super E> action) {
forEachEntity(e -> {
Vec3 pos = e.getPosition();
if (pos.x < min.x || pos.y < min.y || pos.z < min.z || pos.x > max.x || pos.y > max.y || pos.z > max.z) {
action.accept(e);
}
});
}
default void forEachEntityInChunk(Vec3i pos, Consumer<? super E> action) {
Vec3i v = Vectors.grab3i();
forEachEntity(e -> {
e.getChunkCoords(v);
if (Glm.equals(v, pos)) {
action.accept(e);
}
});
Vectors.release(v);
}
default void forEachEntityInChunk(C chunk, Consumer<? super E> action) {
Vec3i v = Vectors.grab3i();
forEachEntity(e -> {
e.getChunkCoords(v);
if (Glm.equals(v, chunk.getPosition())) {
action.accept(e);
}
});
Vectors.release(v);
}
default void forEachEntityWithId(String id, Consumer<? super E> action) {
forEachEntity(e -> {
if (id.equals(e.getId())) {
action.accept(e);
}
});
}
}

View File

@ -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<V> implements ChunkMap<V> {
protected final TLongObjectMap<V> impl;
private final ChunkSet keys;
public LongBasedChunkMap(TLongObjectMap<V> 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<V> values() {
return impl.valueCollection();
}
@Override
public ChunkSet keys() {
return keys;
}
@Override
public boolean removeIf(BiPredicate<? super Vec3i, ? super V> 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<? super Vec3i, ? super V> action) {
Vec3i v = Vectors.grab3i();
impl.forEachEntry((key, value) -> {
action.accept(getVector(key, v), value);
return true;
});
Vectors.release(v);
}
}

View File

@ -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<? extends Vec3i> 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<Vec3i> 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<? extends Vec3i>) other);
}
@Override
public boolean containsAny(ChunkSet other) {
return ChunkSet.super.containsAny((Iterable<? extends Vec3i>) other);
}
@Override
public void addAll(ChunkSet other) {
if (other instanceof LongBasedChunkSet) {
impl.addAll(((LongBasedChunkSet) other).impl);
return;
}
ChunkSet.super.addAll((Iterable<? extends Vec3i>) other);
}
@Override
public void removeAll(ChunkSet other) {
if (other instanceof LongBasedChunkSet) {
impl.removeAll(((LongBasedChunkSet) other).impl);
return;
}
ChunkSet.super.removeAll((Iterable<? extends Vec3i>) other);
}
@Override
public void retainAll(ChunkSet other) {
if (other instanceof LongBasedChunkSet) {
impl.retainAll(((LongBasedChunkSet) other).impl);
return;
}
ChunkSet.super.retainAll((Iterable<? extends Vec3i>) other);
}
@Override
public void clear() {
impl.clear();
}
@Override
public void forEach(Consumer<? super Vec3i> action) {
Vec3i v = Vectors.grab3i();
impl.forEach(key -> {
getVector(key, v);
action.accept(v);
return true;
});
Vectors.release(v);
}
private class IteratorImpl implements Iterator<Vec3i> {
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();
}
}
}

View File

@ -0,0 +1,61 @@
package ru.windcorp.progressia.common.world.tile;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.PacketChunkChange;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class PacketAddTile extends PacketChunkChange {
private String id;
private final Vec3i blockInWorld = new Vec3i();
private BlockFace face;
public PacketAddTile() {
this("Core:AddTile");
}
protected PacketAddTile(String id) {
super(id);
}
public void set(TileData tile, Vec3i blockInWorld, BlockFace face) {
this.id = tile.getId();
this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z);
this.face = face;
}
@Override
public void read(DataInput input) throws IOException, DecodingException {
this.id = input.readUTF();
this.blockInWorld.set(input.readInt(), input.readInt(), input.readInt());
this.face = BlockFace.getFaces().get(input.readByte());
}
@Override
public void write(DataOutput output) throws IOException {
output.writeUTF(this.id);
output.writeInt(this.blockInWorld.x);
output.writeInt(this.blockInWorld.y);
output.writeInt(this.blockInWorld.z);
output.writeByte(this.face.getId());
}
@Override
public void apply(WorldData world) {
TileData tile = TileDataRegistry.getInstance().get(id);
world.getTiles(blockInWorld, face).add(tile);
}
@Override
public void getAffectedChunk(Vec3i output) {
Coordinates.convertInWorldToChunk(this.blockInWorld, output);
}
}

View File

@ -0,0 +1,61 @@
package ru.windcorp.progressia.common.world.tile;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.PacketChunkChange;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class PacketRemoveTile extends PacketChunkChange {
private final Vec3i blockInWorld = new Vec3i();
private BlockFace face;
private int tag;
public PacketRemoveTile() {
this("Core:RemoveTile");
}
protected PacketRemoveTile(String id) {
super(id);
}
public void set(Vec3i blockInWorld, BlockFace face, int tag) {
this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z);
this.face = face;
this.tag = tag;
}
@Override
public void read(DataInput input) throws IOException, DecodingException {
this.blockInWorld.set(input.readInt(), input.readInt(), input.readInt());
this.face = BlockFace.getFaces().get(input.readByte());
this.tag = input.readInt();
}
@Override
public void write(DataOutput output) throws IOException {
output.writeInt(this.blockInWorld.x);
output.writeInt(this.blockInWorld.y);
output.writeInt(this.blockInWorld.z);
output.writeByte(this.face.getId());
output.writeInt(this.tag);
}
@Override
public void apply(WorldData world) {
TileDataStack stack = world.getTiles(blockInWorld, face);
stack.remove(stack.getIndexByTag(tag));
}
@Override
public void getAffectedChunk(Vec3i output) {
Coordinates.convertInWorldToChunk(this.blockInWorld, output);
}
}

View File

@ -18,8 +18,9 @@
package ru.windcorp.progressia.common.world.tile;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.generic.GenericTile;
public class TileData extends Namespaced {
public class TileData extends Namespaced implements GenericTile {
public TileData(String id) {
super(id);

View File

@ -1,10 +1,15 @@
package ru.windcorp.progressia.common.world.tile;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
public abstract class TileDataStack extends GenericTileStack<TileData, ChunkData> {
public abstract class TileDataStack
extends GenericTileStack<
TileDataStack,
TileData,
ChunkData
> {
/**
* Inserts the specified tile at the specified position in this stack.
@ -21,6 +26,16 @@ public abstract class TileDataStack extends GenericTileStack<TileData, ChunkData
@Override
public abstract void add(int index, TileData tile);
/**
* Adds the specified tile at the end of this stack assigning it the provided tag.
* This method is useful for copying stacks when preserving tags is necessary.
* @param tile the tile to add
* @param tag the tag to assign the new tile
* @throws IllegalArgumentException if this stack already contains a tile with the
* provided tag
*/
public abstract void load(TileData tile, int tag);
/**
* Replaces the tile at the specified position in this stack with the specified tile.
* @param index index of the tile to replace
@ -54,15 +69,6 @@ public abstract class TileDataStack extends GenericTileStack<TileData, ChunkData
public abstract int getTagByIndex(int index);
/*
* Implementation
*/
@Override
public Vec3i getChunkPos() {
return getChunk().getPosition();
}
/*
* Aliases and overloads
*/

View File

@ -1,92 +0,0 @@
package ru.windcorp.progressia.server;
import java.util.ArrayList;
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.test.TestContent;
public class ChunkLoadManager {
private final Server server;
private final Collection<Collection<? extends ChunkLoader>> allChunkLoaders =
Collections.synchronizedCollection(new ArrayList<>());
private final TLongSet requested = new TLongHashSet();
private final TLongSet toLoad = new TLongHashSet();
private final TLongSet toUnload = new TLongHashSet();
public ChunkLoadManager(Server server) {
this.server = server;
allChunkLoaders.add(server.getPlayerManager().getPlayers());
}
public void tick() {
gatherRequests();
updateQueues();
processQueues();
}
private void gatherRequests() {
requested.clear();
allChunkLoaders.forEach(collection -> {
collection.forEach(this::gatherRequests);
});
}
private void gatherRequests(ChunkLoader loader) {
loader.requestChunksToLoad(v -> requested.add(CoordinatePacker.pack3IntsIntoLong(v)));
}
private void updateQueues() {
TLongSet loaded = getServer().getWorld().getData().getChunkKeys();
toLoad.clear();
toLoad.addAll(requested);
toLoad.removeAll(loaded);
toUnload.clear();
toUnload.addAll(loaded);
toUnload.removeAll(requested);
}
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;
});
}
public Server getServer() {
return server;
}
public void loadChunk(Vec3i pos) {
ChunkData chunk = new ChunkData(pos, getServer().getWorld().getData());
TestContent.generateChunk(chunk);
getServer().getWorld().getData().addChunk(chunk);
}
public void unloadChunk(Vec3i pos) {
getServer().getWorld().getData().removeChunk(getServer().getWorld().getData().getChunk(pos));
}
}

View File

@ -0,0 +1,204 @@
package ru.windcorp.progressia.server;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.PacketRevokeChunk;
import ru.windcorp.progressia.common.world.PacketSendChunk;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.TestWorldDiskIO;
public class ChunkManager {
private class PlayerVision {
private final ChunkSet visible = ChunkSets.newSyncHashSet();
private final ChunkSet requested = ChunkSets.newHashSet();
private final ChunkSet toSend = ChunkSets.newHashSet();
private final ChunkSet toRevoke = ChunkSets.newHashSet();
public boolean isChunkVisible(Vec3i chunkPos) {
return visible.contains(chunkPos);
}
public void gatherRequests(Player player) {
requested.clear();
player.requestChunksToLoad(requested::add);
}
public void updateQueues(Player player) {
toSend.clear();
toSend.addAll(requested);
toSend.removeAll(visible);
toSend.retainAll(loaded);
toRevoke.clear();
toRevoke.addAll(visible);
toRevoke.removeIf(v -> loaded.contains(v) && requested.contains(v));
}
public void processQueues(Player player) {
toRevoke.forEach(chunkPos -> revokeChunk(player, chunkPos));
toRevoke.clear();
toSend.forEach(chunkPos -> sendChunk(player, chunkPos));
toSend.clear();
}
}
private final Server server;
private final ChunkSet loaded;
private final ChunkSet requested = ChunkSets.newHashSet();
private final ChunkSet toLoad = ChunkSets.newHashSet();
private final ChunkSet toUnload = ChunkSets.newHashSet();
// TODO replace with a normal Map managed by some sort of PlayerListener, weak maps are weak
private final Map<Player, PlayerVision> visions = Collections.synchronizedMap(new WeakHashMap<>());
public ChunkManager(Server server) {
this.server = server;
this.loaded = server.getWorld().getData().getLoadedChunks();
}
public void tick() {
synchronized (getServer().getWorld().getData()) {
synchronized (visions) {
gatherRequests();
updateQueues();
processQueues();
}
}
}
private void gatherRequests() {
requested.clear();
server.getPlayerManager().getPlayers().forEach(p -> {
PlayerVision vision = getVision(p, true);
vision.gatherRequests(p);
requested.addAll(vision.requested);
});
}
private void updateQueues() {
toLoad.clear();
toLoad.addAll(requested);
toLoad.removeAll(loaded);
toUnload.clear();
toUnload.addAll(loaded);
toUnload.removeAll(requested);
visions.forEach((p, v) -> {
v.updateQueues(p);
});
}
private void processQueues() {
toUnload.forEach(this::unloadChunk);
toUnload.clear();
toLoad.forEach(this::loadChunk);
toLoad.clear();
visions.forEach((p, v) -> {
v.processQueues(p);
});
}
private PlayerVision getVision(Player player, boolean createIfMissing) {
return createIfMissing ? visions.computeIfAbsent(player, k -> new PlayerVision()) : visions.get(player);
}
public void loadChunk(Vec3i chunkPos) {
WorldData world = getServer().getWorld().getData();
ChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world);
if (chunk == null) {
chunk = new ChunkData(chunkPos, world);
TestContent.generateChunk(chunk);
}
world.addChunk(chunk);
}
public void unloadChunk(Vec3i chunkPos) {
WorldData world = getServer().getWorld().getData();
ChunkData chunk = world.getChunk(chunkPos);
if (chunk == null) {
throw new IllegalStateException(String.format(
"Chunk (%d; %d; %d) not loaded, cannot unload",
chunkPos.x, chunkPos.y, chunkPos.z
));
}
world.removeChunk(chunk);
TestWorldDiskIO.saveChunk(chunk);
}
public void sendChunk(Player player, Vec3i chunkPos) {
ChunkData chunk = server.getWorld().getData().getChunk(chunkPos);
if (chunk == null) {
throw new IllegalStateException(String.format(
"Chunk (%d; %d; %d) is not loaded, cannot send",
chunkPos.x, chunkPos.y, chunkPos.z
));
}
PacketSendChunk packet = new PacketSendChunk();
packet.set(chunk);
player.getClient().sendPacket(packet);
getVision(player, true).visible.add(chunkPos);
}
public void revokeChunk(Player player, Vec3i chunkPos) {
PacketRevokeChunk packet = new PacketRevokeChunk();
packet.set(chunkPos);
player.getClient().sendPacket(packet);
PlayerVision vision = getVision(player, false);
if (vision != null) {
vision.visible.remove(chunkPos);
}
}
public boolean isChunkVisible(Vec3i chunkPos, Player player) {
PlayerVision vision = getVision(player, false);
if (vision == null) {
return false;
}
return vision.isChunkVisible(chunkPos);
}
public ChunkSet getVisibleChunks(Player player) {
PlayerVision vision = getVision(player, false);
if (vision == null) {
return ChunkSets.empty();
}
return vision.visible;
}
public Server getServer() {
return server;
}
}

View File

@ -0,0 +1,177 @@
package ru.windcorp.progressia.server;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import glm.vec._3.i.Vec3i;
import gnu.trove.TCollections;
import gnu.trove.iterator.TLongIterator;
import gnu.trove.set.TLongSet;
import gnu.trove.set.hash.TLongHashSet;
import ru.windcorp.jputil.chars.StringUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.PacketRevokeEntity;
import ru.windcorp.progressia.common.world.generic.ChunkSet;
public class EntityManager {
private class PlayerVision {
private final TLongSet visible = TCollections.synchronizedSet(new TLongHashSet());
private final TLongSet requested = new TLongHashSet();
private final TLongSet toSend = new TLongHashSet();
private final TLongSet toRevoke = new TLongHashSet();
public boolean isEntityVisible(long entityId) {
return visible.contains(entityId);
}
public void gatherRequests(Player player) {
requested.clear();
ChunkSet visibleChunks = player.getClient().getVisibleChunks();
Vec3i v = Vectors.grab3i();
getServer().getWorld().forEachEntity(entity -> {
if (visibleChunks.contains(entity.getChunkCoords(v))) {
requested.add(entity.getEntityId());
}
});
Vectors.release(v);
}
public void updateQueues(Player player) {
toSend.clear();
toSend.addAll(requested);
toSend.removeAll(visible);
toSend.retainAll(loaded);
toRevoke.clear();
for (TLongIterator it = visible.iterator(); it.hasNext();) {
long entityId = it.next();
if (!loaded.contains(entityId) || !requested.contains(entityId)) {
toRevoke.add(entityId);
}
}
}
public void processQueues(Player player) {
toRevoke.forEach(entityId -> {
revokeEntity(player, entityId);
return true;
});
toRevoke.clear();
toSend.forEach(entityId -> {
sendEntity(player, entityId);
return true;
});
toSend.clear();
}
}
private final Server server;
private final TLongSet loaded;
// TODO replace with a normal Map managed by some sort of PlayerListener, weak maps are weak
private final Map<Player, PlayerVision> visions = Collections.synchronizedMap(new WeakHashMap<>());
public EntityManager(Server server) {
this.server = server;
this.loaded = server.getWorld().getData().getLoadedEntities();
}
public void tick() {
synchronized (getServer().getWorld().getData()) {
synchronized (visions) {
gatherRequests();
updateQueues();
processQueues();
}
}
}
private void gatherRequests() {
server.getPlayerManager().getPlayers().forEach(p -> {
PlayerVision vision = getVision(p, true);
vision.gatherRequests(p);
});
}
private void updateQueues() {
visions.forEach((p, v) -> {
v.updateQueues(p);
});
}
private void processQueues() {
visions.forEach((p, v) -> {
v.processQueues(p);
});
}
private PlayerVision getVision(Player player, boolean createIfMissing) {
return createIfMissing ? visions.computeIfAbsent(player, k -> new PlayerVision()) : visions.get(player);
}
public void sendEntity(Player player, long entityId) {
EntityData entity = server.getWorld().getData().getEntity(entityId);
if (entity == null) {
throw new IllegalStateException(
"Entity with entity ID " + new String(StringUtil.toFullHex(entityId)) + " is not loaded, cannot send"
);
}
PacketSendEntity packet = new PacketSendEntity();
packet.set(entity);
player.getClient().sendPacket(packet);
getVision(player, true).visible.add(entityId);
}
public void revokeEntity(Player player, long entityId) {
PacketRevokeEntity packet = new PacketRevokeEntity();
packet.set(entityId);
player.getClient().sendPacket(packet);
PlayerVision vision = getVision(player, false);
if (vision != null) {
vision.visible.remove(entityId);
}
}
public boolean isEntityVisible(long entityId, Player player) {
PlayerVision vision = getVision(player, false);
if (vision == null) {
return false;
}
return vision.isEntityVisible(entityId);
}
private static final TLongSet EMPTY_LONG_SET = TCollections.unmodifiableSet(new TLongHashSet());
public TLongSet getVisibleEntities(Player player) {
PlayerVision vision = getVision(player, false);
if (vision == null) {
return EMPTY_LONG_SET;
}
return vision.visible;
}
public Server getServer() {
return server;
}
}

View File

@ -0,0 +1,70 @@
package ru.windcorp.progressia.server;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import ru.windcorp.progressia.common.state.IOContext;
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.PacketWorldChange;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
public class PacketSendEntity extends PacketWorldChange {
private String id;
private long entityId;
private final DataBuffer buffer = new DataBuffer();
public PacketSendEntity() {
this("Core:SendEntity");
}
protected PacketSendEntity(String id) {
super(id);
}
public void set(EntityData entity) {
this.id = entity.getId();
this.entityId = entity.getEntityId();
try {
entity.write(this.buffer.getWriter(), IOContext.COMMS);
} catch (IOException e) {
CrashReports.report(e, "Could not write an entity into an internal buffer");
}
}
@Override
public void read(DataInput input) throws IOException, DecodingException {
this.id = input.readUTF();
this.entityId = input.readLong();
this.buffer.fill(input, input.readInt());
}
@Override
public void write(DataOutput output) throws IOException {
output.writeUTF(this.id);
output.writeLong(this.entityId);
output.writeInt(this.buffer.getSize());
this.buffer.flush(output);
}
@Override
public void apply(WorldData world) {
EntityData entity = EntityDataRegistry.getInstance().create(this.id);
entity.setEntityId(this.entityId);
try {
entity.read(this.buffer.getReader(), IOContext.COMMS);
} catch (IOException e) {
CrashReports.report(e, "Could not read an entity from an internal buffer");
}
world.addEntity(entity);
}
}

View File

@ -18,6 +18,8 @@ public class Player extends PlayerData implements ChunkLoader {
super(entity);
this.server = server;
this.client = client;
client.setPlayer(this);
}
public Server getServer() {
@ -34,22 +36,28 @@ public class Player extends PlayerData implements ChunkLoader {
Coordinates.convertInWorldToChunk(start, start);
Vec3i cursor = new Vec3i();
float radius = getServer().getLoadDistance(this);
float radiusSq = radius / Units.get(16.0f, "m");
radiusSq *= radiusSq;
float radius = getServer().getLoadDistance(this) / Units.get(16.0f, "m");
float radiusSq = radius * radius;
int iRadius = (int) Math.ceil(radius);
for (cursor.x = -iRadius; cursor.x <= +iRadius; ++cursor.x) {
for (cursor.y = -iRadius; cursor.y <= +iRadius; ++cursor.y) {
for (cursor.z = -iRadius; cursor.z <= +iRadius; ++cursor.z) {
if (cursor.x * cursor.x + cursor.y * cursor.y + cursor.z * cursor.z <= radius) {
if (cursor.x * cursor.x + cursor.y * cursor.y + cursor.z * cursor.z <= radiusSq) {
cursor.add(start);
chunkConsumer.accept(cursor);
cursor.sub(start);
}
}
}
}
}
public String getLogin() {
return getClient().getLogin();
}
}

View File

@ -4,8 +4,13 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import glm.vec._2.Vec2;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
import ru.windcorp.progressia.test.TestContent;
public class PlayerManager {
@ -29,14 +34,35 @@ public class PlayerManager {
public EntityData conjurePlayerEntity(String login) {
// TODO Live up to the name
if (TestContent.PLAYER_LOGIN.equals(login)) {
// TODO load appropriate chunks
return getServer().getWorld().getData().getEntity(TestContent.PLAYER_ENTITY_ID);
Vec3i chunkPos = Vectors.ZERO_3i;
if (getServer().getWorld().getChunk(chunkPos) == null) {
getServer().getChunkManager().loadChunk(chunkPos);
}
EntityData entity = spawnPlayerEntity(login);
return entity;
} else {
CrashReports.report(null, "Unknown login %s, javahorse stupid", login);
return null;
}
}
private EntityData spawnPlayerEntity(String login) {
EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
player.setEntityId(TestContent.PLAYER_ENTITY_ID);
player.setPosition(new Vec3(8, 8, 8));
player.setDirection(new Vec2(
Math.toRadians(40), Math.toRadians(10)
));
getServer().getWorld().getData().addEntity(player);
return player;
}
public Server getServer() {
return server;
}

View File

@ -29,8 +29,10 @@ public class Server {
private final ServerThread serverThread;
private final ClientManager clientManager = new ClientManager(this);
private final PlayerManager playerManager = new PlayerManager(this);
private final ClientManager clientManager;
private final PlayerManager playerManager;
private final ChunkManager chunkManager;
private final EntityManager entityManager;
private final TaskQueue taskQueue = new TaskQueue(this::isServerThread);
@ -40,7 +42,14 @@ public class Server {
this.world = new WorldLogic(world, this);
this.serverThread = new ServerThread(this);
schedule(this::scheduleChunkTicks);
this.clientManager = new ClientManager(this);
this.playerManager = new PlayerManager(this);
this.chunkManager = new ChunkManager(this);
this.entityManager = new EntityManager(this);
schedule(this::scheduleWorldTicks);
schedule(chunkManager::tick);
schedule(entityManager::tick);
}
/**
@ -64,6 +73,10 @@ public class Server {
return playerManager;
}
public ChunkManager getChunkManager() {
return chunkManager;
}
/**
* Checks if this thread is the main thread of this server.
* @return {@code true} iff the invocation occurs in server main thread
@ -187,8 +200,9 @@ public class Server {
serverThread.stop();
}
private void scheduleChunkTicks(Server server) {
private void scheduleWorldTicks(Server server) {
server.getWorld().getChunks().forEach(chunk -> requestEvaluation(chunk.getTickTask()));
requestEvaluation(server.getWorld().getTickEntitiesTask());
}
/**

View File

@ -16,7 +16,6 @@ public class ServerState {
public static void startServer() {
Server server = new Server(new WorldData());
server.getWorld().getData().tmp_generate();
setInstance(server);
server.start();
}

View File

@ -1,20 +1,18 @@
package ru.windcorp.progressia.server.comms;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.LogManager;
import glm.vec._3.i.Vec3i;
import gnu.trove.TCollections;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
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.common.world.PacketSetLocalPlayer;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server;
@ -45,10 +43,10 @@ public class ClientManager {
if (client instanceof ClientChat) {
addClientChat((ClientChat) client);
}
if (client instanceof ClientPlayer) {
addClientPlayer((ClientPlayer) client);
if (client instanceof ClientPlayer) {
addClientPlayer((ClientPlayer) client);
}
}
client.addListener(new DefaultServerCommsListener(this, client));
@ -61,29 +59,15 @@ public class ClientManager {
private void addClientPlayer(ClientPlayer client) {
String login = client.getLogin();
EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login);
Player player = new Player(entity, getServer(), client);
getServer().getPlayerManager().getPlayers().add(player);
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(entity.getEntityId()));
PacketSetLocalPlayer packet = new PacketSetLocalPlayer();
LogManager.getLogger().info("Sending local player ID {}", EntityData.formatEntityId(entity.getEntityId()));
packet.set(entity.getEntityId());
client.sendPacket(packet);
}
public void disconnectClient(Client client) {
@ -91,7 +75,11 @@ public class ClientManager {
clientsById.remove(client.getId());
}
public void broadcastGamePacket(Packet packet) {
/**
* Sends the provided packet to all connected player clients.
* @param packet the packet to broadcast
*/
public void broadcastToAllPlayers(Packet packet) {
getClients().forEach(c -> {
if (c.getState() != State.CONNECTED) return;
if (!(c instanceof ClientPlayer)) return;
@ -99,6 +87,34 @@ public class ClientManager {
});
}
/**
* Sends the provided packet to all connected player clients that can see the chunk identified by {@code chunkPos}.
* @param packet the packet to broadcast
* @param chunkPos the chunk coordinates of the chunk that must be visible
*/
public void broadcastLocal(Packet packet, Vec3i chunkPos) {
getClients().forEach(c -> {
if (c.getState() != State.CONNECTED) return;
if (!(c instanceof ClientPlayer)) return;
if (!((ClientPlayer) c).isChunkVisible(chunkPos)) return;
c.sendPacket(packet);
});
}
/**
* Sends the provided packet to all connected player clients that can see the entity identified by {@code entityId}.
* @param packet the packet to broadcast
* @param entityId the ID of the entity that must be visible
*/
public void broadcastLocal(Packet packet, long entityId) {
getClients().forEach(c -> {
if (c.getState() != State.CONNECTED) return;
if (!(c instanceof ClientPlayer)) return;
if (!((ClientPlayer) c).isChunkVisible(entityId)) return;
c.sendPacket(packet);
});
}
public Collection<Client> getClients() {
return clients;
}

View File

@ -1,11 +1,40 @@
package ru.windcorp.progressia.server.comms;
public abstract class ClientPlayer extends Client {
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.server.Player;
public abstract class ClientPlayer extends ClientChat {
private Player player;
public ClientPlayer(int id) {
super(id);
}
public Player getPlayer() {
return player;
}
public void setPlayer(Player player) {
this.player = player;
}
public abstract String getLogin();
public boolean isChunkVisible(Vec3i chunkPos) {
if (player == null) return false;
return player.getServer().getChunkManager().isChunkVisible(chunkPos, player);
}
public ChunkSet getVisibleChunks() {
if (player == null) return ChunkSets.empty();
return player.getServer().getChunkManager().getVisibleChunks(player);
}
public boolean isChunkVisible(long entityId) {
return true;
}
}

View File

@ -11,14 +11,12 @@ import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.GenericChunk;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.common.world.tile.TileReference;
import ru.windcorp.progressia.server.world.block.BlockLogic;
import ru.windcorp.progressia.server.world.block.BlockLogicRegistry;
import ru.windcorp.progressia.server.world.block.TickableBlock;
import ru.windcorp.progressia.server.world.entity.EntityLogic;
import ru.windcorp.progressia.server.world.entity.EntityLogicRegistry;
import ru.windcorp.progressia.server.world.tasks.TickChunk;
import ru.windcorp.progressia.server.world.ticking.TickingPolicy;
import ru.windcorp.progressia.server.world.tile.TickableTile;
@ -26,7 +24,12 @@ import ru.windcorp.progressia.server.world.tile.TileLogic;
import ru.windcorp.progressia.server.world.tile.TileLogicRegistry;
import ru.windcorp.progressia.server.world.tile.TileLogicStack;
public class ChunkLogic {
public class ChunkLogic implements GenericChunk<
ChunkLogic,
BlockLogic,
TileLogic,
TileLogicStack
> {
private final WorldLogic world;
private final ChunkData data;
@ -43,31 +46,36 @@ public class ChunkLogic {
this.world = world;
this.data = data;
generateTickLists();
tmp_generateTickLists();
}
private void generateTickLists() {
ChunkTickContext context = TickContextMutable.start().withChunk(this).build();
@Override
public Vec3i getPosition() {
return getData().getPosition();
}
context.forEachBlock(bctxt -> {
BlockLogic block = bctxt.getBlock();
@Override
public BlockLogic getBlock(Vec3i blockInChunk) {
return BlockLogicRegistry.getInstance().get(
getData().getBlock(blockInChunk).getId()
);
}
if (!(block instanceof TickableBlock)) return;
@Override
public TileLogicStack getTiles(Vec3i blockInChunk, BlockFace face) {
return getTileStackWrapper(getData().getTiles(blockInChunk, face));
}
if (((TickableBlock) block).getTickingPolicy(bctxt) == TickingPolicy.REGULAR) {
tickingBlocks.add(Coordinates.convertInWorldToInChunk(bctxt.getBlockInWorld(), null));
}
@Override
public boolean hasTiles(Vec3i blockInChunk, BlockFace face) {
return getData().hasTiles(blockInChunk, face);
}
bctxt.forEachFace(fctxt -> fctxt.forEachTile(tctxt -> {
TileLogic tile = tctxt.getTile();
if (!(tile instanceof TickableTile)) return;
if (((TickableTile) tile).getTickingPolicy(tctxt) == TickingPolicy.REGULAR) {
tickingTiles.add(tctxt.getReference());
}
}));
});
private TileLogicStack getTileStackWrapper(TileDataStack tileDataList) {
return tileLogicLists.computeIfAbsent(
tileDataList,
TileLogicStackImpl::new
);
}
public WorldLogic getWorld() {
@ -78,10 +86,6 @@ public class ChunkLogic {
return data;
}
public Vec3i getPosition() {
return getData().getPosition();
}
public boolean hasTickingBlocks() {
return !tickingBlocks.isEmpty();
}
@ -105,38 +109,6 @@ public class ChunkLogic {
});
}
public void forEachEntity(BiConsumer<EntityLogic, EntityData> action) {
getData().forEachEntity(data -> {
action.accept(
EntityLogicRegistry.getInstance().get(data.getId()),
data
);
});
}
public BlockLogic getBlock(Vec3i blockInChunk) {
return BlockLogicRegistry.getInstance().get(
getData().getBlock(blockInChunk).getId()
);
}
public TileLogicStack getTiles(Vec3i blockInChunk, BlockFace face) {
return getTileStackWrapper(getData().getTiles(blockInChunk, face));
}
public TileLogicStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) {
TileDataStack tiles = getData().getTilesOrNull(blockInChunk, face);
if (tiles == null) return null;
return getTileStackWrapper(tiles);
}
private TileLogicStack getTileStackWrapper(TileDataStack tileDataList) {
return tileLogicLists.computeIfAbsent(
tileDataList,
TileLogicStackImpl::new
);
}
public TickChunk getTickTask() {
return tickTask;
}
@ -154,11 +126,6 @@ public class ChunkLogic {
return parent.getBlockInChunk(output);
}
@Override
public Vec3i getChunkPos() {
return ChunkLogic.this.getPosition();
}
@Override
public ChunkLogic getChunk() {
return ChunkLogic.this;
@ -186,4 +153,28 @@ public class ChunkLogic {
}
private void tmp_generateTickLists() {
ChunkTickContext context = TickContextMutable.start().withChunk(this).build();
context.forEachBlock(bctxt -> {
BlockLogic block = bctxt.getBlock();
if (!(block instanceof TickableBlock)) return;
if (((TickableBlock) block).getTickingPolicy(bctxt) == TickingPolicy.REGULAR) {
tickingBlocks.add(Coordinates.convertInWorldToInChunk(bctxt.getBlockInWorld(), null));
}
bctxt.forEachFace(fctxt -> fctxt.forEachTile(tctxt -> {
TileLogic tile = tctxt.getTile();
if (!(tile instanceof TickableTile)) return;
if (((TickableTile) tile).getTickingPolicy(tctxt) == TickingPolicy.REGULAR) {
tickingTiles.add(tctxt.getReference());
}
}));
});
}
}

View File

@ -112,49 +112,6 @@ public class TickAndUpdateUtil {
tickEntity(EntityLogicRegistry.getInstance().get(data.getId()), data, TickContextMutable.start().withServer(server).build());
}
// public static BlockTickContext getBlockTickContext(
// Server server,
// Vec3i blockInWorld
// ) {
// MutableBlockTickContext result = new MutableBlockTickContext();
// result.init(server, blockInWorld);
// return result;
// }
//
// public static TileTickContext getTileTickContext(
// Server server,
// Vec3i blockInWorld,
// BlockFace face,
// int layer
// ) {
// MutableTileTickContext result = new MutableTileTickContext();
// result.init(server, blockInWorld, face, layer);
// return result;
// }
//
// public static TileTickContext getTileTickContext(
// Server server,
// TileDataStack stack,
// int index
// ) {
// MutableTileTickContext result = new MutableTileTickContext();
// result.init(server, stack, index);
// return result;
// }
//
// public static TileTickContext getTileTickContext(
// Server server,
// TileReference ref
// ) {
// MutableTileTickContext result = new MutableTileTickContext();
// result.init(server, ref);
// return result;
// }
//
// public static TickContext getTickContext(Server server) {
// return getBlockTickContext(server, null);
// }
private TickAndUpdateUtil() {}
}

View File

@ -8,7 +8,7 @@ import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.GenericTileStack;
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.common.world.tile.TileReference;
import ru.windcorp.progressia.server.Server;
@ -87,7 +87,7 @@ public abstract class TickContextMutable implements BlockTickContext, TSTickCont
public static interface World extends Builder {
Chunk withChunk(Vec3i chunk);
Block withBlock(Vec3i blockInWorld);
TileStack withTS(GenericTileStack<?, ?> tileStack);
TileStack withTS(GenericTileStack<?, ?, ?> tileStack);
default Builder.Chunk withChunk(ChunkData chunk) {
Objects.requireNonNull(chunk, "chunk");
@ -237,7 +237,7 @@ public abstract class TickContextMutable implements BlockTickContext, TSTickCont
}
@Override
public TileStack withTS(GenericTileStack<?, ?> tileStack) {
public TileStack withTS(GenericTileStack<?, ?, ?> tileStack) {
Objects.requireNonNull(tileStack, "tileStack");
return withBlock(tileStack.getBlockInWorld(this.blockInWorld)).withFace(tileStack.getFace());

View File

@ -2,28 +2,38 @@ package ru.windcorp.progressia.server.world;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.ChunkData;
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.BlockFace;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.GenericWorld;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.block.BlockLogic;
import ru.windcorp.progressia.server.world.tasks.TickEntitiesTask;
import ru.windcorp.progressia.server.world.ticking.Evaluation;
import ru.windcorp.progressia.server.world.tile.TileLogic;
import ru.windcorp.progressia.test.TestChunkSender;
import ru.windcorp.progressia.server.world.tile.TileLogicStack;
public class WorldLogic {
public class WorldLogic
implements GenericWorld<
BlockLogic,
TileLogic,
TileLogicStack,
ChunkLogic,
EntityData // not using EntityLogic because it is stateless
> {
private final WorldData data;
private final Server server;
private final Map<ChunkData, ChunkLogic> chunks = new HashMap<>();
private final Evaluation tickEntitiesTask = new TickEntitiesTask();
public WorldLogic(WorldData data, Server server) {
this.data = data;
this.server = server;
@ -41,7 +51,25 @@ public class WorldLogic {
});
data.addListener(ChunkDataListeners.createAdder(new UpdateTriggerer(server)));
data.addListener(new TestChunkSender(server));
}
@Override
public ChunkLogic getChunk(Vec3i pos) {
return chunks.get(getData().getChunk(pos));
}
@Override
public Collection<ChunkLogic> getChunks() {
return chunks.values();
}
@Override
public Collection<EntityData> getEntities() {
return getData().getEntities();
}
public Evaluation getTickEntitiesTask() {
return tickEntitiesTask;
}
public Server getServer() {
@ -56,52 +84,4 @@ public class WorldLogic {
return chunks.get(chunkData);
}
public ChunkLogic getChunk(Vec3i pos) {
return chunks.get(getData().getChunk(pos));
}
public ChunkLogic getChunkByBlock(Vec3i blockInWorld) {
return getChunk(Coordinates.convertInWorldToChunk(blockInWorld, null));
}
public BlockLogic getBlock(Vec3i blockInWorld) {
ChunkLogic chunk = getChunkByBlock(blockInWorld);
if (chunk == null) return null;
return chunk.getBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null));
}
public List<TileLogic> getTiles(Vec3i blockInWorld, BlockFace face) {
return getTilesImpl(blockInWorld, face, true);
}
public List<TileLogic> getTilesOrNull(Vec3i blockInWorld, BlockFace face) {
return getTilesImpl(blockInWorld, face, false);
}
private List<TileLogic> getTilesImpl(Vec3i blockInWorld, BlockFace face, boolean createIfMissing) {
ChunkLogic chunk = getChunkByBlock(blockInWorld);
if (chunk == null) return null;
Vec3i blockInChunk = Coordinates.convertInWorldToInChunk(blockInWorld, null);
List<TileLogic> result =
createIfMissing
? chunk.getTiles(blockInChunk, face)
: chunk.getTilesOrNull(blockInChunk, face);
return result;
}
public TileLogic getTile(Vec3i blockInWorld, BlockFace face, int layer) {
List<TileLogic> tiles = getTilesOrNull(blockInWorld, face);
if (tiles == null || tiles.size() <= layer) return null;
return tiles.get(layer);
}
public Collection<ChunkLogic> getChunks() {
return chunks.values();
}
}

View File

@ -2,8 +2,9 @@ package ru.windcorp.progressia.server.world.block;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericBlock;
public class BlockLogic extends Namespaced {
public class BlockLogic extends Namespaced implements GenericBlock {
public BlockLogic(String id) {
super(id);

View File

@ -2,53 +2,12 @@ package ru.windcorp.progressia.server.world.tasks;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.common.world.tile.PacketAddTile;
class AddTile extends CachedWorldChange {
private final Vec3i blockInWorld = new Vec3i();
private BlockFace face;
private TileData tile;
class AddTile extends CachedChunkChange<PacketAddTile> {
public AddTile(Consumer<? super CachedChange> disposer) {
super(disposer, "Core:AddTile");
}
public void initialize(
Vec3i position, BlockFace face,
TileData tile
) {
if (this.tile != null)
throw new IllegalStateException("Payload is not null. Current: " + this.tile + "; requested: " + tile);
this.blockInWorld.set(position.x, position.y, position.z);
this.face = face;
this.tile = tile;
}
@Override
protected void affectCommon(WorldData world) {
TileDataStack tiles = world
.getChunkByBlock(blockInWorld)
.getTiles(Coordinates.convertInWorldToInChunk(blockInWorld, null), face);
tiles.add(tile);
}
@Override
public void getRelevantChunk(Vec3i output) {
Coordinates.convertInWorldToChunk(blockInWorld, output);
}
@Override
public void dispose() {
super.dispose();
this.tile = null;
super(disposer, new PacketAddTile());
}
}

View File

@ -0,0 +1,19 @@
package ru.windcorp.progressia.server.world.tasks;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.PacketChunkChange;
public abstract class CachedChunkChange<P extends PacketChunkChange> extends CachedWorldChange<P> {
public CachedChunkChange(Consumer<? super CachedChange> disposer, P packet) {
super(disposer, packet);
}
@Override
public void getRelevantChunk(Vec3i output) {
getPacket().getAffectedChunk(output);
}
}

View File

@ -2,35 +2,49 @@ package ru.windcorp.progressia.server.world.tasks;
import java.util.function.Consumer;
import ru.windcorp.progressia.common.comms.packets.PacketWorldChange;
import ru.windcorp.progressia.common.world.WorldData;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.PacketWorldChange;
import ru.windcorp.progressia.server.Server;
public abstract class CachedWorldChange extends CachedChange {
public abstract class CachedWorldChange<P extends PacketWorldChange> extends CachedChange {
private final PacketWorldChange packet;
private final P packet;
public CachedWorldChange(Consumer<? super CachedChange> disposer, String packetId) {
public CachedWorldChange(Consumer<? super CachedChange> disposer, P packet) {
super(disposer);
this.packet = new PacketWorldChange(packetId) {
@Override
public void apply(WorldData world) {
affectCommon(world);
}
};
this.packet = packet;
}
@Override
public void affect(Server server) {
affectCommon(server.getWorld().getData());
server.getClientManager().broadcastGamePacket(packet);
affectLocal(server);
sendPacket(server);
}
/**
* Invoked by both Change and Packet.
* @param world the world to affect
*/
protected abstract void affectCommon(WorldData world);
protected void affectLocal(Server server) {
packet.apply(server.getWorld().getData());
}
protected void sendPacket(Server server) {
Vec3i v = Vectors.grab3i();
Vec3i chunkPos = getAffectedChunk(v);
if (chunkPos == null) {
server.getClientManager().broadcastToAllPlayers(packet);
} else {
server.getClientManager().broadcastLocal(packet, chunkPos);
}
Vectors.release(chunkPos);
}
protected Vec3i getAffectedChunk(Vec3i output) {
return null;
}
public P getPacket() {
return packet;
}
}

View File

@ -1,11 +1,8 @@
package ru.windcorp.progressia.server.world.tasks;
import java.io.IOException;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.PacketEntityChange;
import ru.windcorp.progressia.server.Server;
@ -31,26 +28,15 @@ class ChangeEntity extends CachedChange {
this.entity = entity;
this.change = change;
packet.setEntityId(entity.getEntityId());
try {
entity.write(packet.getWriter(), IOContext.COMMS); // TODO wtf is this... (see whole file)
} catch (IOException e) {
CrashReports.report(e, "Could not write entity %s", entity);
}
}
@SuppressWarnings("unchecked")
@Override
public void affect(Server server) {
((StateChange<EntityData>) change).change(entity);
packet.set(entity);
try {
entity.write(packet.getWriter(), IOContext.COMMS); // ...and this doing at the same time? - javapony at 1 AM
} catch (IOException e) {
CrashReports.report(e, "Could not write entity %s", entity);
}
server.getClientManager().broadcastGamePacket(packet);
server.getClientManager().broadcastLocal(packet, entity.getEntityId());
}
@Override

View File

@ -2,43 +2,12 @@ package ru.windcorp.progressia.server.world.tasks;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.common.world.tile.PacketRemoveTile;
class RemoveTile extends CachedWorldChange {
private final Vec3i blockInWorld = new Vec3i();
private BlockFace face;
private int tag;
class RemoveTile extends CachedChunkChange<PacketRemoveTile> {
public RemoveTile(Consumer<? super CachedChange> disposer) {
super(disposer, "Core:RemoveTile");
}
public void initialize(
Vec3i position, BlockFace face,
int tag
) {
this.blockInWorld.set(position.x, position.y, position.z);
this.face = face;
this.tag = tag;
}
@Override
protected void affectCommon(WorldData world) {
TileDataStack tiles = world
.getChunkByBlock(blockInWorld)
.getTiles(Coordinates.convertInWorldToInChunk(blockInWorld, null), face);
tiles.remove(tiles.getIndexByTag(tag));
}
@Override
public void getRelevantChunk(Vec3i output) {
Coordinates.convertInWorldToChunk(blockInWorld, output);
super(disposer, new PacketRemoveTile());
}
}

View File

@ -2,44 +2,12 @@ package ru.windcorp.progressia.server.world.tasks;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.PacketSetBlock;
class SetBlock extends CachedWorldChange {
private final Vec3i blockInWorld = new Vec3i();
private BlockData block;
class SetBlock extends CachedChunkChange<PacketSetBlock> {
public SetBlock(Consumer<? super CachedChange> disposer) {
super(disposer, "Core:SetBlock");
}
public void initialize(Vec3i blockInWorld, BlockData block) {
if (this.block != null)
throw new IllegalStateException("Payload is not null. Current: " + this.block + "; requested: " + block);
this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z);
this.block = block;
}
@Override
protected void affectCommon(WorldData world) {
world
.getChunkByBlock(blockInWorld)
.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, true);
}
@Override
public void getRelevantChunk(Vec3i output) {
Coordinates.convertInWorldToChunk(blockInWorld, output);
}
@Override
public void dispose() {
super.dispose();
this.block = null;
super(disposer, new PacketSetBlock());
}
}

View File

@ -14,7 +14,6 @@ import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.ChunkLogic;
import ru.windcorp.progressia.server.world.TickAndUpdateUtil;
import ru.windcorp.progressia.server.world.TickContextMutable;
import ru.windcorp.progressia.server.world.block.BlockLogic;
import ru.windcorp.progressia.server.world.block.TickableBlock;
@ -55,7 +54,6 @@ public class TickChunk extends Evaluation {
public void evaluate(Server server) {
tickRegulars(server);
tickRandom(server);
tickEntities(server);
}
private void tickRegulars(Server server) {
@ -165,12 +163,6 @@ public class TickChunk extends Evaluation {
);
}
private void tickEntities(Server server) {
chunk.getData().forEachEntity(entity -> {
TickAndUpdateUtil.tickEntity(entity, server);
});
}
@Override
public void getRelevantChunk(Vec3i output) {
Vec3i p = chunk.getData().getPosition();

View File

@ -0,0 +1,27 @@
package ru.windcorp.progressia.server.world.tasks;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.TickAndUpdateUtil;
import ru.windcorp.progressia.server.world.ticking.Evaluation;
public class TickEntitiesTask extends Evaluation {
@Override
public void evaluate(Server server) {
server.getWorld().forEachEntity(entity -> {
TickAndUpdateUtil.tickEntity(entity, server);
});
}
@Override
public void getRelevantChunk(Vec3i output) {
// Do nothing
}
@Override
public boolean isThreadSensitive() {
return false;
}
}

View File

@ -38,7 +38,7 @@ public class WorldAccessor {
public void setBlock(Vec3i blockInWorld, BlockData block) {
SetBlock change = cache.grab(SetBlock.class);
change.initialize(blockInWorld, block);
change.getPacket().set(block, blockInWorld);
server.requestChange(change);
}
@ -48,7 +48,7 @@ public class WorldAccessor {
public void addTile(Vec3i blockInWorld, BlockFace face, TileData tile) {
AddTile change = cache.grab(AddTile.class);
change.initialize(blockInWorld, face, tile);
change.getPacket().set(tile, blockInWorld, face);
server.requestChange(change);
}
@ -58,7 +58,7 @@ public class WorldAccessor {
public void removeTile(Vec3i blockInWorld, BlockFace face, int tag) {
RemoveTile change = cache.grab(RemoveTile.class);
change.initialize(blockInWorld, face, tag);
change.getPacket().set(blockInWorld, face, tag);
server.requestChange(change);
}

View File

@ -2,8 +2,9 @@ package ru.windcorp.progressia.server.world.tile;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericTile;
public class TileLogic extends Namespaced {
public class TileLogic extends Namespaced implements GenericTile {
public TileLogic(String id) {
super(id);

View File

@ -1,12 +1,15 @@
package ru.windcorp.progressia.server.world.tile;
import ru.windcorp.progressia.common.world.tile.GenericTileStack;
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.server.world.ChunkLogic;
public abstract class TileLogicStack extends GenericTileStack<TileLogic, ChunkLogic> {
// TODO add @Deprecated or smth similar to all modification methods
public abstract class TileLogicStack
extends GenericTileStack<
TileLogicStack,
TileLogic,
ChunkLogic
> {
public abstract TileDataStack getData();

View File

@ -73,6 +73,12 @@ public class LayerTestGUI extends GUILayer {
128
));
panel.addChild(new DynamicLabel(
"ChunkUpdatesDisplay", new Font().withColor(0x37A3E6).deriveShadow(),
() -> "Pending updates: " + Integer.toString(ClientState.getInstance().getWorld().getPendingChunkUpdates()),
128
));
panel.getChildren().forEach(c -> {
if (c instanceof Label) {
labels.add((Label) c);

View File

@ -1,26 +1,59 @@
package ru.windcorp.progressia.test;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import ru.windcorp.jputil.functions.ThrowingConsumer;
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;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataRegistry;
public class TestChunkCodec extends ChunkCodec {
public TestChunkCodec() {
super("Test:TestCodec", 0x00);
private static class Palette<E> {
private final List<E> nidToElement = new ArrayList<>();
private final TObjectIntMap<E> elementToNid = new TObjectIntHashMap<>();
public void add(E element) {
if (elementToNid.containsKey(element)) return;
nidToElement.add(element);
elementToNid.put(element, elementToNid.size());
}
public E getByNid(int nid) {
return nidToElement.get(nid);
}
public int getNid(E element) {
return elementToNid.get(element);
}
public int size() {
return nidToElement.size();
}
}
@Override
public ChunkData decode(WorldData world, Vec3i position, InputStream data) throws DecodingException, IOException {
ChunkData chunk = new ChunkData(position, world);
TestContent.generateChunk(chunk);
return chunk;
public TestChunkCodec() {
super("Test:TestCodec", 0x00);
}
@Override
@ -28,9 +61,168 @@ public class TestChunkCodec extends ChunkCodec {
return true;
}
/*
* Decoding
*/
@Override
public void encode(ChunkData chunk, OutputStream output) throws IOException {
// Do nothing. Heh.
public ChunkData decode(WorldData world, Vec3i position, InputStream inputStream) throws DecodingException, IOException {
DataInput input = new DataInputStream(inputStream);
BlockData[] blockPalette = readBlockPalette(input);
TileData[] tilePalette = readTilePalette(input);
ChunkData chunk = new ChunkData(position, world);
readBlocks(input, blockPalette, chunk);
readTiles(input, tilePalette, chunk);
return chunk;
}
private BlockData[] readBlockPalette(DataInput input) throws IOException {
BlockData[] palette = new BlockData[input.readInt()];
for (int nid = 0; nid < palette.length; ++nid) {
String id = input.readUTF();
palette[nid] = BlockDataRegistry.getInstance().get(id);
}
return palette;
}
private TileData[] readTilePalette(DataInput input) throws IOException {
TileData[] palette = new TileData[input.readInt()];
for (int nid = 0; nid < palette.length; ++nid) {
String id = input.readUTF();
palette[nid] = TileDataRegistry.getInstance().get(id);
}
return palette;
}
private void readBlocks(DataInput input, BlockData[] blockPalette, ChunkData chunk) throws IOException {
try {
chunk.forEachBiC(guard(v -> {
chunk.setBlock(v, blockPalette[input.readInt()], false);
}));
} catch (UncheckedIOException e) {
throw e.getCause();
}
}
private void readTiles(DataInput input, TileData[] tilePalette, ChunkData chunk) throws IOException {
Vec3i bic = new Vec3i();
while (true) {
int xOrEndMarker = input.readByte() & 0xFF;
if (xOrEndMarker == 0xFF) break;
bic.set(xOrEndMarker, input.readByte() & 0xFF, input.readByte() & 0xFF);
BlockFace face = BlockFace.getFaces().get(input.readByte() & 0xFF);
int tiles = input.readByte() & 0xFF;
for (int i = 0; i < tiles; ++i) {
TileData tile = tilePalette[input.readInt()];
int tag = input.readInt();
chunk.getTiles(bic, face).load(tile, tag);
}
}
}
/*
* Encoding
*/
@Override
public void encode(ChunkData chunk, OutputStream outputStream) throws IOException {
DataOutput output = new DataOutputStream(outputStream);
Palette<BlockData> blockPalette = createBlockPalette(chunk);
Palette<TileData> tilePalette = createTilePalette(chunk);
writeBlockPalette(blockPalette, output);
writeTilePalette(tilePalette, output);
writeBlocks(chunk, blockPalette, output);
writeTiles(chunk, tilePalette, output);
}
private Palette<BlockData> createBlockPalette(ChunkData chunk) {
Palette<BlockData> blockPalette = new Palette<>();
chunk.forEachBiC(v -> blockPalette.add(chunk.getBlock(v)));
return blockPalette;
}
private Palette<TileData> createTilePalette(ChunkData chunk) {
Palette<TileData> tilePalette = new Palette<>();
chunk.forEachTile((ts, t) -> tilePalette.add(t));
return tilePalette;
}
private void writeBlockPalette(Palette<BlockData> blockPalette, DataOutput output) throws IOException {
output.writeInt(blockPalette.size());
for (int nid = 0; nid < blockPalette.size(); ++nid) {
BlockData block = blockPalette.getByNid(nid);
output.writeUTF(block.getId());
}
}
private void writeTilePalette(Palette<TileData> tilePalette, DataOutput output) throws IOException {
output.writeInt(tilePalette.size());
for (int nid = 0; nid < tilePalette.size(); ++nid) {
TileData tile = tilePalette.getByNid(nid);
output.writeUTF(tile.getId());
}
}
private void writeBlocks(ChunkData chunk, Palette<BlockData> blockPalette, DataOutput output) throws IOException {
try {
chunk.forEachBiC(guard(v -> {
output.writeInt(blockPalette.getNid(chunk.getBlock(v)));
}));
} catch (UncheckedIOException e) {
throw e.getCause();
}
}
private void writeTiles(ChunkData chunk, Palette<TileData> tilePalette, DataOutput output) throws IOException {
Vec3i bic = new Vec3i();
try {
chunk.forEachTileStack(guard(ts -> {
if (ts.isEmpty()) return;
ts.getBlockInChunk(bic);
output.writeByte(bic.x);
output.writeByte(bic.y);
output.writeByte(bic.z);
output.writeByte(ts.getFace().getId());
output.writeByte(ts.size());
for (int index = 0; index < ts.size(); ++index) {
output.writeInt(tilePalette.getNid(ts.get(index)));
output.writeInt(ts.getTagByIndex(index));
}
}));
} catch (UncheckedIOException e) {
throw e.getCause();
}
output.writeByte(0xFF);
}
private static <V> Consumer<V> guard(ThrowingConsumer<? super V, IOException> action) {
return v -> {
try {
action.accept(v);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
}

View File

@ -1,55 +0,0 @@
package ru.windcorp.progressia.test;
import java.io.IOException;
import glm.Glm;
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.common.world.WorldData;
import ru.windcorp.progressia.common.world.WorldDataListener;
import ru.windcorp.progressia.common.world.entity.EntityData;
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");
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, "TestChunkSender fjcked up. javahorse stupid");
}
server.getClientManager().broadcastGamePacket(packet);
tmp_sendPlayerIfPossible(world, chunk);
}
private void tmp_sendPlayerIfPossible(WorldData world, ChunkData chunk) {
EntityData e = world.getEntity(TestContent.PLAYER_ENTITY_ID);
if (e == null) return;
if (Glm.equals(e.getChunkCoords(null), chunk.getPosition())) {
System.out.printf("TestChunkSender: player found in (%d; %d; %d)\n", e.getChunkCoords(null).x, e.getChunkCoords(null).y, e.getChunkCoords(null).z);
server.getClientManager().broadcastGamePacket(new PacketSetLocalPlayer(e.getEntityId()));
}
}
}

View File

@ -7,16 +7,12 @@ import java.util.function.Consumer;
import org.lwjgl.glfw.GLFW;
import glm.Glm;
import glm.vec._2.Vec2;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.audio.SoundEffect;
import ru.windcorp.progressia.client.comms.controls.*;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
import ru.windcorp.progressia.client.graphics.world.Selection;
import ru.windcorp.progressia.client.world.block.*;
import ru.windcorp.progressia.client.world.entity.*;
@ -26,7 +22,6 @@ 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;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.*;
@ -214,12 +209,9 @@ public class TestContent {
private static Selection getSelection() {
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null) return null;
if (client == null || !client.isReady()) return null;
LocalPlayer player = client.getLocalPlayer();
if (player == null) return null;
return player.getSelection();
return client.getLocalPlayer().getSelection();
}
private static void onBlockBreakTrigger(ControlData control) {
@ -336,21 +328,6 @@ public class TestContent {
}
}
}
if (Glm.equals(chunk.getPosition(), Vectors.ZERO_3i)) {
EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
player.setEntityId(PLAYER_ENTITY_ID);
player.setPosition(new Vec3(8, 8, 8));
player.setDirection(new Vec2(
(float) Math.toRadians(40), (float) Math.toRadians(45)
));
chunk.getEntities().add(player);
EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie");
statie.setEntityId(STATIE_ENTITY_ID);
statie.setPosition(new Vec3(0, 15, 16));
chunk.getEntities().add(statie);
}
}
private static void registerMisc() {

View File

@ -17,6 +17,7 @@ import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.FloatMathUtils;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.ServerState;
public class TestPlayerControls {
@ -28,8 +29,8 @@ public class TestPlayerControls {
private TestPlayerControls() {}
private static final double MODE_SWITCH_MAX_DELAY = 100 * Units.MILLISECONDS;
private static final double MIN_JUMP_DELAY = 200 * Units.MILLISECONDS;
private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS;
private static final double MIN_JUMP_DELAY = 400 * Units.MILLISECONDS;
// Horizontal and vertical max control speed when flying
private static final float FLYING_SPEED = 6.0f * Units.METERS_PER_SECOND;
@ -60,7 +61,7 @@ public class TestPlayerControls {
private Runnable updateCallback = null;
public void applyPlayerControls() {
if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
@ -96,10 +97,17 @@ public class TestPlayerControls {
}
player.getVelocity().set(change);
// THIS IS TERRIBLE TEST
EntityData serverEntity = ServerState.getInstance().getWorld().getData().getEntity(TestContent.PLAYER_ENTITY_ID);
if (serverEntity != null) {
serverEntity.setPosition(player.getPosition());
}
}
public void handleInput(Input input) {
if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}

View File

@ -0,0 +1,96 @@
package ru.windcorp.progressia.test;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.io.ChunkIO;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.WorldData;
public class TestWorldDiskIO {
private static final Path SAVE_DIR = Paths.get("tmp_world");
private static final Logger LOG = LogManager.getLogger("TestWorldDiskIO");
private static final boolean ENABLE = true;
public static void saveChunk(ChunkData chunk) {
if (!ENABLE) return;
try {
LOG.debug(
"Saving {} {} {}",
chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z
);
Files.createDirectories(SAVE_DIR);
Path path = SAVE_DIR.resolve(String.format(
"chunk_%+d_%+d_%+d.progressia_chunk",
chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z
));
try (OutputStream output = new DeflaterOutputStream(new BufferedOutputStream(Files.newOutputStream(path)))) {
ChunkIO.save(chunk, output);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static ChunkData tryToLoad(Vec3i chunkPos, WorldData world) {
if (!ENABLE) return null;
Path path = SAVE_DIR.resolve(String.format(
"chunk_%+d_%+d_%+d.progressia_chunk",
chunkPos.x, chunkPos.y, chunkPos.z
));
if (!Files.exists(path)) {
LOG.debug(
"Not found {} {} {}",
chunkPos.x, chunkPos.y, chunkPos.z
);
return null;
}
try {
ChunkData result = load(path, chunkPos, world);
LOG.debug(
"Loaded {} {} {}",
chunkPos.x, chunkPos.y, chunkPos.z
);
return result;
} catch (Exception e) {
e.printStackTrace();
LOG.debug(
"Could not load {} {} {}",
chunkPos.x, chunkPos.y, chunkPos.z
);
return null;
}
}
private static ChunkData load(Path path, Vec3i chunkPos, WorldData world) throws IOException, DecodingException {
try (InputStream input = new InflaterInputStream(new BufferedInputStream(Files.newInputStream(path)))) {
return ChunkIO.load(world, chunkPos, input);
}
}
}