diff --git a/game/src/main/org/apollo/game/model/World.java b/game/src/main/org/apollo/game/model/World.java index af48bbf9..29f43e3b 100644 --- a/game/src/main/org/apollo/game/model/World.java +++ b/game/src/main/org/apollo/game/model/World.java @@ -33,9 +33,11 @@ import org.apollo.util.NameUtil; import com.google.common.base.Preconditions; /** - * The world class is a singleton which contains objects like the {@link MobRepository} for players and NPCs. It should - * only contain things relevant to the in-game world and not classes which deal with I/O and such (these may be better - * off inside some custom {@link Service} or other code, however, the circumstances are rare). + * The world class is a singleton which contains objects like the + * {@link MobRepository} for players and NPCs. It should only contain things + * relevant to the in-game world and not classes which deal with I/O and such + * (these may be better off inside some custom {@link Service} or other code, + * however, the circumstances are rare). * * @author Graham */ @@ -139,8 +141,8 @@ public final class World { } /** - * Gets the {@link Player} with the specified username. Note that this will return {@code null} if the player is - * offline. + * Gets the {@link Player} with the specified username. Note that this will + * return {@code null} if the player is offline. * * @param username The username. * @return The player. @@ -186,12 +188,14 @@ public final class World { } /** - * Initialises the world by loading definitions from the specified file system. + * Initialises the world by loading definitions from the specified file + * system. * * @param release The release number. * @param fs The file system. * @param manager The plugin manager. TODO move this. - * @throws Exception If any definitions could not be loaded or there was a failure when loading plugins. + * @throws Exception If any definitions could not be loaded or there was a + * failure when loading plugins. */ public void init(int release, IndexedFileSystem fs, PluginManager manager) throws Exception { releaseNumber = release; @@ -202,7 +206,9 @@ public final class World { decoder.block(); - npcMovement = new NpcMovementTask(regions); // Must be exactly here because of ordering issues. + npcMovement = new NpcMovementTask(regions); // Must be exactly here + // because of ordering + // issues. scheduler.schedule(npcMovement); manager.start(); @@ -221,7 +227,8 @@ public final class World { } /** - * Adds an {@link EventListener}, listening for an {@link Event} of the specified type. + * Adds an {@link EventListener}, listening for an {@link Event} of the + * specified type. * * @param type The type of the Event. * @param listener The EventListener. @@ -241,7 +248,8 @@ public final class World { * Registers the specified npc. * * @param npc The npc. - * @return {@code true} if the npc registered successfully, otherwise {@code false}. + * @return {@code true} if the npc registered successfully, otherwise + * {@code false}. */ public boolean register(Npc npc) { boolean success = npcRepository.add(npc); @@ -264,24 +272,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() + "]"); } /** @@ -295,8 +293,8 @@ public final class World { } /** - * Spawns the specified {@link Entity}, which must not be a {@link Player} or an {@link Npc}, which have their own - * register methods. + * Spawns the specified {@link Entity}, which must not be a {@link Player} + * or an {@link Npc}, which have their own register methods. * * @param entity The Entity. */ @@ -350,4 +348,16 @@ public final class World { } } +<<<<<<< HEAD +======= + /** + * Adds entities to regions in the {@link RegionRepository}. By default, we + * do not notify listeners. + * + * @param entities The entities. + */ + private void placeEntities(Entity... entities) { + Arrays.stream(entities).forEach(entity -> regions.fromPosition(entity.getPosition()).addEntity(entity, false)); + } +>>>>>>> c3443521f43dde6310e77de16637503af54805c2 } \ No newline at end of file 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..66501ebb 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; @@ -217,7 +218,8 @@ public final class Player extends Mob { } /** - * Adds a click, represented by a {@link Point}, to the {@link List} of clicks. + * Adds a click, represented by a {@link Point}, to the {@link List} of + * clicks. * * @param point The point. * @return {@code true} if the point was added successfully. @@ -245,7 +247,8 @@ public final class Player extends Mob { } /** - * Adds the specified {@link DynamicGameObject} to this Player's {@link Set} of visible objects. + * Adds the specified {@link DynamicGameObject} to this Player's {@link Set} + * of visible objects. * * @param object The DynamicGameObject. */ @@ -288,10 +291,12 @@ public final class Player extends Mob { } /** - * Indicates whether this player is friends with the player with the specified username or not. + * Indicates whether this player is friends with the player with the + * specified username or not. * * @param username The username of the other player. - * @return {@code true} if the specified username is on this player's friend list, otherwise {@code false}. + * @return {@code true} if the specified username is on this player's friend + * list, otherwise {@code false}. */ public boolean friendsWith(String username) { return friends.contains(username.toLowerCase()); @@ -409,7 +414,8 @@ public final class Player extends Mob { /** * Gets the last known region. * - * @return The last known region, or {@code null} if the player has never known a region. + * @return The last known region, or {@code null} if the player has never + * known a region. */ public Position getLastKnownRegion() { return lastKnownRegion; @@ -507,7 +513,8 @@ public final class Player extends Mob { } /** - * Indicates whether or not the player with the specified username is on this player's ignore list. + * Indicates whether or not the player with the specified username is on + * this player's ignore list. * * @param username The username of the player. * @return {@code true} if the player is ignored, {@code false} if not. @@ -540,7 +547,8 @@ public final class Player extends Mob { } /** - * Increments this player's viewing distance if it is less than the maximum viewing distance. + * Increments this player's viewing distance if it is less than the maximum + * viewing distance. */ public void incrementViewingDistance() { if (viewingDistance < Position.MAX_DISTANCE) { @@ -596,12 +604,31 @@ public final class Player extends Mob { /** * Checks if this player is withdrawing noted items. * - * @return {@code true} if the player is currently withdrawing notes, otherwise {@code false}. + * @return {@code true} if the player is currently withdrawing notes, + * otherwise {@code false}. */ public boolean isWithdrawingNotes() { 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. */ @@ -642,7 +669,8 @@ public final class Player extends Mob { * Removes the specified username from this player's friend list. * * @param username The username. - * @return {@code true} if the player's friend list contained the specified user, {@code false} if not. + * @return {@code true} if the player's friend list contained the specified + * user, {@code false} if not. */ public boolean removeFriend(String username) { return friends.remove(username.toLowerCase()); @@ -652,14 +680,16 @@ public final class Player extends Mob { * Removes the specified username from this player's ignore list. * * @param username The username. - * @return {@code true} if the player's ignore list contained the specified user, {@code false} if not. + * @return {@code true} if the player's ignore list contained the specified + * user, {@code false} if not. */ public boolean removeIgnore(String username) { return ignores.remove(username.toLowerCase()); } /** - * Removes the specified {@link DynamicGameObject} from this Player's {@link Set} of visible objects. + * Removes the specified {@link DynamicGameObject} from this Player's + * {@link Set} of visible objects. * * @param object The DynamicGameObject. */ @@ -911,7 +941,8 @@ public final class Player extends Mob { /** * Sets whether or not the player is withdrawing notes from the bank. * - * @param withdrawingNotes Whether or not the player is withdrawing noted items. + * @param withdrawingNotes Whether or not the player is withdrawing noted + * items. */ public void setWithdrawingNotes(boolean withdrawingNotes) { this.withdrawingNotes = withdrawingNotes; @@ -932,8 +963,7 @@ public final class Player extends Mob { @Override public String toString() { - return MoreObjects.toStringHelper(this).add("username", getUsername()).add("privilege", privilegeLevel) - .toString(); + return MoreObjects.toStringHelper(this).add("username", getUsername()).add("privilege", privilegeLevel).toString(); } /** @@ -966,16 +996,13 @@ public final class Player extends Mob { * Initialises the player's inventories. */ private void initInventories() { - InventoryListener fullInventoryListener = new FullInventoryListener(this, - FullInventoryListener.FULL_INVENTORY_MESSAGE); + InventoryListener fullInventoryListener = new FullInventoryListener(this, FullInventoryListener.FULL_INVENTORY_MESSAGE); InventoryListener fullBankListener = new FullInventoryListener(this, FullInventoryListener.FULL_BANK_MESSAGE); InventoryListener appearanceListener = new AppearanceInventoryListener(this); - InventoryListener syncInventoryListener = new SynchronizationInventoryListener(this, - SynchronizationInventoryListener.INVENTORY_ID); + InventoryListener syncInventoryListener = new SynchronizationInventoryListener(this, SynchronizationInventoryListener.INVENTORY_ID); InventoryListener syncBankListener = new SynchronizationInventoryListener(this, BankConstants.BANK_INVENTORY_ID); - InventoryListener syncEquipmentListener = new SynchronizationInventoryListener(this, - SynchronizationInventoryListener.EQUIPMENT_ID); + InventoryListener syncEquipmentListener = new SynchronizationInventoryListener(this, SynchronizationInventoryListener.EQUIPMENT_ID); inventory.addListener(syncInventoryListener); inventory.addListener(fullInventoryListener); diff --git a/game/src/main/org/apollo/game/service/GameService.java b/game/src/main/org/apollo/game/service/GameService.java index 4c7922a7..bc7f207c 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; @@ -27,18 +26,25 @@ import org.apollo.util.xml.XmlParser; import org.xml.sax.SAXException; /** - * The {@link GameService} class schedules and manages the execution of the {@link GamePulseHandler} class. + * The {@link GameService} class schedules and manages the execution of the + * {@link GamePulseHandler} class. * * @author Graham */ public final class GameService extends Service { /** - * The number of times to unregister players per cycle. This is to ensure the saving threads don't get swamped with - * requests and slow everything down. + * The number of times to unregister players per cycle. This is to ensure + * the saving threads don't get swamped with 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. */ @@ -49,11 +55,15 @@ public final class GameService extends Service { */ private final Queue oldPlayers = new ConcurrentLinkedQueue<>(); + /** + * A queue of players to add. + */ + private final Queue newPlayers = new ConcurrentLinkedQueue<>(); + /** * The scheduled executor service. */ - private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(ThreadUtil - .create("GameService")); + private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(ThreadUtil.create("GameService")); /** * The {@link MessageHandlerChainSet}. @@ -85,6 +95,21 @@ public final class GameService extends Service { 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 +122,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 +138,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. * @@ -138,17 +145,19 @@ public final class GameService extends Service { */ public void shutdown(boolean natural) { scheduledExecutor.shutdownNow(); - // TODO: Other events that should happen upon natural or unexpected shutdown. + // TODO: Other events that should happen upon natural or unexpected + // shutdown. } @Override public void start() { - scheduledExecutor.scheduleAtFixedRate(new GamePulseHandler(this), GameConstants.PULSE_DELAY, - GameConstants.PULSE_DELAY, TimeUnit.MILLISECONDS); + scheduledExecutor.scheduleAtFixedRate(new GamePulseHandler(this), GameConstants.PULSE_DELAY, GameConstants.PULSE_DELAY, + TimeUnit.MILLISECONDS); } /** - * Unregisters a player. Returns immediately. The player is unregistered at the start of the next cycle. + * Unregisters a player. Returns immediately. The player is unregistered at + * the start of the next cycle. * * @param player The player. */ @@ -156,6 +165,16 @@ public final class GameService extends Service { 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. */ @@ -172,12 +191,27 @@ public final class GameService extends Service { } } + /** + * Finalizes the registration of Player's queued to be registered. + */ + private void finalizeRegistrations() { + for (int count = 0; count < REGISTERS_PER_CYCLE; count++) { + Player player = newPlayers.poll(); + if (player == null) { + break; + } + + finalizePlayerRegistration(player); + } + } + /** * Initializes the game service. * * @throws IOException If there is an error accessing the file. * @throws SAXException If there is an error parsing the file. - * @throws ReflectiveOperationException If a MessageHandler could not be created. + * @throws ReflectiveOperationException If a MessageHandler could not be + * created. */ private void init() throws IOException, SAXException, ReflectiveOperationException { try (InputStream input = new FileInputStream("data/messages.xml")) { diff --git a/game/src/main/org/apollo/game/session/GameSession.java b/game/src/main/org/apollo/game/session/GameSession.java index 52c84226..d42779de 100644 --- a/game/src/main/org/apollo/game/session/GameSession.java +++ b/game/src/main/org/apollo/game/session/GameSession.java @@ -43,17 +43,24 @@ public final class GameSession extends Session { */ private final Player player; + /** + * If the player was reconnecting. + */ + private final boolean reconnecting; + /** * Creates a login session for the specified channel. * * @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..e5df58a3 100644 --- a/game/src/main/org/apollo/game/session/LoginSession.java +++ b/game/src/main/org/apollo/game/session/LoginSession.java @@ -80,14 +80,18 @@ 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; + 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); } } @@ -102,20 +106,16 @@ public final class LoginSession extends Session { channel.pipeline().addFirst("messageEncoder", new GameMessageEncoder(release)); channel.pipeline().addBefore("messageEncoder", "gameEncoder", new GamePacketEncoder(randomPair.getEncodingRandom())); - channel.pipeline().addBefore("handler", "gameDecoder", new GamePacketDecoder(randomPair.getDecodingRandom(), context.getRelease())); + channel.pipeline().addBefore("handler", "gameDecoder", + new GamePacketDecoder(randomPair.getDecodingRandom(), context.getRelease())); channel.pipeline().addAfter("gameDecoder", "messageDecoder", new GameMessageDecoder(release)); 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 +124,4 @@ public final class LoginSession extends Session { handleLoginRequest((LoginRequest) message); } } - } \ No newline at end of file