/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.regions.file.basic;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.jcwhatever.nucleus.mixins.IPluginOwned;
import com.jcwhatever.nucleus.regions.file.IRegionFileData;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.file.IAppliedSerializable;
import com.jcwhatever.nucleus.utils.materials.Materials;
import com.jcwhatever.nucleus.utils.performance.queued.QueueTask;
import com.jcwhatever.nucleus.utils.performance.queued.TaskConcurrency;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.plugin.Plugin;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Builds region data into a world.
*
* <p>Basic implementation of {@link IRegionFileData}</p>
*/
public class WorldBuilder implements IRegionFileData, IPluginOwned {
private final Plugin _plugin;
private final World _world;
private final Object _sync = new Object();
private Builder _builder;
/**
* Constructor.
*
* @param plugin The owning plugin.
* @param world The world to build in.
*/
public WorldBuilder(Plugin plugin, World world) {
PreCon.notNull(plugin);
PreCon.notNull(world);
_plugin = plugin;
_world = world;
_builder = new Builder(plugin, world);
}
@Override
public Plugin getPlugin() {
return _plugin;
}
public World getWorld() {
return _world;
}
@Override
public void addBlock(int x, int y, int z, Material material, int data, int light, int skylight) {
synchronized (_sync) {
_builder.blockQueue.add(new BlockInfo(x, y, z, material, data));
}
}
@Override
public void addSerializable(IAppliedSerializable blockEntity) {
synchronized (_sync) {
_builder.serializables.add(blockEntity);
}
}
@Override
@Nullable
public QueueTask commit() {
Builder builder;
synchronized (_sync) {
builder = _builder;
_builder = new Builder(getPlugin(), getWorld());
}
return builder;
}
private static class BlockInfo implements Comparable<BlockInfo> {
final int x;
final int y;
final int z;
final Material material;
final int data;
BlockInfo(int x, int y, int z, Material material, int data) {
this.x = x;
this.y = y;
this.z = z;
this.material = material;
this.data = data;
}
@Override
public int compareTo(BlockInfo o) {
//noinspection SuspiciousNameCombination
return Integer.compare(this.y, o.y);
}
}
/*
* Restore blocks from blockInfo stack on the main thread.
*/
private static class Builder extends QueueTask {
World world;
Queue<BlockInfo> blockQueue = new ArrayDeque<>(512);
Queue<IAppliedSerializable> serializables = new ArrayDeque<>(100);
/**
* Constructor.
*
* @param plugin The owning plugin.
* @param world The world to build in.
*/
public Builder(Plugin plugin, World world) {
super(plugin, TaskConcurrency.MAIN_THREAD);
this.world = world;
}
@Override
protected void onRun() {
Deque<BlockInfo> multiBlocks = new ArrayDeque<>(blockQueue.size());
while (!blockQueue.isEmpty()) {
BlockInfo info = blockQueue.remove();
// skip multi-blocks and restore afterwards
if (Materials.isMultiBlock(info.material)) {
multiBlocks.add(info);
continue;
}
restoreBlock(info);
}
// Restore block Pairs
// keyed to block x, y z value as a string
Multimap<String, BlockInfo> placedMultiBlocks =
MultimapBuilder.hashKeys(multiBlocks.size()).hashSetValues(3).build();
// Get block pairs
while (!multiBlocks.isEmpty()) {
BlockInfo info = multiBlocks.remove();
int x = info.x;
int y = info.y;
int z = info.z;
String lowerKey = getKey(x, y - 1, z);
Collection<BlockInfo> lowerBlock = placedMultiBlocks.get(lowerKey);
if (lowerBlock != null) {
placedMultiBlocks.put(lowerKey, info);
continue;
}
String upperKey = getKey(x, y + 1, z);
Collection<BlockInfo> upperBlock = placedMultiBlocks.get(upperKey);
if (upperBlock != null) {
placedMultiBlocks.put(upperKey, info);
continue;
}
placedMultiBlocks.put(getKey(x, y, z), info);
}
// Restore pairs
Set<String> keys = placedMultiBlocks.keySet();
for (String key : keys) {
Collection<BlockInfo> multiBlockSetSet = placedMultiBlocks.get(key);
if (multiBlockSetSet == null)
continue;
List<BlockInfo> multiBlockSet = new ArrayList<>(multiBlockSetSet);
Collections.sort(multiBlockSet);
for (BlockInfo info : multiBlockSet) {
restoreBlock(info);
}
}
// restore serializables
while (!serializables.isEmpty()) {
IAppliedSerializable meta = serializables.remove();
meta.apply();
}
complete();
}
/*
* Get a block map key using its coordinates
*/
private String getKey(int x, int y, int z) {
return String.valueOf(x) + '.' + y + '.' + z;
}
/*
* Restore a block
*/
private void restoreBlock(BlockInfo info) {
int x = info.x;
int y = info.y;
int z = info.z;
Block block = this.world.getBlockAt(x, y, z);
BlockState state = block.getState();
state.setType(info.material);
state.setRawData((byte) info.data);
state.update(true);
}
}
}