haxepunk/debug/Console.hx
package haxepunk.debug;
import haxepunk.utils.BlendMode;
import haxepunk.math.Vector2;
import haxepunk.math.Rectangle;
import haxepunk.HXP;
import haxepunk.cameras.UICamera;
import haxepunk.graphics.Image;
import haxepunk.graphics.hardware.HardwareRenderer;
import haxepunk.input.Key;
import haxepunk.input.Mouse;
import haxepunk.input.MouseManager;
import haxepunk.utils.CircularBuffer;
import haxepunk.utils.DrawContext;
@:access(haxepunk.graphics.hardware)
@:access(haxepunk.Engine)
class Console extends Scene
{
static inline var SAMPLE_TIME:Float = 20 / 60;
static inline var DATA_SIZE:Int = Std.int(5 / SAMPLE_TIME);
static inline var CAMERA_PAN_PER_SECOND:Float = 256;
static inline var MIN_DRAG:Int = 8;
public static function enable():Void enabled = true;
public static var enabled(get, set):Bool;
static inline function get_enabled() return HXP.engine.console != null;
static inline function set_enabled(v:Bool)
{
if (v != enabled)
{
HXP.engine.console = v ? new Console() : null;
}
return v;
}
static inline function avg<T:Float>(buffer:CircularBuffer<T>):Float
{
return buffer.length == 0 ? 0 : (Lambda.fold(buffer, add2, 0) / buffer.length);
}
static inline function add2(a:Float, b:Float):Float return a + b;
static var drawContext:DrawContext;
public var fps:CircularBuffer<Float>;
public var memory:CircularBuffer<Float>;
public var entities:CircularBuffer<Int>;
public var triangles:CircularBuffer<Int>;
public var drawCalls:CircularBuffer<Int>;
public var paused(get, set):Bool;
inline function get_paused() return HXP.engine.paused;
inline function set_paused(v:Bool)
{
return HXP.engine.paused = v;
}
public var debugDraw:Bool = false;
var logo:Image;
var buttonTray:ButtonTray;
var logPanel:LogPanel;
var layerList:LayerList;
var fpsChart:Metric<Float>;
var memoryChart:Metric<Float>;
var entitiesChart:Metric<Int>;
var trianglesChart:Metric<Int>;
var drawCallsChart:Metric<Int>;
var selected:Array<Entity> = new Array();
var _fps:Float = 0;
var _mem:Float = 0;
var _ent:Float = 0;
var _tri:Float = 0;
var _dc:Float = 0;
var _t:Float = 0;
var click:Vector2 = new Vector2();
var selBox:Rectangle = new Rectangle();
var clickActive:Bool = false;
var dragging:Bool = false;
var panning:Bool = false;
var mouseManager:MouseManager;
function new()
{
super();
trackDrawCalls = false;
fps = new CircularBuffer(DATA_SIZE);
memory = new CircularBuffer(DATA_SIZE);
entities = new CircularBuffer(DATA_SIZE);
triangles = new CircularBuffer(DATA_SIZE);
drawCalls = new CircularBuffer(DATA_SIZE);
logo = new Image("graphics/debug/console_logo.png");
logo.blend = BlendMode.Multiply;
addGraphic(logo);
fpsChart = new Metric("FPS", fps, 0xff0000, HXP.frameRate);
fpsChart.x = fpsChart.y = 8;
add(fpsChart);
memoryChart = new Metric("Memory (MB)", memory, 0xffdd55, 256);
memoryChart.x = fpsChart.x;
memoryChart.y = fpsChart.y + fpsChart.height + 8;
add(memoryChart);
entitiesChart = new Metric("Entities", entities, 0xff6600, 16);
entitiesChart.x = memoryChart.x;
entitiesChart.y = memoryChart.y + memoryChart.height + 8;
add(entitiesChart);
trianglesChart = new Metric("Triangles", triangles, 0x00ff00, 128);
trianglesChart.x = entitiesChart.x;
trianglesChart.y = entitiesChart.y + entitiesChart.height + 8;
add(trianglesChart);
drawCallsChart = new Metric("Draw calls", drawCalls, 0x0000ff, 16);
drawCallsChart.x = trianglesChart.x;
drawCallsChart.y = trianglesChart.y + trianglesChart.height + 8;
add(drawCallsChart);
mouseManager = new MouseManager();
mouseManager.type = "hxp_debug_ui";
buttonTray = new ButtonTray(mouseManager, toggleDebugDraw, togglePause, step);
buttonTray.y = 8;
add(buttonTray);
layerList = new LayerList(mouseManager);
layerList.y = 8;
add(layerList);
logPanel = new LogPanel(mouseManager);
logPanel.x = 8;
add(logPanel);
add(mouseManager);
bgColor = 0xc0c0c0;
preRender.bind(debugRender);
camera = new UICamera();
if (drawContext == null)
{
drawContext = new DrawContext();
drawContext.lineThickness = 2;
}
}
override public function update()
{
super.update();
if (Key.pressed(Key.TILDE))
{
togglePause();
debugDraw = paused;
}
if (!paused)
{
updateMetrics();
}
if (paused)
{
if (Key.check(Key.RIGHT_SQUARE_BRACKET)) step();
if (Key.check(Key.SHIFT))
{
var mx:Int = 0, my:Int = 0;
if (Key.check(Key.LEFT)) mx = -1;
else if (Key.check(Key.RIGHT)) mx = 1;
if (Key.check(Key.UP)) my = -1;
else if (Key.check(Key.DOWN)) my = 1;
if (mx != 0 || my != 0)
{
var camera = HXP.scene.camera;
camera.x = Std.int(camera.x + HXP.elapsed * CAMERA_PAN_PER_SECOND * mx);
camera.y = Std.int(camera.y + HXP.elapsed * CAMERA_PAN_PER_SECOND * my);
}
if (!clickActive && Mouse.mouseDown)
{
panning = true;
}
}
if (Mouse.mousePressed)
{
clickActive = true;
dragging = false;
click.setTo(HXP.scene.mouseX, HXP.scene.mouseY);
}
if (clickActive)
{
var mx = HXP.scene.mouseX,
my = HXP.scene.mouseY;
if (panning)
{
// panning
var dx = Std.int(mx - click.x),
dy = Std.int(my - click.y);
if (dx != 0 || dy != 0)
{
if (selected.length > 0)
{
for (e in selected)
{
e.x += dx;
e.y += dy;
}
}
else
{
HXP.scene.camera.x -= dx;
HXP.scene.camera.y -= dy;
}
click.setTo(HXP.scene.mouseX, HXP.scene.mouseY);
}
}
else
{
// check for drag selection
var moved = Math.abs(mx - click.x) + Math.abs(my - click.y);
if (moved > MIN_DRAG)
{
dragging = true;
}
if (dragging)
{
selBox.setTo(Math.min(mx, click.x), Math.min(my, click.y), Math.abs(mx - click.x), Math.abs(my - click.y));
}
if (Mouse.mouseReleased)
{
if (!dragging)
{
selBox.setTo(click.x, click.y, 1, 1);
}
setSelection();
clickActive = dragging = false;
}
}
}
if (!Mouse.mouseDown)
{
clickActive = dragging = panning = false;
}
}
fpsChart.enabled =
memoryChart.enabled =
entitiesChart.enabled =
trianglesChart.enabled =
drawCallsChart.enabled =
logPanel.enabled =
layerList.enabled =
debugDraw;
logo.x = (camera.width - logo.width) / 2;
logo.y = (camera.height - logo.height) / 2;
logo.visible = paused;
buttonTray.x = (camera.width - buttonTray.width) / 2;
logPanel.width = Std.int(camera.width - logPanel.x - 8);
logPanel.y = camera.height - logPanel.height - 8;
layerList.x = camera.width - layerList.width - 8;
bgAlpha = paused ? 0.75 : 0;
updateLists();
}
public inline function log(data:Dynamic)
{
logPanel.log(data);
}
public inline function watch(properties:Array<Dynamic>)
{
// TODO
}
function toggleDebugDraw()
{
debugDraw = !debugDraw;
}
function togglePause()
{
paused = !paused;
}
var _stepping:Bool = false;
function step()
{
if (_stepping || !paused) return;
_stepping = true;
HXP.engine.update();
updateMetrics();
_stepping = false;
}
function updateMetrics()
{
var s = HXP.elapsed / SAMPLE_TIME;
_fps += 1 / HXP.elapsed * s;
_mem += HXP.app.getMemoryUse() / 1024 / 1024 * s;
_ent += HXP.scene.count * s;
_tri += HardwareRenderer.triangleCount * s;
_dc += HardwareRenderer.drawCallCount * s;
_t += s;
if (_t >= 1)
{
fps.push(_fps / _t);
memory.push(_mem / _t);
entities.push(Std.int(_ent / _t));
triangles.push(Std.int(_tri / _t));
drawCalls.push(Std.int(_dc / _t));
_fps = _mem = _ent = _tri = _dc = _t = 0;
}
}
function debugRender()
{
if (debugDraw)
{
var scene = HXP.scene;
for (layer in scene._layerList)
{
if (!scene.layerVisible(layer)) continue;
for (e in scene._layers.get(layer))
{
e.debugDraw(e.camera == null ? scene.camera : e.camera, selected.indexOf(e) > -1);
}
}
}
if (dragging)
{
drawContext.setColor(0xffffff, 0.9);
var camera = HXP.scene.camera;
drawContext.rect(
(selBox.x - camera.x) * camera.screenScaleX,
(selBox.y - camera.y) * camera.screenScaleY,
selBox.width * camera.screenScaleX,
selBox.height * camera.screenScaleY
);
}
}
function setSelection()
{
var _rect = HXP.rect;
HXP.clear(selected);
for (entity in HXP.scene._update)
{
_rect.setTo(entity.x - 4, entity.y - 4, 8, 8);
if (selBox.intersects(_rect))
{
selected.push(entity);
}
}
}
}