Load war zone blocks in batches to prevent server hangs

War zone blocks are now loaded from the database at a configurable speed of blocks per tick. This prevents an entire server from hanging whenever a war zone is reset. The speed can be increased or decreased based on your server's performance.
This commit is contained in:
cmastudios 2013-10-06 13:31:56 -05:00
parent aacd93b960
commit 60e74eff99
7 changed files with 121 additions and 6 deletions

View File

@ -183,6 +183,7 @@ public class War extends JavaPlugin {
warConfig.put(WarConfig.MAXZONES, 12);
warConfig.put(WarConfig.PVPINZONESONLY, false);
warConfig.put(WarConfig.TNTINZONESONLY, false);
warConfig.put(WarConfig.RESETSPEED, 5000);
warzoneDefaultConfig.put(WarzoneConfig.AUTOASSIGN, false);
warzoneDefaultConfig.put(WarzoneConfig.BLOCKHEADS, true);

View File

@ -1097,7 +1097,6 @@ public class Warzone {
public void reinitialize() {
this.isReinitializing = true;
this.getVolume().resetBlocksAsJob();
this.initializeZoneAsJob();
}
public void handlePlayerLeave(Player player, Location destination, PlayerMoveEvent event, boolean removeFromTeam) {

View File

@ -8,7 +8,8 @@ public enum WarConfig {
KEEPOLDZONEVERSIONS (Boolean.class),
MAXZONES (Integer.class),
PVPINZONESONLY (Boolean.class),
TNTINZONESONLY (Boolean.class);
TNTINZONESONLY (Boolean.class),
RESETSPEED (Integer.class);
private final Class<?> configType;

View File

@ -0,0 +1,85 @@
package com.tommytony.war.job;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.logging.Level;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.structure.ZoneLobby;
import com.tommytony.war.volume.ZoneVolume;
public class PartialZoneResetJob extends BukkitRunnable implements Cloneable {
private final Warzone zone;
private final ZoneVolume volume;
private final int speed;
private final int total;
private int completed = 0;
private final long startTime = System.currentTimeMillis();
private long messageCounter = System.currentTimeMillis();
public static final long MESSAGE_INTERVAL = 3000;
// Ticks between job runs
public static final int JOB_INTERVAL = 1;
/**
* Reset a warzone's blocks at a certain speed.
*
* @param volume
* Warzone to reset.
* @param speed
* Blocks to modify per #INTERVAL.
*/
public PartialZoneResetJob(Warzone zone, int speed) {
this.zone = zone;
this.volume = zone.getVolume();
this.speed = speed;
this.total = volume.size();
}
@Override
public void run() {
try {
volume.resetSection(completed, speed);
completed += speed;
if (completed < total) {
if (System.currentTimeMillis() - messageCounter > MESSAGE_INTERVAL) {
messageCounter = System.currentTimeMillis();
int percent = (int) (((double) completed / (double) total) * 100);
long seconds = (System.currentTimeMillis() - startTime) / 1000;
String message = MessageFormat.format(
War.war.getString("zone.battle.resetprogress"),
percent, seconds);
for (Player player : War.war.getServer().getOnlinePlayers()) {
ZoneLobby lobby = ZoneLobby.getLobbyByLocation(player);
if (zone.getPlayers().contains(player)
|| (lobby != null && lobby.getZone() == zone)) {
War.war.msg(player, message);
}
}
}
War.war.getServer().getScheduler()
.runTaskLater(War.war, this.clone(), JOB_INTERVAL);
} else {
zone.initializeZone();
War.war.getLogger().info(
"Finished reset cycle for warzone " + volume.getName());
}
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING,
"Failed to load zone during reset loop", e);
}
}
@Override
protected PartialZoneResetJob clone() {
try {
return (PartialZoneResetJob) super.clone();
} catch (CloneNotSupportedException e) {
throw new Error(e);
}
}
}

View File

@ -56,10 +56,12 @@ public class ZoneVolumeMapper {
* @param String zoneName Zone to load the volume from
* @param World world The world the zone is located
* @param boolean onlyLoadCorners Should only the corners be loaded
* @param start Starting position to load blocks at
* @param total Amount of blocks to read
* @return integer Changed blocks
* @throws SQLException Error communicating with SQLite3 database
*/
public static int load(ZoneVolume volume, String zoneName, World world, boolean onlyLoadCorners) throws SQLException {
public static int load(ZoneVolume volume, String zoneName, World world, boolean onlyLoadCorners, int start, int total) throws SQLException {
int changed = 0;
File databaseFile = new File(War.war.getDataFolder(), String.format("/dat/warzone-%s/volume-%s.sl3", zoneName, volume.getName()));
if (!databaseFile.exists()) {
@ -99,7 +101,7 @@ public class ZoneVolumeMapper {
databaseConnection.close();
return 0;
}
ResultSet query = stmt.executeQuery("SELECT * FROM blocks");
ResultSet query = stmt.executeQuery("SELECT * FROM blocks ORDER BY rowid LIMIT " + start + ", " + total);
while (query.next()) {
int x = query.getInt("x"), y = query.getInt("y"), z = query.getInt("z");
BlockState modify = corner1.getRelative(x, y, z).getState();

View File

@ -10,6 +10,8 @@ import org.bukkit.block.Block;
import com.tommytony.war.Team;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.config.WarConfig;
import com.tommytony.war.job.PartialZoneResetJob;
import com.tommytony.war.mapper.ZoneVolumeMapper;
import com.tommytony.war.structure.Monument;
@ -48,7 +50,7 @@ public class ZoneVolume extends Volume {
}
public void loadCorners() throws SQLException {
ZoneVolumeMapper.load(this, this.zone.getName(), this.getWorld(), true);
ZoneVolumeMapper.load(this, this.zone.getName(), this.getWorld(), true, 0, 0);
this.isSaved = true;
}
@ -57,7 +59,7 @@ public class ZoneVolume extends Volume {
// Load blocks directly from disk and onto the map (i.e. no more in-memory warzone blocks)
int reset = 0;
try {
reset = ZoneVolumeMapper.load(this, this.zone.getName(), this.getWorld(), false);
reset = ZoneVolumeMapper.load(this, this.zone.getName(), this.getWorld(), false, 0, Integer.MAX_VALUE);
} catch (SQLException ex) {
War.war.log("Failed to load warzone " + zone.getName() + ": " + ex.getMessage(), Level.WARNING);
ex.printStackTrace();
@ -66,6 +68,30 @@ public class ZoneVolume extends Volume {
this.isSaved = true;
}
/**
* Reset a section of blocks in the warzone.
*
* @param start
* Starting position for reset.
* @param total
* Amount of blocks to reset.
* @throws SQLException
*/
public void resetSection(int start, int total) throws SQLException {
ZoneVolumeMapper.load(this, this.zone.getName(), this.getWorld(), false, start, total);
}
@Override
/**
* Reset the blocks in this warzone at the speed defined in WarConfig#RESETSPEED.
* The job will automatically spawn new instances of itself to run every tick until it is done resetting all blocks.
*/
public void resetBlocksAsJob() {
PartialZoneResetJob job = new PartialZoneResetJob(zone, War.war
.getWarConfig().getInt(WarConfig.RESETSPEED));
War.war.getServer().getScheduler().runTask(War.war, job);
}
public void setNorthwest(Location block) throws NotNorthwestException, TooSmallException, TooBigException {
// northwest defaults to top block
Location topBlock = new Location(block.getWorld(), block.getX(), block.getWorld().getMaxHeight(), block.getZ());

View File

@ -92,6 +92,7 @@ zone.battle.end = The battle is over. Team {0} lost: {1} died and the
zone.battle.newscores = New scores - {0}
zone.battle.next = The battle was interrupted. Resetting warzone {0}...
zone.battle.reset = A new battle begins. Resetting warzone...
zone.battle.resetprogress = Reset progress: {0}%, {1} seconds
zone.bomb.broadcast = {0} blew up team {1}''s spawn. Team {2} scores one point.
zone.cake.broadcast = {0} captured cake {1}. Team {2} scores one point and gets a full lifepool.
zone.flagcapture.broadcast = {0} captured team {1}''s flag. Team {2} scores one point.