Skip to content

Commit

Permalink
Refactor and change right click to return to last root path
Browse files Browse the repository at this point in the history
  • Loading branch information
pfirsich committed Jun 6, 2018
1 parent 7a0e70b commit c09ee07
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 187 deletions.
105 changes: 66 additions & 39 deletions draw.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local lg = love.graphics

local const = require("const")
local util = require("util")
local frames = require("frames")

local draw = {}

Expand All @@ -12,6 +13,26 @@ draw.nextGraphMean = {
harmonic = "max",
}

draw.flameGraphType = "time" -- so far: "time" or "memory"

local rootPath = {}
local rootPathHistory = {}

local fonts = {
mode = lg.newFont(22),
node = lg.newFont(18),
graph = lg.newFont(12),
}

-- the data to to be passed to love.graphics.line is saved here, so I don't create new tables all the time
local graphs = {
mem = {},
time = {},
}

local noticeText = lg.newText(fonts.mode, "")
local noticeSent = 0

local helpText
do
local L = const.helpTitleColor
Expand All @@ -21,7 +42,7 @@ do
L, "Shift + Left Click (graph area): ", R, "Select a frame range.\n\n",

L, "Left Click (flame graph): ", R, "Select a node as the new root node.\n",
L, "Right Click (flame graph): ", R, "Select the parent of the current root node as the new root node. If not present in the frame, the whole frame is the new root node.\n\n",
L, "Right Click (flame graph): ", R, "Return to the previous root node.\n\n",

L, "Arrow Left/Right: ", R, "Seek 1 frame left/right.\n",
L, "Ctrl + Arrow Left/Right: ", R, "Seek 100 frames left/right.\n\n",
Expand All @@ -32,19 +53,6 @@ do
}
end

function draw.getGraphCoords()
local winH = love.graphics.getHeight()
local graphHeight = winH * const.graphHeightFactor
local graphY = winH - const.graphYOffset - graphHeight
return graphY, graphHeight
end

local fonts = {
mode = lg.newFont(22),
node = lg.newFont(18),
graph = lg.newFont(12),
}

local flameGraphFuncs = {
time = function(node, child)
local x
Expand Down Expand Up @@ -80,7 +88,7 @@ local function getNodeString(node)
end

local str
if flameGraphType == "time" then
if draw.flameGraphType == "time" then
str = ("- %.4f ms, %s"):format(node.deltaTime*1000, memStr)
else
str = ("- %s, %.4f ms"):format(memStr, node.deltaTime*1000)
Expand Down Expand Up @@ -157,24 +165,11 @@ local function renderSubGraph(node, x, y, width, graphFunc, center)
return hovered
end

local noticeText = lg.newText(fonts.mode, "")
local noticeSent = 0
function draw.notice(str)
noticeText:set(str)
noticeSent = love.timer.getTime()
end

local function getFramePos(i)
return lg.getWidth() / (#frames - 1) * (i - 1)
end

-- the data to to be passed to love.graphics.line is saved here, so I don't create new tables all the time
local graphs = {
mem = {},
time = {},
}

function buildGraph(graph, frameKey, valueOffset, valueScale, mean)
local function buildGraph(graph, key, valueOffset, valueScale, mean, path)
local x, w = 0, lg.getWidth()
local y, h = draw.getGraphCoords()

Expand All @@ -187,9 +182,9 @@ function buildGraph(graph, frameKey, valueOffset, valueScale, mean)
local accum = nil
local n = endIndex - startIndex + 1
for f = startIndex, endIndex do
local node = util.getNodeByPath(frames[f], frames.drawRoot)
local node = util.getNodeByPath(frames[f], path)
if node then
accum = mean.add(accum, util.clamp((node[frameKey] - valueOffset) / valueScale))
accum = mean.add(accum, util.clamp((node[key] - valueOffset) / valueScale))
end
end
frameIndex = frameIndex + step
Expand All @@ -198,6 +193,41 @@ function buildGraph(graph, frameKey, valueOffset, valueScale, mean)
end
end

function draw.updateGraphs()
buildGraph(graphs.time, "deltaTime", 0, frames.maxDeltaTime, util.mean[draw.graphMean], rootPath)
buildGraph(graphs.mem, "memoryEnd", 0, frames.maxMemUsage, util.mean[draw.graphMean], rootPath)
end

function draw.getGraphCoords()
local winH = love.graphics.getHeight()
local graphHeight = winH * const.graphHeightFactor
local graphY = winH - const.graphYOffset - graphHeight
return graphY, graphHeight
end

function draw.notice(str)
noticeText:set(str)
noticeSent = love.timer.getTime()
end

local function setRootPath(path)
rootPath = path
draw.updateGraphs()
draw.notice("new draw root: " .. util.nodePathToStr(path))
end

function draw.pushRootPath(path)
table.insert(rootPathHistory, rootPath)
setRootPath(path)
end

function draw.popRootPath(path)
if #rootPathHistory > 0 then
setRootPath(rootPathHistory[#rootPathHistory])
table.remove(rootPathHistory)
end
end

function love.draw()
local winW, winH = lg.getDimensions()

Expand Down Expand Up @@ -283,12 +313,10 @@ function love.draw()

if #frames > 1 then
lg.setLineWidth(1)
buildGraph(graphs.time, "deltaTime", 0, frames.maxDeltaTime, mean)
lg.setColor(const.timeGraphColor)
lg.line(graphs.time)

lg.setLineWidth(2)
buildGraph(graphs.mem, "memoryEnd", 0, frames.maxMemUsage, mean)
lg.setColor(const.memGraphColor)
lg.line(graphs.mem)
end
Expand All @@ -313,27 +341,26 @@ function love.draw()

-- render flame graph for current frame
lg.setFont(fonts.mode)
lg.print("graph type: " .. flameGraphType, 5, 5)
lg.print("graph type: " .. draw.flameGraphType, 5, 5)
lg.setFont(fonts.node)
-- do not order flame layers (just center) if either memory graph or average frame
local node = util.getNodeByPath(frames.current, frames.drawRoot)
local node = util.getNodeByPath(frames.current, rootPath)
if node then
local hovered = renderSubGraph(node, 0, graphY - const.infoLineHeight, winW,
flameGraphFuncs[flameGraphType],
flameGraphFuncs[draw.flameGraphType],
flameGraphType == "memory" or not frames.current.index)
if hovered then
infoLine = hovered.name .. " " .. getNodeString(hovered)

local mouseDown = love.mouse.isDown(1)
if mouseDown and not lastMouseDown then
frames.drawRoot = hovered.path
draw.notice("new draw root: " .. util.nodePathToStr(hovered.path))
draw.pushRootPath(hovered.path)
end
lastMouseDown = mouseDown
end
else
infoLine = ("This frame does not have a node with path '%s'"):format(
util.nodePathToStr(frames.drawRoot))
util.nodePathToStr(rootPath))
end

if infoLine then
Expand Down
129 changes: 129 additions & 0 deletions frames.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
-- the frames itself are stored in this table as well
local frames = {}

frames.current = nil

frames.minDeltaTime, frames.maxDeltaTime = nil, nil
frames.minMemUsage, frames.maxMemUsage = nil, nil

local function getNodeCount(node)
assert(node.parent)
local counter = 1
for _, child in ipairs(node.parent.children) do
if child.name == node.name then
counter = counter + 1
if child == node then
break
end
end
end
return counter
end

local function buildGraph(data)
local frames = {}
local nodeStack = {}
for _, event in ipairs(data) do
local name, time, memory, annotation = unpack(event)
local top = nodeStack[#nodeStack]
if name ~= "pop" then
local node = {
name = name,
startTime = time,
memoryStart = memory,
annotation = annotation,
parent = top,
children = {},
}
if top then
node.path = {unpack(top.path)}
table.insert(node.path, {node.name, getNodeCount(node)})
else
node.path = {}
end

if name == "frame" then
if #nodeStack > 0 then
error("Profiling data malformed: Pushed a new frame when the last one was not popped yet!")
end

node.index = #frames + 1
table.insert(frames, node)
else
if not top then
error("Profiling data malformed: Pushed a profiling zone without a 'frame' profiling zone on the stack!")
end

table.insert(top.children, node)
end

table.insert(nodeStack, node)
else
if not top then
error("Profiling data malformed: Popped a profiling zone on an empty stack!")
end

top.endTime = time + 1e-8
top.deltaTime = top.endTime - top.startTime
top.memoryEnd = memory
top.memoryDelta = top.memoryEnd - top.memoryStart
table.remove(nodeStack)
end
end
return frames
end

local function updateRange(newFrames, valueList, key, cutoffPercent, cutoffMin)
if #frames == 0 then
for _, frame in ipairs(newFrames) do
table.insert(valueList, frame[key])
end
table.sort(valueList)
else
for _, frame in ipairs(newFrames) do
local value = frame[key]
local i = 1
while valueList[i] and valueList[i] < value do
i = i + 1
end
table.insert(valueList, i, value)
i = i + 1
end
end

local margin = 0
if cutoffPercent then
assert(cutoffMin)
-- cut off the lowest and highest cutoffPercent of the values
margin = math.max(cutoffMin, math.floor(cutoffPercent * #valueList))
end
if cutoffMin and #valueList > cutoffMin * 5 then
return valueList[1 + margin], valueList[#valueList - margin]
else
return valueList[1], valueList[#valueList]
end
end

local deltaTimes = {}
local memUsages = {}

local function updateRanges(newFrames)
frames.minDeltaTime, frames.maxDeltaTime =
updateRange(newFrames, deltaTimes, "deltaTime", 0.005, 5)
frames.minMemUsage, frames.maxMemUsage =
updateRange(newFrames, memUsages, "memoryEnd")
end

function frames.addFrames(data)
local newFrames = buildGraph(data)
for i, frame in ipairs(newFrames) do
table.insert(frames, frame)
frame.index = #frames
end
if frames.current == nil then
frames.current = frames[1]
end
updateRanges(newFrames)
end

return frames
Loading

0 comments on commit c09ee07

Please sign in to comment.