Debugging y Profiling Profesional
Debugging y Profiling Profesional
El debugging y profiling son habilidades esenciales para crear juegos de calidad profesional. En esta lección aprenderás técnicas avanzadas para identificar, diagnosticar y resolver problemas de rendimiento y bugs complejos.
🔍 Sistema de Debugging Avanzado
Logger Personalizado
-- debug_logger.script
local Logger = {}
-- Niveles de log
local LOG_LEVELS = {
TRACE = 1,
DEBUG = 2,
INFO = 3,
WARN = 4,
ERROR = 5,
FATAL = 6
}
local LOG_COLORS = {
TRACE = "#808080",
DEBUG = "#00FF00",
INFO = "#0080FF",
WARN = "#FFFF00",
ERROR = "#FF8000",
FATAL = "#FF0000"
}
function init(self)
self.log_level = LOG_LEVELS.DEBUG
self.log_file = nil
self.log_buffer = {}
self.max_buffer_size = 1000
self.session_id = os.time()
-- Configurar archivo de log
if sys.get_sys_info().system_name ~= "HTML5" then
local log_path = sys.get_save_file("defold_game", "debug_" .. self.session_id .. ".log")
self.log_file = io.open(log_path, "w")
end
self:info("Logger initialized - Session: " .. self.session_id)
end
local function format_message(level, category, message, context)
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
local formatted = string.format("[%s] [%s] [%s] %s",
timestamp, level, category or "GENERAL", message)
if context then
formatted = formatted .. " | Context: " .. pprint.pformat(context)
end
return formatted
end
local function add_to_buffer(self, level, category, message, context)
local log_entry = {
timestamp = os.time(),
level = level,
category = category,
message = message,
context = context,
stack_trace = debug.traceback()
}
table.insert(self.log_buffer, log_entry)
-- Mantener buffer limitado
if #self.log_buffer > self.max_buffer_size then
table.remove(self.log_buffer, 1)
end
end
local function write_log(self, level, category, message, context)
if LOG_LEVELS[level] < self.log_level then
return
end
local formatted = format_message(level, category, message, context)
-- Escribir a consola con color
local color = LOG_COLORS[level] or "#FFFFFF"
print(formatted)
-- Escribir a archivo si está disponible
if self.log_file then
self.log_file:write(formatted .. "\n")
self.log_file:flush()
end
-- Añadir a buffer
add_to_buffer(self, level, category, message, context)
-- Enviar a sistema de analytics si es error
if level == "ERROR" or level == "FATAL" then
self:send_error_analytics(level, category, message, context)
end
end
function Logger.trace(self, category, message, context)
write_log(self, "TRACE", category, message, context)
end
function Logger.debug(self, category, message, context)
write_log(self, "DEBUG", category, message, context)
end
function Logger.info(self, category, message, context)
write_log(self, "INFO", category, message, context)
end
function Logger.warn(self, category, message, context)
write_log(self, "WARN", category, message, context)
end
function Logger.error(self, category, message, context)
write_log(self, "ERROR", category, message, context)
end
function Logger.fatal(self, category, message, context)
write_log(self, "FATAL", category, message, context)
end
-- Función para capturar stack trace detallado
local function capture_stack_trace(self, skip_levels)
skip_levels = skip_levels or 2
local stack = {}
for level = skip_levels, 10 do
local info = debug.getinfo(level, "Sln")
if not info then break end
table.insert(stack, {
source = info.source,
line = info.currentline,
name = info.name or "unknown",
func_type = info.namewhat
})
end
return stack
end
-- Sistema de breakpoints condicionales
local function create_conditional_breakpoint(self, condition_func, action_func)
return function(...)
if condition_func(...) then
local context = {
args = {...},
stack = capture_stack_trace(self),
timestamp = os.time()
}
self:debug("BREAKPOINT", "Conditional breakpoint triggered", context)
if action_func then
action_func(context)
end
end
end
end
return Logger
Crash Reporter
-- crash_reporter.script
local CrashReporter = {}
function init(self)
self.crash_data = {}
self.auto_report = true
self.report_endpoint = "https://api.example.com/crashes"
-- Configurar manejo de errores globales
self:setup_error_handler()
end
local function setup_error_handler(self)
-- Capturar errores Lua
local original_error = _G.error
_G.error = function(message, level)
level = level or 1
local crash_info = {
type = "lua_error",
message = tostring(message),
stack_trace = debug.traceback("", level + 1),
timestamp = os.time(),
game_state = self:capture_game_state(),
device_info = self:capture_device_info(),
memory_info = self:capture_memory_info()
}
self:record_crash(crash_info)
original_error(message, level + 1)
end
-- Capturar assertions fallidas
local original_assert = _G.assert
_G.assert = function(condition, message)
if not condition then
local crash_info = {
type = "assertion_failed",
message = message or "Assertion failed",
stack_trace = debug.traceback(),
timestamp = os.time(),
game_state = self:capture_game_state()
}
self:record_crash(crash_info)
end
return original_assert(condition, message)
end
end
local function capture_game_state(self)
return {
scene = msg.url().path,
fps = sys.get_engine_info().frame_count,
uptime = socket.gettime(),
input_state = self:get_current_input_state(),
audio_state = self:get_audio_state(),
network_state = self:get_network_state()
}
end
local function capture_device_info(self)
local sys_info = sys.get_sys_info()
return {
platform = sys_info.system_name,
device_model = sys_info.device_model,
language = sys_info.language,
territory = sys_info.territory,
gmt_offset = sys_info.gmt_offset,
device_ident = sys_info.device_ident,
user_agent = sys_info.user_agent
}
end
local function capture_memory_info(self)
return {
lua_memory = collectgarbage("count"),
texture_memory = profiler.get_memory_usage().texture_memory,
total_memory = sys.get_engine_info().heap_size
}
end
local function record_crash(self, crash_info)
-- Guardar crash localmente
local crash_id = "crash_" .. os.time() .. "_" .. math.random(1000, 9999)
crash_info.crash_id = crash_id
table.insert(self.crash_data, crash_info)
-- Guardar en archivo
local crash_file = sys.get_save_file("defold_game", crash_id .. ".json")
local file = io.open(crash_file, "w")
if file then
file:write(json.encode(crash_info))
file:close()
end
-- Enviar automáticamente si está habilitado
if self.auto_report then
self:send_crash_report(crash_info)
end
print("CRASH RECORDED:", crash_id)
end
local function send_crash_report(self, crash_info)
local data = json.encode(crash_info)
http.request(self.report_endpoint, "POST", function(self, id, response)
if response.status == 200 then
print("Crash report sent successfully:", crash_info.crash_id)
else
print("Failed to send crash report:", response.status)
end
end, nil, nil, {["Content-Type"] = "application/json"}, data)
end
return CrashReporter
📊 Sistema de Profiling Detallado
Performance Profiler
-- performance_profiler.script
local Profiler = {}
local PROFILER_CATEGORIES = {
RENDER = "render",
PHYSICS = "physics",
SCRIPTS = "scripts",
AUDIO = "audio",
NETWORK = "network",
INPUT = "input"
}
function init(self)
self.enabled = true
self.frame_data = {}
self.category_timers = {}
self.memory_samples = {}
self.fps_history = {}
self.profiling_overhead = 0
-- Configurar sampling
self.sample_interval = 1/60 -- 60 FPS
self.max_samples = 3600 -- 1 minuto de datos
for category, _ in pairs(PROFILER_CATEGORIES) do
self.category_timers[category] = {
start_time = 0,
total_time = 0,
call_count = 0,
min_time = math.huge,
max_time = 0
}
end
end
-- Función para medir tiempo de ejecución
local function start_timer(self, category)
if not self.enabled then return end
local timer = self.category_timers[category]
if timer then
timer.start_time = socket.gettime()
end
end
local function end_timer(self, category)
if not self.enabled then return end
local current_time = socket.gettime()
local timer = self.category_timers[category]
if timer and timer.start_time > 0 then
local elapsed = current_time - timer.start_time
timer.total_time = timer.total_time + elapsed
timer.call_count = timer.call_count + 1
timer.min_time = math.min(timer.min_time, elapsed)
timer.max_time = math.max(timer.max_time, elapsed)
timer.start_time = 0
end
end
-- Decorator para funciones
local function profile_function(self, category, func)
return function(...)
start_timer(self, category)
local results = {func(...)}
end_timer(self, category)
return unpack(results)
end
end
-- Muestreo de memoria
local function sample_memory(self)
local memory_info = {
timestamp = socket.gettime(),
lua_memory = collectgarbage("count") * 1024,
texture_memory = render.get_render_stats().texture_memory or 0,
total_objects = game_object_count() or 0
}
table.insert(self.memory_samples, memory_info)
if #self.memory_samples > self.max_samples then
table.remove(self.memory_samples, 1)
end
end
-- Análisis de hotspots
local function analyze_hotspots(self)
local hotspots = {}
for category, timer in pairs(self.category_timers) do
if timer.call_count > 0 then
hotspots[category] = {
total_time = timer.total_time,
avg_time = timer.total_time / timer.call_count,
min_time = timer.min_time,
max_time = timer.max_time,
call_count = timer.call_count,
percentage = 0 -- Se calculará después
}
end
end
-- Calcular porcentajes
local total_time = 0
for _, data in pairs(hotspots) do
total_time = total_time + data.total_time
end
for category, data in pairs(hotspots) do
data.percentage = (data.total_time / total_time) * 100
end
return hotspots
end
-- Detección automática de problemas
local function detect_performance_issues(self)
local issues = {}
local hotspots = analyze_hotspots(self)
-- Frame rate bajo
local avg_fps = self:calculate_average_fps()
if avg_fps < 50 then
table.insert(issues, {
type = "LOW_FPS",
severity = "HIGH",
description = string.format("Average FPS: %.1f", avg_fps)
})
end
-- Hotspots problemáticos
for category, data in pairs(hotspots) do
if data.percentage > 30 then
table.insert(issues, {
type = "HOTSPOT",
severity = "MEDIUM",
category = category,
description = string.format("%s consuming %.1f%% of frame time",
category, data.percentage)
})
end
end
-- Memoria alta
local current_memory = collectgarbage("count") * 1024
if current_memory > 100 * 1024 * 1024 then -- 100MB
table.insert(issues, {
type = "HIGH_MEMORY",
severity = "MEDIUM",
description = string.format("Memory usage: %.1fMB", current_memory / (1024*1024))
})
end
return issues
end
function update(self, dt)
if not self.enabled then return end
-- Muestrear FPS
local fps = 1.0 / dt
table.insert(self.fps_history, fps)
if #self.fps_history > self.max_samples then
table.remove(self.fps_history, 1)
end
-- Muestrear memoria cada segundo
if #self.memory_samples == 0 or
socket.gettime() - self.memory_samples[#self.memory_samples].timestamp >= 1.0 then
sample_memory(self)
end
end
-- Generar reporte
local function generate_report(self)
local hotspots = analyze_hotspots(self)
local issues = detect_performance_issues(self)
local report = {
timestamp = os.date("%Y-%m-%d %H:%M:%S"),
session_duration = socket.gettime(),
average_fps = self:calculate_average_fps(),
memory_peak = self:calculate_peak_memory(),
hotspots = hotspots,
issues = issues,
frame_samples = #self.fps_history,
memory_samples = #self.memory_samples
}
return report
end
Profiler.start_timer = start_timer
Profiler.end_timer = end_timer
Profiler.profile_function = profile_function
Profiler.generate_report = generate_report
return Profiler
GPU Profiler
-- gpu_profiler.script
local GPUProfiler = {}
function init(self)
self.render_stats = {}
self.draw_call_history = {}
self.texture_usage = {}
self.shader_stats = {}
-- Configurar hooks de render
self:setup_render_hooks()
end
local function setup_render_hooks(self)
-- Hook para interceptar draw calls
local original_render_draw = render.draw
render.draw = function(predicate, options)
local start_time = socket.gettime()
-- Obtener estadísticas antes del draw
local stats_before = render.get_render_stats()
-- Ejecutar draw call original
local result = original_render_draw(predicate, options)
-- Obtener estadísticas después del draw
local stats_after = render.get_render_stats()
local end_time = socket.gettime()
-- Registrar estadísticas
self:record_draw_call({
predicate = tostring(predicate),
duration = end_time - start_time,
triangles_before = stats_before.triangles or 0,
triangles_after = stats_after.triangles or 0,
draw_calls_before = stats_before.draw_calls or 0,
draw_calls_after = stats_after.draw_calls or 0
})
return result
end
end
local function record_draw_call(self, call_data)
call_data.timestamp = socket.gettime()
call_data.triangles_drawn = call_data.triangles_after - call_data.triangles_before
call_data.new_draw_calls = call_data.draw_calls_after - call_data.draw_calls_before
table.insert(self.draw_call_history, call_data)
-- Mantener historial limitado
if #self.draw_call_history > 1000 then
table.remove(self.draw_call_history, 1)
end
end
local function analyze_render_performance(self)
local total_draw_calls = 0
local total_triangles = 0
local total_time = 0
local predicate_stats = {}
for _, call in ipairs(self.draw_call_history) do
total_draw_calls = total_draw_calls + call.new_draw_calls
total_triangles = total_triangles + call.triangles_drawn
total_time = total_time + call.duration
-- Estadísticas por predicado
if not predicate_stats[call.predicate] then
predicate_stats[call.predicate] = {
draw_calls = 0,
triangles = 0,
time = 0
}
end
local pred_stat = predicate_stats[call.predicate]
pred_stat.draw_calls = pred_stat.draw_calls + call.new_draw_calls
pred_stat.triangles = pred_stat.triangles + call.triangles_drawn
pred_stat.time = pred_stat.time + call.duration
end
return {
total_draw_calls = total_draw_calls,
total_triangles = total_triangles,
total_render_time = total_time,
average_triangles_per_call = total_triangles / math.max(total_draw_calls, 1),
predicate_breakdown = predicate_stats
}
end
-- Análisis de uso de texturas
local function analyze_texture_usage(self)
local render_stats = render.get_render_stats()
local texture_analysis = {
current_memory = render_stats.texture_memory or 0,
estimated_textures = render_stats.texture_count or 0,
average_texture_size = 0
}
if texture_analysis.estimated_textures > 0 then
texture_analysis.average_texture_size =
texture_analysis.current_memory / texture_analysis.estimated_textures
end
return texture_analysis
end
-- Recomendaciones de optimización
local function generate_optimization_suggestions(self)
local render_analysis = analyze_render_performance(self)
local suggestions = {}
-- Demasiados draw calls
if render_analysis.total_draw_calls > 500 then
table.insert(suggestions, {
type = "BATCHING",
priority = "HIGH",
description = string.format("Consider batching: %d draw calls detected",
render_analysis.total_draw_calls)
})
end
-- Pocos triángulos por draw call
if render_analysis.average_triangles_per_call < 100 then
table.insert(suggestions, {
type = "MESH_OPTIMIZATION",
priority = "MEDIUM",
description = string.format("Low triangle density: %.1f triangles per draw call",
render_analysis.average_triangles_per_call)
})
end
-- Análisis por predicado
for predicate, stats in pairs(render_analysis.predicate_breakdown) do
if stats.time > 0.005 then -- Más de 5ms
table.insert(suggestions, {
type = "SHADER_OPTIMIZATION",
priority = "MEDIUM",
predicate = predicate,
description = string.format("Predicate '%s' consuming %.2fms per frame",
predicate, stats.time * 1000)
})
end
end
return suggestions
end
return GPUProfiler
🧠 Memory Profiling
Garbage Collection Analyzer
-- gc_analyzer.script
local GCAnalyzer = {}
function init(self)
self.gc_events = {}
self.memory_snapshots = {}
self.allocation_tracking = {}
-- Configurar hooks de GC
self:setup_gc_hooks()
-- Configurar tracking de allocaciones
self:setup_allocation_tracking()
end
local function setup_gc_hooks(self)
-- Hook para collectgarbage
local original_collectgarbage = _G.collectgarbage
_G.collectgarbage = function(opt, arg)
local start_time = socket.gettime()
local memory_before = original_collectgarbage("count")
local result = original_collectgarbage(opt, arg)
local end_time = socket.gettime()
local memory_after = original_collectgarbage("count")
if opt == "collect" or opt == nil then
self:record_gc_event({
type = "manual_collect",
duration = end_time - start_time,
memory_before = memory_before,
memory_after = memory_after,
memory_freed = memory_before - memory_after
})
end
return result
end
-- Monitoring automático de GC
timer.delay(1.0, true, function()
self:check_automatic_gc()
end)
end
local function setup_allocation_tracking(self)
-- Tracking de creación de tables
local original_setmetatable = _G.setmetatable
_G.setmetatable = function(table, metatable)
if self.allocation_tracking.enabled then
local info = debug.getinfo(2, "Sl")
local location = string.format("%s:%d", info.source or "unknown", info.currentline or 0)
if not self.allocation_tracking[location] then
self.allocation_tracking[location] = 0
end
self.allocation_tracking[location] = self.allocation_tracking[location] + 1
end
return original_setmetatable(table, metatable)
end
end
local function record_gc_event(self, event_data)
event_data.timestamp = socket.gettime()
table.insert(self.gc_events, event_data)
-- Mantener historial limitado
if #self.gc_events > 100 then
table.remove(self.gc_events, 1)
end
print(string.format("GC Event: %s, Duration: %.2fms, Freed: %.1fKB",
event_data.type,
event_data.duration * 1000,
event_data.memory_freed))
end
local function check_automatic_gc(self)
local current_memory = collectgarbage("count")
local last_snapshot = self.memory_snapshots[#self.memory_snapshots]
if last_snapshot and current_memory < last_snapshot.memory * 0.8 then
-- Posible GC automático detectado
self:record_gc_event({
type = "automatic_collect",
memory_before = last_snapshot.memory,
memory_after = current_memory,
memory_freed = last_snapshot.memory - current_memory,
duration = 0 -- No podemos medir duración de GC automático
})
end
-- Tomar snapshot de memoria
table.insert(self.memory_snapshots, {
timestamp = socket.gettime(),
memory = current_memory
})
if #self.memory_snapshots > 60 then -- 1 minuto de snapshots
table.remove(self.memory_snapshots, 1)
end
end
-- Análisis de patrones de memoria
local function analyze_memory_patterns(self)
if #self.memory_snapshots < 10 then
return {error = "Insufficient data for analysis"}
end
local memory_values = {}
for _, snapshot in ipairs(self.memory_snapshots) do
table.insert(memory_values, snapshot.memory)
end
-- Calcular estadísticas
local min_memory = math.min(unpack(memory_values))
local max_memory = math.max(unpack(memory_values))
local avg_memory = 0
for _, value in ipairs(memory_values) do
avg_memory = avg_memory + value
end
avg_memory = avg_memory / #memory_values
-- Detectar memory leaks (tendencia creciente)
local growth_rate = (memory_values[#memory_values] - memory_values[1]) /
(#memory_values - 1)
-- Detectar fragmentación (variabilidad alta)
local variance = 0
for _, value in ipairs(memory_values) do
variance = variance + (value - avg_memory) ^ 2
end
variance = variance / #memory_values
local fragmentation_index = math.sqrt(variance) / avg_memory
return {
min_memory = min_memory,
max_memory = max_memory,
avg_memory = avg_memory,
memory_range = max_memory - min_memory,
growth_rate = growth_rate,
fragmentation_index = fragmentation_index,
potential_leak = growth_rate > 1.0, -- Más de 1KB por segundo
high_fragmentation = fragmentation_index > 0.2
}
end
-- Recomendaciones de optimización de memoria
local function generate_memory_recommendations(self)
local analysis = analyze_memory_patterns(self)
local recommendations = {}
if analysis.potential_leak then
table.insert(recommendations, {
type = "MEMORY_LEAK",
priority = "HIGH",
description = string.format("Potential memory leak detected: %.1fKB/s growth rate",
analysis.growth_rate)
})
end
if analysis.high_fragmentation then
table.insert(recommendations, {
type = "FRAGMENTATION",
priority = "MEDIUM",
description = string.format("High memory fragmentation: %.1f%% variability",
analysis.fragmentation_index * 100)
})
end
-- Análisis de GC frecuencia
local gc_frequency = #self.gc_events / (socket.gettime() / 60) -- GCs por minuto
if gc_frequency > 10 then
table.insert(recommendations, {
type = "GC_FREQUENCY",
priority = "MEDIUM",
description = string.format("High GC frequency: %.1f collections per minute",
gc_frequency)
})
end
return recommendations
end
return GCAnalyzer
🎮 Debug UI en Runtime
Console de Debug
-- debug_console.script
local DebugConsole = {}
function init(self)
self.visible = false
self.commands = {}
self.command_history = {}
self.output_buffer = {}
self.input_text = ""
self.cursor_pos = 0
-- Registrar comandos por defecto
self:register_default_commands()
-- Configurar GUI
self:setup_gui()
end
local function register_default_commands(self)
-- Comando help
self:register_command("help", function(args)
self:print("Available commands:")
for cmd_name, cmd_info in pairs(self.commands) do
self:print(string.format(" %s - %s", cmd_name, cmd_info.description))
end
end, "Show available commands")
-- Comando clear
self:register_command("clear", function(args)
self.output_buffer = {}
end, "Clear console output")
-- Comando memory
self:register_command("memory", function(args)
local memory_kb = collectgarbage("count")
self:print(string.format("Lua memory: %.1f KB", memory_kb))
if args[1] == "collect" then
collectgarbage("collect")
local after_gc = collectgarbage("count")
self:print(string.format("After GC: %.1f KB (freed %.1f KB)",
after_gc, memory_kb - after_gc))
end
end, "Show memory usage (use 'memory collect' to force GC)")
-- Comando fps
self:register_command("fps", function(args)
local enable = args[1] ~= "off"
msg.post("debug_manager", "toggle_fps_display", {enabled = enable})
self:print("FPS display " .. (enable and "enabled" or "disabled"))
end, "Toggle FPS display")
-- Comando goto
self:register_command("goto", function(args)
if not args[1] then
self:print("Usage: goto <x> <y>")
return
end
local x = tonumber(args[1]) or 0
local y = tonumber(args[2]) or 0
local player = game.get_player()
if player then
go.set_position(vmath.vector3(x, y, 0), player)
self:print(string.format("Teleported to (%.1f, %.1f)", x, y))
else
self:print("Player not found")
end
end, "Teleport to position (x, y)")
-- Comando spawn
self:register_command("spawn", function(args)
if not args[1] then
self:print("Usage: spawn <factory_name>")
return
end
local factory_url = "#" .. args[1] .. "_factory"
local success, obj = pcall(factory.create, factory_url)
if success then
self:print("Spawned: " .. tostring(obj))
else
self:print("Failed to spawn: " .. args[1])
end
end, "Spawn object from factory")
-- Comando set
self:register_command("set", function(args)
if #args < 3 then
self:print("Usage: set <object_url> <property> <value>")
return
end
local obj_url = args[1]
local property = args[2]
local value = args[3]
-- Intentar convertir value al tipo apropiado
local converted_value = tonumber(value) or value
if value == "true" then converted_value = true
elseif value == "false" then converted_value = false
end
local success, err = pcall(go.set, obj_url, property, converted_value)
if success then
self:print(string.format("Set %s.%s = %s", obj_url, property, tostring(converted_value)))
else
self:print("Error: " .. tostring(err))
end
end, "Set object property")
end
local function register_command(self, name, func, description)
self.commands[name] = {
func = func,
description = description or "No description"
}
end
local function execute_command(self, command_line)
if command_line == "" then return end
-- Añadir al historial
table.insert(self.command_history, command_line)
if #self.command_history > 50 then
table.remove(self.command_history, 1)
end
-- Parsear comando
local parts = {}
for part in command_line:gmatch("%S+") do
table.insert(parts, part)
end
if #parts == 0 then return end
local command_name = parts[1]
local args = {}
for i = 2, #parts do
table.insert(args, parts[i])
end
-- Mostrar comando ejecutado
self:print("> " .. command_line)
-- Ejecutar comando
local command = self.commands[command_name]
if command then
local success, err = pcall(command.func, args)
if not success then
self:print("Error executing command: " .. tostring(err))
end
else
self:print("Unknown command: " .. command_name .. " (type 'help' for available commands)")
end
end
local function print(self, text)
table.insert(self.output_buffer, {
text = tostring(text),
timestamp = os.time()
})
-- Mantener buffer limitado
if #self.output_buffer > 100 then
table.remove(self.output_buffer, 1)
end
-- Actualizar GUI
self:update_console_display()
end
function on_input(self, action_id, action)
if action_id == hash("debug_console") and action.pressed then
self.visible = not self.visible
gui.set_enabled(gui.get_node("console_root"), self.visible)
if self.visible then
msg.post(".", "acquire_input_focus")
else
msg.post(".", "release_input_focus")
end
end
if not self.visible then return end
if action_id == hash("enter") and action.pressed then
execute_command(self, self.input_text)
self.input_text = ""
self.cursor_pos = 0
self:update_input_display()
elseif action_id == hash("backspace") and action.pressed then
if #self.input_text > 0 and self.cursor_pos > 0 then
self.input_text = self.input_text:sub(1, self.cursor_pos - 1) ..
self.input_text:sub(self.cursor_pos + 1)
self.cursor_pos = self.cursor_pos - 1
self:update_input_display()
end
elseif action_id == hash("text") then
self.input_text = self.input_text:sub(1, self.cursor_pos) ..
action.text ..
self.input_text:sub(self.cursor_pos + 1)
self.cursor_pos = self.cursor_pos + #action.text
self:update_input_display()
end
end
DebugConsole.register_command = register_command
DebugConsole.execute_command = execute_command
DebugConsole.print = print
return DebugConsole
📚 Recursos y Referencias
Herramientas de Debugging
- Defold Debugger - Debugger integrado
- Visual Studio Code - Debug con extensión Lua
- ZeroBrane Studio - IDE Lua con debugging
- Repl.it - Testing online rápido
Profiling Tools
- Chrome DevTools - Para builds HTML5
- Xcode Instruments - Profiling en iOS
- Android Studio Profiler - Profiling en Android
- Intel VTune - Profiling avanzado en PC
Links Útiles
🎯 Ejercicios Propuestos
-
Sistema de Métricas: Crea un sistema completo de métricas que envíe datos a un servidor de analytics.
-
Memory Leak Detector: Implementa un detector automático de memory leaks que alerte en tiempo real.
-
Performance Budget: Crea un sistema de “presupuesto de rendimiento” que alerte cuando se superen límites.
-
Visual Profiler: Desarrolla un profiler visual que muestre gráficos de rendimiento en tiempo real.
-
Automated Testing: Implementa un framework de testing automatizado para detectar regresiones.
El debugging y profiling profesional son habilidades que separan a los desarrolladores amateur de los profesionales. Dominar estas técnicas te permitirá crear juegos estables, optimizados y de calidad profesional.