package net.mostlyoriginal.plugin.profiler;
import com.artemis.BaseSystem;
import com.artemis.World;
import com.artemis.utils.ArtemisProfiler;
import com.artemis.utils.Bag;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.TimeUtils;
/**
* {@link ArtemisProfiler} implementation, {@link SystemProfiler#dispose()} should be called to clean static references as needed
*
* @author piotr-j
*/
public class SystemProfiler implements ArtemisProfiler {
/**
* Samples to store per system, only changes before initialization have effect
*/
public static int SAMPLES = 60 * 5;
private static boolean RUNNING = false;
private static Array<SystemProfiler> profilers = new Array<>();
private static ObjectMap<String, SystemProfiler> profilerByName = new ObjectMap<>();
/**
* Add manually created profiler
* @param profiler to add
* @return added profiler
*/
public static SystemProfiler add(SystemProfiler profiler) {
if (profiler.added) return profiler;
profiler.added = true;
profilers.add(profiler);
profilerByName.put(profiler.getName(), profiler);
return profiler;
}
/**
*
* @param name of the profiler
* @return profiler registered with given name or null
*/
public static SystemProfiler get (String name) {
return profilerByName.get(name, null);
}
/**
* @param index of profiler to get, no bounds check!
* @return profiler with given index
*/
public static SystemProfiler get (int index) {
return profilers.get(index);
}
/**
* Get profiler for given system
* @return profiler or null
*/
public static SystemProfiler getFor (BaseSystem system) {
Object[] items = profilers.items;
for (int i = 0; i < profilers.size; i++) {
SystemProfiler profiler = (SystemProfiler)items[i];
if (profiler.system == system) {
return profiler;
}
}
return null;
}
/**
* Create a profiler with given name
* @param name of the profiler
*/
public static SystemProfiler create (String name) {
return SystemProfiler.add(new SystemProfiler(name));
}
/**
* Create a profiler for given system
* @param system to profiler
* @param world to init profiler with
* @return created profiler
*/
public static SystemProfiler createFor (BaseSystem system, World world) {
return SystemProfiler.add(new SystemProfiler(system, world));
}
/**
* @return {@link Bag} with all registered profilers
*/
public static Array<SystemProfiler> get() {
return profilers;
}
/**
* @return number of registered profilers
*/
public static int size () {
return profilers.size;
}
/**
* Pause all profilers
*/
public static void pause() {
RUNNING = false;
}
/**
* Resume all profilers
*/
public static void resume() {
RUNNING = true;
}
/**
* @return if profilers are running
*/
public static boolean isRunning() {
return RUNNING;
}
/**
* Clear registered profilers, should be called when {@link World} is disposed
*/
public static void dispose() {
profilers.clear();
profilerByName.clear();
}
/*
If this profiler was already added via SystemProfiler.add()
*/
private boolean added;
protected long[] times = new long[SAMPLES];
protected int index;
protected long max;
protected int lastMaxCounter;
protected long localMax;
protected long localMaxIndex;
protected int samples;
protected long total;
protected Color color;
protected String name;
protected BaseSystem system;
public SystemProfiler() {}
/**
* Create not initialized profiler, must be initialized with {@link SystemProfiler#initialize(BaseSystem, World)}
* @param name of the profiler
*/
public SystemProfiler(String name) {
this.name = name;
}
/**
* Create profiler with default name and initialize it
* @param system to profiler
* @param world to init with
*/
public SystemProfiler (BaseSystem system, World world) {
this(null, system, world);
}
/**
* Create profiler with specified name and initialize it
* @param name of the profiler
* @param system to profiler
* @param world to init with
*/
public SystemProfiler (String name, BaseSystem system, World world) {
this.name = name;
initialize(system, world);
}
@Override
public void initialize(BaseSystem baseSystem, World world) {
system = baseSystem;
if (name == null) {
name = toString();
}
if (color == null) {
calculateColor(toString().hashCode(), color = new Color());
}
SystemProfiler.add(this);
}
private long startTime;
@Override
public void start() {
startTime = TimeUtils.nanoTime();
}
@Override
public void stop() {
long time = TimeUtils.nanoTime() - startTime;
if (RUNNING) sample(time);
}
public long getAverage() {
return samples == 0 ? 0 : total / Math.min(times.length, samples);
}
public float getMax () {
return max / 1000000f;
}
public float getLocalMax () {
return localMax / 1000000f;
}
public float getMovingAvg () {
return getAverage() / 1000000f;
}
/**
* Create a sample with specified time
* @param time in nanoseconds
*/
public void sample(long time) {
lastMaxCounter++;
if (time > max || lastMaxCounter > 2000) {
max = time;
lastMaxCounter = 0;
}
if (time > localMax || index == localMaxIndex) {
localMax = time;
localMaxIndex = index;
}
total -= times[index];
samples++;
times[index] = time;
total += time;
if (++index == times.length){
index = 0;
}
}
/**
* Add time to current sample
* @param time in nanoseconds
*/
public void add(long time) {
times[index] += time;
total += time;
}
public int getCurrentSampleIndex () {
return index;
}
public int getTotalSamples () {
return samples;
}
public long[] getSampleData () {
return times;
}
@Override
public String toString() {
return name!= null ? name :
system != null ? system.getClass().getSimpleName():"<dummy>";
}
public String getName () {
return name;
}
/**
* @return color assigned to this profiler, maybe null if it is not initialized
*/
public Color getColor () {
return color;
}
public void setColor (float r, float g, float b, float a) {
if (color == null) {
color = new Color();
}
color.set(r, g, b, a);
}
boolean drawGraph = true;
public boolean getDrawGraph () {
return drawGraph;
}
public void setDrawGraph (boolean drawGraph) {
this.drawGraph = drawGraph;
}
/**
* Calculates semi unique color from given hash
*/
public static Color calculateColor(int hash, Color color) {
float hue = (hash % 333) / 333f;
float saturation = ((hash % 271) / 271f) * 0.2f + 0.8f;
float brightness = ((hash % 577) / 577f) * 0.1f + 0.9f;
int r = 0, g = 0, b = 0;
if (saturation == 0) {
r = g = b = (int) (brightness * 255.0f + 0.5f);
} else {
float h = (hue - (float) Math.floor(hue)) * 6.0f;
float f = h - (float) Math.floor(h);
float p = brightness * (1.0f - saturation);
float q = brightness * (1.0f - saturation * f);
float t = brightness * (1.0f - (saturation * (1.0f - f)));
switch ((int) h) {
case 0:
r = (int) (brightness * 255.0f + 0.5f);
g = (int) (t * 255.0f + 0.5f);
b = (int) (p * 255.0f + 0.5f);
break;
case 1:
r = (int) (q * 255.0f + 0.5f);
g = (int) (brightness * 255.0f + 0.5f);
b = (int) (p * 255.0f + 0.5f);
break;
case 2:
r = (int) (p * 255.0f + 0.5f);
g = (int) (brightness * 255.0f + 0.5f);
b = (int) (t * 255.0f + 0.5f);
break;
case 3:
r = (int) (p * 255.0f + 0.5f);
g = (int) (q * 255.0f + 0.5f);
b = (int) (brightness * 255.0f + 0.5f);
break;
case 4:
r = (int) (t * 255.0f + 0.5f);
g = (int) (p * 255.0f + 0.5f);
b = (int) (brightness * 255.0f + 0.5f);
break;
case 5:
r = (int) (brightness * 255.0f + 0.5f);
g = (int) (p * 255.0f + 0.5f);
b = (int) (q * 255.0f + 0.5f);
break;
}
}
return color.set(r / 255f, g / 255f, b / 255f, 1);
}
}