5555import net .minecraft .world .level .LevelSettings ;
5656import net .minecraft .world .level .biome .BiomeManager ;
5757import net .minecraft .world .level .chunk .ChunkGenerator ;
58+ import net .minecraft .world .level .chunk .storage .IOWorker ;
5859import net .minecraft .world .level .dimension .BuiltinDimensionTypes ;
5960import net .minecraft .world .level .dimension .DimensionType ;
6061import net .minecraft .world .level .dimension .LevelStem ;
@@ -351,12 +352,7 @@ public CompletableFuture<Boolean> unloadWorld(final ServerWorld world) {
351352 return CompletableFuture .completedFuture (false );
352353 }
353354
354- try {
355- this .unloadWorld0 ((ServerLevel ) world );
356- return CompletableFuture .completedFuture (true );
357- } catch (final IOException e ) {
358- return FutureUtil .completedWithException (e );
359- }
355+ return this .unloadWorld0 ((ServerLevel ) world );
360356 }
361357
362358 @ Override
@@ -537,7 +533,6 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr
537533 @ Override
538534 public CompletableFuture <Boolean > moveWorld (final ResourceKey key , final ResourceKey movedKey ) {
539535 final net .minecraft .resources .ResourceKey <Level > registryKey = SpongeWorldManager .createRegistryKey (Objects .requireNonNull (key , "key" ));
540- final net .minecraft .resources .ResourceKey <Level > movedKeyKey = SpongeWorldManager .createRegistryKey (Objects .requireNonNull (movedKey , "movedKey" ));
541536
542537 if (Level .OVERWORLD .equals (registryKey )) {
543538 return CompletableFuture .completedFuture (false );
@@ -553,13 +548,15 @@ public CompletableFuture<Boolean> moveWorld(final ResourceKey key, final Resourc
553548
554549 final ServerLevel loadedWorld = this .worlds .get (registryKey );
555550 if (loadedWorld != null ) {
556- try {
557- this .unloadWorld0 (loadedWorld );
558- } catch (final IOException e ) {
559- return FutureUtil .completedWithException (e );
560- }
551+ return this .unloadWorld0 (loadedWorld ).thenCompose ($ -> this .moveWorld0 (key , movedKey ));
561552 }
562553
554+ return this .moveWorld0 (key , movedKey );
555+ }
556+
557+ private CompletableFuture <Boolean > moveWorld0 (final ResourceKey key , final ResourceKey movedKey ) {
558+ final net .minecraft .resources .ResourceKey <Level > movedKeyKey = SpongeWorldManager .createRegistryKey (Objects .requireNonNull (movedKey , "movedKey" ));
559+
563560 return CompletableFuture .runAsync (() -> {
564561 final Path originalDirectory = this .getDirectory (key );
565562 final Path movedDirectory = this .getDirectory (movedKey );
@@ -611,14 +608,21 @@ public CompletableFuture<Boolean> deleteWorld(final ResourceKey key) {
611608 final boolean disableLevelSaving = loadedWorld .noSave ;
612609 loadedWorld .noSave = true ;
613610 ((IOWorkerBridge ) loadedWorld .getChunkSource ().chunkMap .chunkScanner ()).bridge$forciblyClear ();
614- try {
615- this .unloadWorld0 (loadedWorld );
616- } catch (final IOException e ) {
617- loadedWorld .noSave = disableLevelSaving ;
618- return FutureUtil .completedWithException (e );
619- }
611+ return this .unloadWorld0 (loadedWorld )
612+ .thenCompose ($ -> this .deleteWorld0 (key ))
613+ .whenComplete (($ , e ) -> {
614+ if (e != null ) {
615+ loadedWorld .noSave = disableLevelSaving ;
616+ }
617+ });
620618 }
621619
620+ return this .deleteWorld0 (key );
621+ }
622+
623+ private CompletableFuture <Boolean > deleteWorld0 (final ResourceKey key ) {
624+ final net .minecraft .resources .ResourceKey <Level > registryKey = SpongeWorldManager .createRegistryKey (key );
625+
622626 return CompletableFuture .runAsync (() -> {
623627 final Path directory = this .getDirectory (key );
624628 if (Files .exists (directory )) {
@@ -653,38 +657,50 @@ public CompletableFuture<Boolean> deleteWorld(final ResourceKey key) {
653657 }, SpongeCommon .server ());
654658 }
655659
656- private void unloadWorld0 (final ServerLevel level ) throws IOException {
660+ private CompletableFuture < Boolean > unloadWorld0 (final ServerLevel level ) {
657661 final net .minecraft .resources .ResourceKey <Level > registryKey = level .dimension ();
658662
659663 if (!level .getPlayers (p -> true ).isEmpty ()) {
660- throw new IOException (String .format ("World '%s' was told to unload but players remain." , registryKey .location ()));
664+ return CompletableFuture . failedFuture ( new IOException (String .format ("World '%s' was told to unload but players remain." , registryKey .location () )));
661665 }
662666
663- SpongeCommon .logger ().info ("Unloading world '{}'" , registryKey .location ());
667+ // We first tell the world to save without flushing
668+ // and wait for the callback when I/O queue is empty.
669+ level .save (null , false , level .noSave );
664670
665- final UnloadWorldEvent unloadWorldEvent = SpongeEventFactory .createUnloadWorldEvent (PhaseTracker .getInstance ().currentCause (), (ServerWorld ) level );
666- SpongeCommon .post (unloadWorldEvent );
671+ return ((IOWorker ) level .getChunkSource ().chunkMap .chunkScanner ()).synchronize (true ).thenComposeAsync ($ -> {
672+ if (!level .getPlayers (p -> true ).isEmpty ()) {
673+ return CompletableFuture .failedFuture (new IOException (String .format ("World '%s' was told to unload but players remain." , registryKey .location ())));
674+ }
667675
668- final int lastSpawnChunkRadius = ((ServerLevelAccessor ) level ).accessor$lastSpawnChunkRadius ();
669- if (lastSpawnChunkRadius > 1 ) {
670- level .getChunkSource ().removeRegionTicket (TicketType .START , new ChunkPos (level .getSharedSpawnPos ()), lastSpawnChunkRadius , Unit .INSTANCE );
671- ((ServerLevelAccessor ) level ).accessor$setLastSpawnChunkRadius (1 );
672- }
676+ SpongeCommon .logger ().info ("Unloading world '{}'" , registryKey .location ());
673677
674- final var configAdapter = ((ServerLevelDataBridge ) level .getLevelData ()).bridge$spongeData ().configAdapter ();
675- if (configAdapter != null ) {
676- configAdapter .save ();
677- }
678+ final UnloadWorldEvent unloadWorldEvent = SpongeEventFactory .createUnloadWorldEvent (PhaseTracker .getInstance ().currentCause (), (ServerWorld ) level );
679+ SpongeCommon .post (unloadWorldEvent );
678680
679- try {
680- level .save (null , true , level .noSave );
681- level .close ();
682- ((ServerLevelBridge ) level ).bridge$getLevelSave ().close ();
683- } catch (final Exception ex ) {
684- throw new IOException (ex );
685- }
681+ final int lastSpawnChunkRadius = ((ServerLevelAccessor ) level ).accessor$lastSpawnChunkRadius ();
682+ if (lastSpawnChunkRadius > 1 ) {
683+ level .getChunkSource ().removeRegionTicket (TicketType .START , new ChunkPos (level .getSharedSpawnPos ()), lastSpawnChunkRadius , Unit .INSTANCE );
684+ ((ServerLevelAccessor ) level ).accessor$setLastSpawnChunkRadius (1 );
685+ }
686+
687+ final var configAdapter = ((ServerLevelDataBridge ) level .getLevelData ()).bridge$spongeData ().configAdapter ();
688+ if (configAdapter != null ) {
689+ configAdapter .save ();
690+ }
686691
687- this .worlds .remove (registryKey );
692+ try {
693+ level .save (null , true , level .noSave );
694+ level .close ();
695+ ((ServerLevelBridge ) level ).bridge$getLevelSave ().close ();
696+ } catch (final Exception ex ) {
697+ return CompletableFuture .failedFuture (new IOException (ex ));
698+ }
699+
700+ this .worlds .remove (registryKey );
701+
702+ return CompletableFuture .completedFuture (true );
703+ }, SpongeCommon .server ());
688704 }
689705
690706 public void createNonDefaultLevels () {
0 commit comments