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.common.base.Preconditions; 022import com.google.inject.Inject; 023import com.plotsquared.core.configuration.Settings; 024import com.plotsquared.core.inject.factory.ChunkCoordinatorFactory; 025import com.plotsquared.core.location.Location; 026import com.plotsquared.core.queue.subscriber.ProgressSubscriber; 027import com.sk89q.worldedit.math.BlockVector2; 028import com.sk89q.worldedit.world.World; 029import org.checkerframework.checker.nullness.qual.NonNull; 030import org.checkerframework.checker.nullness.qual.Nullable; 031 032import java.util.ArrayList; 033import java.util.Collection; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.function.Consumer; 037 038/** 039 * Builds a {@link ChunkCoordinator} instance 040 */ 041public class ChunkCoordinatorBuilder { 042 043 private final List<BlockVector2> requestedChunks = new LinkedList<>(); 044 private final List<ProgressSubscriber> progressSubscribers = new ArrayList<>(); 045 private final ChunkCoordinatorFactory chunkCoordinatorFactory; 046 private Consumer<Throwable> throwableConsumer = Throwable::printStackTrace; 047 private World world; 048 private Consumer<BlockVector2> chunkConsumer; 049 private Runnable whenDone = () -> { 050 }; 051 private long maxIterationTime = Settings.QUEUE.MAX_ITERATION_TIME; // A little over 1 tick; 052 private int initialBatchSize = Settings.QUEUE.INITIAL_BATCH_SIZE; 053 private boolean unloadAfter = true; 054 private boolean forceSync = false; 055 private boolean shouldGen = true; 056 057 @Inject 058 public ChunkCoordinatorBuilder(@NonNull ChunkCoordinatorFactory chunkCoordinatorFactory) { 059 this.chunkCoordinatorFactory = chunkCoordinatorFactory; 060 } 061 062 /** 063 * Set the world 064 * 065 * @param world world 066 * @return this ChunkCoordinatorBuilder instance 067 */ 068 public @NonNull ChunkCoordinatorBuilder inWorld(final @NonNull World world) { 069 this.world = Preconditions.checkNotNull(world, "World may not be null"); 070 return this; 071 } 072 073 /** 074 * Add a chunk to be accessed 075 * 076 * @param chunkLocation BlockVector2 of chunk to add 077 * @return this ChunkCoordinatorBuilder instance 078 */ 079 public @NonNull ChunkCoordinatorBuilder withChunk(final @NonNull BlockVector2 chunkLocation) { 080 this.requestedChunks.add(Preconditions.checkNotNull(chunkLocation, "Chunk location may not be null")); 081 return this; 082 } 083 084 /** 085 * Add a Collection of chunks to be accessed 086 * 087 * @param chunkLocations Collection of BlockVector2 to add 088 * @return this ChunkCoordinatorBuilder instance 089 */ 090 public @NonNull ChunkCoordinatorBuilder withChunks(final @NonNull Collection<BlockVector2> chunkLocations) { 091 chunkLocations.forEach(this::withChunk); 092 return this; 093 } 094 095 /** 096 * Add chunks within a region to be accessed 097 * 098 * @param pos1 minimum region location 099 * @param pos2 maximum region location 100 * @return this ChunkCoordinatorBuilder instance 101 */ 102 public @NonNull ChunkCoordinatorBuilder withRegion(@NonNull Location pos1, @NonNull Location pos2) { 103 final int p1x = pos1.getX(); 104 final int p1z = pos1.getZ(); 105 final int p2x = pos2.getX(); 106 final int p2z = pos2.getZ(); 107 final int bcx = p1x >> 4; 108 final int bcz = p1z >> 4; 109 final int tcx = p2x >> 4; 110 final int tcz = p2z >> 4; 111 final ArrayList<BlockVector2> chunks = new ArrayList<>(); 112 113 for (int x = bcx; x <= tcx; x++) { 114 for (int z = bcz; z <= tcz; z++) { 115 chunks.add(BlockVector2.at(x, z)); 116 } 117 } 118 119 chunks.forEach(this::withChunk); 120 return this; 121 } 122 123 /** 124 * Set the consumer to be used when a chunk is loaded 125 * 126 * @param chunkConsumer Consumer to be used by the ChunkCoordinator 127 * @return this ChunkCoordinatorBuilder instance 128 */ 129 public @NonNull ChunkCoordinatorBuilder withConsumer(final @NonNull Consumer<BlockVector2> chunkConsumer) { 130 this.chunkConsumer = Preconditions.checkNotNull(chunkConsumer, "Chunk consumer may not be null"); 131 return this; 132 } 133 134 /** 135 * Set the Runnable to run when all chunks have been accessed 136 * 137 * @param whenDone task to run when all chunks are accessed 138 * @return this ChunkCoordinatorBuilder instance 139 */ 140 public @NonNull ChunkCoordinatorBuilder withFinalAction(final @Nullable Runnable whenDone) { 141 if (whenDone == null) { 142 return this; 143 } 144 this.whenDone = whenDone; 145 return this; 146 } 147 148 /** 149 * Set the max time taken while iterating over and accessing loaded chunks 150 * 151 * @param maxIterationTime max iteration time 152 * @return this ChunkCoordinatorBuilder instance 153 */ 154 public @NonNull ChunkCoordinatorBuilder withMaxIterationTime(final long maxIterationTime) { 155 Preconditions.checkArgument(maxIterationTime > 0, "Max iteration time must be positive"); 156 this.maxIterationTime = maxIterationTime; 157 return this; 158 } 159 160 /** 161 * Set the initial batch size to be used for loading chunks 162 * 163 * @param initialBatchSize initial batch size 164 * @return this ChunkCoordinatorBuilder instance 165 */ 166 public @NonNull ChunkCoordinatorBuilder withInitialBatchSize(final int initialBatchSize) { 167 Preconditions.checkArgument(initialBatchSize > 0, "Initial batch size must be positive"); 168 this.initialBatchSize = initialBatchSize; 169 return this; 170 } 171 172 /** 173 * Set the consumer to be used to handle {@link Throwable}s 174 * 175 * @param throwableConsumer consumer to hanble throwables 176 * @return this ChunkCoordinatorBuilder instance 177 */ 178 public @NonNull ChunkCoordinatorBuilder withThrowableConsumer(final @NonNull Consumer<Throwable> throwableConsumer) { 179 this.throwableConsumer = Preconditions.checkNotNull(throwableConsumer, "Throwable consumer may not be null"); 180 return this; 181 } 182 183 /** 184 * Set whether the chunks should be allow to unload after being accessed. This should only be used where the chunks are read from 185 * and then written to from a separate queue where they're consequently unloaded. 186 * 187 * @param unloadAfter if to unload chunks afterwards 188 * @return this ChunkCoordinatorBuilder instance 189 */ 190 public @NonNull ChunkCoordinatorBuilder unloadAfter(final boolean unloadAfter) { 191 this.unloadAfter = unloadAfter; 192 return this; 193 } 194 195 /** 196 * Set whether the chunks coordinator should be forced to be synchronous. This is not necessarily synchronous to the server, 197 * and simply effectively makes {@link ChunkCoordinator#start()} ()} a blocking operation. 198 * 199 * @param forceSync force sync or not 200 * @since 6.9.0 201 */ 202 public @NonNull ChunkCoordinatorBuilder forceSync(final boolean forceSync) { 203 this.forceSync = forceSync; 204 return this; 205 } 206 207 /** 208 * Set whether chunks should be generated as part of this operation. Default is true. Disabling this may not be supported 209 * depending on server implementation. (i.e. setting to false may not actually disable generation as part of this operation 210 * - this is just a catch-all in case of future differing server implementations; the option will work on Spigot/Paper). 211 * 212 * @param shouldGen should generate new chunks or not 213 * @since 7.5.0 214 */ 215 public @NonNull ChunkCoordinatorBuilder shouldGen(final boolean shouldGen) { 216 this.shouldGen = shouldGen; 217 return this; 218 } 219 220 public @NonNull ChunkCoordinatorBuilder withProgressSubscriber(ProgressSubscriber progressSubscriber) { 221 this.progressSubscribers.add(progressSubscriber); 222 return this; 223 } 224 225 public @NonNull ChunkCoordinatorBuilder withProgressSubscribers(Collection<ProgressSubscriber> progressSubscribers) { 226 this.progressSubscribers.addAll(progressSubscribers); 227 return this; 228 } 229 230 /** 231 * Create a new {@link ChunkCoordinator} instance based on the values in the Builder instance. 232 * 233 * @return a new ChunkCoordinator 234 */ 235 public @NonNull ChunkCoordinator build() { 236 Preconditions.checkNotNull(this.world, "No world was supplied"); 237 Preconditions.checkNotNull(this.chunkConsumer, "No chunk consumer was supplied"); 238 Preconditions.checkNotNull(this.whenDone, "No final action was supplied"); 239 Preconditions.checkNotNull(this.throwableConsumer, "No throwable consumer was supplied"); 240 return chunkCoordinatorFactory 241 .create( 242 this.maxIterationTime, 243 this.initialBatchSize, 244 this.chunkConsumer, 245 this.world, 246 this.requestedChunks, 247 this.whenDone, 248 this.throwableConsumer, 249 this.unloadAfter, 250 this.progressSubscribers, 251 this.forceSync, 252 this.shouldGen 253 ); 254 } 255 256}