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 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,105 +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());
try{
DB db = mongo.getDB("mar");
MongoClient mongo;
try {
mongo = new MongoClient("localhost", 27017);
int unloaded_worlds = 0;
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());
// 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);
}
}
DBCollection worlds = db.getCollection("world");
DBCollection users = db.getCollection("user");
DBCollection server = db.getCollection("server");
List<DBObject> userDocuments = new ArrayList<>();
int insertedUsers = 0;
ArrayList<User> users_ = new ArrayList<>(GameServer.INSTANCE.getGameUniverse().getUsers());
for (User u : users_) {
List<DBObject> worldDocuments = new ArrayList<>();
int perBatch = 35;
int insertedWorlds = 0;
ArrayList<World> worlds_ = new ArrayList<>(GameServer.INSTANCE.getGameUniverse().getWorlds());
for (World w : worlds_) {
worldDocuments.add(w.mongoSerialise());
insertedWorlds++;
insertedUsers++;
if (worldDocuments.size() >= perBatch || insertedWorlds >= GameServer.INSTANCE.getGameUniverse().getWorlds().size()) {
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_) {
if (!u.isGuest()) {
users.save(u.mongoSerialise());
}
insertedUsers++;
}
if (!u.isGuest()) {
userDocuments.add(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);
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);
mongo.close();
LogManager.LOGGER.info("Done!");
LogManager.LOGGER.info(""+insertedWorlds+" worlds saved, "+unloaded_worlds+" unloaded");
LogManager.LOGGER.info("Done!");
} catch (Exception e) {
LogManager.LOGGER.severe("Problem happened during save function");
e.printStackTrace();

View File

@ -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<World> worlds;
//private ArrayList<World> worlds;
private Hashtable<String,World> worlds;
private ArrayList<User> 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<String,World>(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<World> getWorlds() {
return worlds;
return new ArrayList<World>(worlds.values());
}
public ArrayList<User> getUsers() {

View File

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