diff --git a/game/src/main/org/apollo/game/model/World.java b/game/src/main/org/apollo/game/model/World.java index 57ae4706..1a45019f 100644 --- a/game/src/main/org/apollo/game/model/World.java +++ b/game/src/main/org/apollo/game/model/World.java @@ -298,24 +298,14 @@ public final class World { * Registers the specified player. * * @param player The player. - * @return A {@link RegistrationStatus}. */ - public RegistrationStatus register(Player player) { + public void register(Player player) { String username = player.getUsername(); - if (isPlayerOnline(username)) { - return RegistrationStatus.ALREADY_ONLINE; - } - boolean success = playerRepository.add(player); - if (success) { - players.put(NameUtil.encodeBase37(username), player); + playerRepository.add(player); + players.put(NameUtil.encodeBase37(username), player); - logger.info("Registered player: " + player + " [count=" + playerRepository.size() + "]"); - return RegistrationStatus.OK; - } - - logger.warning("Failed to register player: " + player + " [count=" + playerRepository.size() + "]"); - return RegistrationStatus.WORLD_FULL; + logger.info("Registered player: " + player + " [count=" + playerRepository.size() + "]"); } /** diff --git a/game/src/main/org/apollo/game/model/entity/Player.java b/game/src/main/org/apollo/game/model/entity/Player.java index 6c7b312a..63db72cf 100644 --- a/game/src/main/org/apollo/game/model/entity/Player.java +++ b/game/src/main/org/apollo/game/model/entity/Player.java @@ -19,6 +19,7 @@ import org.apollo.game.message.impl.UpdateRunEnergyMessage; import org.apollo.game.model.Appearance; import org.apollo.game.model.Position; import org.apollo.game.model.World; +import org.apollo.game.model.World.RegistrationStatus; import org.apollo.game.model.entity.attr.Attribute; import org.apollo.game.model.entity.attr.AttributeDefinition; import org.apollo.game.model.entity.attr.AttributeMap; @@ -602,6 +603,24 @@ public final class Player extends Mob { return withdrawingNotes; } + /** + * Determines the {@link RegistrationStatus} for this player. This method + * can remain lock-free since writes to the player {@link MobRepository} are + * only happening on the game thread. + * + * @return The status. + */ + public RegistrationStatus getRegistrationStatus() { + MobRepository repository = world.getPlayerRepository(); + + if (world.isPlayerOnline(getUsername())) { + return RegistrationStatus.ALREADY_ONLINE; + } else if (repository.capacity() == repository.size()) { + return RegistrationStatus.WORLD_FULL; + } + return RegistrationStatus.OK; + } + /** * Logs the player out, if possible. */ diff --git a/game/src/main/org/apollo/game/service/GameService.java b/game/src/main/org/apollo/game/service/GameService.java index 4c7922a7..db4fbd44 100644 --- a/game/src/main/org/apollo/game/service/GameService.java +++ b/game/src/main/org/apollo/game/service/GameService.java @@ -15,7 +15,6 @@ import org.apollo.game.GamePulseHandler; import org.apollo.game.io.MessageHandlerChainSetParser; import org.apollo.game.message.handler.MessageHandlerChainSet; import org.apollo.game.model.World; -import org.apollo.game.model.World.RegistrationStatus; import org.apollo.game.model.area.Region; import org.apollo.game.model.entity.MobRepository; import org.apollo.game.model.entity.Player; @@ -38,6 +37,11 @@ public final class GameService extends Service { * requests and slow everything down. */ private static final int UNREGISTERS_PER_CYCLE = 50; + + /** + * The number of times to register players per cycle. + */ + private static final int REGISTERS_PER_CYCLE = 25; /** * The World this Service is for. @@ -48,6 +52,11 @@ public final class GameService extends Service { * A queue of players to remove. */ private final Queue oldPlayers = new ConcurrentLinkedQueue<>(); + + /** + * A queue of players to add. + */ + private final Queue newPlayers = new ConcurrentLinkedQueue<>(); /** * The scheduled executor service. @@ -84,6 +93,21 @@ public final class GameService extends Service { public synchronized void finalizePlayerUnregistration(Player player) { world.unregister(player); } + + /** + * Finalizes the registration of a player. + * + * @param player The player. + */ + public void finalizePlayerRegistration(Player player) { + world.register(player); + Region region = world.getRegionRepository().fromPosition(player.getPosition()); + region.addEntity(player); + + if (player.getSession().isReconnecting()) { + player.sendInitialMessages(); + } + } /** * Gets the MessageHandlerChainSet @@ -97,7 +121,8 @@ public final class GameService extends Service { /** * Called every pulse. */ - public synchronized void pulse() { + public void pulse() { + finalizeRegistrations(); finalizeUnregistrations(); MobRepository players = world.getPlayerRepository(); @@ -112,25 +137,6 @@ public final class GameService extends Service { synchronizer.synchronize(players, world.getNpcRepository()); } - /** - * Registers a {@link Player} (may block!). - * - * @param player The Player. - * @param session The {@link GameSession} of the Player. - * @return A {@link RegistrationStatus}. - */ - public synchronized RegistrationStatus registerPlayer(Player player, GameSession session) { - RegistrationStatus status = world.register(player); - if (status == RegistrationStatus.OK) { - player.setSession(session); - - Region region = world.getRegionRepository().fromPosition(player.getPosition()); - region.addEntity(player); - } - - return status; - } - /** * Shuts down this game service. * @@ -155,6 +161,15 @@ public final class GameService extends Service { public void unregisterPlayer(Player player) { oldPlayers.add(player); } + + /** + * Registers a player. Returns immediately. The player is registered at the start of the next cycle. + * + * @param player The player. + */ + public void registerPlayer(Player player) { + newPlayers.add(player); + } /** * Finalizes the unregistration of Player's queued to be unregistered. @@ -171,6 +186,20 @@ public final class GameService extends Service { loginService.submitSaveRequest(player.getSession(), player); } } + + /** + * Finalizes the registration of Player's queued to be registered. + */ + private void finalizeRegistrations() { + for (int count = 0; count < REGISTERS_PER_CYCLE; count++) { + Player player = oldPlayers.poll(); + if (player == null) { + break; + } + + finalizePlayerRegistration(player); + } + } /** * Initializes the game service. diff --git a/game/src/main/org/apollo/game/session/GameSession.java b/game/src/main/org/apollo/game/session/GameSession.java index 52c84226..fe277e90 100644 --- a/game/src/main/org/apollo/game/session/GameSession.java +++ b/game/src/main/org/apollo/game/session/GameSession.java @@ -42,6 +42,11 @@ public final class GameSession extends Session { * The player. */ private final Player player; + + /** + * If the player was reconnecting. + */ + private final boolean reconnecting; /** * Creates a login session for the specified channel. @@ -49,11 +54,13 @@ public final class GameSession extends Session { * @param channel The channel. * @param context The server context. * @param player The player. + * @param reconnecting If the player was reconnecting. */ - public GameSession(Channel channel, ServerContext context, Player player) { + public GameSession(Channel channel, ServerContext context, Player player, boolean reconnecting) { super(channel); this.context = context; this.player = player; + this.reconnecting = reconnecting; } @Override @@ -111,4 +118,13 @@ public final class GameSession extends Session { } } + /** + * Determines if this player is reconnecting. + * + * @return {@code true} if reconnecting, {@code false} otherwise. + */ + public boolean isReconnecting() { + return reconnecting; + } + } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/session/LoginSession.java b/game/src/main/org/apollo/game/session/LoginSession.java index 5ff71ab9..e8b4bab3 100644 --- a/game/src/main/org/apollo/game/session/LoginSession.java +++ b/game/src/main/org/apollo/game/session/LoginSession.java @@ -80,14 +80,17 @@ public final class LoginSession extends Session { Player player = optional.get(); rights = player.getPrivilegeLevel().toInteger(); - GameSession session = new GameSession(channel, context, player); - RegistrationStatus registration = service.registerPlayer(player, session); + RegistrationStatus registration = player.getRegistrationStatus(); if (registration != RegistrationStatus.OK) { optional = Optional.empty(); rights = 0; status = registration == RegistrationStatus.ALREADY_ONLINE ? LoginConstants.STATUS_ACCOUNT_ONLINE : LoginConstants.STATUS_SERVER_FULL; + } else { + GameSession session = new GameSession(channel, context, player, request.isReconnecting()); + channel.attr(ApolloHandler.SESSION_KEY).set(session); + player.setSession(session); } } @@ -107,15 +110,10 @@ public final class LoginSession extends Session { channel.pipeline().remove("loginDecoder"); channel.pipeline().remove("loginEncoder"); - - channel.attr(ApolloHandler.SESSION_KEY).set(optional.get().getSession()); + service.registerPlayer(optional.get()); } else { future.addListener(ChannelFutureListener.CLOSE); } - - if (optional.isPresent() && !request.isReconnecting()) { - optional.get().sendInitialMessages(); - } } @Override @@ -124,5 +122,4 @@ public final class LoginSession extends Session { handleLoginRequest((LoginRequest) message); } } - } \ No newline at end of file