package tv.dyndns.kishibe.qmaclone.server; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.inject.Inject; import tv.dyndns.kishibe.qmaclone.client.packet.PacketServerStatus; import tv.dyndns.kishibe.qmaclone.client.packet.PacketUserData; import tv.dyndns.kishibe.qmaclone.server.database.Database; import tv.dyndns.kishibe.qmaclone.server.database.DatabaseException; import tv.dyndns.kishibe.qmaclone.server.websocket.MessageSender; public class ServerStatusManager { private static final Logger logger = Logger.getLogger(ServerStatusManager.class.getName()); private static final int UPDATE_DURATION = 10; // 秒 private final Database database; @VisibleForTesting final AtomicInteger numberOfPageView = new AtomicInteger(); private final AtomicInteger numberOfTotalSessions = new AtomicInteger(); private final AtomicInteger numberOfTotalPlayers = new AtomicInteger(); private final AtomicInteger numberOfCurrentSessions = new AtomicInteger(); private final AtomicInteger numberOfCurrentPlayers = new AtomicInteger(); @VisibleForTesting volatile Set<Integer> loginUserCodes = Collections.synchronizedSet(new HashSet<Integer>()); private volatile List<PacketUserData> loginUsers = Lists.newArrayList(); private final GameManager gameManager; private final NormalModeProblemManager normalModeProblemManager; private final PlayerHistoryManager playerHistoryManager; private final MessageSender<PacketServerStatus> serverStatusMessageSender; private volatile PacketServerStatus serverStatus; private final Runnable saveServerStatusRunner = new Runnable() { public void run() { saveServerStatus(); } }; private final Runnable updateLoginUsersRunner = new Runnable() { public void run() { updateLoginUsers(); } }; private final Runnable updateServerStatusRunner = new Runnable() { @Override public void run() { try { updateServerStatus(); } catch (DatabaseException e) { logger.log(Level.WARNING, "サーバーステータスの更新に失敗しました", e); } } }; @Inject public ServerStatusManager(Database database, GameManager gameManager, NormalModeProblemManager normalModeProblemManager, PlayerHistoryManager playerHistoryManager, MessageSender<PacketServerStatus> serverStatusMessageSender, ThreadPool threadPool) { this.database = Preconditions.checkNotNull(database); this.gameManager = Preconditions.checkNotNull(gameManager); this.normalModeProblemManager = Preconditions.checkNotNull(normalModeProblemManager); this.playerHistoryManager = Preconditions.checkNotNull(playerHistoryManager); this.serverStatusMessageSender = Preconditions.checkNotNull(serverStatusMessageSender); threadPool.addMinuteTasks(saveServerStatusRunner); threadPool.addMinuteTasks(updateLoginUsersRunner); threadPool.scheduleAtFixedRate(updateServerStatusRunner, UPDATE_DURATION, UPDATE_DURATION, TimeUnit.SECONDS); try { updateServerStatus(); } catch (DatabaseException e) { logger.log(Level.WARNING, "サーバーステータスの更新に失敗しました", e); } loadPageView(); } private void loadPageView() { // データベースから読み込み PageView pageView; try { pageView = database.loadPageView(); } catch (DatabaseException e) { logger.log(Level.WARNING, "ページビューの読み込みに失敗しました", e); return; } numberOfPageView.addAndGet(pageView.numberOfPageView); numberOfTotalSessions.addAndGet(pageView.numberOfSessions); numberOfTotalPlayers.addAndGet(pageView.numberOfPlayers); } public void login() { numberOfPageView.getAndIncrement(); } @VisibleForTesting void updateServerStatus() throws DatabaseException { PacketServerStatus status = new PacketServerStatus(); status.numberOfCurrentSessions = numberOfCurrentSessions.get(); status.numberOfTotalSessions = numberOfTotalSessions.get(); status.numberOfCurrentPlayers = numberOfCurrentPlayers.get(); status.numberOfTotalPlayers = numberOfTotalPlayers.get(); status.numberOfProblems = normalModeProblemManager.getNumberOfProblem(); status.numberOfPageView = numberOfPageView.get(); // 読み取りのみなので同期不要 status.numberOfLoginPlayers = Math.max(loginUsers.size(), loginUserCodes.size()); status.numberOfActivePlayers = database.getNumberOfActiveUsers(); status.numberOfPlayersInWhole = gameManager.getNumberOfPlayersInWhole(); status.lastestPlayers = playerHistoryManager.get(); if (Objects.equal(serverStatus, status)) { return; } serverStatus = status; serverStatusMessageSender.send(status); } public PacketServerStatus getServerStatus() { return serverStatus; } public void saveServerStatus() { PageView pageView = new PageView(); pageView.numberOfPageView = numberOfPageView.get(); pageView.numberOfPlayers = numberOfTotalPlayers.get(); pageView.numberOfSessions = numberOfTotalSessions.get(); if (pageView.numberOfPageView < 10000 || pageView.numberOfPlayers < 10000 || pageView.numberOfSessions < 10000) { logger.info("ページビューが読み込まれていない可能性があります。再読み込みを行います。"); loadPageView(); return; } try { database.savePageView(pageView); } catch (DatabaseException e) { logger.log(Level.WARNING, "ページビューの保存に失敗しました", e); } } public void keepAlive(int userCode) { loginUserCodes.add(userCode); } public List<PacketUserData> getLoginUsers() { return loginUsers; } private void updateLoginUsers() { Set<Integer> userCodes = loginUserCodes; loginUserCodes = Collections.synchronizedSet(new HashSet<Integer>()); List<PacketUserData> list = Lists.newArrayList(); for (int userCode : userCodes) { try { list.add(database.getUserData(userCode)); } catch (DatabaseException e) { logger.log(Level.WARNING, "ユーザー情報の読み込みに失敗しました", e); } } loginUsers = list; } public void changeStatics(int sessionDelta, int playDelta) { numberOfCurrentSessions.set(gameManager.getNumberOfSessions()); numberOfCurrentPlayers.set(gameManager.getNumberOfPlayers()); numberOfTotalSessions.addAndGet(sessionDelta); numberOfTotalPlayers.addAndGet(playDelta); } public MessageSender<PacketServerStatus> getServerStatusMessageSender() { return serverStatusMessageSender; } }