Merge pull request #118 from sg495/master

Implemented selective loading/unloading of worlds.
This commit is contained in:
Simon Fortier 2018-01-09 20:35:41 -05:00 committed by GitHub
commit 59fd620e4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 302 additions and 107 deletions

View File

@ -36,11 +36,20 @@ public class GameServer implements Runnable {
private DayNightCycle dayNightCycle; private DayNightCycle dayNightCycle;
private MongoClient mongo = null;
public GameServer() { public GameServer() {
this.config = new ServerConfiguration("config.properties"); this.config = new ServerConfiguration("config.properties");
try{
mongo = new MongoClient("localhost", 27017);
} catch (UnknownHostException e) {
e.printStackTrace();
}
gameUniverse = new GameUniverse(config); gameUniverse = new GameUniverse(config);
gameUniverse.setMongo(mongo);
pluginManager = new PluginManager(); pluginManager = new PluginManager();
maxExecutionTime = config.getInt("user_timeout"); maxExecutionTime = config.getInt("user_timeout");
@ -169,105 +178,101 @@ public class GameServer implements Runnable {
") updated"); ") updated");
} }
void load() { void load() {
LogManager.LOGGER.info("Loading from MongoDB"); LogManager.LOGGER.info("Loading all data from MongoDB");
MongoClient mongo;
try {
mongo = new MongoClient("localhost", 27017);
DB db = mongo.getDB("mar"); DB db = mongo.getDB("mar");
DBCollection worlds = db.getCollection("world"); DBCollection worlds = db.getCollection("world");
DBCollection users = db.getCollection("user"); DBCollection users = db.getCollection("user");
DBCollection server = db.getCollection("server"); DBCollection server = db.getCollection("server");
//Load worlds BasicDBObject whereQuery = new BasicDBObject();
DBCursor cursor = worlds.find(); whereQuery.put("shouldUpdate", true);
while (cursor.hasNext()) { DBCursor cursor = worlds.find(whereQuery);
GameServer.INSTANCE.getGameUniverse().getWorlds().add(World.deserialize(cursor.next())); GameUniverse universe = GameServer.INSTANCE.getGameUniverse();
} while (cursor.hasNext()) {
World w = World.deserialize(cursor.next());
//Load users universe.addWorld(w);
cursor = users.find();
while (cursor.hasNext()) {
try {
GameServer.INSTANCE.getGameUniverse().getUsers().add(User.deserialize(cursor.next()));
} catch (CancelledException e) {
e.printStackTrace();
}
}
//Load misc server info
cursor = server.find();
if (cursor.hasNext()) {
DBObject serverObj = cursor.next();
gameUniverse.setTime((long) serverObj.get("time"));
gameUniverse.setNextObjectId((long) serverObj.get("nextObjectId"));
}
LogManager.LOGGER.info("Done loading! W:" + GameServer.INSTANCE.getGameUniverse().getWorlds().size() +
" | U:" + GameServer.INSTANCE.getGameUniverse().getUsers().size());
} catch (UnknownHostException e) {
e.printStackTrace();
} }
//Load users
cursor = users.find();
while (cursor.hasNext()) {
try {
universe.getUsers().add(User.deserialize(cursor.next()));
} catch (CancelledException e) {
e.printStackTrace();
}
}
//Load misc server info
cursor = server.find();
if (cursor.hasNext()) {
DBObject serverObj = cursor.next();
gameUniverse.setTime((long) serverObj.get("time"));
gameUniverse.setNextObjectId((long) serverObj.get("nextObjectId"));
}
LogManager.LOGGER.info("Done loading! W:" + GameServer.INSTANCE.getGameUniverse().getWorlds().size() +
" | U:" + GameServer.INSTANCE.getGameUniverse().getUsers().size());
} }
private void save() { private void save() {
LogManager.LOGGER.info("Saving to MongoDB | W:" + gameUniverse.getWorlds().size() + " | U:" + gameUniverse.getUsers().size()); LogManager.LOGGER.info("Saving to MongoDB | W:" + gameUniverse.getWorlds().size() + " | U:" + gameUniverse.getUsers().size());
try{
DB db = mongo.getDB("mar");
MongoClient mongo; int unloaded_worlds = 0;
try {
mongo = new MongoClient("localhost", 27017);
DB db = mongo.getDB("mar"); DBCollection worlds = db.getCollection("world");
DBCollection users = db.getCollection("user");
DBCollection server = db.getCollection("server");
db.dropDatabase(); //Todo: Update database / keep history instead of overwriting List<DBObject> worldDocuments = new ArrayList<>();
int perBatch = 35;
int insertedWorlds = 0;
GameUniverse universe = GameServer.INSTANCE.getGameUniverse();
ArrayList<World> worlds_ = new ArrayList<>(universe.getWorlds());
for (World w : worlds_) {
LogManager.LOGGER.fine("Saving world "+w.getId()+" to mongodb");
insertedWorlds++;
worlds.save(w.mongoSerialise());
DBCollection worlds = db.getCollection("world"); // If the world should unload, it is removed from the Universe after having been saved.
DBCollection users = db.getCollection("user"); if (w.shouldUnload()){
DBCollection server = db.getCollection("server"); unloaded_worlds++;
LogManager.LOGGER.fine("Unloading world "+w.getId()+" from universe");
universe.removeWorld(w);
}
}
List<DBObject> worldDocuments = new ArrayList<>(); List<DBObject> userDocuments = new ArrayList<>();
int perBatch = 35; int insertedUsers = 0;
int insertedWorlds = 0; ArrayList<User> users_ = new ArrayList<>(GameServer.INSTANCE.getGameUniverse().getUsers());
ArrayList<World> worlds_ = new ArrayList<>(GameServer.INSTANCE.getGameUniverse().getWorlds()); for (User u : users_) {
for (World w : worlds_) {
worldDocuments.add(w.mongoSerialise());
insertedWorlds++;
if (worldDocuments.size() >= perBatch || insertedWorlds >= GameServer.INSTANCE.getGameUniverse().getWorlds().size()) { insertedUsers++;
worlds.insert(worldDocuments);
worldDocuments.clear();
}
}
List<DBObject> userDocuments = new ArrayList<>();
int insertedUsers = 0;
ArrayList<User> users_ = new ArrayList<>(GameServer.INSTANCE.getGameUniverse().getUsers());
for (User u : users_) {
insertedUsers++; if (!u.isGuest()) {
users.save(u.mongoSerialise());
}
if (!u.isGuest()) { }
userDocuments.add(u.mongoSerialise());
}
if (userDocuments.size() >= perBatch || insertedUsers >= GameServer.INSTANCE.getGameUniverse().getUsers().size()) { BasicDBObject serverObj = new BasicDBObject();
users.insert(userDocuments); serverObj.put("_id","serverinfo"); // a constant id ensures only one entry is kept and updated, instead of a new entry created every save.
userDocuments.clear(); serverObj.put("time", gameUniverse.getTime());
} serverObj.put("nextObjectId", gameUniverse.getNextObjectId());
} server.save(serverObj);
BasicDBObject serverObj = new BasicDBObject(); LogManager.LOGGER.info(""+insertedWorlds+" worlds saved, "+unloaded_worlds+" unloaded");
serverObj.put("time", gameUniverse.getTime()); LogManager.LOGGER.info("Done!");
serverObj.put("nextObjectId", gameUniverse.getNextObjectId());
server.insert(serverObj);
mongo.close();
LogManager.LOGGER.info("Done!");
} catch (Exception e) { } catch (Exception e) {
LogManager.LOGGER.severe("Problem happened during save function"); LogManager.LOGGER.severe("Problem happened during save function");
e.printStackTrace(); e.printStackTrace();

View File

@ -1,5 +1,6 @@
package net.simon987.server.game; package net.simon987.server.game;
import com.mongodb.*;
import net.simon987.server.GameServer; import net.simon987.server.GameServer;
import net.simon987.server.ServerConfiguration; import net.simon987.server.ServerConfiguration;
import net.simon987.server.assembly.Assembler; import net.simon987.server.assembly.Assembler;
@ -9,14 +10,21 @@ import net.simon987.server.assembly.exception.CancelledException;
import net.simon987.server.logging.LogManager; import net.simon987.server.logging.LogManager;
import net.simon987.server.user.User; import net.simon987.server.user.User;
import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Hashtable;
public class GameUniverse { public class GameUniverse {
private ArrayList<World> worlds; //private ArrayList<World> worlds;
private Hashtable<String,World> worlds;
private ArrayList<User> users; private ArrayList<User> users;
private WorldGenerator worldGenerator; private WorldGenerator worldGenerator;
private MongoClient mongo = null;
private long time; private long time;
private long nextObjectId = 0; private long nextObjectId = 0;
@ -25,63 +33,149 @@ public class GameUniverse {
public GameUniverse(ServerConfiguration config) { public GameUniverse(ServerConfiguration config) {
worlds = new ArrayList<>(32); //worlds = new ArrayList<>(32);
worlds = new Hashtable<String,World>(32);
users = new ArrayList<>(16); users = new ArrayList<>(16);
worldGenerator = new WorldGenerator(config); worldGenerator = new WorldGenerator(config);
}
public void setMongo(MongoClient mongo){
this.mongo = mongo;
} }
public long getTime() { public long getTime() {
return time; return time;
} }
/**
* Attempts loading a world from mongoDB by coordinates
*
* @param x the x coordinate of the world
* @param y the y coordinate of the world
*
* @return World, null if not found
*/
private World loadWorld(int x, int y){
DB db = mongo.getDB("mar");
DBCollection worlds = db.getCollection("world");
BasicDBObject whereQuery = new BasicDBObject();
whereQuery.put("_id", World.idFromCoordinates(x,y));
DBCursor cursor = worlds.find(whereQuery);
if (cursor.hasNext()) {
World w = World.deserialize(cursor.next());
return w;
}
else{
return null;
}
}
/**
* Get a world by coordinates, attempts to load from mongoDB if not found.
*
* @param x the x coordinate of the world
* @param y the y coordinate of the world
* @param createNew if true, a new world is created when a world with given coordinates is not found
*
* @return World, null if not found and not created.
*/
public World getWorld(int x, int y, boolean createNew) { public World getWorld(int x, int y, boolean createNew) {
for (World world : worlds) { // Wrapping coordinates around cyclically
if (world.getX() == x && world.getY() == y) { x %= maxWidth+1;
return world; y %= maxWidth+1;
}
// Looks for a previously loaded world
World world = getLoadedWorld(x,y);
if (world != null){
return world;
} }
if (x >= 0 && x <= maxWidth && y >= 0 && y <= maxWidth) { // Tries loading the world from the database
if (createNew) { world = loadWorld(x,y);
//World does not exist if (world != null){
World world = createWorld(x, y); addWorld(world);
worlds.add(world); LogManager.LOGGER.fine("Loaded world "+(World.idFromCoordinates(x,y))+" from mongodb.");
return world;
return world; }
} else {
return null;
}
// World does not exist
if (createNew) {
// Creates a new world
world = createWorld(x, y);
addWorld(world);
LogManager.LOGGER.fine("Created new world "+(World.idFromCoordinates(x,y))+".");
return world;
} else { } else {
return null; return null;
} }
} }
public World createWorld(int x, int y) { public World getLoadedWorld(int x, int y) {
// Wrapping coordinates around cyclically
x %= maxWidth+1;
y %= maxWidth+1;
return worlds.get(World.idFromCoordinates(x,y));
}
/**
* Adds a new or freshly loaded world to the universe (if not already present).
*
* @param world the world to be added
*/
public void addWorld(World world){
World w = worlds.get(world.getId());
if (w == null){
world.setUniverse(this);
worlds.put(world.getId(),world);
}
}
/**
* Removes the world with given coordinates from the universe.
*
* @param x the x coordinate of the world to be removed
* @param y the y coordinate of the world to be removed
*/
public void removeWorld(int x, int y){
World w = worlds.remove(World.idFromCoordinates(x,y));
if (w != null){
w.setUniverse(null);
}
}
/**
* Removes the given world from the universe.
*
* @param world the world to be removed.
*/
public void removeWorld(World world){
World w = worlds.remove(world.getId());
if (w != null){
w.setUniverse(null);
}
}
public World createWorld(int x, int y) {
World world = null; World world = null;
try { try {
world = worldGenerator.generateWorld(x, y); world = worldGenerator.generateWorld(x, y);
} catch (CancelledException e) { } catch (CancelledException e) {
e.printStackTrace(); e.printStackTrace();
} }
return world; return world;
} }
public User getUser(String username) { public User getUser(String username) {
for (User user : users) { for (User user : users) {
if (user.getUsername().equals(username)) { if (user.getUsername().equals(username)) {
return user; return user;
} }
} }
return null; return null;
} }
@ -142,7 +236,7 @@ public class GameUniverse {
public GameObject getObject(long id) { public GameObject getObject(long id) {
// //
for (World world : worlds) { for (World world : getWorlds()) {
for (GameObject object : world.getGameObjects()) { for (GameObject object : world.getGameObjects()) {
if (object.getObjectId() == id) { if (object.getObjectId() == id) {
return object; return object;
@ -160,7 +254,7 @@ public class GameUniverse {
} }
public ArrayList<World> getWorlds() { public ArrayList<World> getWorlds() {
return worlds; return new ArrayList<World>(worlds.values());
} }
public ArrayList<User> getUsers() { public ArrayList<User> getUsers() {

View File

@ -6,9 +6,11 @@ import com.mongodb.DBObject;
import net.simon987.server.GameServer; import net.simon987.server.GameServer;
import net.simon987.server.event.GameEvent; import net.simon987.server.event.GameEvent;
import net.simon987.server.event.WorldUpdateEvent; import net.simon987.server.event.WorldUpdateEvent;
import net.simon987.server.game.GameUniverse;
import net.simon987.server.game.pathfinding.Pathfinder; import net.simon987.server.game.pathfinding.Pathfinder;
import net.simon987.server.io.MongoSerialisable; import net.simon987.server.io.MongoSerialisable;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import net.simon987.server.logging.LogManager;
import java.awt.*; import java.awt.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -57,7 +59,28 @@ public class World implements MongoSerialisable {
public boolean isTileBlocked(int x, int y) { public boolean isTileBlocked(int x, int y) {
return getGameObjectsBlockingAt(x, y).size() > 0 || tileMap.getTileAt(x, y) == TileMap.WALL_TILE; return getGameObjectsBlockingAt(x, y).size() > 0 || tileMap.getTileAt(x, y) == TileMap.WALL_TILE;
}
/**
* Computes the world's unique id from its coordinates.
*
* @param x the x coordinate of the world
* @param y the y coordinate of the world
*
* @return long
*/
public static String idFromCoordinates(int x, int y){
return "w-"+"0x"+Integer.toHexString(x)+"-"+"0x"+Integer.toHexString(y);
//return ((long)x)*(((long)maxWidth)+1)+((long)y);
}
/**
* Returns the world's unique id, computed with idFromCoordinates.
*
* @return long
*/
public String getId(){
return World.idFromCoordinates(x,y);
} }
public int getX() { public int getX() {
@ -124,6 +147,9 @@ public class World implements MongoSerialisable {
objects.add(obj.mongoSerialise()); objects.add(obj.mongoSerialise());
} }
dbObject.put("_id", getId());
dbObject.put("objects", objects); dbObject.put("objects", objects);
dbObject.put("terrain", tileMap.mongoSerialise()); dbObject.put("terrain", tileMap.mongoSerialise());
@ -131,7 +157,7 @@ public class World implements MongoSerialisable {
dbObject.put("y", y); dbObject.put("y", y);
dbObject.put("updatable", updatable); dbObject.put("updatable", updatable);
dbObject.put("shouldUpdate",shouldUpdate());
return dbObject; return dbObject;
} }
@ -330,4 +356,73 @@ public class World implements MongoSerialisable {
public boolean shouldUpdate() { public boolean shouldUpdate() {
return updatable > 0; return updatable > 0;
} }
private GameUniverse universe = null;
public void setUniverse(GameUniverse universe){
this.universe = universe;
}
public ArrayList<World> getNeighbouringLoadedWorlds(){
ArrayList<World> neighbouringWorlds = new ArrayList<>();
if (universe == null){
return neighbouringWorlds;
}
for (int dx=-1; dx<=+1; dx+=2){
World nw = universe.getLoadedWorld(x+dx,y);
if (nw != null){
neighbouringWorlds.add(nw);
}
}
for (int dy=-1; dy<=+1; dy+=2){
World nw = universe.getLoadedWorld(x,y+dy);
if (nw != null){
neighbouringWorlds.add(nw);
}
}
return neighbouringWorlds;
}
public ArrayList<World> getNeighbouringExistingWorlds(){
ArrayList<World> neighbouringWorlds = new ArrayList<>();
if (universe == null){
return neighbouringWorlds;
}
for (int dx=-1; dx<=+1; dx+=2){
World nw = universe.getWorld(x+dx,y,false);
if (nw != null){
neighbouringWorlds.add(nw);
}
}
for (int dy=-1; dy<=+1; dy+=2){
World nw = universe.getWorld(x,y+dy,false);
if (nw != null){
neighbouringWorlds.add(nw);
}
}
return neighbouringWorlds;
}
public boolean canUnload(){
return updatable==0;
}
public boolean shouldUnload(){
boolean res = canUnload();
for (World nw : getNeighbouringLoadedWorlds() ){
res &= nw.canUnload();
}
return res;
}
} }

View File

@ -44,6 +44,7 @@ public class User implements MongoSerialisable {
BasicDBObject dbObject = new BasicDBObject(); BasicDBObject dbObject = new BasicDBObject();
dbObject.put("_id", username); // a constant id ensures only one entry per user is kept and updated, instead of a new entry created every save for every user.
dbObject.put("username", username); dbObject.put("username", username);
dbObject.put("code", userCode); dbObject.put("code", userCode);
dbObject.put("controlledUnit", controlledUnit.getObjectId()); dbObject.put("controlledUnit", controlledUnit.getObjectId());