mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 00:38:21 +00:00
Fix issue #64.
This commit is contained in:
@@ -76,16 +76,16 @@ public final class Server {
|
||||
*/
|
||||
private final ServerBootstrap jaggrabBootstrap = new ServerBootstrap();
|
||||
|
||||
/**
|
||||
* The {@link ServerBootstrap} for the service listener.
|
||||
*/
|
||||
private final ServerBootstrap serviceBootstrap = new ServerBootstrap();
|
||||
|
||||
/**
|
||||
* The event loop group.
|
||||
*/
|
||||
private final EventLoopGroup loopGroup = new NioEventLoopGroup();
|
||||
|
||||
/**
|
||||
* The {@link ServerBootstrap} for the service listener.
|
||||
*/
|
||||
private final ServerBootstrap serviceBootstrap = new ServerBootstrap();
|
||||
|
||||
/**
|
||||
* Creates the Apollo server.
|
||||
*/
|
||||
@@ -99,18 +99,17 @@ public final class Server {
|
||||
* @param service The service address to bind to.
|
||||
* @param http The HTTP address to bind to.
|
||||
* @param jaggrab The JAGGRAB address to bind to.
|
||||
* @throws BindException If the ServerBootstrap fails to bind to the SocketAddress for any
|
||||
* reason.
|
||||
* @throws BindException If the ServerBootstrap fails to bind to the SocketAddress.
|
||||
*/
|
||||
public void bind(SocketAddress service, SocketAddress http, SocketAddress jaggrab) throws BindException {
|
||||
logger.fine("Binding service listener to address: " + service + "...");
|
||||
bind(serviceBootstrap, service);
|
||||
|
||||
try {
|
||||
logger.fine("Binding HTTP listener to address: " + http + "...");
|
||||
bind(httpBootstrap, http);
|
||||
logger.fine("Binding HTTP listener to address: " + http + "...");
|
||||
bind(httpBootstrap, http);
|
||||
} catch (Exception cause) {
|
||||
logger.warning("Unable to bind to HTTP, JAGGRAB will be used as a fallback however this is not recommended.");
|
||||
logger.warning("Unable to bind to HTTP, JAGGRAB will be used as a fallback however this is not recommended.");
|
||||
}
|
||||
|
||||
logger.fine("Binding JAGGRAB listener to address: " + jaggrab + "...");
|
||||
@@ -119,22 +118,6 @@ public final class Server {
|
||||
logger.info("Ready for connections.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to bind the specified ServerBootstrap to the specified SocketAddress.
|
||||
*
|
||||
* @param bootstrap The ServerBootstrap.
|
||||
* @param address The SocketAddress.
|
||||
* @throws BindException If the ServerBootstrap fails to bind to the SocketAddress for any
|
||||
* reason.
|
||||
*/
|
||||
private void bind(ServerBootstrap bootstrap, SocketAddress address) throws BindException {
|
||||
try {
|
||||
bootstrap.bind(address).sync();
|
||||
} catch (Exception cause) {
|
||||
throw new BindException("Failed to bind to: " + address);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the server.
|
||||
*
|
||||
@@ -176,4 +159,19 @@ public final class Server {
|
||||
world.init(version, fs, manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to bind the specified ServerBootstrap to the specified SocketAddress.
|
||||
*
|
||||
* @param bootstrap The ServerBootstrap.
|
||||
* @param address The SocketAddress.
|
||||
* @throws BindException If the ServerBootstrap fails to bind to the SocketAddress.
|
||||
*/
|
||||
private void bind(ServerBootstrap bootstrap, SocketAddress address) throws BindException {
|
||||
try {
|
||||
bootstrap.bind(address).sync();
|
||||
} catch (Exception cause) {
|
||||
throw new BindException("Failed to bind to: " + address);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.apollo.game.message.impl;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
import org.apollo.game.model.area.RegionCoordinates;
|
||||
@@ -18,24 +19,25 @@ public final class GroupedRegionUpdateMessage extends Message {
|
||||
*/
|
||||
private final Position lastKnownRegion;
|
||||
|
||||
/**
|
||||
* The Set of RegionUpdateMessages to be sent.
|
||||
*/
|
||||
private final Set<RegionUpdateMessage> messages;
|
||||
|
||||
/**
|
||||
* The Position of the Region being updated.
|
||||
*/
|
||||
private final Position region;
|
||||
|
||||
/**
|
||||
* The List of RegionUpdateMessages to be sent.
|
||||
*/
|
||||
private final List<RegionUpdateMessage> messages;
|
||||
|
||||
/**
|
||||
* Creates the GroupedRegionUpdateMessage.
|
||||
*
|
||||
* @param lastKnownRegion The last known region {@link Position} of the Player.
|
||||
* @param coordinates The {@link RegionCoordinates} of the Region being updated.
|
||||
* @param messages The {@link List} of {@link RegionUpdateMessage}s.
|
||||
* @param messages The {@link Set} of {@link RegionUpdateMessage}s.
|
||||
*/
|
||||
public GroupedRegionUpdateMessage(Position lastKnownRegion, RegionCoordinates coordinates, List<RegionUpdateMessage> messages) {
|
||||
public GroupedRegionUpdateMessage(Position lastKnownRegion, RegionCoordinates coordinates,
|
||||
Set<RegionUpdateMessage> messages) {
|
||||
this.lastKnownRegion = lastKnownRegion;
|
||||
region = new Position(coordinates.getAbsoluteX(), coordinates.getAbsoluteY());
|
||||
this.messages = messages;
|
||||
@@ -51,11 +53,11 @@ public final class GroupedRegionUpdateMessage extends Message {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link List} of {@link RegionUpdateMessage}s.
|
||||
* Gets the {@link Set} of {@link RegionUpdateMessage}s.
|
||||
*
|
||||
* @return The Collection.
|
||||
* @return The Set.
|
||||
*/
|
||||
public List<RegionUpdateMessage> getMessages() {
|
||||
public Set<RegionUpdateMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.apollo.game.model.area;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -21,7 +20,6 @@ import org.apollo.game.model.entity.EntityType;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
@@ -79,14 +77,17 @@ public final class Region {
|
||||
private final CollisionMatrix[] matrices = CollisionMatrix.createMatrices(Position.HEIGHT_LEVELS, SIZE, SIZE);
|
||||
|
||||
/**
|
||||
* The Set containing RegionUpdateMessages which can be sent to add every non-Mob Entity in this Region.
|
||||
* The List of Sets containing RegionUpdateMessages that specifically remove StaticGameObjects. The
|
||||
* List is ordered based on the height level the RegionUpdateMessages concern.
|
||||
*/
|
||||
private final List<Map<Entity, RegionUpdateMessage>> snapshots = new ArrayList<>(Position.HEIGHT_LEVELS);
|
||||
private final List<Set<RegionUpdateMessage>> removedObjects = new ArrayList<>(Position.HEIGHT_LEVELS);
|
||||
|
||||
/**
|
||||
* The Set containing UpdateOperations.
|
||||
* The List of Sets containing RegionUpdateMessages. The List is ordered based on the height level the
|
||||
* RegionUpdateMessages concern. This only contains the updates to this Region that have occurred in the last
|
||||
* pulse.
|
||||
*/
|
||||
private final List<List<RegionUpdateMessage>> updates = new ArrayList<>(Position.HEIGHT_LEVELS);
|
||||
private final List<Set<RegionUpdateMessage>> updates = new ArrayList<>(Position.HEIGHT_LEVELS);
|
||||
|
||||
/**
|
||||
* Creates a new Region.
|
||||
@@ -108,8 +109,8 @@ public final class Region {
|
||||
listeners.add(new UpdateRegionListener());
|
||||
|
||||
for (int height = 0; height < Position.HEIGHT_LEVELS; height++) {
|
||||
snapshots.add(new HashMap<>());
|
||||
updates.add(new ArrayList<>(DEFAULT_LIST_SIZE));
|
||||
removedObjects.add(new HashSet<>());
|
||||
updates.add(new HashSet<>(DEFAULT_LIST_SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +155,6 @@ public final class Region {
|
||||
* @param entity The Entity.
|
||||
* @return {@code true} if this Region contains the Entity, otherwise {@code false}.
|
||||
*/
|
||||
|
||||
public boolean contains(Entity entity) {
|
||||
Position position = entity.getPosition();
|
||||
Set<Entity> local = entities.get(position);
|
||||
@@ -162,6 +162,22 @@ public final class Region {
|
||||
return local != null && local.contains(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the contents of this Region into a {@link Set} of {@link RegionUpdateMessage}s, to be sent to a client.
|
||||
*
|
||||
* @return The Set of RegionUpdateMessages.
|
||||
*/
|
||||
public Set<RegionUpdateMessage> encode(int height) {
|
||||
Set<RegionUpdateMessage> additions = entities.values().stream()
|
||||
.flatMap(Set::stream).filter(entity -> entity instanceof GroupableEntity)
|
||||
.map(entity -> ((GroupableEntity) entity).toUpdateOperation(this, EntityUpdateType.ADD).toMessage())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
ImmutableSet.Builder<RegionUpdateMessage> builder = ImmutableSet.builder();
|
||||
builder.addAll(additions).addAll(updates.get(height)).addAll(removedObjects.get(height));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this Region's {@link RegionCoordinates}.
|
||||
*
|
||||
@@ -218,31 +234,14 @@ public final class Region {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link Set} containing {@link RegionUpdateMessage}s that add every {@link Entity} in this Region.
|
||||
* Gets the {@link Set} of {@link RegionUpdateMessage}s that have occurred in the last pulse. This method can
|
||||
* only be called <strong>once</strong> per pulse.
|
||||
*
|
||||
* @param height The height level to get the Set of RegionUpdateMessages for.
|
||||
* @param height The height level to get the RegionUpdateMessages for.
|
||||
* @return The Set of RegionUpdateMessages.
|
||||
*/
|
||||
public List<RegionUpdateMessage> getSnapshot(int height) {
|
||||
List<RegionUpdateMessage> copy = new ArrayList<>(snapshots.get(height).values());
|
||||
Collections.sort(copy);
|
||||
return ImmutableList.copyOf(copy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the updates that have occurred in the last tick in this Region, as a {@link Set} of
|
||||
* {@link RegionUpdateMessage}s.
|
||||
*
|
||||
* @param height The height level to get the Set of RegionUpdateMessages for.
|
||||
* @return The Set of RegionUpdateMessages.
|
||||
*/
|
||||
public List<RegionUpdateMessage> getUpdates(int height) {
|
||||
List<RegionUpdateMessage> original = this.updates.get(height);
|
||||
List<RegionUpdateMessage> updates = new ArrayList<>(original);
|
||||
original.clear();
|
||||
|
||||
Collections.sort(updates);
|
||||
return ImmutableList.copyOf(updates);
|
||||
public Set<RegionUpdateMessage> getUpdates(int height) {
|
||||
return ImmutableSet.copyOf(updates.get(height));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,15 +260,14 @@ public final class Region {
|
||||
* @param entity The Entity.
|
||||
* @throws IllegalArgumentException If the Entity does not belong in this Region, or if it was never added.
|
||||
*/
|
||||
public void removeEntity(Entity entity) {
|
||||
public void removeEntity(Entity entity) { // TODO entity update stuff
|
||||
Position position = entity.getPosition();
|
||||
checkPosition(position);
|
||||
|
||||
Set<Entity> local = entities.get(position);
|
||||
|
||||
if (local == null || !local.remove(entity)) {
|
||||
throw new IllegalArgumentException("Entity (" + entity + ") belongs in this Region (" + this
|
||||
+ ") but does not exist.");
|
||||
throw new IllegalArgumentException("Entity (" + entity + ") belongs in (" + this + ") but does not exist.");
|
||||
}
|
||||
|
||||
notifyListeners(entity, EntityUpdateType.REMOVE);
|
||||
@@ -315,16 +313,19 @@ public final class Region {
|
||||
* @throws UnsupportedOperationException If the specified Entity cannot be operated on in this manner.
|
||||
*/
|
||||
private <T extends Entity & GroupableEntity> void record(T entity, EntityUpdateType type) {
|
||||
RegionUpdateMessage message = entity.toUpdateOperation(this, type).toMessage();
|
||||
UpdateOperation<?> operation = entity.toUpdateOperation(this, type);
|
||||
RegionUpdateMessage message = operation.toMessage(), inverse = operation.inverse();
|
||||
|
||||
int height = entity.getPosition().getHeight();
|
||||
Set<RegionUpdateMessage> updates = this.updates.get(height);
|
||||
|
||||
Map<Entity, RegionUpdateMessage> snapshot = snapshots.get(height);
|
||||
updates.get(height).add(message);
|
||||
|
||||
snapshot.remove(entity);
|
||||
if ((entity.getEntityType() == EntityType.STATIC_OBJECT && type == EntityUpdateType.REMOVE)
|
||||
|| (entity.getEntityType() != EntityType.STATIC_OBJECT && type == EntityUpdateType.ADD)) {
|
||||
snapshot.put(entity, message);
|
||||
if (entity.getEntityType() == EntityType.STATIC_OBJECT && type == EntityUpdateType.REMOVE) {
|
||||
removedObjects.get(height).add(message);
|
||||
updates.add(message);
|
||||
updates.remove(inverse);
|
||||
} else {
|
||||
updates.add(message);
|
||||
updates.remove(inverse);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ import com.google.common.base.Preconditions;
|
||||
* An type that is contained in the snapshot of a {@link Region}, which consists of an {@link Entity} being added,
|
||||
* removed, or moved.
|
||||
*
|
||||
* @author Major
|
||||
* @param <E> The type of {@link Entity} in this type.
|
||||
* @author Major
|
||||
*/
|
||||
public abstract class UpdateOperation<E extends Entity> {
|
||||
|
||||
@@ -47,6 +47,24 @@ public abstract class UpdateOperation<E extends Entity> {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link RegionUpdateMessage} that would counteract the effect of this UpdateOperation.
|
||||
*
|
||||
* @return The RegionUpdateMessage.
|
||||
*/
|
||||
public final RegionUpdateMessage inverse() {
|
||||
int offset = getPositionOffset(entity.getPosition());
|
||||
|
||||
switch (type) {
|
||||
case ADD:
|
||||
return remove(offset);
|
||||
case REMOVE:
|
||||
return add(offset);
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported EntityUpdateType " + type + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this UpdateOperation as a {@link Message}.
|
||||
*
|
||||
|
||||
@@ -26,8 +26,7 @@ public final class GroupedRegionUpdateMessageEncoder extends MessageEncoder<Grou
|
||||
/**
|
||||
* Creates the GroupedRegionUpdateMessageEncoder.
|
||||
*
|
||||
* @param release The {@link Release} containing the {@link MessageEncoder}s for the {@link RegionUpdateMessage}
|
||||
* s.
|
||||
* @param release The {@link Release} containing the {@link MessageEncoder}s for the {@link RegionUpdateMessage}s.
|
||||
*/
|
||||
public GroupedRegionUpdateMessageEncoder(Release release) {
|
||||
this.release = release;
|
||||
|
||||
@@ -81,10 +81,8 @@ public final class GameService extends Service {
|
||||
*
|
||||
* @param player The player.
|
||||
*/
|
||||
public void finalizePlayerUnregistration(Player player) {
|
||||
synchronized (this) {
|
||||
world.unregister(player);
|
||||
}
|
||||
public synchronized void finalizePlayerUnregistration(Player player) {
|
||||
world.unregister(player);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -73,7 +73,7 @@ public final class LoginService extends Service {
|
||||
public void submitLoadRequest(LoginSession session, LoginRequest request) throws IOException {
|
||||
int response = LoginConstants.STATUS_OK;
|
||||
|
||||
if (requiresUpdate(session, request)) {
|
||||
if (requiresUpdate(request)) {
|
||||
response = LoginConstants.STATUS_GAME_UPDATED;
|
||||
}
|
||||
|
||||
@@ -125,12 +125,11 @@ public final class LoginService extends Service {
|
||||
/**
|
||||
* Checks if an update is required whenever a {@link Player} submits a login request.
|
||||
*
|
||||
* @param session The login session.
|
||||
* @param request The login request.
|
||||
* @return {@code true} if an update is required, otherwise return {@code false}.
|
||||
* @throws IOException If some I/O exception occurs.
|
||||
*/
|
||||
private boolean requiresUpdate(LoginSession session, LoginRequest request) throws IOException {
|
||||
private boolean requiresUpdate(LoginRequest request) throws IOException {
|
||||
Release release = context.getRelease();
|
||||
if (release.getReleaseNumber() != request.getReleaseNumber()) {
|
||||
return true;
|
||||
@@ -139,11 +138,7 @@ public final class LoginService extends Service {
|
||||
int[] clientCrcs = request.getArchiveCrcs();
|
||||
int[] serverCrcs = context.getFileSystem().getCrcs();
|
||||
|
||||
if (Arrays.equals(clientCrcs, serverCrcs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return !Arrays.equals(clientCrcs, serverCrcs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +1,5 @@
|
||||
package org.apollo.game.sync;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Phaser;
|
||||
|
||||
import org.apollo.game.message.impl.RegionUpdateMessage;
|
||||
import org.apollo.game.model.area.RegionCoordinates;
|
||||
import org.apollo.game.model.entity.MobRepository;
|
||||
@@ -23,11 +16,19 @@ import org.apollo.game.sync.task.PrePlayerSynchronizationTask;
|
||||
import org.apollo.game.sync.task.SynchronizationTask;
|
||||
import org.apollo.util.ThreadUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Phaser;
|
||||
|
||||
/**
|
||||
* An implementation of {@link ClientSynchronizer} which runs in a thread pool. A {@link Phaser} is used to ensure that
|
||||
* the synchronization is complete, allowing control to return to the {@link GameService} that started the
|
||||
* synchronization. This class will scale well with machines that have multiple cores/processors. The
|
||||
* {@link SequentialClientSynchronizer} will work better on machines with a single core/processor, however, both classes
|
||||
* {@link SequentialClientSynchronizer} will work better on machines with a single core/processor, however, both
|
||||
* classes
|
||||
* will work.
|
||||
*
|
||||
* @author Graham
|
||||
@@ -58,12 +59,12 @@ public final class ParallelClientSynchronizer extends ClientSynchronizer {
|
||||
int playerCount = players.size();
|
||||
int npcCount = npcs.size();
|
||||
|
||||
Map<RegionCoordinates, List<RegionUpdateMessage>> updates = new HashMap<>();
|
||||
Map<RegionCoordinates, List<RegionUpdateMessage>> snapshots = new HashMap<>();
|
||||
Map<RegionCoordinates, Set<RegionUpdateMessage>> encodes = new ConcurrentHashMap<>();
|
||||
Map<RegionCoordinates, Set<RegionUpdateMessage>> updates = new ConcurrentHashMap<>();
|
||||
|
||||
phaser.bulkRegister(playerCount);
|
||||
for (Player player : players) {
|
||||
SynchronizationTask task = new PrePlayerSynchronizationTask(player, updates, snapshots);
|
||||
SynchronizationTask task = new PrePlayerSynchronizationTask(player, encodes, updates);
|
||||
executor.submit(new PhasedSynchronizationTask(phaser, task));
|
||||
}
|
||||
phaser.arriveAndAwaitAdvance();
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.apollo.game.sync;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apollo.game.message.impl.RegionUpdateMessage;
|
||||
import org.apollo.game.model.area.RegionCoordinates;
|
||||
@@ -31,11 +32,10 @@ public final class SequentialClientSynchronizer extends ClientSynchronizer {
|
||||
|
||||
@Override
|
||||
public void synchronize(MobRepository<Player> players, MobRepository<Npc> npcs) {
|
||||
Map<RegionCoordinates, List<RegionUpdateMessage>> updates = new HashMap<>();
|
||||
Map<RegionCoordinates, List<RegionUpdateMessage>> snapshots = new HashMap<>();
|
||||
Map<RegionCoordinates, Set<RegionUpdateMessage>> encodes = new HashMap<>(), updates = new HashMap<>();
|
||||
|
||||
for (Player player : players) {
|
||||
SynchronizationTask task = new PrePlayerSynchronizationTask(player, updates, snapshots);
|
||||
SynchronizationTask task = new PrePlayerSynchronizationTask(player, encodes, updates);
|
||||
task.run();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package org.apollo.game.sync.task;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apollo.game.message.impl.ClearRegionMessage;
|
||||
@@ -17,8 +14,6 @@ import org.apollo.game.model.area.RegionCoordinates;
|
||||
import org.apollo.game.model.area.RegionRepository;
|
||||
import org.apollo.game.model.entity.Player;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* A {@link SynchronizationTask} which does pre-synchronization work for the specified {@link Player}.
|
||||
*
|
||||
@@ -28,62 +23,44 @@ import com.google.common.collect.ImmutableSet;
|
||||
public final class PrePlayerSynchronizationTask extends SynchronizationTask {
|
||||
|
||||
/**
|
||||
* The update mode used when sending a {@link GroupedRegionUpdateMessage}.
|
||||
* The radius of viewable regions.
|
||||
*/
|
||||
private enum RegionUpdateMode {
|
||||
|
||||
/**
|
||||
* The difference update mode, which only sends updates for changes that have occurred in the last pulse.
|
||||
*/
|
||||
DIFFERENCE,
|
||||
|
||||
/**
|
||||
* The full update mode, which sends everything in the Region.
|
||||
*/
|
||||
FULL;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of Regions that are in view of a player, in one direction.
|
||||
*/
|
||||
private static final int REGION_COUNT = (int) Math.ceil(Position.MAX_DISTANCE / Region.SIZE);
|
||||
private static final int VIEWABLE_REGION_RADIUS = 3;
|
||||
|
||||
/**
|
||||
* The width of the viewport of every Player, in tiles.
|
||||
*/
|
||||
private static final int VIEWPORT_WIDTH = Region.SIZE * 13;
|
||||
|
||||
/**
|
||||
* The Map of RegionCoordinates to Sets of RegionUpdateMessages, which contain all of the Entity information for a
|
||||
* Region.
|
||||
*/
|
||||
private final Map<RegionCoordinates, Set<RegionUpdateMessage>> encodes;
|
||||
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
private final Player player;
|
||||
|
||||
/**
|
||||
* The Map of RegionCoordinates to Sets of RegionUpdateMessages, which contain all of the Entity information for a
|
||||
* Region.
|
||||
*/
|
||||
private final Map<RegionCoordinates, List<RegionUpdateMessage>> snapshots;
|
||||
|
||||
/**
|
||||
* The Map of RegionCoordinates to Sets of RegionUpdateMessages, which contain the updates for a Region a Player
|
||||
* can
|
||||
* already view.
|
||||
* can already view.
|
||||
*/
|
||||
private final Map<RegionCoordinates, List<RegionUpdateMessage>> updates;
|
||||
private final Map<RegionCoordinates, Set<RegionUpdateMessage>> updates;
|
||||
|
||||
/**
|
||||
* Creates the {@link PrePlayerSynchronizationTask} for the specified {@link Player}.
|
||||
*
|
||||
* @param player The Player.
|
||||
* @param encodes The Map containing Region encodes.
|
||||
* @param updates The {@link Map} containing {@link Region} updates.
|
||||
* @param snapshots The Map containing Region snapshots.
|
||||
*/
|
||||
public PrePlayerSynchronizationTask(Player player, Map<RegionCoordinates, List<RegionUpdateMessage>> updates,
|
||||
Map<RegionCoordinates, List<RegionUpdateMessage>> snapshots) {
|
||||
public PrePlayerSynchronizationTask(Player player, Map<RegionCoordinates, Set<RegionUpdateMessage>> encodes,
|
||||
Map<RegionCoordinates, Set<RegionUpdateMessage>> updates) {
|
||||
this.player = player;
|
||||
this.updates = updates;
|
||||
this.snapshots = snapshots;
|
||||
this.encodes = encodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,71 +68,49 @@ public final class PrePlayerSynchronizationTask extends SynchronizationTask {
|
||||
Position old = player.getPosition();
|
||||
player.getWalkingQueue().pulse();
|
||||
|
||||
RegionUpdateMode mode = RegionUpdateMode.DIFFERENCE;
|
||||
|
||||
if (player.isTeleporting()) {
|
||||
player.resetViewingDistance();
|
||||
mode = RegionUpdateMode.FULL;
|
||||
}
|
||||
|
||||
boolean hasKnownRegion = player.hasLastKnownRegion();
|
||||
if (!hasKnownRegion) {
|
||||
mode = RegionUpdateMode.FULL;
|
||||
}
|
||||
|
||||
if (!hasKnownRegion || isRegionUpdateRequired()) {
|
||||
Position position = player.getPosition();
|
||||
if (!player.hasLastKnownRegion() || isRegionUpdateRequired()) {
|
||||
player.setRegionChanged(true);
|
||||
|
||||
Position position = player.getPosition();
|
||||
player.setLastKnownRegion(position);
|
||||
player.send(new RegionChangeMessage(position));
|
||||
}
|
||||
|
||||
Set<RegionCoordinates> newRegions = getNewRegions(old, player.getPosition());
|
||||
sendRegionUpdates(mode, newRegions);
|
||||
Set<RegionCoordinates> oldViewable = getViewableRegions(old), newViewable = getViewableRegions(position);
|
||||
|
||||
Set<RegionCoordinates> differences = new HashSet<>(newViewable);
|
||||
differences.retainAll(oldViewable);
|
||||
|
||||
Set<RegionCoordinates> full = new HashSet<>(newViewable);
|
||||
full.removeAll(oldViewable);
|
||||
|
||||
sendUpdates(player.getLastKnownRegion(), differences, full);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Set} of {@link RegionCoordinates} of {@link Region}s that the {@link Player} in this task has
|
||||
* only just became able to view.
|
||||
* Gets the {@link Set} of {@link RegionCoordinates} of Regions that are viewable from the specified {@link
|
||||
* Position}.
|
||||
*
|
||||
* @param old The old {@link Position} of the Player.
|
||||
* @param next The new Position of the Player.
|
||||
* @return The Set of RegionCoordinates. Will not be {@code null}, but may be empty.
|
||||
* @param position The Position.
|
||||
* @return The Set of RegionCoordinates.
|
||||
*/
|
||||
private Set<RegionCoordinates> getNewRegions(Position old, Position next) {
|
||||
RegionCoordinates oldRegion = old.getRegionCoordinates();
|
||||
RegionCoordinates nextRegion = next.getRegionCoordinates();
|
||||
private Set<RegionCoordinates> getViewableRegions(Position position) { // TODO possibly more complicated than this
|
||||
RegionCoordinates local = position.getRegionCoordinates();
|
||||
int localX = local.getX(), localY = local.getY();
|
||||
int maxX = localX + VIEWABLE_REGION_RADIUS, maxY = localY + VIEWABLE_REGION_RADIUS;
|
||||
|
||||
if (oldRegion.equals(nextRegion)) {
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
|
||||
Set<RegionCoordinates> coordinates = new HashSet<>(9);
|
||||
int oldX = oldRegion.getX(), oldY = oldRegion.getY();
|
||||
|
||||
int dx = nextRegion.getX() - oldX;
|
||||
int dy = nextRegion.getY() - oldY;
|
||||
|
||||
if (dx != 0) {
|
||||
int x = oldX + dx;
|
||||
int maxY = oldY + REGION_COUNT;
|
||||
|
||||
for (int y = oldY - REGION_COUNT; y <= maxY; y++) {
|
||||
coordinates.add(new RegionCoordinates(x, y));
|
||||
Set<RegionCoordinates> viewable = new HashSet<>();
|
||||
for (int x = localX - VIEWABLE_REGION_RADIUS; x < maxX; x++) {
|
||||
for (int y = localY - VIEWABLE_REGION_RADIUS; y < maxY; y++) {
|
||||
viewable.add(new RegionCoordinates(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
if (dy != 0) {
|
||||
int y = oldY + dy;
|
||||
int maxX = oldX + REGION_COUNT;
|
||||
|
||||
for (int x = oldX - REGION_COUNT; x <= maxX; x++) {
|
||||
coordinates.add(new RegionCoordinates(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
return coordinates;
|
||||
return viewable;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,83 +130,30 @@ public final class PrePlayerSynchronizationTask extends SynchronizationTask {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link List} of {@link GroupedRegionUpdateMessage}s.
|
||||
* Sends the updates for a {@link Region}
|
||||
*
|
||||
* @param mode The {@link RegionUpdateMode} used when creating the Messages.
|
||||
* @param newRegions The {@link Set} of {@link RegionCoordinates} that should be sent as a full update.
|
||||
* @param position The {@link Position} of the last known region.
|
||||
* @param differences The {@link Set} of {@link RegionCoordinates} of Regions that changed.
|
||||
* @param full The {@link Set} of {@link RegionCoordinates} of Regions that require a full update.
|
||||
*/
|
||||
private void sendRegionUpdates(RegionUpdateMode mode, Set<RegionCoordinates> newRegions) {
|
||||
Position position = player.getPosition();
|
||||
RegionCoordinates base = position.getRegionCoordinates();
|
||||
int baseX = base.getX(), baseY = base.getY();
|
||||
|
||||
private void sendUpdates(Position position, Set<RegionCoordinates> differences, Set<RegionCoordinates> full) {
|
||||
RegionRepository repository = player.getWorld().getRegionRepository();
|
||||
List<GroupedRegionUpdateMessage> messages = new ArrayList<>();
|
||||
int height = position.getHeight();
|
||||
|
||||
for (int x = baseX - REGION_COUNT; x <= baseX + REGION_COUNT; x++) {
|
||||
for (int y = baseY - REGION_COUNT; y <= baseY + REGION_COUNT; y++) {
|
||||
RegionCoordinates coordinates = new RegionCoordinates(x, y);
|
||||
differences.stream().map(coordinates -> {
|
||||
Set<RegionUpdateMessage> messages = updates.computeIfAbsent(coordinates,
|
||||
coords -> repository.get(coords).getUpdates(height));
|
||||
|
||||
RegionUpdateMode local = mode;
|
||||
if (mode == RegionUpdateMode.DIFFERENCE && newRegions.contains(coordinates)) {
|
||||
local = RegionUpdateMode.FULL;
|
||||
return new GroupedRegionUpdateMessage(position, coordinates, messages);
|
||||
}).forEach(player::send);
|
||||
|
||||
player.send(new ClearRegionMessage(position, coordinates));
|
||||
}
|
||||
full.stream().map(coordinates -> {
|
||||
Set<RegionUpdateMessage> messages = encodes.computeIfAbsent(coordinates,
|
||||
coords -> repository.get(coords).encode(height));
|
||||
|
||||
toUpdateMessage(local, player.getLastKnownRegion(), coordinates, repository).ifPresent(messages::add);
|
||||
}
|
||||
}
|
||||
|
||||
messages.forEach(player::send);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link GroupedRegionUpdateMessage} using the specified {@link RegionUpdateMode}, returning
|
||||
* {@link Optional#empty()} if no update message is required.
|
||||
*
|
||||
* @param mode The RegionUpdateMode for the Message.
|
||||
* @param lastKnownRegion The last known region {@link Position} of the Player.
|
||||
* @param coordinates The {@link RegionCoordinates} of the {@link Region}.
|
||||
* @param repository The {@link RegionRepository} containing the Regions.
|
||||
* @return The Optional containing the GroupedRegionUpdateMessage.
|
||||
*/
|
||||
private Optional<GroupedRegionUpdateMessage> toUpdateMessage(RegionUpdateMode mode, Position lastKnownRegion,
|
||||
RegionCoordinates coordinates, RegionRepository repository) {
|
||||
List<RegionUpdateMessage> messages;
|
||||
|
||||
/*
|
||||
* Here we used Map#computeIfAbsent because the value may have been inserted into the Map by another thread
|
||||
* after our Map#get call. This is done in two separate parts (rather than acquiring the lock every time, and
|
||||
* just calling Map#computeIfAbsent immediately) for performance - once the List<RegionUpdateMessage> has been
|
||||
* placed into the Map once, it will never be changed, and is therefore a read-only operation after this. The
|
||||
* alternative, acquiring the lock for every access, would be very slow.
|
||||
*/
|
||||
|
||||
switch (mode) {
|
||||
case DIFFERENCE:
|
||||
messages = updates.get(coordinates);
|
||||
if (messages == null) {
|
||||
synchronized (updates) {
|
||||
messages = updates.computeIfAbsent(coordinates, coords -> repository.get(coords).getUpdates(lastKnownRegion.getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case FULL:
|
||||
messages = snapshots.get(coordinates);
|
||||
if (messages == null) {
|
||||
synchronized (snapshots) {
|
||||
messages = snapshots.computeIfAbsent(coordinates, coords -> repository.get(coords).getSnapshot(lastKnownRegion.getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognised RegionUpdateMode " + mode + ".");
|
||||
}
|
||||
|
||||
return messages.isEmpty() ? Optional.empty() : Optional.of(new GroupedRegionUpdateMessage(lastKnownRegion, coordinates, messages));
|
||||
player.send(new ClearRegionMessage(position, coordinates));
|
||||
return new GroupedRegionUpdateMessage(position, coordinates, messages);
|
||||
}).forEach(player::send);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user