Skip to content

Commit

Permalink
Added the ability to simulate more than 1 star at a time.
Browse files Browse the repository at this point in the history
  • Loading branch information
codeka committed Nov 4, 2014
1 parent 3936810 commit de08193
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 129 deletions.
2 changes: 1 addition & 1 deletion common/src/au/com/codeka/common/TimeFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public String format(Period p) {
if (days > 0) {
int hours = p.getHours() - (days * 24);
if (alwaysIncludeMinutes) {
int minutes = p.getMinutes() - (hours * 60) - (Period.days(days).toStandardMinutes().getMinutes());
int minutes = p.getMinutes();
return String.format(Locale.ENGLISH, "%d day%s, %d hr%s %d min%s", days,
days == 1 ? "" : "s", hours, hours == 1 ? "" : "s", minutes, minutes == 1 ? "" : "s");
} else {
Expand Down
5 changes: 2 additions & 3 deletions server/data/config-tmpl.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
"realmName": "Blitz", // name of the realm we're accepting requests for
"listenPort": 8080, // port to listen for HTTP connections on

// If false, we won't do star simulations (not good in production, but useful while testing
// to reduce CPU load and noise in log files)
"enableStarSimulationThread": false,
// The number of star simulation threads to run, set to 0 to disable star simulations.
"numStarSimulationThreads": 1,

// Database configuration
"database": {
Expand Down
2 changes: 1 addition & 1 deletion server/data/tmpl/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ <h1>Active Empires</h1>
"success": function(data) {
$("p#oldest-star span").html("<b>" + data["oldest_star_name"] + "</b> (" +
data["oldest_star_id"] + "): " + data["oldest_star_time"] + " ago, <b>" +
formatNumber(data["num_stars"]) + "</b> stars to simulate");
formatNumber(data["num_stars_older_than_3_hrs"]) + "</b> stars are &gt; 3hrs old");
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion server/scripts/logging-debug.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
handlers = au.com.codeka.warworlds.server.ConsoleLoggingHandler, au.com.codeka.warworlds.server.ErrorReportingLoggingHandler
.level = ALL

au.com.codeka.warworlds.server.ConsoleLoggingHandler.level = ALL
au.com.codeka.warworlds.server.ConsoleLoggingHandler.level = INFO
au.com.codeka.warworlds.server.ConsoleLoggingHandler.formatter = au.com.codeka.warworlds.server.LogFormatter

java.util.logging.SimpleFormatter.format = %1tF %1tT %4s : %5s%6s
9 changes: 6 additions & 3 deletions server/src/au/com/codeka/warworlds/server/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static void loadConfig() throws FileNotFoundException {
private String realmName;
private String dataDirectory;
private int listenPort;
private boolean enableStarSimulationThread;
private Integer numStarSimulationThreads;
private DatabaseConfiguration database;
private SinbinConfiguration sinbin;

Expand All @@ -48,8 +48,11 @@ public int getListenPort() {
return listenPort;
}

public boolean getEnableStarSimulationThread() {
return enableStarSimulationThread;
public int getNumStarSimulationThreads() {
if (numStarSimulationThreads == null) {
return 1;
}
return numStarSimulationThreads.intValue();
}

public DatabaseConfiguration getDatabaseConfig() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package au.com.codeka.warworlds.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
Expand Down
13 changes: 3 additions & 10 deletions server/src/au/com/codeka/warworlds/server/Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,8 @@ private static void cronMain(String method, String extra) throws Exception {
private static void gameMain() throws Exception {
EventProcessor.i.ping();

StarSimulatorThread starSimulatorThread = null;
if (Configuration.i.getEnableStarSimulationThread()) {
starSimulatorThread = new StarSimulatorThread();
starSimulatorThread.start();
} else {
log.info("Star simulation thread disabled.");
}
StarSimulatorThreadManager starSimulatorThreadManager = new StarSimulatorThreadManager();
starSimulatorThreadManager.start();

int port = Configuration.i.getListenPort();
Server server = new Server(port);
Expand All @@ -61,8 +56,6 @@ private static void gameMain() throws Exception {
log.info("Server started on http://localhost:%d/", port);
server.join();

if (starSimulatorThread != null) {
starSimulatorThread.stop();
}
starSimulatorThreadManager.stop();
}
}
209 changes: 102 additions & 107 deletions server/src/au/com/codeka/warworlds/server/StarSimulatorThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,127 +6,122 @@
import au.com.codeka.common.Log;
import au.com.codeka.common.model.Simulation;
import au.com.codeka.warworlds.server.ctrl.StarController;
import au.com.codeka.warworlds.server.data.DB;
import au.com.codeka.warworlds.server.data.SqlResult;
import au.com.codeka.warworlds.server.data.SqlStmt;
import au.com.codeka.warworlds.server.model.Star;

/**
* This is a background thread that runs every 2 seconds and simulates
* the oldest star in the system. This ensures we never let our stars get
* TOO out-of-date.
* This is a background thread that runs every 2 seconds and simulates the
* oldest star in the system. This ensures we never let our stars get TOO
* out-of-date.
*/
public class StarSimulatorThread {
private static final Log log = new Log("StarSimulatorThread");
private Thread mThread;
private boolean mStopped;

private static int WAIT_TIME_NO_STARS = 10 * 60 * 1000; // 10 minutes, after no stars found
private static int WAIT_TIME_ERROR = 60 * 1000; // 1 minute, in case of error
private static int WAIT_TIME_NORMAL = 0; // don't wait if there's more stars to simulate
private static final Log log = new Log("StarSimulatorThread");
private Thread thread;
private boolean stopped;
private final StarSimulatorThreadManager manager;

private static int WAIT_TIME_NO_STARS = 10 * 60 * 1000; // 10 minutes, after
// no stars found
private static int WAIT_TIME_ERROR = 60 * 1000; // 1 minute, in case of error
private static int WAIT_TIME_NORMAL = 0; // don't wait if there's more stars
// to simulate

public StarSimulatorThread(StarSimulatorThreadManager manager) {
this.manager = manager;
}

public void start() {
if (thread != null) {
stop();
}

public void start() {
if (mThread != null) {
stop();
}
thread = new Thread(new Runnable() {
@Override
public void run() {
threadproc();
}
});
thread.setDaemon(true);
thread.setName("Star-Simulator");
thread.setPriority(Thread.NORM_PRIORITY - 1);
thread.start();
}

public void stop() {
if (thread == null) {
return;
}

mThread = new Thread(new Runnable() {
@Override
public void run() {
threadproc();
}
});
mThread.setDaemon(true);
mThread.setName("Star-Simulator");
mThread.setPriority(Thread.NORM_PRIORITY - 1);
mThread.start();
stopped = true;
try {
thread.interrupt();
thread.join();
} catch (InterruptedException e) {
// ignore
}

public void stop() {
if (mThread == null) {
return;
}
thread = null;
}

mStopped = true;
try {
mThread.interrupt();
mThread.join();
} catch (InterruptedException e) {
// ignore
}
private void threadproc() {
int numSimulatedSinceEventProcessorPinged = 0;
while (!stopped) {
int waitTimeMs = simulateOneStar();
numSimulatedSinceEventProcessorPinged++;

mThread = null;
}

private void threadproc() {
int numSimulatedSinceEventProcessorPinged = 0;
while (!mStopped) {
int waitTimeMs = simulateOneStar();
numSimulatedSinceEventProcessorPinged ++;

if (numSimulatedSinceEventProcessorPinged >= 50) {
EventProcessor.i.ping();
numSimulatedSinceEventProcessorPinged = 0;
}

if (waitTimeMs > 0) {
log.info(String.format("Waiting %d seconds before simulating next star.",
waitTimeMs / 1000));
try {
Thread.sleep(waitTimeMs);
} catch (InterruptedException e) {
}
}
}
}
if (numSimulatedSinceEventProcessorPinged >= 50) {
EventProcessor.i.ping();
numSimulatedSinceEventProcessorPinged = 0;
}

private int simulateOneStar() {
if (waitTimeMs > 0) {
log.info(String
.format("Waiting %d seconds before simulating next star.", waitTimeMs / 1000));
try {
int starID = findOldestStar();
if (starID == 0) {
return WAIT_TIME_NO_STARS;
}
log.debug("Simulating star: "+starID);
long startTime = System.currentTimeMillis();

Star star = new StarController().getStar(starID);
if (star.getLastSimulation().isAfter(DateTime.now().minusHours(1))) {
// how long would we have to wait (in seconds) before there WOULD HAVE been one
// hour since it was last simulated?
int seconds = Seconds.secondsBetween(DateTime.now().minusHours(1),
star.getLastSimulation()).getSeconds();
return seconds * 1000;
}

new Simulation().simulate(star);
// we don't ping event processor now, because we rather do it once every 50 stars or so.
new StarController().update(star, false);

long endTime = System.currentTimeMillis();
log.info(String.format("Simulated star (%d colonies, %d fleets) in %dms: \"%s\" [%d]",
star.getColonies().size(), star.getFleets().size(),
endTime - startTime, star.getName(), star.getID()));
return WAIT_TIME_NORMAL;
} catch (Exception e) {
log.info("HERE");
log.error("Exception caught simulating star!", e);
// TODO: if there are errors, it'll just keep reporting
// over and over... probably a good thing because we'll
// definitely need to fix it!
return WAIT_TIME_ERROR;
Thread.sleep(waitTimeMs);
} catch (InterruptedException e) {
}
}
}

public static int findOldestStar() throws Exception {
String sql = "SELECT id FROM stars" +
" WHERE empire_count > 0" +
" ORDER BY last_simulation ASC LIMIT 1";
try (SqlStmt stmt = DB.prepare(sql)) {
SqlResult res = stmt.select();
if (res.next()) {
return res.getInt(1);
}
return 0;
}
}

private int simulateOneStar() {
try {
int starID = manager.getNextStar();
if (starID == 0) {
return WAIT_TIME_NO_STARS;
}
log.debug("Simulating star: " + starID);
long startTime = System.currentTimeMillis();

Star star = new StarController().getStar(starID);
if (star.getLastSimulation().isAfter(DateTime.now().minusHours(1))) {
// how long would we have to wait (in seconds) before there WOULD HAVE
// been one
// hour since it was last simulated?
int seconds = Seconds
.secondsBetween(DateTime.now().minusHours(1), star.getLastSimulation()).getSeconds();
return seconds * 1000;
}

new Simulation().simulate(star);
long simulateEndTime = System.currentTimeMillis();
// we don't ping event processor now, because we rather do it once every
// 50 stars or so.
new StarController().update(star, false);

long endTime = System.currentTimeMillis();
log.info(String.format(
"Simulated star (%d colonies, %d fleets) in %dms (%dms in DB): \"%s\" [%d]", star
.getColonies().size(), star.getFleets().size(), endTime - startTime, endTime
- simulateEndTime, star.getName(), star.getID()));
return WAIT_TIME_NORMAL;
} catch (Exception e) {
log.info("HERE");
log.error("Exception caught simulating star!", e);
// TODO: if there are errors, it'll just keep reporting
// over and over... probably a good thing because we'll
// definitely need to fix it!
return WAIT_TIME_ERROR;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package au.com.codeka.warworlds.server;

import java.sql.SQLException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Queue;

import au.com.codeka.common.Log;
import au.com.codeka.warworlds.server.data.DB;
import au.com.codeka.warworlds.server.data.SqlResult;
import au.com.codeka.warworlds.server.data.SqlStmt;

public class StarSimulatorThreadManager {
private static final Log log = new Log("StarSimulatorThreadManager");

private final ArrayList<StarSimulatorThread> threads = new ArrayList<StarSimulatorThread>();
private final Queue<Integer> starIDs = new ArrayDeque<Integer>();
private final Object lock = new Object();

public void start() {
for (int i = 0; i < Configuration.i.getNumStarSimulationThreads(); i++) {
StarSimulatorThread thread = new StarSimulatorThread(this);
thread.start();
threads.add(thread);
}
log.info("Started %d star simulation threads.", threads.size());
}

public void stop() {
for (StarSimulatorThread thread : threads) {
thread.stop();
}
}

/** Returns the ID of the next star to simulate. */
public int getNextStar() {
synchronized(lock) {
if (starIDs.isEmpty()) {
// Grab 50 stars at a time, to save all those queries.
String sql = "SELECT id FROM stars" + " WHERE empire_count > 0"
+ " ORDER BY last_simulation ASC LIMIT 50";
try (SqlStmt stmt = DB.prepare(sql)) {
SqlResult res = stmt.select();
while (res.next()) {
starIDs.add(res.getInt(1));
}
} catch (Exception e) {
log.error("Error fetching starIDs to simulate.", e);
}
}

if (starIDs.isEmpty()) {
return 0;
}
return starIDs.remove();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,12 @@ private void populateOldestStar(JsonObject json) {

if (lastSimulation != null) {
sql = "SELECT COUNT(*) FROM stars"
+ " WHERE empire_count > 0";
+ " WHERE empire_count > 0 AND last_simulation < ?";
try (SqlStmt stmt = DB.prepare(sql)) {
stmt.setDateTime(1, DateTime.now().minusHours(3));
SqlResult res = stmt.select();
while (res.next()) {
json.addProperty("num_stars", res.getInt(1));
json.addProperty("num_stars_older_than_3_hrs", res.getInt(1));
}
} catch (Exception e) {
log.error("Error fetching number of stars to simulate.", e);
Expand Down

0 comments on commit de08193

Please sign in to comment.