package Hexel.blocks;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import Hexel.Engine;
import Hexel.LogData;
import Hexel.Resources;
import Hexel.blocks.types.Block;
import Hexel.blocks.types.BlockDirt;
import Hexel.blocks.types.BlockEmpty;
import Hexel.blocks.types.BlockLeaf;
import Hexel.blocks.types.BlockSeed;
import Hexel.blocks.types.BlockTransparent;
import Hexel.blocks.types.BlockWater;
import Hexel.blocks.types.BlockWood;
import Hexel.chunk.Chunk;
import Hexel.chunk.ChunkVisibilityManager;
import Hexel.chunk.Chunks;
import Hexel.chunk.HighestBlockChunk;
import Hexel.math.Vector2i;
import Hexel.math.Vector3i;
import Hexel.util.Container;
import Hexel.util.Pair;
public class BlockSimulator {
private Chunks chunks;
private Engine engine;
private ChunkVisibilityManager cVisMan;
private BlockRules blockRules;
public static final int DONT_RUN = Integer.MAX_VALUE;
private Vector3i tmp3i = new Vector3i();
private Vector2i tmp2i = new Vector2i();
private ArrayList<Vector3i> toUpdate = new ArrayList<Vector3i>();
private HashSet<Chunk> fastified = new HashSet<Chunk>();
public BlockSimulator(Engine engine, Chunks chunks, ChunkVisibilityManager cVisMan) {
this.chunks = chunks;
this.cVisMan = cVisMan;
this.engine = engine;
this.blockRules = new BlockRules(engine, new PointInSimRange() {
@Override
public boolean pointIsInRange(Vector3i v) {
for (int i = 0; i < toUpdate.size(); i++){
Vector3i c = toUpdate.get(i);
if (c.x*32 <= v.x && c.x*32+32 > v.x &&
c.y*16 <= v.y && c.y*16+16 > v.y &&
c.z*32 <= v.z && c.z*32+32 > v.z){
return true;
}
}
return false;
}
});
}
private ExecutorService ste = Executors.newSingleThreadExecutor();
public void step() {
this.ste.execute(new Runnable() {
@Override
public void run() {
sim();
}
});
}
public static int nSim = 0;
public static long timeX = 0;
public static long timeY = 0;
public static long timeZ = 0;
private Hashtable<Vector3i, BlockDelta> deltas = new Hashtable<Vector3i, BlockDelta>();
private void sim(){
int step = engine.time;
toUpdate.clear();
fastified.clear();
boolean goAgain = false;
Set<Vector3i> loadedChunks = this.cVisMan.simulatedChunks;
int minNextSimStep = Integer.MAX_VALUE;
for (Vector3i pos : loadedChunks) {
toUpdate.add(pos);
Chunk chunk = BlockSimulator.this.chunks.getChunk(pos);
minNextSimStep = Math.min(minNextSimStep, chunk.nextSimStep);
}
if (minNextSimStep == Integer.MAX_VALUE)
LogData.set("minNextSim", Integer.MAX_VALUE);
else
LogData.set("minNextSim", (minNextSimStep - engine.time) + " " + minNextSimStep);
do {
deltas.clear();
goAgain = false;
for (Vector3i pos : toUpdate) {
Chunk chunk = BlockSimulator.this.chunks.getChunk(pos);
chunk.beingUpdated = true;
simChunk(pos, deltas, step);
if (chunk.nextSimStep == -1){
goAgain = true;
}
}
for (BlockDelta delta : deltas.values()) {
BlockSimulator.this.chunks.setBlock(delta.x, delta.y, delta.z, delta.block, tmp3i, tmp2i, null);
BlockSimulator.this.chunks.setStepsToSim(delta.x, delta.y, delta.z, delta.srcStepsToSim-1, tmp3i);
if (delta.srcStepsToSim-1 > 0){
tmp3i.x = (int) Math.floor(delta.x / 32.0);
tmp3i.y = (int) Math.floor(delta.y / 16.0);
tmp3i.z = (int) Math.floor(delta.z / 32.0);
Chunk chunk = BlockSimulator.this.chunks.getChunk(tmp3i);
if (!fastified.contains(chunk)){
chunk.fastMode = true;
fastified.add(chunk);
}
}
delta.recycle();
}
} while (goAgain);
for (Vector3i pos : toUpdate) {
Chunk chunk = BlockSimulator.this.chunks.getChunk(pos);
chunk.needFirstSim = false;
chunk.beingUpdated = false;
}
for (Chunk chunk : fastified){
chunk.fastMode = false;
}
}
private void simChunk(Vector3i p, Hashtable<Vector3i, BlockDelta> deltas, int step){
Chunk chunk = this.chunks.getChunk(p);
if (chunk.nextSimStep != -1 && chunk.nextSimStep > step) {
return;
}
chunk.nextSimStep = Integer.MAX_VALUE;
Vector2i tmp2 = new Vector2i();
for (int x = 0; x < 32; x++) {
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 32; z++) {
Block b = chunk.get(x, y, z);
int gx = p.x * 32 + x;
int gy = p.y * 16 + y;
int gz = p.z * 32 + z;
HighestBlockChunk hbc = chunks.getHighestBlockChunkAtXY(gx, gy, tmp2);
simBlock(gx, gy, gz, chunk, hbc, b, step, deltas);
int lx = gx - chunk.cx*32;
int ly = gy - chunk.cy*16;
int lz = gz - chunk.cz*32;
chunk.nextSimStep = Math.min(chunk.nextSimSteps[lx][ly][lz], chunk.nextSimStep);
}
}
}
}
public interface BlockDeltaAdder {
public abstract void addBlockDelta(BlockDelta delta);
public abstract boolean hasBlockDelta(Vector3i v);
public abstract BlockDelta getBlockDelta(Vector3i v);
}
public interface PointInSimRange {
public abstract boolean pointIsInRange(Vector3i v);
}
private void simBlock(int bx, int by, int bz, Chunk c, HighestBlockChunk hbc, Block b, int step, final Hashtable<Vector3i, BlockDelta> deltas){
int lx = bx - c.cx*32;
int ly = by - c.cy*16;
int lz = bz - c.cz*32;
boolean fastMode = c.stepsToSim[lx][ly][lz] > 0;
if (!fastMode){
int nextSimStep = c.nextSimSteps[lx][ly][lz];
if (nextSimStep != -1 && nextSimStep > step){
return;
}
}
tmp3i.x = bx;
tmp3i.y = by;
tmp3i.z = bz;
if (deltas.containsKey(tmp3i))
return;
c.nextSimSteps[lx][ly][lz] = Integer.MAX_VALUE;
ArrayList<BlockRule> rules = getRules(b);
final Container<Boolean> hadMatch = new Container<Boolean>();
hadMatch.contents = false;
for (BlockRule rule : rules) {
int nextSimStep = rule.run(bx, by, bz, fastMode, b, c, hbc, this.chunks, step, new BlockDeltaAdder(){
@Override
public void addBlockDelta(BlockDelta delta){
Vector3i pos = new Vector3i();
pos.x = delta.x;
pos.y = delta.y;
pos.z = delta.z;
if (hasBlockDelta(pos)){
try {
throw new Exception("multiple deltas per block");
} catch (Exception e) {
e.printStackTrace();
}
}
deltas.put(pos, delta);
hadMatch.contents = true;
}
@Override
public boolean hasBlockDelta(Vector3i v){
return deltas.containsKey(v);
}
@Override
public BlockDelta getBlockDelta(Vector3i v){
return deltas.get(v);
}
});
c.nextSimSteps[lx][ly][lz] = Math.min(c.nextSimSteps[lx][ly][lz], nextSimStep);
if (hadMatch.contents){
break;
}
}
if (rules.size() == 0 || !hadMatch.contents){
c.stepsToSim[lx][ly][lz] = 0;
}
else if (c.stepsToSim[lx][ly][lz] > 0){
c.stepsToSim[lx][ly][lz] -= 1;
if (c.stepsToSim[lx][ly][lz] > 0){
c.nextSimSteps[lx][ly][lz] = -1;
}
}
}
private ArrayList<BlockRule> getRules(Block b){
ArrayList<BlockRule> rules = null;
if (b instanceof BlockSeed) {
rules = blockRules.seedRules;
}
else if (b instanceof BlockWood) {
rules = blockRules.woodRules;
}
else if (b instanceof BlockWater) {
rules = blockRules.waterRules;
}
else if (b instanceof BlockLeaf) {
rules = blockRules.leafRules;
}
else if (b instanceof BlockDirt) {
rules = blockRules.dirtRules;
}
else if (b instanceof BlockEmpty) {
rules = blockRules.emptyRules;
}
else if (b instanceof BlockTransparent) {
rules = blockRules.transparentRules;
}
else {
rules = blockRules.everywhereRules;
}
return rules;
}
}