Changed some collection messages to prepacked for network efficiency

This commit is contained in:
kuroppoi 2022-08-14 14:35:47 +02:00
parent e50ecd1b2b
commit 0d36d7cb65
14 changed files with 376 additions and 108 deletions

View file

@ -29,7 +29,8 @@ public @interface MessageInfo {
/**
* Defines whether or not the values of this message should be part of a collection,
* allowing for multiple messages to be sent in a single packet.
* This feature is currently not supported and this option only exists for client compatibility.
* This feature is not supported and this option only exists for client compatibility,
* but you can use the {@code prepacked} option to achieve the same result.
*
* The default value is false.
*/

View file

@ -2,6 +2,7 @@ package brainwine.gameserver.entity.player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -58,6 +59,7 @@ import brainwine.gameserver.server.messages.TeleportMessage;
import brainwine.gameserver.server.messages.WardrobeMessage;
import brainwine.gameserver.server.messages.XpMessage;
import brainwine.gameserver.server.messages.ZoneStatusMessage;
import brainwine.gameserver.server.models.EntityStatusData;
import brainwine.gameserver.server.pipeline.Connection;
import brainwine.gameserver.util.MapHelper;
import brainwine.gameserver.util.MathUtils;
@ -173,11 +175,7 @@ public class Player extends Entity implements CommandExecutor {
// Update tracked entities
if(now - lastTrackedEntityUpdate >= TRACKED_ENTITY_UPDATE_INTERVAL) {
updateTrackedEntities();
for(Entity entity : trackedEntities) {
sendMessage(new EntityPositionMessage(entity));
}
sendMessage(new EntityPositionMessage(trackedEntities));
lastTrackedEntityUpdate = now;
}
}
@ -269,21 +267,24 @@ public class Player extends Entity implements CommandExecutor {
sendMessage(new HealthMessage(health));
sendMessage(new InventoryMessage(inventory));
sendMessage(new WardrobeMessage(wardrobe));
sendMessage(new BlockMetaMessage(zone.getGlobalMetaBlocks()));
for(MetaBlock metaBlock : zone.getGlobalMetaBlocks()) {
sendMessage(new BlockMetaMessage(metaBlock));
}
// Send skill data
for(Skill skill : skills.keySet()) {
sendMessage(new SkillMessage(skill, skills.get(skill)));
}
for(Player peer : zone.getPlayers()) {
sendMessage(new EntityStatusMessage(peer, EntityStatus.ENTERING));
sendMessage(new EntityPositionMessage(peer.getId(), peer.getX(), peer.getY(), 0, 0, FacingDirection.EAST, 0, 0, 0));
// Send peer data
Collection<Player> peers = zone.getPlayers();
sendMessage(new EntityStatusMessage(peers, EntityStatus.ENTERING));
sendMessage(new EntityPositionMessage(peers));
// TODO prepack this as well
for(Player peer : peers) {
sendMessage(new EntityItemUseMessage(peer.getId(), 0, peer.getHeldItem(), 0));
}
// Send achievement data
for(Achievement achievement : AchievementManager.getAchievements()) {
if(hasAchievement(achievement)) {
sendMessage(new AchievementMessage(achievement.getTitle(), 0));
@ -296,6 +297,7 @@ public class Player extends Entity implements CommandExecutor {
}
}
// And finally, enter the zone!
if(isV3()) {
sendMessage(new EventMessage("zoneEntered", null));
notify("Welcome to " + zone.getName(), NotificationType.LARGE);
@ -983,16 +985,28 @@ public class Player extends Entity implements CommandExecutor {
}
private void updateTrackedEntities() {
// Get all entities in range of the player
List<Entity> entitiesInRange = zone.getEntitiesInRange(x, y, ENTITY_VISIBILITY_RANGE);
// Exclude self
entitiesInRange.remove(this);
List<Entity> enteredEntities = entitiesInRange.stream().filter(entity -> !trackedEntities.contains(entity))
.collect(Collectors.toList());
List<Entity> departedEntities = trackedEntities.stream().filter(entity -> !entitiesInRange.contains(entity))
// Get entities that have entered the player's view
List<Entity> enteredEntities = entitiesInRange.stream()
.filter(entity -> !trackedEntities.contains(entity))
.collect(Collectors.toList());
for(Entity entity : enteredEntities) {
// Get entities that have left the player's view
List<Entity> departedEntities = trackedEntities.stream()
.filter(entity -> !entitiesInRange.contains(entity))
.collect(Collectors.toList());
// Create status data for relevant entities
List<EntityStatusData> statuses = new ArrayList<>();
for(Entity entity : enteredEntities) {
if(entity instanceof Npc) {
sendMessage(new EntityStatusMessage(entity, EntityStatus.ENTERING));
statuses.add(EntityStatusData.entering(entity));
}
entity.addTracker(this);
@ -1000,12 +1014,17 @@ public class Player extends Entity implements CommandExecutor {
for(Entity entity : departedEntities) {
if(entity instanceof Npc) {
sendMessage(new EntityStatusMessage(entity, EntityStatus.EXITING));
statuses.add(EntityStatusData.exiting(entity));
}
entity.removeTracker(this);
}
// Send status data if there is any
if(!statuses.isEmpty()) {
sendMessage(new EntityStatusMessage(statuses));
}
trackedEntities.clear();
trackedEntities.addAll(entitiesInRange);
}

View file

@ -0,0 +1,28 @@
package brainwine.gameserver.serialization;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import brainwine.gameserver.zone.MetaBlock;
public class NetworkMetaBlockSerializer extends StdSerializer<MetaBlock> {
public static final NetworkMetaBlockSerializer INSTANCE = new NetworkMetaBlockSerializer();
private static final long serialVersionUID = -3597246970639428645L;
protected NetworkMetaBlockSerializer() {
super(MetaBlock.class);
}
@Override
public void serialize(MetaBlock metaBlock, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeStartArray();
generator.writeNumber(metaBlock.getX());
generator.writeNumber(metaBlock.getY());
generator.writeObject(metaBlock.getClientMetadata());
generator.writeEndArray();
}
}

View file

@ -22,6 +22,7 @@ import brainwine.gameserver.serialization.BlockSerializer;
import brainwine.gameserver.serialization.ItemCodeSerializer;
import brainwine.gameserver.serialization.MessageSerializer;
import brainwine.gameserver.serialization.NetworkChunkSerializer;
import brainwine.gameserver.serialization.NetworkMetaBlockSerializer;
import brainwine.gameserver.serialization.RequestDeserializerModifier;
import brainwine.gameserver.server.pipeline.Connection;
import brainwine.gameserver.server.pipeline.MessageEncoder;
@ -57,6 +58,7 @@ public class Server {
.addSerializer(MessageSerializer.INSTANCE)
.addSerializer(BlockSerializer.INSTANCE)
.addSerializer(NetworkChunkSerializer.INSTANCE)
.addSerializer(NetworkMetaBlockSerializer.INSTANCE)
.addSerializer(ItemCodeSerializer.INSTANCE))
.build();
private static final ObjectWriter writer = mapper.writer();

View file

@ -1,25 +1,24 @@
package brainwine.gameserver.server.messages;
import java.util.Arrays;
import java.util.Collection;
import brainwine.gameserver.annotations.MessageInfo;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.item.Layer;
import brainwine.gameserver.server.Message;
import brainwine.gameserver.server.models.BlockChangeData;
@MessageInfo(id = 9, collection = true)
@MessageInfo(id = 9, prepacked = true)
public class BlockChangeMessage extends Message {
public int x;
public int y;
public Layer layer;
public int entityId;
public Item item;
public int mod;
public Collection<BlockChangeData> blockChanges;
public BlockChangeMessage(Collection<BlockChangeData> blockChanges) {
this.blockChanges = blockChanges;
}
public BlockChangeMessage(int x, int y, Layer layer, Item item, int mod) {
this.x = x;
this.y = y;
this.layer = layer;
this.item = item;
this.mod = mod;
this(Arrays.asList(new BlockChangeData(x, y, layer, item, mod)));
}
}

View file

@ -1,33 +1,27 @@
package brainwine.gameserver.server.messages;
import java.util.Map;
import java.util.Arrays;
import java.util.Collection;
import brainwine.gameserver.annotations.MessageInfo;
import brainwine.gameserver.server.Message;
import brainwine.gameserver.util.MapHelper;
import brainwine.gameserver.zone.MetaBlock;
@MessageInfo(id = 20, collection = true)
@MessageInfo(id = 20, prepacked = true)
public class BlockMetaMessage extends Message {
public int x;
public int y;
public Map<String, Object> metadata;
public Collection<MetaBlock> metaBlocks;
public BlockMetaMessage(MetaBlock block) {
this.x = block.getX();
this.y = block.getY();
this.metadata = MapHelper.copy(block.getMetadata());
this.metadata.put("i", block.getItem().getId());
if(block.hasOwner()) {
this.metadata.put("p", block.getOwner());
}
public BlockMetaMessage(Collection<MetaBlock> metaBlocks) {
this.metaBlocks = metaBlocks;
}
public BlockMetaMessage(int x, int y, Map<String, Object> metadata) {
this.x = x;
this.y = y;
this.metadata = metadata;
public BlockMetaMessage(MetaBlock metaBlock) {
this(Arrays.asList(metaBlock));
}
// TODO Kind of evil...
public BlockMetaMessage(int x, int y) {
this(new MetaBlock(x, y));
}
}

View file

@ -1,36 +1,30 @@
package brainwine.gameserver.server.messages;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
import brainwine.gameserver.annotations.MessageInfo;
import brainwine.gameserver.entity.Entity;
import brainwine.gameserver.entity.FacingDirection;
import brainwine.gameserver.server.Message;
import brainwine.gameserver.server.models.EntityPositionData;
@MessageInfo(id = 6, collection = true)
@MessageInfo(id = 6, prepacked = true)
public class EntityPositionMessage extends Message {
public int id;
public int x;
public int y;
public int velocityX;
public int velocityY;
public FacingDirection direction;
public int targetX;
public int targetY;
public int animation;
public Collection<EntityPositionData> positions;
public EntityPositionMessage(Collection<? extends Entity> entities) {
this.positions = entities.stream().map(EntityPositionData::new).collect(Collectors.toList());
}
public EntityPositionMessage(Entity entity) {
this(entity.getId(), entity.getX(), entity.getY(), entity.getVelocityX(), entity.getVelocityY(), entity.getDirection(), entity.getTargetX(), entity.getTargetY(), entity.getAnimation());
this.positions = Arrays.asList(new EntityPositionData(entity));
}
public EntityPositionMessage(int id, float x, float y, float velocityX, float velocityY, FacingDirection direction, int targetX, int targetY, int animation) {
this.id = id;
this.x = (int)(x * Entity.POSITION_MODIFIER);
this.y = (int)(y * Entity.POSITION_MODIFIER);
this.velocityX = (int)(velocityX * Entity.VELOCITY_MODIFIER);
this.velocityY = (int)(velocityY * Entity.VELOCITY_MODIFIER);
this.direction = direction;
this.targetX = targetX * Entity.VELOCITY_MODIFIER;
this.targetY = targetY * Entity.VELOCITY_MODIFIER;
this.animation = animation;
public EntityPositionMessage(int id, float x, float y, float velocityX, float velocityY, FacingDirection direction,
int targetX, int targetY, int animation) {
this.positions = Arrays.asList(new EntityPositionData(id, x, y, velocityX, velocityY, direction, targetX, targetY, animation));
}
}

View file

@ -1,30 +1,34 @@
package brainwine.gameserver.server.messages;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import brainwine.gameserver.annotations.MessageInfo;
import brainwine.gameserver.entity.Entity;
import brainwine.gameserver.entity.EntityStatus;
import brainwine.gameserver.server.Message;
import brainwine.gameserver.server.models.EntityStatusData;
@MessageInfo(id = 7, collection = true)
@MessageInfo(id = 7, prepacked = true)
public class EntityStatusMessage extends Message {
public int id;
public int type;
public String name;
public EntityStatus status;
public Map<String, Object> details;
public Collection<EntityStatusData> statuses;
public EntityStatusMessage(Collection<EntityStatusData> statuses) {
this.statuses = statuses;
}
public EntityStatusMessage(Collection<? extends Entity> entities, EntityStatus status) {
this(entities.stream().map(entity -> new EntityStatusData(entity, status)).collect(Collectors.toList()));
}
public EntityStatusMessage(Entity entity, EntityStatus status) {
this(entity.getId(), entity.getType(), entity.getName(), status, entity.getStatusConfig());
this(Arrays.asList(new EntityStatusData(entity, status)));
}
public EntityStatusMessage(int id, int type, String name, EntityStatus status, Map<String, Object> details) {
this.id = id;
this.type = type;
this.name = name;
this.status = status;
this.details = details;
this(Arrays.asList(new EntityStatusData(id, type, name, status, details)));
}
}

View file

@ -0,0 +1,50 @@
package brainwine.gameserver.server.models;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.item.Layer;
@JsonFormat(shape = Shape.ARRAY)
public class BlockChangeData {
private final int x;
private final int y;
private final Layer layer;
private final int entityId = 0;
private final Item item;
private final int mod;
public BlockChangeData(int x, int y, Layer layer, Item item, int mod) {
this.x = x;
this.y = y;
this.layer = layer;
this.item = item;
this.mod = mod;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Layer getLayer() {
return layer;
}
public int getEntityId() {
return entityId;
}
public Item getItem() {
return item;
}
public int getMod() {
return mod;
}
}

View file

@ -0,0 +1,75 @@
package brainwine.gameserver.server.models;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import brainwine.gameserver.entity.Entity;
import brainwine.gameserver.entity.FacingDirection;
@JsonFormat(shape = Shape.ARRAY)
public class EntityPositionData {
private final int id;
private final int x;
private final int y;
private final int velocityX;
private final int velocityY;
private final FacingDirection direction;
private final int targetX;
private final int targetY;
private final int animation;
public EntityPositionData(Entity entity) {
this(entity.getId(), entity.getX(), entity.getY(), entity.getVelocityX(), entity.getVelocityY(), entity.getDirection(),
entity.getTargetX(), entity.getTargetY(), entity.getAnimation());
}
public EntityPositionData(int id, float x, float y, float velocityX, float velocityY, FacingDirection direction,
int targetX, int targetY, int animation) {
this.id = id;
this.x = (int)(x * Entity.POSITION_MODIFIER);
this.y = (int)(y * Entity.POSITION_MODIFIER);
this.velocityX = (int)(velocityX * Entity.VELOCITY_MODIFIER);
this.velocityY = (int)(velocityY * Entity.VELOCITY_MODIFIER);
this.direction = direction;
this.targetX = targetX * Entity.VELOCITY_MODIFIER;
this.targetY = targetY * Entity.VELOCITY_MODIFIER;
this.animation = animation;
}
public int getId() {
return id;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getVelocityX() {
return velocityX;
}
public int getVelocityY() {
return velocityY;
}
public FacingDirection getDirection() {
return direction;
}
public int getTargetX() {
return targetX;
}
public int getTargetY() {
return targetY;
}
public int getAnimation() {
return animation;
}
}

View file

@ -0,0 +1,59 @@
package brainwine.gameserver.server.models;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import brainwine.gameserver.entity.Entity;
import brainwine.gameserver.entity.EntityStatus;
@JsonFormat(shape = Shape.ARRAY)
public class EntityStatusData {
private final int id;
private final int type;
private final String name;
private final EntityStatus status;
private final Map<String, Object> details;
public EntityStatusData(Entity entity, EntityStatus status) {
this(entity.getId(), entity.getType(), entity.getName(), status, entity.getStatusConfig());
}
public EntityStatusData(int id, int type, String name, EntityStatus status, Map<String, Object> details) {
this.id = id;
this.type = type;
this.name = name;
this.status = status;
this.details = details;
}
public static EntityStatusData entering(Entity entity) {
return new EntityStatusData(entity, EntityStatus.ENTERING);
}
public static EntityStatusData exiting(Entity entity) {
return new EntityStatusData(entity, EntityStatus.EXITING);
}
public int getId() {
return id;
}
public int getType() {
return type;
}
public String getName() {
return name;
}
public EntityStatus getStatus() {
return status;
}
public Map<String, Object> getDetails() {
return details;
}
}

View file

@ -1,11 +1,14 @@
package brainwine.gameserver.server.requests;
import java.util.ArrayList;
import java.util.List;
import brainwine.gameserver.annotations.RequestInfo;
import brainwine.gameserver.entity.Entity;
import brainwine.gameserver.entity.EntityStatus;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.server.PlayerRequest;
import brainwine.gameserver.server.messages.EntityStatusMessage;
import brainwine.gameserver.server.models.EntityStatusData;
@RequestInfo(id = 51)
public class EntitiesRequest extends PlayerRequest {
@ -15,11 +18,19 @@ public class EntitiesRequest extends PlayerRequest {
public void process(Player player) {
int count = Math.min(entityIds.length, 10);
for(int i = 0; i < count; i++) {
Entity entity = player.getZone().getEntity(entityIds[i]);
if(count > 0) {
List<EntityStatusData> statuses = new ArrayList<>();
if(entity != null && player.isTrackingEntity(entity)) {
player.sendMessage(new EntityStatusMessage(entity, EntityStatus.ENTERING));
for(int i = 0; i < count; i++) {
Entity entity = player.getZone().getEntity(entityIds[i]);
if(entity != null && player.isTrackingEntity(entity)) {
statuses.add(EntityStatusData.entering(entity));
}
}
if(!statuses.isEmpty()) {
player.sendMessage(new EntityStatusMessage(statuses));
}
}
}

View file

@ -4,6 +4,7 @@ import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@ -39,7 +40,7 @@ public class MetaBlock {
}
public MetaBlock(int x, int y, Item item) {
this(x, y, item, null);
this(x, y, item, new HashMap<>());
}
public MetaBlock(int x, int y, Item item, Map<String, Object> metadata) {
@ -86,6 +87,18 @@ public class MetaBlock {
this.metadata = metadata;
}
@JsonIgnore
public Map<String, Object> getClientMetadata() {
Map<String, Object> clientMetadata = new HashMap<>(metadata); // Shallow copy
clientMetadata.put("i", item.getId());
if(hasOwner()) {
clientMetadata.put("p", owner);
}
return clientMetadata;
}
public Map<String, Object> getMetadata() {
return metadata;
}

View file

@ -44,6 +44,7 @@ import brainwine.gameserver.server.messages.ConfigurationMessage;
import brainwine.gameserver.server.messages.LightMessage;
import brainwine.gameserver.server.messages.ZoneExploredMessage;
import brainwine.gameserver.server.messages.ZoneStatusMessage;
import brainwine.gameserver.server.models.BlockChangeData;
import brainwine.gameserver.util.MapHelper;
import brainwine.gameserver.util.MathUtils;
import brainwine.gameserver.util.Vector2i;
@ -74,6 +75,7 @@ public class Zone {
private final WeatherManager weatherManager = new WeatherManager();
private final EntityManager entityManager = new EntityManager(this);
private final Queue<DugBlock> digQueue = new ArrayDeque<>();
private final List<BlockChangeData> blockChanges = new ArrayList<>();
private final Set<Integer> pendingSunlight = new HashSet<>();
private final Map<String, Integer> dungeons = new HashMap<>();
private final Map<Integer, MetaBlock> metaBlocks = new HashMap<>();
@ -163,6 +165,21 @@ public class Zone {
}
}
}
// Send block changes to players who they are relevant to
if(!blockChanges.isEmpty()) {
for(Player player : getPlayers()) {
List<BlockChangeData> blockChangesNearPlayer = blockChanges.stream()
.filter(blockChange -> player.isChunkActive(blockChange.getX(), blockChange.getY()))
.collect(Collectors.toList());
if(!blockChangesNearPlayer.isEmpty()) {
player.sendMessage(new BlockChangeMessage(blockChangesNearPlayer));
}
}
blockChanges.clear();
}
}
/**
@ -681,7 +698,12 @@ public class Zone {
Chunk chunk = getChunk(x, y);
chunk.getBlock(x, y).updateLayer(layer, item, mod);
chunk.setModified(true);
sendMessageToChunk(new BlockChangeMessage(x, y, layer, item, mod), chunk);
// Queue block update if there are players in this zone.
// TODO maybe check if the block update was in an active chunk, too?
if(!getPlayers().isEmpty()) {
blockChanges.add(new BlockChangeData(x, y, layer, item, mod));
}
if(layer == Layer.FRONT) {
if(item.isWhole()) {
@ -743,34 +765,31 @@ public class Zone {
MetaType meta = item.getMeta();
int index = getBlockIndex(x, y);
Map<String, Object> metadata = data == null ? new HashMap<>() : MapHelper.copy(data);
Map<String, Object> toSend = MapHelper.copy(metadata);
toSend.put("i", item.getId());
if(owner != null) {
toSend.put("p", owner.getDocumentId());
}
MetaBlock metaBlock = null;
if(item.hasMeta()) {
MetaBlock metaBlock = new MetaBlock(x, y, item, owner, metadata);
metaBlock = new MetaBlock(x, y, item, owner, data);
metaBlocks.put(index, metaBlock);
indexMetaBlock(index, metaBlock);
} else if(metaBlocks.containsKey(index)) {
meta = metaBlocks.remove(index).getItem().getMeta();
toSend.clear();
unindexMetaBlock(index);
}
switch(meta) {
case LOCAL:
sendMessageToChunk(new BlockMetaMessage(x, y, toSend), getChunk(x, y));
break;
case GLOBAL:
sendMessage(new BlockMetaMessage(x, y, Collections.emptyMap()));
sendMessage(new BlockMetaMessage(x, y, toSend));
break;
default:
break;
case LOCAL:
sendMessageToChunk(metaBlock == null ? new BlockMetaMessage(x, y)
: new BlockMetaMessage(metaBlock), getChunk(x, y));
break;
case GLOBAL:
sendMessage(new BlockMetaMessage(x, y)); // Send empty one first or it won't work for some reason
if(metaBlock != null) {
sendMessage(new BlockMetaMessage(metaBlock));
}
break;
default:
break;
}
}