Allow "post processing" of chunks (#658)

* begin allowing "post processing" of chunks
 - restores legacy capability to continue saving edits in the background after sending the chunks
 - speeds up the edit clientside
 - nail in the coffin of the terrible and staticly coded coreedit
 - We should totally make IronGolem work so Core* is no longer used by anyone

* begin allowing background history saving

* Handle post processors in queues properly

* Use futures for postprocessing so we're not waiting for them needlessly

* better use of closed boolean

* Reword
This commit is contained in:
dordsor21
2020-09-28 11:13:02 +01:00
committed by GitHub
parent 2aef0ee27e
commit 82bcc0e9a5
37 changed files with 811 additions and 18 deletions

View File

@ -11,6 +11,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.function.Function;
public interface IBatchProcessor {
@ -23,6 +24,8 @@ public interface IBatchProcessor {
*/
IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set);
Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set);
default boolean processGet(int chunkX, int chunkZ) {
return true;
}
@ -115,6 +118,10 @@ public interface IBatchProcessor {
return MultiBatchProcessor.of(this, other);
}
default IBatchProcessor joinPost(IBatchProcessor other) {
return MultiBatchProcessor.of(this, other);
}
default void flush() {}
/**

View File

@ -7,6 +7,7 @@ import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import java.util.concurrent.Future;
@ -46,4 +47,13 @@ public interface IChunkGet extends IBlocks, Trimable, InputExtent, ITileInput {
<T extends Future<T>> T call(IChunkSet set, Runnable finalize);
CompoundTag getEntity(UUID uuid);
void setCreateCopy(boolean createCopy);
boolean isCreateCopy();
@Nullable
default IChunkGet getCopy() {
return null;
}
}

View File

@ -98,6 +98,12 @@ public class FallbackChunkGet implements IChunkGet {
return null;
}
@Override public void setCreateCopy(boolean createCopy) {}
@Override public boolean isCreateCopy() {
return false;
}
@Override
public boolean trim(boolean aggressive) {
return true;

View File

@ -63,6 +63,12 @@ public final class NullChunkGet implements IChunkGet {
return null;
}
@Override public void setCreateCopy(boolean createCopy) {}
@Override public boolean isCreateCopy() {
return false;
}
public boolean trim(boolean aggressive) {
return true;
}

View File

@ -9,6 +9,7 @@ import com.boydti.fawe.beta.IQueueChunk;
import com.boydti.fawe.beta.IQueueExtent;
import com.boydti.fawe.beta.implementation.filter.block.ChunkFilterBlock;
import com.boydti.fawe.beta.implementation.lighting.HeightMapType;
import com.boydti.fawe.beta.implementation.processors.EmptyBatchProcessor;
import com.boydti.fawe.beta.implementation.queue.Pool;
import com.boydti.fawe.config.Settings;
import com.sk89q.jnbt.CompoundTag;
@ -20,11 +21,11 @@ import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import org.jetbrains.annotations.Range;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Future;
import javax.annotation.Nullable;
/**
* An abstract {@link IChunk} class that implements basic get/set blocks
@ -47,6 +48,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
private boolean fastmode;
private int bitMask = -1; // Allow forceful setting of bitmask (for lighting)
private boolean isInit = false; // Lighting handles queue differently. It relies on the chunk cache and not doing init.
private boolean createCopy = false;
private ChunkHolder() {
this.delegate = NULL;
@ -140,6 +142,14 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
return delegate.get(this).getEntity(uuid);
}
@Override public void setCreateCopy(boolean createCopy) {
this.createCopy = createCopy;
}
@Override public boolean isCreateCopy() {
return createCopy;
}
private static final IBlockDelegate BOTH = new IBlockDelegate() {
@Override
public IChunkGet get(ChunkHolder chunk) {
@ -778,9 +788,15 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
if (set != null) {
IChunkGet get = getOrCreateGet();
get.trim(false);
boolean postProcess = !(getExtent().getPostProcessor() instanceof EmptyBatchProcessor);
get.setCreateCopy(postProcess);
set = getExtent().processSet(this, get, set);
if (set != null) {
try {
return get.call(set, finalize);
} finally {
if (postProcess) {
getExtent().postProcessSet(this, get.getCopy(), set);
}
}
}
return null;

View File

@ -171,6 +171,12 @@ public final class NullChunk implements IQueueChunk {
return null;
}
@Override public void setCreateCopy(boolean createCopy) {}
@Override public boolean isCreateCopy() {
return false;
}
@Nullable
public <T extends Future<T>> T call(@Nullable IChunkSet set, @Nullable Runnable finalize) {
return null;

View File

@ -5,19 +5,32 @@ import com.boydti.fawe.beta.IChunk;
import com.boydti.fawe.beta.IChunkGet;
import com.boydti.fawe.beta.IChunkSet;
import java.util.concurrent.Future;
public class BatchProcessorHolder implements IBatchProcessorHolder {
private IBatchProcessor processor = EmptyBatchProcessor.getInstance();
private IBatchProcessor postProcessor = EmptyBatchProcessor.getInstance();
@Override
public IBatchProcessor getProcessor() {
return processor;
}
@Override
public IBatchProcessor getPostProcessor() {
return postProcessor;
}
@Override
public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return getProcessor().processSet(chunk, get, set);
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return getPostProcessor().postProcessSet(chunk, get, set);
}
@Override
public void flush() {
getProcessor().flush();
@ -28,6 +41,11 @@ public class BatchProcessorHolder implements IBatchProcessorHolder {
this.processor = set;
}
@Override
public void setPostProcessor(IBatchProcessor set) {
this.postProcessor = set;
}
@Override
public String toString() {
IBatchProcessor tmp = getProcessor();

View File

@ -12,6 +12,8 @@ import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.world.World;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Supplier;
public class ChunkSendProcessor implements IBatchProcessor {
@ -65,6 +67,12 @@ public class ChunkSendProcessor implements IBatchProcessor {
return set;
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
// Doesn't need to do anything
return CompletableFuture.completedFuture(set);
}
public IBlocks combine(IChunk chunk, IChunkGet get, IChunkSet set) {
return new CombinedBlocks(get, set, 0);
}
@ -73,4 +81,4 @@ public class ChunkSendProcessor implements IBatchProcessor {
public Extent construct(Extent child) {
throw new UnsupportedOperationException("Processing only");
}
}
}

View File

@ -9,6 +9,9 @@ import com.sk89q.worldedit.extent.Extent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
public final class EmptyBatchProcessor implements IBatchProcessor {
private static final EmptyBatchProcessor instance = new EmptyBatchProcessor();
@ -26,11 +29,23 @@ public final class EmptyBatchProcessor implements IBatchProcessor {
return set;
}
@Override
@NotNull
public Future<IChunkSet> postProcessSet(@Nullable IChunk chunk, @Nullable IChunkGet get, @Nullable IChunkSet set) {
// Doesn't need to do anything
return CompletableFuture.completedFuture(set);
}
@NotNull
public IBatchProcessor join(@Nullable IBatchProcessor other) {
return other;
}
@NotNull
public IBatchProcessor joinPost(@Nullable IBatchProcessor other) {
return other;
}
private EmptyBatchProcessor() {
}

View File

@ -1,6 +1,7 @@
package com.boydti.fawe.beta.implementation.processors;
import com.boydti.fawe.beta.IBatchProcessor;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.changeset.AbstractChangeSet;
import com.sk89q.worldedit.extent.Extent;
@ -11,9 +12,19 @@ public abstract class ExtentBatchProcessorHolder extends BatchProcessorHolder im
return this;
}
@Override
public Extent addPostProcessor(IBatchProcessor processor) {
joinPost(processor);
return this;
}
@Override
public Extent enableHistory(AbstractChangeSet changeSet) {
return this.addProcessor(changeSet);
if (Settings.IMP.EXPERIMENTAL.SEND_BEFORE_HISTORY) {
return this.addPostProcessor(changeSet);
} else {
return this.addProcessor(changeSet);
}
}
@Override

View File

@ -6,6 +6,8 @@ import com.boydti.fawe.beta.IChunkGet;
import com.boydti.fawe.beta.IChunkSet;
import com.sk89q.worldedit.extent.Extent;
import java.util.concurrent.Future;
/**
* Holds a batch processor
* (Join and remove operations affect the held processor)
@ -13,17 +15,26 @@ import com.sk89q.worldedit.extent.Extent;
public interface IBatchProcessorHolder extends IBatchProcessor {
IBatchProcessor getProcessor();
IBatchProcessor getPostProcessor();
/**
* set the held processor
* @param set
*/
void setProcessor(IBatchProcessor set);
void setPostProcessor(IBatchProcessor set);
@Override
default IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return getProcessor().processSet(chunk, get, set);
}
@Override
default Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return getPostProcessor().postProcessSet(chunk, get, set);
}
@Override
default boolean processGet(int chunkX, int chunkZ) {
return getProcessor().processGet(chunkX, chunkZ);
@ -40,6 +51,12 @@ public interface IBatchProcessorHolder extends IBatchProcessor {
return this;
}
@Override
default IBatchProcessor joinPost(IBatchProcessor other) {
setPostProcessor(getPostProcessor().joinPost(other));
return this;
}
@Override
default <T extends IBatchProcessor> IBatchProcessor remove(Class<T> clazz) {
setProcessor(getProcessor().remove(clazz));

View File

@ -14,6 +14,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.Map;
import java.util.function.Supplier;
@ -84,6 +86,23 @@ public class MultiBatchProcessor implements IBatchProcessor {
}
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
try {
for (int i = processors.length - 1 ; i >= 0; i--) {
IBatchProcessor processor = processors[i];
set = processor.postProcessSet(chunk, get, set).get();
if (set == null) {
return null;
}
}
return CompletableFuture.completedFuture(set);
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
@Override
public boolean processGet(int chunkX, int chunkZ) {
for (IBatchProcessor processor : this.processors) {
@ -121,6 +140,18 @@ public class MultiBatchProcessor implements IBatchProcessor {
return this;
}
@Override
public IBatchProcessor joinPost(IBatchProcessor other) {
if (other instanceof MultiBatchProcessor) {
for (IBatchProcessor processor : ((MultiBatchProcessor) other).processors) {
addBatchProcessor(processor);
}
} else {
addBatchProcessor(other);
}
return this;
}
@Override
public void flush() {
for (IBatchProcessor processor : this.processors) processor.flush();

View File

@ -9,6 +9,8 @@ import com.sk89q.worldedit.extent.NullExtent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.Future;
public final class NullProcessor implements IBatchProcessor {
private static final NullProcessor instance = new NullProcessor();
@ -21,6 +23,11 @@ public final class NullProcessor implements IBatchProcessor {
return null;
}
@Nullable
public Future<IChunkSet> postProcessSet(@NotNull IChunk chunk, @NotNull IChunkGet get, @NotNull IChunkSet set) {
return null;
}
@NotNull
public Extent construct(@NotNull Extent child) {
return new NullExtent();

View File

@ -43,14 +43,16 @@ public class ParallelQueueExtent extends PassthroughExtent implements IQueueWrap
private final World world;
private final QueueHandler handler;
private final BatchProcessorHolder processor;
private final BatchProcessorHolder postProcessor;
private int changes;
private final boolean fastmode;
public ParallelQueueExtent(QueueHandler handler, World world, boolean fastmode) {
super(handler.getQueue(world, new BatchProcessorHolder()));
super(handler.getQueue(world, new BatchProcessorHolder(), new BatchProcessorHolder()));
this.world = world;
this.handler = handler;
this.processor = (BatchProcessorHolder) getExtent().getProcessor();
this.postProcessor = (BatchProcessorHolder) getExtent().getPostProcessor();
this.fastmode = fastmode;
}
@ -63,19 +65,21 @@ public class ParallelQueueExtent extends PassthroughExtent implements IQueueWrap
public boolean cancel() {
if (super.cancel()) {
processor.setProcessor(new NullExtent(this, FaweCache.MANUAL));
postProcessor.setPostProcessor(new NullExtent(this, FaweCache.MANUAL));
return true;
}
return false;
}
private IQueueExtent<IQueueChunk> getNewQueue() {
return wrapQueue(handler.getQueue(this.world, this.processor));
return wrapQueue(handler.getQueue(this.world, this.processor, this.postProcessor));
}
@Override
public IQueueExtent<IQueueChunk> wrapQueue(IQueueExtent<IQueueChunk> queue) {
// TODO wrap
queue.setProcessor(this.processor);
queue.setPostProcessor(this.postProcessor);
return queue;
}

View File

@ -280,10 +280,10 @@ public abstract class QueueHandler implements Trimable, Runnable {
public abstract void endSet(boolean parallel);
public IQueueExtent<IQueueChunk> getQueue(World world) {
return getQueue(world, null);
return getQueue(world, null, null);
}
public IQueueExtent<IQueueChunk> getQueue(World world, IBatchProcessor processor) {
public IQueueExtent<IQueueChunk> getQueue(World world, IBatchProcessor processor, IBatchProcessor postProcessor) {
final IQueueExtent<IQueueChunk> queue = pool();
IChunkCache<IChunkGet> cacheGet = getOrCreateWorldCache(world);
IChunkCache<IChunkSet> set = null; // TODO cache?
@ -291,6 +291,9 @@ public abstract class QueueHandler implements Trimable, Runnable {
if (processor != null) {
queue.setProcessor(processor);
}
if (postProcessor != null) {
queue.setPostProcessor(postProcessor);
}
return queue;
}

View File

@ -113,6 +113,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
this.currentThread = null;
this.initialized = false;
this.setProcessor(EmptyBatchProcessor.getInstance());
this.setPostProcessor(EmptyBatchProcessor.getInstance());
}
/**
@ -132,6 +133,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
this.cacheGet = get;
this.cacheSet = set;
this.setProcessor(EmptyBatchProcessor.getInstance());
this.setPostProcessor(EmptyBatchProcessor.getInstance());
initialized = true;
}

View File

@ -393,6 +393,12 @@ public class Settings extends Config {
"This will increase time taken slightly."
})
public boolean ALLOW_TICK_EXISTING = true;
@Comment({
"Do not wait for a chunk's history to save before sending it",
" - Undo/redo commands will wait until the history has been written to disk before executing",
" - Requires combine_stages = true"
})
public boolean SEND_BEFORE_HISTORY = true;
}
public static class PLOTSQUARED_INTEGRATION {

View File

@ -62,6 +62,8 @@ public class MCAChunk implements IChunk {
public int chunkX;
public int chunkZ;
private boolean createCopy = false;
public MCAChunk() {}
private boolean readLayer(Section section) {
@ -602,6 +604,14 @@ public class MCAChunk implements IChunk {
return this.entities.get(uuid);
}
@Override public void setCreateCopy(boolean createCopy) {
this.createCopy = createCopy;
}
@Override public boolean isCreateCopy() {
return createCopy;
}
@Override
public Future call(IChunkSet set, Runnable finalize) {
return null;

View File

@ -59,7 +59,6 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
if (closed) {
return;
}
closed = true;
waitingAsync.incrementAndGet();
TaskManager.IMP.async(() -> {
waitingAsync.decrementAndGet();
@ -97,8 +96,8 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
@Override
public void close() throws IOException {
if (!closed) {
closed = true;
flush();
closed = true;
}
}
@ -204,6 +203,11 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
return set;
}
@Override
public Future<IChunkSet> postProcessSet(final IChunk chunk, final IChunkGet get,final IChunkSet set) {
return (Future<IChunkSet>) addWriteTask(() -> processSet(chunk, get, set));
}
public abstract void addTileCreate(CompoundTag tag);
public abstract void addTileRemove(CompoundTag tag);

View File

@ -10,6 +10,8 @@ import com.sk89q.worldedit.regions.Region;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
public class HeightBoundExtent extends FaweRegionExtent {
@ -51,4 +53,9 @@ public class HeightBoundExtent extends FaweRegionExtent {
}
return null;
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return CompletableFuture.completedFuture(set);
}
}

View File

@ -10,6 +10,7 @@ import com.sk89q.worldedit.regions.RegionIntersection;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Future;
public class MultiRegionExtent extends FaweRegionExtent {
@ -86,4 +87,9 @@ public class MultiRegionExtent extends FaweRegionExtent {
public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return intersection.processSet(chunk, get, set);
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return intersection.postProcessSet(chunk, get, set);
}
}

View File

@ -37,6 +37,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
import javax.annotation.Nullable;
//todo This should be removed in favor of com.sk89q.worldedit.extent.NullExtent
@ -340,6 +341,11 @@ public class NullExtent extends FaweRegionExtent implements IBatchProcessor {
throw reason;
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
throw reason;
}
@Override
public boolean processGet(int chunkX, int chunkZ) {
throw reason;

View File

@ -9,6 +9,7 @@ import com.sk89q.worldedit.regions.Region;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Future;
public class SingleRegionExtent extends FaweRegionExtent {
@ -44,6 +45,11 @@ public class SingleRegionExtent extends FaweRegionExtent {
return region.processSet(chunk, get, set);
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return region.postProcessSet(chunk, get, set);
}
@Override
public boolean processGet(int chunkX, int chunkZ) {
return region.containsChunk(chunkX, chunkZ);

View File

@ -317,6 +317,24 @@ public class AbstractDelegateExtent implements Extent {
return this;
}
@Override
public Extent addPostProcessor(IBatchProcessor processor) {
if (Settings.IMP.EXPERIMENTAL.OTHER) {
logger.info("addPostProcessor Info: \t " + processor.getClass().getName());
logger.info("The following is not an error or a crash:");
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
logger.info(stackTraceElement.toString());
}
}
Extent result = this.extent.addPostProcessor(processor);
if (result != this.extent) {
new ExtentTraverser<Extent>(this).setNext(result);
}
return this;
}
@Override
public Extent disableHistory() {
Extent result = this.extent.disableHistory();

View File

@ -23,6 +23,7 @@ import com.boydti.fawe.FaweCache;
import com.boydti.fawe.beta.Filter;
import com.boydti.fawe.beta.IBatchProcessor;
import com.boydti.fawe.beta.implementation.filter.block.ExtentFilterBlock;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.changeset.AbstractChangeSet;
import com.boydti.fawe.object.clipboard.WorldCopyClipboard;
import com.boydti.fawe.object.exception.FaweException;
@ -671,8 +672,16 @@ public interface Extent extends InputExtent, OutputExtent {
return processor.construct(this);
}
default Extent addPostProcessor(IBatchProcessor processor) {
return processor.construct(this);
}
default Extent enableHistory(AbstractChangeSet changeSet) {
return addProcessor(changeSet);
if (Settings.IMP.EXPERIMENTAL.SEND_BEFORE_HISTORY) {
return addPostProcessor(changeSet);
} else {
return addProcessor(changeSet);
}
}
default Extent disableHistory() {

View File

@ -36,6 +36,9 @@ import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import static com.google.common.base.Preconditions.checkNotNull;
/**
@ -106,6 +109,12 @@ public class MaskingExtent extends AbstractDelegateExtent implements IBatchProce
return filter.filter(chunk, get, set, MaskingExtent.this);
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
// This should not do anything otherwise dangerous...
return CompletableFuture.completedFuture(set);
}
@Override
public void applyBlock(final FilterBlock block) {
if (!this.mask.test(block)) {

View File

@ -37,6 +37,8 @@ import com.sk89q.worldedit.world.World;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import javax.annotation.Nullable;
/**
@ -350,6 +352,12 @@ public interface Region extends Iterable<BlockVector3>, Cloneable, IBatchProcess
}
}
@Override
default Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
// Doesn't need to do anything
return CompletableFuture.completedFuture(set);
}
@Override
default Extent construct(Extent child) {
if (isGlobal()) {

View File

@ -34,6 +34,8 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@ -170,6 +172,12 @@ public class RegionIntersection extends AbstractRegion {
return null;
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
// Doesn't need to do anything
return CompletableFuture.completedFuture(set);
}
public List<Region> getRegions() {
return regions;
}