Auto-Save Roblox com BindToClose e DataStore Seguro
Gere um sistema de persistência robusto para Roblox que salva dados de jogadores em intervalos periódicos, ao sair do servidor e durante o desligamento da instância. O prompt exige práticas adequadas para DataStoreService, incluindo UpdateAsync, retentativas com backoff exponencial, controle de estado sujo e prevenção de salvamentos concorrentes para o mesmo jogador.
Ideal para jogos com moedas, níveis, inventário, estatísticas ou progressão persistente. O resultado é orientado para um Script de servidor pronto para o ServerScriptService, mas considera o contexto real do projeto — como estrutura de dados, pastas, atributos, Values, módulos e RemoteEvents já existentes — que o desenvolvedor poderá informar antes de gerar o código.
Atue como um desenvolvedor Roblox sênior especializado em Luau, DataStoreService, arquitetura servidor-autoritativa e confiabilidade de persistência. Gere um único Script de servidor completo para um sistema de auto-save periódico com salvamento em PlayerRemoving e proteção de encerramento usando game:BindToClose(). Antes de escrever o código, use o contexto abaixo. Se algum campo estiver vazio, adote uma solução segura e configurável, documente brevemente a suposição e não invente RemoteEvents ou estruturas desnecessárias. === CONTEXTO DO MEU JOGO (PREENCHER) === - Nome do DataStore: [ex.: PlayerData_v1] - Estrutura dos dados a salvar: [ex.: leaderstats.Coins, leaderstats.Level, pasta Inventory, atributos do Player etc.] - Onde os dados ficam em runtime: [caminhos completos no Explorer] - Valores padrão para jogador novo: [descrever tabela/valores] - Intervalo desejado do auto-save em segundos: [ex.: 120] - Existe sistema de carregamento de dados? [sim/não; caminho do script/módulo e formato retornado] - Existe sistema de salvamento já criado? [sim/não; caminho e APIs disponíveis] - RemoteEvents/RemoteFunctions existentes relacionados a dados: [listar caminhos e finalidade] - Regras especiais de dados: [itens não serializáveis, limites, migrations, versões, dados opcionais] - Nome de objetos/pastas relevantes no Explorer: [listar] === FIM DO CONTEXTO === O tipo de script deve ser obrigatoriamente um Script de servidor, colocado em ServerScriptService. Não gere LocalScript, ModuleScript nem múltiplos arquivos, salvo se o contexto indicar que já existe um módulo de dados obrigatório; nesse caso, mantenha este resultado como um único Script que o integra. O código deve ser pronto para colar no Roblox Studio e deve conter comentários úteis em português. Implemente um sistema profissional com estes requisitos: 1. Use DataStoreService e um DataStore configurável no topo do arquivo. Faça persistência com UpdateAsync, nunca SetAsync como estratégia principal, para reduzir risco de sobrescrita entre servidores e permitir mesclagem segura quando aplicável. 2. Inclua uma função central SavePlayer(player, reason), em que reason identifique pelo menos "AutoSave", "PlayerRemoving" e "BindToClose". Ela deve coletar e validar os dados do servidor antes de persistir. 3. Não confie no cliente para moeda, inventário, nível, dano ou qualquer dado persistente. Este sistema não deve aceitar payload de salvamento enviado por RemoteEvent/RemoteFunction. Caso o contexto cite remotes existentes, deixe explícito em comentários que eles não podem autorizar salvamento direto e que qualquer alteração de estado deve ser validada no servidor. 4. Implemente controle por jogador para impedir duas operações de save simultâneas. Use uma tabela de estado com, no mínimo, flags de salvamento em andamento, estado "dirty"/alterado e tentativas pendentes. Se ocorrer alteração enquanto um save estiver em andamento, garanta que o jogador continue marcado para um novo save posterior. 5. Implemente retentativas para falhas transitórias de DataStore com pcall, quantidade máxima configurável de tentativas e backoff exponencial limitado. Registre avisos claros com warn, incluindo UserId, motivo e número da tentativa, sem expor dados sensíveis desnecessariamente. 6. Crie um loop periódico usando task.spawn/task.wait que percorra jogadores conectados e salve somente jogadores marcados como dirty. Se o projeto não fornecer um mecanismo de detectar alterações, apresente no próprio código uma função MarkDirty(player) e conecte exemplos seguros a Changed/AttributeChanged apenas para os objetos que existirem no contexto. Evite uma conexão genérica e cara em DescendantAdded para todo o Player. 7. Conecte Players.PlayerRemoving para tentar salvar o jogador que sai. Após a tentativa, limpe referências e conexões associadas com cuidado, sem destruir dados antes da confirmação ou sem explicar o comportamento em caso de falha. 8. Use game:BindToClose() para iniciar o salvamento final de todos os jogadores ainda conectados. Durante o fechamento, evite iniciar saves duplicados, tente salvar em paralelo com limite de concorrência configurável ou de forma segura e aguarde apenas por um tempo máximo configurável, respeitando o limite prático de encerramento do Roblox. Não use espera infinita. Explique em comentários que BindToClose reduz perda de dados, mas não garante persistência absoluta em quedas abruptas. 9. Garanta serialização compatível com DataStore: apenas números, strings, booleanos e tabelas simples. Valide tipos, trate nil, evite salvar Instances, funções, userdata ou referências de objetos. Se o contexto possuir inventário, converta-o explicitamente para uma tabela serializável estável. 10. Organize constantes configuráveis no início: DATASTORE_NAME, AUTOSAVE_INTERVAL, MAX_RETRIES, RETRY_BASE_DELAY, SHUTDOWN_TIMEOUT e, se usado, MAX_CONCURRENT_SHUTDOWN_SAVES. Inclua validações defensivas e comentários de manutenção. Entregue a resposta nesta ordem: primeiro, uma lista curta de suposições feitas com base no contexto; depois, o código integral em exatamente um bloco markdown identificado como ```lua; por fim, instruções objetivas para instalar e testar no Roblox Studio. Nas instruções de teste, inclua como habilitar API Services em Game Settings > Security, como testar com Start Server/Start Player, como verificar Output, como simular saída de jogador e como testar o fechamento do servidor. Não omita partes do código, não use pseudocódigo e não deixe trechos como "complete aqui".