Chapter 6: Refatoração da Modularidade e a Abstração do Teleporte
A interface de usuário estava funcionalmente completa, refletindo o estado dos dados e reagindo às mudanças de estado com um padrão de qualidade visual aceitável. O desenvolvedor sênior, referenciado metaforicamente como 'o Arquiteto' em seus próprios devaneios, sentiu a satisfação fria de quem constrói uma máquina bem lubrificada. Tudo estava ligado. Mas conexões diretas entre módulos, como visto nas últimas linhas do capítulo anterior onde o TeleportModule e o DataModule eram diretamente required e chamados dentro do CreateWaypointItem, representavam uma dependência rígida. Essa rigidez era um ponto de atrito para a manutenção e para futuros testes de unidade.
A arquitetura limpa exigia abstração. O UIManager deveria se concentrar em apresentar os dados e capturar ações do usuário, delegando a execução dessas ações a um nível superior ou a um sistema de eventos.
O Problema da Injeção Direta
O código finalizado do CreateWaypointItem tinha:
-- Dentro de CreateWaypointItem(waypointData): local TeleportModule = require(script.Parent.TeleportModule) TeleportButton.MouseButton1Click:Connect(function() local success, result = pcall(TeleportModule.Teleport, TeleportModule, waypointData.CFrameData) -- ... Feedback visual ... end) DeleteButton.MouseButton1Click:Connect(function() DataModule.RemoveWaypoint(waypointData.ID) end)
No contexto de módulos Lua injetados por um executor, require repetitivo poderia levar a duplicação desnecessária de dependências se o gerenciador de módulos do executor não fosse eficiente, embora no ambiente Roblox padrão, require armazene em cache. O risco principal, contudo, era de acoplamento. Se o TeleportModule mudasse seu nome de função, ou se quiséssemos simular a ação de teleporte sem realmente executá-la (para testes de UI), o UIManager travaria na dependência.
Abstraindo Ações via Callbacks
A solução era simples e robusta no paradigma Lua: injeção de dependência via callbacks ao criar o item da UI. O UIManager não deveria saber como o teleporte ou a exclusão funcionam, apenas que, ao clicar, uma função específica precisa ser chamada com os dados do waypoint.
O Arquiteto decidiu refatorar a função CreateWaypointItem para aceitar duas novas funções como argumentos: uma para a ação de teleporte e outra para a ação de exclusão.
Refatoração do CreateWaypointItem:
A assinatura da função seria alterada:
local function CreateWaypointItem(waypointData, TeleportCallback, DeleteCallback) -- ... (criação da UI, permanecendo inalterada) ... -- Lógica de binding para Teleporte TeleportButton.MouseButton1Click:Connect(function() print("Iniciando teleporte delegado para: " .. waypointData.Name) -- Chama a função injetada local success, result = pcall(TeleportCallback, waypointData) if success then -- Feedback visual de sucesso TeleportButton.BackgroundColor3 = Color3.fromRGB(50, 200, 50) task.delay(0.1, function() TeleportButton.BackgroundColor3 = Color3.fromRGB(50, 150, 255) end) else -- Feedback visual de erro print("[UIManager: Error] Falha no teleporte delegado: " .. (result or "Erro desconhecido.")) TeleportButton.BackgroundColor3 = Color3.fromRGB(255, 50, 50) task.delay(0.2, function() TeleportButton.BackgroundColor3 = Color3.fromRGB(50, 150, 255) end) end end) -- Lógica de binding para Exclusão DeleteButton.MouseButton1Click:Connect(function() print("Confirmação de exclusão delegada para: " .. waypointData.Name) -- Chama a função injetada DeleteCallback(waypointData.ID) -- Feedback visual de exclusão (não dependemos aqui do sucesso do callback do DataModule) DeleteButton.BackgroundColor3 = Color3.fromRGB(255, 100, 100) task.delay(0.1, function() DeleteButton.BackgroundColor3 = Color3.fromRGB(180, 50, 50) end) end) -- ... (Restante da função) ... return ItemContainer end
O UIManager agora delegava completamente a responsabilidade da ação, mas mantinha o controle estrito sobre o feedback visual, que era sua responsabilidade primária. A única premissa era: o TeleportCallback deve retornar um booleano de sucesso e, opcionalmente, uma mensagem de erro ou resultado.
Reconexão na Rotina de Renderização
A mudança mais significativa agora ocorreria em UIManager.RenderWaypointList. Esta função, que atua como o ponto de entrada para a criação dos itens, precisava injetar as funções reais de teleporte e exclusão, que residiam externamente.
Para manter a responsabilidade no ponto de controle mais alto (o UIManager em si, ou uma rotina de inicialização), era necessário que o módulo pai que gerenciava o UIManager (o script principal) passasse estas referências. No contexto atual dentro do UIManager, o Arquiteto faria uma suposição temporária para a próxima etapa:
O módulo principal do sistema (a ser nomeado SystemCore ou algo similar) seria o responsável por juntar as peças. Assumindo que DataModule e TeleportModule estavam acessíveis ao UIManager no mesmo nível hierárquico, a rotina de renderização agora precisava ser modificada:
-- UIManager.lua -- Referências internas (para simular o ambiente de injeção) local DataModule = require(script.Parent.DataModule) local TeleportModule = require(script.Parent.TeleportModule) -- ... (Omitindo a rotina GetWaypointScrollFrame e a ordenação) ... function UIManager.RenderWaypointList() local ScrollFrame = GetWaypointScrollFrame() if not ScrollFrame then return end -- Limpar itens atuais for _, child in ipairs(ScrollFrame:GetChildren()) do if child:IsA("Frame") and child.Name:sub(1, 6) == "WP_Item" then child:Destroy() end end local waypoints = DataModule.GetWaypoints() table.sort(waypoints, function(a, b) return a.Timestamp < b.Timestamp -- Ordenação crescente pelo timestamp end) local renderedCount = 0 for _, data in ipairs(waypoints) do -- 1. Criação das Callbacks de Ação -- Callback para Teleporte: Usa o WaypointData (ID, Name, CFrameData) local TeleportAction = function(waypointDataToTeleport) -- A lógica de teleporte reside aqui, adaptada para a injeção. return TeleportModule.Teleport(waypointDataToTeleport.CFrameData) end -- Callback para Exclusão: Usa apenas o ID local DeleteAction = function(waypointID) -- A exclusão dispara o WaypointsChanged, que re-renderizará a lista DataModule.RemoveWaypoint(waypointID) end -- 2. Criação do Item com injeção das ações local item = CreateWaypointItem(data, TeleportAction, DeleteAction) item.LayoutOrder = renderedCount + 1 item.Parent = ScrollFrame renderedCount = renderedCount + 1 end -- ... (Ajuste do CanvasSize) ... print(string.format("[UIManager] Lista renderizada após refatoração delegado. [%d] Waypoints encontrados.", renderedCount)) end
Com esta refatoração, a responsabilidade estava mais clara. O CreateWaypointItem não conhecia o DataModule nem o TeleportModule; ele apenas executava funções anônimas que lhe eram passadas. A RenderWaypointList, por sua vez, assumia a responsabilidade de ligar os módulos reais aos itens da UI no momento da renderização. Embora ainda fosse acoplada aos módulos reais, esse acoplamento agora estava concentrado em um único ponto, facilitando a substituição futura por um sistema de eventos global, se necessário.
Tratamento de Exclusão: Consistência do Estado
A lógica de exclusão no DataModule.RemoveWaypoint(waypointData.ID) precisava de uma confirmação mental de que o ciclo de eventos estava intacto.
- Usuário clica em 'X' no WaypointItem.
- DeleteCallback (que é apenas uma chamada para DataModule.RemoveWaypoint) é executada.
- DataModule.RemoveWaypoint executa a exclusão, salva os dados e, o mais importante, dispara o evento DataModule.WaypointsChanged.
- O UIManager está conectado a este evento via SetupDataSynchronization.
- O evento dispara UIManager.RenderWaypointList(), que destrói todos os itens e os reconstrói, sincronizando a UI com o novo estado dos dados (sem o item excluído).
Esse fluxo, embora envolvesse um re-render completo, era o padrão de ouro para a consistência em aplicações reativas de pequeno porte. Em um sistema de larga escala, seria preferível um algoritmo de reconciliação de UI (tipo diffing), mas para uma lista de ~20-50 waypoints, o re-render completo garantia robustez contra falhas de sincronização complexas.
A Questão da Velocidade do Tween
Outro detalhe que o Arquiteto precisava resolver era a interação do TweenSpeedControl com o DataModule. No capítulo anterior, o código para salvar a velocidade do Tween estava comentado:
-- Trecho revisado do TweenSpeedControl.FocusLost: local function SaveTweenSpeed(speed) -- DataModule.SetSetting("tweenSpeed", speed) end
E a rotina de alternância de métodos (SetMethodVisual) também precisava carregar e exibir a velocidade salva:
-- Trecho revisado do SetMethodVisual: if TweenSpeedControl and TweenSpeedControl:FindFirstChild("SpeedInput") then local speedInput = TweenSpeedControl:FindFirstChild("SpeedInput") -- speedInput.Text = tostring(DataModule.GetSetting("tweenSpeed", 50)) end
O UIManager dependia do DataModule para obter e salvar o estado das configurações, o que era um acoplamento aceitável, pois a UI era especificamente projetada para gerenciar as configurações persistentes.
O Arquiteto revisou o código do input de velocidade, ativando as chamadas ao DataModule.
-- UIManager.lua (Dentro de CreateTweenSpeedControl) local SpeedInput = TweenSpeedControl:FindFirstChild("SpeedInput") SpeedInput.FocusLost:Connect(function(enterPressed) if enterPressed or not enterPressed then local speed = tonumber(SpeedInput.Text) local minSpeed = 10 local maxSpeed = 500 if speed and speed >= minSpeed and speed <= maxSpeed then local floorSpeed = math.floor(speed) -- Ação real: Salvar a configuração DataModule.SetSetting("tweenSpeed", floorSpeed) SpeedInput.Text = tostring(floorSpeed) elseif speed then -- Clamp e Salvar local clampedSpeed = math.clamp(speed, minSpeed, maxSpeed) local floorClampedSpeed = math.floor(clampedSpeed) DataModule.SetSetting("tweenSpeed", floorClampedSpeed) SpeedInput.Text = tostring(floorClampedSpeed) else -- Não é número, restaurar o valor salvo SpeedInput.Text = tostring(DataModule.GetSetting("tweenSpeed", 50)) end end end) -- Garante que Label refletirá o valor atual ao ser criado -- Isso deve ser feito depois que o componente for criado, preferencialmente na rotina de inicialização, -- mas pode ser defensivamente colocado aqui. SpeedInput.Text = tostring(DataModule.GetSetting("tweenSpeed", 50)) SpeedInput.PlaceholderText = "Vel. Atual: " .. DataModule.GetSetting("tweenSpeed", 50)
O mesmo tratamento precisava ser aplicado à alternância de modo de teleporte em SetMethodVisual:
-- UIManager.lua local function SetMethodVisual(method) -- ... (lógica visual de destaque dos botões omitida) ... CurrentMethod = method -- Atualiza o estado interno da UI -- 1. Notificar DataModule sobre a mudança do método principal DataModule.SetSetting("teleportMethod", method) -- 2. Iniciar o tween de redimensionamento da ControlArea local isTweenSelected = (method == "tween") UpdateControlAreaHeight(isTweenSelected) -- 3. Atualizar o TextLabel do SpeedControl com o valor salvo no DataModule if TweenSpeedControl and TweenSpeedControl:FindFirstChild("SpeedInput") then local speedInput = TweenSpeedControl:FindFirstChild("SpeedInput") local savedSpeed = DataModule.GetSetting("tweenSpeed", 50) speedInput.Text = tostring(savedSpeed) speedInput.PlaceholderText = "Vel. Atual: " .. savedSpeed end end
O Desafio da Sincronização Inicial
O Arquiteto retornou à rotina de inicialização para garantir que o estado inicial fosse carregado corretamente do disco (via DataModule) e aplicado visualmente.
-- UIManager.lua (Rotina de Setup/Initialize) function UIManager.Initialize() -- ... (Criação de MainFrame, Header, ControlArea, WaypointList, etc.) ... -- Criação dos sub-componentes (WaypointCreation, MethodSelect, TweenSpeedControl) -- ... -- Preparar a sincronização de dados (conexão ao WaypointsChanged) SetupDataSynchronization() -- 1. Carregar o estado de teleporte salvo local initialMethod = DataModule.GetSetting("teleportMethod", "instant") -- 2. Aplicar o estado visual carregado. -- Para a inicialização, precisamos garantir que o estado visual é aplicado, -- mas evitando o loop de eventos MouseClick se SetMethodVisual chama DataModule.SetSetting. -- Para evitar a recursão ou efeitos colaterais na inicialização, SetMethodVisual -- deve ser chamado com um flag de "inicialização". Mas como o DataModule.SetSetting -- é seguro para idempotência, podemos chamá-lo diretamente, garantindo que a ControlArea -- seja dimensionada corretamente. SetMethodVisual(initialMethod) -- O SetMethodVisual já trata: -- a) Destaque dos botões de método. -- b) Visualização/Ocultação do TweenSpeedControl (via UpdateControlAreaHeight). -- c) Carregamento da velocidade salva no input. -- Note: RenderWaypointList é chamado dentro de SetupDataSynchronization(), -- que é chamado antes de SetMethodVisual, garantindo que o scrollframe -- esteja dimensionado corretamente para a altura de lista *inicial*. print("[UIManager] Inicialização concluída. Estado persistido carregado: " .. initialMethod) end
Este último ajuste garante que a UI não apenas reaja às ações do usuário, mas também inicie no estado exato em que foi deixada na última sessão, lendo as configurações (método, velocidade) do disco.
Refinando a Criação de Waypoint
A rotina de criação de waypoint também precisava ser revisada, pois anteriormente apenas chamava o DataModule.AddWaypoint e em seguida o UIManager.RenderWaypointList().
-- UIManager.lua (Dentro de CreateWaypointCreation) CreateButton.MouseButton1Click:Connect(function() local name = NameInput.Text -- ... (Validação e busca do HRP) ... local character = Players.LocalPlayer.Character local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart") if not humanoidRootPart then print("[UIManager: Error] HRP não encontrado, não é possível criar waypoint.") -- Feedback visual de erro NameInput.BackgroundColor3 = Color3.fromRGB(200, 50, 50) task.delay(0.2, function() NameInput.BackgroundColor3 = GlobalStyle.AccentColor end) return end -- 2. Adicionar Waypoint -- Note que AddWaypoint no DataModule deve disparar o evento WaypointsChanged, -- que, por sua vez, dispara a RenderWaypointList. -- Se AddWaypoint disparar o evento corretamente, NÃO PRECISAMOS chamar RenderWaypointList aqui. local success, newWaypoint = pcall(DataModule.AddWaypoint, DataModule, name, humanoidRootPart.CFrame) if success and newWaypoint then print("Waypoint criado: " .. name) NameInput.Text = "" -- Limpa o input -- A sincronização (RenderWaypointList) é garantida pelo DataModule.WaypointsChanged. -- Chamá-la diretamente aqui seria redundante e quebraria a arquitetura orientada a eventos. else -- Tratamento de erro print("[UIManager: Error] Falha ao adicionar waypoint: " .. (newWaypoint or "Desconhecido")) -- Feedback visual de erro (input vermelho) NameInput.BackgroundColor3 = Color3.fromRGB(200, 50, 50) task.delay(0.2, function() NameInput.BackgroundColor3 = GlobalStyle.AccentColor end) end end)
O Arquiteto removeu a chamada direta a UIManager.RenderWaypointList() após o pcall de AddWaypoint. Se o DataModule estivesse operando como um Model reativo, a UI operaria unicamente como uma View reativa ao evento WaypointsChanged. Isso solidificava a separação de responsabilidades. A criação de um waypoint agora era uma transação que, se bem-sucedida, alterava o modelo de dados e o modelo de dados notificava a UI para que ela se atualizasse.
Teste de Unidade Mental: O Fluxo Reativo
O Arquiteto simulou mentalmente o fluxo de criação:
- Usuário digita "Base Secreta" e clica em Criar.
- CreateButton.MouseButton1Click aciona, valida o texto, obtém o CFrame do HRP.
- DataModule.AddWaypoint é chamado.
- Dentro de DataModule, o novo waypoint é adicionado à lista em memória, o arquivo JSON é reescrito (salvamento), e WaypointsChanged:Fire() é executado.
- O UIManager capta o evento: DataModule.WaypointsChanged:Connect(UIManager.RenderWaypointList).
- UIManager.RenderWaypointList limpa o ScrollFrame, reordena a lista completa (incluindo "Base Secreta") e injeta a ação de Teleporte e Exclusão refatoradas (que chamam os módulos reais: TeleportModule.Teleport e DataModule.RemoveWaypoint).
- A lista aparece na UI com o novo item, corretamente ordenada pelo Timestamp.
O fluxo estava coeso e minimamente acoplado para a arquitetura de módulos autônomos.
Preparação para o TeleportModule: Contexto de Informação
A refatoração do WaypointItem transferiu a responsabilidade de feedback visual de sucesso do teleporte de volta para a UI (código dentro do TeleportButton.MouseButton1Click), que era o correto.
No entanto, o TeleportModule.Teleport precisava, para funcionar corretamente, saber qual método e qual velocidade usar. No TeleportAction refatorado, isso não estava explícito:
-- UIManager.lua (dentro de RenderWaypointList) local TeleportAction = function(waypointDataToTeleport) -- O TeleportModule precisa saber as configurações! return TeleportModule.Teleport(waypointDataToTeleport.CFrameData) end
O TeleportModule, conforme definido no Capítulo 3, tinha a função unificada TeleportModule.Teleport(cframeData) que implicitamente deveria buscar as configurações atuais (método e velocidade) no DataModule.
O Arquiteto confirmou que a arquitetura do Capítulo 3 previa essa autodeterminação:
O sistema de teleporte... [usa] uma função unificada, TeleportModule.Teleport, [que] escolhe o método apropriado baseado nas configurações.
Portanto, o TeleportAction injetado estava correto: ele apenas fornecia o destino (CFrameData), e o TeleportModule se encarregava de buscar o método e a velocidade preferidos pelo usuário no DataModule (que já estava devidamente configurado e salvo no disco).
Esta interdependência oculta era inevitável: (1) UI salva configurações no DataModule. (2) TeleportModule lê configurações do DataModule. O acoplamento era concentrado no DataModule, que agia como um hub de estado persistido.
Finalizando a Conexão: O Botão TP
Com a arquitetura reavaliada e refatorada, o foco voltou para o aspecto físico e interativo da UI.
O DeleteButton.MouseButton1Click agora estava extremamente limpo, resumindo-se a uma única chamada DataModule.RemoveWaypoint(waypointData.ID) e um fade visual rápido, confiando que o ciclo de re-renderização faria o resto.
O TeleportButton.MouseButton1Click era o que restava. O uso de pcall era obrigatório: teleporte é uma ação que pode falhar (HRP bloqueado, personagem morto, sub-mapa inválido, etc.).
A lógica final dentro do TeleportButton.MouseButton1Click (agora dentro do TeleportAction injetado na RenderWaypointList e executado dentro do CreateWaypointItem) era:
- Executar TeleportModule.Teleport.
- pcall captura o sucesso ou falha da execução.
- O feedback visual do botão (verde para sucesso, vermelho para falha) reage deterministicamente a esse pcall.
A pequena janela de 0.1s para o fade de cor era crucial para a UX. Era um feedback tátil, visualmente rápido o suficiente para comunicar ação imediata sem ser intrusivo.
O Estado "Pendente" de Teleporte
Havia um detalhe de UX a ser considerado: o que acontece se o usuário clica no botão TP enquanto um teleporte anterior (via Tween) ainda está em curso?
A melhor prática ditava que o botão deveria ser visualmente desativado (cinza, talvez) ou ignorar cliques enquanto o TeleportModule estivesse em estado IsTeleporting.
O Arquiteto decidiu que a inclusão dessa funcionalidade no WaypointItem aumentaria o acoplamento desnecessariamente. Ele teria que fazer o WaypointItem assinar o estado TeleportModule.IsTeleporting.
Em vez disso, a validação deveria ser feita dentro do TeleportModule no ponto de entrada TeleportModule.Teleport.
Se TeleportModule.Teleport retornasse false (e uma mensagem de erro como "Teleporte em curso"), o WaypointItem reagiria:
-- UIManager.lua (Lógica dentro do TeleportAction) if success then -- ... Sucesso, botão verde ... else -- Falha (pode ser "Teleporte em curso" ou erro fatal de HRP) if result and string.find(result, "em curso") then print("[UIManager: Aviso] Teleporte já em progresso. Ignorando clique.") -- Feedback visual mais suave, como um piscar rápido sem mudar para vermelho TeleportButton.BackgroundColor3 = Color3.fromRGB(150, 150, 150) -- Cinza de aviso task.delay(0.1, function() TeleportButton.BackgroundColor3 = Color3.fromRGB(50, 150, 255) end) else print("[UIManager: Error] Falha no teleporte delegado: " .. (result or "Erro desconhecido.")) -- Feedback visual de erro grave TeleportButton.BackgroundColor3 = Color3.fromRGB(255, 50, 50) task.delay(0.2, function() TeleportButton.BackgroundColor3 = Color3.fromRGB(50, 150, 255) end) end end
Esta abordagem evitava o acoplamento direto de estado (o WaypointItem não precisava saber quando o teleporte terminava), mas ainda fornecia feedback visual adequado ao usuário se a ação fosse rejeitada pelo módulo de teleporte.
O Estresse Iminente
A abstração estava completa. O UIManager não era mais um conjunto estático de caixas; era uma interface reativa e configurável, ligada dinamicamente ao modelo de dados e delegando ações a módulos de execução.
Tudo estava pronto para o próximo passo. A próxima iteração seria o teste de estresse de estabilidade e performance, focando na telemetria.
O Arquiteto olhou para a tela, onde o protótipo da UI flutuava, mostrando três waypoints de teste estáticos: "Safe Zone", "Sea 1 Boss" e "Dungeon Entrance." A lista estava ordenada corretamente, o botão "Tween" estava selecionado, e a velocidade em 75 studs/s. A máquina virtual do executor estava estável. Tudo funcionava isoladamente.
O requisito final era verificar se a RenderWaypointList injetava corretamente as ações refatoradas no último item da lista.
Simulação interna final:
- RenderWaypointList() processa o último item (o mais recente).
- Cria o WaypointItem com o data da "Dungeon Entrance".
- Injeta TeleportAction e DeleteAction.
O código era limpo:
local item = CreateWaypointItem(data, TeleportAction, DeleteAction) -- ...
E o CreateWaypointItem era agora um módulo de UI puro, sem dependências lógicas:
local function CreateWaypointItem(waypointData, TeleportCallback, DeleteCallback) -- ... (todo o setup visual) ... TeleportButton.MouseButton1Click:Connect(function() local success, result = pcall(TeleportCallback, waypointData) -- ... (feedback visual) ... end) DeleteButton.MouseButton1Click:Connect(function() DeleteCallback(waypointData.ID) -- ... (feedback visual) ... end) return ItemContainer end
O circuito estava completo. O Arquiteto recostou-se para iniciar a próxima fase, a de martelar o sistema até a quebra (o teste de estresse), mas antes, deveria executar a checagem final, a garantia de que as últimas linhas de código do capítulo anterior, que demonstravam a dependência rígida, seriam substituídas por essa nova arquitetura de callbacks.
Ele moveu o cursor para a parte inferior do arquivo UIManager.lua, pronto para aplicar as mudanças de injeção de dependência na rotina de renderização.
Onde o foco estava em finalizar a conexão dos botões:
-- Trecho final do capítulo anterior: -- ... Com a arquitetura concluída e os eventos `Connect` finalizados, o Módulo UI estava operacional. A conexão física entre a UI e os outros módulos estava estabelecida. O sistema agora estava totalmente sincronizado, desde a persistência de dados até a reação visual do painel. A interface refletia o estado da lista de waypoints em tempo real. O desenvolvedor sênior observou a interface renderizada, estática, mas pronta para operar, esperando o clique do mouse que iniciaria o ciclo de vida dinâmico do sistema.
O Arquiteto sabia que o próximo passo crucial era garantir que a lógica estivesse encapsulada. A refatoração já estava feita, agora era hora de garantir que o código base estivesse refletindo essa nova, limpa, e abstrata...
A próxima etapa lógica no ciclo de desenvolvimento, antes de prosseguir para estresse e telemetria, era garantir que o DataModule.RemoveWaypoint fosse implementado corretamente para disparar o evento WaypointsChanged e que a injeção via RenderWaypointList estivesse formalmente codificada.
O Arquiteto moveu o cursor para o bloco UIManager.RenderWaypointList para consolidar a injeção das callbacks de ação. Ele executou uma visualização rápida da estrutura do código.
O resultado final e coeso dessa refatoração, que agora injetava as ações reais de teleporte e exclusão na criação de cada item, era a prova da modularidade:
-- UIManager.lua function UIManager.RenderWaypointList() -- ... (inicialização do ScrollFrame e obtenção/ordenação dos waypoints) ... local renderedCount = 0 for _, data in ipairs(waypoints) do -- 1. Definição das Callbacks de Ação -- Teleporte: Usa CFrameData e delega a TeleportModule local TeleportAction = function(waypointDataToTeleport) -- O TeleportModule lê o método e a velocidade do DataModule internamente. return TeleportModule.Teleport(waypointDataToTeleport.CFrameData) end -- Exclusão: Dispara o WriteFile/WaypointsChanged via DataModule local DeleteAction = function(waypointID) DataModule.RemoveWaypoint(waypointID) end -- 2. Criação do Item com injeção das ações local item = CreateWaypointItem(data, TeleportAction, DeleteAction) item.LayoutOrder = renderedCount + 1 item.Parent = ScrollFrame renderedCount = renderedCount + 1 end -- ... (Ajuste do CanvasSize e logs) ... end
Com o código limpo, a separação de View e Controller estava maximizada dentro das limitações do ambiente de executor. Ele deu um último commit mental na arquitetura...
a interface estava pronta para suportar a próxima fase de desenvolvimento. Ele se preparou para...
Comments (0)
No comments yet. Be the first to share your thoughts!