Repackage timer tasks. Add new timer task to broadcast game list updates every 60 seconds instead of every time a game changes (massive bandwidth use). Change chat rate limit from 5 per 10 sec to 6 per 30 sec.

This commit is contained in:
Andy Janata 2015-05-03 13:52:05 -07:00
parent 7a574d39b8
commit aa43eac98b
11 changed files with 119 additions and 71 deletions

View File

@ -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>
<%
}

View File

@ -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>

View File

@ -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>

View File

@ -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")

View File

@ -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);

View File

@ -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;

View File

@ -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;
@ -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.
*/
@ -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() {
@ -183,9 +180,7 @@ 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();
}
/**

View File

@ -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;
}
}
}

View File

@ -1,4 +1,4 @@
package net.socialgamer.cah;
package net.socialgamer.cah.task;
import org.apache.log4j.Logger;

View File

@ -21,13 +21,14 @@
* 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;
/**
@ -35,13 +36,14 @@ import com.google.inject.Inject;
*
* @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;
}

View File

@ -96,27 +96,23 @@ public class GameManagerTest {
bind(ScheduledThreadPoolExecutor.class).toInstance(threadPool);
}
@SuppressWarnings("unused")
@Provides
@MaxGames
Integer provideMaxGames() {
return 3;
}
@SuppressWarnings("unused")
@Provides
@GameId
Integer provideGameId() {
return gameId;
}
@SuppressWarnings("unused")
@Provides
Session provideSession() {
return HibernateUtil.instance.sessionFactory.openSession();
}
@SuppressWarnings("unused")
@Provides
@CardcastCardId
Integer provideCardcastCardId() {
@ -133,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);
@ -172,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);