Skip to content

Commit 0b928fb

Browse files
committed
Move region-finding process into a separate render-task to be able to handle huge maps better. Closes: #583
1 parent 61612dd commit 0b928fb

File tree

11 files changed

+276
-182
lines changed

11 files changed

+276
-182
lines changed

common/src/main/java/de/bluecolored/bluemap/common/api/BlueMapMapImpl.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import de.bluecolored.bluemap.api.BlueMapWorld;
3131
import de.bluecolored.bluemap.api.markers.MarkerSet;
3232
import de.bluecolored.bluemap.common.plugin.Plugin;
33+
import de.bluecolored.bluemap.common.rendermanager.MapUpdatePreparationTask;
3334
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
3435
import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask;
3536
import de.bluecolored.bluemap.core.map.BmMap;
@@ -117,7 +118,8 @@ private synchronized void unfreeze() {
117118
BmMap map = unpack(this.map);
118119
plugin.startWatchingMap(map);
119120
plugin.getPluginState().getMapState(map).setUpdateEnabled(true);
120-
plugin.getRenderManager().scheduleRenderTask(new MapUpdateTask(map));
121+
plugin.getRenderManager().scheduleRenderTaskNext(MapUpdatePreparationTask
122+
.updateMap(map, plugin.getRenderManager()));
121123
}
122124

123125
private synchronized void freeze() {

common/src/main/java/de/bluecolored/bluemap/common/api/RenderManagerImpl.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import de.bluecolored.bluemap.api.RenderManager;
3030
import de.bluecolored.bluemap.common.plugin.Plugin;
3131
import de.bluecolored.bluemap.common.rendermanager.MapPurgeTask;
32+
import de.bluecolored.bluemap.common.rendermanager.MapUpdatePreparationTask;
3233
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
3334
import de.bluecolored.bluemap.common.rendermanager.TileUpdateStrategy;
3435

@@ -49,7 +50,8 @@ public RenderManagerImpl(BlueMapAPIImpl api, Plugin plugin) {
4950
@Override
5051
public boolean scheduleMapUpdateTask(BlueMapMap map, boolean force) {
5152
BlueMapMapImpl cmap = castMap(map);
52-
return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.map(), TileUpdateStrategy.fixed(force)));
53+
return renderManager.scheduleRenderTask(MapUpdatePreparationTask
54+
.updateMap(cmap.map(), TileUpdateStrategy.fixed(force), renderManager));
5355
}
5456

5557
@Override

common/src/main/java/de/bluecolored/bluemap/common/commands/commands/PurgeCommand.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@
2828
import de.bluecolored.bluecommands.annotations.Command;
2929
import de.bluecolored.bluemap.common.commands.Permission;
3030
import de.bluecolored.bluemap.common.plugin.Plugin;
31-
import de.bluecolored.bluemap.common.rendermanager.MapPurgeTask;
32-
import de.bluecolored.bluemap.common.rendermanager.MapRenderTask;
33-
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
34-
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
31+
import de.bluecolored.bluemap.common.rendermanager.*;
3532
import de.bluecolored.bluemap.common.serverinterface.CommandSource;
3633
import de.bluecolored.bluemap.core.logger.Logger;
3734
import de.bluecolored.bluemap.core.map.BmMap;
@@ -85,8 +82,8 @@ public boolean purge(CommandSource source, @Argument("map") BmMap map) {
8582
source.sendMessage(lines(lines));
8683

8784
if (updateMap) {
88-
RenderTask updateTask = new MapUpdateTask(map);
89-
plugin.getRenderManager().scheduleRenderTask(updateTask);
85+
plugin.getRenderManager().scheduleRenderTask(MapUpdatePreparationTask
86+
.updateMap(map, plugin.getRenderManager()));
9087
}
9188

9289
return true;

common/src/main/java/de/bluecolored/bluemap/common/commands/commands/TasksCommand.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ public Component taskList() {
6262
.sorted(Map.Entry.comparingByValue())
6363
.map(entry -> format("% % %",
6464
ICON_UPDATED,
65-
entry.getKey().getDescription(),
66-
text("(done)").decorate(TextDecoration.ITALIC)
65+
text("(done)").decorate(TextDecoration.ITALIC),
66+
entry.getKey().getDescription()
6767
).color(BASE_COLOR))
6868
.forEach(tasks::add);
6969

common/src/main/java/de/bluecolored/bluemap/common/commands/commands/UnfreezeCommand.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import de.bluecolored.bluecommands.annotations.Command;
2929
import de.bluecolored.bluemap.common.commands.Permission;
3030
import de.bluecolored.bluemap.common.plugin.Plugin;
31-
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
31+
import de.bluecolored.bluemap.common.rendermanager.MapUpdatePreparationTask;
3232
import de.bluecolored.bluemap.common.serverinterface.CommandSource;
3333
import de.bluecolored.bluemap.core.map.BmMap;
3434
import lombok.RequiredArgsConstructor;
@@ -51,7 +51,8 @@ public void unfreeze(CommandSource source, @Argument("map") BmMap map) {
5151
formatMap(map).color(HIGHLIGHT_COLOR),
5252
text("frozen").color(FROZEN_COLOR)
5353
).color(BASE_COLOR));
54-
plugin.getRenderManager().scheduleRenderTask(new MapUpdateTask(map));
54+
plugin.getRenderManager().scheduleRenderTask(MapUpdatePreparationTask
55+
.updateMap(map, plugin.getRenderManager()));
5556
plugin.save();
5657
}
5758

common/src/main/java/de/bluecolored/bluemap/common/commands/commands/UpdateCommand.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import de.bluecolored.bluemap.common.commands.WithPosition;
3333
import de.bluecolored.bluemap.common.commands.WithWorld;
3434
import de.bluecolored.bluemap.common.plugin.Plugin;
35+
import de.bluecolored.bluemap.common.rendermanager.MapUpdatePreparationTask;
3536
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
37+
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
3638
import de.bluecolored.bluemap.common.rendermanager.TileUpdateStrategy;
3739
import de.bluecolored.bluemap.common.serverinterface.CommandSource;
3840
import de.bluecolored.bluemap.core.map.BmMap;
@@ -157,12 +159,18 @@ private boolean update(
157159
) throws IOException {
158160
source.sendMessage(text("Creating update-tasks ...").color(INFO_COLOR));
159161
plugin.flushWorldUpdates(world);
160-
for (BmMap map : maps) {
161-
plugin.getRenderManager().scheduleRenderTask(new MapUpdateTask(map, center, radius, updateStrategy));
162-
source.sendMessage(format("Created new update-task for map %",
163-
formatMap(map).color(HIGHLIGHT_COLOR)
164-
).color(POSITIVE_COLOR));
165-
}
162+
plugin.getRenderManager().scheduleRenderTasksNext(maps.stream()
163+
.map(map -> MapUpdatePreparationTask.builder()
164+
.map(map)
165+
.center(center)
166+
.radius(radius)
167+
.force(updateStrategy)
168+
.taskConsumer(plugin.getRenderManager()::scheduleRenderTask)
169+
.build())
170+
.peek(task -> source.sendMessage(format("Created new update-task for map %",
171+
formatMap(task.getMap()).color(HIGHLIGHT_COLOR)
172+
).color(POSITIVE_COLOR)))
173+
.toArray(RenderTask[]::new));
166174
source.sendMessage(format("Use % to see the progress",
167175
command("/bluemap").color(HIGHLIGHT_COLOR)
168176
).color(BASE_COLOR));

common/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@
3434
import de.bluecolored.bluemap.common.debug.StateDumper;
3535
import de.bluecolored.bluemap.common.live.LivePlayersDataSupplier;
3636
import de.bluecolored.bluemap.common.plugin.skins.PlayerSkinUpdater;
37-
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
37+
import de.bluecolored.bluemap.common.rendermanager.MapUpdatePreparationTask;
3838
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
39+
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
3940
import de.bluecolored.bluemap.common.serverinterface.Server;
4041
import de.bluecolored.bluemap.common.serverinterface.ServerEventListener;
4142
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
@@ -327,13 +328,11 @@ public void run() {
327328
TimerTask updateAllMapsTask = new TimerTask() {
328329
@Override
329330
public void run() {
330-
maps.values().stream()
331+
renderManager.scheduleRenderTasksNext(maps.values().stream()
332+
.filter(map -> pluginState.getMapState(map).isUpdateEnabled())
331333
.sorted(Comparator.comparing(bmMap -> bmMap.getMapSettings().getSorting()))
332-
.forEach(map -> {
333-
if (pluginState.getMapState(map).isUpdateEnabled()) {
334-
renderManager.scheduleRenderTask(new MapUpdateTask(map));
335-
}
336-
});
334+
.map(map -> MapUpdatePreparationTask.updateMap(map, renderManager))
335+
.toArray(RenderTask[]::new));
337336
}
338337
};
339338
daemonTimer.scheduleAtFixedRate(updateAllMapsTask, 0, fullUpdateTime);
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* This file is part of BlueMap, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package de.bluecolored.bluemap.common.rendermanager;
26+
27+
import com.flowpowered.math.vector.Vector2i;
28+
import de.bluecolored.bluemap.core.logger.Logger;
29+
import de.bluecolored.bluemap.core.map.BmMap;
30+
import de.bluecolored.bluemap.core.map.renderstate.MapTileState;
31+
import de.bluecolored.bluemap.core.map.renderstate.TileInfoRegion;
32+
import de.bluecolored.bluemap.core.map.renderstate.TileState;
33+
import de.bluecolored.bluemap.core.storage.GridStorage;
34+
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
35+
import de.bluecolored.bluemap.core.util.Grid;
36+
import de.bluecolored.bluemap.core.world.World;
37+
import lombok.Builder;
38+
import lombok.Getter;
39+
import lombok.NonNull;
40+
import org.jetbrains.annotations.Nullable;
41+
42+
import java.io.IOException;
43+
import java.util.ArrayList;
44+
import java.util.Collection;
45+
import java.util.HashSet;
46+
import java.util.Set;
47+
import java.util.function.Consumer;
48+
import java.util.function.Predicate;
49+
import java.util.stream.Stream;
50+
51+
public class MapUpdatePreparationTask implements MapRenderTask {
52+
53+
@Getter private final BmMap map;
54+
private final @Nullable Vector2i center;
55+
private final @Nullable Integer radius;
56+
private final TileUpdateStrategy force;
57+
private final Consumer<MapUpdateTask> taskConsumer;
58+
59+
private volatile boolean hasMoreWork, cancelled;
60+
61+
@Builder
62+
protected MapUpdatePreparationTask(
63+
@NonNull BmMap map,
64+
@Nullable Vector2i center,
65+
@Nullable Integer radius,
66+
TileUpdateStrategy force,
67+
@NonNull Consumer<MapUpdateTask> taskConsumer
68+
) {
69+
this.map = map;
70+
this.center = center;
71+
this.radius = radius;
72+
this.force = force != null ? force : TileUpdateStrategy.FORCE_NONE;
73+
this.taskConsumer = taskConsumer;
74+
this.hasMoreWork = true;
75+
}
76+
77+
@Override
78+
public void doWork() {
79+
synchronized (this) {
80+
if (!hasMoreWork) return;
81+
hasMoreWork = false;
82+
}
83+
if (cancelled) return;
84+
85+
// do work
86+
Collection<Vector2i> regions = findRegions();
87+
Collection<RenderTask> tasks = createTasks(regions);
88+
MapUpdateTask mapUpdateTask = new MapUpdateTask(map, tasks);
89+
90+
if (cancelled) return;
91+
92+
// return created task
93+
taskConsumer.accept(mapUpdateTask);
94+
}
95+
96+
@Override
97+
public boolean hasMoreWork() {
98+
return hasMoreWork && !cancelled;
99+
}
100+
101+
@Override
102+
public void cancel() {
103+
cancelled = true;
104+
}
105+
106+
@Override
107+
public String getDescription() {
108+
return "preparing map '%s' update".formatted(map.getId());
109+
}
110+
111+
private Collection<RenderTask> createTasks(Collection<Vector2i> regions) {
112+
ArrayList<WorldRegionRenderTask> regionTasks = new ArrayList<>(regions.size());
113+
regions.forEach(region -> regionTasks.add(new WorldRegionRenderTask(map, region, force)));
114+
115+
// get spawn region
116+
World world = map.getWorld();
117+
Vector2i spawnPoint = world.getSpawnPoint().toVector2(true);
118+
Grid regionGrid = world.getRegionGrid();
119+
Vector2i spawnRegion = regionGrid.getCell(spawnPoint);
120+
121+
// sort tasks by distance to the spawn region
122+
regionTasks.sort(WorldRegionRenderTask.defaultComparator(spawnRegion));
123+
124+
// save map before and after the whole update
125+
ArrayList<RenderTask> tasks = new ArrayList<>(regionTasks.size() + 2);
126+
tasks.add(new MapSaveTask(map));
127+
tasks.addAll(regionTasks);
128+
tasks.add(new MapSaveTask(map));
129+
130+
return tasks;
131+
}
132+
133+
private Collection<Vector2i> findRegions() {
134+
World world = map.getWorld();
135+
Grid regionGrid = world.getRegionGrid();
136+
137+
Predicate<Vector2i> regionBoundsFilter = map.getMapSettings().getCellRenderBoundariesFilter(regionGrid, true);
138+
Predicate<Vector2i> regionRadiusFilter;
139+
if (center == null || radius == null || radius < 0) {
140+
regionRadiusFilter = r -> true;
141+
} else {
142+
Vector2i halfCell = regionGrid.getGridSize().div(2);
143+
long increasedRadiusSquared = (long) Math.pow(radius + Math.ceil(halfCell.length()), 2);
144+
regionRadiusFilter = r -> {
145+
Vector2i min = regionGrid.getCellMin(r);
146+
Vector2i regionCenter = min.add(halfCell);
147+
return regionCenter.toLong().distanceSquared(center.toLong()) <= increasedRadiusSquared;
148+
};
149+
}
150+
151+
Set<Vector2i> regions = new HashSet<>();
152+
153+
// update all regions in the world-files
154+
world.listRegions().stream()
155+
.filter(regionBoundsFilter)
156+
.filter(regionRadiusFilter)
157+
.forEach(regions::add);
158+
159+
// also update regions that are present as map-tile-state files (they might have been rendered before but deleted now)
160+
// (a little hacky as we are operating on raw tile-state files -> maybe find a better way?)
161+
if (map.getMapSettings().isCheckForRemovedRegions()) {
162+
Grid tileGrid = map.getHiresModelManager().getTileGrid();
163+
Grid cellGrid = MapTileState.GRID.multiply(tileGrid);
164+
try (Stream<GridStorage.Cell> stream = map.getStorage().tileState().stream()) {
165+
stream
166+
.filter(c -> {
167+
// filter out files that are fully UNKNOWN/NOT_GENERATED
168+
// this avoids unnecessarily converting UNKNOWN tiles into NOT_GENERATED tiles on force-updates
169+
try (CompressedInputStream in = c.read()) {
170+
if (in == null) return false;
171+
TileState[] states = TileInfoRegion.loadPalette(in.decompress());
172+
for (TileState state : states) {
173+
if (
174+
state != TileState.UNKNOWN &&
175+
state != TileState.NOT_GENERATED
176+
) return true;
177+
}
178+
return false;
179+
} catch (IOException ignore) {
180+
return true;
181+
}
182+
})
183+
.map(c -> new Vector2i(c.getX(), c.getZ()))
184+
.flatMap(v -> cellGrid.getIntersecting(v, regionGrid).stream())
185+
.filter(regionRadiusFilter)
186+
.forEach(regions::add);
187+
} catch (IOException ex) {
188+
Logger.global.logError("Failed to load map tile state!", ex);
189+
}
190+
}
191+
192+
return regions;
193+
}
194+
195+
public static MapUpdatePreparationTask updateMap(BmMap map, RenderManager renderManager) {
196+
return builder()
197+
.map(map)
198+
.taskConsumer(renderManager::scheduleRenderTask)
199+
.build();
200+
}
201+
202+
public static MapUpdatePreparationTask updateMap(BmMap map, TileUpdateStrategy force, RenderManager renderManager) {
203+
return builder()
204+
.map(map)
205+
.force(force)
206+
.taskConsumer(renderManager::scheduleRenderTask)
207+
.build();
208+
}
209+
210+
}

0 commit comments

Comments
 (0)