1.21.5 Suppression optimisé des chunks et application d'un biome

  • Auteur de la discussion Auteur de la discussion Chokaopik
  • Date de début Date de début

Chokaopik

Aventurier
19 Mai 2025
8
1
3
31
Bonjour à tous,

J'ai cherché une méthode efficace pour vider les chunks en version 1.21.5. Après avoir testé des plugins comme FAWE, j’ai réalisé que je ne voulais pas dépendre d’un outil externe qui pourrait limiter la mise à jour de mon serveur à ma convenance. J’ai donc réfléchi à une approche optimisée : bien que la modification d’un bloc doive se faire dans le thread principal, la lecture des données et le changement de biome peuvent s’effectuer en dehors de celui-ci.

J’ai expérimenté sur mon serveur One Block avec une zone de 5x5 chunks, comprenant 8 chunks vides et 1 plein, couvrant les couches de -64 à 250. Résultat : chaque chunk, vide ou rempli, est traité en une seconde, sans engendrer de ralentissement du serveur.

Je un peu modifier mon code pour qu'il soit utilisable, à savoir que List<Double> contient 4 valeurs : la valeur minimal du chunk x, maximal x, minimal z et maximal z.
Comme dans le deuxième code il y à les variables directement à créer vous même ou à remplacer par une List<Double> comme le premier code.

Exemple en image mon premier chunk en 0,0 sera de -2 x à 2 x et de -2 z à 2 z

1748369313931.png


Exemple en image
Java:
    public static void changerBiomeChunks(String biome, List<Double> ile) {
        Bukkit.getScheduler().runTaskAsynchronously(General.main, () -> {
            World world = Bukkit.getWorld(General.one_block.getName());

            Biome targetBiome = Biome.valueOf(biome.toUpperCase());

            for (int x = ile.get(0).intValue(); x <= ile.get(1).intValue(); x++) {
                for (int z = ile.get(2).intValue(); z <= ile.get(3).intValue(); z++) {
                    if (!world.isChunkGenerated(x, z)) continue; // Ignore les chunks non générés

                    Chunk chunk = world.getChunkAt(x, z);

                    for (int cx = 0; cx < 16; cx++) {
                        for (int cz = 0; cz < 16; cz++) {
                            for (int cy = world.getMinHeight(); cy < world.getMaxHeight(); cy++) {
                                chunk.getBlock(cx, cy, cz).setBiome(targetBiome);
                            }
                        }
                    }
                }
            }
        });
    }



Java:
 public void supprimerChunk() {
        Bukkit.getScheduler().runTaskAsynchronously(General.main, () -> {
            World world = Bukkit.getWorld(General.one_block.getName());

            for (int x = chunk_min_x.intValue(); x <= chunk_max_x.intValue(); x++) {
                for (int z = chunk_min_z.intValue(); z <= chunk_max_z.intValue(); z++) {
                
                    Chunk chunk = world.getChunkAt(x, z);

                    if (!world.isChunkGenerated(x, z)) continue; // Ignore les chunks non générés

                    List<Block> blocksToRemove = new ArrayList<>();

                    for (int bx = 0; bx < 16; bx++) {
                        for (int by = world.getMinHeight(); by < world.getMaxHeight(); by++) {
                            for (int bz = 0; bz < 16; bz++) {
                                Block block = chunk.getBlock(bx, by, bz);
                                if (block.getType() != Material.AIR) {
                                    blocksToRemove.add(block); // Stocker les blocs à modifier
                                }
                            }
                        }
                    }

                    // Modifier les blocs sur le thread principal
                    Bukkit.getScheduler().runTask(General.main, () -> {
                        for (Block block : blocksToRemove) {
                            block.setType(Material.AIR);
                        }
                    });
                }
            }
        });
    }

En espérant que ça aide et si vous avez des optis hésitez pas je prend :D
 
  • J'aime
Reactions: Detobel36
Bonjour,

Lorsque tu fais une fonction, c'est mieux d'extraire le plus de paramètres possibles histoire d'avoir une fonction générale :
Java:
public static void changerBiomeChunks(String biome, List<Double> ile) {
    Bukkit.getScheduler().runTaskAsynchronously(General.main, () -> {
    //~^ comme dit, tu ne peux pas faire de `setBiome` async
        World world = Bukkit.getWorld(General.one_block.getName());
        Biome targetBiome = Biome.valueOf(biome.toUpperCase());
        //~^ ce n'est pas à cette fonction de déterminer ses paramètres ;
        //   si `biome` est incorrect, c'est à ta commande de traiter l'erreur,
        //   pas à cette fonction de throw IllegalArgumentException

        for (int x = ile.get(0).intValue(); x <= ile.get(1).intValue(); x++) {
            for (int z = ile.get(2).intValue(); z <= ile.get(3).intValue(); z++) {
                //~^ si tu veux des ints, tu dois prendre des ints en paramètre.
                //   c'est aussi mieux d'avoir 4 paramètres plutôt qu'une liste/un tableau

Si la fonction traite des chunks, elle doit prendre des chunks :
Java:
public static void changerBiomeChunks(Biome targetBiome, Iterable<Chunk> chunks) {
    for(Chunk chunk : chunks) {
        World w = chunk.getWorld();
        
        // le format des chunks est YZX -> 256y + 16z + x,
        //  donc pour la localité spatiale, l'on a intérêt à itérer dans cet ordre
        
        for(int y = w.getMinHeight(); y < w.getMaxHeight(); ++y) {
            for(int z = 0; z < 16; ++z) {
                for(int x = 0; x < 16; ++x) {
                    chunk.getBlock(x, y, z).setBiome(targetBiome);
                }
            }
        }
    }
}

public static void changerBiomeChunks(Biome targetBiome, Iterator<Chunk> chunks) {
    changerBiomesChunks(targetBiome, () -> chunks);
    //                               ------------ conversion Iterator<T> -> Iterable<T>
}

public static void changerBiomeChunks(Biome targetBiome, Stream<Chunk> chunks) {
    changerBiomesChunks(targetBiome, chunks::iterator);
    //~^ sous-optimal dans le cas général: on perd chunks::spliterator,
    //   mais dans notre cas on n'utilise qu'Iterable::iterator
}

bien que la modification d’un bloc doive se faire dans le thread principal, la lecture des données et le changement de biome peuvent s’effectuer en dehors de celui-ci.
Généralement, non ; tu risques d'avoir une TOCTOU ; un joueur peut poser/enlever un bloc entre ta tâche asynchrone et ta tâche synchrone, qui ne sera pas dans la liste. Le mieux est d'enfiler les chunks à traiter dans une FIFO, e.g. ArrayDeque<T>, et à chaque tick de traiter quelques chunks de celle-ci.

Cordialement,
ShE3py
 
Bonjour,

Lorsque tu fais une fonction, c'est mieux d'extraire le plus de paramètres possibles histoire d'avoir une fonction générale :
Java:
public static void changerBiomeChunks(String biome, List<Double> ile) {
    Bukkit.getScheduler().runTaskAsynchronously(General.main, () -> {
    //~^ comme dit, tu ne peux pas faire de `setBiome` async
        World world = Bukkit.getWorld(General.one_block.getName());
        Biome targetBiome = Biome.valueOf(biome.toUpperCase());
        //~^ ce n'est pas à cette fonction de déterminer ses paramètres ;
        //   si `biome` est incorrect, c'est à ta commande de traiter l'erreur,
        //   pas à cette fonction de throw IllegalArgumentException

        for (int x = ile.get(0).intValue(); x <= ile.get(1).intValue(); x++) {
            for (int z = ile.get(2).intValue(); z <= ile.get(3).intValue(); z++) {
                //~^ si tu veux des ints, tu dois prendre des ints en paramètre.
                //   c'est aussi mieux d'avoir 4 paramètres plutôt qu'une liste/un tableau

Si la fonction traite des chunks, elle doit prendre des chunks :
Java:
public static void changerBiomeChunks(Biome targetBiome, Iterable<Chunk> chunks) {
    for(Chunk chunk : chunks) {
        World w = chunk.getWorld();
  
        // le format des chunks est YZX -> 256y + 16z + x,
        //  donc pour la localité spatiale, l'on a intérêt à itérer dans cet ordre
  
        for(int y = w.getMinHeight(); y < w.getMaxHeight(); ++y) {
            for(int z = 0; z < 16; ++z) {
                for(int x = 0; x < 16; ++x) {
                    chunk.getBlock(x, y, z).setBiome(targetBiome);
                }
            }
        }
    }
}

public static void changerBiomeChunks(Biome targetBiome, Iterator<Chunk> chunks) {
    changerBiomesChunks(targetBiome, () -> chunks);
    //                               ------------ conversion Iterator<T> -> Iterable<T>
}

public static void changerBiomeChunks(Biome targetBiome, Stream<Chunk> chunks) {
    changerBiomesChunks(targetBiome, chunks::iterator);
    //~^ sous-optimal dans le cas général: on perd chunks::spliterator,
    //   mais dans notre cas on n'utilise qu'Iterable::iterator
}


Généralement, non ; tu risques d'avoir une TOCTOU ; un joueur peut poser/enlever un bloc entre ta tâche asynchrone et ta tâche synchrone, qui ne sera pas dans la liste. Le mieux est d'enfiler les chunks à traiter dans une FIFO, e.g. ArrayDeque<T>, et à chaque tick de traiter quelques chunks de celle-ci.

Cordialement,
ShE3py

Salut ShE3py,

Alors plusieurs points ne concernent pas mon projet ou demanderais plus de ressources de calcul inutilement.

Utiliser des int plutôt que mes doubles d'origine : Oui sauf que cette tâche est sensé être une exception là où mes récupérations de positions à partir de mes doubles sont régulières, cela me permet de ne pas faire de conversion inutiles dans des tâches régulièrements appelés. Je pourrais stocker tout ça mais ce serait un beau bordel au démarrage.

Pour le joueur, sur mon projet il ne peut pas poser ou casser, c'est un projet one block et le joueur ne possédant plus l'île ne peut plus rien faire, l'île est mis disponible une fois sa suppression terminé pour qu'un autre joueur puisse récupérer la zone supprimé.

Pour les chunks si je n'ai pas fourni des chunks c'est parce que ... Je n'ai pas de liste de chunks. J'ai 100 000 positions d'îles pré-définies. Je récupère donc les chunks directement dans la fonction.

Pour le fait que tu me parle de TOCTOU je suis désoler mais pour avoir testé pleins de code trouvé sur internet, WorldEdit ou encore Fawe même avec des chunks vide j'avais des lag de 3s par chunk. Mon code actuel même avec des chunks pleins je n'ai strictement aucun timeout ni même d'alerte.

Et non je te confirme bien qu'on peut lire un bloc en async sans le modifer sans aucun problème. C'est bien sa modification qui doit être géré sur le thread principale mais visiblement ce n'est pas le cas pour setBiome, puisque sans le async j'ai un timeout, avec je n'ai même pas un lag et pas d'alerte non plus.

Pour avoir testé beaucoup de fonction trouvé sur internet je n'ai pas trouvé une seule qui ne lag pas, même FAWE et World Edit créer des lag de 2 à 3s par chunks. Après c'est possible que je les est mal utilisé, je ne suis pas developpeur Java :fou:

Mais je pense que oui le code est clairement optimisable. En attendant je n'avais pas trouvé de code sur internet qui était un minimum sans lag.

PS : Le code fourni ici à été modifié, car d'origine le code est celui-ci, il obtient un Biome avec déjà une vérification réalisé pour voir s'il existe, ensuite c'était un tableau de Double car ma class Ile contient les chunks sous un tableau de double que je trouve bien plus simple plutôt qu'utiliser mes 10 variables séparéments faisant 10 fonctions de retours inutiles. Au vu du nombre de fonctions déjà présentes ... J'ai déjà plus de 310 lignes et c'est loin d'être fini.

Java:
    public static void changerBiomeChunks(Biome targetBiome, Ile ile) {
        Bukkit.getScheduler().runTaskAsynchronously(General.main, () -> {
            World world = Bukkit.getWorld(General.one_block.getName());

            for (int x = ile.getChunk().get(0).intValue(); x <= ile.getChunk().get(1).intValue(); x++) {
                for (int z = ile.getChunk().get(2).intValue(); z <= ile.getChunk().get(3).intValue(); z++) {
                    if (!world.isChunkGenerated(x, z)) continue; // Ignore les chunks non générés

                    Chunk chunk = world.getChunkAt(x, z);

                    for (int cx = 0; cx < 16; cx++) {
                        for (int cz = 0; cz < 16; cz++) {
                            for (int cy = world.getMinHeight(); cy < world.getMaxHeight(); cy++) {
                                chunk.getBlock(cx, cy, cz).setBiome(targetBiome);
                            }
                        }
                    }
                }
            }
        });
    }

Supprimer est non static à donc accès à la class donc récupère les variables directement, l'autre ne l'est pas et n'ayant pas l'envie de faire 4 retours, je retourne un tableau des 4 :D
 
Dernière édition: