/*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package lineage2.gameserver.geodata;
import gnu.trove.iterator.TIntIntIterator;
import gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import lineage2.commons.text.StrTable;
import lineage2.gameserver.Config;
import lineage2.gameserver.utils.Location;
/**
* @author Mobius
* @version $Revision: 1.0 $
*/
public class PathFindBuffers
{
/**
* Field MIN_MAP_SIZE.
*/
public final static int MIN_MAP_SIZE = 1 << 6;
/**
* Field STEP_MAP_SIZE.
*/
public final static int STEP_MAP_SIZE = 1 << 5;
/**
* Field MAX_MAP_SIZE.
*/
public final static int MAX_MAP_SIZE = 1 << 9;
/**
* Field buffers.
*/
private static TIntObjectHashMap<PathFindBuffer[]> buffers = new TIntObjectHashMap<>();
/**
* Field sizes.
*/
private static int[] sizes = new int[0];
/**
* Field lock.
*/
private static Lock lock = new ReentrantLock();
static
{
TIntIntHashMap config = new TIntIntHashMap();
String[] k;
for (String e : Config.PATHFIND_BUFFERS.split(";"))
{
if (!e.isEmpty() && ((k = e.split("x")).length == 2))
{
config.put(Integer.valueOf(k[1]), Integer.valueOf(k[0]));
}
}
TIntIntIterator itr = config.iterator();
while (itr.hasNext())
{
itr.advance();
int size = itr.key();
int count = itr.value();
PathFindBuffer[] buff = new PathFindBuffer[count];
for (int i = 0; i < count; i++)
{
buff[i] = new PathFindBuffer(size);
}
buffers.put(size, buff);
}
sizes = config.keys();
Arrays.sort(sizes);
}
/**
* Method create.
* @param mapSize int
* @return PathFindBuffer
*/
private static PathFindBuffer create(int mapSize)
{
lock.lock();
try
{
PathFindBuffer buffer;
PathFindBuffer[] buff = buffers.get(mapSize);
if (buff != null)
{
buff = lineage2.commons.lang.ArrayUtils.add(buff, buffer = new PathFindBuffer(mapSize));
}
else
{
buff = new PathFindBuffer[]
{
buffer = new PathFindBuffer(mapSize)
};
sizes = org.apache.commons.lang3.ArrayUtils.add(sizes, mapSize);
Arrays.sort(sizes);
}
buffers.put(mapSize, buff);
buffer.inUse = true;
return buffer;
}
finally
{
lock.unlock();
}
}
/**
* Method get.
* @param mapSize int
* @return PathFindBuffer
*/
private static PathFindBuffer get(int mapSize)
{
lock.lock();
try
{
PathFindBuffer[] buff = buffers.get(mapSize);
for (PathFindBuffer buffer : buff)
{
if (!buffer.inUse)
{
buffer.inUse = true;
return buffer;
}
}
return null;
}
finally
{
lock.unlock();
}
}
/**
* Method alloc.
* @param mapSize int
* @return PathFindBuffer
*/
public static PathFindBuffer alloc(int mapSize)
{
if (mapSize > MAX_MAP_SIZE)
{
return null;
}
mapSize += STEP_MAP_SIZE;
if (mapSize < MIN_MAP_SIZE)
{
mapSize = MIN_MAP_SIZE;
}
PathFindBuffer buffer = null;
for (int size : sizes)
{
if (size >= mapSize)
{
mapSize = size;
buffer = get(mapSize);
break;
}
}
if (buffer == null)
{
for (int size = MIN_MAP_SIZE; size < MAX_MAP_SIZE; size += STEP_MAP_SIZE)
{
if (size >= mapSize)
{
mapSize = size;
buffer = create(mapSize);
break;
}
}
}
return buffer;
}
/**
* Method recycle.
* @param buffer PathFindBuffer
*/
public static void recycle(PathFindBuffer buffer)
{
lock.lock();
try
{
buffer.inUse = false;
}
finally
{
lock.unlock();
}
}
/**
* Method getStats.
* @return StrTable
*/
public static StrTable getStats()
{
StrTable table = new StrTable("PathFind Buffers Stats");
lock.lock();
try
{
long totalUses = 0, totalPlayable = 0, totalTime = 0;
int index = 0;
int count;
long uses;
long playable;
long itrs;
long success;
long overtime;
long time;
for (int size : sizes)
{
index++;
count = 0;
uses = 0;
playable = 0;
itrs = 0;
success = 0;
overtime = 0;
time = 0;
for (PathFindBuffer buff : buffers.get(size))
{
count++;
uses += buff.totalUses;
playable += buff.playableUses;
success += buff.successUses;
overtime += buff.overtimeUses;
time += buff.totalTime / 1000000;
itrs += buff.totalItr;
}
totalUses += uses;
totalPlayable += playable;
totalTime += time;
table.set(index, "Size", size);
table.set(index, "Count", count);
table.set(index, "Uses (success%)", uses + "(" + String.format("%2.2f", (uses > 0) ? (success * 100.) / uses : 0) + "%)");
table.set(index, "Uses, playble", playable + "(" + String.format("%2.2f", (uses > 0) ? (playable * 100.) / uses : 0) + "%)");
table.set(index, "Uses, overtime", overtime + "(" + String.format("%2.2f", (uses > 0) ? (overtime * 100.) / uses : 0) + "%)");
table.set(index, "Iter., avg", (uses > 0) ? itrs / uses : 0);
table.set(index, "Time, avg (ms)", String.format("%1.3f", (uses > 0) ? (double) time / uses : 0.));
}
table.addTitle("Uses, total / playable : " + totalUses + " / " + totalPlayable);
table.addTitle("Uses, total time / avg (ms) : " + totalTime + " / " + String.format("%1.3f", totalUses > 0 ? (double) totalTime / totalUses : 0));
}
finally
{
lock.unlock();
}
return table;
}
/**
* @author Mobius
*/
public static class PathFindBuffer
{
/**
* Field mapSize.
*/
final int mapSize;
/**
* Field nodes.
*/
final GeoNode[][] nodes;
/**
* Field open.
*/
final Queue<GeoNode> open;
/**
* Field offsetY.
*/
/**
* Field offsetX.
*/
int offsetX, offsetY;
/**
* Field inUse.
*/
boolean inUse;
/**
* Field totalUses.
*/
long totalUses;
/**
* Field successUses.
*/
long successUses;
/**
* Field overtimeUses.
*/
long overtimeUses;
/**
* Field playableUses.
*/
long playableUses;
/**
* Field totalTime.
*/
long totalTime;
/**
* Field totalItr.
*/
long totalItr;
/**
* Constructor for PathFindBuffer.
* @param mapSize int
*/
public PathFindBuffer(int mapSize)
{
open = new PriorityQueue<>(mapSize);
this.mapSize = mapSize;
nodes = new GeoNode[mapSize][mapSize];
for (int i = 0; i < nodes.length; i++)
{
for (int j = 0; j < nodes[i].length; j++)
{
nodes[i][j] = new GeoNode();
}
}
}
/**
* Method free.
*/
public void free()
{
open.clear();
for (GeoNode[] node : nodes)
{
for (GeoNode element : node)
{
element.free();
}
}
}
}
/**
* @author Mobius
*/
public static class GeoNode implements Comparable<GeoNode>
{
/**
* Field NONE. (value is 0)
*/
public final static int NONE = 0;
/**
* Field OPENED. (value is 1)
*/
public final static int OPENED = 1;
/**
* Field CLOSED. (value is -1)
*/
public final static int CLOSED = -1;
/**
* Field y.
*/
/**
* Field x.
*/
public int x, y;
/**
* Field nswe.
*/
/**
* Field z.
*/
public short z, nswe;
/**
* Field costToEnd.
*/
/**
* Field costFromStart.
*/
/**
* Field totalCost.
*/
public float totalCost, costFromStart, costToEnd;
/**
* Field state.
*/
public int state;
/**
* Field parent.
*/
public GeoNode parent;
/**
* Constructor for GeoNode.
*/
public GeoNode()
{
nswe = -1;
}
/**
* Method set.
* @param x int
* @param y int
* @param z short
* @return GeoNode
*/
public GeoNode set(int x, int y, short z)
{
this.x = x;
this.y = y;
this.z = z;
return this;
}
/**
* Method isSet.
* @return boolean
*/
public boolean isSet()
{
return nswe != -1;
}
/**
* Method free.
*/
public void free()
{
nswe = -1;
costFromStart = 0f;
totalCost = 0f;
costToEnd = 0f;
parent = null;
state = NONE;
}
/**
* Method getLoc.
* @return Location
*/
public Location getLoc()
{
return new Location(x, y, z);
}
/**
* Method toString.
* @return String
*/
@Override
public String toString()
{
return "[" + x + "," + y + "," + z + "] f: " + totalCost;
}
/**
* Method compareTo.
* @param o GeoNode
* @return int
*/
@Override
public int compareTo(GeoNode o)
{
if (totalCost > o.totalCost)
{
return 1;
}
if (totalCost < o.totalCost)
{
return -1;
}
return 0;
}
}
}