Entities are now transferred from server to client

- Entities are no longer internally bound to chunks
- Entity visibility for a player is now unique for each entity
  - Entities are send/revoked just like chunks by EntityManager
- LocalPlayer no longer stores an entity; instead it stores entity ID
and looks up the entity on demand
This commit is contained in:
OLEGSHA 2020-12-29 16:40:37 +03:00
parent cde854346c
commit b44540999b
27 changed files with 647 additions and 168 deletions

View File

@ -1,8 +1,11 @@
package ru.windcorp.progressia.client; package ru.windcorp.progressia.client;
import org.apache.logging.log4j.LogManager;
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,7 +14,7 @@ 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));
@ -32,8 +35,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 +47,17 @@ public class Client {
return comms; return comms;
} }
public void onLocalPlayerEntityChanged(EntityData entity, EntityData lastKnownEntity) {
LogManager.getLogger().info("LocalPlayer entity changed from {} to {}", lastKnownEntity, entity);
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.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.PacketSetLocalPlayer; import ru.windcorp.progressia.common.world.PacketSetLocalPlayer;
import ru.windcorp.progressia.common.world.PacketWorldChange; import ru.windcorp.progressia.common.world.PacketWorldChange;
import ru.windcorp.progressia.common.world.entity.EntityData;
// 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.getEntityId()
);
if (entity == null) {
CrashReports.report(
null,
"Player entity with ID %s not found",
new String(StringUtil.toFullHex(packet.getEntityId()))
);
}
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

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

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

@ -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,7 +31,6 @@ 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.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;
@ -61,9 +59,6 @@ implements GenericChunk<
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<>());
@ -160,10 +155,6 @@ implements GenericChunk<
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(
@ -219,10 +210,6 @@ implements GenericChunk<
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 WorldData getWorld() { public WorldData getWorld() {
return world; return world;
} }

View File

@ -20,11 +20,12 @@ 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 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 ru.windcorp.progressia.common.collision.CollisionModel; import ru.windcorp.progressia.common.collision.CollisionModel;
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;
@ -46,14 +47,14 @@ implements GenericWorld<
> { > {
private final ChunkMap<ChunkData> chunksByPos = new LongBasedChunkMap<>( 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.values()); 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());
@ -86,6 +87,10 @@ implements GenericWorld<
return entities; return entities;
} }
public TLongSet getLoadedEntities() {
return entitiesById.keySet();
}
public void tmp_generate() { public void tmp_generate() {
final int size = 1; final int size = 1;
Vec3i cursor = new Vec3i(0, 0, 0); Vec3i cursor = new Vec3i(0, 0, 0);
@ -118,10 +123,6 @@ implements GenericWorld<
chunksByPos.put(chunk, 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));
} }
@ -130,10 +131,6 @@ implements GenericWorld<
getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk)); getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk));
chunk.beforeUnloaded(); chunk.beforeUnloaded();
chunk.forEachEntity(entity ->
entitiesById.remove(entity.getEntityId())
);
chunksByPos.remove(chunk); chunksByPos.remove(chunk);
} }
@ -153,6 +150,45 @@ implements GenericWorld<
return entitiesById.get(entityId); return entitiesById.get(entityId);
} }
public void addEntity(EntityData entity) {
Objects.requireNonNull(entity, "entity");
EntityData previous = entitiesById.putIfAbsent(entity.getEntityId(), entity);
if (previous != null) {
String message = "Cannot add entity " + entity + ": ";
if (previous == entity) {
message += "already present";
} else {
message += "entity with the same EntityID already present (" + previous + ")";
}
throw new IllegalStateException(message);
}
getListeners().forEach(l -> l.onEntityAdded(this, entity));
}
public void removeEntity(long entityId) {
synchronized (entitiesById) {
EntityData entity = entitiesById.get(entityId);
if (entity == null) {
throw new IllegalArgumentException("Entity with EntityID " + EntityData.formatEntityId(entityId) + " not present");
} else {
removeEntity(entity);
}
}
}
public void removeEntity(EntityData entity) {
Objects.requireNonNull(entity, "entity");
getListeners().forEach(l -> l.beforeEntityRemoved(this, entity));
entitiesById.remove(entity.getEntityId());
}
public float getTime() { public float getTime() {
return time; return time;
} }

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

@ -2,6 +2,7 @@ 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 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;
@ -14,6 +15,12 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
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;
@ -69,6 +76,9 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
} }
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;
} }
@ -128,4 +138,17 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
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

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

@ -1,5 +1,9 @@
package ru.windcorp.progressia.common.world.generic; package ru.windcorp.progressia.common.world.generic;
import java.util.Iterator;
import java.util.NoSuchElementException;
import glm.vec._3.i.Vec3i;
import gnu.trove.impl.sync.TSynchronizedLongSet; import gnu.trove.impl.sync.TSynchronizedLongSet;
import gnu.trove.set.hash.TLongHashSet; import gnu.trove.set.hash.TLongHashSet;
@ -17,6 +21,78 @@ public class ChunkSets {
return new LongBasedChunkSet(new TSynchronizedLongSet(new TLongHashSet())); return new LongBasedChunkSet(new TSynchronizedLongSet(new TLongHashSet()));
} }
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();
}
};
public static ChunkSet empty() {
return EMPTY_SET;
}
private ChunkSets() {} private ChunkSets() {}
} }

View File

@ -8,10 +8,11 @@ import org.apache.logging.log4j.LogManager;
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.PacketRevokeChunk;
import ru.windcorp.progressia.common.world.PacketSendChunk;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.generic.ChunkSet; import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets; import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.test.TestChunkSender;
import ru.windcorp.progressia.test.TestContent; import ru.windcorp.progressia.test.TestContent;
public class ChunkManager { public class ChunkManager {
@ -148,14 +149,29 @@ public class ChunkManager {
public void sendChunk(Player player, Vec3i chunkPos) { public void sendChunk(Player player, Vec3i chunkPos) {
LogManager.getLogger().info("Sending {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z); LogManager.getLogger().info("Sending {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z);
TestChunkSender.sendChunk(server, player.getClient(), 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); getVision(player, true).visible.add(chunkPos);
} }
public void revokeChunk(Player player, Vec3i chunkPos) { public void revokeChunk(Player player, Vec3i chunkPos) {
LogManager.getLogger().info("Revoking {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z); LogManager.getLogger().info("Revoking {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z);
TestChunkSender.revokeChunk(player.getClient(), chunkPos);
PacketRevokeChunk packet = new PacketRevokeChunk();
packet.set(chunkPos);
player.getClient().sendPacket(packet);
PlayerVision vision = getVision(player, false); PlayerVision vision = getVision(player, false);
if (vision != null) { if (vision != null) {
@ -173,6 +189,16 @@ public class ChunkManager {
return vision.isChunkVisible(chunkPos); 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() { public Server getServer() {
return server; return server;
} }

View File

@ -0,0 +1,183 @@
package ru.windcorp.progressia.server;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import org.apache.logging.log4j.LogManager;
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"
);
}
LogManager.getLogger().info("Sending {}", entity);
PacketSendEntity packet = new PacketSendEntity();
packet.set(entity);
player.getClient().sendPacket(packet);
getVision(player, true).visible.add(entityId);
}
public void revokeEntity(Player player, long entityId) {
LogManager.getLogger().info("Revoking {}", new String(StringUtil.toFullHex(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

@ -55,4 +55,8 @@ public class Player extends PlayerData implements ChunkLoader {
} }
} }
public String getLogin() {
return getClient().getLogin();
}
} }

View File

@ -4,10 +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 glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.Vectors; 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 {
@ -38,13 +41,28 @@ public class PlayerManager {
getServer().getChunkManager().loadChunk(chunkPos); getServer().getChunkManager().loadChunk(chunkPos);
} }
return getServer().getWorld().getData().getEntity(TestContent.PLAYER_ENTITY_ID); 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

@ -32,6 +32,7 @@ public class Server {
private final ClientManager clientManager; private final ClientManager clientManager;
private final PlayerManager playerManager; private final PlayerManager playerManager;
private final ChunkManager chunkManager; private final ChunkManager chunkManager;
private final EntityManager entityManager;
private final TaskQueue taskQueue = new TaskQueue(this::isServerThread); private final TaskQueue taskQueue = new TaskQueue(this::isServerThread);
@ -44,9 +45,11 @@ public class Server {
this.clientManager = new ClientManager(this); this.clientManager = new ClientManager(this);
this.playerManager = new PlayerManager(this); this.playerManager = new PlayerManager(this);
this.chunkManager = new ChunkManager(this); this.chunkManager = new ChunkManager(this);
this.entityManager = new EntityManager(this);
schedule(this::scheduleChunkTicks); schedule(this::scheduleWorldTicks);
schedule(chunkManager::tick); schedule(chunkManager::tick);
schedule(entityManager::tick);
} }
/** /**
@ -197,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,7 @@ 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(); // server.getWorld().getData().tmp_generate();
setInstance(server); setInstance(server);
server.start(); server.start();
} }

View File

@ -4,6 +4,8 @@ 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 glm.vec._3.i.Vec3i;
import gnu.trove.TCollections; import gnu.trove.TCollections;
import gnu.trove.map.TIntObjectMap; import gnu.trove.map.TIntObjectMap;
@ -41,11 +43,11 @@ 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));
} }
@ -58,18 +60,12 @@ public class ClientManager {
private void addClientPlayer(ClientPlayer client) { private void addClientPlayer(ClientPlayer client) {
String login = client.getLogin(); String login = client.getLogin();
EntityData entity; EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login);
synchronized (getServer().getWorld().getData()) {
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);
getServer().getChunkManager().sendChunk(player, entity.getChunkCoords(null));
}
PacketSetLocalPlayer packet = new PacketSetLocalPlayer(); PacketSetLocalPlayer packet = new PacketSetLocalPlayer();
LogManager.getLogger().info("Sending local player ID {}", EntityData.formatEntityId(entity.getEntityId()));
packet.set(entity.getEntityId()); packet.set(entity.getEntityId());
client.sendPacket(packet); client.sendPacket(packet);
} }

View File

@ -1,6 +1,8 @@
package ru.windcorp.progressia.server.comms; package ru.windcorp.progressia.server.comms;
import glm.vec._3.i.Vec3i; 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; import ru.windcorp.progressia.server.Player;
public abstract class ClientPlayer extends ClientChat { public abstract class ClientPlayer extends ClientChat {
@ -26,6 +28,11 @@ public abstract class ClientPlayer extends ClientChat {
return player.getServer().getChunkManager().isChunkVisible(chunkPos, player); 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) { public boolean isChunkVisible(long entityId) {
return true; return true;
} }

View File

@ -11,15 +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.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;
@ -112,15 +109,6 @@ public class ChunkLogic implements GenericChunk<
}); });
} }
public void forEachEntity(BiConsumer<EntityLogic, EntityData> action) {
getData().forEachEntity(data -> {
action.accept(
EntityLogicRegistry.getInstance().get(data.getId()),
data
);
});
}
public TickChunk getTickTask() { public TickChunk getTickTask() {
return tickTask; return tickTask;
} }

View File

@ -13,6 +13,8 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.GenericWorld; 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.server.world.tile.TileLogicStack; import ru.windcorp.progressia.server.world.tile.TileLogicStack;
@ -30,6 +32,8 @@ implements GenericWorld<
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;
@ -64,6 +68,10 @@ implements GenericWorld<
return getData().getEntities(); return getData().getEntities();
} }
public Evaluation getTickEntitiesTask() {
return tickEntitiesTask;
}
public Server getServer() { public Server getServer() {
return server; return server;
} }

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

@ -1,44 +0,0 @@
package ru.windcorp.progressia.test;
import java.io.IOException;
import glm.vec._3.i.Vec3i;
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.PacketRevokeChunk;
import ru.windcorp.progressia.common.world.PacketSendChunk;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.comms.ClientPlayer;
public class TestChunkSender {
public static void sendChunk(Server server, ClientPlayer receiver, 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.getPosition().set(chunkPos.x, chunkPos.y, chunkPos.z);
try {
ChunkIO.save(chunk, packet.getData().getOutputStream());
} catch (IOException e) {
CrashReports.report(e, "TestChunkSender fjcked up. javahorse stupid");
}
receiver.sendPacket(packet);
}
public static void revokeChunk(ClientPlayer receiver, Vec3i chunkPos) {
PacketRevokeChunk packet = new PacketRevokeChunk();
packet.set(chunkPos);
receiver.sendPacket(packet);
}
}

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

@ -61,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;
} }
@ -107,7 +107,7 @@ public class TestPlayerControls {
} }
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;
} }