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.DefaultClientCommsListener;
import ru.windcorp.progressia.client.comms.ServerCommsChannel; import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.client.graphics.world.Camera; 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.graphics.world.LocalPlayer;
import ru.windcorp.progressia.client.world.WorldRender; import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
@ -11,14 +12,14 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
public class Client { public class Client {
private final WorldRender world; 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 Camera camera = new Camera((float) Math.toRadians(70));
private final ServerCommsChannel comms; private final ServerCommsChannel comms;
public Client(WorldData world, ServerCommsChannel comms) { public Client(WorldData world, ServerCommsChannel comms) {
this.world = new WorldRender(world); this.world = new WorldRender(world, this);
this.comms = comms; this.comms = comms;
comms.addListener(new DefaultClientCommsListener(this)); comms.addListener(new DefaultClientCommsListener(this));
@ -32,8 +33,8 @@ public class Client {
return localPlayer; return localPlayer;
} }
public void setLocalPlayer(EntityData localPlayer) { public boolean isReady() {
this.localPlayer = new LocalPlayer(localPlayer); return localPlayer.hasEntity();
} }
public Camera getCamera() { public Camera getCamera() {
@ -44,4 +45,15 @@ public class Client {
return comms; 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 java.io.IOException;
import ru.windcorp.jputil.chars.StringUtil;
import ru.windcorp.progressia.client.Client; 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.CommsListener;
import ru.windcorp.progressia.common.comms.packets.Packet; 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.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 // TODO refactor with no mercy
public class DefaultClientCommsListener implements CommsListener { public class DefaultClientCommsListener implements CommsListener {
@ -33,26 +30,12 @@ public class DefaultClientCommsListener implements CommsListener {
} }
private void setLocalPlayer(PacketSetLocalPlayer packet) { private void setLocalPlayer(PacketSetLocalPlayer packet) {
EntityData entity = getClient().getWorld().getData().getEntity( getClient().getLocalPlayer().setEntityId(packet.getEntityId());
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)
));
} }
@Override @Override
public void onIOError(IOException reason) { public void onIOError(IOException reason) {
CrashReports.report(reason, "An IOException has occurred in communications");
// TODO implement // TODO implement
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,59 @@
package ru.windcorp.progressia.client.graphics.world; 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.WorldRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderable; import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.common.world.PlayerData;
import ru.windcorp.progressia.common.world.entity.EntityData; 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(); private final Selection selection = new Selection();
public LocalPlayer(EntityData entity) { public LocalPlayer(Client client) {
super(entity); 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() { public Selection getSelection() {

View File

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

View File

@ -18,8 +18,11 @@
package ru.windcorp.progressia.client.world; package ru.windcorp.progressia.client.world;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.WeakHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import glm.mat._4.Mat4; 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.cro.ChunkRenderOptimizers;
import ru.windcorp.progressia.client.world.tile.TileRender; import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry; 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.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace; 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.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 WorldRender world;
private final ChunkData data; private final ChunkData data;
private boolean needsUpdate;
private Model model = null; private Model model = null;
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists =
Collections.synchronizedMap(new WeakHashMap<>());
public ChunkRender(WorldRender world, ChunkData data) { public ChunkRender(WorldRender world, ChunkData data) {
this.world = world; this.world = world;
this.data = data; 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() { public WorldRender getWorld() {
return world; return world;
} }
@ -63,23 +106,13 @@ public class ChunkRender {
return data; return data;
} }
public void markForUpdate() { public synchronized void markForUpdate() {
this.needsUpdate = true; getWorld().markChunkForUpdate(getPosition());
} }
public boolean needsUpdate() { public synchronized void render(ShapeRenderHelper renderer) {
return needsUpdate; if (model == null) {
} return;
public BlockRender getBlock(Vec3i posInChunk) {
return BlockRenderRegistry.getInstance().get(
getData().getBlock(posInChunk).getId()
);
}
public void render(ShapeRenderHelper renderer) {
if (model == null || needsUpdate()) {
buildModel();
} }
renderer.pushTransform().translate( renderer.pushTransform().translate(
@ -93,7 +126,7 @@ public class ChunkRender {
renderer.popTransform(); renderer.popTransform();
} }
private void buildModel() { public synchronized void update() {
Collection<ChunkRenderOptimizer> optimizers = Collection<ChunkRenderOptimizer> optimizers =
ChunkRenderOptimizers.getAllSuppliers().stream() ChunkRenderOptimizers.getAllSuppliers().stream()
.map(ChunkRenderOptimizerSupplier::createOptimizer) .map(ChunkRenderOptimizerSupplier::createOptimizer)
@ -121,7 +154,6 @@ public class ChunkRender {
.forEach(builder::addPart); .forEach(builder::addPart);
model = new StaticModel(builder); model = new StaticModel(builder);
needsUpdate = false;
} }
private void buildBlock( 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.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import glm.vec._3.i.Vec3i; 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.backend.FaceCulling;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; 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.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable; 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.ChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListeners; import ru.windcorp.progressia.common.world.ChunkDataListeners;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.WorldDataListener; import ru.windcorp.progressia.common.world.WorldDataListener;
import ru.windcorp.progressia.common.world.entity.EntityData; 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 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 = private final Map<EntityData, EntityRenderable> entityModels =
Collections.synchronizedMap(new WeakHashMap<>()); Collections.synchronizedMap(new WeakHashMap<>());
public WorldRender(WorldData data) { private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
public WorldRender(WorldData data, Client client) {
this.data = data; this.data = data;
this.client = client;
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this))); data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
data.addListener(new WorldDataListener() { data.addListener(new WorldDataListener() {
@Override @Override
public void onChunkLoaded(WorldData world, ChunkData chunk) { public void onChunkLoaded(WorldData world, ChunkData chunk) {
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk)); addChunk(chunk);
} }
@Override @Override
public void beforeChunkUnloaded(WorldData world, ChunkData chunk) { 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() { public WorldData getData() {
return data; return data;
} }
public Client getClient() {
return client;
}
public ChunkRender getChunk(ChunkData chunkData) { public ChunkRender getChunk(ChunkData chunkData) {
return chunks.get(chunkData); return chunks.get(chunkData);
} }
@Override
public ChunkRender getChunk(Vec3i pos) { public ChunkRender getChunk(Vec3i pos) {
return chunks.get(getData().getChunk(pos)); return chunks.get(getData().getChunk(pos));
} }
@Override
public Collection<ChunkRender> getChunks() { public Collection<ChunkRender> getChunks() {
return chunks.values(); return chunks.values();
} }
public void render(ShapeRenderHelper renderer) { @Override
for (ChunkRender chunk : getChunks()) { public Collection<EntityRenderable> getEntities() {
chunk.render(renderer); return entityModels.values();
} }
public void render(ShapeRenderHelper renderer) {
updateChunks();
getChunks().forEach(chunk -> chunk.render(renderer));
renderEntities(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) { private void renderEntities(ShapeRenderHelper renderer) {
FaceCulling.push(false); FaceCulling.push(false);
for (ChunkRender chunk : getChunks()) { getData().forEachEntity(entity -> {
chunk.getData().forEachEntity(entity -> { renderer.pushTransform().translate(entity.getPosition());
renderer.pushTransform().translate(entity.getPosition()); getEntityRenderable(entity).render(renderer);
getEntityRenderable(entity).render(renderer); renderer.popTransform();
renderer.popTransform(); });
});
}
FaceCulling.pop(); FaceCulling.pop();
} }
@ -109,4 +202,10 @@ public class WorldRender {
.createRenderable(entity); .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.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.generic.GenericBlock;
import ru.windcorp.progressia.client.graphics.model.Renderable; 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) { public BlockRender(String id) {
super(id); super(id);

View File

@ -18,8 +18,8 @@
package ru.windcorp.progressia.client.world.cro; 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.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.block.BlockFace.BLOCK_FACE_COUNT;
import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;

View File

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

View File

@ -116,6 +116,8 @@ public abstract class NPedModel extends EntityRenderable {
this.body = body; this.body = body;
this.head = head; this.head = head;
this.scale = scale; this.scale = scale;
evaluateAngles();
} }
@Override @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.client.world.cro.ChunkRenderOptimizer;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace; 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) { public TileRender(String id) {
super(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; 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.comms.packets.Packet;
import ru.windcorp.progressia.common.world.DecodingException;
public class PacketControl extends Packet { public class PacketControl extends Packet {
@ -15,4 +20,14 @@ public class PacketControl extends Packet {
return control; 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; 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) { public Packet(String id) {
super(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()]; this.ints = new int[sizes.getInts()];
} }
@Override
public int getInt(int index) { public int getInt(int index) {
return ints[index]; return ints[index];
} }
@Override
public void setInt(int index, int value) { public void setInt(int index, int value) {
ints[index] = value; ints[index] = value;
} }

View File

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

View File

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

View File

@ -25,7 +25,7 @@ public class DataBuffer {
@Override @Override
public int read() throws IOException { public int read() throws IOException {
if (DataBuffer.this.position >= buffer.size()) return -1; 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; ++DataBuffer.this.position;
return result; return result;
} }

View File

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

View File

@ -19,10 +19,10 @@ public class VectorUtil {
X, Y, Z, W; X, Y, Z, W;
} }
public static void forEachVectorInCuboid( public static void iterateCuboid(
int x0, int y0, int z0, int x0, int y0, int z0,
int x1, int y1, int z1, int x1, int y1, int z1,
Consumer<Vec3i> action Consumer<? super Vec3i> action
) { ) {
Vec3i cursor = Vectors.grab3i(); Vec3i cursor = Vectors.grab3i();
@ -38,6 +38,57 @@ public class VectorUtil {
Vectors.release(cursor); 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) { public static void applyMat4(Vec3 in, Mat4 mat, Vec3 out) {
Vec4 vec4 = Vectors.grab4(); Vec4 vec4 = Vectors.grab4();
vec4.set(in, 1f); vec4.set(in, 1f);

View File

@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; 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.util.VectorUtil;
import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.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.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataStack; import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.common.world.tile.TileReference; import ru.windcorp.progressia.common.world.tile.TileReference;
import ru.windcorp.progressia.common.world.tile.TileStackIsFullException; 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; public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
@ -54,9 +59,6 @@ public class ChunkData {
BLOCK_FACE_COUNT BLOCK_FACE_COUNT
]; ];
private final List<EntityData> entities =
Collections.synchronizedList(new ArrayList<>());
private final Collection<ChunkDataListener> listeners = private final Collection<ChunkDataListener> listeners =
Collections.synchronizedCollection(new ArrayList<>()); Collections.synchronizedCollection(new ArrayList<>());
@ -65,6 +67,12 @@ public class ChunkData {
this.world = world; this.world = world;
} }
@Override
public Vec3i getPosition() {
return position;
}
@Override
public BlockData getBlock(Vec3i posInChunk) { public BlockData getBlock(Vec3i posInChunk) {
return blocks[getBlockIndex(posInChunk)]; return blocks[getBlockIndex(posInChunk)];
} }
@ -81,6 +89,7 @@ public class ChunkData {
} }
} }
@Override
public TileDataStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) { public TileDataStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) {
return tiles[getTileIndex(blockInChunk, face)]; return tiles[getTileIndex(blockInChunk, face)];
} }
@ -98,10 +107,12 @@ public class ChunkData {
this.tiles[getTileIndex(blockInChunk, face)] = tiles; this.tiles[getTileIndex(blockInChunk, face)] = tiles;
} }
@Override
public boolean hasTiles(Vec3i blockInChunk, BlockFace face) { public boolean hasTiles(Vec3i blockInChunk, BlockFace face) {
return getTilesOrNull(blockInChunk, face) != null; return getTilesOrNull(blockInChunk, face) != null;
} }
@Override
public TileDataStack getTiles(Vec3i blockInChunk, BlockFace face) { public TileDataStack getTiles(Vec3i blockInChunk, BlockFace face) {
int index = getTileIndex(blockInChunk, face); int index = getTileIndex(blockInChunk, face);
@ -144,10 +155,6 @@ public class ChunkData {
face.getId(); face.getId();
} }
public List<EntityData> getEntities() {
return entities;
}
private static void checkLocalCoordinates(Vec3i posInChunk) { private static void checkLocalCoordinates(Vec3i posInChunk) {
if (!isInBounds(posInChunk)) { if (!isInBounds(posInChunk)) {
throw new IllegalCoordinatesException( throw new IllegalCoordinatesException(
@ -176,7 +183,7 @@ public class ChunkData {
} }
public void forEachBlock(Consumer<Vec3i> action) { public void forEachBlock(Consumer<Vec3i> action) {
VectorUtil.forEachVectorInCuboid( VectorUtil.iterateCuboid(
0, 0, 0, 0, 0, 0,
BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK,
action action
@ -203,26 +210,6 @@ public class ChunkData {
forEachTileStack(stack -> stack.forEach(tileData -> action.accept(stack, tileData))); 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() { public WorldData getWorld() {
return world; return world;
} }
@ -407,6 +394,24 @@ public class ChunkData {
report(null, tile); 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 @Override
public TileData remove(int index) { public TileData remove(int index) {
TileData previous = get(index); // checks 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 { 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.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Objects;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i; 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.TLongObjectMap;
import gnu.trove.map.hash.TLongObjectHashMap; import gnu.trove.map.hash.TLongObjectHashMap;
import gnu.trove.set.TLongSet; import gnu.trove.set.TLongSet;
import ru.windcorp.progressia.common.collision.CollisionModel; 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.block.BlockData;
import ru.windcorp.progressia.common.world.entity.EntityData; 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 = private final ChunkMap<ChunkData> chunksByPos = new LongBasedChunkMap<>(
new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this); TCollections.synchronizedMap(new TLongObjectHashMap<>())
);
private final Collection<ChunkData> chunks = private final Collection<ChunkData> chunks =
Collections.unmodifiableCollection(chunksByPos.valueCollection()); Collections.unmodifiableCollection(chunksByPos.values());
private final TLongObjectMap<EntityData> entitiesById = private final TLongObjectMap<EntityData> entitiesById =
new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this); TCollections.synchronizedMap(new TLongObjectHashMap<>());
private final Collection<EntityData> entities = private final Collection<EntityData> entities =
Collections.unmodifiableCollection(entitiesById.valueCollection()); Collections.unmodifiableCollection(entitiesById.valueCollection());
@ -55,21 +69,37 @@ public class WorldData {
} }
public void tmp_generate() { @Override
final int size = 10; public ChunkData getChunk(Vec3i pos) {
Vec3i cursor = new Vec3i(0, 0, 0); return chunksByPos.get(pos);
}
for (cursor.x = -(size / 2); cursor.x <= (size / 2); ++cursor.x) { @Override
for (cursor.y = -(size / 2); cursor.y <= (size / 2); ++cursor.y) { public Collection<ChunkData> getChunks() {
for (cursor.z = -(size / 2); cursor.z <= (size / 2); ++cursor.z) { return chunks;
ChunkData chunk = new ChunkData(cursor, this); }
TestContent.generateChunk(chunk);
addChunk(chunk); 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) { private void addChunkListeners(ChunkData chunk) {
getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener)); getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener));
} }
@ -77,9 +107,7 @@ public class WorldData {
public synchronized void addChunk(ChunkData chunk) { public synchronized void addChunk(ChunkData chunk) {
addChunkListeners(chunk); addChunkListeners(chunk);
long key = getChunkKey(chunk); ChunkData previous = chunksByPos.get(chunk);
ChunkData previous = chunksByPos.get(key);
if (previous != null) { if (previous != null) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(String.format(
"Chunk at (%d; %d; %d) already exists", "Chunk at (%d; %d; %d) already exists",
@ -87,11 +115,7 @@ public class WorldData {
)); ));
} }
chunksByPos.put(key, chunk); chunksByPos.put(chunk, chunk);
chunk.forEachEntity(entity ->
entitiesById.put(entity.getEntityId(), entity)
);
chunk.onLoaded(); chunk.onLoaded();
getListeners().forEach(l -> l.onChunkLoaded(this, chunk)); getListeners().forEach(l -> l.onChunkLoaded(this, chunk));
@ -101,30 +125,7 @@ public class WorldData {
getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk)); getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk));
chunk.beforeUnloaded(); chunk.beforeUnloaded();
chunk.forEachEntity(entity -> chunksByPos.remove(chunk);
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));
} }
public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) { public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) {
@ -139,20 +140,47 @@ public class WorldData {
chunk.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, notify); 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) { public EntityData getEntity(long entityId) {
return entitiesById.get(entityId); return entitiesById.get(entityId);
} }
public Collection<EntityData> getEntities() { public void addEntity(EntityData entity) {
return entities; 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() { public float getTime() {

View File

@ -3,6 +3,7 @@ package ru.windcorp.progressia.common.world;
import java.util.function.Consumer; import java.util.function.Consumer;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.entity.EntityData;
public interface WorldDataListener { public interface WorldDataListener {
@ -31,4 +32,18 @@ public interface WorldDataListener {
*/ */
default void beforeChunkUnloaded(WorldData world, ChunkData chunk) {} 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.AABB;
import ru.windcorp.progressia.common.collision.CollisionModel; import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; 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) { public BlockData(String id) {
super(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._2.Vec2;
import glm.vec._3.Vec3; 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.Collideable;
import ru.windcorp.progressia.common.collision.CollisionModel; import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.state.StatefulObject; 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 position = new Vec3();
private final Vec3 velocity = new Vec3(); private final Vec3 velocity = new Vec3();
private final Vec2 direction = new Vec2(); 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 long entityId;
private CollisionModel collisionModel = null; private CollisionModel collisionModel = null;
@ -25,20 +31,11 @@ public class EntityData extends StatefulObject implements Collideable {
super(EntityDataRegistry.getInstance(), id); super(EntityDataRegistry.getInstance(), id);
} }
@Override
public Vec3 getPosition() { public Vec3 getPosition() {
return position; 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) { public void setPosition(Vec3 position) {
move(position.sub_(getPosition())); move(position.sub_(getPosition()));
} }
@ -79,6 +76,9 @@ public class EntityData extends StatefulObject implements Collideable {
} }
public void setEntityId(long entityId) { 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; this.entityId = entityId;
} }
@ -138,4 +138,17 @@ public class EntityData extends StatefulObject implements Collideable {
return output; 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.DataOutput;
import java.io.IOException; import java.io.IOException;
import ru.windcorp.progressia.common.comms.packets.PacketWorldChange;
import ru.windcorp.progressia.common.state.IOContext; import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.util.DataBuffer; import ru.windcorp.progressia.common.util.DataBuffer;
import ru.windcorp.progressia.common.util.crash.CrashReports; 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.WorldData;
public class PacketEntityChange extends PacketWorldChange { public class PacketEntityChange extends PacketWorldChange {
@ -43,6 +44,28 @@ public class PacketEntityChange extends PacketWorldChange {
return buffer.getWriter(); 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 @Override
public void apply(WorldData world) { public void apply(WorldData world) {
EntityData entity = world.getEntity(getEntityId()); 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.AbstractList;
import java.util.Objects; import java.util.Objects;
@ -6,11 +6,14 @@ import java.util.RandomAccess;
import java.util.function.Consumer; import java.util.function.Consumer;
import glm.vec._3.i.Vec3i; 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.Coordinates;
import ru.windcorp.progressia.common.world.block.BlockFace; 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> extends AbstractList<T>
implements RandomAccess { implements RandomAccess {
@ -21,13 +24,12 @@ implements RandomAccess {
public static final int TILES_PER_FACE = 8; public static final int TILES_PER_FACE = 8;
public abstract Vec3i getBlockInChunk(Vec3i output); public abstract Vec3i getBlockInChunk(Vec3i output);
protected abstract Vec3i getChunkPos();
public abstract C getChunk(); public abstract C getChunk();
public abstract BlockFace getFace(); public abstract BlockFace getFace();
public Vec3i getBlockInWorld(Vec3i output) { public Vec3i getBlockInWorld(Vec3i output) {
// This is safe // This is safe
return Coordinates.getInWorld(getChunkPos(), getBlockInChunk(output), output); return Coordinates.getInWorld(getChunk().getPosition(), getBlockInChunk(output), output);
} }
public boolean isFull() { 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; package ru.windcorp.progressia.common.world.tile;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; 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) { public TileData(String id) {
super(id); super(id);

View File

@ -1,10 +1,15 @@
package ru.windcorp.progressia.common.world.tile; 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.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockData; 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. * Inserts the specified tile at the specified position in this stack.
@ -21,6 +26,16 @@ public abstract class TileDataStack extends GenericTileStack<TileData, ChunkData
@Override @Override
public abstract void add(int index, TileData tile); 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. * Replaces the tile at the specified position in this stack with the specified tile.
* @param index index of the tile to replace * @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); public abstract int getTagByIndex(int index);
/*
* Implementation
*/
@Override
public Vec3i getChunkPos() {
return getChunk().getPosition();
}
/* /*
* Aliases and overloads * 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); super(entity);
this.server = server; this.server = server;
this.client = client; this.client = client;
client.setPlayer(this);
} }
public Server getServer() { public Server getServer() {
@ -34,22 +36,28 @@ public class Player extends PlayerData implements ChunkLoader {
Coordinates.convertInWorldToChunk(start, start); Coordinates.convertInWorldToChunk(start, start);
Vec3i cursor = new Vec3i(); Vec3i cursor = new Vec3i();
float radius = getServer().getLoadDistance(this); float radius = getServer().getLoadDistance(this) / Units.get(16.0f, "m");
float radiusSq = radius / Units.get(16.0f, "m");
radiusSq *= radiusSq; float radiusSq = radius * radius;
int iRadius = (int) Math.ceil(radius); int iRadius = (int) Math.ceil(radius);
for (cursor.x = -iRadius; cursor.x <= +iRadius; ++cursor.x) { for (cursor.x = -iRadius; cursor.x <= +iRadius; ++cursor.x) {
for (cursor.y = -iRadius; cursor.y <= +iRadius; ++cursor.y) { for (cursor.y = -iRadius; cursor.y <= +iRadius; ++cursor.y) {
for (cursor.z = -iRadius; cursor.z <= +iRadius; ++cursor.z) { 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); cursor.add(start);
chunkConsumer.accept(cursor); chunkConsumer.accept(cursor);
cursor.sub(start); 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.Collection;
import java.util.Collections; 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.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
import ru.windcorp.progressia.test.TestContent; import ru.windcorp.progressia.test.TestContent;
public class PlayerManager { public class PlayerManager {
@ -29,14 +34,35 @@ public class PlayerManager {
public EntityData conjurePlayerEntity(String login) { public EntityData conjurePlayerEntity(String login) {
// TODO Live up to the name // TODO Live up to the name
if (TestContent.PLAYER_LOGIN.equals(login)) { 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 { } else {
CrashReports.report(null, "Unknown login %s, javahorse stupid", login); CrashReports.report(null, "Unknown login %s, javahorse stupid", login);
return null; 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() { public Server getServer() {
return server; return server;
} }

View File

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

View File

@ -1,20 +1,18 @@
package ru.windcorp.progressia.server.comms; package ru.windcorp.progressia.server.comms;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.LogManager;
import glm.vec._3.i.Vec3i;
import gnu.trove.TCollections; import gnu.trove.TCollections;
import gnu.trove.map.TIntObjectMap; import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.map.hash.TIntObjectHashMap;
import ru.windcorp.progressia.common.comms.CommsChannel.State; import ru.windcorp.progressia.common.comms.CommsChannel.State;
import ru.windcorp.progressia.common.comms.packets.Packet; import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.common.comms.packets.PacketLoadChunk; import ru.windcorp.progressia.common.world.PacketSetLocalPlayer;
import ru.windcorp.progressia.common.comms.packets.PacketSetLocalPlayer;
import ru.windcorp.progressia.common.io.ChunkIO;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.Player; import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
@ -45,10 +43,10 @@ public class ClientManager {
if (client instanceof ClientChat) { if (client instanceof ClientChat) {
addClientChat((ClientChat) client); addClientChat((ClientChat) client);
}
if (client instanceof ClientPlayer) { if (client instanceof ClientPlayer) {
addClientPlayer((ClientPlayer) client); addClientPlayer((ClientPlayer) client);
}
} }
client.addListener(new DefaultServerCommsListener(this, client)); client.addListener(new DefaultServerCommsListener(this, client));
@ -61,29 +59,15 @@ public class ClientManager {
private void addClientPlayer(ClientPlayer client) { private void addClientPlayer(ClientPlayer client) {
String login = client.getLogin(); String login = client.getLogin();
EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login); EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login);
Player player = new Player(entity, getServer(), client); Player player = new Player(entity, getServer(), client);
getServer().getPlayerManager().getPlayers().add(player); getServer().getPlayerManager().getPlayers().add(player);
for (ChunkData chunk : server.getWorld().getData().getChunks()) { PacketSetLocalPlayer packet = new PacketSetLocalPlayer();
PacketLoadChunk packet = new PacketLoadChunk("Core:LoadChunk"); LogManager.getLogger().info("Sending local player ID {}", EntityData.formatEntityId(entity.getEntityId()));
packet.getPosition().set( packet.set(entity.getEntityId());
chunk.getPosition().x, client.sendPacket(packet);
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()));
} }
public void disconnectClient(Client client) { public void disconnectClient(Client client) {
@ -91,7 +75,11 @@ public class ClientManager {
clientsById.remove(client.getId()); 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 -> { getClients().forEach(c -> {
if (c.getState() != State.CONNECTED) return; if (c.getState() != State.CONNECTED) return;
if (!(c instanceof ClientPlayer)) 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() { public Collection<Client> getClients() {
return clients; return clients;
} }

View File

@ -1,11 +1,40 @@
package ru.windcorp.progressia.server.comms; 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) { public ClientPlayer(int id) {
super(id); super(id);
} }
public Player getPlayer() {
return player;
}
public void setPlayer(Player player) {
this.player = player;
}
public abstract String getLogin(); 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.ChunkData;
import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.BlockFace; 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.TileDataStack;
import ru.windcorp.progressia.common.world.tile.TileReference; import ru.windcorp.progressia.common.world.tile.TileReference;
import ru.windcorp.progressia.server.world.block.BlockLogic; import ru.windcorp.progressia.server.world.block.BlockLogic;
import ru.windcorp.progressia.server.world.block.BlockLogicRegistry; import ru.windcorp.progressia.server.world.block.BlockLogicRegistry;
import ru.windcorp.progressia.server.world.block.TickableBlock; 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.tasks.TickChunk;
import ru.windcorp.progressia.server.world.ticking.TickingPolicy; import ru.windcorp.progressia.server.world.ticking.TickingPolicy;
import ru.windcorp.progressia.server.world.tile.TickableTile; 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.TileLogicRegistry;
import ru.windcorp.progressia.server.world.tile.TileLogicStack; 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 WorldLogic world;
private final ChunkData data; private final ChunkData data;
@ -43,31 +46,36 @@ public class ChunkLogic {
this.world = world; this.world = world;
this.data = data; this.data = data;
generateTickLists(); tmp_generateTickLists();
} }
private void generateTickLists() { @Override
ChunkTickContext context = TickContextMutable.start().withChunk(this).build(); public Vec3i getPosition() {
return getData().getPosition();
}
context.forEachBlock(bctxt -> { @Override
BlockLogic block = bctxt.getBlock(); 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) { @Override
tickingBlocks.add(Coordinates.convertInWorldToInChunk(bctxt.getBlockInWorld(), null)); public boolean hasTiles(Vec3i blockInChunk, BlockFace face) {
} return getData().hasTiles(blockInChunk, face);
}
bctxt.forEachFace(fctxt -> fctxt.forEachTile(tctxt -> { private TileLogicStack getTileStackWrapper(TileDataStack tileDataList) {
TileLogic tile = tctxt.getTile(); return tileLogicLists.computeIfAbsent(
tileDataList,
if (!(tile instanceof TickableTile)) return; TileLogicStackImpl::new
);
if (((TickableTile) tile).getTickingPolicy(tctxt) == TickingPolicy.REGULAR) {
tickingTiles.add(tctxt.getReference());
}
}));
});
} }
public WorldLogic getWorld() { public WorldLogic getWorld() {
@ -78,10 +86,6 @@ public class ChunkLogic {
return data; return data;
} }
public Vec3i getPosition() {
return getData().getPosition();
}
public boolean hasTickingBlocks() { public boolean hasTickingBlocks() {
return !tickingBlocks.isEmpty(); 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() { public TickChunk getTickTask() {
return tickTask; return tickTask;
} }
@ -154,11 +126,6 @@ public class ChunkLogic {
return parent.getBlockInChunk(output); return parent.getBlockInChunk(output);
} }
@Override
public Vec3i getChunkPos() {
return ChunkLogic.this.getPosition();
}
@Override @Override
public ChunkLogic getChunk() { public ChunkLogic getChunk() {
return ChunkLogic.this; 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()); 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() {} 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.ChunkData;
import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.BlockFace; 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.TileDataStack;
import ru.windcorp.progressia.common.world.tile.TileReference; import ru.windcorp.progressia.common.world.tile.TileReference;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
@ -87,7 +87,7 @@ public abstract class TickContextMutable implements BlockTickContext, TSTickCont
public static interface World extends Builder { public static interface World extends Builder {
Chunk withChunk(Vec3i chunk); Chunk withChunk(Vec3i chunk);
Block withBlock(Vec3i blockInWorld); Block withBlock(Vec3i blockInWorld);
TileStack withTS(GenericTileStack<?, ?> tileStack); TileStack withTS(GenericTileStack<?, ?, ?> tileStack);
default Builder.Chunk withChunk(ChunkData chunk) { default Builder.Chunk withChunk(ChunkData chunk) {
Objects.requireNonNull(chunk, "chunk"); Objects.requireNonNull(chunk, "chunk");
@ -237,7 +237,7 @@ public abstract class TickContextMutable implements BlockTickContext, TSTickCont
} }
@Override @Override
public TileStack withTS(GenericTileStack<?, ?> tileStack) { public TileStack withTS(GenericTileStack<?, ?, ?> tileStack) {
Objects.requireNonNull(tileStack, "tileStack"); Objects.requireNonNull(tileStack, "tileStack");
return withBlock(tileStack.getBlockInWorld(this.blockInWorld)).withFace(tileStack.getFace()); 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.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListeners; 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.WorldData;
import ru.windcorp.progressia.common.world.WorldDataListener; 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.Server;
import ru.windcorp.progressia.server.world.block.BlockLogic; 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.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 WorldData data;
private final Server server; private final Server server;
private final Map<ChunkData, ChunkLogic> chunks = new HashMap<>(); private final Map<ChunkData, ChunkLogic> chunks = new HashMap<>();
private final Evaluation tickEntitiesTask = new TickEntitiesTask();
public WorldLogic(WorldData data, Server server) { public WorldLogic(WorldData data, Server server) {
this.data = data; this.data = data;
this.server = server; this.server = server;
@ -41,7 +51,25 @@ public class WorldLogic {
}); });
data.addListener(ChunkDataListeners.createAdder(new UpdateTriggerer(server))); 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() { public Server getServer() {
@ -56,52 +84,4 @@ public class WorldLogic {
return chunks.get(chunkData); 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.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace; 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) { public BlockLogic(String id) {
super(id); super(id);

View File

@ -2,53 +2,12 @@ package ru.windcorp.progressia.server.world.tasks;
import java.util.function.Consumer; import java.util.function.Consumer;
import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.tile.PacketAddTile;
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;
class AddTile extends CachedWorldChange { class AddTile extends CachedChunkChange<PacketAddTile> {
private final Vec3i blockInWorld = new Vec3i();
private BlockFace face;
private TileData tile;
public AddTile(Consumer<? super CachedChange> disposer) { public AddTile(Consumer<? super CachedChange> disposer) {
super(disposer, "Core:AddTile"); super(disposer, new PacketAddTile());
}
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;
} }
} }

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 java.util.function.Consumer;
import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.PacketWorldChange;
import ru.windcorp.progressia.server.Server; 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); super(disposer);
this.packet = packet;
this.packet = new PacketWorldChange(packetId) {
@Override
public void apply(WorldData world) {
affectCommon(world);
}
};
} }
@Override @Override
public void affect(Server server) { public void affect(Server server) {
affectCommon(server.getWorld().getData()); affectLocal(server);
server.getClientManager().broadcastGamePacket(packet); sendPacket(server);
} }
/** protected void affectLocal(Server server) {
* Invoked by both Change and Packet. packet.apply(server.getWorld().getData());
* @param world the world to affect }
*/
protected abstract void affectCommon(WorldData world); 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; package ru.windcorp.progressia.server.world.tasks;
import java.io.IOException;
import java.util.function.Consumer; import java.util.function.Consumer;
import glm.vec._3.i.Vec3i; 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.EntityData;
import ru.windcorp.progressia.common.world.entity.PacketEntityChange; import ru.windcorp.progressia.common.world.entity.PacketEntityChange;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
@ -31,26 +28,15 @@ class ChangeEntity extends CachedChange {
this.entity = entity; this.entity = entity;
this.change = change; 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") @SuppressWarnings("unchecked")
@Override @Override
public void affect(Server server) { public void affect(Server server) {
((StateChange<EntityData>) change).change(entity); ((StateChange<EntityData>) change).change(entity);
packet.set(entity);
try { server.getClientManager().broadcastLocal(packet, entity.getEntityId());
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);
} }
@Override @Override

View File

@ -2,43 +2,12 @@ package ru.windcorp.progressia.server.world.tasks;
import java.util.function.Consumer; import java.util.function.Consumer;
import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.tile.PacketRemoveTile;
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;
class RemoveTile extends CachedWorldChange { class RemoveTile extends CachedChunkChange<PacketRemoveTile> {
private final Vec3i blockInWorld = new Vec3i();
private BlockFace face;
private int tag;
public RemoveTile(Consumer<? super CachedChange> disposer) { public RemoveTile(Consumer<? super CachedChange> disposer) {
super(disposer, "Core:RemoveTile"); super(disposer, new PacketRemoveTile());
}
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);
} }
} }

View File

@ -2,44 +2,12 @@ package ru.windcorp.progressia.server.world.tasks;
import java.util.function.Consumer; import java.util.function.Consumer;
import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.block.PacketSetBlock;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.block.BlockData;
class SetBlock extends CachedWorldChange { class SetBlock extends CachedChunkChange<PacketSetBlock> {
private final Vec3i blockInWorld = new Vec3i();
private BlockData block;
public SetBlock(Consumer<? super CachedChange> disposer) { public SetBlock(Consumer<? super CachedChange> disposer) {
super(disposer, "Core:SetBlock"); super(disposer, new PacketSetBlock());
}
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;
} }
} }

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.common.world.tile.TileDataStack;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.ChunkLogic; 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.TickContextMutable;
import ru.windcorp.progressia.server.world.block.BlockLogic; import ru.windcorp.progressia.server.world.block.BlockLogic;
import ru.windcorp.progressia.server.world.block.TickableBlock; import ru.windcorp.progressia.server.world.block.TickableBlock;
@ -55,7 +54,6 @@ public class TickChunk extends Evaluation {
public void evaluate(Server server) { public void evaluate(Server server) {
tickRegulars(server); tickRegulars(server);
tickRandom(server); tickRandom(server);
tickEntities(server);
} }
private void tickRegulars(Server 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 @Override
public void getRelevantChunk(Vec3i output) { public void getRelevantChunk(Vec3i output) {
Vec3i p = chunk.getData().getPosition(); 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) { public void setBlock(Vec3i blockInWorld, BlockData block) {
SetBlock change = cache.grab(SetBlock.class); SetBlock change = cache.grab(SetBlock.class);
change.initialize(blockInWorld, block); change.getPacket().set(block, blockInWorld);
server.requestChange(change); server.requestChange(change);
} }
@ -48,7 +48,7 @@ public class WorldAccessor {
public void addTile(Vec3i blockInWorld, BlockFace face, TileData tile) { public void addTile(Vec3i blockInWorld, BlockFace face, TileData tile) {
AddTile change = cache.grab(AddTile.class); AddTile change = cache.grab(AddTile.class);
change.initialize(blockInWorld, face, tile); change.getPacket().set(tile, blockInWorld, face);
server.requestChange(change); server.requestChange(change);
} }
@ -58,7 +58,7 @@ public class WorldAccessor {
public void removeTile(Vec3i blockInWorld, BlockFace face, int tag) { public void removeTile(Vec3i blockInWorld, BlockFace face, int tag) {
RemoveTile change = cache.grab(RemoveTile.class); RemoveTile change = cache.grab(RemoveTile.class);
change.initialize(blockInWorld, face, tag); change.getPacket().set(blockInWorld, face, tag);
server.requestChange(change); 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.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace; 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) { public TileLogic(String id) {
super(id); super(id);

View File

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

View File

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

View File

@ -1,26 +1,59 @@
package ru.windcorp.progressia.test; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; 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 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.io.ChunkCodec;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DecodingException; import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.WorldData; 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 class TestChunkCodec extends ChunkCodec {
public TestChunkCodec() { private static class Palette<E> {
super("Test:TestCodec", 0x00); 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 TestChunkCodec() {
public ChunkData decode(WorldData world, Vec3i position, InputStream data) throws DecodingException, IOException { super("Test:TestCodec", 0x00);
ChunkData chunk = new ChunkData(position, world);
TestContent.generateChunk(chunk);
return chunk;
} }
@Override @Override
@ -28,9 +61,168 @@ public class TestChunkCodec extends ChunkCodec {
return true; return true;
} }
/*
* Decoding
*/
@Override @Override
public void encode(ChunkData chunk, OutputStream output) throws IOException { public ChunkData decode(WorldData world, Vec3i position, InputStream inputStream) throws DecodingException, IOException {
// Do nothing. Heh. 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 org.lwjgl.glfw.GLFW;
import glm.Glm;
import glm.vec._2.Vec2;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.ClientState; import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.audio.SoundEffect; import ru.windcorp.progressia.client.audio.SoundEffect;
import ru.windcorp.progressia.client.comms.controls.*; import ru.windcorp.progressia.client.comms.controls.*;
import ru.windcorp.progressia.client.graphics.input.KeyEvent; import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher; 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.graphics.world.Selection;
import ru.windcorp.progressia.client.world.block.*; import ru.windcorp.progressia.client.world.block.*;
import ru.windcorp.progressia.client.world.entity.*; 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.comms.controls.*;
import ru.windcorp.progressia.common.io.ChunkIO; import ru.windcorp.progressia.common.io.ChunkIO;
import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory; import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.*; import ru.windcorp.progressia.common.world.block.*;
@ -214,12 +209,9 @@ public class TestContent {
private static Selection getSelection() { private static Selection getSelection() {
ru.windcorp.progressia.client.Client client = ClientState.getInstance(); ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null) return null; if (client == null || !client.isReady()) return null;
LocalPlayer player = client.getLocalPlayer(); return client.getLocalPlayer().getSelection();
if (player == null) return null;
return player.getSelection();
} }
private static void onBlockBreakTrigger(ControlData control) { 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() { 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.Units;
import ru.windcorp.progressia.common.util.FloatMathUtils; import ru.windcorp.progressia.common.util.FloatMathUtils;
import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.ServerState;
public class TestPlayerControls { public class TestPlayerControls {
@ -28,8 +29,8 @@ public class TestPlayerControls {
private TestPlayerControls() {} private TestPlayerControls() {}
private static final double MODE_SWITCH_MAX_DELAY = 100 * Units.MILLISECONDS; private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS;
private static final double MIN_JUMP_DELAY = 200 * Units.MILLISECONDS; private static final double MIN_JUMP_DELAY = 400 * Units.MILLISECONDS;
// Horizontal and vertical max control speed when flying // Horizontal and vertical max control speed when flying
private static final float FLYING_SPEED = 6.0f * Units.METERS_PER_SECOND; private static final float FLYING_SPEED = 6.0f * Units.METERS_PER_SECOND;
@ -60,7 +61,7 @@ public class TestPlayerControls {
private Runnable updateCallback = null; private Runnable updateCallback = null;
public void applyPlayerControls() { public void applyPlayerControls() {
if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) { if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return; return;
} }
@ -96,10 +97,17 @@ public class TestPlayerControls {
} }
player.getVelocity().set(change); 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) { public void handleInput(Input input) {
if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) { if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return; 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);
}
}
}