Skip to content

Commit 77a803f

Browse files
committed
Refactor World metadata into World.Info to avoid World object creation.
1 parent 78df071 commit 77a803f

14 files changed

Lines changed: 357 additions & 257 deletions

File tree

chunky/src/java/se/llbit/chunky/map/WorldMapLoader.java

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import se.llbit.chunky.renderer.ChunkViewListener;
2323
import se.llbit.chunky.ui.controller.ChunkyFxController;
2424
import se.llbit.chunky.world.*;
25+
import se.llbit.chunky.world.java.JavaWorldFormat;
2526
import se.llbit.chunky.world.region.RegionChangeWatcher;
2627
import se.llbit.chunky.world.region.RegionParser;
2728
import se.llbit.chunky.world.region.RegionQueue;
@@ -68,42 +69,73 @@ public WorldMapLoader(ChunkyFxController controller, MapView mapView) {
6869
topographyUpdater.start();
6970
}
7071

71-
public void loadWorldFromDirectory(@Nullable File worldLocation) {
72-
if (worldLocation == null) {
72+
public void loadWorldFromDirectory(@Nullable File worldLocation, @Nullable String worldFormatId) {
73+
if (worldLocation != null) {
74+
if (worldFormatId == null || worldFormatId.isEmpty()) {
75+
worldFormatId = JavaWorldFormat.ID;
76+
}
77+
Optional<World.Info> info = WorldFormats.getWorldFormat(worldFormatId)
78+
.flatMap(format -> format.getWorldInfo(worldLocation.toPath())) // attempt to get the given format
79+
.or(() -> WorldFormats.getInfos(worldLocation.toPath()).stream().findFirst()); // get any format
80+
info.ifPresent(this::loadWorld);
7381
return;
7482
}
75-
this.loadWorld(WorldFormats.createWorld(worldLocation).orElse(EmptyWorld.INSTANCE));
83+
setWorld(EmptyWorld.INSTANCE);
7684
}
85+
7786
/**
78-
* This is called when a new world is loaded
87+
* Load the world referred to by the {@link World.Info}
88+
*
89+
* @return The loaded world. May be {@link EmptyWorld} if loading failed.
7990
*/
80-
public void loadWorld(World newWorld) {
81-
if (this.world != null) {
82-
this.world.currentDimension().removeChunkTopographyListener(this);
83-
}
84-
boolean isSameWorld = !(this.world instanceof EmptyWorld) && newWorld.getWorldDirectory().equals(this.world.getWorldDirectory());
91+
public World loadWorld(World.Info info) {
92+
World newWorld = WorldFormats.createWorld(info);
8593

8694
Optional<Dimension.Identifier> dimensionToLoad = Optional.of(world.currentDimension())
8795
.map(Dimension::getDimensionId)
8896
.filter(dimension -> newWorld.getAvailableDimensions().contains(dimension))
8997
.or(newWorld::getDefaultDimension)
9098
.or(() -> newWorld.getAvailableDimensions().stream().findFirst());
9199

92-
if (dimensionToLoad.isEmpty()) {
93-
Log.infof("No dimension loaded for world %s", newWorld.toString());
94-
return;
100+
if (dimensionToLoad.isPresent()) {
101+
newWorld.loadDimension(dimensionToLoad.get());
102+
} else {
103+
Log.infof("No dimension loaded for world %s", info.toString());
104+
}
105+
106+
setWorld(newWorld);
107+
return this.world;
108+
}
109+
110+
/**
111+
* Sets the map view world.
112+
* <p>This is intended to be called with worlds with a dimension already loaded, as it will not trigger dimension
113+
* loading.</p>
114+
*
115+
* @param newWorld The world to set
116+
*/
117+
public void setWorld(World newWorld) {
118+
if (this.world != null) {
119+
this.world.currentDimension().removeChunkTopographyListener(this);
120+
}
121+
122+
boolean isSameWorld = !(this.world instanceof EmptyWorld) && newWorld.getInfo().path().equals(this.world.getInfo().path());
123+
124+
Dimension loadedDim = newWorld.currentDimension();
125+
if (loadedDim == EmptyDimension.INSTANCE) {
126+
Log.warn("Map view world was set but it has no dimension!");
95127
}
96128

97-
Dimension loadedDim = newWorld.loadDimension(dimensionToLoad.get());
98129
loadedDim.addChunkTopographyListener(this);
99130
synchronized (this) {
100131
this.world = newWorld;
101132
updateRegionChangeWatcher(loadedDim);
102133

103-
File newWorldDir = this.world.getWorldDirectory();
134+
File newWorldDir = this.world.getInfo().path().toFile();
104135
if (!newWorldDir.equals(PersistentSettings.getLastWorld())) {
105136
PersistentSettings.setLastWorld(newWorldDir);
106137
}
138+
PersistentSettings.setLastWorldFormat(newWorld.getInfo().worldFormat().getId());
107139
}
108140
worldLoadListeners.forEach(listener -> listener.accept(newWorld, isSameWorld));
109141
}

chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import se.llbit.chunky.world.biome.Biome;
5454
import se.llbit.chunky.world.biome.BiomePalette;
5555
import se.llbit.chunky.world.biome.Biomes;
56+
import se.llbit.chunky.world.java.JavaWorldFormat;
5657
import se.llbit.chunky.world.region.Region;
5758
import se.llbit.chunky.world.worldformat.WorldFormats;
5859
import se.llbit.json.*;
@@ -68,6 +69,7 @@
6869
import se.llbit.util.mojangapi.MinecraftProfile;
6970

7071
import java.io.*;
72+
import java.nio.file.Path;
7173
import java.text.SimpleDateFormat;
7274
import java.util.*;
7375
import java.util.concurrent.ExecutionException;
@@ -193,6 +195,7 @@ public class Scene implements JsonSerializable {
193195
*/
194196
protected int rayDepth = PersistentSettings.getRayDepthDefault();
195197
protected String worldPath = "";
198+
protected String worldFormat = JavaWorldFormat.ID;
196199
protected Dimension.Identifier worldDimension = Dimension.Identifier.OVERWORLD;
197200
protected RenderMode mode = RenderMode.PREVIEW;
198201
protected int dumpFrequency = DEFAULT_DUMP_FREQUENCY;
@@ -405,6 +408,7 @@ public synchronized void copyState(Scene other, boolean copyChunks) {
405408
if (copyChunks) {
406409
loadedWorld = other.loadedWorld;
407410
worldPath = other.worldPath;
411+
worldFormat = other.worldFormat;
408412
worldDimension = other.worldDimension;
409413

410414
// The octree reference is overwritten to save time.
@@ -547,14 +551,17 @@ public synchronized void loadScene(RenderContext context, String sceneName, Task
547551

548552
loadedWorld = EmptyWorld.INSTANCE;
549553
if (!worldPath.isEmpty()) {
550-
File worldDirectory = new File(worldPath);
551-
Optional<World> newWorld = WorldFormats.createWorld(worldDirectory);
552-
if (newWorld.isPresent()) {
553-
loadedWorld = newWorld.get();
554+
Path worldDirectory = Path.of(worldPath);
555+
Optional<World.Info> info = WorldFormats.getWorldFormat(worldFormat) // only try to load the world as its known world format.
556+
.flatMap(format -> format.getWorldInfo(worldDirectory));
557+
558+
if (info.isPresent()) {
559+
World newWorld = WorldFormats.createWorld(info.get());
560+
loadedWorld = newWorld;
554561
loadedWorld.loadDimension(this.worldDimension);
555-
} else {
556-
Log.info("Could not load world: " + worldPath);
557-
loadedWorld = EmptyWorld.INSTANCE;
562+
if (newWorld == EmptyWorld.INSTANCE) {
563+
Log.info("Could not load world: " + worldPath);
564+
}
558565
}
559566
}
560567

@@ -807,7 +814,8 @@ public synchronized void loadChunks(TaskTracker taskTracker, World world, Map<Re
807814
task.update(2, 1);
808815

809816
loadedWorld = world;
810-
worldPath = loadedWorld.getWorldDirectory().getAbsolutePath();
817+
worldPath = loadedWorld.getInfo().path().toAbsolutePath().toString();
818+
worldFormat = world.getInfo().worldFormat().getId();
811819
worldDimension = world.currentDimension().getDimensionId();
812820

813821
if (chunksToLoadByRegion.isEmpty()) {
@@ -2690,6 +2698,7 @@ public void setUseCustomWaterColor(boolean value) {
26902698
// Save world info.
26912699
JsonObject world = new JsonObject();
26922700
world.add("path", worldPath);
2701+
world.add("worldType", loadedWorld.getInfo().worldFormat().getId());
26932702
world.add("dimension", worldDimension.getNamespacedName());
26942703
json.add("world", world);
26952704
}
@@ -3012,7 +3021,7 @@ else if(waterShader.equals("SIMPLEX"))
30123021
if (json.get("world").isObject()) {
30133022
JsonObject world = json.get("world").object();
30143023
worldPath = world.get("path").stringValue(worldPath);
3015-
3024+
worldFormat = world.get("worldFormat").stringValue(JavaWorldFormat.ID);
30163025
if (world.get("dimension") instanceof JsonString) {
30173026
// dimension already is a string (or undefined)
30183027
worldDimension = Dimension.Identifier.fromNamespacedName(world.get("dimension").stringValue("minecraft:overworld"));

chunky/src/java/se/llbit/chunky/ui/controller/ChunkyFxController.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,10 @@
2323
import java.nio.file.Path;
2424
import java.text.DecimalFormat;
2525
import java.time.Duration;
26-
import java.util.ArrayList;
27-
import java.util.Collection;
28-
import java.util.IdentityHashMap;
29-
import java.util.Map;
30-
import java.util.Optional;
31-
import java.util.ResourceBundle;
26+
import java.util.*;
3227
import java.util.concurrent.CountDownLatch;
3328
import java.util.concurrent.atomic.AtomicBoolean;
3429

35-
import it.unimi.dsi.fastutil.ints.IntIntPair;
3630
import javafx.application.Platform;
3731
import javafx.beans.binding.Bindings;
3832
import javafx.beans.property.BooleanProperty;
@@ -287,7 +281,7 @@ public void exportMapView() {
287281
fileChooser.setTitle("Export PNG");
288282
fileChooser
289283
.getExtensionFilters().add(new FileChooser.ExtensionFilter("PNG image", "*.png"));
290-
mapLoader.withWorld(world -> fileChooser.setInitialFileName(world.levelName() + ".png"));
284+
mapLoader.withWorld(world -> fileChooser.setInitialFileName(world.getInfo().name() + ".png"));
291285
if (prevPngDir != null) {
292286
fileChooser.setInitialDirectory(prevPngDir.toFile());
293287
}
@@ -327,7 +321,7 @@ public void exportMapView() {
327321
World newWorld = scene.getWorld();
328322
World currentWorld = mapLoader.getWorld();
329323
boolean isSameWorld = currentWorld != EmptyWorld.INSTANCE &&
330-
currentWorld.getWorldDirectory().equals(newWorld.getWorldDirectory());
324+
currentWorld.getInfo().isSameWorld(newWorld.getInfo());
331325

332326
if (isSameWorld) {
333327
getChunkSelection().setSelection(chunky.getSceneManager().getScene().getChunks());
@@ -341,7 +335,7 @@ public void exportMapView() {
341335
"This scene shows a different world than the one that is currently loaded. Do you want to load the world of this scene?");
342336
Dialogs.stayOnTop(loadWorldConfirm);
343337
if (loadWorldConfirm.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.YES) {
344-
mapLoader.loadWorld(newWorld);
338+
mapLoader.setWorld(newWorld);
345339
getChunkSelection().setSelection(chunky.getSceneManager().getScene().getChunks());
346340
}
347341
}
@@ -473,7 +467,7 @@ public File getSceneFile(String fileName) {
473467
ignoreYUpdate.set(false);
474468
}
475469
map.redrawMap();
476-
mapName.setText(world.levelName());
470+
mapName.setText(world.getInfo().name());
477471
showWorldMap();
478472
});
479473
});
@@ -652,7 +646,7 @@ public File getSceneFile(String fileName) {
652646
mapOverlay.setOnKeyPressed(map::onKeyPressed);
653647
mapOverlay.setOnKeyReleased(map::onKeyReleased);
654648

655-
mapLoader.loadWorldFromDirectory(PersistentSettings.getLastWorld());
649+
mapLoader.loadWorldFromDirectory(PersistentSettings.getLastWorld(), PersistentSettings.getLastWorldFormat());
656650
HeightRange heightRange = mapLoader.getWorld().currentDimension().heightRange();
657651
mapView.setYMin(heightRange.min());
658652
mapView.setYMax(heightRange.max());

chunky/src/java/se/llbit/chunky/ui/controller/WorldChooserController.java

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,17 @@
4747
public class WorldChooserController implements Initializable {
4848
@FXML private Label statusLabel;
4949

50-
@FXML private TableView<World> worldTbl;
50+
@FXML private TableView<World.Info> worldTbl;
5151

52-
@FXML private TableColumn<World, String> worldNameCol;
52+
@FXML private TableColumn<World.Info, String> worldNameCol;
5353

54-
@FXML private TableColumn<World, String> worldDirCol;
54+
@FXML private TableColumn<World.Info, String> worldDirCol;
5555

56-
@FXML private TableColumn<World, String> gameModeCol;
56+
@FXML private TableColumn<World.Info, String> gameModeCol;
5757

58-
@FXML private TableColumn<World, Number> seedCol;
58+
@FXML private TableColumn<World.Info, Number> seedCol;
5959

60-
@FXML public TableColumn<World, Date> modifiedCol;
60+
@FXML public TableColumn<World.Info, Date> modifiedCol;
6161

6262
@FXML private Button changeWorldDirBtn;
6363

@@ -68,15 +68,15 @@ public class WorldChooserController implements Initializable {
6868

6969
@Override public void initialize(URL location, ResourceBundle resources) {
7070
worldNameCol
71-
.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().levelName()));
71+
.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().name()));
7272
worldDirCol.setCellValueFactory(
73-
data -> new ReadOnlyStringWrapper(data.getValue().getWorldDirectory().getName()));
73+
data -> new ReadOnlyStringWrapper(data.getValue().path().getFileName().toString()));
7474
gameModeCol.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().gameMode()));
75-
seedCol.setCellValueFactory(data -> new ReadOnlyLongWrapper(data.getValue().getSeed()));
75+
seedCol.setCellValueFactory(data -> new ReadOnlyLongWrapper(data.getValue().seed()));
7676

7777
DateFormat localeFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
78-
modifiedCol.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(data.getValue().getLastModified()));
79-
modifiedCol.setCellFactory(col -> new TableCell<World, Date>() {
78+
modifiedCol.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(new Date(data.getValue().lastModified())));
79+
modifiedCol.setCellFactory(_ -> new TableCell<>() {
8080
public void updateItem(Date item, boolean empty) {
8181
if (item == this.getItem()) return;
8282
super.updateItem(item, empty);
@@ -95,7 +95,7 @@ public void setStage(Stage stage) {
9595
*/
9696
public void populate(WorldMapLoader mapLoader) {
9797
worldTbl.setRowFactory(tbl -> {
98-
TableRow<World> row = new TableRow<>();
98+
TableRow<World.Info> row = new TableRow<>();
9999
row.setOnMouseClicked(e -> {
100100
if (e.getClickCount() == 2 && !row.isEmpty()) {
101101
this.loadWorld(row.getItem(), mapLoader);
@@ -134,7 +134,12 @@ public void populate(WorldMapLoader mapLoader) {
134134
File directory = chooser.showDialog(stage);
135135
if (directory != null) {
136136
if (directory.isDirectory()) {
137-
this.loadWorld(WorldFormats.createWorld(directory).orElse(EmptyWorld.INSTANCE), mapLoader);
137+
Optional<World.Info> info = WorldFormats.getInfos(directory.toPath()).stream().findFirst(); // TODO: could ask which world format to load as
138+
if (info.isPresent()) {
139+
this.loadWorld(info.get(), mapLoader);
140+
} else {
141+
mapLoader.setWorld(EmptyWorld.INSTANCE);
142+
}
138143
stage.close();
139144
} else {
140145
Log.warn("Non-directory selected.");
@@ -149,8 +154,9 @@ public void populate(WorldMapLoader mapLoader) {
149154
});
150155
}
151156

152-
private void loadWorld(World world, WorldMapLoader mapLoader) {
153-
world.getResourcePack()
157+
private void loadWorld(World.Info info, WorldMapLoader mapLoader) {
158+
mapLoader.loadWorld(info)
159+
.getResourcePack()
154160
.ifPresent(worldResourcePack -> {
155161
List<File> currentlyLoadedPacks = new ArrayList<>(ResourcePackLoader.getLoadedResourcePacks());
156162

@@ -160,17 +166,16 @@ private void loadWorld(World world, WorldMapLoader mapLoader) {
160166
loadTexturesConfirm.getButtonTypes().addAll(ButtonType.YES, ButtonType.NO);
161167
loadTexturesConfirm.setTitle("Bundled resource pack");
162168
loadTexturesConfirm.setContentText(
163-
"The world \"" + world.levelName() + "\" contains a resource pack. Do you want to load it now?");
169+
"The world \"" + info.name() + "\" contains a resource pack. Do you want to load it now?");
164170
Dialogs.stayOnTop(loadTexturesConfirm);
165171

166172
if (loadTexturesConfirm.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.YES) {
167173
// add world resource pack with highest priority
168-
currentlyLoadedPacks.add(0, worldResourcePack);
174+
currentlyLoadedPacks.addFirst(worldResourcePack);
169175
ResourcePackLoader.loadAndPersistResourcePacks(currentlyLoadedPacks);
170176
}
171177
}
172178
});
173-
mapLoader.loadWorld(world);
174179
}
175180

176181
/**
@@ -187,15 +192,15 @@ private void fillWorldList(final File worldSavesDir) {
187192
statusLabel.setText("Loading worlds list...");
188193
disableControls(true);
189194

190-
Task<List<World>> loadWorldsTask = new Task<>() {
195+
Task<List<World.Info>> loadWorldsTask = new Task<>() {
191196
@Override
192-
protected List<World> call() {
193-
List<World> worlds = new ArrayList<>();
197+
protected List<World.Info> call() {
198+
List<World.Info> worlds = new ArrayList<>();
194199
if (worldSavesDir != null) {
195200
File[] worldDirs = worldSavesDir.listFiles();
196201
if (worldDirs != null) {
197202
for (File dir : worldDirs) {
198-
WorldFormats.createWorld(dir).ifPresent(worlds::add);
203+
worlds.addAll(WorldFormats.getInfos(dir.toPath()));
199204
}
200205
}
201206
}
@@ -204,7 +209,7 @@ protected List<World> call() {
204209
};
205210

206211
loadWorldsTask.setOnSucceeded((WorkerStateEvent event) -> {
207-
List<World> worlds = loadWorldsTask.getValue();
212+
List<World.Info> worlds = loadWorldsTask.getValue();
208213

209214
worldTbl.setItems(FXCollections.observableArrayList(worlds));
210215
if (!worlds.isEmpty()) {

chunky/src/java/se/llbit/chunky/world/CubicDimension.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class CubicDimension extends JavaDimension {
2626
/**
2727
* @param dimensionDirectory Minecraft world directory.
2828
*/
29-
public CubicDimension(JavaWorld world, Dimension.Identifier dimensionId, File dimensionDirectory, Set<PlayerEntityData> playerEntities, @Nullable Vector3i spawnPos) {
29+
public CubicDimension(JavaWorld world, Dimension.Identifier dimensionId, Path dimensionDirectory, Set<PlayerEntityData> playerEntities, @Nullable Vector3i spawnPos) {
3030
super(world, dimensionId, dimensionDirectory, playerEntities, spawnPos);
3131
}
3232

@@ -35,7 +35,7 @@ public CubicDimension(JavaWorld world, Dimension.Identifier dimensionId, File di
3535
*/
3636
@Override
3737
public synchronized File getRegionDirectory() {
38-
return new File(dimensionDirectory, "region3d");
38+
return dimensionDirectory.resolve("region3d").toFile();
3939
}
4040

4141
@Override

0 commit comments

Comments
 (0)