add gs
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"lint": {
|
||||
"SameLineStatement": "fatal",
|
||||
"FunctionUnused": "fatal"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
--[[
|
||||
Filename: BarGraph.lua
|
||||
Written by: dbanks
|
||||
Description: A simple bar graph.
|
||||
--]]
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Globals ]]--
|
||||
local BarZIndex = 1
|
||||
local LineZIndex = 2
|
||||
|
||||
|
||||
--[[ Modules ]]--
|
||||
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
|
||||
|
||||
--[[ Classes ]]--
|
||||
local BarGraphClass = {}
|
||||
BarGraphClass.__index = BarGraphClass
|
||||
|
||||
function BarGraphClass.new(showExtras)
|
||||
local self = {}
|
||||
setmetatable(self, BarGraphClass)
|
||||
|
||||
self._barFrame = Instance.new("Frame")
|
||||
self._barFrame.Name = "PS_BarFrame"
|
||||
self._barFrame.BackgroundTransparency = 1.0
|
||||
|
||||
self._lineFrame = Instance.new("Frame")
|
||||
self._lineFrame.Name = "PS_LineFrame"
|
||||
self._lineFrame.BackgroundTransparency = 1.0
|
||||
|
||||
self._showExtras = showExtras
|
||||
|
||||
-- All of the values we are showing in the bar graph, in order.
|
||||
self._values = {}
|
||||
self._bars = {}
|
||||
-- Average of these values.
|
||||
self._average = 0
|
||||
-- Suggested max for these values.
|
||||
self._target = 0
|
||||
|
||||
if self._showExtras then
|
||||
self:_addGraphTarget()
|
||||
self:_addGraphAverage()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function BarGraphClass:SetZIndex(zIndex)
|
||||
self._barFrame.ZIndex = zIndex
|
||||
self._lineFrame.ZIndex = zIndex + 1
|
||||
if self._showExtras then
|
||||
self._targetLine.ZIndex = self._lineFrame.ZIndex
|
||||
self._averageLine.ZIndex = self._lineFrame.ZIndex
|
||||
end
|
||||
end
|
||||
|
||||
function BarGraphClass:PlaceInParent(parent, size, position)
|
||||
self._barFrame.Position = position
|
||||
self._barFrame.Size = size
|
||||
self._barFrame.Parent = parent
|
||||
self._lineFrame.Position = position
|
||||
self._lineFrame.Size = size
|
||||
self._lineFrame.Parent = parent
|
||||
end
|
||||
|
||||
function BarGraphClass:SetAxisMax(axisMax)
|
||||
self._axisMax = axisMax
|
||||
end
|
||||
|
||||
function BarGraphClass:SetValues(values)
|
||||
self._values = values
|
||||
end
|
||||
|
||||
function BarGraphClass:SetAverage(average)
|
||||
self._average = average
|
||||
end
|
||||
|
||||
function BarGraphClass:SetTarget(target)
|
||||
-- Set the target value, move corresponding graph line
|
||||
-- accordingly (if present).
|
||||
self._target = target
|
||||
self:_moveGraphTarget()
|
||||
end
|
||||
|
||||
function BarGraphClass:_updateBarCount(newBarCount)
|
||||
-- Make sure we have exactly this number of bars in _bars.
|
||||
-- Reuse old ones if possible.
|
||||
-- If we have more old ones than we need, delete them.
|
||||
local newBars = {}
|
||||
local currentBarCount = table.getn(self._bars)
|
||||
|
||||
for i = 1, currentBarCount, 1 do
|
||||
if (i <= currentBarCount) then
|
||||
table.insert(newBars, self._bars[i])
|
||||
else
|
||||
self._bars[i].Destroy()
|
||||
end
|
||||
end
|
||||
|
||||
for i = currentBarCount + 1, newBarCount, 1 do
|
||||
table.insert(newBars, self:_makeNthBar(i))
|
||||
end
|
||||
|
||||
self._bars = newBars
|
||||
end
|
||||
|
||||
|
||||
function BarGraphClass:Render()
|
||||
local numValues = table.getn(self._values)
|
||||
|
||||
self:_updateBarCount(numValues)
|
||||
|
||||
for i, value in ipairs(self._values) do
|
||||
self:_updateBar(i, value, numValues)
|
||||
end
|
||||
|
||||
if self._showExtras then
|
||||
self:_moveGraphAverage()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function BarGraphClass:_addGraphTarget()
|
||||
-- Add the line used to mark target value.
|
||||
local line = Instance.new("ImageLabel")
|
||||
line.Name = "TargetLine"
|
||||
line.Size = UDim2.new(1, 0, 0, StatsUtils.GraphTargetLineInnerThickness)
|
||||
|
||||
line.Image = 'rbxasset://textures/ui/PerformanceStats/TargetLine.png'
|
||||
line.BackgroundTransparency = 1
|
||||
line.Parent = self._lineFrame
|
||||
line.ZIndex = self._lineFrame.ZIndex
|
||||
line.BorderSizePixel = 0
|
||||
|
||||
line.Changed:connect(function()
|
||||
self:_updateTargetLineImageSize()
|
||||
end)
|
||||
|
||||
self._targetLine = line
|
||||
self:_updateTargetLineImageSize()
|
||||
end
|
||||
|
||||
function BarGraphClass:_updateTargetLineImageSize()
|
||||
self._targetLine.ImageRectSize = self._targetLine.AbsoluteSize
|
||||
end
|
||||
|
||||
function BarGraphClass:_addGraphAverage()
|
||||
-- Add the line used to mark average of current values.
|
||||
local line = Instance.new("Frame")
|
||||
line.Name = "AverageLine"
|
||||
line.Size = UDim2.new(1, 0, 0, StatsUtils.GraphAverageLineInnerThickness)
|
||||
|
||||
line.Parent = self._lineFrame
|
||||
line.ZIndex = self._lineFrame.ZIndex
|
||||
|
||||
StatsUtils.StyleAverageLine(line)
|
||||
|
||||
self._averageLine = line
|
||||
end
|
||||
|
||||
function BarGraphClass:_moveGraphTarget()
|
||||
-- Update position of graph target line, if present.
|
||||
if self._targetLine == nil then
|
||||
return
|
||||
end
|
||||
self._targetLine.Position = UDim2.new(0,
|
||||
0, (
|
||||
self._axisMax - self._target)/self._axisMax,
|
||||
-StatsUtils.GraphTargetLineInnerThickness/2)
|
||||
end
|
||||
|
||||
function BarGraphClass:_moveGraphAverage()
|
||||
-- Update position of graph average line, if present.
|
||||
if self._averageLine == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Never let it go above axis max.
|
||||
local adjustedAverage = math.min(self._average, self._axisMax)
|
||||
|
||||
self._averageLine.Position = UDim2.new(0,
|
||||
0,
|
||||
(self._axisMax - adjustedAverage)/self._axisMax,
|
||||
-StatsUtils.GraphAverageLineTotalThickness/2)
|
||||
end
|
||||
|
||||
function BarGraphClass:_makeNthBar(index)
|
||||
-- Make the nth bar in the bar graph.
|
||||
local realIndex = index-1
|
||||
local bar = Instance.new("Frame")
|
||||
bar.Name = string.format("Bar_%d", realIndex)
|
||||
bar.Parent = self._barFrame
|
||||
bar.ZIndex = self._barFrame.ZIndex
|
||||
bar.BorderSizePixel = 0
|
||||
return bar
|
||||
end
|
||||
|
||||
function BarGraphClass:_updateBar(i, value, numValues)
|
||||
-- Update nth bar in graph: size, position, and color.
|
||||
local bar = self._bars[i]
|
||||
local realIndex = i-1
|
||||
|
||||
-- Don't let it go off the chart.
|
||||
local clampedValue = math.max(0, math.min(value, self._axisMax))
|
||||
|
||||
bar.Position = UDim2.new(realIndex/numValues, 0,
|
||||
(self._axisMax - clampedValue)/self._axisMax, 0)
|
||||
bar.Size = UDim2.new(1/numValues, 0,
|
||||
clampedValue/self._axisMax, 0)
|
||||
|
||||
bar.BackgroundColor3 = StatsUtils.GetColorForValue(value, self._target)
|
||||
end
|
||||
|
||||
return BarGraphClass
|
||||
@@ -0,0 +1,346 @@
|
||||
--[[
|
||||
Filename: BaseMemoryAnalyzer.lua
|
||||
Written by: dbanks
|
||||
Description: Base class for a widget that displays data about memory usage.
|
||||
--]]
|
||||
|
||||
|
||||
--[[ Globals ]]--
|
||||
local RowHeight = 20
|
||||
local ValueFrameWidth = 100
|
||||
local StdRowColor3 = Color3.new(0.35, 0.55, 0.35)
|
||||
local AltRowColor3 = Color3.new(0.15, 0.35, 0.15)
|
||||
local RowLabelTextColor3 = Color3.new(1, 1, 1)
|
||||
local RowLabelBorderColor3 = Color3.new(1, 1, 1)
|
||||
local ButtonBorderColor3 = Color3.new(1, 1, 1)
|
||||
|
||||
local RowLabelBorderWidth = 1
|
||||
local ButtonBorderWidth = 1
|
||||
|
||||
local IndentSize = RowHeight
|
||||
local ButtonSize = IndentSize - 4
|
||||
|
||||
local ButtonFrameUDim2Position = UDim2.new(0, 0, 0, 0)
|
||||
|
||||
local ButtonUDim2Position = UDim2.new(1, -(IndentSize + ButtonSize)/2, 0, (IndentSize - ButtonSize)/2)
|
||||
local ButtonUDim2Size = UDim2.new(0, ButtonSize, 0, ButtonSize)
|
||||
|
||||
local ValueUDim2Position = UDim2.new(1, -ValueFrameWidth, 0, 0)
|
||||
local ValueUDim2Size = UDim2.new(0, ValueFrameWidth, 0, RowHeight)
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Helper functions ]]--
|
||||
local function __StyleAndSizeButton(button)
|
||||
button.TextXAlignment = Enum.TextXAlignment.Center
|
||||
button.TextColor3 = RowLabelTextColor3
|
||||
button.BackgroundTransparency = 1
|
||||
button.BorderColor3 = ButtonBorderColor3
|
||||
button.BorderSizePixel = ButtonBorderWidth
|
||||
|
||||
button.Position = ButtonUDim2Position
|
||||
button.Size = ButtonUDim2Size
|
||||
end
|
||||
|
||||
local function __StyleAndSizeLabel(label)
|
||||
label.TextXAlignment = Enum.TextXAlignment.Left
|
||||
label.TextColor3 = RowLabelTextColor3
|
||||
label.BackgroundTransparency = 1
|
||||
|
||||
label.Position = UDim2.new(0, 5, 0, 0)
|
||||
label.Size = UDim2.new(1, -10, 1, 0)
|
||||
end
|
||||
|
||||
local function __StyleRowCellFrame(labelFrame)
|
||||
labelFrame.BorderColor3 = RowLabelBorderColor3
|
||||
labelFrame.BorderSizePixel = RowLabelBorderWidth
|
||||
labelFrame.BackgroundTransparency = 0.6
|
||||
end
|
||||
|
||||
--[[ Classes ]]--
|
||||
|
||||
--////////////////////////////////////
|
||||
--
|
||||
-- MemoryAnalyzerRowClass
|
||||
-- A single row in the table.
|
||||
--
|
||||
--////////////////////////////////////
|
||||
local MemoryAnalyzerRowClass = {}
|
||||
MemoryAnalyzerRowClass.__index = MemoryAnalyzerRowClass
|
||||
|
||||
|
||||
function MemoryAnalyzerRowClass.new(treeViewItem)
|
||||
local self = {}
|
||||
setmetatable(self, MemoryAnalyzerRowClass)
|
||||
|
||||
self._treeViewItem = treeViewItem
|
||||
|
||||
self._expanded = true
|
||||
self._expansionToggleCallback = nil
|
||||
|
||||
-- The gui widget for the row
|
||||
self._frame = Instance.new("Frame")
|
||||
self._frame.Name = "MemoryAnalyzerRowClassFrame"
|
||||
self._frame.BackgroundTransparency = 1
|
||||
|
||||
-- The button
|
||||
self._buttonFrame = Instance.new("Frame")
|
||||
self._buttonFrame.Name = "ButtonFrame"
|
||||
self._buttonFrame.Parent = self._frame
|
||||
self._buttonFrame.Position = ButtonFrameUDim2Position
|
||||
local buttonFrameWidth = (1 + treeViewItem:getStackDepth()) * IndentSize
|
||||
self._buttonFrame.Size = UDim2.new(0, buttonFrameWidth, 0, RowHeight)
|
||||
__StyleRowCellFrame(self._buttonFrame)
|
||||
|
||||
self._button = Instance.new("TextButton")
|
||||
self._button.Name = "Button"
|
||||
self._button.Parent = self._buttonFrame
|
||||
__StyleAndSizeButton(self._button)
|
||||
|
||||
self._button.MouseButton1Click:connect(function()
|
||||
self:__toggleExpansion()
|
||||
end)
|
||||
|
||||
self:__updateButtonState()
|
||||
|
||||
-- The label
|
||||
self._labelFrame = Instance.new("Frame")
|
||||
self._labelFrame.Name = "LabelFrame"
|
||||
self._labelFrame.Parent = self._frame
|
||||
-- From the left edge of button frame to right edge of value frame.
|
||||
self._labelFrame.Position = UDim2.new(0, buttonFrameWidth, 0, 0)
|
||||
self._labelFrame.Size = UDim2.new(1, -buttonFrameWidth - ValueFrameWidth, 0, RowHeight)
|
||||
__StyleRowCellFrame(self._labelFrame)
|
||||
|
||||
self._labelTextLabel = Instance.new("TextLabel")
|
||||
self._labelTextLabel.Name = "Label"
|
||||
self._labelTextLabel.Parent = self._labelFrame
|
||||
__StyleAndSizeLabel(self._labelTextLabel)
|
||||
|
||||
self._labelTextLabel.Text = treeViewItem:getLabel()
|
||||
|
||||
-- The value
|
||||
self._valueFrame = Instance.new("Frame")
|
||||
self._valueFrame.Name = "ValueFrame"
|
||||
self._valueFrame.Parent = self._frame
|
||||
self._valueFrame.Position = ValueUDim2Position
|
||||
self._valueFrame.Size = ValueUDim2Size
|
||||
__StyleRowCellFrame(self._valueFrame)
|
||||
|
||||
self._valueTextLabel = Instance.new("TextLabel")
|
||||
self._valueTextLabel.Name = "Value"
|
||||
self._valueTextLabel.Parent = self._valueFrame
|
||||
__StyleAndSizeLabel(self._valueTextLabel)
|
||||
|
||||
self._valueHasBeenNonZero = false
|
||||
self:updateValue()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function MemoryAnalyzerRowClass:isExpanded()
|
||||
return self._expanded
|
||||
end
|
||||
|
||||
function MemoryAnalyzerRowClass:valueHasBeenNonZero()
|
||||
return self._valueHasBeenNonZero
|
||||
end
|
||||
|
||||
function MemoryAnalyzerRowClass:setExpansionToggleCallback(callback)
|
||||
self._expansionToggleCallback = callback
|
||||
end
|
||||
|
||||
function MemoryAnalyzerRowClass:__toggleExpansion()
|
||||
self._expanded = not self._expanded
|
||||
self:__updateButtonState()
|
||||
if (self._expansionToggleCallback ~= nil) then
|
||||
self._expansionToggleCallback()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function MemoryAnalyzerRowClass:__updateButtonState()
|
||||
local children = self._treeViewItem:getChildren()
|
||||
if #children == 0 then
|
||||
self._button.Visible = false
|
||||
else
|
||||
self._button.Visible = true
|
||||
if (self._expanded) then
|
||||
self._button.Text = "-"
|
||||
else
|
||||
self._button.Text = "+"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MemoryAnalyzerRowClass:updateValue()
|
||||
local value = self._treeViewItem:getValue()
|
||||
-- We know we're only dealing with positive numbers.
|
||||
-- And if I'm only going to display 3 significant digits (see below) I'm really
|
||||
-- asking if this is >= 0.001
|
||||
if (value >= 0.001) then
|
||||
self._valueHasBeenNonZero = true
|
||||
end
|
||||
|
||||
self._valueTextLabel.Text = string.format("%.3f", self._treeViewItem:getValue())
|
||||
end
|
||||
|
||||
function MemoryAnalyzerRowClass:setZIndex(zIndex)
|
||||
self._frame.ZIndex = zIndex
|
||||
self._labelTextLabel.ZIndex = zIndex
|
||||
self._valueTextLabel.ZIndex = zIndex
|
||||
end
|
||||
|
||||
function MemoryAnalyzerRowClass:setRowValue(value)
|
||||
self._valueTextLabel.Text = string.format("%.3f", value)
|
||||
end
|
||||
|
||||
function MemoryAnalyzerRowClass:getFrame()
|
||||
return self._frame
|
||||
end
|
||||
|
||||
function MemoryAnalyzerRowClass:setRowNumber(rowNumber)
|
||||
if (rowNumber % 2 == 1) then
|
||||
self._buttonFrame.BackgroundColor3 = StdRowColor3
|
||||
self._labelFrame.BackgroundColor3 = StdRowColor3
|
||||
self._valueFrame.BackgroundColor3 = StdRowColor3
|
||||
else
|
||||
self._buttonFrame.BackgroundColor3 = AltRowColor3
|
||||
self._labelFrame.BackgroundColor3 = AltRowColor3
|
||||
self._valueFrame.BackgroundColor3 = AltRowColor3
|
||||
end
|
||||
end
|
||||
|
||||
--////////////////////////////////////
|
||||
--
|
||||
-- BaseMemoryAnalyzerClass
|
||||
-- The whole table.
|
||||
--
|
||||
--////////////////////////////////////
|
||||
local BaseMemoryAnalyzerClass = {}
|
||||
BaseMemoryAnalyzerClass.__index = BaseMemoryAnalyzerClass
|
||||
|
||||
BaseMemoryAnalyzerClass.Indent = string.rep(" ", 4)
|
||||
|
||||
function BaseMemoryAnalyzerClass.new(parentFrame)
|
||||
local self = {}
|
||||
setmetatable(self, BaseMemoryAnalyzerClass)
|
||||
|
||||
-- The gui widget containing the whole thing.
|
||||
self._frame = Instance.new("Frame")
|
||||
self._frame.Name = "MemoryAnalyzerClassFrame"
|
||||
self._frame.ZIndex = parentFrame.ZIndex
|
||||
self._frame.BackgroundTransparency = 1
|
||||
|
||||
-- a map from treeViewItem to the Row used to display the treeViewItem.
|
||||
self._rowsByTreeViewItem = {}
|
||||
|
||||
-- things need to be laid out, either because I have new content or
|
||||
-- the size of my parent container changed.
|
||||
-- Starts out as 'false' because there's no rows -> nothing to lay out.
|
||||
self._layoutDirty = false
|
||||
|
||||
self._heightChangedCallback = nil
|
||||
self._heightInPix = 0
|
||||
|
||||
self._frame.Parent = parentFrame
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- This needs to be overridden in derived classes.
|
||||
function BaseMemoryAnalyzerClass:getMemoryUsageTree()
|
||||
return nil
|
||||
end
|
||||
|
||||
function BaseMemoryAnalyzerClass:setHeightChangedCallback(callback)
|
||||
self._heightChangedCallback = callback
|
||||
end
|
||||
|
||||
function BaseMemoryAnalyzerClass:__getOrMakeRowForTreeViewItem(treeViewItem)
|
||||
if (self._rowsByTreeViewItem[treeViewItem] == nil) then
|
||||
local row = MemoryAnalyzerRowClass.new(treeViewItem)
|
||||
row:setExpansionToggleCallback(function()
|
||||
self:__layoutRows()
|
||||
end)
|
||||
local frame = row:getFrame()
|
||||
frame.Parent = self._frame
|
||||
|
||||
self._rowsByTreeViewItem[treeViewItem] = row
|
||||
self._layoutDirty = true
|
||||
end
|
||||
return self._rowsByTreeViewItem[treeViewItem]
|
||||
end
|
||||
|
||||
function BaseMemoryAnalyzerClass:__recursiveUpdateStatValue(treeViewItem)
|
||||
local row = self:__getOrMakeRowForTreeViewItem(treeViewItem)
|
||||
row:updateValue()
|
||||
|
||||
local children = treeViewItem:getChildren()
|
||||
for i, child in ipairs(children) do
|
||||
self:__recursiveUpdateStatValue(child)
|
||||
end
|
||||
end
|
||||
|
||||
-- Write latest stat values into each row.
|
||||
function BaseMemoryAnalyzerClass:renderUpdates()
|
||||
local treeViewItemRoot = self:getMemoryUsageTree()
|
||||
if (treeViewItemRoot ~= nil) then
|
||||
self:__recursiveUpdateStatValue(treeViewItemRoot)
|
||||
end
|
||||
|
||||
if self._layoutDirty then
|
||||
self:__layoutRows()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function BaseMemoryAnalyzerClass:__layoutRows()
|
||||
self._layoutDirty = false
|
||||
self._heightInPix = 0
|
||||
|
||||
local treeViewItemRoot = self:getMemoryUsageTree()
|
||||
if (treeViewItemRoot ~= nil) then
|
||||
self:__recursiveLayoutTreeViewItem(self:getMemoryUsageTree(), true)
|
||||
end
|
||||
|
||||
self._frame.Size = UDim2.new(1, 0, 0, self._heightInPix)
|
||||
self._frame.Position = UDim2.new(0, 0, 0, 0)
|
||||
|
||||
if (self._heightChangedCallback) then
|
||||
self._heightChangedCallback(self._heightInPix)
|
||||
end
|
||||
end
|
||||
|
||||
function BaseMemoryAnalyzerClass:__recursiveLayoutTreeViewItem(treeViewItem, isVisible)
|
||||
local row = self:__getOrMakeRowForTreeViewItem(treeViewItem)
|
||||
local frame = row:getFrame()
|
||||
|
||||
-- caller has something to say about whether it's visible (ancestor may be collapsed).
|
||||
-- I also may be invisible if I have never been non-zero.
|
||||
isVisible = (isVisible and row:valueHasBeenNonZero())
|
||||
|
||||
if (isVisible) then
|
||||
frame.Visible = true
|
||||
frame.Size = UDim2.new(1, 0, 0, RowHeight)
|
||||
frame.Position = UDim2.new(0, 0, 0, self._heightInPix)
|
||||
row:setZIndex(self._frame.ZIndex)
|
||||
row:setRowNumber(self._heightInPix/RowHeight)
|
||||
|
||||
self._heightInPix = self._heightInPix + RowHeight
|
||||
else
|
||||
frame.Visible = false
|
||||
end
|
||||
|
||||
local children = treeViewItem:getChildren()
|
||||
for i, child in ipairs(children) do
|
||||
self:__recursiveLayoutTreeViewItem(child, row:isExpanded() and isVisible)
|
||||
end
|
||||
end
|
||||
|
||||
function BaseMemoryAnalyzerClass:getHeightInPix()
|
||||
return self._heightInPix
|
||||
end
|
||||
|
||||
return BaseMemoryAnalyzerClass
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
--[[
|
||||
Filename: ClientMemoryAnalyzer.lua
|
||||
Written by: dbanks
|
||||
Description: Widget to display client memory usage.
|
||||
--]]
|
||||
|
||||
--[[ Services ]]--
|
||||
local StatsService = game:GetService("Stats")
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Modules ]]--
|
||||
local BaseMemoryAnalyzerClass = require(CoreGuiService.RobloxGui.Modules.Stats.BaseMemoryAnalyzer)
|
||||
local CommonUtils = require(CoreGuiService.RobloxGui.Modules.Common.CommonUtil)
|
||||
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
|
||||
local TreeViewItem = require(CoreGuiService.RobloxGui.Modules.Stats.TreeViewItem)
|
||||
|
||||
--[[ Helper functions ]]--
|
||||
local function __GetMemoryPerformanceStatsItem()
|
||||
local performanceStats = StatsService and StatsService:FindFirstChild("PerformanceStats")
|
||||
if performanceStats == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local memoryStats = performanceStats:FindFirstChild(
|
||||
StatsUtils.StatNames[StatsUtils.StatType_Memory])
|
||||
return memoryStats
|
||||
end
|
||||
|
||||
local function __FillInMemoryUsageTreeRecursive(treeViewItem, statsItem)
|
||||
local statId = statsItem.Name
|
||||
local statLabel = StatsUtils.GetMemoryAnalyzerStatName(statId)
|
||||
local statValue = statsItem:GetValue()
|
||||
|
||||
treeViewItem:setLabelAndValue(statLabel, statValue)
|
||||
|
||||
local rawChildren = statsItem:GetChildren()
|
||||
-- sort children by name.
|
||||
local sortedChildren = CommonUtils.SortByName(rawChildren)
|
||||
|
||||
for i, childStatItem in ipairs(sortedChildren) do
|
||||
local childStatId = childStatItem.Name
|
||||
local childTreeItem = treeViewItem:getOrMakeChildById(childStatId)
|
||||
__FillInMemoryUsageTreeRecursive(childTreeItem, childStatItem)
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Classes ]]--
|
||||
|
||||
--////////////////////////////////////
|
||||
--
|
||||
-- ClientMemoryAnalyzerClass
|
||||
-- The whole table customized for Client memory.
|
||||
--
|
||||
--////////////////////////////////////
|
||||
local ClientMemoryAnalyzerClass = {}
|
||||
-- Derive from BaseMemoryAnalyzerClass
|
||||
setmetatable(ClientMemoryAnalyzerClass, BaseMemoryAnalyzerClass)
|
||||
ClientMemoryAnalyzerClass.__index = ClientMemoryAnalyzerClass
|
||||
|
||||
function ClientMemoryAnalyzerClass.new(parentFrame)
|
||||
local self = BaseMemoryAnalyzerClass.new(parentFrame)
|
||||
setmetatable(self, ClientMemoryAnalyzerClass)
|
||||
|
||||
self._rootTreeViewItem = nil
|
||||
|
||||
-- am I currently listening for updates?
|
||||
self._shouldListenForUpdates = false
|
||||
|
||||
-- management of loop that does the listening.
|
||||
-- Note: it is not enough to just track _shouldListenForUpdates.
|
||||
-- If we called
|
||||
-- startListeningForUpdates
|
||||
-- stopListeningForUpdates
|
||||
-- startListeningForUpdates
|
||||
-- in quick succession, then in the second startListeningForUpdates,
|
||||
-- _shouldListenForUpdates is false, but we still have a loop spawned from
|
||||
-- the earlier call to startListeningForUpdates.
|
||||
self._spawnedLoopScheduled = false
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Start a thread that wakes up every n seconds
|
||||
-- and updates contents of stats widget.
|
||||
function ClientMemoryAnalyzerClass:startListeningForUpdates()
|
||||
self._shouldListenForUpdates = true
|
||||
|
||||
-- If we have already scheduled a loop to start, we're done.
|
||||
if (self._spawnedLoopScheduled) then
|
||||
return
|
||||
end
|
||||
self._spawnedLoopScheduled = true
|
||||
|
||||
spawn(function()
|
||||
while(self._shouldListenForUpdates) do
|
||||
self:refreshMemoryUsageTree()
|
||||
self:renderUpdates()
|
||||
wait(1)
|
||||
end
|
||||
-- Note that scheduled loop is now dead.
|
||||
self._spawnedLoopScheduled = false
|
||||
end)
|
||||
end
|
||||
|
||||
-- Stop the thread that does the updates.
|
||||
function ClientMemoryAnalyzerClass:stopListeningForUpdates()
|
||||
self._shouldListenForUpdates = false
|
||||
end
|
||||
|
||||
-- Generate the memory usage tree.
|
||||
function ClientMemoryAnalyzerClass:refreshMemoryUsageTree()
|
||||
if (self._rootTreeViewItem == nil) then
|
||||
self._rootTreeViewItem = TreeViewItem.new("root", nil)
|
||||
end
|
||||
|
||||
local statsItem = __GetMemoryPerformanceStatsItem()
|
||||
if statsItem == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
__FillInMemoryUsageTreeRecursive(self._rootTreeViewItem, statsItem)
|
||||
end
|
||||
|
||||
function ClientMemoryAnalyzerClass:getMemoryUsageTree()
|
||||
return self._rootTreeViewItem
|
||||
end
|
||||
|
||||
return ClientMemoryAnalyzerClass
|
||||
@@ -0,0 +1,93 @@
|
||||
--[[
|
||||
Filename: DecoratedValueLabel.lua
|
||||
Written by: dbanks
|
||||
Description: Icon, text label, numeric value.
|
||||
Icon is set by creator/caller.
|
||||
--]]
|
||||
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
|
||||
|
||||
local RobloxTranslator = require(CoreGuiService.RobloxGui.Modules.RobloxTranslator)
|
||||
|
||||
function LocalizedGetKey(key)
|
||||
local rtv = key
|
||||
pcall(function()
|
||||
rtv = RobloxTranslator:FormatByKey(key)
|
||||
end)
|
||||
|
||||
return rtv
|
||||
end
|
||||
|
||||
local success, result = pcall(function() return settings():GetFFlag('UseNotificationsLocalization') end)
|
||||
local FFlagUseNotificationsLocalization = success and result
|
||||
|
||||
local DecoratedValueLabelClass = {}
|
||||
DecoratedValueLabelClass.__index = DecoratedValueLabelClass
|
||||
|
||||
function DecoratedValueLabelClass.new(statType, valueName)
|
||||
local self = {}
|
||||
setmetatable(self, DecoratedValueLabelClass)
|
||||
|
||||
self._frame = Instance.new("Frame")
|
||||
self._frame.Name = "PS_DecoratedValueLabel"
|
||||
self._frame.BackgroundTransparency = 1.0
|
||||
|
||||
if FFlagUseNotificationsLocalization == true then
|
||||
self._valueName = LocalizedGetKey(valueName)
|
||||
else
|
||||
self._valueName = valueName
|
||||
end
|
||||
|
||||
self._statType = statType
|
||||
|
||||
self._decorationFrame = Instance.new("Frame")
|
||||
self._decorationFrame.Name = "PS_Decoration"
|
||||
self._decorationFrame.Parent = self._frame
|
||||
self._decorationFrame.Position = UDim2.new(0, 0, 0.5, -StatsUtils.DecorationSize/2)
|
||||
self._decorationFrame.Size = UDim2.new(0, StatsUtils.DecorationSize,
|
||||
0, StatsUtils.DecorationSize)
|
||||
self._decorationFrame.BackgroundTransparency = 1.0
|
||||
|
||||
self._label =Instance.new("TextLabel")
|
||||
self._label.Name = "Label"
|
||||
self._label.Parent = self._frame
|
||||
self._label.Position = UDim2.new(0, StatsUtils.DecorationSize + StatsUtils.DecorationMargin,
|
||||
0, 0)
|
||||
self._label.Size = UDim2.new(1, -(StatsUtils.DecorationSize + StatsUtils.DecorationMargin),
|
||||
1, 0)
|
||||
self._label.FontSize = StatsUtils.PanelValueFontSize
|
||||
self._label.TextXAlignment = Enum.TextXAlignment.Left
|
||||
self._label.TextYAlignment = Enum.TextYAlignment.Center
|
||||
|
||||
StatsUtils.StyleTextWidget(self._label)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function DecoratedValueLabelClass:SetZIndex(zIndex)
|
||||
self._frame.ZIndex = zIndex
|
||||
self._decorationFrame.ZIndex = zIndex
|
||||
self._label.ZIndex = zIndex
|
||||
end
|
||||
|
||||
|
||||
function DecoratedValueLabelClass:PlaceInParent(parent, size, position)
|
||||
self._frame.Parent = parent
|
||||
self._frame.Size = size
|
||||
self._frame.Position = position
|
||||
end
|
||||
|
||||
|
||||
function DecoratedValueLabelClass:GetDecorationFrame()
|
||||
return self._decorationFrame
|
||||
end
|
||||
|
||||
function DecoratedValueLabelClass:SetValue(value)
|
||||
local formattedValue = StatsUtils.FormatTypedValue(value, self._statType)
|
||||
self._label.Text = string.format("%s: %s", self._valueName, formattedValue)
|
||||
end
|
||||
|
||||
|
||||
return DecoratedValueLabelClass
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
--[[
|
||||
Filename: ServerMemoryAnalyzer.lua
|
||||
Written by: dbanks
|
||||
Description: Widget to display server memory usage.
|
||||
--]]
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Modules ]]--
|
||||
local BaseMemoryAnalyzerClass = require(CoreGuiService.RobloxGui.Modules.Stats.BaseMemoryAnalyzer)
|
||||
local TreeViewItem = require(CoreGuiService.RobloxGui.Modules.Stats.TreeViewItem)
|
||||
local CommonUtils = require(CoreGuiService.RobloxGui.Modules.Common.CommonUtil)
|
||||
|
||||
--[[ Globals ]]--
|
||||
local BYTES_PER_MB = 1048576.0;
|
||||
|
||||
-- labelToBytesUsedMap maps string to "num bytes used".
|
||||
-- for each item in the map:
|
||||
-- Make sure there's a child in the tree for that string.
|
||||
-- Update that child to have latest value.
|
||||
-- Add value to a sum total.
|
||||
-- Return the sum total.
|
||||
local function __ReadAndSumValues(treeViewItem, labelToBytesUsedMap)
|
||||
local totalMB = 0;
|
||||
|
||||
for label, numBytes in pairs(labelToBytesUsedMap) do
|
||||
-- Convert to MB.
|
||||
local numMB = numBytes / BYTES_PER_MB
|
||||
totalMB = totalMB + numMB
|
||||
local childTreeViewItem = treeViewItem:getOrMakeChildById(label)
|
||||
childTreeViewItem:setLabelAndValue(label, numMB)
|
||||
end
|
||||
return totalMB
|
||||
end
|
||||
|
||||
|
||||
--[[ Classes ]]--
|
||||
|
||||
--////////////////////////////////////
|
||||
--
|
||||
-- ServerMemoryAnalyzerClass
|
||||
-- The whole table customized for Server memory.
|
||||
--
|
||||
--////////////////////////////////////
|
||||
local ServerMemoryAnalyzerClass = {}
|
||||
-- Derive from BaseMemoryAnalyzerClass
|
||||
setmetatable(ServerMemoryAnalyzerClass, BaseMemoryAnalyzerClass)
|
||||
ServerMemoryAnalyzerClass.__index = ServerMemoryAnalyzerClass
|
||||
|
||||
function ServerMemoryAnalyzerClass.new(parentFrame)
|
||||
local self = BaseMemoryAnalyzerClass.new(parentFrame)
|
||||
setmetatable(self, ServerMemoryAnalyzerClass)
|
||||
|
||||
self._cachedRootTreeViewItem = nil
|
||||
self._coreTreeViewItem = nil
|
||||
self._placeTreeViewItem = nil
|
||||
self._untrackedTreeViewItem = nil
|
||||
|
||||
self._isVisible = false
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- 'static' function.
|
||||
-- 'stats' is a value table from server.
|
||||
-- One top-level key is "ServerMemoryTree".
|
||||
-- That contains a table that looks like this:
|
||||
-- "totalServerMemory": <some value>
|
||||
-- "developerTags": <table that looks like...>
|
||||
-- <developer tag label>: <developer tag value>
|
||||
-- (for all developer tags).
|
||||
-- "internalCategories": <table that looks like...>
|
||||
-- <category label>: <category value>
|
||||
-- (for all categories associated with the "internal" developer tag.)
|
||||
-- We want to 'filter' this so that we return just the "ServerMemoryTree" value.
|
||||
function ServerMemoryAnalyzerClass:filterServerMemoryTreeStats(stats)
|
||||
if (stats.ServerMemoryTree == nil) then
|
||||
return {}
|
||||
else
|
||||
return stats.ServerMemoryTree
|
||||
end
|
||||
end
|
||||
|
||||
function ServerMemoryAnalyzerClass:updateWithTreeStats(stats)
|
||||
local totalServerMemory = 0
|
||||
|
||||
if (self._cachedRootTreeViewItem == nil) then
|
||||
self._cachedRootTreeViewItem = TreeViewItem.new("root", nil)
|
||||
-- make sure the childen are in the order I want them in.
|
||||
self._coreTreeViewItem = self._cachedRootTreeViewItem:getOrMakeChildById("CoreMemory")
|
||||
self._placeTreeViewItem = self._cachedRootTreeViewItem:getOrMakeChildById("PlaceMemory")
|
||||
self._untrackedTreeViewItem = self._cachedRootTreeViewItem:getOrMakeChildById("UntrackedMemory")
|
||||
end
|
||||
|
||||
|
||||
-- All values are in bytes.
|
||||
-- Convert to MB ASAP.
|
||||
for key, value in pairs(stats) do
|
||||
if key == "totalServerMemory" then
|
||||
totalServerMemory = value / BYTES_PER_MB
|
||||
elseif key == "developerTags" then
|
||||
local sum = __ReadAndSumValues(self._placeTreeViewItem, value)
|
||||
self._placeTreeViewItem:setLabelAndValue("PlaceMemory", sum)
|
||||
elseif key == "internalCategories" then
|
||||
local sum = __ReadAndSumValues(self._coreTreeViewItem, value)
|
||||
self._coreTreeViewItem:setLabelAndValue("CoreMemory", sum)
|
||||
end
|
||||
end
|
||||
|
||||
-- Update total.
|
||||
self._cachedRootTreeViewItem:setLabelAndValue("Memory", totalServerMemory)
|
||||
|
||||
-- Update untracked.
|
||||
local untrackedMemory = totalServerMemory -
|
||||
(self._coreTreeViewItem:getValue() + self._placeTreeViewItem:getValue())
|
||||
self._untrackedTreeViewItem:setLabelAndValue("UntrackedMemory", untrackedMemory)
|
||||
|
||||
self:renderUpdates();
|
||||
end
|
||||
|
||||
function ServerMemoryAnalyzerClass:getMemoryUsageTree()
|
||||
return self._cachedRootTreeViewItem
|
||||
end
|
||||
|
||||
function ServerMemoryAnalyzerClass:setVisible(isVisible)
|
||||
self._isVisible = isVisible
|
||||
if (self._isVisible) then
|
||||
self:renderUpdates()
|
||||
end
|
||||
end
|
||||
|
||||
return ServerMemoryAnalyzerClass
|
||||
@@ -0,0 +1,156 @@
|
||||
--[[
|
||||
Filename: StatsAggregator.lua
|
||||
Written by: dbanks
|
||||
Description: Gather and store stats on regular heartbeat.
|
||||
--]]
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Modules ]]--
|
||||
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
|
||||
|
||||
--[[ Classes ]]--
|
||||
local StatsAggregatorClass = {}
|
||||
StatsAggregatorClass.__index = StatsAggregatorClass
|
||||
|
||||
function StatsAggregatorClass.new(statType, numSamples, pauseBetweenSamples)
|
||||
local self = {}
|
||||
setmetatable(self, StatsAggregatorClass)
|
||||
|
||||
self._statType = statType
|
||||
self._numSamples = numSamples
|
||||
self._pauseBetweenSamples = pauseBetweenSamples
|
||||
|
||||
self._statName = StatsUtils.StatNames[self._statType]
|
||||
self._statMaxName = StatsUtils.StatMaxNames[self._statType]
|
||||
|
||||
-- init our circular buffer.
|
||||
self._samples = {}
|
||||
for i = 0, numSamples-1, 1 do
|
||||
self._samples[i] = 0
|
||||
end
|
||||
self._oldestIndex = 0
|
||||
|
||||
self._listeners = {}
|
||||
|
||||
-- FIXME(dbanks)
|
||||
-- Just want to be real clear this is a key, not an array index.
|
||||
self._nextListenerId = 1001
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:AddListener(callbackFunction)
|
||||
local id = self._nextListenerId
|
||||
self._nextListenerId = self._nextListenerId+1
|
||||
self._listeners[id] = callbackFunction
|
||||
return id
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:RemoveListener(listenerId)
|
||||
self._listeners[listenerId] = nil
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:_notifyAllListeners()
|
||||
for listenerId, listenerCallback in pairs(self._listeners) do
|
||||
listenerCallback()
|
||||
end
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:StartListening()
|
||||
-- On a regular heartbeat, wake up and read the latest
|
||||
-- value into circular buffer.
|
||||
-- Don't bother if we're already listening.
|
||||
if (self._listening == true) then
|
||||
return
|
||||
end
|
||||
|
||||
spawn(function()
|
||||
self._listening = true
|
||||
while(self._listening) do
|
||||
local statValue = self:_getStatValue()
|
||||
self:_storeStatValue(statValue)
|
||||
self:_notifyAllListeners()
|
||||
wait(self._pauseBetweenSamples)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:StopListening()
|
||||
self._listening = false
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:GetValues()
|
||||
-- Get the past N values, from oldest to newest.
|
||||
local retval = {}
|
||||
local actualIndex
|
||||
for i = 0, self._numSamples-1, 1 do
|
||||
actualIndex = (self._oldestIndex + i) % self._numSamples
|
||||
retval[i+1] = self._samples[actualIndex]
|
||||
end
|
||||
return retval
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:GetAverage()
|
||||
-- Get average of past N values.
|
||||
local retval = 0.0
|
||||
for i = 0, self._numSamples-1, 1 do
|
||||
retval = retval + self._samples[i]
|
||||
end
|
||||
return retval / self._numSamples
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:GetLatestValue()
|
||||
-- Get latest value.
|
||||
local index = (self._oldestIndex + self._numSamples -1) % self._numSamples
|
||||
return self._samples[index]
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:_storeStatValue(value)
|
||||
-- Store this as the latest value in our circular buffer.
|
||||
self._samples[self._oldestIndex] = value
|
||||
self._oldestIndex = (self._oldestIndex + 1) % self._numSamples
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:_getStatValue()
|
||||
-- Look up and return the statistic we care about.
|
||||
local statsService = game:GetService("Stats")
|
||||
if statsService == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
local performanceStats = statsService:FindFirstChild("PerformanceStats")
|
||||
if performanceStats == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
local itemStats = performanceStats:FindFirstChild(self._statName)
|
||||
if itemStats == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
return itemStats:GetValue()
|
||||
end
|
||||
|
||||
function StatsAggregatorClass:GetTarget()
|
||||
-- Look up and return the statistic we care about.
|
||||
local statsService = game:GetService("Stats")
|
||||
if statsService == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
local performanceStats = statsService:FindFirstChild("PerformanceStats")
|
||||
if performanceStats == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
local itemStats = performanceStats:FindFirstChild(self._statMaxName)
|
||||
if itemStats == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
return itemStats:GetValue()
|
||||
end
|
||||
|
||||
return StatsAggregatorClass
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
|
||||
--[[
|
||||
Filename: StatsAggregatorManager.lua
|
||||
Written by: dbanks
|
||||
Description: Indexed array of stats aggregators, one for each stat.
|
||||
--]]
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Modules ]]--
|
||||
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
|
||||
local StatsAggregatorClass = require(CoreGuiService.RobloxGui.Modules.Stats.StatsAggregator)
|
||||
|
||||
|
||||
--[[ Classes ]]--
|
||||
local StatsAggregatorManagerClass = {}
|
||||
StatsAggregatorManagerClass.__index = StatsAggregatorManagerClass
|
||||
|
||||
StatsAggregatorManagerClass.SecondsBetweenUpdate = 1.0
|
||||
StatsAggregatorManagerClass.NumSamplesToKeep = 20
|
||||
|
||||
local statsAggregatorManagerSingleton = nil
|
||||
|
||||
function StatsAggregatorManagerClass.getSingleton()
|
||||
if (statsAggregatorManagerSingleton == nil) then
|
||||
statsAggregatorManagerSingleton = StatsAggregatorManagerClass.__new()
|
||||
-- Start listening for updates in stats.
|
||||
statsAggregatorManagerSingleton:StartListening()
|
||||
end
|
||||
return statsAggregatorManagerSingleton
|
||||
end
|
||||
|
||||
function StatsAggregatorManagerClass.__new()
|
||||
local self = {}
|
||||
setmetatable(self, StatsAggregatorManagerClass)
|
||||
|
||||
self._statsAggregators = {}
|
||||
|
||||
for i, statType in ipairs(StatsUtils.AllStatTypes) do
|
||||
local statsAggregator = StatsAggregatorClass.new(statType,
|
||||
StatsAggregatorManagerClass.NumSamplesToKeep,
|
||||
StatsAggregatorManagerClass.SecondsBetweenUpdate)
|
||||
self._statsAggregators[statType] = statsAggregator
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function StatsAggregatorManagerClass:StartListening()
|
||||
for i, statsAggregator in pairs(self._statsAggregators) do
|
||||
statsAggregator:StartListening()
|
||||
end
|
||||
end
|
||||
|
||||
function StatsAggregatorManagerClass:StopListening()
|
||||
for i, statsAggregator in pairs(self._statsAggregators) do
|
||||
statsAggregator:StopListening()
|
||||
end
|
||||
end
|
||||
|
||||
function StatsAggregatorManagerClass:GetAggregator(statsType)
|
||||
return self._statsAggregators[statsType]
|
||||
end
|
||||
|
||||
return StatsAggregatorManagerClass
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
--[[
|
||||
Filename: StatsAnnotatedGraph.lua
|
||||
Written by: dbanks
|
||||
Description: A graph plus extra annotations like axis markings,
|
||||
target lines, etc.
|
||||
--]]
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Globals ]]--
|
||||
local Margin = 10
|
||||
local LabelXWidth = 30
|
||||
|
||||
--[[ Modules ]]--
|
||||
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
|
||||
local BarGraphClass = require(CoreGuiService.RobloxGui.Modules.Stats.BarGraph)
|
||||
|
||||
--[[ Classes ]]--
|
||||
local StatsAnnotatedGraphClass = {}
|
||||
StatsAnnotatedGraphClass.__index = StatsAnnotatedGraphClass
|
||||
|
||||
function StatsAnnotatedGraphClass.new(statType, isMaximized)
|
||||
local self = {}
|
||||
setmetatable(self, StatsAnnotatedGraphClass)
|
||||
|
||||
self._statType = statType
|
||||
self._statMaxName = StatsUtils.StatMaxNames[statType]
|
||||
self._isMaximized = isMaximized
|
||||
|
||||
self._values = {}
|
||||
|
||||
-- Average value of all bars in the graph.
|
||||
self._average = 0
|
||||
-- Suggested max value for the stat being measured.
|
||||
self._target = 0
|
||||
-- Max value we display on the y-axis. Values higher than this are truncated.
|
||||
self._axisMax = 0
|
||||
|
||||
self._frame = Instance.new("Frame")
|
||||
self._frame.Name = "PS_AnnotatedGraph"
|
||||
self._frame.BackgroundTransparency = 1.0
|
||||
self._frame.ZIndex = StatsUtils.GraphZIndex
|
||||
|
||||
self._topLabel = Instance.new("TextLabel")
|
||||
self._topLabel.Name = "PS_TopAxisLabel"
|
||||
self._topLabel.Parent = self._frame
|
||||
self._topLabel.TextXAlignment = Enum.TextXAlignment.Left
|
||||
self._topLabel.TextYAlignment = Enum.TextYAlignment.Top
|
||||
self._topLabel.FontSize = StatsUtils.PanelGraphFontSize
|
||||
|
||||
self._bottomLabel = Instance.new("TextLabel")
|
||||
self._bottomLabel.Name = "PS_BottomAxisLabel"
|
||||
self._bottomLabel.Parent = self._frame
|
||||
self._bottomLabel.TextXAlignment = Enum.TextXAlignment.Left
|
||||
self._bottomLabel.TextYAlignment = Enum.TextYAlignment.Bottom
|
||||
self._bottomLabel.FontSize = StatsUtils.PanelGraphFontSize
|
||||
|
||||
local showExtras = isMaximized
|
||||
self._graph = BarGraphClass.new(showExtras)
|
||||
|
||||
StatsUtils.StyleTextWidget(self._topLabel)
|
||||
StatsUtils.StyleTextWidget(self._bottomLabel)
|
||||
|
||||
self:_layoutElements()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:SetZIndex(zIndex)
|
||||
self._frame.ZIndex = zIndex
|
||||
self._topLabel.ZIndex = zIndex
|
||||
self._bottomLabel.ZIndex = zIndex
|
||||
self._graph:SetZIndex(zIndex)
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:_layoutElements()
|
||||
local labelWidth
|
||||
if (self._isMaximized) then
|
||||
labelWidth = LabelXWidth
|
||||
|
||||
self._topLabel.Visible = true
|
||||
self._bottomLabel.Visible = true
|
||||
else
|
||||
labelWidth = 0
|
||||
|
||||
self._topLabel.Visible = false
|
||||
self._bottomLabel.Visible = false
|
||||
end
|
||||
|
||||
local GraphFramePosition = UDim2.new(0, Margin, 0, Margin)
|
||||
local GraphFrameSize = UDim2.new(1, -(2 * Margin + labelWidth), 1, -2 * Margin)
|
||||
|
||||
local TopLabelFramePosition = UDim2.new(1, -(Margin + labelWidth), 0, Margin)
|
||||
local TopLabelFrameSize = UDim2.new(0, labelWidth, 0.333, -2 * Margin)
|
||||
local BottomLabelFramePosition = UDim2.new(1, -(Margin + labelWidth), 0.666, Margin)
|
||||
local BottomLabelFrameSize = UDim2.new(0, labelWidth, 0.333, -2 * Margin)
|
||||
|
||||
self._topLabel.Size = TopLabelFrameSize
|
||||
self._topLabel.Position = TopLabelFramePosition
|
||||
self._bottomLabel.Size = BottomLabelFrameSize
|
||||
self._bottomLabel.Position = BottomLabelFramePosition
|
||||
|
||||
self._graph:PlaceInParent(self._frame, GraphFrameSize, GraphFramePosition)
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:PlaceInParent(parent, size, position)
|
||||
self._frame.Position = position
|
||||
self._frame.Size = size
|
||||
self._frame.Parent = parent
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:_getTarget()
|
||||
-- Get the current target value for the graphed stat.
|
||||
if self._performanceStats == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
local maxItemStats = self._performanceStats:FindFirstChild(self._statMaxName)
|
||||
if maxItemStats == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
return maxItemStats:GetValue()
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:_render()
|
||||
self._graph:SetAxisMax(self._axisMax)
|
||||
self._graph:SetValues(self._values)
|
||||
|
||||
self._graph:SetAverage(self._average)
|
||||
self._graph:SetTarget(self._target)
|
||||
self._graph:Render()
|
||||
|
||||
self._topLabel.Text = string.format("%.2f", self._axisMax)
|
||||
self._bottomLabel.Text = string.format("%.2f", 0.0)
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:_calculateAxisMax()
|
||||
-- Calculate an optimal max axis label for this graph, given this 'target' value.
|
||||
-- We want target to be roughly in the middle.
|
||||
-- Say, roughly twice the target.
|
||||
local max = self._target * 2
|
||||
local orderOfMagnitude = self:_recursiveGetOrderOfMagnitude(1.0, max)
|
||||
local div = math.floor(0.5 + max/orderOfMagnitude)
|
||||
self._axisMax = div * orderOfMagnitude
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:SetStatsAggregator(aggregator)
|
||||
if (self._aggregator) then
|
||||
self._aggregator:RemoveListener(self._listenerId)
|
||||
self._listenerId = nil
|
||||
self._aggregator = nil
|
||||
end
|
||||
|
||||
self._aggregator = aggregator
|
||||
|
||||
self:_refreshVisibility()
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:_stopListening()
|
||||
if (self._aggregator == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
if (self._listenerId == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
self._aggregator:RemoveListener(self._listenerId)
|
||||
self._listenerId = nil
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:_startListening()
|
||||
if (self._aggregator == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
if (self._listenerId ~= nil) then
|
||||
return
|
||||
end
|
||||
|
||||
self._listenerId = self._aggregator:AddListener(function()
|
||||
self:_updateValuesAndRender()
|
||||
end)
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:_shouldBeVisible()
|
||||
if StatsUtils:PerformanceStatsShouldBeVisible() then
|
||||
return self._frame.Visible
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:SetVisible(visible)
|
||||
self._frame.Visible = visible
|
||||
self:_refreshVisibility()
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:_refreshVisibility()
|
||||
if self:_shouldBeVisible() then
|
||||
self:_startListening()
|
||||
self:_updateValuesAndRender()
|
||||
else
|
||||
self:_stopListening()
|
||||
end
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:OnPerformanceStatsShouldBeVisibleChanged()
|
||||
self:_refreshVisibility()
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:_recursiveGetOrderOfMagnitude(estimate, target)
|
||||
if (estimate > target) then
|
||||
return self:_recursiveGetOrderOfMagnitude(estimate/10.0, target)
|
||||
end
|
||||
|
||||
if (estimate * 10 >= target) then
|
||||
return estimate
|
||||
end
|
||||
|
||||
return self:_recursiveGetOrderOfMagnitude(estimate*10.0, target)
|
||||
end
|
||||
|
||||
function StatsAnnotatedGraphClass:_updateValuesAndRender()
|
||||
self._values = {}
|
||||
self._average = 0
|
||||
self._target = 0
|
||||
if self._aggregator ~= nil then
|
||||
self._values = self._aggregator:GetValues()
|
||||
self._average = self._aggregator:GetAverage()
|
||||
self._target = self._aggregator:GetTarget()
|
||||
end
|
||||
|
||||
self:_calculateAxisMax()
|
||||
self:_render()
|
||||
end
|
||||
|
||||
return StatsAnnotatedGraphClass
|
||||
@@ -0,0 +1,102 @@
|
||||
--[[
|
||||
Filename: StatsButton.lua
|
||||
Written by: dbanks
|
||||
Description: Button that displays latest deets of one or two
|
||||
particular stats.
|
||||
--]]
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Modules ]]--
|
||||
local StyleWidgets = require(CoreGuiService.RobloxGui.Modules.StyleWidgets)
|
||||
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
|
||||
local StatsMiniTextPanelClass = require(CoreGuiService.RobloxGui.Modules.Stats.StatsMiniTextPanel)
|
||||
local StatsAnnotatedGraphClass = require(CoreGuiService.RobloxGui.Modules.Stats.StatsAnnotatedGraph)
|
||||
|
||||
--[[ Globals ]]--
|
||||
local TextPanelXFraction = 0.5
|
||||
local GraphXFraction = 1 - TextPanelXFraction
|
||||
|
||||
local TextPanelPosition = UDim2.new(0, 0, 0, 0)
|
||||
local TextPanelSize = UDim2.new(TextPanelXFraction, 0, 1, -StyleWidgets.TabSelectionHeight)
|
||||
local GraphPosition = UDim2.new(TextPanelXFraction, 0, 0, 0)
|
||||
local GraphSize = UDim2.new(GraphXFraction, 0, 1, -StyleWidgets.TabSelectionHeight)
|
||||
|
||||
--[[ Classes ]]--
|
||||
local StatsButtonClass = {}
|
||||
StatsButtonClass.__index = StatsButtonClass
|
||||
|
||||
function StatsButtonClass.new(statType)
|
||||
local self = {}
|
||||
setmetatable(self, StatsButtonClass)
|
||||
|
||||
self._statType = statType
|
||||
self._button = Instance.new("TextButton")
|
||||
self._button.Name = "PS_Button"
|
||||
self._button.Text = ""
|
||||
|
||||
StatsUtils.StyleButton(self._button)
|
||||
|
||||
self._textPanel = StatsMiniTextPanelClass.new(statType)
|
||||
self._textPanel:PlaceInParent(self._button,
|
||||
TextPanelSize,
|
||||
TextPanelPosition)
|
||||
|
||||
self._graph = StatsAnnotatedGraphClass.new(statType, false)
|
||||
self._graph:PlaceInParent(self._button,
|
||||
GraphSize,
|
||||
GraphPosition)
|
||||
|
||||
self._textPanel:SetZIndex(StatsUtils.TextZIndex)
|
||||
self._graph:SetZIndex(StatsUtils.GraphZIndex)
|
||||
|
||||
self._tabSelection = StyleWidgets.MakeTabSelectionWidget(self._button)
|
||||
|
||||
self._isSelected = false
|
||||
|
||||
self:_updateTabSelectionState();
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function StatsButtonClass:OnPerformanceStatsShouldBeVisibleChanged()
|
||||
if self._graph then
|
||||
self._graph:OnPerformanceStatsShouldBeVisibleChanged()
|
||||
end
|
||||
|
||||
if self._textPanel then
|
||||
self._textPanel:OnPerformanceStatsShouldBeVisibleChanged()
|
||||
end
|
||||
end
|
||||
|
||||
function StatsButtonClass:SetToggleCallbackFunction(callbackFunction)
|
||||
self._button.MouseButton1Click:connect(function()
|
||||
callbackFunction(self._statType)
|
||||
end)
|
||||
end
|
||||
|
||||
function StatsButtonClass:SetSizeAndPosition(size, position)
|
||||
self._button.Size = size;
|
||||
self._button.Position = position;
|
||||
end
|
||||
|
||||
function StatsButtonClass:SetIsSelected(isSelected)
|
||||
self._isSelected = isSelected
|
||||
self:_updateTabSelectionState();
|
||||
end
|
||||
|
||||
function StatsButtonClass:_updateTabSelectionState()
|
||||
self._tabSelection.Visible = self._isSelected
|
||||
end
|
||||
|
||||
function StatsButtonClass:SetParent(parent)
|
||||
self._button.Parent = parent
|
||||
end
|
||||
|
||||
function StatsButtonClass:SetStatsAggregator(aggregator)
|
||||
self._textPanel:SetStatsAggregator(aggregator)
|
||||
self._graph:SetStatsAggregator(aggregator)
|
||||
end
|
||||
|
||||
return StatsButtonClass
|
||||
@@ -0,0 +1,175 @@
|
||||
--[[
|
||||
Filename: StatsMiniTextPanel.lua
|
||||
Written by: dbanks
|
||||
Description: Panel that shows title and current value for a given stat.
|
||||
--]]
|
||||
|
||||
--[[ Globals ]]--
|
||||
local TitleHeightYFraction = 0.4
|
||||
local ValueHeightYFraction = 0.3
|
||||
local TitleTopYFraction = (1 - TitleHeightYFraction -
|
||||
ValueHeightYFraction)/2
|
||||
local LeftMarginPix = 10
|
||||
|
||||
local TitlePosition = UDim2.new(0,
|
||||
LeftMarginPix,
|
||||
TitleTopYFraction,
|
||||
0)
|
||||
local TitleSize = UDim2.new(1,
|
||||
-LeftMarginPix * 2,
|
||||
TitleHeightYFraction,
|
||||
0)
|
||||
local ValuePosition = UDim2.new(0,
|
||||
LeftMarginPix,
|
||||
TitleTopYFraction + TitleHeightYFraction,
|
||||
0)
|
||||
local ValueSize = UDim2.new(1,
|
||||
-LeftMarginPix * 2,
|
||||
ValueHeightYFraction,
|
||||
0)
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Modules ]]--
|
||||
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
|
||||
local StatsAggregatorClass = require(CoreGuiService.RobloxGui.Modules.Stats.StatsAggregator)
|
||||
|
||||
--[[ Classes ]]--
|
||||
local StatsMiniTextPanelClass = {}
|
||||
StatsMiniTextPanelClass.__index = StatsMiniTextPanelClass
|
||||
|
||||
function StatsMiniTextPanelClass.new(statType)
|
||||
local self = {}
|
||||
setmetatable(self, StatsMiniTextPanelClass)
|
||||
|
||||
self._statType = statType
|
||||
|
||||
self._frame = Instance.new("Frame")
|
||||
self._frame.Name = "StatsMiniTextPanelClass"
|
||||
|
||||
self._frame.BackgroundTransparency = 1.0
|
||||
self._frame.ZIndex = StatsUtils.TextPanelZIndex
|
||||
|
||||
self._titleLabel = Instance.new("TextLabel")
|
||||
self._titleLabel.Name = "TitleLabel"
|
||||
self._valueLabel = Instance.new("TextLabel")
|
||||
self._valueLabel.Name = "ValueLabel"
|
||||
|
||||
StatsUtils.StyleTextWidget(self._titleLabel)
|
||||
StatsUtils.StyleTextWidget(self._valueLabel)
|
||||
|
||||
self._titleLabel.FontSize = StatsUtils.MiniPanelTitleFontSize
|
||||
self._titleLabel.Text = self:_getTitle()
|
||||
self._titleLabel.Parent = self._frame
|
||||
self._titleLabel.Size = TitleSize
|
||||
self._titleLabel.Position = TitlePosition
|
||||
self._titleLabel.TextXAlignment = Enum.TextXAlignment.Left
|
||||
|
||||
self._valueLabel.FontSize = StatsUtils.MiniPanelValueFontSize
|
||||
self._valueLabel.Text = "0"
|
||||
self._valueLabel.Parent = self._frame
|
||||
self._valueLabel.Size = ValueSize
|
||||
self._valueLabel.Position = ValuePosition
|
||||
self._valueLabel.TextXAlignment = Enum.TextXAlignment.Left
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function StatsMiniTextPanelClass:SetZIndex(zIndex)
|
||||
self._frame.ZIndex = zIndex
|
||||
self._titleLabel.ZIndex = zIndex
|
||||
self.ZIndex = zIndex
|
||||
end
|
||||
|
||||
function StatsMiniTextPanelClass:_getTitle()
|
||||
return StatsUtils.TypeToShortName[self._statType]
|
||||
end
|
||||
|
||||
function StatsMiniTextPanelClass:PlaceInParent(parent, size, position)
|
||||
self._frame.Position = position
|
||||
self._frame.Size = size
|
||||
self._frame.Parent = parent
|
||||
end
|
||||
|
||||
function StatsMiniTextPanelClass:SetStatsAggregator(aggregator)
|
||||
if (self._aggregator) then
|
||||
self._aggregator:RemoveListener(self._listenerId)
|
||||
self._listenerId = nil
|
||||
self._aggregator = nil
|
||||
end
|
||||
|
||||
self._aggregator = aggregator
|
||||
|
||||
self:_refreshVisibility()
|
||||
end
|
||||
|
||||
function StatsMiniTextPanelClass:SetVisible(visible)
|
||||
self.frame.Visible = visible
|
||||
self:_refreshVisibility()
|
||||
end
|
||||
|
||||
function StatsMiniTextPanelClass:_shouldBeVisible()
|
||||
if StatsUtils.PerformanceStatsShouldBeVisible() then
|
||||
return self._frame.Visible
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function StatsMiniTextPanelClass:_refreshVisibility()
|
||||
if self:_shouldBeVisible() then
|
||||
self:_startListening()
|
||||
self:_updateFromAggregator()
|
||||
else
|
||||
self:_stopListening()
|
||||
end
|
||||
end
|
||||
|
||||
function StatsMiniTextPanelClass:_stopListening()
|
||||
if (self._aggregator == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
if (self._listenerId == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
self._aggregator:RemoveListener(self._listenerId)
|
||||
self._listenerId = nil
|
||||
end
|
||||
|
||||
function StatsMiniTextPanelClass:_startListening()
|
||||
if (self._aggregator == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
if (self._listenerId ~= nil) then
|
||||
return
|
||||
end
|
||||
|
||||
self._listenerId = self._aggregator:AddListener(function()
|
||||
self:_updateFromAggregator()
|
||||
end)
|
||||
end
|
||||
|
||||
function StatsMiniTextPanelClass:OnPerformanceStatsShouldBeVisibleChanged()
|
||||
self:_refreshVisibility()
|
||||
end
|
||||
|
||||
function StatsMiniTextPanelClass:_updateFromAggregator()
|
||||
local value
|
||||
|
||||
if self._aggregator ~= nil then
|
||||
value = self._aggregator:GetLatestValue()
|
||||
else
|
||||
value = 0
|
||||
end
|
||||
|
||||
self._valueLabel.Text = StatsUtils.FormatTypedValue(value, self._statType)
|
||||
end
|
||||
|
||||
|
||||
|
||||
return StatsMiniTextPanelClass
|
||||
@@ -0,0 +1,265 @@
|
||||
--[[
|
||||
Filename: StatsTextPanel.lua
|
||||
Written by: dbanks
|
||||
Description: Panel that shows a "Legend" for a graph, including:
|
||||
- name of stat being displayed.
|
||||
- current value of stat.
|
||||
- target (suggested max) value of stat.
|
||||
- average value of stat over the whole graph.
|
||||
particular stat.
|
||||
--]]
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Modules ]]--
|
||||
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
|
||||
local StatsAggregatorClass = require(CoreGuiService.RobloxGui.Modules.Stats.StatsAggregator)
|
||||
local DecoratedValueLabelClass = require(CoreGuiService.RobloxGui.Modules.Stats.DecoratedValueLabel)
|
||||
|
||||
--[[ Globals ]]--
|
||||
-- Positions
|
||||
local top = StatsUtils.TextPanelTopMarginPix
|
||||
local TitlePosition = UDim2.new(0,
|
||||
StatsUtils.TextPanelLeftMarginPix,
|
||||
0,
|
||||
top)
|
||||
top = top + StatsUtils.TextPanelTitleHeightY
|
||||
local CurrentValuePosition = UDim2.new(0,
|
||||
StatsUtils.TextPanelLeftMarginPix,
|
||||
0,
|
||||
top)
|
||||
top = top + StatsUtils.TextPanelLegendItemHeightY
|
||||
local TargetValuePosition = UDim2.new(0,
|
||||
StatsUtils.TextPanelLeftMarginPix,
|
||||
0,
|
||||
top)
|
||||
top = top + StatsUtils.TextPanelLegendItemHeightY
|
||||
local AverageValuePosition = UDim2.new(0,
|
||||
StatsUtils.TextPanelLeftMarginPix,
|
||||
0,
|
||||
top)
|
||||
|
||||
-- Sizes
|
||||
local TitleSize = UDim2.new(1,
|
||||
-StatsUtils.TextPanelLeftMarginPix * 2,
|
||||
0,
|
||||
StatsUtils.TextPanelTitleHeightY)
|
||||
local LegendItemValueSize = UDim2.new(1,
|
||||
-StatsUtils.TextPanelLeftMarginPix * 2,
|
||||
0,
|
||||
StatsUtils.TextPanelLegendItemHeightY)
|
||||
|
||||
--[[ Classes ]]--
|
||||
local StatsTextPanelClass = {}
|
||||
StatsTextPanelClass.__index = StatsTextPanelClass
|
||||
|
||||
function StatsTextPanelClass.new(statType)
|
||||
local self = {}
|
||||
setmetatable(self, StatsTextPanelClass)
|
||||
|
||||
self._statType = statType
|
||||
|
||||
self._frame = Instance.new("Frame")
|
||||
self._frame.BackgroundTransparency = 1.0
|
||||
self._frame.ZIndex = StatsUtils.TextPanelZIndex
|
||||
|
||||
self._titleLabel = Instance.new("TextLabel")
|
||||
|
||||
StatsUtils.StyleTextWidget(self._titleLabel)
|
||||
|
||||
self._titleLabel.FontSize = StatsUtils.PanelTitleFontSize
|
||||
self._titleLabel.Text = self:_getTitle()
|
||||
|
||||
self._titleLabel.Parent = self._frame
|
||||
self._titleLabel.Size = TitleSize
|
||||
self._titleLabel.Position = TitlePosition
|
||||
self._titleLabel.TextXAlignment = Enum.TextXAlignment.Left
|
||||
self._titleLabel.TextYAlignment = Enum.TextYAlignment.Top
|
||||
|
||||
-- Icon + text widgets to show the current value of this stat.
|
||||
self:_addCurrentValueWidget()
|
||||
-- Icon + text widgets to show the suggested max value of this stat.
|
||||
self:_addTargetValueWidget()
|
||||
-- Icon + text widgets to show the average value of this stat.
|
||||
self:_addAverageValueWidget()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:_getTarget()
|
||||
-- Get the current target value for the graphed stat.
|
||||
if self._performanceStats == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
local maxItemStats = self._performanceStats:FindFirstChild(self._statMaxName)
|
||||
if maxItemStats == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
return maxItemStats:GetValue()
|
||||
end
|
||||
|
||||
|
||||
function StatsTextPanelClass:_addCurrentValueWidget()
|
||||
self._currentValueWidget = DecoratedValueLabelClass.new(self._statType,
|
||||
"Current")
|
||||
|
||||
self._currentValueWidget:PlaceInParent(self._frame,
|
||||
LegendItemValueSize,
|
||||
CurrentValuePosition)
|
||||
|
||||
local decorationFrame = self._currentValueWidget:GetDecorationFrame()
|
||||
local decoration = Instance.new("ImageLabel")
|
||||
decoration.Position = UDim2.new(0.5, -StatsUtils.OvalKeySize/2,
|
||||
0.5, -StatsUtils.OvalKeySize/2)
|
||||
decoration.Size = UDim2.new(0, StatsUtils.OvalKeySize,
|
||||
0, StatsUtils.OvalKeySize)
|
||||
|
||||
decoration.Parent = decorationFrame
|
||||
decoration.BackgroundTransparency = 1
|
||||
decoration.Image = 'rbxasset://textures/ui/PerformanceStats/OvalKey.png'
|
||||
decoration.BorderSizePixel = 0
|
||||
self._currentValueDecoration = decoration
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:_addTargetValueWidget()
|
||||
self._targetValueWidget = DecoratedValueLabelClass.new(self._statType,
|
||||
"Target")
|
||||
|
||||
self._targetValueWidget:PlaceInParent(self._frame,
|
||||
LegendItemValueSize,
|
||||
TargetValuePosition)
|
||||
|
||||
local decorationFrame = self._targetValueWidget:GetDecorationFrame()
|
||||
local decoration = Instance.new("ImageLabel")
|
||||
decoration.Position = UDim2.new(0.5, -StatsUtils.TargetKeyWidth/2,
|
||||
0.5, -StatsUtils.TargetKeyHeight/2)
|
||||
decoration.Size = UDim2.new(0, StatsUtils.TargetKeyWidth,
|
||||
0, StatsUtils.TargetKeyHeight)
|
||||
|
||||
decoration.Parent = decorationFrame
|
||||
decoration.BackgroundTransparency = 1
|
||||
decoration.Image = 'rbxasset://textures/ui/PerformanceStats/TargetKey.png'
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:_addAverageValueWidget()
|
||||
self._averageValueWidget = DecoratedValueLabelClass.new(self._statType,
|
||||
"Average")
|
||||
|
||||
self._averageValueWidget:PlaceInParent(self._frame,
|
||||
LegendItemValueSize,
|
||||
AverageValuePosition)
|
||||
|
||||
local decorationFrame = self._averageValueWidget:GetDecorationFrame()
|
||||
|
||||
local decoration = Instance.new("Frame")
|
||||
decoration.Position = UDim2.new(0, 0, 0.5, -StatsUtils.GraphAverageLineTotalThickness/2)
|
||||
decoration.Size = UDim2.new(1, 0, 0, StatsUtils.GraphAverageLineInnerThickness)
|
||||
decoration.Parent = decorationFrame
|
||||
|
||||
StatsUtils.StyleAverageLine(decoration)
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:_getTitle()
|
||||
return StatsUtils.TypeToName[self._statType]
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:PlaceInParent(parent, size, position)
|
||||
self._frame.Position = position
|
||||
self._frame.Size = size
|
||||
self._frame.Parent = parent
|
||||
end
|
||||
|
||||
|
||||
function StatsTextPanelClass:SetStatsAggregator(aggregator)
|
||||
if (self._aggregator) then
|
||||
self._aggregator:RemoveListener(self._listenerId)
|
||||
self._listenerId = nil
|
||||
self._aggregator = nil
|
||||
end
|
||||
|
||||
self._aggregator = aggregator
|
||||
|
||||
self:_refreshVisibility()
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:SetVisible(visible)
|
||||
self._frame.Visible = visible
|
||||
self:_refreshVisibility()
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:_shouldBeVisible()
|
||||
if StatsUtils.PerformanceStatsShouldBeVisible() then
|
||||
return self._frame.Visible
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function StatsTextPanelClass:_refreshVisibility()
|
||||
if self:_shouldBeVisible() then
|
||||
self:_startListening()
|
||||
self:_updateFromAggregator()
|
||||
else
|
||||
self:_stopListening()
|
||||
end
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:_stopListening()
|
||||
if (self._aggregator == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
if (self._listenerId == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
self._aggregator:RemoveListener(self._listenerId)
|
||||
self._listenerId = nil
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:_startListening()
|
||||
if (self._aggregator == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
if (self._listenerId ~= nil) then
|
||||
return
|
||||
end
|
||||
|
||||
self._listenerId = self._aggregator:AddListener(function()
|
||||
self:_updateFromAggregator()
|
||||
end)
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:_updateFromAggregator()
|
||||
local value = 0
|
||||
local average = 0
|
||||
local target = 0
|
||||
|
||||
if self._aggregator ~= nil then
|
||||
value = self._aggregator:GetLatestValue()
|
||||
average = self._aggregator:GetAverage()
|
||||
target = self._aggregator:GetTarget()
|
||||
end
|
||||
|
||||
self._currentValueWidget:SetValue(value)
|
||||
self._targetValueWidget:SetValue(target)
|
||||
self._averageValueWidget:SetValue(average)
|
||||
|
||||
self._currentValueDecoration.ImageColor3 = StatsUtils.GetColorForValue(value, target)
|
||||
|
||||
end
|
||||
|
||||
function StatsTextPanelClass:SetZIndex(zIndex)
|
||||
-- Pass through to all children.
|
||||
self._frame.ZIndex = zIndex
|
||||
self._titleLabel.ZIndex = zIndex
|
||||
self._currentValueWidget:SetZIndex(zIndex)
|
||||
self._averageValueWidget:SetZIndex(zIndex)
|
||||
end
|
||||
|
||||
return StatsTextPanelClass
|
||||
@@ -0,0 +1,247 @@
|
||||
--!nocheck
|
||||
|
||||
--[[
|
||||
Filename: StatsUtils.lua
|
||||
Written by: dbanks
|
||||
Description: Common work in the performance stats world.
|
||||
--]]
|
||||
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
local PlayersService = game:GetService("Players")
|
||||
local Settings = UserSettings()
|
||||
local GameSettings = Settings.GameSettings
|
||||
|
||||
local StyleWidgets = require(CoreGuiService.RobloxGui.Modules.StyleWidgets)
|
||||
|
||||
local RobloxTranslator = require(CoreGuiService.RobloxGui.Modules.RobloxTranslator)
|
||||
|
||||
function LocalizedGetKey(key)
|
||||
local rtv = key
|
||||
pcall(function()
|
||||
local LocalizationService = game:GetService("LocalizationService")
|
||||
local CorescriptLocalization = LocalizationService:GetCorescriptLocalizations()[1]
|
||||
|
||||
rtv = RobloxTranslator:FormatByKey(key)
|
||||
end)
|
||||
return rtv
|
||||
end
|
||||
|
||||
local success, result = pcall(function() return settings():GetFFlag('UseNotificationsLocalization') end)
|
||||
local FFlagUseNotificationsLocalization = success and result
|
||||
|
||||
--[[ Classes ]]--
|
||||
local StatsUtils = {}
|
||||
|
||||
-- Colors
|
||||
StatsUtils.SelectedBackgroundColor = Color3.new(0.4, 0.4, 0.4)
|
||||
StatsUtils.FontColor = Color3.new(1, 1, 1)
|
||||
StatsUtils.GraphBarGreenColor = Color3.new(126/255.0, 211/255.0, 33/255.0)
|
||||
StatsUtils.GraphBarYellowColor = Color3.new(209/255.0, 211/255.0, 33/255.0)
|
||||
StatsUtils.GraphBarRedColor = Color3.new(211/255.0, 88/255.0, 33/255.0)
|
||||
StatsUtils.GraphAverageLineColor = Color3.new(208/255.0, 1/255.0, 27/255.0)
|
||||
StatsUtils.GraphAverageLineBorderColor = Color3.new(1, 1, 1)
|
||||
StatsUtils.NormalColor = Color3.fromRGB(31, 31, 31)
|
||||
StatsUtils.Transparency = 0.5
|
||||
|
||||
-- Font Sizes
|
||||
StatsUtils.MiniPanelTitleFontSize = Enum.FontSize.Size12
|
||||
StatsUtils.MiniPanelValueFontSize = Enum.FontSize.Size10
|
||||
StatsUtils.PanelTitleFontSize = Enum.FontSize.Size24
|
||||
StatsUtils.PanelValueFontSize = Enum.FontSize.Size14
|
||||
StatsUtils.PanelGraphFontSize = Enum.FontSize.Size10
|
||||
|
||||
-- Layout
|
||||
-- Layout: Buttons
|
||||
StatsUtils.ButtonHeight = 36 + StyleWidgets.TabSelectionHeight
|
||||
|
||||
-- Layout: Viewer
|
||||
StatsUtils.ViewerTopMargin = 10
|
||||
StatsUtils.ViewerHeight = 144
|
||||
StatsUtils.ViewerWidth = 306
|
||||
|
||||
StatsUtils.TextZIndex = 5
|
||||
StatsUtils.GraphZIndex = 2
|
||||
|
||||
-- Layout: Graph
|
||||
StatsUtils.GraphTargetLineInnerThickness = 2
|
||||
StatsUtils.GraphAverageLineInnerThickness = 2
|
||||
StatsUtils.GraphAverageLineBorderThickness = 1
|
||||
StatsUtils.GraphAverageLineTotalThickness = (StatsUtils.GraphAverageLineInnerThickness +
|
||||
2 * StatsUtils.GraphAverageLineBorderThickness)
|
||||
|
||||
-- Layout: Main Text Panel
|
||||
StatsUtils.TextPanelTitleHeightY = 52
|
||||
StatsUtils.TextPanelLegendItemHeightY = 20
|
||||
|
||||
StatsUtils.TextPanelLeftMarginPix = 10
|
||||
StatsUtils.TextPanelTopMarginPix = 10
|
||||
|
||||
-- Layout: Graph Legend
|
||||
StatsUtils.DecorationSize = 12
|
||||
StatsUtils.OvalKeySize = 8
|
||||
StatsUtils.TargetKeyWidth = 11
|
||||
StatsUtils.TargetKeyHeight = 2
|
||||
StatsUtils.DecorationMargin = 6
|
||||
|
||||
|
||||
-- Enums
|
||||
StatsUtils.StatType_Memory = "st_Memory"
|
||||
StatsUtils.StatType_CPU = "st_CPU"
|
||||
StatsUtils.StatType_GPU = "st_GPU"
|
||||
StatsUtils.StatType_NetworkSent = "st_NetworkSent"
|
||||
StatsUtils.StatType_NetworkReceived = "st_NetworkReceived"
|
||||
StatsUtils.StatType_Ping = "st_Ping"
|
||||
|
||||
StatsUtils.AllStatTypes = {
|
||||
StatsUtils.StatType_Memory,
|
||||
StatsUtils.StatType_CPU,
|
||||
StatsUtils.StatType_GPU,
|
||||
StatsUtils.StatType_NetworkSent,
|
||||
StatsUtils.StatType_NetworkReceived,
|
||||
StatsUtils.StatType_Ping,
|
||||
}
|
||||
|
||||
StatsUtils.StatNames = {
|
||||
[StatsUtils.StatType_Memory] = "Memory",
|
||||
[StatsUtils.StatType_CPU] = "CPU",
|
||||
[StatsUtils.StatType_GPU] = "GPU",
|
||||
[StatsUtils.StatType_NetworkSent] = "NetworkSent",
|
||||
[StatsUtils.StatType_NetworkReceived] = "NetworkReceived",
|
||||
[StatsUtils.StatType_Ping] = "Ping",
|
||||
}
|
||||
|
||||
StatsUtils.StatMaxNames = {
|
||||
[StatsUtils.StatType_Memory] = "MaxMemory",
|
||||
[StatsUtils.StatType_CPU] = "MaxCPU",
|
||||
[StatsUtils.StatType_GPU] = "MaxGPU",
|
||||
[StatsUtils.StatType_NetworkSent] = "MaxNetworkSent",
|
||||
[StatsUtils.StatType_NetworkReceived] = "MaxNetworkReceived",
|
||||
[StatsUtils.StatType_Ping] = "MaxPing",
|
||||
}
|
||||
|
||||
StatsUtils.NumButtonTypes = table.getn(StatsUtils.AllStatTypes)
|
||||
|
||||
local strSentNetwork = LocalizedGetKey("Sent") .. "\n" .. LocalizedGetKey("Network")
|
||||
local strReceivedNetwork = LocalizedGetKey("Received") .. "\n" .. LocalizedGetKey("Network")
|
||||
|
||||
StatsUtils.TypeToName = {
|
||||
[StatsUtils.StatType_Memory] = "Memory",
|
||||
[StatsUtils.StatType_CPU] = "CPU",
|
||||
[StatsUtils.StatType_GPU] = "GPU",
|
||||
[StatsUtils.StatType_NetworkSent] = strSentNetwork,
|
||||
[StatsUtils.StatType_NetworkReceived] = strReceivedNetwork,
|
||||
[StatsUtils.StatType_Ping] = "Ping",
|
||||
}
|
||||
|
||||
StatsUtils.TypeToShortName = {
|
||||
[StatsUtils.StatType_Memory] = "Mem",
|
||||
[StatsUtils.StatType_CPU] = "CPU",
|
||||
[StatsUtils.StatType_GPU] = "GPU",
|
||||
[StatsUtils.StatType_NetworkSent] = "Sent",
|
||||
[StatsUtils.StatType_NetworkReceived] = "Recv",
|
||||
[StatsUtils.StatType_Ping] = "Ping",
|
||||
}
|
||||
|
||||
StatsUtils.MemoryAnalyzerTypeToName = {
|
||||
["HumanoidTexture"] = "Humanoid Textures",
|
||||
["HumanoidTextureOrphan"] = "Humanoid Textures (Unused)",
|
||||
["OtherTexture"] = "Other Textures",
|
||||
["OtherTextureOrphan"] = "Other Textures (Unused)",
|
||||
["CoreScript"] = "Core Scripts",
|
||||
["UserScript"] = "User Scripts",
|
||||
["Sounds"] = "Sounds",
|
||||
["CSG"] = "Solid Models",
|
||||
["Meshes"] = "Meshes",
|
||||
}
|
||||
|
||||
function StatsUtils.GetMemoryAnalyzerStatName(memoryAnalyzerStatType)
|
||||
if (StatsUtils.MemoryAnalyzerTypeToName[memoryAnalyzerStatType] == nil) then
|
||||
return memoryAnalyzerStatType
|
||||
else
|
||||
return StatsUtils.MemoryAnalyzerTypeToName[memoryAnalyzerStatType]
|
||||
end
|
||||
end
|
||||
|
||||
function StatsUtils.StyleFrame(frame)
|
||||
frame.BackgroundColor3 = StatsUtils.NormalColor
|
||||
frame.BackgroundTransparency = StatsUtils.Transparency
|
||||
end
|
||||
|
||||
function StatsUtils.StyleButton(button)
|
||||
button.BackgroundColor3 = StatsUtils.NormalColor
|
||||
button.BackgroundTransparency = StatsUtils.Transparency
|
||||
end
|
||||
|
||||
function StatsUtils.StyleTextWidget(textLabel)
|
||||
textLabel.BackgroundTransparency = 1.0
|
||||
textLabel.TextColor3 = StatsUtils.FontColor
|
||||
textLabel.Font = Enum.Font.SourceSansBold
|
||||
end
|
||||
|
||||
function StatsUtils.StyleButtonSelected(frame, isSelected)
|
||||
StatsUtils.StyleButton(frame)
|
||||
if (isSelected) then
|
||||
frame.BackgroundColor3 = StatsUtils.SelectedBackgroundColor
|
||||
end
|
||||
end
|
||||
|
||||
local success, result = pcall(function() return settings():GetFFlag('UseNotificationsLocalization') end)
|
||||
local FFlagUseNotificationsLocalization = success and result
|
||||
local function LocalizedGetString(key, rtv)
|
||||
pcall(function()
|
||||
local LocalizationService = game:GetService("LocalizationService")
|
||||
local CorescriptLocalization = LocalizationService:GetCorescriptLocalizations()[1]
|
||||
rtv = CorescriptLocalization:GetString(LocalizationService.RobloxLocaleId, key)
|
||||
end)
|
||||
|
||||
return rtv
|
||||
end
|
||||
|
||||
function StatsUtils.FormatTypedValue(value, statType)
|
||||
if FFlagUseNotificationsLocalization then
|
||||
if statType == StatsUtils.StatType_CPU or statType == StatsUtils.StatType_GPU then
|
||||
return string.gsub(LocalizedGetString("StatsUtil.ms",string.format("%.2f MB", value)),"{RBX_NUMBER}",string.format("%.2f",value))
|
||||
elseif statType == StatsUtils.StatType_PlaceMemory then
|
||||
return string.gsub(LocalizedGetString("StatsUtil.MB",string.format("%.2f ms", value)),"{RBX_NUMBER}",string.format("%.2f",value))
|
||||
elseif statType == StatsUtils.StatType_NetworkSent or statType == StatsUtils.StatType_NetworkReceived then
|
||||
return string.gsub(LocalizedGetString("StatsUtil.KBps",string.format("%.2f KB/s", value)),"{RBX_NUMBER}",string.format("%.2f",value))
|
||||
end
|
||||
end
|
||||
|
||||
if statType == StatsUtils.StatType_Memory then
|
||||
return string.format("%.2f MB", value)
|
||||
elseif statType == StatsUtils.StatType_CPU then
|
||||
return string.format("%.2f ms", value)
|
||||
elseif statType == StatsUtils.StatType_GPU then
|
||||
return string.format("%.2f ms", value)
|
||||
elseif statType == StatsUtils.StatType_NetworkSent then
|
||||
return string.format("%.2f KB/s", value)
|
||||
elseif statType == StatsUtils.StatType_NetworkReceived then
|
||||
return string.format("%.2f KB/s", value)
|
||||
elseif statType == StatsUtils.StatType_Ping then
|
||||
return string.format("%.2f ms", value)
|
||||
end
|
||||
end
|
||||
|
||||
function StatsUtils.StyleAverageLine(frame)
|
||||
frame.BackgroundColor3 = StatsUtils.GraphAverageLineColor
|
||||
frame.BorderSizePixel = StatsUtils.GraphAverageLineBorderThickness
|
||||
frame.BorderColor3 = StatsUtils.GraphAverageLineBorderColor
|
||||
end
|
||||
|
||||
function StatsUtils.GetColorForValue(value, target)
|
||||
if value < 0.666 * target then
|
||||
return StatsUtils.GraphBarGreenColor
|
||||
elseif value < 1.333 * target then
|
||||
return StatsUtils.GraphBarYellowColor
|
||||
else
|
||||
return StatsUtils.GraphBarRedColor
|
||||
end
|
||||
end
|
||||
|
||||
function StatsUtils.PerformanceStatsShouldBeVisible()
|
||||
local localPlayer = PlayersService.LocalPlayer
|
||||
return (GameSettings.PerformanceStatsVisible and localPlayer ~= nil)
|
||||
end
|
||||
|
||||
return StatsUtils
|
||||
@@ -0,0 +1,139 @@
|
||||
--[[
|
||||
Filename: StatsButtonViewer.lua
|
||||
Written by: dbanks
|
||||
Description: Widget that displays one or more stats in closeup view:
|
||||
text and graphics.
|
||||
--]]
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGuiService = game:GetService('CoreGui')
|
||||
|
||||
--[[ Modules ]]--
|
||||
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
|
||||
local StatsTextPanelClass = require(CoreGuiService.RobloxGui.Modules.Stats.StatsTextPanel)
|
||||
local StatsAnnotatedGraphClass = require(CoreGuiService.RobloxGui.Modules.Stats.StatsAnnotatedGraph)
|
||||
|
||||
--[[ Globals ]]--
|
||||
local TextPanelXFraction = 0.4
|
||||
local GraphXFraction = 1 - TextPanelXFraction
|
||||
|
||||
local TextPanelPosition = UDim2.new(0, 0, 0, 0)
|
||||
local TextPanelSize = UDim2.new(TextPanelXFraction, 0, 1, 0)
|
||||
local GraphPosition = UDim2.new(TextPanelXFraction, 0, 0, 0)
|
||||
local GraphSize = UDim2.new(GraphXFraction, 0, 1, 0)
|
||||
|
||||
--[[ Classes ]]--
|
||||
local StatsViewerClass = {}
|
||||
StatsViewerClass.__index = StatsViewerClass
|
||||
|
||||
|
||||
function StatsViewerClass.new()
|
||||
local self = {}
|
||||
setmetatable(self, StatsViewerClass)
|
||||
|
||||
self._frameImageLabel = Instance.new("ImageLabel")
|
||||
self._frameImageLabel.Name = "PS_Viewer"
|
||||
self._frameImageLabel.Image = 'rbxasset://textures/ui/PerformanceStats/BackgroundRounded.png'
|
||||
self._frameImageLabel.ScaleType = Enum.ScaleType.Slice
|
||||
self._frameImageLabel.SliceCenter = Rect.new(10, 10, 22, 22)
|
||||
self._frameImageLabel.BackgroundTransparency = 1
|
||||
self._frameImageLabel.ImageColor3 = StatsUtils.NormalColor
|
||||
self._frameImageLabel.ImageTransparency = StatsUtils.Transparency
|
||||
|
||||
self._textPanel = nil
|
||||
self._statType = nil
|
||||
self._graph = nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function StatsViewerClass:OnPerformanceStatsShouldBeVisibleChanged()
|
||||
if self._graph then
|
||||
self._graph:OnPerformanceStatsShouldBeVisibleChanged()
|
||||
end
|
||||
if self._textPanel then
|
||||
self._textPanel:OnPerformanceStatsShouldBeVisibleChanged()
|
||||
end
|
||||
if self._textPanel then
|
||||
self._textPanel:OnVisibilityChanged()
|
||||
end
|
||||
end
|
||||
|
||||
function StatsViewerClass:GetIsVisible()
|
||||
return self._frameImageLabel.Visible
|
||||
end
|
||||
|
||||
function StatsViewerClass:GetStatType()
|
||||
return self._statType
|
||||
end
|
||||
|
||||
function StatsViewerClass:SetSizeAndPosition(size, position)
|
||||
self._frameImageLabel.Size = size
|
||||
self._frameImageLabel.Position = position
|
||||
end
|
||||
|
||||
function StatsViewerClass:SetParent(parent)
|
||||
self._frameImageLabel.Parent = parent
|
||||
end
|
||||
|
||||
function StatsViewerClass:SetVisible(visible)
|
||||
self._frameImageLabel.Visible = visible
|
||||
|
||||
if (self._graph) then
|
||||
self._graph:SetVisible(visible)
|
||||
end
|
||||
|
||||
if (self._textPanel) then
|
||||
self._textPanel:SetVisible(visible)
|
||||
end
|
||||
end
|
||||
|
||||
function StatsViewerClass:SetStatType(statType)
|
||||
self._statType = statType
|
||||
self._frameImageLabel:ClearAllChildren()
|
||||
-- If there's already a text panel, make it clear it is no longer visible.
|
||||
if (self._textPanel) then
|
||||
self._textPanel:SetVisible(false)
|
||||
self._textPanel = nil
|
||||
end
|
||||
|
||||
self._textPanel = StatsTextPanelClass.new(statType)
|
||||
self._textPanel:PlaceInParent(self._frameImageLabel,
|
||||
TextPanelSize,
|
||||
TextPanelPosition)
|
||||
|
||||
self._graph = StatsAnnotatedGraphClass.new(statType, true)
|
||||
self._graph:PlaceInParent(self._frameImageLabel,
|
||||
GraphSize,
|
||||
GraphPosition)
|
||||
|
||||
self._textPanel:SetZIndex(StatsUtils.TextZIndex)
|
||||
self._graph:SetZIndex(StatsUtils.GraphZIndex)
|
||||
|
||||
self._graph:SetVisible(self._frameImageLabel.Visible)
|
||||
self._textPanel:SetVisible(self._frameImageLabel.Visible)
|
||||
|
||||
|
||||
self:_applyStatsAggregator();
|
||||
end
|
||||
|
||||
function StatsViewerClass:_applyStatsAggregator()
|
||||
if (self._aggregator == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
if (self._textPanel) then
|
||||
self._textPanel:SetStatsAggregator(self._aggregator)
|
||||
end
|
||||
if (self._graph) then
|
||||
self._graph:SetStatsAggregator(self._aggregator)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function StatsViewerClass:SetStatsAggregator(aggregator)
|
||||
self._aggregator = aggregator
|
||||
self:_applyStatsAggregator();
|
||||
end
|
||||
|
||||
return StatsViewerClass
|
||||
@@ -0,0 +1,90 @@
|
||||
--[[
|
||||
Filename: TreeViewItem.lua
|
||||
Written by: dbanks
|
||||
Description: Generic tree view data stucture.
|
||||
--]]
|
||||
|
||||
|
||||
--[[ Globals ]]--
|
||||
|
||||
--[[ Helper functions ]]--
|
||||
|
||||
--[[ Classes ]]--
|
||||
|
||||
local TreeViewItem = {}
|
||||
TreeViewItem.__index = TreeViewItem
|
||||
|
||||
|
||||
function TreeViewItem.new(id, parent)
|
||||
local self = {}
|
||||
setmetatable(self, TreeViewItem)
|
||||
|
||||
self._id = id
|
||||
self._parent = parent
|
||||
self._children = {}
|
||||
return self
|
||||
end
|
||||
|
||||
function TreeViewItem:getValue()
|
||||
return self._value
|
||||
end
|
||||
|
||||
function TreeViewItem:getLabel()
|
||||
return self._label
|
||||
end
|
||||
|
||||
function TreeViewItem:getChildren()
|
||||
return self._children
|
||||
end
|
||||
|
||||
function TreeViewItem:getId()
|
||||
return self._id
|
||||
end
|
||||
|
||||
function TreeViewItem:getStackDepth()
|
||||
if (self._parent == nil) then
|
||||
return 0
|
||||
else
|
||||
return 1 + self._parent:getStackDepth()
|
||||
end
|
||||
end
|
||||
|
||||
function TreeViewItem:setLabelAndValue(label, value)
|
||||
self._label = label
|
||||
self._value = value
|
||||
end
|
||||
|
||||
function TreeViewItem:getOrMakeChildById(id)
|
||||
for i, childTreeViewItem in ipairs(self._children) do
|
||||
if (childTreeViewItem:getId() == id) then
|
||||
return childTreeViewItem
|
||||
end
|
||||
end
|
||||
|
||||
local newChild = TreeViewItem.new(id, self)
|
||||
table.insert(self._children, newChild)
|
||||
return newChild
|
||||
end
|
||||
|
||||
function TreeViewItem:removeChildren()
|
||||
if not self._children then return end
|
||||
for i, childTreeViewItem in ipairs(self._children) do
|
||||
childTreeViewItem:removeChildren()
|
||||
childTreeViewItem = nil
|
||||
end
|
||||
|
||||
self._id = nil
|
||||
self._parent = nil
|
||||
self._children = nil
|
||||
end
|
||||
|
||||
function TreeViewItem:removeChild(id)
|
||||
for i, childTreeViewItem in ipairs(self._children) do
|
||||
if (childTreeViewItem:getId() == id) then
|
||||
childTreeViewItem:removeChildren()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return TreeViewItem
|
||||
Reference in New Issue
Block a user