hover&unhover events
This commit is contained in:
186
examples/hover_demo.lua
Normal file
186
examples/hover_demo.lua
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
-- Example demonstrating hover and unhover events in FlexLöve
|
||||||
|
-- This shows how to use the new hover/unhover events for interactive UI elements
|
||||||
|
|
||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
|
||||||
|
function love.load()
|
||||||
|
FlexLove.init({
|
||||||
|
baseScale = { width = 1920, height = 1080 },
|
||||||
|
immediateMode = true,
|
||||||
|
autoFrameManagement = false,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- State to track hover status
|
||||||
|
local hoverStatus = "Not hovering"
|
||||||
|
local hoverCount = 0
|
||||||
|
local unhoverCount = 0
|
||||||
|
local lastEventTime = 0
|
||||||
|
|
||||||
|
function love.update(dt)
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
-- Create a container
|
||||||
|
FlexLove.new({
|
||||||
|
width = "100vw",
|
||||||
|
height = "100vh",
|
||||||
|
backgroundColor = FlexLove.Color.fromHex("#1a1a2e"),
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "vertical",
|
||||||
|
justifyContent = "center",
|
||||||
|
alignItems = "center",
|
||||||
|
gap = 30,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Title
|
||||||
|
FlexLove.new({
|
||||||
|
text = "Hover Event Demo",
|
||||||
|
textSize = "4xl",
|
||||||
|
textColor = FlexLove.Color.fromHex("#ffffff"),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Instructions
|
||||||
|
FlexLove.new({
|
||||||
|
text = "Move your mouse over the boxes below to see hover events",
|
||||||
|
textSize = "lg",
|
||||||
|
textColor = FlexLove.Color.fromHex("#a0a0a0"),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Status display
|
||||||
|
FlexLove.new({
|
||||||
|
text = hoverStatus,
|
||||||
|
textSize = "xl",
|
||||||
|
textColor = FlexLove.Color.fromHex("#4ecca3"),
|
||||||
|
padding = 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Event counters
|
||||||
|
FlexLove.new({
|
||||||
|
text = string.format("Hover events: %d | Unhover events: %d", hoverCount, unhoverCount),
|
||||||
|
textSize = "md",
|
||||||
|
textColor = FlexLove.Color.fromHex("#ffffff"),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Container for hover boxes
|
||||||
|
local boxContainer = FlexLove.new({
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "horizontal",
|
||||||
|
gap = 30,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Hover Box 1
|
||||||
|
FlexLove.new({
|
||||||
|
parent = boxContainer,
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
backgroundColor = FlexLove.Color.fromHex("#e94560"),
|
||||||
|
cornerRadius = 15,
|
||||||
|
positioning = "flex",
|
||||||
|
justifyContent = "center",
|
||||||
|
alignItems = "center",
|
||||||
|
text = "Hover Me!",
|
||||||
|
textSize = "xl",
|
||||||
|
textColor = FlexLove.Color.fromHex("#ffffff"),
|
||||||
|
onEvent = function(element, event)
|
||||||
|
if event.type == "hover" then
|
||||||
|
hoverStatus = "Hovering over RED box!"
|
||||||
|
hoverCount = hoverCount + 1
|
||||||
|
lastEventTime = love.timer.getTime()
|
||||||
|
elseif event.type == "unhover" then
|
||||||
|
hoverStatus = "Left RED box"
|
||||||
|
unhoverCount = unhoverCount + 1
|
||||||
|
lastEventTime = love.timer.getTime()
|
||||||
|
elseif event.type == "click" then
|
||||||
|
print("Clicked RED box!")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Hover Box 2
|
||||||
|
FlexLove.new({
|
||||||
|
parent = boxContainer,
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
backgroundColor = FlexLove.Color.fromHex("#4ecca3"),
|
||||||
|
cornerRadius = 15,
|
||||||
|
positioning = "flex",
|
||||||
|
justifyContent = "center",
|
||||||
|
alignItems = "center",
|
||||||
|
text = "Hover Me!",
|
||||||
|
textSize = "xl",
|
||||||
|
textColor = FlexLove.Color.fromHex("#1a1a2e"),
|
||||||
|
onEvent = function(element, event)
|
||||||
|
if event.type == "hover" then
|
||||||
|
hoverStatus = "Hovering over GREEN box!"
|
||||||
|
hoverCount = hoverCount + 1
|
||||||
|
lastEventTime = love.timer.getTime()
|
||||||
|
elseif event.type == "unhover" then
|
||||||
|
hoverStatus = "Left GREEN box"
|
||||||
|
unhoverCount = unhoverCount + 1
|
||||||
|
lastEventTime = love.timer.getTime()
|
||||||
|
elseif event.type == "click" then
|
||||||
|
print("Clicked GREEN box!")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Hover Box 3
|
||||||
|
FlexLove.new({
|
||||||
|
parent = boxContainer,
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
backgroundColor = FlexLove.Color.fromHex("#0f3460"),
|
||||||
|
cornerRadius = 15,
|
||||||
|
positioning = "flex",
|
||||||
|
justifyContent = "center",
|
||||||
|
alignItems = "center",
|
||||||
|
text = "Hover Me!",
|
||||||
|
textSize = "xl",
|
||||||
|
textColor = FlexLove.Color.fromHex("#ffffff"),
|
||||||
|
onEvent = function(element, event)
|
||||||
|
if event.type == "hover" then
|
||||||
|
hoverStatus = "Hovering over BLUE box!"
|
||||||
|
hoverCount = hoverCount + 1
|
||||||
|
lastEventTime = love.timer.getTime()
|
||||||
|
elseif event.type == "unhover" then
|
||||||
|
hoverStatus = "Left BLUE box"
|
||||||
|
unhoverCount = unhoverCount + 1
|
||||||
|
lastEventTime = love.timer.getTime()
|
||||||
|
elseif event.type == "click" then
|
||||||
|
print("Clicked BLUE box!")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Reset button
|
||||||
|
FlexLove.new({
|
||||||
|
width = 200,
|
||||||
|
height = 50,
|
||||||
|
backgroundColor = FlexLove.Color.fromHex("#e94560"),
|
||||||
|
cornerRadius = 25,
|
||||||
|
positioning = "flex",
|
||||||
|
justifyContent = "center",
|
||||||
|
alignItems = "center",
|
||||||
|
text = "Reset Counters",
|
||||||
|
textSize = "md",
|
||||||
|
textColor = FlexLove.Color.fromHex("#ffffff"),
|
||||||
|
margin = { top = 30 },
|
||||||
|
onEvent = function(element, event)
|
||||||
|
if event.type == "click" then
|
||||||
|
hoverCount = 0
|
||||||
|
unhoverCount = 0
|
||||||
|
hoverStatus = "Counters reset!"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
function love.draw()
|
||||||
|
FlexLove.draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
function love.resize(w, h)
|
||||||
|
FlexLove.resize(w, h)
|
||||||
|
end
|
||||||
@@ -152,12 +152,65 @@ function EventHandler:processMouseEvents(element, mx, my, isHovering, isActiveEl
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Track hover state changes even when events can't be processed
|
||||||
|
-- Fire unhover event if we were hovering and now we're not
|
||||||
|
if self._hovered and not isHovering then
|
||||||
|
self._hovered = false
|
||||||
|
-- Fire unhover event if handler exists
|
||||||
|
if self.onEvent then
|
||||||
|
local modifiers = EventHandler._utils.getModifiers()
|
||||||
|
local unhoverEvent = EventHandler._InputEvent.new({
|
||||||
|
type = "unhover",
|
||||||
|
button = 0,
|
||||||
|
x = mx,
|
||||||
|
y = my,
|
||||||
|
modifiers = modifiers,
|
||||||
|
clickCount = 0,
|
||||||
|
})
|
||||||
|
self:_invokeCallback(element, unhoverEvent)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if EventHandler._Performance and EventHandler._Performance.enabled then
|
if EventHandler._Performance and EventHandler._Performance.enabled then
|
||||||
EventHandler._Performance:stopTimer("event_mouse")
|
EventHandler._Performance:stopTimer("event_mouse")
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Track hover state changes and fire hover/unhover events BEFORE button processing
|
||||||
|
-- This ensures hover fires before press when mouse first enters element
|
||||||
|
local wasHovered = self._hovered
|
||||||
|
local isHoveringAndActive = isHovering and isActiveElement
|
||||||
|
|
||||||
|
if isHoveringAndActive and not wasHovered then
|
||||||
|
-- Just started hovering - fire hover event
|
||||||
|
self._hovered = true
|
||||||
|
local modifiers = EventHandler._utils.getModifiers()
|
||||||
|
local hoverEvent = EventHandler._InputEvent.new({
|
||||||
|
type = "hover",
|
||||||
|
button = 0,
|
||||||
|
x = mx,
|
||||||
|
y = my,
|
||||||
|
modifiers = modifiers,
|
||||||
|
clickCount = 0,
|
||||||
|
})
|
||||||
|
self:_invokeCallback(element, hoverEvent)
|
||||||
|
elseif not isHoveringAndActive and wasHovered then
|
||||||
|
-- Just stopped hovering - fire unhover event
|
||||||
|
self._hovered = false
|
||||||
|
local modifiers = EventHandler._utils.getModifiers()
|
||||||
|
local unhoverEvent = EventHandler._InputEvent.new({
|
||||||
|
type = "unhover",
|
||||||
|
button = 0,
|
||||||
|
x = mx,
|
||||||
|
y = my,
|
||||||
|
modifiers = modifiers,
|
||||||
|
clickCount = 0,
|
||||||
|
})
|
||||||
|
self:_invokeCallback(element, unhoverEvent)
|
||||||
|
end
|
||||||
|
|
||||||
-- Process all three mouse buttons
|
-- Process all three mouse buttons
|
||||||
local buttons = { 1, 2, 3 } -- left, right, middle
|
local buttons = { 1, 2, 3 } -- left, right, middle
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---@class InputEvent
|
---@class InputEvent
|
||||||
---@field type "click"|"press"|"release"|"rightclick"|"middleclick"|"drag"|"touchpress"|"touchmove"|"touchrelease"|"touchcancel"
|
---@field type "click"|"press"|"release"|"rightclick"|"middleclick"|"drag"|"hover"|"unhover"|"touchpress"|"touchmove"|"touchrelease"|"touchcancel"
|
||||||
---@field button number -- Mouse button: 1 (left), 2 (right), 3 (middle)
|
---@field button number -- Mouse button: 1 (left), 2 (right), 3 (middle)
|
||||||
---@field x number -- Mouse/Touch X position
|
---@field x number -- Mouse/Touch X position
|
||||||
---@field y number -- Mouse/Touch Y position
|
---@field y number -- Mouse/Touch Y position
|
||||||
@@ -15,7 +15,7 @@ local InputEvent = {}
|
|||||||
InputEvent.__index = InputEvent
|
InputEvent.__index = InputEvent
|
||||||
|
|
||||||
---@class InputEventProps
|
---@class InputEventProps
|
||||||
---@field type "click"|"press"|"release"|"rightclick"|"middleclick"|"drag"|"touchpress"|"touchmove"|"touchrelease"|"touchcancel"
|
---@field type "click"|"press"|"release"|"rightclick"|"middleclick"|"drag"|"hover"|"unhover"|"touchpress"|"touchmove"|"touchrelease"|"touchcancel"
|
||||||
---@field button number
|
---@field button number
|
||||||
---@field x number
|
---@field x number
|
||||||
---@field y number
|
---@field y number
|
||||||
@@ -76,7 +76,7 @@ function InputEvent.fromTouch(id, x, y, phase, pressure)
|
|||||||
y = y,
|
y = y,
|
||||||
dx = 0,
|
dx = 0,
|
||||||
dy = 0,
|
dy = 0,
|
||||||
modifiers = {shift = false, ctrl = false, alt = false, super = false},
|
modifiers = { shift = false, ctrl = false, alt = false, super = false },
|
||||||
clickCount = 1,
|
clickCount = 1,
|
||||||
timestamp = love.timer.getTime(),
|
timestamp = love.timer.getTime(),
|
||||||
touchId = touchIdStr,
|
touchId = touchIdStr,
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
-- Profiling test comparing retained mode flag vs. default behavior in complex UI
|
-- Settings Menu Mode Comparison Profile
|
||||||
-- This simulates creating a settings menu multiple times per frame to stress test
|
-- Compares performance between explicit mode="retained" flags vs implicit retained mode
|
||||||
-- the performance difference between explicit mode="retained" and implicit retained mode
|
|
||||||
|
|
||||||
package.path = package.path .. ";../../?.lua;../../modules/?.lua"
|
|
||||||
|
|
||||||
local FlexLove = require("FlexLove")
|
local FlexLove = require("FlexLove")
|
||||||
local Color = require("modules.Color")
|
|
||||||
|
|
||||||
-- Mock resolution sets (simplified)
|
local profile = {
|
||||||
local resolution_sets = {
|
name = "Settings Menu Mode Comparison",
|
||||||
["16:9"] = {
|
description = "Tests whether explicit mode='retained' has performance overhead",
|
||||||
{ 1920, 1080 },
|
testPhase = "warmup", -- warmup, implicit, explicit, complete
|
||||||
{ 1600, 900 },
|
frameCount = 0,
|
||||||
{ 1280, 720 },
|
framesPerPhase = 300, -- 5 seconds at 60 FPS
|
||||||
},
|
results = {
|
||||||
["16:10"] = {
|
implicit = { startMem = 0, endMem = 0, avgFrameTime = 0, frameTimes = {} },
|
||||||
{ 1920, 1200 },
|
explicit = { startMem = 0, endMem = 0, avgFrameTime = 0, frameTimes = {} },
|
||||||
{ 1680, 1050 },
|
|
||||||
{ 1280, 800 },
|
|
||||||
},
|
},
|
||||||
|
currentFrameStart = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Mock Settings object
|
-- Mock Settings object
|
||||||
@@ -28,48 +23,32 @@ local Settings = {
|
|||||||
fullscreen = false,
|
fullscreen = false,
|
||||||
vsync = true,
|
vsync = true,
|
||||||
msaa = 4,
|
msaa = 4,
|
||||||
resizable = true,
|
|
||||||
borderless = false,
|
|
||||||
masterVolume = 0.8,
|
masterVolume = 0.8,
|
||||||
musicVolume = 0.7,
|
|
||||||
sfxVolume = 0.9,
|
|
||||||
crtEffectStrength = 0.3,
|
|
||||||
},
|
},
|
||||||
get = function(self, key)
|
get = function(self, key)
|
||||||
return self.values[key]
|
return self.values[key]
|
||||||
end,
|
end,
|
||||||
set = function(self, key, value)
|
|
||||||
self.values[key] = value
|
|
||||||
end,
|
|
||||||
reset_to_defaults = function(self) end,
|
|
||||||
apply = function(self) end,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Helper function to round numbers
|
|
||||||
local function round(num, decimals)
|
|
||||||
local mult = 10 ^ (decimals or 0)
|
|
||||||
return math.floor(num * mult + 0.5) / mult
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Simplified SettingsMenu implementation
|
-- Simplified SettingsMenu implementation
|
||||||
local function create_settings_menu_with_mode_flag(use_mode_flag)
|
local function createSettingsMenu(useExplicitMode)
|
||||||
local GuiZIndexing = { MainMenuOverlay = 100 }
|
local GuiZIndexing = { MainMenuOverlay = 100 }
|
||||||
|
|
||||||
-- Backdrop
|
-- Backdrop
|
||||||
local backdrop_props = {
|
local backdropProps = {
|
||||||
z = GuiZIndexing.MainMenuOverlay - 1,
|
z = GuiZIndexing.MainMenuOverlay - 1,
|
||||||
width = "100%",
|
width = "100%",
|
||||||
height = "100%",
|
height = "100%",
|
||||||
backdropBlur = { radius = 10 },
|
backdropBlur = { radius = 10 },
|
||||||
backgroundColor = Color.new(1, 1, 1, 0.1),
|
backgroundColor = FlexLove.Color.new(1, 1, 1, 0.1),
|
||||||
}
|
}
|
||||||
if use_mode_flag then
|
if useExplicitMode then
|
||||||
backdrop_props.mode = "retained"
|
backdropProps.mode = "retained"
|
||||||
end
|
end
|
||||||
local backdrop = FlexLove.new(backdrop_props)
|
local backdrop = FlexLove.new(backdropProps)
|
||||||
|
|
||||||
-- Main window
|
-- Main window
|
||||||
local window_props = {
|
local windowProps = {
|
||||||
z = GuiZIndexing.MainMenuOverlay,
|
z = GuiZIndexing.MainMenuOverlay,
|
||||||
x = "5%",
|
x = "5%",
|
||||||
y = "5%",
|
y = "5%",
|
||||||
@@ -85,10 +64,10 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
padding = { horizontal = "5%", vertical = "3%" },
|
padding = { horizontal = "5%", vertical = "3%" },
|
||||||
gap = 10,
|
gap = 10,
|
||||||
}
|
}
|
||||||
if use_mode_flag then
|
if useExplicitMode then
|
||||||
window_props.mode = "retained"
|
windowProps.mode = "retained"
|
||||||
end
|
end
|
||||||
local window = FlexLove.new(window_props)
|
local window = FlexLove.new(windowProps)
|
||||||
|
|
||||||
-- Close button
|
-- Close button
|
||||||
FlexLove.new({
|
FlexLove.new({
|
||||||
@@ -131,7 +110,7 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
textAlign = "start",
|
textAlign = "start",
|
||||||
textSize = "xl",
|
textSize = "xl",
|
||||||
width = "100%",
|
width = "100%",
|
||||||
textColor = Color.new(0.8, 0.9, 1, 1),
|
textColor = FlexLove.Color.new(0.8, 0.9, 1, 1),
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Resolution control
|
-- Resolution control
|
||||||
@@ -246,7 +225,7 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
width = "30%",
|
width = "30%",
|
||||||
})
|
})
|
||||||
|
|
||||||
local button_container = FlexLove.new({
|
local buttonContainer = FlexLove.new({
|
||||||
parent = row4,
|
parent = row4,
|
||||||
width = "60%",
|
width = "60%",
|
||||||
height = "100%",
|
height = "100%",
|
||||||
@@ -255,19 +234,19 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
gap = 5,
|
gap = 5,
|
||||||
})
|
})
|
||||||
|
|
||||||
local msaa_values = { 0, 1, 2, 4, 8, 16 }
|
local msaaValues = { 0, 1, 2, 4, 8, 16 }
|
||||||
for _, msaa_val in ipairs(msaa_values) do
|
for _, msaaVal in ipairs(msaaValues) do
|
||||||
local is_selected = Settings:get("msaa") == msaa_val
|
local isSelected = Settings:get("msaa") == msaaVal
|
||||||
FlexLove.new({
|
FlexLove.new({
|
||||||
parent = button_container,
|
parent = buttonContainer,
|
||||||
themeComponent = is_selected and "buttonv1" or "buttonv2",
|
themeComponent = isSelected and "buttonv1" or "buttonv2",
|
||||||
text = tostring(msaa_val),
|
text = tostring(msaaVal),
|
||||||
textAlign = "center",
|
textAlign = "center",
|
||||||
width = "8vw",
|
width = "8vw",
|
||||||
height = "100%",
|
height = "100%",
|
||||||
textSize = "sm",
|
textSize = "sm",
|
||||||
disabled = is_selected,
|
disabled = isSelected,
|
||||||
opacity = is_selected and 0.7 or 1.0,
|
opacity = isSelected and 0.7 or 1.0,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -278,7 +257,7 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
textAlign = "start",
|
textAlign = "start",
|
||||||
textSize = "xl",
|
textSize = "xl",
|
||||||
width = "100%",
|
width = "100%",
|
||||||
textColor = Color.new(0.8, 0.9, 1, 1),
|
textColor = FlexLove.Color.new(0.8, 0.9, 1, 1),
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Master volume slider
|
-- Master volume slider
|
||||||
@@ -301,7 +280,7 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
width = "30%",
|
width = "30%",
|
||||||
})
|
})
|
||||||
|
|
||||||
local slider_container = FlexLove.new({
|
local sliderContainer = FlexLove.new({
|
||||||
parent = row5,
|
parent = row5,
|
||||||
width = "50%",
|
width = "50%",
|
||||||
height = "100%",
|
height = "100%",
|
||||||
@@ -314,8 +293,8 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
local value = Settings:get("masterVolume")
|
local value = Settings:get("masterVolume")
|
||||||
local normalized = value
|
local normalized = value
|
||||||
|
|
||||||
local slider_track = FlexLove.new({
|
local sliderTrack = FlexLove.new({
|
||||||
parent = slider_container,
|
parent = sliderContainer,
|
||||||
width = "80%",
|
width = "80%",
|
||||||
height = "75%",
|
height = "75%",
|
||||||
positioning = "flex",
|
positioning = "flex",
|
||||||
@@ -324,7 +303,7 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
})
|
})
|
||||||
|
|
||||||
FlexLove.new({
|
FlexLove.new({
|
||||||
parent = slider_track,
|
parent = sliderTrack,
|
||||||
width = (normalized * 100) .. "%",
|
width = (normalized * 100) .. "%",
|
||||||
height = "100%",
|
height = "100%",
|
||||||
themeComponent = "buttonv1",
|
themeComponent = "buttonv1",
|
||||||
@@ -332,7 +311,7 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
})
|
})
|
||||||
|
|
||||||
FlexLove.new({
|
FlexLove.new({
|
||||||
parent = slider_container,
|
parent = sliderContainer,
|
||||||
text = string.format("%d", value * 100),
|
text = string.format("%d", value * 100),
|
||||||
textAlign = "center",
|
textAlign = "center",
|
||||||
textSize = "md",
|
textSize = "md",
|
||||||
@@ -340,7 +319,7 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
})
|
})
|
||||||
|
|
||||||
-- Meta controls (bottom buttons)
|
-- Meta controls (bottom buttons)
|
||||||
local meta_container = FlexLove.new({
|
local metaContainer = FlexLove.new({
|
||||||
parent = window,
|
parent = window,
|
||||||
positioning = "absolute",
|
positioning = "absolute",
|
||||||
width = "100%",
|
width = "100%",
|
||||||
@@ -349,8 +328,8 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
x = "0%",
|
x = "0%",
|
||||||
})
|
})
|
||||||
|
|
||||||
local button_bar = FlexLove.new({
|
local buttonBar = FlexLove.new({
|
||||||
parent = meta_container,
|
parent = metaContainer,
|
||||||
width = "100%",
|
width = "100%",
|
||||||
positioning = "flex",
|
positioning = "flex",
|
||||||
flexDirection = "horizontal",
|
flexDirection = "horizontal",
|
||||||
@@ -360,7 +339,7 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
})
|
})
|
||||||
|
|
||||||
FlexLove.new({
|
FlexLove.new({
|
||||||
parent = button_bar,
|
parent = buttonBar,
|
||||||
themeComponent = "buttonv2",
|
themeComponent = "buttonv2",
|
||||||
text = "Reset",
|
text = "Reset",
|
||||||
textAlign = "center",
|
textAlign = "center",
|
||||||
@@ -372,136 +351,365 @@ local function create_settings_menu_with_mode_flag(use_mode_flag)
|
|||||||
return { backdrop = backdrop, window = window }
|
return { backdrop = backdrop, window = window }
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Profile configuration
|
function profile.init()
|
||||||
local PROFILE_NAME = "Settings Menu Mode Comparison"
|
print("\n=== Settings Menu Mode Comparison Profile ===\n")
|
||||||
local ITERATIONS_PER_TEST = 100 -- Create the menu 100 times to measure difference
|
print("Testing whether explicit mode='retained' has performance overhead")
|
||||||
|
print("compared to implicit retained mode (global setting).\n")
|
||||||
|
|
||||||
print("=" .. string.rep("=", 78))
|
FlexLove.init({
|
||||||
print(string.format(" %s", PROFILE_NAME))
|
width = love.graphics.getWidth(),
|
||||||
print("=" .. string.rep("=", 78))
|
height = love.graphics.getHeight(),
|
||||||
print()
|
immediateMode = false, -- Global retained mode
|
||||||
print("This profile compares performance when creating a complex settings menu")
|
theme = "space",
|
||||||
print("with explicit mode='retained' flags vs. implicit retained mode (global).")
|
})
|
||||||
print()
|
|
||||||
print(string.format("Test configuration:"))
|
|
||||||
print(string.format(" - Iterations: %d menu creations per test", ITERATIONS_PER_TEST))
|
|
||||||
print(string.format(" - Elements per menu: ~45 (backdrop, window, buttons, sliders, etc.)"))
|
|
||||||
print(string.format(" - Total elements created: ~%d per test", ITERATIONS_PER_TEST * 45))
|
|
||||||
print()
|
|
||||||
|
|
||||||
-- Warm up
|
profile.testPhase = "warmup"
|
||||||
print("Warming up...")
|
profile.frameCount = 0
|
||||||
FlexLove.init({ immediateMode = false, theme = "space" })
|
|
||||||
for i = 1, 10 do
|
|
||||||
create_settings_menu_with_mode_flag(false)
|
|
||||||
end
|
|
||||||
collectgarbage("collect")
|
|
||||||
|
|
||||||
-- Test 1: Without explicit mode flags (implicit retained via global setting)
|
print("Phase 1: Warmup (30 frames)...")
|
||||||
print("Running Test 1: Without explicit mode='retained' flags...")
|
|
||||||
FlexLove.init({ immediateMode = false, theme = "space" })
|
|
||||||
collectgarbage("collect")
|
|
||||||
local mem_before_implicit = collectgarbage("count")
|
|
||||||
local time_before_implicit = os.clock()
|
|
||||||
|
|
||||||
for i = 1, ITERATIONS_PER_TEST do
|
|
||||||
local menu = create_settings_menu_with_mode_flag(false)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local time_after_implicit = os.clock()
|
function profile.update(dt)
|
||||||
collectgarbage("collect")
|
if profile.testPhase == "complete" then
|
||||||
local mem_after_implicit = collectgarbage("count")
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local time_implicit = time_after_implicit - time_before_implicit
|
-- Track frame time
|
||||||
local mem_implicit = mem_after_implicit - mem_before_implicit
|
local frameStart = love.timer.getTime()
|
||||||
|
|
||||||
print(string.format(" Time: %.4f seconds", time_implicit))
|
if profile.testPhase == "warmup" then
|
||||||
print(string.format(" Memory: %.2f KB", mem_implicit))
|
-- Warmup phase - create menu a few times
|
||||||
print(string.format(" Avg time per menu: %.4f ms", (time_implicit / ITERATIONS_PER_TEST) * 1000))
|
createSettingsMenu(false)
|
||||||
print()
|
profile.frameCount = profile.frameCount + 1
|
||||||
|
|
||||||
-- Test 2: With explicit mode="retained" flags
|
if profile.frameCount >= 30 then
|
||||||
print("Running Test 2: With explicit mode='retained' flags...")
|
print(" Warmup complete.\n")
|
||||||
FlexLove.init({ immediateMode = false, theme = "space" })
|
print("Phase 2: Testing WITHOUT explicit mode flags (" .. profile.framesPerPhase .. " frames)...")
|
||||||
collectgarbage("collect")
|
profile.testPhase = "implicit"
|
||||||
local mem_before_explicit = collectgarbage("count")
|
profile.frameCount = 0
|
||||||
local time_before_explicit = os.clock()
|
collectgarbage("collect")
|
||||||
|
collectgarbage("collect")
|
||||||
|
profile.results.implicit.startMem = collectgarbage("count")
|
||||||
|
end
|
||||||
|
|
||||||
for i = 1, ITERATIONS_PER_TEST do
|
elseif profile.testPhase == "implicit" then
|
||||||
local menu = create_settings_menu_with_mode_flag(true)
|
-- Test implicit mode (no explicit mode flags)
|
||||||
|
createSettingsMenu(false)
|
||||||
|
|
||||||
|
local frameTime = (love.timer.getTime() - frameStart) * 1000
|
||||||
|
table.insert(profile.results.implicit.frameTimes, frameTime)
|
||||||
|
|
||||||
|
profile.frameCount = profile.frameCount + 1
|
||||||
|
|
||||||
|
if profile.frameCount >= profile.framesPerPhase then
|
||||||
|
collectgarbage("collect")
|
||||||
|
collectgarbage("collect")
|
||||||
|
profile.results.implicit.endMem = collectgarbage("count")
|
||||||
|
|
||||||
|
-- Calculate average
|
||||||
|
local sum = 0
|
||||||
|
for _, ft in ipairs(profile.results.implicit.frameTimes) do
|
||||||
|
sum = sum + ft
|
||||||
|
end
|
||||||
|
profile.results.implicit.avgFrameTime = sum / #profile.results.implicit.frameTimes
|
||||||
|
|
||||||
|
print(" Complete. Avg frame time: " .. string.format("%.4f", profile.results.implicit.avgFrameTime) .. "ms\n")
|
||||||
|
print("Phase 3: Testing WITH explicit mode='retained' flags (" .. profile.framesPerPhase .. " frames)...")
|
||||||
|
|
||||||
|
profile.testPhase = "explicit"
|
||||||
|
profile.frameCount = 0
|
||||||
|
collectgarbage("collect")
|
||||||
|
collectgarbage("collect")
|
||||||
|
profile.results.explicit.startMem = collectgarbage("count")
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif profile.testPhase == "explicit" then
|
||||||
|
-- Test explicit mode (with mode="retained" flags)
|
||||||
|
createSettingsMenu(true)
|
||||||
|
|
||||||
|
local frameTime = (love.timer.getTime() - frameStart) * 1000
|
||||||
|
table.insert(profile.results.explicit.frameTimes, frameTime)
|
||||||
|
|
||||||
|
profile.frameCount = profile.frameCount + 1
|
||||||
|
|
||||||
|
if profile.frameCount >= profile.framesPerPhase then
|
||||||
|
collectgarbage("collect")
|
||||||
|
collectgarbage("collect")
|
||||||
|
profile.results.explicit.endMem = collectgarbage("count")
|
||||||
|
|
||||||
|
-- Calculate average
|
||||||
|
local sum = 0
|
||||||
|
for _, ft in ipairs(profile.results.explicit.frameTimes) do
|
||||||
|
sum = sum + ft
|
||||||
|
end
|
||||||
|
profile.results.explicit.avgFrameTime = sum / #profile.results.explicit.frameTimes
|
||||||
|
|
||||||
|
print(" Complete. Avg frame time: " .. string.format("%.4f", profile.results.explicit.avgFrameTime) .. "ms\n")
|
||||||
|
|
||||||
|
profile.testPhase = "complete"
|
||||||
|
profile.generateReport()
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local time_after_explicit = os.clock()
|
function profile.draw()
|
||||||
collectgarbage("collect")
|
-- Draw the current menu
|
||||||
local mem_after_explicit = collectgarbage("count")
|
if profile.testPhase ~= "complete" then
|
||||||
|
FlexLove.draw()
|
||||||
|
end
|
||||||
|
|
||||||
local time_explicit = time_after_explicit - time_before_explicit
|
-- Draw status overlay
|
||||||
local mem_explicit = mem_after_explicit - mem_before_explicit
|
love.graphics.setColor(0, 0, 0, 0.85)
|
||||||
|
love.graphics.rectangle("fill", 10, 10, 400, 120)
|
||||||
|
|
||||||
print(string.format(" Time: %.4f seconds", time_explicit))
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
print(string.format(" Memory: %.2f KB", mem_explicit))
|
love.graphics.print("Settings Menu Mode Comparison", 20, 20)
|
||||||
print(string.format(" Avg time per menu: %.4f ms", (time_explicit / ITERATIONS_PER_TEST) * 1000))
|
|
||||||
print()
|
|
||||||
|
|
||||||
-- Calculate differences
|
if profile.testPhase == "warmup" then
|
||||||
print("=" .. string.rep("=", 78))
|
love.graphics.print("Phase: Warmup (" .. profile.frameCount .. "/30)", 20, 45)
|
||||||
print("RESULTS COMPARISON")
|
elseif profile.testPhase == "implicit" then
|
||||||
print("=" .. string.rep("=", 78))
|
love.graphics.print("Phase: Without mode flags", 20, 45)
|
||||||
print()
|
love.graphics.print("Progress: " .. profile.frameCount .. "/" .. profile.framesPerPhase, 20, 65)
|
||||||
|
elseif profile.testPhase == "explicit" then
|
||||||
|
love.graphics.print("Phase: With mode='retained' flags", 20, 45)
|
||||||
|
love.graphics.print("Progress: " .. profile.frameCount .. "/" .. profile.framesPerPhase, 20, 65)
|
||||||
|
elseif profile.testPhase == "complete" then
|
||||||
|
love.graphics.print("Phase: COMPLETE", 20, 45)
|
||||||
|
love.graphics.print("Report saved! Press S to save again, ESC to exit.", 20, 65)
|
||||||
|
end
|
||||||
|
|
||||||
local time_diff = time_explicit - time_implicit
|
love.graphics.print("Memory: " .. string.format("%.2f", collectgarbage("count") / 1024) .. " MB", 20, 85)
|
||||||
local time_percent = (time_diff / time_implicit) * 100
|
love.graphics.print("Press ESC to return to menu", 20, 105)
|
||||||
local mem_diff = mem_explicit - mem_implicit
|
|
||||||
|
|
||||||
print(string.format("Time Difference:"))
|
|
||||||
print(string.format(" Without mode flag: %.4f seconds", time_implicit))
|
|
||||||
print(string.format(" With mode flag: %.4f seconds", time_explicit))
|
|
||||||
print(string.format(" Difference: %.4f seconds (%+.2f%%)", time_diff, time_percent))
|
|
||||||
print()
|
|
||||||
|
|
||||||
print(string.format("Memory Difference:"))
|
|
||||||
print(string.format(" Without mode flag: %.2f KB", mem_implicit))
|
|
||||||
print(string.format(" With mode flag: %.2f KB", mem_explicit))
|
|
||||||
print(string.format(" Difference: %+.2f KB", mem_diff))
|
|
||||||
print()
|
|
||||||
|
|
||||||
-- Interpretation
|
|
||||||
print("INTERPRETATION:")
|
|
||||||
print()
|
|
||||||
if math.abs(time_percent) < 5 then
|
|
||||||
print(" ✓ Performance is essentially identical (< 5% difference)")
|
|
||||||
print(" The explicit mode flag has negligible impact on performance.")
|
|
||||||
elseif time_percent > 0 then
|
|
||||||
print(string.format(" ⚠ Explicit mode flag is %.2f%% SLOWER", time_percent))
|
|
||||||
print(" This indicates overhead from mode checking/resolution.")
|
|
||||||
else
|
|
||||||
print(string.format(" ✓ Explicit mode flag is %.2f%% FASTER", -time_percent))
|
|
||||||
print(" This indicates potential optimization benefits.")
|
|
||||||
end
|
end
|
||||||
print()
|
|
||||||
|
|
||||||
if math.abs(mem_diff) < 50 then
|
function profile.generateReport()
|
||||||
print(" ✓ Memory usage is essentially identical (< 50 KB difference)")
|
print("\n" .. string.rep("=", 80))
|
||||||
elseif mem_diff > 0 then
|
print("RESULTS COMPARISON")
|
||||||
print(string.format(" ⚠ Explicit mode flag uses %.2f KB MORE memory", mem_diff))
|
print(string.rep("=", 80) .. "\n")
|
||||||
else
|
|
||||||
print(string.format(" ✓ Explicit mode flag uses %.2f KB LESS memory", -mem_diff))
|
local timeDiff = profile.results.explicit.avgFrameTime - profile.results.implicit.avgFrameTime
|
||||||
|
local timePercent = (timeDiff / profile.results.implicit.avgFrameTime) * 100
|
||||||
|
local memDiff = (profile.results.explicit.endMem - profile.results.explicit.startMem) -
|
||||||
|
(profile.results.implicit.endMem - profile.results.implicit.startMem)
|
||||||
|
|
||||||
|
print("Time Comparison:")
|
||||||
|
print(string.format(" Without mode flag: %.4f ms", profile.results.implicit.avgFrameTime))
|
||||||
|
print(string.format(" With mode flag: %.4f ms", profile.results.explicit.avgFrameTime))
|
||||||
|
print(string.format(" Difference: %.4f ms (%+.2f%%)", timeDiff, timePercent))
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("Memory Comparison:")
|
||||||
|
print(string.format(" Without mode flag: %.2f KB", profile.results.implicit.endMem - profile.results.implicit.startMem))
|
||||||
|
print(string.format(" With mode flag: %.2f KB", profile.results.explicit.endMem - profile.results.explicit.startMem))
|
||||||
|
print(string.format(" Difference: %+.2f KB", memDiff))
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("INTERPRETATION:")
|
||||||
|
print()
|
||||||
|
if math.abs(timePercent) < 5 then
|
||||||
|
print(" ✓ Performance is essentially identical (< 5% difference)")
|
||||||
|
print(" The explicit mode flag has negligible impact on performance.")
|
||||||
|
elseif timePercent > 0 then
|
||||||
|
print(string.format(" ⚠ Explicit mode flag is %.2f%% SLOWER", timePercent))
|
||||||
|
print(" This indicates overhead from mode checking/resolution.")
|
||||||
|
else
|
||||||
|
print(string.format(" ✓ Explicit mode flag is %.2f%% FASTER", -timePercent))
|
||||||
|
print(" This indicates potential optimization benefits.")
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
|
||||||
|
if math.abs(memDiff) < 50 then
|
||||||
|
print(" ✓ Memory usage is essentially identical (< 50 KB difference)")
|
||||||
|
elseif memDiff > 0 then
|
||||||
|
print(string.format(" ⚠ Explicit mode flag uses %.2f KB MORE memory", memDiff))
|
||||||
|
else
|
||||||
|
print(string.format(" ✓ Explicit mode flag uses %.2f KB LESS memory", -memDiff))
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("RECOMMENDATION:")
|
||||||
|
print()
|
||||||
|
if math.abs(timePercent) < 5 and math.abs(memDiff) < 50 then
|
||||||
|
print(" The explicit mode='retained' flag provides clarity and explicitness")
|
||||||
|
print(" without any meaningful performance cost. It's recommended for:")
|
||||||
|
print(" - Code readability (makes intent explicit)")
|
||||||
|
print(" - Future-proofing (if global mode changes)")
|
||||||
|
print(" - Mixed-mode UIs (where some elements are immediate)")
|
||||||
|
else
|
||||||
|
print(" Consider the trade-offs based on your specific use case.")
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
print(string.rep("=", 80))
|
||||||
|
print("Profile complete! Press S to save report.")
|
||||||
|
print(string.rep("=", 80) .. "\n")
|
||||||
|
|
||||||
|
-- Save report automatically
|
||||||
|
profile.saveReportToFile()
|
||||||
end
|
end
|
||||||
print()
|
|
||||||
|
|
||||||
print("RECOMMENDATION:")
|
function profile.saveReportToFile()
|
||||||
print()
|
local timestamp = os.date("%Y-%m-%d_%H-%M-%S")
|
||||||
if math.abs(time_percent) < 5 and math.abs(mem_diff) < 50 then
|
local filename = string.format("reports/settings_menu_mode_profile/%s.md", timestamp)
|
||||||
print(" The explicit mode='retained' flag provides clarity and explicitness")
|
|
||||||
print(" without any meaningful performance cost. It's recommended for:")
|
-- Get the actual project directory
|
||||||
print(" - Code readability (makes intent explicit)")
|
local sourceDir = love.filesystem.getSource()
|
||||||
print(" - Future-proofing (if global mode changes)")
|
local filepath
|
||||||
print(" - Mixed-mode UIs (where some elements are immediate)")
|
|
||||||
else
|
if sourceDir:match("%.love$") then
|
||||||
print(" Consider the trade-offs based on your specific use case.")
|
-- Running from .love file, use save directory
|
||||||
|
love.filesystem.createDirectory("reports/settings_menu_mode_profile")
|
||||||
|
-- Use love.filesystem for sandboxed writes
|
||||||
|
local content = profile.formatReportMarkdown()
|
||||||
|
local success = love.filesystem.write(filename, content)
|
||||||
|
if success then
|
||||||
|
print("\n✓ Report saved to: " .. love.filesystem.getSaveDirectory() .. "/" .. filename)
|
||||||
|
else
|
||||||
|
print("\n✗ Failed to save report")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Running from source, use io module
|
||||||
|
filepath = sourceDir .. "/" .. filename
|
||||||
|
os.execute('mkdir -p "' .. sourceDir .. '/reports/settings_menu_mode_profile"')
|
||||||
|
|
||||||
|
local file, err = io.open(filepath, "w")
|
||||||
|
if file then
|
||||||
|
file:write(profile.formatReportMarkdown())
|
||||||
|
file:close()
|
||||||
|
print("\n✓ Report saved to: " .. filepath)
|
||||||
|
|
||||||
|
-- Also save as latest.md
|
||||||
|
local latestPath = sourceDir .. "/reports/settings_menu_mode_profile/latest.md"
|
||||||
|
local latestFile = io.open(latestPath, "w")
|
||||||
|
if latestFile then
|
||||||
|
latestFile:write(profile.formatReportMarkdown())
|
||||||
|
latestFile:close()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print("\n✗ Failed to save report: " .. tostring(err))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
print()
|
|
||||||
|
|
||||||
print("=" .. string.rep("=", 78))
|
function profile.formatReportMarkdown()
|
||||||
print("Profile complete!")
|
local lines = {}
|
||||||
print("=" .. string.rep("=", 78))
|
|
||||||
|
table.insert(lines, "# Settings Menu Mode Comparison Report")
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "**Generated:** " .. os.date("%Y-%m-%d %H:%M:%S"))
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "This profile compares performance when creating a complex settings menu")
|
||||||
|
table.insert(lines, "with explicit `mode='retained'` flags vs. implicit retained mode (global setting).")
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "---")
|
||||||
|
table.insert(lines, "")
|
||||||
|
|
||||||
|
table.insert(lines, "## Test Configuration")
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "- **Frames per test:** " .. profile.framesPerPhase)
|
||||||
|
table.insert(lines, "- **Elements per menu:** ~45 (backdrop, window, buttons, sliders, etc.)")
|
||||||
|
table.insert(lines, "- **Global mode:** `immediateMode = false` (retained)")
|
||||||
|
table.insert(lines, "")
|
||||||
|
|
||||||
|
table.insert(lines, "## Results")
|
||||||
|
table.insert(lines, "")
|
||||||
|
|
||||||
|
local timeDiff = profile.results.explicit.avgFrameTime - profile.results.implicit.avgFrameTime
|
||||||
|
local timePercent = (timeDiff / profile.results.implicit.avgFrameTime) * 100
|
||||||
|
local memDiff = (profile.results.explicit.endMem - profile.results.explicit.startMem) -
|
||||||
|
(profile.results.implicit.endMem - profile.results.implicit.startMem)
|
||||||
|
|
||||||
|
table.insert(lines, "### Frame Time Comparison")
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "| Metric | Without `mode` flag | With `mode='retained'` flag | Difference |")
|
||||||
|
table.insert(lines, "|--------|--------------------:|----------------------------:|-----------:|")
|
||||||
|
table.insert(lines, string.format("| Average Frame Time | %.4f ms | %.4f ms | %+.4f ms (%+.2f%%) |",
|
||||||
|
profile.results.implicit.avgFrameTime,
|
||||||
|
profile.results.explicit.avgFrameTime,
|
||||||
|
timeDiff,
|
||||||
|
timePercent))
|
||||||
|
table.insert(lines, "")
|
||||||
|
|
||||||
|
table.insert(lines, "### Memory Comparison")
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "| Metric | Without `mode` flag | With `mode='retained'` flag | Difference |")
|
||||||
|
table.insert(lines, "|--------|--------------------:|----------------------------:|-----------:|")
|
||||||
|
table.insert(lines, string.format("| Memory Used | %.2f KB | %.2f KB | %+.2f KB |",
|
||||||
|
profile.results.implicit.endMem - profile.results.implicit.startMem,
|
||||||
|
profile.results.explicit.endMem - profile.results.explicit.startMem,
|
||||||
|
memDiff))
|
||||||
|
table.insert(lines, "")
|
||||||
|
|
||||||
|
table.insert(lines, "## Interpretation")
|
||||||
|
table.insert(lines, "")
|
||||||
|
|
||||||
|
if math.abs(timePercent) < 5 then
|
||||||
|
table.insert(lines, "✓ **Performance is essentially identical** (< 5% difference)")
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "The explicit `mode='retained'` flag has negligible impact on performance.")
|
||||||
|
elseif timePercent > 0 then
|
||||||
|
table.insert(lines, string.format("⚠ **Explicit mode flag is %.2f%% SLOWER**", timePercent))
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "This indicates overhead from mode checking/resolution.")
|
||||||
|
else
|
||||||
|
table.insert(lines, string.format("✓ **Explicit mode flag is %.2f%% FASTER**", -timePercent))
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "This indicates potential optimization benefits.")
|
||||||
|
end
|
||||||
|
table.insert(lines, "")
|
||||||
|
|
||||||
|
if math.abs(memDiff) < 50 then
|
||||||
|
table.insert(lines, "✓ **Memory usage is essentially identical** (< 50 KB difference)")
|
||||||
|
elseif memDiff > 0 then
|
||||||
|
table.insert(lines, string.format("⚠ **Explicit mode flag uses %.2f KB MORE memory**", memDiff))
|
||||||
|
else
|
||||||
|
table.insert(lines, string.format("✓ **Explicit mode flag uses %.2f KB LESS memory**", -memDiff))
|
||||||
|
end
|
||||||
|
table.insert(lines, "")
|
||||||
|
|
||||||
|
table.insert(lines, "## Recommendation")
|
||||||
|
table.insert(lines, "")
|
||||||
|
|
||||||
|
if math.abs(timePercent) < 5 and math.abs(memDiff) < 50 then
|
||||||
|
table.insert(lines, "The explicit `mode='retained'` flag provides clarity and explicitness")
|
||||||
|
table.insert(lines, "without any meaningful performance cost. It's recommended for:")
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "- **Code readability** - Makes intent explicit")
|
||||||
|
table.insert(lines, "- **Future-proofing** - If global mode changes")
|
||||||
|
table.insert(lines, "- **Mixed-mode UIs** - Where some elements are immediate")
|
||||||
|
else
|
||||||
|
table.insert(lines, "Consider the trade-offs based on your specific use case.")
|
||||||
|
end
|
||||||
|
table.insert(lines, "")
|
||||||
|
|
||||||
|
table.insert(lines, "---")
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "*Report generated by FlexLöve Performance Profiler*")
|
||||||
|
|
||||||
|
return table.concat(lines, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
function profile.keypressed(key, profiler)
|
||||||
|
if key == "s" and profile.testPhase == "complete" then
|
||||||
|
profile.saveReportToFile()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function profile.resize(w, h)
|
||||||
|
FlexLove.resize(w, h)
|
||||||
|
end
|
||||||
|
|
||||||
|
function profile.reset()
|
||||||
|
profile.testPhase = "warmup"
|
||||||
|
profile.frameCount = 0
|
||||||
|
profile.results = {
|
||||||
|
implicit = { startMem = 0, endMem = 0, avgFrameTime = 0, frameTimes = {} },
|
||||||
|
explicit = { startMem = 0, endMem = 0, avgFrameTime = 0, frameTimes = {} },
|
||||||
|
}
|
||||||
|
print("\nProfile reset. Starting over...\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
function profile.cleanup()
|
||||||
|
print("\nCleaning up settings menu mode profile...\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
return profile
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
-- FlexLöve Profiler - Main Entry Point
|
-- FlexLöve Profiler - Main Entry Point
|
||||||
-- Load FlexLöve from parent directory
|
-- Load FlexLöve from parent directory
|
||||||
package.path = package.path .. ";../?.lua;../?/init.lua"
|
|
||||||
|
|
||||||
local FlexLove = require("libs.FlexLove")
|
-- Override require to handle FlexLove.modules.X properly
|
||||||
|
-- When FlexLove.lua requires "FlexLove.modules.ErrorHandler",
|
||||||
|
-- we redirect it to "../modules/ErrorHandler"
|
||||||
|
local originalRequire = require
|
||||||
|
local function customRequire(modname)
|
||||||
|
-- Check if this is a FlexLove.modules.X require
|
||||||
|
local moduleName = modname:match("^FlexLove%.modules%.(.+)$")
|
||||||
|
if moduleName then
|
||||||
|
-- Redirect to ../modules/X
|
||||||
|
return originalRequire("modules." .. moduleName)
|
||||||
|
end
|
||||||
|
-- Otherwise use original require
|
||||||
|
return originalRequire(modname)
|
||||||
|
end
|
||||||
|
_G.require = customRequire
|
||||||
|
|
||||||
|
-- Set up package.path for normal requires
|
||||||
|
package.path = package.path .. ";../?.lua;../?/init.lua;../modules/?.lua"
|
||||||
|
|
||||||
|
local FlexLove = originalRequire("FlexLove")
|
||||||
local PerformanceProfiler = require("profiling.utils.PerformanceProfiler")
|
local PerformanceProfiler = require("profiling.utils.PerformanceProfiler")
|
||||||
local lv = love
|
local lv = love
|
||||||
|
|
||||||
@@ -118,17 +136,9 @@ local function buildMenu()
|
|||||||
padding = { horizontal = 40, vertical = 40 },
|
padding = { horizontal = 40, vertical = 40 },
|
||||||
})
|
})
|
||||||
|
|
||||||
local container = FlexLove.new({
|
|
||||||
parent = root,
|
|
||||||
positioning = "flex",
|
|
||||||
flexDirection = "vertical",
|
|
||||||
alignItems = "center",
|
|
||||||
gap = 30,
|
|
||||||
})
|
|
||||||
|
|
||||||
-- Title
|
-- Title
|
||||||
FlexLove.new({
|
FlexLove.new({
|
||||||
parent = container,
|
parent = root,
|
||||||
width = 600,
|
width = 600,
|
||||||
height = 80,
|
height = 80,
|
||||||
backgroundColor = FlexLove.Color.new(0.15, 0.15, 0.25, 1),
|
backgroundColor = FlexLove.Color.new(0.15, 0.15, 0.25, 1),
|
||||||
@@ -141,6 +151,16 @@ local function buildMenu()
|
|||||||
textColor = FlexLove.Color.new(0.3, 0.8, 1, 1),
|
textColor = FlexLove.Color.new(0.3, 0.8, 1, 1),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
local container = FlexLove.new({
|
||||||
|
parent = root,
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "vertical",
|
||||||
|
alignItems = "center",
|
||||||
|
height = "100%",
|
||||||
|
width = "100%",
|
||||||
|
gap = 30,
|
||||||
|
})
|
||||||
|
|
||||||
-- Subtitle
|
-- Subtitle
|
||||||
FlexLove.new({
|
FlexLove.new({
|
||||||
parent = container,
|
parent = container,
|
||||||
@@ -152,32 +172,43 @@ local function buildMenu()
|
|||||||
-- Profile list
|
-- Profile list
|
||||||
local profileList = FlexLove.new({
|
local profileList = FlexLove.new({
|
||||||
parent = container,
|
parent = container,
|
||||||
width = 600,
|
width = "80%",
|
||||||
|
height = "80%",
|
||||||
positioning = "flex",
|
positioning = "flex",
|
||||||
flexDirection = "vertical",
|
flexDirection = "vertical",
|
||||||
gap = 10,
|
gap = 10,
|
||||||
|
padding = { vertical = 20, horizontal = 20 },
|
||||||
|
overflowY = "scroll",
|
||||||
|
--mode = "retained",
|
||||||
})
|
})
|
||||||
|
|
||||||
for i, profile in ipairs(state.profiles) do
|
for i, profile in ipairs(state.profiles) do
|
||||||
local isSelected = i == state.selectedIndex
|
local isSelected = i == state.selectedIndex
|
||||||
|
local isHovered = i == state.hoveredIndex
|
||||||
local button = FlexLove.new({
|
local button = FlexLove.new({
|
||||||
parent = profileList,
|
parent = profileList,
|
||||||
width = "100%",
|
width = "50%",
|
||||||
height = 50,
|
height = 50,
|
||||||
backgroundColor = isSelected and FlexLove.Color.new(0.2, 0.4, 0.8, 1) or FlexLove.Color.new(0.15, 0.15, 0.25, 1),
|
backgroundColor = isSelected and FlexLove.Color.new(0.2, 0.4, 0.8, 1)
|
||||||
|
or isHovered and FlexLove.Color.new(0.2, 0.2, 0.35, 1)
|
||||||
|
or FlexLove.Color.new(0.15, 0.15, 0.25, 1),
|
||||||
borderRadius = 8,
|
borderRadius = 8,
|
||||||
positioning = "flex",
|
positioning = "flex",
|
||||||
justifyContent = "flex-start",
|
justifyContent = "center",
|
||||||
alignItems = "center",
|
alignItems = "center",
|
||||||
|
alignSelf = "center",
|
||||||
|
--mode = "retained",
|
||||||
padding = { horizontal = 15, vertical = 15 },
|
padding = { horizontal = 15, vertical = 15 },
|
||||||
onEvent = function(element, event)
|
onEvent = function(_, event)
|
||||||
if event.type == "release" then
|
if event.type == "release" then
|
||||||
state.selectedIndex = i
|
state.selectedIndex = i
|
||||||
loadProfile(profile)
|
loadProfile(profile)
|
||||||
elseif event.type == "hover" and not isSelected then
|
elseif event.type == "hover" and not isSelected then
|
||||||
element.backgroundColor = FlexLove.Color.new(0.2, 0.2, 0.35, 1)
|
state.hoveredIndex = i
|
||||||
|
--element.backgroundColor = FlexLove.Color.new(0.2, 0.2, 0.35, 1)
|
||||||
elseif event.type == "unhover" and not isSelected then
|
elseif event.type == "unhover" and not isSelected then
|
||||||
element.backgroundColor = FlexLove.Color.new(0.15, 0.15, 0.25, 1)
|
state.hoveredIndex = 0
|
||||||
|
--element.backgroundColor = FlexLove.Color.new(0.15, 0.15, 0.25, 1)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
@@ -223,8 +254,6 @@ end
|
|||||||
|
|
||||||
function lv.load(args)
|
function lv.load(args)
|
||||||
FlexLove.init({
|
FlexLove.init({
|
||||||
width = lv.graphics.getWidth(),
|
|
||||||
height = lv.graphics.getHeight(),
|
|
||||||
immediateMode = true,
|
immediateMode = true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -266,6 +295,10 @@ function lv.update(dt)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function lv.wheelmoved(x, y)
|
||||||
|
FlexLove.wheelmoved(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
function lv.draw()
|
function lv.draw()
|
||||||
if state.mode == "menu" then
|
if state.mode == "menu" then
|
||||||
buildMenu()
|
buildMenu()
|
||||||
|
|||||||
@@ -97,11 +97,19 @@ function TestTouchEvents:testEventHandler_TouchBegan()
|
|||||||
element._eventHandler:processTouchEvents(element)
|
element._eventHandler:processTouchEvents(element)
|
||||||
FlexLove.endFrame()
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Filter out hover/unhover events (from mouse processing)
|
||||||
|
local filteredEvents = {}
|
||||||
|
for _, event in ipairs(touchEvents) do
|
||||||
|
if event.type ~= "hover" and event.type ~= "unhover" then
|
||||||
|
table.insert(filteredEvents, event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Should have received at least one touchpress event
|
-- Should have received at least one touchpress event
|
||||||
-- Note: May receive multiple events due to test state/frame processing
|
-- Note: May receive multiple events due to test state/frame processing
|
||||||
lu.assertTrue(#touchEvents >= 1, "Should receive at least 1 touch event, got " .. #touchEvents)
|
lu.assertTrue(#filteredEvents >= 1, "Should receive at least 1 touch event, got " .. #filteredEvents)
|
||||||
lu.assertEquals(touchEvents[1].type, "touchpress")
|
lu.assertEquals(filteredEvents[1].type, "touchpress")
|
||||||
lu.assertEquals(touchEvents[1].touchId, "touch1")
|
lu.assertEquals(filteredEvents[1].touchId, "touch1")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test: EventHandler tracks touch moved
|
-- Test: EventHandler tracks touch moved
|
||||||
@@ -147,12 +155,20 @@ function TestTouchEvents:testEventHandler_TouchMoved()
|
|||||||
element._eventHandler:processTouchEvents(element)
|
element._eventHandler:processTouchEvents(element)
|
||||||
FlexLove.endFrame()
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Filter out hover/unhover events (from mouse processing)
|
||||||
|
local filteredEvents = {}
|
||||||
|
for _, event in ipairs(touchEvents) do
|
||||||
|
if event.type ~= "hover" and event.type ~= "unhover" then
|
||||||
|
table.insert(filteredEvents, event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Should have received touchpress and touchmove events
|
-- Should have received touchpress and touchmove events
|
||||||
lu.assertEquals(#touchEvents, 2)
|
lu.assertEquals(#filteredEvents, 2)
|
||||||
lu.assertEquals(touchEvents[1].type, "touchpress")
|
lu.assertEquals(filteredEvents[1].type, "touchpress")
|
||||||
lu.assertEquals(touchEvents[2].type, "touchmove")
|
lu.assertEquals(filteredEvents[2].type, "touchmove")
|
||||||
lu.assertEquals(touchEvents[2].dx, 50)
|
lu.assertEquals(filteredEvents[2].dx, 50)
|
||||||
lu.assertEquals(touchEvents[2].dy, 50)
|
lu.assertEquals(filteredEvents[2].dy, 50)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test: EventHandler tracks touch ended
|
-- Test: EventHandler tracks touch ended
|
||||||
@@ -195,10 +211,18 @@ function TestTouchEvents:testEventHandler_TouchEnded()
|
|||||||
element._eventHandler:processTouchEvents(element)
|
element._eventHandler:processTouchEvents(element)
|
||||||
FlexLove.endFrame()
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Filter out hover/unhover events (from mouse processing)
|
||||||
|
local filteredEvents = {}
|
||||||
|
for _, event in ipairs(touchEvents) do
|
||||||
|
if event.type ~= "hover" and event.type ~= "unhover" then
|
||||||
|
table.insert(filteredEvents, event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Should have received touchpress and touchrelease events
|
-- Should have received touchpress and touchrelease events
|
||||||
lu.assertEquals(#touchEvents, 2)
|
lu.assertEquals(#filteredEvents, 2)
|
||||||
lu.assertEquals(touchEvents[1].type, "touchpress")
|
lu.assertEquals(filteredEvents[1].type, "touchpress")
|
||||||
lu.assertEquals(touchEvents[2].type, "touchrelease")
|
lu.assertEquals(filteredEvents[2].type, "touchrelease")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test: EventHandler tracks multiple simultaneous touches
|
-- Test: EventHandler tracks multiple simultaneous touches
|
||||||
@@ -234,10 +258,18 @@ function TestTouchEvents:testEventHandler_MultiTouch()
|
|||||||
element._eventHandler:processTouchEvents(element)
|
element._eventHandler:processTouchEvents(element)
|
||||||
FlexLove.endFrame()
|
FlexLove.endFrame()
|
||||||
|
|
||||||
-- Should have received two touchpress events
|
-- Filter out hover/unhover events (from mouse processing)
|
||||||
lu.assertEquals(#touchEvents, 2)
|
local filteredEvents = {}
|
||||||
lu.assertEquals(touchEvents[1].type, "touchpress")
|
for _, event in ipairs(touchEvents) do
|
||||||
lu.assertEquals(touchEvents[2].type, "touchpress")
|
if event.type ~= "hover" and event.type ~= "unhover" then
|
||||||
|
table.insert(filteredEvents, event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Should have received two touchpress events (one for each touch)
|
||||||
|
lu.assertEquals(#filteredEvents, 2)
|
||||||
|
lu.assertEquals(filteredEvents[1].type, "touchpress")
|
||||||
|
lu.assertEquals(filteredEvents[2].type, "touchpress")
|
||||||
|
|
||||||
-- Different touch IDs
|
-- Different touch IDs
|
||||||
lu.assertNotEquals(touchEvents[1].touchId, touchEvents[2].touchId)
|
lu.assertNotEquals(touchEvents[1].touchId, touchEvents[2].touchId)
|
||||||
|
|||||||
Reference in New Issue
Block a user