001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.queue;
020
021import com.google.inject.Inject;
022import com.plotsquared.core.PlotSquared;
023import com.plotsquared.core.location.Location;
024import com.plotsquared.core.queue.subscriber.ProgressSubscriber;
025import com.plotsquared.core.util.PatternUtil;
026import com.sk89q.jnbt.CompoundTag;
027import com.sk89q.worldedit.entity.Entity;
028import com.sk89q.worldedit.function.pattern.Pattern;
029import com.sk89q.worldedit.math.BlockVector2;
030import com.sk89q.worldedit.regions.CuboidRegion;
031import com.sk89q.worldedit.util.SideEffectSet;
032import com.sk89q.worldedit.world.World;
033import com.sk89q.worldedit.world.biome.BiomeType;
034import com.sk89q.worldedit.world.block.BaseBlock;
035import com.sk89q.worldedit.world.block.BlockState;
036import org.checkerframework.checker.nullness.qual.NonNull;
037import org.checkerframework.checker.nullness.qual.Nullable;
038
039import java.util.List;
040import java.util.Set;
041import java.util.concurrent.atomic.AtomicBoolean;
042import java.util.function.Consumer;
043
044public abstract class QueueCoordinator {
045
046    private final AtomicBoolean enqueued = new AtomicBoolean();
047    private boolean forceSync = false;
048    @Nullable
049    private Object chunkObject;
050    @SuppressWarnings({"unused", "FieldCanBeLocal"})
051    @Inject
052    private GlobalBlockQueue blockQueue;
053
054    /**
055     * Default constructor requires world to indicate any extents given to {@link QueueCoordinator} also need this constructor.
056     *
057     * @param world world as all queues should have this constructor
058     */
059    public QueueCoordinator(@Nullable World world) {
060        PlotSquared.platform().injector().injectMembers(this);
061    }
062
063    /**
064     * Get a {@link ZeroedDelegateScopedQueueCoordinator} limited to the chunk at the specific chunk Coordinates
065     *
066     * @param x chunk x coordinate
067     * @param z chunk z coordinate
068     * @return a new {@link ZeroedDelegateScopedQueueCoordinator}
069     * @since 7.0.0
070     */
071    public ZeroedDelegateScopedQueueCoordinator getForChunk(int x, int z, int minY, int maxY) {
072        int bx = x << 4;
073        int bz = z << 4;
074        return new ZeroedDelegateScopedQueueCoordinator(this, Location.at(getWorld().getName(), bx, minY, bz),
075                Location.at(getWorld().getName(), bx + 15, maxY, bz + 15)
076        );
077    }
078
079    /**
080     * Get the size of the queue in chunks
081     *
082     * @return size
083     */
084    public abstract int size();
085
086    /**
087     * Set when the queue was last modified
088     *
089     * @param modified long of system millis
090     */
091    public abstract void setModified(long modified);
092
093    /**
094     * Returns true if the queue should be forced to be synchronous when enqueued. This is not necessarily synchronous to the
095     * server, and simply effectively makes {@link QueueCoordinator#enqueue()} a blocking operation.
096     *
097     * @return is force sync
098     */
099    public boolean isForceSync() {
100        return forceSync;
101    }
102
103    /**
104     * Set whether the queue should be forced to be synchronous. This is not necessarily synchronous to the server, and simply
105     * effectively makes {@link QueueCoordinator#enqueue()} a blocking operation.
106     *
107     * @param forceSync force sync or not
108     */
109    public void setForceSync(boolean forceSync) {
110        this.forceSync = forceSync;
111    }
112
113    /**
114     * Get the Chunk Object set to the queue
115     *
116     * @return chunk object. Usually the implementation-specific chunk (e.g. bukkit Chunk)
117     */
118    public @Nullable Object getChunkObject() {
119        return chunkObject;
120    }
121
122    /**
123     * Set a chunk object (e.g. the Bukkit Chunk object) to the queue. This will be used as fallback in case of WNA failure.
124     * Should ONLY be used in specific cases (i.e. generation, where a chunk is being populated)
125     *
126     * @param chunkObject chunk object. Usually the implementation-specific chunk (e.g. bukkit Chunk)
127     */
128    public void setChunkObject(@NonNull Object chunkObject) {
129        this.chunkObject = chunkObject;
130    }
131
132    /**
133     * Sets the block at the coordinates provided to the given id.
134     *
135     * @param x  the x coordinate from from 0 to 15 inclusive
136     * @param y  the y coordinate from from 0 (inclusive) - maxHeight(exclusive)
137     * @param z  the z coordinate from 0 to 15 inclusive
138     * @param id the BlockState to set the block to
139     * @return success or not
140     */
141    public abstract boolean setBlock(final int x, final int y, final int z, final @NonNull BlockState id);
142
143    /**
144     * Sets the block at the coordinates provided to the given id.
145     *
146     * @param x  the x coordinate from from 0 to 15 inclusive
147     * @param y  the y coordinate from from 0 (inclusive) - maxHeight(exclusive)
148     * @param z  the z coordinate from 0 to 15 inclusive
149     * @param id the BaseBlock to set the block to
150     * @return success or not
151     */
152    public abstract boolean setBlock(final int x, final int y, final int z, final @NonNull BaseBlock id);
153
154    /**
155     * Sets the block at the coordinates provided to the given id.
156     *
157     * @param x       the x coordinate from from 0 to 15 inclusive
158     * @param y       the y coordinate from from 0 (inclusive) - maxHeight(exclusive)
159     * @param z       the z coordinate from 0 to 15 inclusive
160     * @param pattern the pattern to set the block to
161     * @return success or not
162     */
163    public boolean setBlock(final int x, final int y, final int z, final @NonNull Pattern pattern) {
164        return setBlock(x, y, z, PatternUtil.apply(pattern, x, y, z));
165    }
166
167    /**
168     * Sets a tile entity at the coordinates provided to the given CompoundTag
169     *
170     * @param x   the x coordinate from from 0 to 15 inclusive
171     * @param y   the y coordinate from from 0 (inclusive) - maxHeight(exclusive)
172     * @param z   the z coordinate from 0 to 15 inclusive
173     * @param tag the CompoundTag to set the tile to
174     * @return success or not
175     */
176    public abstract boolean setTile(int x, int y, int z, @NonNull CompoundTag tag);
177
178    /**
179     * Whether the queue has any tiles being set
180     *
181     * @return if setting tiles
182     */
183    public abstract boolean isSettingTiles();
184
185    /**
186     * Get a block at the given coordinates.
187     *
188     * @param x block x
189     * @param y block y
190     * @param z block z
191     * @return WorldEdit BlockState
192     */
193    public @Nullable
194    abstract BlockState getBlock(int x, int y, int z);
195
196    /**
197     * Set a biome in XZ. This will likely set to the whole column
198     *
199     * @param x     x coordinate
200     * @param z     z coordinate
201     * @param biome biome
202     * @return success or not
203     * @deprecated Biomes now take XYZ, see {@link #setBiome(int, int, int, BiomeType)}
204     *         <br>
205     *         Scheduled for removal once we drop the support for versions not supporting 3D biomes, 1.18 and earlier.
206     */
207    @Deprecated(forRemoval = true, since = "6.0.0")
208    public abstract boolean setBiome(int x, int z, @NonNull BiomeType biome);
209
210    /**
211     * Set a biome in XYZ
212     *
213     * @param x     x coordinate
214     * @param y     y coordinate
215     * @param z     z coordinate
216     * @param biome biome
217     * @return success or not
218     */
219    public abstract boolean setBiome(int x, int y, int z, @NonNull BiomeType biome);
220
221    /**
222     * Whether the queue has any biomes to be set
223     *
224     * @return if setting biomes
225     */
226    public abstract boolean isSettingBiomes();
227
228    /**
229     * If the queue should accept biome placement
230     *
231     * @param enabled If biomes should be enabled
232     * @since 6.8.0
233     */
234    public abstract void setBiomesEnabled(boolean enabled);
235
236    /**
237     * Add entities to be created
238     *
239     * @param entities list of entities to add to queue
240     */
241    public void addEntities(@NonNull List<? extends Entity> entities) {
242        for (Entity e : entities) {
243            this.setEntity(e);
244        }
245    }
246
247    /**
248     * Add an entity to be created
249     *
250     * @param entity entity to add to queue
251     * @return success or not
252     */
253    public abstract boolean setEntity(@NonNull Entity entity);
254
255    /**
256     * Get the list of chunks that are added manually. This usually indicated the queue is "read only".
257     *
258     * @return list of BlockVector2 of chunks that are to be "read"
259     */
260    public @NonNull
261    abstract List<BlockVector2> getReadChunks();
262
263    /**
264     * Add a set of {@link BlockVector2} Chunk coordinates to the Read Chunks list
265     *
266     * @param readChunks set of BlockVector2 to add to "read" chunks
267     */
268    public abstract void addReadChunks(@NonNull Set<BlockVector2> readChunks);
269
270    /**
271     * Add a {@link BlockVector2} Chunk coordinate to the Read Chunks list
272     *
273     * @param chunk BlockVector2 to add to "read" chunks
274     */
275    public abstract void addReadChunk(@NonNull BlockVector2 chunk);
276
277    /**
278     * Whether chunks should be unloaded after being accessed
279     *
280     * @return if is unloading chunks after accessing them
281     */
282    public abstract boolean isUnloadAfter();
283
284    /**
285     * Set whether chunks should be unloaded after being accessed
286     *
287     * @param unloadAfter if to unload chunks after being accessed
288     */
289    public abstract void setUnloadAfter(boolean unloadAfter);
290
291    /**
292     * Get the {@link CuboidRegion} designated for direct regeneration
293     *
294     * @return CuboidRegion to regenerate
295     */
296    public @Nullable
297    abstract CuboidRegion getRegenRegion();
298
299    /**
300     * Set the {@link CuboidRegion} designated for direct regeneration
301     *
302     * @param regenRegion CuboidRegion to regenerate
303     */
304    public abstract void setRegenRegion(@NonNull CuboidRegion regenRegion);
305
306    /**
307     * Set a specific chunk at the chunk coordinates XZ to be regenerated.
308     *
309     * @param x chunk x
310     * @param z chunk z
311     */
312    public abstract void regenChunk(int x, int z);
313
314    /**
315     * Get the world the queue is writing to
316     *
317     * @return world of the queue
318     */
319    public @Nullable
320    abstract World getWorld();
321
322    /**
323     * Set the queue as having been modified now
324     */
325    public final void setModified() {
326        setModified(System.currentTimeMillis());
327    }
328
329    /**
330     * Enqueue the queue to start it
331     *
332     * @return success or not
333     * @since 6.0.10
334     */
335    public boolean enqueue() {
336        boolean success = false;
337        if (enqueued.compareAndSet(false, true)) {
338            success = true;
339            start();
340        }
341        return success;
342    }
343
344    /**
345     * Start the queue
346     */
347    public abstract void start();
348
349    /**
350     * Cancel the queue
351     */
352    public abstract void cancel();
353
354    /**
355     * Get the task to be run when all chunks have been accessed
356     *
357     * @return task to be run when queue is complete
358     */
359    public abstract Runnable getCompleteTask();
360
361    /**
362     * Set the task to be run when all chunks have been accessed
363     *
364     * @param whenDone task to be run when queue is complete
365     */
366    public abstract void setCompleteTask(@Nullable Runnable whenDone);
367
368    /**
369     * Return the chunk consumer set to the queue or null if one is not set
370     *
371     * @return Consumer to be executed on each chunk in queue
372     */
373    public @Nullable
374    abstract Consumer<BlockVector2> getChunkConsumer();
375
376    /**
377     * Set the Consumer that will be executed on each chunk in queue
378     *
379     * @param consumer Consumer to be executed on each chunk in queue
380     */
381    public abstract void setChunkConsumer(@NonNull Consumer<BlockVector2> consumer);
382
383    /**
384     * Add a {@link ProgressSubscriber} to the Queue to subscribe to the relevant Chunk Processor
385     */
386    public abstract void addProgressSubscriber(@NonNull ProgressSubscriber progressSubscriber);
387
388    /**
389     * Get the {@link LightingMode} to be used when setting blocks
390     */
391    public @NonNull
392    abstract LightingMode getLightingMode();
393
394    /**
395     * Set the {@link LightingMode} to be used when setting blocks
396     *
397     * @param mode lighting mode. Null to use default.
398     */
399    public abstract void setLightingMode(@Nullable LightingMode mode);
400
401    /**
402     * Get the overriding {@link SideEffectSet} to be used by the queue if it exists, else null
403     *
404     * @return Overriding {@link SideEffectSet} or null
405     */
406    public abstract @Nullable SideEffectSet getSideEffectSet();
407
408    /**
409     * Set the overriding {@link SideEffectSet} to be used by the queue. Null to use default side effects.
410     *
411     * @param sideEffectSet side effects to override with, or null to use default
412     */
413    public abstract void setSideEffectSet(@Nullable SideEffectSet sideEffectSet);
414
415    /**
416     * Fill a cuboid between two positions with a BlockState
417     *
418     * @param pos1  1st cuboid position
419     * @param pos2  2nd cuboid position
420     * @param block block to fill
421     */
422    public void setCuboid(@NonNull Location pos1, @NonNull Location pos2, @NonNull BlockState block) {
423        int yMin = Math.min(pos1.getY(), pos2.getY());
424        int yMax = Math.max(pos1.getY(), pos2.getY());
425        int xMin = Math.min(pos1.getX(), pos2.getX());
426        int xMax = Math.max(pos1.getX(), pos2.getX());
427        int zMin = Math.min(pos1.getZ(), pos2.getZ());
428        int zMax = Math.max(pos1.getZ(), pos2.getZ());
429        for (int y = yMin; y <= yMax; y++) {
430            for (int x = xMin; x <= xMax; x++) {
431                for (int z = zMin; z <= zMax; z++) {
432                    setBlock(x, y, z, block);
433                }
434            }
435        }
436    }
437
438    /**
439     * Fill a cuboid between two positions with a Pattern
440     *
441     * @param pos1   1st cuboid position
442     * @param pos2   2nd cuboid position
443     * @param blocks pattern to fill
444     */
445    public void setCuboid(@NonNull Location pos1, @NonNull Location pos2, @NonNull Pattern blocks) {
446        int yMin = Math.min(pos1.getY(), pos2.getY());
447        int yMax = Math.max(pos1.getY(), pos2.getY());
448        int xMin = Math.min(pos1.getX(), pos2.getX());
449        int xMax = Math.max(pos1.getX(), pos2.getX());
450        int zMin = Math.min(pos1.getZ(), pos2.getZ());
451        int zMax = Math.max(pos1.getZ(), pos2.getZ());
452        for (int y = yMin; y <= yMax; y++) {
453            for (int x = xMin; x <= xMax; x++) {
454                for (int z = zMin; z <= zMax; z++) {
455                    setBlock(x, y, z, blocks);
456                }
457            }
458        }
459    }
460
461    /**
462     * Fill a cuboid between two positions with a BiomeType
463     *
464     * @param pos1  1st cuboid position
465     * @param pos2  2nd cuboid position
466     * @param biome biome to fill
467     */
468    public void setBiomeCuboid(@NonNull Location pos1, @NonNull Location pos2, @NonNull BiomeType biome) {
469        int yMin = Math.min(pos1.getY(), pos2.getY());
470        int yMax = Math.max(pos1.getY(), pos2.getY());
471        int xMin = Math.min(pos1.getX(), pos2.getX());
472        int xMax = Math.max(pos1.getX(), pos2.getX());
473        int zMin = Math.min(pos1.getZ(), pos2.getZ());
474        int zMax = Math.max(pos1.getZ(), pos2.getZ());
475        for (int y = yMin; y <= yMax; y++) {
476            for (int x = xMin; x <= xMax; x++) {
477                for (int z = zMin; z <= zMax; z++) {
478                    setBiome(x, y, z, biome);
479                }
480            }
481        }
482    }
483
484    /**
485     * Get the min Y limit associated with the queue
486     */
487    protected int getMinY() {
488        return getWorld() != null ? getWorld().getMinY() : PlotSquared.platform().versionMinHeight();
489    }
490
491    /**
492     * Get the max Y limit associated with the queue
493     */
494    protected int getMaxY() {
495        return getWorld() != null ? getWorld().getMinY() : PlotSquared.platform().versionMaxHeight();
496    }
497
498    /**
499     * Get the min chunk layer associated with the queue. Usually 0 or -4;
500     */
501    protected int getMinLayer() {
502        return (getWorld() != null ? getWorld().getMinY() : PlotSquared.platform().versionMinHeight()) >> 4;
503    }
504
505    /**
506     * Get the max chunk layer associated with the queue. Usually 15 or 19
507     */
508    protected int getMaxLayer() {
509        return (getWorld() != null ? getWorld().getMaxY() : PlotSquared.platform().versionMaxHeight()) >> 4;
510    }
511
512}