Merge pull request #1 from ajanata/master
Repackage timer tasks. Add new timer task to broadcast game list upda…
This commit is contained in:
commit
3aea5d144e
|
@ -242,6 +242,7 @@ select {
|
|||
<th>Delete</th>
|
||||
<th>Edit</th>
|
||||
<th>Weight</th>
|
||||
<th>Active</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -253,6 +254,7 @@ select {
|
|||
<td><a href="?delete=<%=cardSet.getId()%>" onclick="return confirm('Are you sure?')">Delete</a></td>
|
||||
<td><a href="?edit=<%=cardSet.getId()%>">Edit</a></td>
|
||||
<td><%=cardSet.getWeight()%></td>
|
||||
<td><%=cardSet.isActive()%></td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
|
|
|
@ -86,27 +86,16 @@ HttpSession hSession = request.getSession(true);
|
|||
If this is your first time playing, you may wish to read <a href="/">the changelog and list of
|
||||
known issues</a>.
|
||||
</p>
|
||||
<p tabindex="0">Most recent update: 21 February 2015:</p>
|
||||
<p tabindex="0">Most recent update: 3 May 2015:</p>
|
||||
<ul>
|
||||
<li>Servers now run in Amazon Web Services. This is going to cost me more, but at least it
|
||||
should be more stable and not take down my other stuff when it does go down...<ul>
|
||||
<li>I am still tweaking server settings in AWS. It likely is going to be unstable for another
|
||||
week or two while I fine-tune cost and performance.</li></ul></li>
|
||||
<li>Card set filters are fixed.</li>
|
||||
<li><pre>/removecardcast</pre> is fixed.</li>
|
||||
<li>Connect and disconnect notices are disabled server-wide. This was a major source of
|
||||
bandwidth and processing time.</li>
|
||||
<li><strong>You can start a game without using any local card sets.</strong>You must have at
|
||||
least 50 black cards and (20 times player limit) white cards to be able to start a game.</li>
|
||||
<li>Several other back-end performance and code maintainability improvements.</li>
|
||||
<li><strong>Custom card sets will be removed from local storage in the near future.</strong>You
|
||||
will have to use Cardcast to use custom card sets. If a card set you want is not already in
|
||||
Cardcast, you can attempt to extract it from
|
||||
<a href='https://github.com/ajanata/PretendYoureXyzzy/blob/737b468/cah_cards.sql'>the last
|
||||
version of the database dump which contains them</a> and add it to Cardcast yourself; I will be
|
||||
unable to provide help in doing so.</li>
|
||||
<li>At roughly the same time, all officially released Cards Against Humanity sets which are not
|
||||
already in the system will be added as local decks.</li>
|
||||
<li>The game list automatically updates once per minute now, instead of several times per
|
||||
second. You can still click the Refresh Games button in the top left corner at any time.</li>
|
||||
<li>Chat flood protection has been made more strict.</li>
|
||||
<li>Other back-end changes to attempt to get the AWS bill in control.</li>
|
||||
<li><strong>All locally-stored custom card sets have been removed.</strong> You must use
|
||||
Cardcast for custom card sets now.</li>
|
||||
<li>The 5th and 6th Expansions, PAX Prime 2014 Panel, Ten Days or Whatever of Kwanzaa,
|
||||
and Science packs have all been added.</li>
|
||||
<li>Remaining known issues and high priority features:<ul>
|
||||
<li>Leaving a game as a spectator doesn't work right.</li>
|
||||
<li>Game owners still can't kick players from their game.</li>
|
||||
|
|
|
@ -54,6 +54,16 @@ to, for instance, display the number of connected players.
|
|||
</p>
|
||||
<p>Recent Changes:</p>
|
||||
<ul>
|
||||
<li>3 May 2015:<ul>
|
||||
<li>The game list automatically updates once per minute now, instead of several times per
|
||||
second. You can still click the Refresh Games button in the top left corner at any time.</li>
|
||||
<li>Chat flood protection has been made more strict.</li>
|
||||
<li>Other back-end changes to attempt to get the AWS bill in control.</li>
|
||||
<li><strong>All locally-stored custom card sets have been removed.</strong> You must use
|
||||
Cardcast for custom card sets now.</li>
|
||||
<li>The 5th and 6th Expansions, PAX Prime 2014 Panel, 10 Days or Whatever of Kwanzaa,
|
||||
and Science packs have all been added.</li>
|
||||
</ul></li>
|
||||
<li>21 February 2015:<ul>
|
||||
<li>Servers now run in Amazon Web Services. This is going to cost me more, but at least it
|
||||
should be more stable and not take down my other stuff when it does go down...<ul>
|
||||
|
|
|
@ -44,8 +44,8 @@ import net.socialgamer.cah.data.Game;
|
|||
*/
|
||||
public class Constants {
|
||||
|
||||
public static final int CHAT_FLOOD_MESSAGE_COUNT = 5;
|
||||
public static final int CHAT_FLOOD_TIME = 10 * 1000;
|
||||
public static final int CHAT_FLOOD_MESSAGE_COUNT = 6;
|
||||
public static final int CHAT_FLOOD_TIME = 30 * 1000;
|
||||
public static final int CHAT_MAX_LENGTH = 200;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
|
|
|
@ -35,6 +35,8 @@ import javax.servlet.ServletContextEvent;
|
|||
|
||||
import net.socialgamer.cah.cardcast.CardcastModule;
|
||||
import net.socialgamer.cah.cardcast.CardcastService;
|
||||
import net.socialgamer.cah.task.BroadcastGameListUpdateTask;
|
||||
import net.socialgamer.cah.task.UserPingTask;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.PropertyConfigurator;
|
||||
|
@ -70,6 +72,16 @@ public class StartupUtils extends GuiceServletContextListener {
|
|||
*/
|
||||
private static final long PING_CHECK_DELAY = 5 * 1000;
|
||||
|
||||
/**
|
||||
* Delay before the "update game list" broadcast timer is started, in milliseconds.
|
||||
*/
|
||||
private static final long BROADCAST_UPDATE_START_DELAY = TimeUnit.SECONDS.toMillis(60);
|
||||
|
||||
/**
|
||||
* Delay between invocations of the "update game list" broadcast timer, in milliseconds.
|
||||
*/
|
||||
private static final long BROADCAST_UPDATE_DELAY = TimeUnit.SECONDS.toMillis(60);
|
||||
|
||||
/**
|
||||
* Context attribute key name for the time the server was started.
|
||||
*/
|
||||
|
@ -104,10 +116,18 @@ public class StartupUtils extends GuiceServletContextListener {
|
|||
public void contextInitialized(final ServletContextEvent contextEvent) {
|
||||
final ServletContext context = contextEvent.getServletContext();
|
||||
final Injector injector = getInjector();
|
||||
final UserPing ping = injector.getInstance(UserPing.class);
|
||||
|
||||
final ScheduledThreadPoolExecutor timer = injector
|
||||
.getInstance(ScheduledThreadPoolExecutor.class);
|
||||
|
||||
final UserPingTask ping = injector.getInstance(UserPingTask.class);
|
||||
timer.scheduleAtFixedRate(ping, PING_START_DELAY, PING_CHECK_DELAY, TimeUnit.MILLISECONDS);
|
||||
|
||||
final BroadcastGameListUpdateTask broadcastUpdate = injector
|
||||
.getInstance(BroadcastGameListUpdateTask.class);
|
||||
timer.scheduleAtFixedRate(broadcastUpdate, BROADCAST_UPDATE_START_DELAY,
|
||||
BROADCAST_UPDATE_DELAY, TimeUnit.MILLISECONDS);
|
||||
|
||||
serverStarted = new Date();
|
||||
context.setAttribute(INJECTOR, injector);
|
||||
context.setAttribute(DATE_NAME, serverStarted);
|
||||
|
|
|
@ -50,11 +50,11 @@ import net.socialgamer.cah.Constants.LongPollEvent;
|
|||
import net.socialgamer.cah.Constants.LongPollResponse;
|
||||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.Constants.WhiteCardData;
|
||||
import net.socialgamer.cah.SafeTimerTask;
|
||||
import net.socialgamer.cah.cardcast.CardcastDeck;
|
||||
import net.socialgamer.cah.cardcast.CardcastService;
|
||||
import net.socialgamer.cah.data.GameManager.GameId;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
import net.socialgamer.cah.task.SafeTimerTask;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.hibernate.Session;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) 2012, Andy Janata
|
||||
* All rights reserved.
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
* provided that the following conditions are met:
|
||||
*
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||
* and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
|
@ -27,17 +27,13 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.socialgamer.cah.Constants.LongPollEvent;
|
||||
import net.socialgamer.cah.Constants.LongPollResponse;
|
||||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.data.Game.TooManyPlayersException;
|
||||
import net.socialgamer.cah.data.GameManager.GameId;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
import net.socialgamer.cah.task.BroadcastGameListUpdateTask;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
@ -49,9 +45,9 @@ import com.google.inject.Singleton;
|
|||
|
||||
/**
|
||||
* Manage games for the server.
|
||||
*
|
||||
*
|
||||
* This is also a Guice provider for game ids.
|
||||
*
|
||||
*
|
||||
* @author Andy Janata (ajanata@socialgamer.net)
|
||||
*/
|
||||
@Singleton
|
||||
|
@ -62,7 +58,8 @@ public class GameManager implements Provider<Integer> {
|
|||
private final Provider<Integer> maxGamesProvider;
|
||||
private final Map<Integer, Game> games = new TreeMap<Integer, Game>();
|
||||
private final Provider<Game> gameProvider;
|
||||
private final ConnectedUsers users;
|
||||
private final BroadcastGameListUpdateTask broadcastUpdate;
|
||||
|
||||
/**
|
||||
* Potential next game id.
|
||||
*/
|
||||
|
@ -70,7 +67,7 @@ public class GameManager implements Provider<Integer> {
|
|||
|
||||
/**
|
||||
* Create a new game manager.
|
||||
*
|
||||
*
|
||||
* @param gameProvider
|
||||
* Provider for new {@code Game} instances.
|
||||
* @param maxGamesProvider
|
||||
|
@ -81,10 +78,10 @@ public class GameManager implements Provider<Integer> {
|
|||
@Inject
|
||||
public GameManager(final Provider<Game> gameProvider,
|
||||
@MaxGames final Provider<Integer> maxGamesProvider,
|
||||
final ConnectedUsers users) {
|
||||
final BroadcastGameListUpdateTask broadcastUpdate) {
|
||||
this.gameProvider = gameProvider;
|
||||
this.maxGamesProvider = maxGamesProvider;
|
||||
this.users = users;
|
||||
this.broadcastUpdate = broadcastUpdate;
|
||||
}
|
||||
|
||||
private int getMaxGames() {
|
||||
|
@ -94,7 +91,7 @@ public class GameManager implements Provider<Integer> {
|
|||
/**
|
||||
* Creates a new game, if there are free game slots. Returns {@code null} if there are already the
|
||||
* maximum number of games in progress.
|
||||
*
|
||||
*
|
||||
* @return Newly created game, or {@code null} if the maximum number of games are in progress.
|
||||
*/
|
||||
private Game createGame() {
|
||||
|
@ -114,11 +111,11 @@ public class GameManager implements Provider<Integer> {
|
|||
/**
|
||||
* Creates a new game and puts the specified user into the game, if there are free game slots.
|
||||
* Returns {@code null} if there are already the maximum number of games in progress.
|
||||
*
|
||||
*
|
||||
* Creating the game and adding the user are done atomically with respect to another game getting
|
||||
* created, or even getting the list of active games. It is impossible for another user to join
|
||||
* the game before the requesting user.
|
||||
*
|
||||
*
|
||||
* @param user
|
||||
* User to place into the game.
|
||||
* @return Newly created game, or {@code null} if the maximum number of games are in progress.
|
||||
|
@ -150,10 +147,10 @@ public class GameManager implements Provider<Integer> {
|
|||
/**
|
||||
* This probably will not be used very often in the server: Games should normally be deleted when
|
||||
* all players leave it. I'm putting this in if only to help with testing.
|
||||
*
|
||||
*
|
||||
* Destroys a game immediately. This will almost certainly cause errors on the client for any
|
||||
* players left in the game. If {@code gameId} isn't valid, this method silently returns.
|
||||
*
|
||||
*
|
||||
* @param gameId
|
||||
* ID of game to destroy.
|
||||
*/
|
||||
|
@ -183,17 +180,15 @@ public class GameManager implements Provider<Integer> {
|
|||
* Broadcast an event to all users that they should refresh the game list.
|
||||
*/
|
||||
public void broadcastGameListRefresh() {
|
||||
final HashMap<ReturnableData, Object> broadcastData = new HashMap<ReturnableData, Object>();
|
||||
broadcastData.put(LongPollResponse.EVENT, LongPollEvent.GAME_LIST_REFRESH.toString());
|
||||
users.broadcastToAll(MessageType.GAME_EVENT, broadcastData);
|
||||
broadcastUpdate.needsUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an unused game ID, or -1 if the maximum number of games are in progress. This should not be
|
||||
* called in such a case, though!
|
||||
*
|
||||
*
|
||||
* TODO: make this not suck
|
||||
*
|
||||
*
|
||||
* @return Next game id, or {@code -1} if the maximum number of games are in progress.
|
||||
*/
|
||||
@Override
|
||||
|
@ -220,7 +215,7 @@ public class GameManager implements Provider<Integer> {
|
|||
|
||||
/**
|
||||
* Try to guess a good candidate for the next game id.
|
||||
*
|
||||
*
|
||||
* @param skip
|
||||
* An id to skip over.
|
||||
* @return A guess for the next game id.
|
||||
|
@ -255,7 +250,7 @@ public class GameManager implements Provider<Integer> {
|
|||
|
||||
/**
|
||||
* Gets the game with the specified id, or {@code null} if there is no game with that id.
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
* Id of game to retrieve.
|
||||
* @return The Game, or {@code null} if there is no game with that id.
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package net.socialgamer.cah.task;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import net.socialgamer.cah.Constants.LongPollEvent;
|
||||
import net.socialgamer.cah.Constants.LongPollResponse;
|
||||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.data.ConnectedUsers;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
|
||||
@Singleton
|
||||
public class BroadcastGameListUpdateTask extends SafeTimerTask {
|
||||
|
||||
private volatile boolean needsUpdate = false;
|
||||
private final ConnectedUsers users;
|
||||
|
||||
@Inject
|
||||
public BroadcastGameListUpdateTask(final ConnectedUsers users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
public void needsUpdate() {
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process() {
|
||||
if (needsUpdate) {
|
||||
final HashMap<ReturnableData, Object> broadcastData = new HashMap<ReturnableData, Object>();
|
||||
broadcastData.put(LongPollResponse.EVENT, LongPollEvent.GAME_LIST_REFRESH.toString());
|
||||
users.broadcastToAll(MessageType.GAME_EVENT, broadcastData);
|
||||
needsUpdate = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package net.socialgamer.cah;
|
||||
package net.socialgamer.cah.task;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) 2012, Andy Janata
|
||||
* All rights reserved.
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
* provided that the following conditions are met:
|
||||
*
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||
* and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
|
@ -21,27 +21,29 @@
|
|||
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.socialgamer.cah;
|
||||
package net.socialgamer.cah.task;
|
||||
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
|
||||
import net.socialgamer.cah.data.ConnectedUsers;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
|
||||
/**
|
||||
* Timer task to check for disconnected and idle clients.
|
||||
*
|
||||
*
|
||||
* @author Andy Janata (ajanata@gmail.com)
|
||||
*/
|
||||
public class UserPing extends SafeTimerTask {
|
||||
@Singleton
|
||||
public class UserPingTask extends SafeTimerTask {
|
||||
|
||||
private final ConnectedUsers users;
|
||||
private final ScheduledThreadPoolExecutor globalTimer;
|
||||
|
||||
@Inject
|
||||
public UserPing(final ConnectedUsers users, final ScheduledThreadPoolExecutor globalTimer) {
|
||||
public UserPingTask(final ConnectedUsers users, final ScheduledThreadPoolExecutor globalTimer) {
|
||||
this.users = users;
|
||||
this.globalTimer = globalTimer;
|
||||
}
|
|
@ -1,16 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) 2012, Andy Janata
|
||||
* All rights reserved.
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
* provided that the following conditions are met:
|
||||
*
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||
* and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
|
@ -58,7 +58,7 @@ import com.google.inject.Provides;
|
|||
|
||||
/**
|
||||
* Tests for {@code GameManager}.
|
||||
*
|
||||
*
|
||||
* @author Andy Janata (ajanata@socialgamer.net)
|
||||
*/
|
||||
public class GameManagerTest {
|
||||
|
@ -129,11 +129,8 @@ public class GameManagerTest {
|
|||
verify(userMock);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testGetAndDestroyGame() {
|
||||
cuMock.broadcastToAll(eq(MessageType.GAME_EVENT), anyObject(HashMap.class));
|
||||
expectLastCall().times(3);
|
||||
replay(cuMock);
|
||||
replay(userMock);
|
||||
|
||||
|
@ -168,8 +165,6 @@ public class GameManagerTest {
|
|||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testCreateGame() {
|
||||
cuMock.broadcastToAll(eq(MessageType.GAME_EVENT), anyObject(HashMap.class));
|
||||
expectLastCall().times(3);
|
||||
cuMock.broadcastToList(anyObject(Collection.class), eq(MessageType.GAME_PLAYER_EVENT),
|
||||
anyObject(HashMap.class));
|
||||
expectLastCall().times(3);
|
||||
|
|
Loading…
Reference in New Issue