/*
* This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT).
*
* Copyright (c) 2015 contributors
*
* 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 cubicchunks.world.column;
import com.google.common.collect.Lists;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Nonnull;
import cubicchunks.world.cube.Cube;
/**
* Stores cubes for columns
*/
class CubeMap implements Iterable<Cube> {
private final List<Cube> cubes = new ArrayList<>();
private ExtendedBlockStorage[] toBlockTick = new ExtendedBlockStorage[0];
/**
* Removes the cube at {@code cubeY}
*
* @param cubeY cube y position
*
* @return the removed cube if it existed, otherwise <code>null</code>
*/
Cube remove(int cubeY) {
int index = binarySearch(cubeY);
return index < cubes.size() && cubes.get(index).getY() == cubeY ? cubes.remove(index) : null;
}
/**
* Adds a cube
*
* @param cube the cube to add
*/
void put(@Nonnull Cube cube) {
int searchIndex = binarySearch(cube.getY());
if (this.contains(cube.getY(), searchIndex)) {
throw new IllegalArgumentException("Cube at " + cube.getY() + " already exists!");
}
cubes.add(searchIndex, cube);
}
/**
* Iterate over all cubes between <code>startY</code> and <code>endY</code> in this storage in order. If
* <code>startY < endY</code>, order is bottom to top, otherwise order is top to bottom.
*
* @param startY initial cube y position
* @param endY last cube y position
*
* @return an iterator over the cubes
*/
Iterable<Cube> cubes(int startY, int endY) {
boolean reverse = false;
if (startY > endY) {
int i = startY;
startY = endY;
endY = i;
reverse = true;
}
int bottom = binarySearch(startY);
int top = binarySearch(endY + 1); // subList()'s second arg is exclusive so we need to add 1
if (bottom < cubes.size() && top <= cubes.size()) {
return reverse ? Lists.reverse(cubes.subList(bottom, top)) : cubes.subList(bottom, top);
} else {
return Collections.emptyList();
}
}
/**
* Check if the target cube is stored here
*
* @param cubeY the y coordinate of the cube
* @param searchIndex the index to search at (got form {@link #binarySearch(int)})
*
* @return <code>true</code> if the cube is contained here, <code>false</code> otherwise
*/
private boolean contains(int cubeY, int searchIndex) {
return searchIndex < cubes.size() && cubes.get(searchIndex).getY() == cubeY;
}
/**
* Iterate over all cubes in this storage
*
* @return the iterator
*/
@Override public Iterator<Cube> iterator() {
return cubes.iterator();
}
/**
* Retrieve a collection of all cubes within this storage. The collection is non-modifiable
*
* @return the collection
*/
public Collection<Cube> all() {
return Collections.unmodifiableCollection(cubes);
}
/**
* Check if this storage is empty
*
* @return <code>true</code> if there are no cubes in this storage, <code>false</code> otherwise
*/
public boolean isEmpty() {
return cubes.isEmpty();
}
/**
* @return An array of EBSs from cubes that need ticking
*/
ExtendedBlockStorage[] getStoragesToTick() {
if (!isToTickValid()) {
int count = 0;
for (Cube cube : cubes) {
if (cube.getStorage() != null && cube.getTickets().shouldTick()) {
count++;
}
}
toBlockTick = new ExtendedBlockStorage[count];
count = 0;
for (Cube cube : cubes) {
if (cube.getStorage() != null && cube.getTickets().shouldTick()) {
toBlockTick[count++] = cube.getStorage();
}
}
}
return toBlockTick;
}
private boolean isToTickValid() {
int index = 0;
for (Cube cube : cubes) {
if (cube.getStorage() != null && cube.getTickets().shouldTick()) {
if (index >= toBlockTick.length) {
return false;
}
if (toBlockTick[index++] != cube.getStorage()) {
return false;
}
}
}
return index == toBlockTick.length; // did we check everything there was in toBlockTick?
}
/**
* Binary search for the index of the specified cube. If the cube is not present, returns the index at which it
* should be inserted.
*
* @param cubeY cube y position
*
* @return the target index
*/
private int binarySearch(int cubeY) {
int start = 0;
int end = cubes.size() - 1;
int mid = 0;
while (start <= end) {
mid = start + end >>> 1;
int at = cubes.get(mid).getY();
if (at < cubeY) { // we are below the target;
start = mid + 1;
} else if (at > cubeY) {
end = mid - 1; // we are above the target
} else {
return mid;// found target!
}
}
return start; // not found :(
}
}