2525import com .google .common .collect .ImmutableList ;
2626import com .google .common .collect .Lists ;
2727import com .google .common .collect .Sets ;
28- import com .google .common .util .concurrent .Futures ;
2928import com .mojang .serialization .Codec ;
3029import com .mojang .serialization .Lifecycle ;
3130import com .sk89q .worldedit .EditSession ;
3231import com .sk89q .worldedit .WorldEditException ;
3332import com .sk89q .worldedit .blocks .BaseItem ;
3433import com .sk89q .worldedit .blocks .BaseItemStack ;
3534import com .sk89q .worldedit .bukkit .BukkitAdapter ;
35+ import com .sk89q .worldedit .bukkit .WorldEditPlugin ;
3636import com .sk89q .worldedit .bukkit .adapter .BukkitImplAdapter ;
37+ import com .sk89q .worldedit .bukkit .folia .FoliaScheduler ;
3738import com .sk89q .worldedit .entity .BaseEntity ;
3839import com .sk89q .worldedit .extension .platform .Watchdog ;
3940import com .sk89q .worldedit .extent .Extent ;
@@ -699,6 +700,10 @@ public boolean canPlaceAt(World world, BlockVector3 position, BlockState blockSt
699700
700701 @ Override
701702 public boolean regenerate (World bukkitWorld , Region region , Extent extent , RegenOptions options ) {
703+ if (FoliaScheduler .isFolia ()) {
704+ return regenerateFolia (bukkitWorld , region , extent , options );
705+ }
706+
702707 try {
703708 doRegen (bukkitWorld , region , extent , options );
704709 } catch (Exception e ) {
@@ -708,6 +713,14 @@ public boolean regenerate(World bukkitWorld, Region region, Extent extent, Regen
708713 return true ;
709714 }
710715
716+ private boolean regenerateFolia (World bukkitWorld , Region region , Extent extent , RegenOptions options ) {
717+ try {
718+ return doRegenFolia (bukkitWorld , region , extent , options );
719+ } catch (Exception e ) {
720+ throw new IllegalStateException ("Regen failed on Folia." , e );
721+ }
722+ }
723+
711724 private void doRegen (World bukkitWorld , Region region , Extent extent , RegenOptions options ) throws Exception {
712725 Environment env = bukkitWorld .getEnvironment ();
713726 ChunkGenerator gen = bukkitWorld .getGenerator ();
@@ -781,6 +794,244 @@ private void doRegen(World bukkitWorld, Region region, Extent extent, RegenOptio
781794 }
782795 }
783796
797+ private boolean doRegenFolia (World bukkitWorld , Region region , Extent extent , RegenOptions options ) throws Exception {
798+ Environment env = bukkitWorld .getEnvironment ();
799+ ChunkGenerator gen = bukkitWorld .getGenerator ();
800+
801+ Path tempDir = Files .createTempDirectory ("WorldEditWorldGen" );
802+ LevelStorageSource levelStorage = LevelStorageSource .createDefault (tempDir );
803+ ResourceKey <LevelStem > worldDimKey = getWorldDimKey (env );
804+ try (LevelStorageSource .LevelStorageAccess session = levelStorage .createAccess ("worldeditregentempworld" , worldDimKey )) {
805+ ServerLevel originalWorld = ((CraftWorld ) bukkitWorld ).getHandle ();
806+ PrimaryLevelData levelProperties = (PrimaryLevelData ) originalWorld .getServer ()
807+ .getWorldData ().overworldData ();
808+ WorldOptions originalOpts = levelProperties .worldGenOptions ();
809+
810+ long seed = options .getSeed ().orElse (originalWorld .getSeed ());
811+ WorldOptions newOpts = options .getSeed ().isPresent ()
812+ ? originalOpts .withSeed (OptionalLong .of (seed ))
813+ : originalOpts ;
814+
815+ LevelSettings newWorldSettings = new LevelSettings (
816+ "worldeditregentempworld" ,
817+ levelProperties .settings .gameType (),
818+ levelProperties .settings .hardcore (),
819+ levelProperties .settings .difficulty (),
820+ levelProperties .settings .allowCommands (),
821+ levelProperties .settings .gameRules (),
822+ levelProperties .settings .getDataConfiguration ()
823+ );
824+
825+ @ SuppressWarnings ("deprecation" )
826+ PrimaryLevelData .SpecialWorldProperty specialWorldProperty =
827+ levelProperties .isFlatWorld ()
828+ ? PrimaryLevelData .SpecialWorldProperty .FLAT
829+ : levelProperties .isDebugWorld ()
830+ ? PrimaryLevelData .SpecialWorldProperty .DEBUG
831+ : PrimaryLevelData .SpecialWorldProperty .NONE ;
832+
833+ PrimaryLevelData newWorldData = new PrimaryLevelData (newWorldSettings , newOpts , specialWorldProperty , Lifecycle .stable ());
834+
835+ ServerLevel freshWorld = new ServerLevel (
836+ originalWorld .getServer (),
837+ originalWorld .getServer ().executor ,
838+ session , newWorldData ,
839+ originalWorld .dimension (),
840+ new LevelStem (
841+ originalWorld .dimensionTypeRegistration (),
842+ originalWorld .getChunkSource ().getGenerator ()
843+ ),
844+ new NoOpWorldLoadListener (),
845+ originalWorld .isDebug (),
846+ seed ,
847+ ImmutableList .of (),
848+ false ,
849+ originalWorld .getRandomSequences (),
850+ env ,
851+ gen ,
852+ bukkitWorld .getBiomeProvider ()
853+ );
854+
855+ try {
856+ ChunkPos spawnChunk = new ChunkPos (
857+ freshWorld .getChunkSource ().randomState ().sampler ().findSpawnPosition ()
858+ );
859+
860+ try {
861+ Field randomSpawnField = ServerLevel .class .getDeclaredField ("randomSpawnSelection" );
862+ randomSpawnField .setAccessible (true );
863+ randomSpawnField .set (freshWorld , spawnChunk );
864+ } catch (ReflectiveOperationException e ) {
865+ throw new RuntimeException ("Failed to set spawn chunk for Folia" , e );
866+ }
867+
868+ MinecraftServer console = originalWorld .getServer ();
869+ CompletableFuture <Void > initFuture = new CompletableFuture <>();
870+
871+ FoliaScheduler .getRegionScheduler ().run (
872+ WorldEditPlugin .getInstance (),
873+ freshWorld .getWorld (),
874+ spawnChunk .x , spawnChunk .z ,
875+ o -> {
876+ try {
877+ console .initWorld (freshWorld , newWorldData , newWorldData , newWorldData .worldGenOptions ());
878+ initFuture .complete (null );
879+ } catch (Exception e ) {
880+ initFuture .completeExceptionally (e );
881+ }
882+ }
883+ );
884+
885+ initFuture .get ();
886+
887+ regenForWorldFolia (region , extent , freshWorld , options );
888+ } finally {
889+ freshWorld .getChunkSource ().close (false );
890+ }
891+ } finally {
892+ try {
893+ @ SuppressWarnings ("unchecked" )
894+ Map <String , World > map = (Map <String , World >) serverWorldsField .get (Bukkit .getServer ());
895+ map .remove ("worldeditregentempworld" );
896+ } catch (IllegalAccessException ignored ) {
897+ }
898+ SafeFiles .tryHardToDeleteDir (tempDir );
899+ }
900+
901+ return true ;
902+ }
903+
904+ @ SuppressWarnings ("unchecked" )
905+ private void regenForWorldFolia (Region region , Extent extent , ServerLevel serverWorld , RegenOptions options ) throws WorldEditException {
906+ Map <BlockVector3 , BlockStateHolder <?>> blockStates = new HashMap <>();
907+ Map <BlockVector3 , BiomeType > biomes = new HashMap <>();
908+ Map <ChunkPos , List <BlockVector3 >> blocksByChunk = new HashMap <>();
909+
910+ for (BlockVector3 vec : region ) {
911+ ChunkPos chunkPos = new ChunkPos (vec .x () >> 4 , vec .z () >> 4 );
912+ blocksByChunk .computeIfAbsent (chunkPos , k -> new ArrayList <>()).add (vec );
913+ }
914+
915+ World bukkitWorld = serverWorld .getWorld ();
916+ List <CompletableFuture <Void >> extractionFutures = new ArrayList <>();
917+
918+ for (Map .Entry <ChunkPos , List <BlockVector3 >> entry : blocksByChunk .entrySet ()) {
919+ ChunkPos chunkPos = entry .getKey ();
920+ List <BlockVector3 > blocks = entry .getValue ();
921+ CompletableFuture <Void > future = new CompletableFuture <>();
922+
923+ FoliaScheduler .getRegionScheduler ().execute (
924+ WorldEditPlugin .getInstance (),
925+ bukkitWorld ,
926+ chunkPos .x ,
927+ chunkPos .z ,
928+ () -> {
929+ try {
930+ ServerChunkCache chunkManager = serverWorld .getChunkSource ();
931+ CompletableFuture <ChunkResult <ChunkAccess >> chunkFuture =
932+ ((CompletableFuture <ChunkResult <ChunkAccess >>)
933+ getChunkFutureMethod .invoke (chunkManager , chunkPos .x , chunkPos .z , ChunkStatus .FEATURES , true ));
934+ chunkFuture .thenApply (either -> either .orElse (null ))
935+ .whenComplete ((chunkAccess , throwable ) -> {
936+ if (throwable != null ) {
937+ future .completeExceptionally (new IllegalStateException ("Couldn't load chunk for regen." , throwable ));
938+ } else if (chunkAccess != null ) {
939+ try {
940+ for (BlockVector3 vec : blocks ) {
941+ BlockPos pos = new BlockPos (vec .x (), vec .y (), vec .z ());
942+ final net .minecraft .world .level .block .state .BlockState blockData = chunkAccess .getBlockState (pos );
943+ int internalId = Block .getId (blockData );
944+ BlockStateHolder <?> state = BlockStateIdAccess .getBlockStateById (internalId );
945+ Objects .requireNonNull (state );
946+ BlockEntity blockEntity = chunkAccess .getBlockEntity (pos );
947+ if (blockEntity != null ) {
948+ net .minecraft .nbt .CompoundTag tag = blockEntity .saveWithId (serverWorld .registryAccess ());
949+ state = state .toBaseBlock (LazyReference .from (() -> (LinCompoundTag ) toNative (tag )));
950+ }
951+ synchronized (blockStates ) {
952+ blockStates .put (vec , state );
953+ }
954+ if (options .shouldRegenBiomes ()) {
955+ Biome origBiome = chunkAccess .getNoiseBiome (vec .x (), vec .y (), vec .z ()).value ();
956+ BiomeType adaptedBiome = adapt (serverWorld , origBiome );
957+ if (adaptedBiome != null ) {
958+ synchronized (biomes ) {
959+ biomes .put (vec , adaptedBiome );
960+ }
961+ }
962+ }
963+ }
964+ future .complete (null );
965+ } catch (Exception e ) {
966+ future .completeExceptionally (e );
967+ }
968+ } else {
969+ future .completeExceptionally (new IllegalStateException ("Failed to generate a chunk, regen failed." ));
970+ }
971+ });
972+ } catch (IllegalAccessException | InvocationTargetException e ) {
973+ future .completeExceptionally (new IllegalStateException ("Couldn't load chunk for regen." , e ));
974+ }
975+ }
976+ );
977+ extractionFutures .add (future );
978+ }
979+
980+ CompletableFuture .allOf (extractionFutures .toArray (new CompletableFuture <?>[0 ])).join ();
981+
982+ for (BlockVector3 vec : region ) {
983+ BlockStateHolder <?> state = blockStates .get (vec );
984+ if (state != null ) {
985+ extent .setBlock (vec , state .toBaseBlock ());
986+ if (options .shouldRegenBiomes ()) {
987+ BiomeType biome = biomes .get (vec );
988+ if (biome != null ) {
989+ extent .setBiome (vec , biome );
990+ }
991+ }
992+ }
993+ }
994+ }
995+
996+ @ SuppressWarnings ("unchecked" )
997+ private List <CompletableFuture <ChunkAccess >> submitChunkLoadTasksFolia (Region region , ServerLevel serverWorld ) {
998+ ServerChunkCache chunkManager = serverWorld .getChunkSource ();
999+ List <CompletableFuture <ChunkAccess >> chunkLoadings = new ArrayList <>();
1000+ World bukkitWorld = serverWorld .getWorld ();
1001+
1002+ for (BlockVector2 chunk : region .getChunks ()) {
1003+ CompletableFuture <ChunkAccess > future = new CompletableFuture <>();
1004+ final int chunkX = chunk .x ();
1005+ final int chunkZ = chunk .z ();
1006+
1007+ FoliaScheduler .getRegionScheduler ().execute (
1008+ WorldEditPlugin .getInstance (),
1009+ bukkitWorld ,
1010+ chunkX ,
1011+ chunkZ ,
1012+ () -> {
1013+ try {
1014+ CompletableFuture <ChunkResult <ChunkAccess >> chunkFuture =
1015+ ((CompletableFuture <ChunkResult <ChunkAccess >>)
1016+ getChunkFutureMethod .invoke (chunkManager , chunkX , chunkZ , ChunkStatus .FEATURES , true ));
1017+ chunkFuture .thenApply (either -> either .orElse (null ))
1018+ .whenComplete ((chunkAccess , throwable ) -> {
1019+ if (throwable != null ) {
1020+ future .completeExceptionally (throwable );
1021+ } else {
1022+ future .complete (chunkAccess );
1023+ }
1024+ });
1025+ } catch (IllegalAccessException | InvocationTargetException e ) {
1026+ future .completeExceptionally (new IllegalStateException ("Couldn't load chunk for regen." , e ));
1027+ }
1028+ }
1029+ );
1030+ chunkLoadings .add (future );
1031+ }
1032+ return chunkLoadings ;
1033+ }
1034+
7841035 private BiomeType adapt (ServerLevel serverWorld , Biome origBiome ) {
7851036 ResourceLocation key = serverWorld .registryAccess ().lookupOrThrow (Registries .BIOME ).getKey (origBiome );
7861037 if (key == null ) {
@@ -801,7 +1052,7 @@ private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld
8011052 executor .managedBlock (() -> {
8021053 // bail out early if a future fails
8031054 if (chunkLoadings .stream ().anyMatch (ftr ->
804- ftr .isDone () && Futures . getUnchecked ( ftr ) == null
1055+ ftr .isDone () && ftr . getNow ( null ) == null
8051056 )) {
8061057 return false ;
8071058 }
0 commit comments