Skip to content

Commit 7f9f84e

Browse files
committed
Another initial commit. World pre-generation works, but is slow.
1 parent 95e916d commit 7f9f84e

File tree

3 files changed

+154
-21
lines changed

3 files changed

+154
-21
lines changed

src/main/java/org/mctourney/autoreferee/UHCMatch.java

+129-17
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package org.mctourney.autoreferee;
22

3-
import java.util.List;
43
import java.util.LinkedList;
54
import java.util.Collections;
65
import java.util.Comparator;
76

87
import org.bukkit.ChatColor;
98
import org.bukkit.Chunk;
9+
import org.bukkit.Location;
1010
import org.bukkit.World;
1111
import org.bukkit.entity.Player;
1212
import org.bukkit.command.CommandSender;
@@ -19,22 +19,47 @@
1919

2020
public class UHCMatch extends AutoRefMatch
2121
{
22-
// size of a default
22+
// size of a default UHC match in blocks
2323
public static final int DEFAULT_SIZE = 1024;
2424

25+
// convert players who are eliminated into spectators?
2526
protected boolean losersBecomeSpectators = false;
2627

28+
// is this world currently accepting players (preload complete?)
2729
protected boolean acceptingPlayers = false;
2830

31+
// match creator - receives updates on load progress
32+
private CommandSender creator = null;
33+
34+
// world region (all players share this region)
35+
private AutoRefRegion matchRegion = null;
36+
37+
// world generation task
2938
private WorldPregenerationTask worldgen = null;
3039

31-
public class WorldPregenerationTask extends BukkitRunnable
40+
/**
41+
* Reason for world pregeneration being paused.
42+
*/
43+
private enum PauseReason
44+
{
45+
MEMORY, USER;
46+
}
47+
48+
private class WorldPregenerationTask extends BukkitRunnable
3249
{
33-
private static final int CHUNKS_PER_STEP = 1;
50+
private static final int CHUNKS_PER_STEP = 4;
51+
52+
public static final int MINIMUM_MEMORY = 10 * 1024; // 10 kb
53+
54+
private static final int PRELOAD_CHUNK_RADIUS = 3;
55+
56+
private PauseReason pause = null;
3457

3558
private int totalChunks = 0;
3659

37-
public CommandSender recipient = null;
60+
public int speed = CHUNKS_PER_STEP;
61+
62+
public long startTime = 0L;
3863

3964
private LinkedList<Chunk> chunkQueue = Lists.newLinkedList();
4065

@@ -57,15 +82,19 @@ public WorldPregenerationTask(World w, int radius)
5782
{
5883
for (int x = -radius; x <= radius; ++x)
5984
for (int z = -radius; z <= radius; ++z)
60-
this.chunkQueue.add(w.getChunkAt(x, z));
85+
{
86+
Chunk chunk = w.getChunkAt(x, z);
87+
this.chunkQueue.add(chunk);
88+
}
6189

6290
this.totalChunks = this.chunkQueue.size();
6391

6492
// sort chunks on distance to spawn
6593
Collections.sort(chunkQueue, new ChunkSorter());
94+
this.startTime = System.currentTimeMillis();
6695
}
6796

68-
private WorldPregenerationTask(AutoRefRegion region)
97+
public WorldPregenerationTask(AutoRefRegion region)
6998
{
7099
CuboidRegion bound = region.getBoundingCuboid();
71100

@@ -77,28 +106,74 @@ private WorldPregenerationTask(AutoRefRegion region)
77106

78107
for (int x = bmin.getX(); x <= bmax.getX(); ++x)
79108
for (int z = bmin.getZ(); z <= bmax.getZ(); ++z)
80-
this.chunkQueue.add(w.getChunkAt(x, z));
109+
{
110+
Chunk chunk = w.getChunkAt(x, z);
111+
this.chunkQueue.add(chunk);
112+
}
81113

82114
this.totalChunks = this.chunkQueue.size();
83115

84116
// sort chunks on distance to spawn
85117
Collections.sort(chunkQueue, new ChunkSorter());
118+
this.startTime = System.currentTimeMillis();
86119
}
87120

121+
public void setPaused(boolean pause)
122+
{ this.pause = pause ? PauseReason.USER : null; }
123+
88124
@Override
89125
public void run()
90126
{
127+
// just quit immediately if paused by user
128+
if (pause == PauseReason.USER) return;
129+
130+
if (Runtime.getRuntime().freeMemory() < MINIMUM_MEMORY)
131+
{
132+
// notify the users that we are pausing the world generation
133+
if (pause != PauseReason.MEMORY) this.notify(
134+
ChatColor.DARK_GRAY + "Waiting for additional memory.");
135+
136+
pause = PauseReason.MEMORY;
137+
return;
138+
}
139+
140+
if (pause != null) this.notify(
141+
ChatColor.DARK_GRAY + "Resuming world generation...");
142+
pause = null;
143+
91144
// load a batch of chunks
145+
int prc_before = (this.totalChunks - this.chunkQueue.size()) * 100 / this.totalChunks;
92146
for (int i = CHUNKS_PER_STEP; i > 0 && this.chunkQueue.size() > 0; --i)
93-
this.chunkQueue.removeFirst().load(true);
147+
{
148+
// load and unload a chunk to force it to generate
149+
Chunk chunk = this.chunkQueue.removeFirst();
150+
chunk.load(true); chunk.unload();
151+
152+
if (!UHCMatch.this.acceptingPlayers)
153+
{
154+
int distance = Math.max(Math.abs(chunk.getX()), Math.abs(chunk.getZ()));
155+
if (this.chunkQueue.isEmpty()) UHCMatch.this.worldPreloadComplete();
156+
}
157+
}
158+
159+
float taken = (System.currentTimeMillis() - startTime) / 1000.0f;
160+
float workremaining = this.chunkQueue.size() / (float) this.totalChunks;
161+
int sec = (int) Math.floor(taken * workremaining / (1.0f - workremaining));
94162

95-
int percent = (this.totalChunks - this.chunkQueue.size()) * 100 / this.totalChunks;
96-
String update = String.format("%d%% chunks generated", percent);
97-
UHCMatch.this.broadcast(ChatColor.DARK_GRAY + update);
163+
int prc_after = (this.totalChunks - this.chunkQueue.size()) * 100 / this.totalChunks;
164+
String update = String.format("%d%% chunks generated (~%02d:%02d remaining)", prc_after, sec/60, sec%60);
165+
if (prc_after / 10 != prc_before / 10) this.notify(ChatColor.GRAY + update);
166+
}
167+
168+
private void notify(String message)
169+
{
170+
message += String.format(" [%d KB remaining]", Runtime.getRuntime().freeMemory());
171+
UHCMatch.this.broadcast(message);
98172

99-
if (this.recipient != null && (!(this.recipient instanceof Player)
100-
|| ((Player) this.recipient).getWorld() != UHCMatch.this.getWorld()))
101-
this.recipient.sendMessage(ChatColor.DARK_GRAY + update);
173+
// if the creator is not yet in this world, send update directly
174+
if (UHCMatch.this.creator instanceof Player &&
175+
((Player) UHCMatch.this.creator).getWorld() != UHCMatch.this.getWorld())
176+
UHCMatch.this.creator.sendMessage(message);
102177
}
103178
}
104179

@@ -112,8 +187,45 @@ public UHCMatch(World world, int size, boolean tmp)
112187
// chunks), then divide by 2 to convert diameter to radius.
113188
this.worldgen = new WorldPregenerationTask(world, size/32);
114189
this.worldgen.runTaskTimer(AutoRefereeUHC.getInstance(), 0L, 20L);
190+
191+
this.addStartRegion(new CuboidRegion(this.getWorld(),
192+
-20, 20, -20, 20, 0, this.getWorld().getMaxHeight()));
193+
this.setWorldSpawn(new Location(this.getWorld(),
194+
0, this.getWorld().getHighestBlockYAt(0, 0), 0));
195+
196+
this.addRegion(this.matchRegion = new CuboidRegion(this.getWorld(),
197+
-size/2, size/2, -size/2, size/2, 0, this.getWorld().getMaxHeight()));
198+
}
199+
200+
@Override
201+
protected void loadWorldConfiguration()
202+
{
115203
}
116204

117-
public void setNotificationRecipient(CommandSender recp)
118-
{ if (this.worldgen != null) this.worldgen.recipient = recp; }
205+
@Override
206+
public void saveWorldConfiguration()
207+
{
208+
}
209+
210+
public UHCMatch setCreator(CommandSender creator)
211+
{ this.creator = creator; return this; }
212+
213+
public UHCMatch setLoadSpeed(int speed)
214+
{ if (this.worldgen != null) this.worldgen.speed = speed; return this; }
215+
216+
public void pauseWorldGen()
217+
{ if (this.worldgen != null) this.worldgen.setPaused(true); }
218+
219+
public void unpauseWorldGen()
220+
{ if (this.worldgen != null) this.worldgen.setPaused(false); }
221+
222+
private void worldPreloadComplete()
223+
{
224+
if (this.creator instanceof Player)
225+
this.joinMatch((Player) this.creator);
226+
this.acceptingPlayers = true;
227+
228+
this.worldgen.cancel();
229+
this.worldgen = null;
230+
}
119231
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.mctourney.autoreferee;
2+
3+
import org.bukkit.entity.Player;
4+
5+
public class UHCPlayer extends AutoRefPlayer
6+
{
7+
public UHCPlayer(Player player)
8+
{
9+
super(player, null);
10+
}
11+
12+
public UHCPlayer(String name)
13+
{
14+
super(name, null);
15+
}
16+
}

src/main/java/org/mctourney/autoreferee/commands/UHCCommands.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public UHCCommands(Plugin plugin)
2626
this.plugin = (AutoReferee) plugin;
2727
}
2828

29-
@AutoRefCommand(name={"autoref", "uhc"}, argmax=0, options="s+g+z+",
29+
@AutoRefCommand(name={"autoref", "loaduhc"}, argmax=0, options="s+g+z+t+",
3030
description="Load a UHC match.")
3131
@AutoRefPermission(console=true, nodes={"autoreferee.admin"})
3232

@@ -57,9 +57,14 @@ public boolean loadUHC(CommandSender sender, AutoRefMatch match, String[] args,
5757

5858
// TODO Custom Generator
5959

60-
// TODO Move this to a sync thread
61-
match = new UHCMatch(Bukkit.createWorld(creator), size, true);
62-
plugin.addMatch(match);
60+
UHCMatch uhcMatch = new UHCMatch(Bukkit.createWorld(creator), size, true).setCreator(sender);
61+
62+
// specify number of chunks to load per step
63+
if (options.hasOption('t'))
64+
try { uhcMatch.setLoadSpeed(Integer.parseInt(options.getOptionValue('t'))); }
65+
catch (NumberFormatException e) { e.printStackTrace(); }
66+
67+
plugin.addMatch(uhcMatch);
6368
return true;
6469
}
6570
}

0 commit comments

Comments
 (0)