:hearts: Introdução
Como a board está muito cheia de pessoas novas, gostaria de ajudar os novos programadores com alguns artigos que os levem a um bom caminho nessa jornada, ajuda-los com escolhas e principalmente pelo fato dessa board ter poucos tutoriais hoje em dia com práticas mais atuais e maduras. Uma boa parte do conteúdo dessa board é bem antigo e usam métodos não tão bons. Então quero dar um contexto melhor aos sistemas de salvamento atuais, dar uma visão geral e de quebra ditar algumas boas práticas e dicas para ter um código mais performático.
:hearts: Sobre os sistemas de salvamento
* Cada include tem seu próprio sistema e suas otimizações. A explicação aqui dada é uma base de como algumas delas funcionam.
Anteriormente aqui na board, uma boa parte dos devs menos experientes usavam bastante o dini. Desde muito tempo, já existia o DOF2 e outras alternativas até mais rápidas. Hoje em dia a comunidade foi um pouco mais pra frente e utilizam o DOF2, mas até ele já foi ultrapassado no quesito desempenho. Mas não se engane; esse tutorial não veio aqui para ditar qual sistema é mais veloz; na prática, todos eles possuem um desempenho que com toda a certeza vai lhe servir e possuem praticamente os mesmos problemas e vantagens. Mas caso seu foco seja desempenho, e esteja curioso sobre includes mais rápidas que DOF2, tem o bini, dini2 e y_ini.
Essas includes possuem praticamente a mesma forma de uso, mas quero dar uma visão melhor aqui sobre como elas realmente são. Para entedê-las bem, devemos começar consultando o sistema de arquivos atual do samp (file.inc).
Quando quero escrever em um arquivo, faço o seguinte:
Como a board está muito cheia de pessoas novas, gostaria de ajudar os novos programadores com alguns artigos que os levem a um bom caminho nessa jornada, ajuda-los com escolhas e principalmente pelo fato dessa board ter poucos tutoriais hoje em dia com práticas mais atuais e maduras. Uma boa parte do conteúdo dessa board é bem antigo e usam métodos não tão bons. Então quero dar um contexto melhor aos sistemas de salvamento atuais, dar uma visão geral e de quebra ditar algumas boas práticas e dicas para ter um código mais performático.
:hearts: Sobre os sistemas de salvamento
* Cada include tem seu próprio sistema e suas otimizações. A explicação aqui dada é uma base de como algumas delas funcionam.
Anteriormente aqui na board, uma boa parte dos devs menos experientes usavam bastante o dini. Desde muito tempo, já existia o DOF2 e outras alternativas até mais rápidas. Hoje em dia a comunidade foi um pouco mais pra frente e utilizam o DOF2, mas até ele já foi ultrapassado no quesito desempenho. Mas não se engane; esse tutorial não veio aqui para ditar qual sistema é mais veloz; na prática, todos eles possuem um desempenho que com toda a certeza vai lhe servir e possuem praticamente os mesmos problemas e vantagens. Mas caso seu foco seja desempenho, e esteja curioso sobre includes mais rápidas que DOF2, tem o bini, dini2 e y_ini.
Essas includes possuem praticamente a mesma forma de uso, mas quero dar uma visão melhor aqui sobre como elas realmente são. Para entedê-las bem, devemos começar consultando o sistema de arquivos atual do samp (file.inc).
Quando quero escrever em um arquivo, faço o seguinte:
- Código:
new File:arquivo = fopen("Arquivo.txt", io_append);
fwrite(arquivo, "bla bla bla\n\r");
fclose(arquivo);
Basicamente esse código, ele:
Abre o arquivo "Arquivo.txt" (caso não exista, será criado);Escreve "bla bla bla\n\r" nele;
Fecha o arquivo.
Quando quero ler um arquivo, faço o seguinte:
- Código:
new File:arquivo = fopen("Arquivo.txt", io_append), string[100];
while(fread(arquivo, string)) {
// Valor de string, é o texto da linha atual.
print(string);
}
fclose(arquivo);
Basicamente esse código, ele:
Abre o arquivo "Arquivo.txt";Lê cada linha do arquivo e printa no console;
Fecha o arquivo.
Como pode ver, a um processo de abrir, executar a ação e fechar o arquivo. Quando se trata de leitura, já vai levar mais tempo dependendo de quantas linhas o arquivo tiver. Oque basicamente faz os sistemas de salvamento, é usar uma metodologia de [CHAVE] e [VALOR].
- [CHAVE]: Seria o campo que você usa para identificar um dado. Por exemplo: "Senha"
- [VALOR]: Seria o que contém o valor dessa chave.
Vou usar de exemplo uma função como DOF2_GetString. Ela basicamente:
- Abre o arquivo;
- Mapeia todas as chaves e valores e aloca na memoria;
- Lhe retorna o resultado.
Aqui que começa o ponto interessante. Caso você use DOF2_GetString novamente, e seja o mesmo arquivo, essa funçao será mais rapida do que usada na primeiro vez. Por que? Basicamente, pela segunda etapa. Após aberto o arquivo, ele é mapeado e armazenado na memória. E acessar a memória, é muuuuito mais rapído do que abrir o arquivo, ler, mapear e retornar o resultado. ALGUNS sistemas armazenam mais de um arquivo na memória.
Agora visualize isso no servidor. Digamos que a include está definida pra manter 10 arquivos em "cache". Um player então entra no servidor, no momento que vai carregar os dados dele, o DOF2_GetString vai mapear os dados dele. Até aqui beleza. Mas ai vai entrando players, e no 11° player, alguem vai perder seus dados em "cache". Então quando você for usar alguma função do DOF2 nesse bem dito, o primeiro DOF2, será mais devagar, mas ai ele será remapeado novamente e alocado novamente; e ai um outro cidadão vai perder seu "cache". Deu pra entender né? Agora imagina isso em um servidor RPG que além do sistema de contas tem: sistema de casa, empresa, veículos e etc usando funções do DOF2. Bom, 10 slots de "cache" para 300 casas, 150 empresas, 30 players, e por ai vai, viu a cagada né?
Então meu servidor ficará lento? Negativo. Apesar de ser uma operação muito mais pesada se comparado a acessar um valor em memória, os computadores modernos conseguem normalmente executar isso. Mas terá casos em que isso poderá cair em um código que provavelmente irá travar seu servidor. Por exemplo, criar um timer a casa 100ms fazendo loop em todas as casas realizando varias operações de DOF2. Mesmo que seu servidor não fique travado, vai ter mais uso de CPU, independente do código estar mal feito ou não. Claro que dependendo do grau, pode travar seu servidor, mas não quer dizer que qualquer coisa que você fizer vai travar ele, já que o computador executa essas operações extremamente rápido. Mas para aproveitar bem esses sistemas e você ter performance, você deve saber usar bem esses sistemas, e estarei dando a maior dica sobre isso.
:hearts: Evite operações usando DOF2_...
Tenha em mente uma coisa, o ideal é você realizar a seguinte sequência: Carregar, Manipular, Salvar. Vou usar de exemplo o sistema de contas que é o mais comum. Seguirá o seguinte padrão:
- Quando o player entrar (OnPlayerConnect), carregue os dados dele para variáveis;
- Enquanto ele estiver no servidor, evite usar DOF2_..., manipule somente as variáveis;
- Quando ele desconectar; de tempos em tempos; e em casos importantes, salve os dados dele.
Exemplo de código
- [CHAVE]: Seria o campo que você usa para identificar um dado. Por exemplo: "Senha"
- [VALOR]: Seria o que contém o valor dessa chave.
Vou usar de exemplo uma função como DOF2_GetString. Ela basicamente:
- Abre o arquivo;
- Mapeia todas as chaves e valores e aloca na memoria;
- Lhe retorna o resultado.
Aqui que começa o ponto interessante. Caso você use DOF2_GetString novamente, e seja o mesmo arquivo, essa funçao será mais rapida do que usada na primeiro vez. Por que? Basicamente, pela segunda etapa. Após aberto o arquivo, ele é mapeado e armazenado na memória. E acessar a memória, é muuuuito mais rapído do que abrir o arquivo, ler, mapear e retornar o resultado. ALGUNS sistemas armazenam mais de um arquivo na memória.
Agora visualize isso no servidor. Digamos que a include está definida pra manter 10 arquivos em "cache". Um player então entra no servidor, no momento que vai carregar os dados dele, o DOF2_GetString vai mapear os dados dele. Até aqui beleza. Mas ai vai entrando players, e no 11° player, alguem vai perder seus dados em "cache". Então quando você for usar alguma função do DOF2 nesse bem dito, o primeiro DOF2, será mais devagar, mas ai ele será remapeado novamente e alocado novamente; e ai um outro cidadão vai perder seu "cache". Deu pra entender né? Agora imagina isso em um servidor RPG que além do sistema de contas tem: sistema de casa, empresa, veículos e etc usando funções do DOF2. Bom, 10 slots de "cache" para 300 casas, 150 empresas, 30 players, e por ai vai, viu a cagada né?
Então meu servidor ficará lento? Negativo. Apesar de ser uma operação muito mais pesada se comparado a acessar um valor em memória, os computadores modernos conseguem normalmente executar isso. Mas terá casos em que isso poderá cair em um código que provavelmente irá travar seu servidor. Por exemplo, criar um timer a casa 100ms fazendo loop em todas as casas realizando varias operações de DOF2. Mesmo que seu servidor não fique travado, vai ter mais uso de CPU, independente do código estar mal feito ou não. Claro que dependendo do grau, pode travar seu servidor, mas não quer dizer que qualquer coisa que você fizer vai travar ele, já que o computador executa essas operações extremamente rápido. Mas para aproveitar bem esses sistemas e você ter performance, você deve saber usar bem esses sistemas, e estarei dando a maior dica sobre isso.
:hearts: Evite operações usando DOF2_...
Tenha em mente uma coisa, o ideal é você realizar a seguinte sequência: Carregar, Manipular, Salvar. Vou usar de exemplo o sistema de contas que é o mais comum. Seguirá o seguinte padrão:
- Quando o player entrar (OnPlayerConnect), carregue os dados dele para variáveis;
- Enquanto ele estiver no servidor, evite usar DOF2_..., manipule somente as variáveis;
- Quando ele desconectar; de tempos em tempos; e em casos importantes, salve os dados dele.
Exemplo de código
- Código:
/*
Gere uma enum ou variáveis para armazenar os dados do jogador. Aconselho usar enum por ser uma
prática melhor e ter um código mais legivel, menos propenso a erros.
*/
enum E_PLAYER_DATA {
PlayerNome[MAX_PLAYER_NAME],
PlayerSenha[20],
PlayerIp[16],
PlayerDinheiro,
PlayerLevel
};
new Player[MAX_PLAYERS][E_PLAYER_DATA];
/*
Carregue os dados dele assim que ele entrar no servidor.
*/
public OnPlayerConnect(playerid) {
GetPlayerName(playerid, Player[playerid][PlayerNome], MAX_PLAYER_NAME);
GetPlayerIp(playerid, Player[playerid][PlayerIp], 16);
format(Player[playerid][PlayerSenha], 20, DOF2_GetString(PlayerFile(playerid), "Senha"));
Player[playerid][PlayerDinheiro] = DOF2_GetInt(PlayerFile(playerid), "Dinheiro");
Player[playerid][PlayerLevel] = DOF2_GetInt(PlayerFile(playerid), "Level");
return 1;
}
/*
Apartir daqui, os dados estão em variáveis. Use-as.
*/
public OnPlayerSpawn(playerid) {
ResetPlayerMoney(playerid);
GivePlayerMoney(playerid, Player[playerid][PlayerDinheiro]);
SetPlayerScore(playerid, Player[playerid][PlayerLevel]);
// Perceba que os dados estão todos armazenados nas variáveis.
return 1;
}
/*
Quando precisar salvar os dados dele na conta, aconselho criar uma função separada para poder
salvar os dados com mais facilidade.
*/
public OnPlayerDisconnect(playerid, reason) {
PlayerSave(playerid);
return 1;
}
PlayerSave(playerid) {
DOF2_SetString(PlayerFile(playerid), "Senha", Player[playerid][PlayerSenha]);
DOF2_SetInt(PlayerFile(playerid), "Dinheiro", Player[playerid][PlayerDinheiro]);
DOF2_SetInt(PlayerFile(playerid), "Level", Player[playerid][PlayerLevel]);
DOF2_SaveFile();
return 1;
}
/*
Essa função é apenas para pegar facilmente o arquivo do jogador.
*/
PlayerFile(playerid) {
new file[40];
format(file, sizeof(file), "Contas/%s.ini");
return file;
}
Isso pode ser usado também em um sistema de casas por exemplo:
- Código:
/*
Gere uma enum ou variáveis para armazenar os dados da casa. Aconselho usar enum por ser uma
prática melhor e ter um código mais legivel, menos propenso a erros.
*/
enum E_CASA_DATA {
CasaDono[MAX_PLAYER_NAME],
CasaID,
CasaValor,
};
new Casa[MAX_CASAS][E_CASA_DATA];
/*
Carregue os dados da casa assim que o servidor ligar.
*/
public OnGameModeInit() {
for(new i = 0; i < MAX_CASAS; ++i) {
if(DOF2_FileExists(CasaFile(i))) {
format(Casa[i][CasaDono], MAX_PLAYER_NAME, DOF2_GetString(CasaFile(i), "Dono"));
Casa[i][CasaID] = i;
Casa[i][CasaValor] = DOF_GetInt(CasaFile(i), "Valor");
}
}
return 1;
}
/*
Apartir daqui, use as variáveis para pegar e manipular os valores da casa e depois basta salva-lá quando
precisar. Aconselho novamente criar uma função de save assim como no sistema de contas.
*/
Enfim, sempre evite usar DOF2_ alguma coisa. Mas não se engane, ela não é uma função que será 100% evitada. Haverá casos em que você deverá usa-la diretamente. Alguns dados não da pra ter um pré carregamento, e vai precisar que você use essa função diretamente, e não tem problema nisso. O objetivo desse tutorial não é matar essa função, e sim, lhe explicar o funcionamento de um sistema assim para você saber o melhor momento para usar e não usar.
:hearts: Última explicação
Essa será bem rápida. Quando você faz alguma alteração no arquivo usando DOF2_Set..., ele re-escreve o arquivo totalmente. Isso é devido ao fato do samp não nos disponibilizar uma função para simplesmente re-escrever uma determinada linha.
:hearts: Notas finais (seguindo o padrão dos meus últimos tutoriais)
:hearts: Última explicação
Essa será bem rápida. Quando você faz alguma alteração no arquivo usando DOF2_Set..., ele re-escreve o arquivo totalmente. Isso é devido ao fato do samp não nos disponibilizar uma função para simplesmente re-escrever uma determinada linha.
:hearts: Notas finais (seguindo o padrão dos meus últimos tutoriais)
♪ Escrevi um pouco correndo o tópico mas estarei revisando algumas vezes, conto com o feedback de vocês.
♪ Foi uma explicação bem simples, mas procurei ser bem objetivo e claro.
♪ Não procurei escrever de forma muito formal e provavelmente haverá erros de escrita, mas os que forem reportados vou corrigir.
♪ Qualquer dúvida estarei respondendo.
♪ Lembre-se que foram códigos de exemplo, e você deve aperfeiçoa-los quando usa-los.
♪ Estarei escrevendo um outro tutorial sobre os SGBD (SQLite e MySQL) com a mesma temática
Creditos: RiqueP