diff --git a/Server/src/main/java/net/simon987/server/GameServer.java b/Server/src/main/java/net/simon987/server/GameServer.java index bc41d72..fd297b4 100644 --- a/Server/src/main/java/net/simon987/server/GameServer.java +++ b/Server/src/main/java/net/simon987/server/GameServer.java @@ -36,11 +36,20 @@ public class GameServer implements Runnable { private DayNightCycle dayNightCycle; + private MongoClient mongo = null; + public GameServer() { this.config = new ServerConfiguration("config.properties"); + try{ + mongo = new MongoClient("localhost", 27017); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + gameUniverse = new GameUniverse(config); + gameUniverse.setMongo(mongo); pluginManager = new PluginManager(); maxExecutionTime = config.getInt("user_timeout"); @@ -169,106 +178,101 @@ public class GameServer implements Runnable { ") updated"); } + + void load() { - LogManager.LOGGER.info("Loading from MongoDB"); - MongoClient mongo; - try { - mongo = new MongoClient("localhost", 27017); + LogManager.LOGGER.info("Loading all data from MongoDB"); - DB db = mongo.getDB("mar"); + DB db = mongo.getDB("mar"); - DBCollection worlds = db.getCollection("world"); - DBCollection users = db.getCollection("user"); - DBCollection server = db.getCollection("server"); + DBCollection worlds = db.getCollection("world"); + DBCollection users = db.getCollection("user"); + DBCollection server = db.getCollection("server"); - //Load worlds - DBCursor cursor = worlds.find(); - while (cursor.hasNext()) { - GameServer.INSTANCE.getGameUniverse().getWorlds().add(World.deserialize(cursor.next())); - } - - //Load users - 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(); + BasicDBObject whereQuery = new BasicDBObject(); + whereQuery.put("shouldUpdate", true); + DBCursor cursor = worlds.find(whereQuery); + GameUniverse universe = GameServer.INSTANCE.getGameUniverse(); + while (cursor.hasNext()) { + World w = World.deserialize(cursor.next()); + universe.addWorld(w); } + + //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() { LogManager.LOGGER.info("Saving to MongoDB | W:" + gameUniverse.getWorlds().size() + " | U:" + gameUniverse.getUsers().size()); - MongoClient mongo; - try { - mongo = new MongoClient("localhost", 27017); + DB db = mongo.getDB("mar"); - DB db = mongo.getDB("mar"); + int unloaded_worlds = 0; - db.dropDatabase(); //Todo: Update database / keep history instead of overwriting + DBCollection worlds = db.getCollection("world"); + DBCollection users = db.getCollection("user"); + DBCollection server = db.getCollection("server"); - DBCollection worlds = db.getCollection("world"); - DBCollection users = db.getCollection("user"); - DBCollection server = db.getCollection("server"); - - List worldDocuments = new ArrayList<>(); - int perBatch = 35; - int insertedWorlds = 0; - ArrayList worlds_ = new ArrayList<>(GameServer.INSTANCE.getGameUniverse().getWorlds()); - for (World w : worlds_) { - worldDocuments.add(w.mongoSerialise()); - insertedWorlds++; - - if (worldDocuments.size() >= perBatch || insertedWorlds >= GameServer.INSTANCE.getGameUniverse().getWorlds().size()) { - worlds.insert(worldDocuments); - worldDocuments.clear(); - } - } - - List userDocuments = new ArrayList<>(); - int insertedUsers = 0; - ArrayList users_ = new ArrayList<>(GameServer.INSTANCE.getGameUniverse().getUsers()); - for (User u : users_) { - - insertedUsers++; - - if (!u.isGuest()) { - userDocuments.add(u.mongoSerialise()); - } - - if (userDocuments.size() >= perBatch || insertedUsers >= GameServer.INSTANCE.getGameUniverse().getUsers().size()) { - users.insert(userDocuments); - userDocuments.clear(); - } - } - - BasicDBObject serverObj = new BasicDBObject(); - serverObj.put("time", gameUniverse.getTime()); - serverObj.put("nextObjectId", gameUniverse.getNextObjectId()); - server.insert(serverObj); - - LogManager.LOGGER.info("Done!"); - } catch (UnknownHostException e) { - e.printStackTrace(); + List worldDocuments = new ArrayList<>(); + int perBatch = 35; + int insertedWorlds = 0; + GameUniverse universe = GameServer.INSTANCE.getGameUniverse(); + ArrayList worlds_ = new ArrayList<>(universe.getWorlds()); + for (World w : worlds_) { + LogManager.LOGGER.fine("Saving world "+w.getId()+" to mongodb"); + insertedWorlds++; + worlds.save(w.mongoSerialise()); + + // If the world should unload, it is removed from the Universe after having been saved. + if (w.shouldUnload()){ + unloaded_worlds++; + LogManager.LOGGER.fine("Unloading world "+w.getId()+" from universe"); + universe.removeWorld(w); + } } + + List userDocuments = new ArrayList<>(); + int insertedUsers = 0; + ArrayList users_ = new ArrayList<>(GameServer.INSTANCE.getGameUniverse().getUsers()); + for (User u : users_) { + + insertedUsers++; + + + if (!u.isGuest()) { + users.save(u.mongoSerialise()); + } + + } + + BasicDBObject serverObj = new BasicDBObject(); + serverObj.put("_id","serverinfo"); // a constant id ensures only one entry is kept and updated, instead of a new entry created every save. + serverObj.put("time", gameUniverse.getTime()); + serverObj.put("nextObjectId", gameUniverse.getNextObjectId()); + server.save(serverObj); + + LogManager.LOGGER.info(""+insertedWorlds+" worlds saved, "+unloaded_worlds+" unloaded"); + LogManager.LOGGER.info("Done!"); } public ServerConfiguration getConfig() { diff --git a/Server/src/main/java/net/simon987/server/game/GameUniverse.java b/Server/src/main/java/net/simon987/server/game/GameUniverse.java index 6ead02c..247acbe 100644 --- a/Server/src/main/java/net/simon987/server/game/GameUniverse.java +++ b/Server/src/main/java/net/simon987/server/game/GameUniverse.java @@ -1,5 +1,6 @@ package net.simon987.server.game; +import com.mongodb.*; import net.simon987.server.GameServer; import net.simon987.server.ServerConfiguration; 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.user.User; +import java.net.UnknownHostException; + import java.util.ArrayList; +import java.util.Hashtable; public class GameUniverse { - private ArrayList worlds; + //private ArrayList worlds; + private Hashtable worlds; private ArrayList users; private WorldGenerator worldGenerator; + private MongoClient mongo = null; + + private long time; private long nextObjectId = 0; @@ -25,63 +33,149 @@ public class GameUniverse { public GameUniverse(ServerConfiguration config) { - worlds = new ArrayList<>(32); + //worlds = new ArrayList<>(32); + worlds = new Hashtable(32); users = new ArrayList<>(16); worldGenerator = new WorldGenerator(config); + } + public void setMongo(MongoClient mongo){ + this.mongo = mongo; } public long getTime() { return time; } - public World getWorld(int x, int y, boolean createNew) { + /** + * 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"); - for (World world : worlds) { - if (world.getX() == x && world.getY() == y) { - return 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; } - - if (x >= 0 && x <= maxWidth && y >= 0 && y <= maxWidth) { - if (createNew) { - //World does not exist - World world = createWorld(x, y); - worlds.add(world); - - return world; - } else { - return null; - } - - } else { + else{ return null; } } - public World createWorld(int x, int y) { + /** + * 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) { + // Wrapping coordinates around cyclically + x %= maxWidth+1; + y %= maxWidth+1; + + // Looks for a previously loaded world + World world = getLoadedWorld(x,y); + if (world != null){ + return world; + } + + // Tries loading the world from the database + world = loadWorld(x,y); + if (world != null){ + addWorld(world); + LogManager.LOGGER.fine("Loaded world "+(World.idFromCoordinates(x,y))+" from mongodb."); + return world; + } + + // 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 { + return null; + } + } + + 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; try { world = worldGenerator.generateWorld(x, y); - - } catch (CancelledException e) { e.printStackTrace(); } - return world; } public User getUser(String username) { - for (User user : users) { if (user.getUsername().equals(username)) { return user; } } - return null; } @@ -142,7 +236,7 @@ public class GameUniverse { public GameObject getObject(long id) { // - for (World world : worlds) { + for (World world : getWorlds()) { for (GameObject object : world.getGameObjects()) { if (object.getObjectId() == id) { return object; @@ -160,7 +254,7 @@ public class GameUniverse { } public ArrayList getWorlds() { - return worlds; + return new ArrayList(worlds.values()); } public ArrayList getUsers() { diff --git a/Server/src/main/java/net/simon987/server/game/World.java b/Server/src/main/java/net/simon987/server/game/World.java index 2bcca90..6545e6b 100644 --- a/Server/src/main/java/net/simon987/server/game/World.java +++ b/Server/src/main/java/net/simon987/server/game/World.java @@ -6,9 +6,11 @@ import com.mongodb.DBObject; import net.simon987.server.GameServer; import net.simon987.server.event.GameEvent; import net.simon987.server.event.WorldUpdateEvent; +import net.simon987.server.game.GameUniverse; import net.simon987.server.game.pathfinding.Pathfinder; import net.simon987.server.io.MongoSerialisable; import org.json.simple.JSONObject; +import net.simon987.server.logging.LogManager; import java.awt.*; import java.util.ArrayList; @@ -57,7 +59,28 @@ public class World implements MongoSerialisable { public boolean isTileBlocked(int x, int y) { 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() { @@ -124,6 +147,9 @@ public class World implements MongoSerialisable { objects.add(obj.mongoSerialise()); } + + dbObject.put("_id", getId()); + dbObject.put("objects", objects); dbObject.put("terrain", tileMap.mongoSerialise()); @@ -131,7 +157,7 @@ public class World implements MongoSerialisable { dbObject.put("y", y); dbObject.put("updatable", updatable); - + dbObject.put("shouldUpdate",shouldUpdate()); return dbObject; } @@ -330,4 +356,73 @@ public class World implements MongoSerialisable { public boolean shouldUpdate() { return updatable > 0; } + + + private GameUniverse universe = null; + + public void setUniverse(GameUniverse universe){ + this.universe = universe; + } + + public ArrayList getNeighbouringLoadedWorlds(){ + ArrayList 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 getNeighbouringExistingWorlds(){ + ArrayList 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; + } + } diff --git a/Server/src/main/java/net/simon987/server/user/User.java b/Server/src/main/java/net/simon987/server/user/User.java index 55875ce..0da9acc 100755 --- a/Server/src/main/java/net/simon987/server/user/User.java +++ b/Server/src/main/java/net/simon987/server/user/User.java @@ -44,6 +44,7 @@ public class User implements MongoSerialisable { 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("code", userCode); dbObject.put("controlledUnit", controlledUnit.getObjectId());