package org.mctourney.autoreferee.util.worldsearch;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import org.mctourney.autoreferee.AutoRefMatch;
import org.mctourney.autoreferee.AutoRefTeam;
import org.mctourney.autoreferee.AutoReferee;
import org.mctourney.autoreferee.regions.AutoRefRegion;
import org.mctourney.autoreferee.regions.CuboidRegion;
import org.mctourney.autoreferee.util.BlockData;
import org.mctourney.autoreferee.util.LocationUtil;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
public class ObjectiveExhaustionMasterTask implements Runnable
{
public final AutoReferee plugin = AutoReferee.getInstance();
public final AutoRefTeam team;
// Safety strategy: Immutable set
public final Set<BlockData> originalSearch;
// Safety strategy: Copy on write
public volatile Set<BlockData> searching;
// Safety strategy: Single writer, stop before reading by using lock
public Map<BlockData, Vector> results = Maps.newHashMap();
public final Object _LOCK_RESULTS = new Object();
/**
* A stop-flag for the chunk snapshot worker threads.
*/
public volatile boolean all_snapshots_added;
public ConcurrentLinkedQueue<Vector> entitychunks = Queues.newConcurrentLinkedQueue();
public ConcurrentLinkedQueue<Vector> contchunks = Queues.newConcurrentLinkedQueue();
public LinkedBlockingQueue<ChunkSnapshot> snapshots = Queues.newLinkedBlockingQueue();
public ConcurrentLinkedQueue<_Entry<BlockData, Vector>> found = Queues.newConcurrentLinkedQueue();
private WorkerEntitySearch entSearcher;
private WorkerContainerSearch containerSearcher;
private List<WorkerAsyncSearchSnapshots> searchers;
private WorkerValidateResults resultChecker;
public ObjectiveExhaustionMasterTask(AutoRefTeam team, Set<BlockData> goals)
{
this.team = team;
this.searching = goals;
originalSearch = ImmutableSet.copyOf(goals);
}
@Override
// REMINDER: This is run async!
public void run()
{
boolean interrupted = Thread.interrupted();
List<Vector> chunks = Lists.newArrayList(getChunkVectors());
// Start entity searcher
entitychunks.addAll(chunks);
entSearcher = new WorkerEntitySearch(this);
entSearcher.runTaskTimer(plugin, 0, 3);
// Start container searcher
contchunks.addAll(chunks);
containerSearcher = new WorkerContainerSearch(this);
containerSearcher.runTaskTimer(plugin, 1, 3);
// Start up chunk snapshot searchers
// TODO pick a count
searchers = Lists.newLinkedList();
WorkerAsyncSearchSnapshots tmp_searcher = new WorkerAsyncSearchSnapshots(this);
tmp_searcher.runTaskAsynchronously(plugin);
searchers.add(tmp_searcher);
tmp_searcher = new WorkerAsyncSearchSnapshots(this);
tmp_searcher.runTaskAsynchronously(plugin);
searchers.add(tmp_searcher);
tmp_searcher = new WorkerAsyncSearchSnapshots(this);
tmp_searcher.runTaskAsynchronously(plugin);
searchers.add(tmp_searcher);
// Start result checker
resultChecker = new WorkerValidateResults(this);
resultChecker.runTaskTimer(plugin, 2, 3);
for (int i = 0; i < chunks.size(); i += 10)
{
if (checkComplete())
{ cleanup(); return; }
int max = Math.max(chunks.size() - 1, i + 10);
List<Vector> sublist = chunks.subList(i, max);
Future<List<ChunkSnapshot>> future = Bukkit.getScheduler().callSyncMethod(plugin, new CallableGetSnapshots(sublist, team.getMatch().getWorld()));
List<ChunkSnapshot> value;
try
{
// Do the get inside the loop to rate-limit.
value = future.get();
}
catch (ExecutionException e)
{ e.printStackTrace(); quit(e.toString(), interrupted); return; }
catch (InterruptedException e)
{ e.printStackTrace(); quit("thread interrupted", true); return; }
// Memory consistency: variable set happens-before adding of final snapshots
if (i + 10 >= chunks.size())
all_snapshots_added = true;
snapshots.addAll(value);
}
chunks = null; // drop
snapshots.add(null); // poison-value, signal to stop trying to take
while (true)
{
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{ interrupted = true; }
if (checkComplete())
{
cleanup();
return;
}
}
}
private void cleanup()
{
// Cancel timer tasks first
tryCancel(entSearcher);
tryCancel(containerSearcher);
tryCancel(resultChecker);
// Prepare all the "done" signals
all_snapshots_added = true;
snapshots.clear();
snapshots.add(null);
for (WorkerAsyncSearchSnapshots searcher : searchers)
{
tryCancel(searcher);
}
}
private void tryCancel(BukkitRunnable r)
{
try { r.cancel(); }
catch (IllegalStateException ignored) { }
}
// unsafe
private void quit(String message, boolean interrupted)
{
for (Player p : team.getMatch().getReferees())
p.sendMessage(ChatColor.RED + "The exhaustion search for " + team.getDisplayName() + " was stopped: " + ChatColor.DARK_RED + message);
if (interrupted)
Thread.currentThread().interrupt();
}
private Set<Vector> getChunkVectors()
{
Set<Vector> chunks = Sets.newHashSet();
Set<AutoRefRegion> regions = team.getRegions();
for (AutoRefRegion region : regions)
{
addChunks(chunks, region.getBoundingCuboid());
}
return chunks;
}
private void addChunks(Set<Vector> chunks, CuboidRegion bound)
{
int czmax = (int) Math.floor(bound.z2) >> 4;
int czmin = (int) Math.floor(bound.z1) >> 4;
int cxmax = (int) Math.floor(bound.x2) >> 4;
for (int cx = (int) Math.floor(bound.x1) >> 4; cx < cxmax; cx++)
for (int cz = czmin; cz < czmax; cz++)
chunks.add(new Vector(cx, 0, cz));
}
private boolean checkComplete()
{
if (searching.isEmpty())
{
synchronized (_LOCK_RESULTS) {
// make results safe to read
resultChecker.cancel();
}
// Schedule announce, return true
Bukkit.getScheduler().runTask(plugin, new Runnable() { public void run()
{
AutoRefMatch match = team.getMatch();
World world = match.getWorld();
StringBuilder sb = new StringBuilder();
sb.append(ChatColor.GREEN).append("Objective search for ").append(team.getDisplayName()).append(ChatColor.GREEN).append(" complete.");
sb.append('\n');
for (BlockData bd : originalSearch)
{
Location loc = results.get(bd).toLocation(world);
sb.append(bd.getDisplayName()).append(ChatColor.GRAY).append(" is at ").append(ChatColor.RED).append(LocationUtil.toBlockCoords(loc));
sb.append('\n');
}
String[] message = sb.toString().split("\n");
for (Player p : match.getSpectators())
p.sendMessage(message);
} });
return true;
}
return false;
}
}