Fix login queue.

This commit is contained in:
Major-
2015-08-30 21:20:24 +01:00
parent ca36264b35
commit ec95fe9d61
5 changed files with 110 additions and 76 deletions
@@ -7,9 +7,9 @@ import com.google.common.base.Preconditions;
/** /**
* A {@link MobRepository} is a repository of {@link Mob}s that are currently active in the game world. * A {@link MobRepository} is a repository of {@link Mob}s that are currently active in the game world.
* *
* @param <T> The type of Mob.
* @author Graham * @author Graham
* @author Ryley * @author Ryley
* @param <T> The type of Mob.
*/ */
public final class MobRepository<T extends Mob> implements Iterable<T> { public final class MobRepository<T extends Mob> implements Iterable<T> {
@@ -95,10 +95,10 @@ public final class MobRepository<T extends Mob> implements Iterable<T> {
* Adds a Mob to the repository. * Adds a Mob to the repository.
* *
* @param mob The Mob to add. * @param mob The Mob to add.
* @return {@code true} if the Mob was added, {@code false} if the size has reached the capacity of this repository. * @return {@code true} if the Mob was added, {@code false} if this MobRepository is at maximum capacity.
*/ */
public boolean add(T mob) { public boolean add(T mob) {
if (size == capacity()) { if (full()) {
return false; return false;
} }
@@ -126,6 +126,15 @@ public final class MobRepository<T extends Mob> implements Iterable<T> {
return mobs.length; return mobs.length;
} }
/**
* Returns whether or not this MobRepository has reached its maxmimum capacity.
*
* @return {@code true} iff this MobRepository is full.
*/
public boolean full() {
return size == mobs.length;
}
/** /**
* Gets the Mob at the given index. * Gets the Mob at the given index.
* *
@@ -447,25 +447,6 @@ public final class Player extends Mob {
public PrivilegeLevel getPrivilegeLevel() { public PrivilegeLevel getPrivilegeLevel() {
return privilegeLevel; return privilegeLevel;
} }
/**
* 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<Player> repository = world.getPlayerRepository();
if (world.isPlayerOnline(getUsername())) {
return RegistrationStatus.ALREADY_ONLINE;
} else if (repository.capacity() == repository.size()) {
return RegistrationStatus.WORLD_FULL;
}
return RegistrationStatus.OK;
}
/** /**
* Gets the player's run energy. * Gets the player's run energy.
* *
@@ -19,7 +19,9 @@ import org.apollo.game.model.area.Region;
import org.apollo.game.model.entity.MobRepository; import org.apollo.game.model.entity.MobRepository;
import org.apollo.game.model.entity.Player; import org.apollo.game.model.entity.Player;
import org.apollo.game.session.GameSession; import org.apollo.game.session.GameSession;
import org.apollo.game.session.LoginSession;
import org.apollo.game.sync.ClientSynchronizer; import org.apollo.game.sync.ClientSynchronizer;
import org.apollo.net.codec.login.LoginConstants;
import org.apollo.util.ThreadUtil; import org.apollo.util.ThreadUtil;
import org.apollo.util.xml.XmlNode; import org.apollo.util.xml.XmlNode;
import org.apollo.util.xml.XmlParser; import org.apollo.util.xml.XmlParser;
@@ -32,6 +34,34 @@ import org.xml.sax.SAXException;
*/ */
public final class GameService extends Service { public final class GameService extends Service {
/**
* A utility class wrapping a {@link Player} and their corresponding {@link LoginSession}.
*/
private static final class LoginPlayerRequest {
/**
* The Player.
*/
private final Player player;
/**
* The LoginSession.
*/
private final LoginSession session;
/**
* Creates the LoginPlayerRequest.
*
* @param player The {@link Player} logging in.
* @param session The {@link LoginSession} of the Player.
*/
public LoginPlayerRequest(Player player, LoginSession session) {
this.player = player;
this.session = session;
}
}
/** /**
* The amount of players to deregister per cycle. This is to ensure the saving threads don't get swamped with * The amount of players to deregister per cycle. This is to ensure the saving threads don't get swamped with
* requests and slow everything down. * requests and slow everything down.
@@ -54,12 +84,12 @@ public final class GameService extends Service {
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(ThreadUtil.create("GameService")); private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(ThreadUtil.create("GameService"));
/** /**
* A queue of players to add. * The Queue of LoginPlayers to add.
*/ */
private final Queue<Player> newPlayers = new ConcurrentLinkedQueue<>(); private final Queue<LoginPlayerRequest> newPlayers = new ConcurrentLinkedQueue<>();
/** /**
* A queue of players to remove. * The Queue of Players to remove.
*/ */
private final Queue<Player> oldPlayers = new ConcurrentLinkedQueue<>(); private final Queue<Player> oldPlayers = new ConcurrentLinkedQueue<>();
@@ -138,13 +168,13 @@ public final class GameService extends Service {
} }
/** /**
* Registers a player. Returns immediately. The player is registered at the * Registers a {@link Player} at the end of the next cycle.
* start of the next cycle.
* *
* @param player The player. * @param player The Player to register.
* @param session the {@link LoginSession} of the Player.
*/ */
public void registerPlayer(Player player) { public void registerPlayer(Player player, LoginSession session) {
newPlayers.add(player); newPlayers.add(new LoginPlayerRequest(player, session));
} }
/** /**
@@ -177,11 +207,20 @@ public final class GameService extends Service {
*/ */
private void finalizeRegistrations() { private void finalizeRegistrations() {
for (int count = 0; count < REGISTRATIONS_PER_CYCLE; count++) { for (int count = 0; count < REGISTRATIONS_PER_CYCLE; count++) {
Player player = newPlayers.poll(); LoginPlayerRequest request = newPlayers.poll();
if (player == null) { if (request == null) {
break; break;
} }
Player player = request.player;
if (world.isPlayerOnline(player.getUsername())) {
request.session.sendLoginFailure(LoginConstants.STATUS_ACCOUNT_ONLINE);
} else if (world.getPlayerRepository().full()) {
request.session.sendLoginFailure(LoginConstants.STATUS_SERVER_FULL);
} else {
request.session.sendLoginSuccess(player);
}
finalizePlayerRegistration(player); finalizePlayerRegistration(player);
} }
} }
@@ -31,10 +31,15 @@ import org.apollo.util.security.IsaacRandomPair;
public final class LoginSession extends Session { public final class LoginSession extends Session {
/** /**
* The server context. * The ServerContext.
*/ */
private final ServerContext context; private final ServerContext context;
/**
* The LoginRequest for this LoginSession.
*/
private LoginRequest request;
/** /**
* Creates a login session for the specified channel. * Creates a login session for the specified channel.
* *
@@ -58,52 +63,14 @@ public final class LoginSession extends Session {
* @param response The response. * @param response The response.
*/ */
public void handlePlayerLoaderResponse(LoginRequest request, PlayerLoaderResponse response) { public void handlePlayerLoaderResponse(LoginRequest request, PlayerLoaderResponse response) {
this.request = request;
GameService service = context.getGameService(); GameService service = context.getGameService();
Channel channel = getChannel();
Optional<Player> optional = response.getPlayer(); Optional<Player> optional = response.getPlayer();
int status = response.getStatus(), rights = 0;
boolean flagged = false;
if (optional.isPresent()) { if (optional.isPresent()) {
Player player = optional.get(); service.registerPlayer(optional.get(), this);
rights = player.getPrivilegeLevel().toInteger();
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);
}
}
ChannelFuture future = channel.write(new LoginResponse(status, rights, flagged));
destroy();
if (optional.isPresent()) {
IsaacRandomPair randomPair = request.getRandomPair();
Release release = context.getRelease();
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().addAfter("gameDecoder", "messageDecoder", new GameMessageDecoder(release));
channel.pipeline().remove("loginDecoder");
channel.pipeline().remove("loginEncoder");
service.registerPlayer(optional.get());
} else { } else {
future.addListener(ChannelFutureListener.CLOSE); sendLoginFailure(response.getStatus());
} }
} }
@@ -114,6 +81,46 @@ public final class LoginSession extends Session {
} }
} }
/**
* Sends a failed {@link LoginResponse} to the client.
*
* @param status The failure status.
*/
public void sendLoginFailure(int status) {
boolean flagged = false;
LoginResponse response = new LoginResponse(status, 0, flagged);
channel.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
/**
* Sends a succesfull {@link LoginResponse} to the client.
*
* @param player The {@link Player} that successfully logged in.
*/
public void sendLoginSuccess(Player player) {
IsaacRandomPair randomPair = request.getRandomPair();
boolean flagged = false;
GameSession session = new GameSession(channel, context, player, request.isReconnecting());
channel.attr(ApolloHandler.SESSION_KEY).set(session);
player.setSession(session);
int rights = player.getPrivilegeLevel().toInteger();
channel.writeAndFlush(new LoginResponse(LoginConstants.STATUS_OK, rights, flagged));
Release release = context.getRelease();
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().addAfter("gameDecoder", "messageDecoder", new GameMessageDecoder(release));
channel.pipeline().remove("loginDecoder");
channel.pipeline().remove("loginEncoder");
}
/** /**
* Handles a login request. * Handles a login request.
* *
@@ -213,8 +213,6 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
private void writeResponseCode(ChannelHandlerContext ctx, int response) { private void writeResponseCode(ChannelHandlerContext ctx, int response) {
ByteBuf buffer = ctx.alloc().buffer(Byte.BYTES); ByteBuf buffer = ctx.alloc().buffer(Byte.BYTES);
buffer.writeByte(response); buffer.writeByte(response);
System.out.println("Sending response: " + response);
ctx.writeAndFlush(buffer).addListener(ChannelFutureListener.CLOSE); ctx.writeAndFlush(buffer).addListener(ChannelFutureListener.CLOSE);
} }