Chat global
Como Eliminar o Lag do Servidor com Particionamento Espacial

Porque é que o Servidor Fica Lento?
Vamos supor que o seu servidor é um segurança magelo numa festa com 500 convidados. A cada segundo, a sua principal tarefa é dizer a cada convidado quem mais ele consegue ver à sua volta.
A abordagem "ingénua" (e que causa lag) é o segurança fazer o seguinte para o Convidado A:
-
Pegar numa lista de todos os 500 convidados.
-
Medir a distância do Convidado A para o Convidado B. Está perto? OK.
-
Medir a distância do Convidado A para o Convidado C. Está perto? OK.
-
...fazer isto 499 vezes.
Depois, ele tem de repetir este processo para o Convidado B, e para o Convidado C, e assim por diante. No final, ele fez 500 * 499 = 249,500
verificações de distância. Se ele demorar muito tempo a fazer isto, a festa inteira "congela" (laga) por um momento. Isto é o que o seu servidor está fazendo agora.
Agora, imagine que o segurança é mais inteligente. Ele divide o salão de festas numa grelha de 10x10 "bairros" imaginários. Ele sabe sempre em que "bairro" cada convidado está.
Quando o Convidado A pergunta "quem está perto de mim?", o segurança faz o seguinte:
-
Vê que o Convidado A está no "Bairro 5".
-
Pega apenas na lista de convidados que estão no Bairro 5 e nos bairros vizinhos (4, 6, etc.). Talvez sejam apenas 20 pessoas.
-
Mede a distância do Convidado A apenas para essas 20 pessoas.
O resultado é o mesmo, mas o trabalho foi drasticamente reduzido. É exatamente isto que vamos implementar no seu servidor. Chega de explicar por metáforas vamos ir para pratica ?
Vamos dar um "Endereço de Bairro" a Cada Jogador? Primeiro, precisamos de uma forma de saber em que "bairro" (que chamaremos de setor) cada jogador está.
-
No Visual Studio, abra o ficheiro de cabeçalho principal do servidor:
SrcServer/onserver.h
. -
Procure pela definição da estrutura "
rsPLAYINFO"
. Esta estrutura é a "ficha de identidade" de cada jogador ligado. -
Dentro da "
struct rsPLAYINFO"
, vamos adicionar duas novas variáveis para guardar o endereço do setor do jogador. Adicione-as perto das outras informações de posição para manter o código organizado.
// Ficheiro: SrcServer/onserver.h typedef struct rsPLAYINFO { // ... muitas outras variáveis ... smCHAR *smChar; smMOTION *smMotion; smPATILLA *smPatilla; // -> INÍCIO DA NOSSA ALTERAÇÃO int sectorX; // Guarda a coordenada X do setor onde o jogador está int sectorY; // Guarda a coordenada Y do setor onde o jogador está // -> FIM DA NOSSA ALTERAÇÃO // ... resto da estrutura ... } rsPLAYINFO;
Agora vamos Atualizar o "Endereço" Sempre que o Jogador se Move
Sempre que um jogador anda, precisamos de recalcular em que setor ele se encontra.
-
Abra o ficheiro de lógica principal do servidor:
SrcServer/OnSever.cpp
. -
Procure pela função
rsPlayServer()
. Dentro dela, encontre ocase
que lida com o movimento do jogador. Na happy ele se chama-seCMD_MOVE
. -
No final deste
case
, depois de o código já ter atualizado a posição(x,y)
do jogador, vamos adicionar o nosso cálculo de setor.
// Ficheiro: SrcServer/OnSever.cpp, dentro de rsPlayServer() case CMD_MOVE: { // ... código original que processa o movimento ... // Move o personagem, atualiza lpPlayInfo->smChar->pX e pY, etc. // -> INÍCIO DA NOSSA ALTERAÇÃO // Define o tamanho de cada setor do nosso "mapa de bairros". // Um valor entre 512 e 2048 costuma ser um bom ponto de partida. // Pense nisto como o tamanho em pixels de cada quadrado da grelha. const int SECTOR_SIZE = 1024; // Calcula o novo setor do jogador com uma simples divisão inteira. lpPlayInfo->sectorX = lpPlayInfo->smChar->pX / SECTOR_SIZE; lpPlayInfo->sectorY = lpPlayInfo->smChar->pY / SECTOR_SIZE; // -> FIM DA NOSSA ALTERAÇÃO } break;
agora vamos usar o Sistema de Setores para Otimizar o game
Agora vem a parte mais gratificante. Vamos pegar numa das funções mais pesadas do servidor, a SendPlayerViews()
, e torná-la centenas de vezes mais rápida.
-
Ainda no ficheiro
SrcServer/OnSever.cpp
, encontre a funçãoSendPlayerViews(rsPLAYINFO *lpPlayInfo)
. -
Substitua o conteúdo inteiro desta função pela nossa nova versão otimizada.
// Ficheiro: SrcServer/OnSever.cpp // VERSÃO ANTIGA E LENTA (NÃO USAR) /* void SendPlayerViews(rsPLAYINFO *lpPlayInfo) { for(int cnt=0; cnt<MAX_PLAYERS; cnt++) { if(rsPlayInfo[cnt].Mode == SMODE_PLAYER) { // ... código que envia pacotes para TODOS os jogadores ... } } } */ // VERSÃO NOVA E OTIMIZADA (USAR ESTA) void SendPlayerViews(rsPLAYINFO *lpPlayInfo) { // Pega no "endereço de bairro" do nosso jogador principal. int playerSectorX = lpPlayInfo->sectorX; int playerSectorY = lpPlayInfo->sectorY; // Continua a percorrer todos os jogadores, mas a verificação será muito mais rápida. for (int i = 0; i < MAX_PLAYERS; i++) { rsPLAYINFO *pOtherPlayer = &rsPlayInfo[i]; // Ignora jogadores que não estão ativos ou se for o próprio jogador. if (pOtherPlayer->Mode != SMODE_PLAYER || pOtherPlayer == lpPlayInfo) continue; // --- AQUI ESTÁ A GRANDE OTIMIZAÇÃO! --- // Em vez de calcular a distância para todos, primeiro fazemos uma verificação de setor // que é extremamente rápida (apenas duas subtrações e comparações). // A função 'abs' calcula a diferença absoluta (sempre positiva). // Só continuamos se o outro jogador estiver no mesmo setor ou num setor vizinho. if (abs(pOtherPlayer->sectorX - playerSectorX) <= 1 && abs(pOtherPlayer->sectorY - playerSectorY) <= 1) { // SÓ PARA OS JOGADORES QUE PASSARAM NA VERIFICAÇÃO RÁPIDA, // é que fazemos o cálculo de distância, que é mais lento. int distance = GetDistance(lpPlayInfo->smChar->pX, lpPlayInfo->smChar->pY, pOtherPlayer->smChar->pX, pOtherPlayer->smChar->pY); // Se a distância for menor que o alcance da visão... if (distance < VIEW_RANGE) // VIEW_RANGE é uma constante que define a sua distância de visão. { // ...então enviamos o pacote para que os jogadores se vejam. rsSendSamePlayer(pOtherPlayer, lpPlayInfo->dwObjectSerial); rsSendSamePlayer(lpPlayInfo, pOtherPlayer->dwObjectSerial); } } } }
Após implementar estas alterações, recompile o seu servidor. A diferença de desempenho será enorme, especialmente com muitos jogadores online. com isso encerramos ... não é um sync mas é uma forma alternativa de eliminar os lags, principalmente em boss, bc etc etc... Agradeça não vai cair o dedo.
Lou como SEMPRE, digo e repito, como SEMPRE inovando e ajudando a comunidade com o conhecimento... Obrigado mais uma vez, ansioso por mais tutoriais diversos.. como disse uma vez, acho justo você manter os tutoriais de coisas basicas e simples (não que essa seja uma, muito pelo contrario).

Obrigado meu mano, a ideia é ajudar a comunidade por mais tóxica que ela sejaPostado por: @datwayLou como SEMPRE, digo e repito, como SEMPRE inovando e ajudando a comunidade com o conhecimento... Obrigado mais uma vez, ansioso por mais tutoriais diversos.. como disse uma vez, acho justo você manter os tutoriais de coisas basicas e simples (não que essa seja uma, muito pelo contrario).

boa lou, to vendo se solto as 2 que falta para complementar essa. muito bom.Postado por: @louPorque é que o Servidor Fica Lento?
Vamos supor que o seu servidor é um segurança magelo numa festa com 500 convidados. A cada segundo, a sua principal tarefa é dizer a cada convidado quem mais ele consegue ver à sua volta.
A abordagem "ingénua" (e que causa lag) é o segurança fazer o seguinte para o Convidado A:
Pegar numa lista de todos os 500 convidados.
Medir a distância do Convidado A para o Convidado B. Está perto? OK.
Medir a distância do Convidado A para o Convidado C. Está perto? OK.
...fazer isto 499 vezes.
Depois, ele tem de repetir este processo para o Convidado B, e para o Convidado C, e assim por diante. No final, ele fez
500 * 499 = 249,500
verificações de distância. Se ele demorar muito tempo a fazer isto, a festa inteira "congela" (laga) por um momento. Isto é o que o seu servidor está fazendo agora.Agora, imagine que o segurança é mais inteligente. Ele divide o salão de festas numa grelha de 10x10 "bairros" imaginários. Ele sabe sempre em que "bairro" cada convidado está.
Quando o Convidado A pergunta "quem está perto de mim?", o segurança faz o seguinte:
Vê que o Convidado A está no "Bairro 5".
Pega apenas na lista de convidados que estão no Bairro 5 e nos bairros vizinhos (4, 6, etc.). Talvez sejam apenas 20 pessoas.
Mede a distância do Convidado A apenas para essas 20 pessoas.
O resultado é o mesmo, mas o trabalho foi drasticamente reduzido. É exatamente isto que vamos implementar no seu servidor. Chega de explicar por metáforas vamos ir para pratica ?
Vamos dar um "Endereço de Bairro" a Cada Jogador? Primeiro, precisamos de uma forma de saber em que "bairro" (que chamaremos de setor) cada jogador está.
No Visual Studio, abra o ficheiro de cabeçalho principal do servidor:
SrcServer/onserver.h
.Procure pela definição da estrutura "
rsPLAYINFO"
. Esta estrutura é a "ficha de identidade" de cada jogador ligado.Dentro da "
struct rsPLAYINFO"
, vamos adicionar duas novas variáveis para guardar o endereço do setor do jogador. Adicione-as perto das outras informações de posição para manter o código organizado.// Ficheiro: SrcServer/onserver.h typedef struct rsPLAYINFO { // ... muitas outras variáveis ... smCHAR *smChar; smMOTION *smMotion; smPATILLA *smPatilla; // -> INÍCIO DA NOSSA ALTERAÇÃO int sectorX; // Guarda a coordenada X do setor onde o jogador está int sectorY; // Guarda a coordenada Y do setor onde o jogador está // -> FIM DA NOSSA ALTERAÇÃO // ... resto da estrutura ... } rsPLAYINFO;
Agora vamos Atualizar o "Endereço" Sempre que o Jogador se Move
Sempre que um jogador anda, precisamos de recalcular em que setor ele se encontra.
Abra o ficheiro de lógica principal do servidor:
SrcServer/OnSever.cpp
.Procure pela função
rsPlayServer()
. Dentro dela, encontre ocase
que lida com o movimento do jogador. Na happy ele se chama-seCMD_MOVE
.No final deste
case
, depois de o código já ter atualizado a posição(x,y)
do jogador, vamos adicionar o nosso cálculo de setor.// Ficheiro: SrcServer/OnSever.cpp, dentro de rsPlayServer() case CMD_MOVE: { // ... código original que processa o movimento ... // Move o personagem, atualiza lpPlayInfo->smChar->pX e pY, etc. // -> INÍCIO DA NOSSA ALTERAÇÃO // Define o tamanho de cada setor do nosso "mapa de bairros". // Um valor entre 512 e 2048 costuma ser um bom ponto de partida. // Pense nisto como o tamanho em pixels de cada quadrado da grelha. const int SECTOR_SIZE = 1024; // Calcula o novo setor do jogador com uma simples divisão inteira. lpPlayInfo->sectorX = lpPlayInfo->smChar->pX / SECTOR_SIZE; lpPlayInfo->sectorY = lpPlayInfo->smChar->pY / SECTOR_SIZE; // -> FIM DA NOSSA ALTERAÇÃO } break;agora vamos usar o Sistema de Setores para Otimizar o game
Agora vem a parte mais gratificante. Vamos pegar numa das funções mais pesadas do servidor, a
SendPlayerViews()
, e torná-la centenas de vezes mais rápida.
Ainda no ficheiro
SrcServer/OnSever.cpp
, encontre a funçãoSendPlayerViews(rsPLAYINFO *lpPlayInfo)
.Substitua o conteúdo inteiro desta função pela nossa nova versão otimizada.
// Ficheiro: SrcServer/OnSever.cpp // VERSÃO ANTIGA E LENTA (NÃO USAR) /* void SendPlayerViews(rsPLAYINFO *lpPlayInfo) { for(int cnt=0; cnt<MAX_PLAYERS; cnt++) { if(rsPlayInfo[cnt].Mode == SMODE_PLAYER) { // ... código que envia pacotes para TODOS os jogadores ... } } } */ // VERSÃO NOVA E OTIMIZADA (USAR ESTA) void SendPlayerViews(rsPLAYINFO *lpPlayInfo) { // Pega no "endereço de bairro" do nosso jogador principal. int playerSectorX = lpPlayInfo->sectorX; int playerSectorY = lpPlayInfo->sectorY; // Continua a percorrer todos os jogadores, mas a verificação será muito mais rápida. for (int i = 0; i < MAX_PLAYERS; i++) { rsPLAYINFO *pOtherPlayer = &rsPlayInfo[i]; // Ignora jogadores que não estão ativos ou se for o próprio jogador. if (pOtherPlayer->Mode != SMODE_PLAYER || pOtherPlayer == lpPlayInfo) continue; // --- AQUI ESTÁ A GRANDE OTIMIZAÇÃO! --- // Em vez de calcular a distância para todos, primeiro fazemos uma verificação de setor // que é extremamente rápida (apenas duas subtrações e comparações). // A função 'abs' calcula a diferença absoluta (sempre positiva). // Só continuamos se o outro jogador estiver no mesmo setor ou num setor vizinho. if (abs(pOtherPlayer->sectorX - playerSectorX) <= 1 && abs(pOtherPlayer->sectorY - playerSectorY) <= 1) { // SÓ PARA OS JOGADORES QUE PASSARAM NA VERIFICAÇÃO RÁPIDA, // é que fazemos o cálculo de distância, que é mais lento. int distance = GetDistance(lpPlayInfo->smChar->pX, lpPlayInfo->smChar->pY, pOtherPlayer->smChar->pX, pOtherPlayer->smChar->pY); // Se a distância for menor que o alcance da visão... if (distance < VIEW_RANGE) // VIEW_RANGE é uma constante que define a sua distância de visão. { // ...então enviamos o pacote para que os jogadores se vejam. rsSendSamePlayer(pOtherPlayer, lpPlayInfo->dwObjectSerial); rsSendSamePlayer(lpPlayInfo, pOtherPlayer->dwObjectSerial); } } } }Após implementar estas alterações, recompile o seu servidor. A diferença de desempenho será enorme, especialmente com muitos jogadores online. com isso encerramos ... não é um sync mas é uma forma alternativa de eliminar os lags, principalmente em boss, bc etc etc... Agradeça não vai cair o dedo.
#DEFINE -> | |
The Witcher

boaa, só quero ver!!Postado por: @alucardboa lou, to vendo se solto as 2 que falta para complementar essa. muito bom.Postado por: @louPorque é que o Servidor Fica Lento?
Vamos supor que o seu servidor é um segurança magelo numa festa com 500 convidados. A cada segundo, a sua principal tarefa é dizer a cada convidado quem mais ele consegue ver à sua volta.
A abordagem "ingénua" (e que causa lag) é o segurança fazer o seguinte para o Convidado A:
Pegar numa lista de todos os 500 convidados.
Medir a distância do Convidado A para o Convidado B. Está perto? OK.
Medir a distância do Convidado A para o Convidado C. Está perto? OK.
...fazer isto 499 vezes.
Depois, ele tem de repetir este processo para o Convidado B, e para o Convidado C, e assim por diante. No final, ele fez
500 * 499 = 249,500
verificações de distância. Se ele demorar muito tempo a fazer isto, a festa inteira "congela" (laga) por um momento. Isto é o que o seu servidor está fazendo agora.Agora, imagine que o segurança é mais inteligente. Ele divide o salão de festas numa grelha de 10x10 "bairros" imaginários. Ele sabe sempre em que "bairro" cada convidado está.
Quando o Convidado A pergunta "quem está perto de mim?", o segurança faz o seguinte:
Vê que o Convidado A está no "Bairro 5".
Pega apenas na lista de convidados que estão no Bairro 5 e nos bairros vizinhos (4, 6, etc.). Talvez sejam apenas 20 pessoas.
Mede a distância do Convidado A apenas para essas 20 pessoas.
O resultado é o mesmo, mas o trabalho foi drasticamente reduzido. É exatamente isto que vamos implementar no seu servidor. Chega de explicar por metáforas vamos ir para pratica ?
Vamos dar um "Endereço de Bairro" a Cada Jogador? Primeiro, precisamos de uma forma de saber em que "bairro" (que chamaremos de setor) cada jogador está.
No Visual Studio, abra o ficheiro de cabeçalho principal do servidor:
SrcServer/onserver.h
.Procure pela definição da estrutura "
rsPLAYINFO"
. Esta estrutura é a "ficha de identidade" de cada jogador ligado.Dentro da "
struct rsPLAYINFO"
, vamos adicionar duas novas variáveis para guardar o endereço do setor do jogador. Adicione-as perto das outras informações de posição para manter o código organizado.// Ficheiro: SrcServer/onserver.h typedef struct rsPLAYINFO { // ... muitas outras variáveis ... smCHAR *smChar; smMOTION *smMotion; smPATILLA *smPatilla; // -> INÍCIO DA NOSSA ALTERAÇÃO int sectorX; // Guarda a coordenada X do setor onde o jogador está int sectorY; // Guarda a coordenada Y do setor onde o jogador está // -> FIM DA NOSSA ALTERAÇÃO // ... resto da estrutura ... } rsPLAYINFO;
Agora vamos Atualizar o "Endereço" Sempre que o Jogador se Move
Sempre que um jogador anda, precisamos de recalcular em que setor ele se encontra.
Abra o ficheiro de lógica principal do servidor:
SrcServer/OnSever.cpp
.Procure pela função
rsPlayServer()
. Dentro dela, encontre ocase
que lida com o movimento do jogador. Na happy ele se chama-seCMD_MOVE
.No final deste
case
, depois de o código já ter atualizado a posição(x,y)
do jogador, vamos adicionar o nosso cálculo de setor.// Ficheiro: SrcServer/OnSever.cpp, dentro de rsPlayServer() case CMD_MOVE: { // ... código original que processa o movimento ... // Move o personagem, atualiza lpPlayInfo->smChar->pX e pY, etc. // -> INÍCIO DA NOSSA ALTERAÇÃO // Define o tamanho de cada setor do nosso "mapa de bairros". // Um valor entre 512 e 2048 costuma ser um bom ponto de partida. // Pense nisto como o tamanho em pixels de cada quadrado da grelha. const int SECTOR_SIZE = 1024; // Calcula o novo setor do jogador com uma simples divisão inteira. lpPlayInfo->sectorX = lpPlayInfo->smChar->pX / SECTOR_SIZE; lpPlayInfo->sectorY = lpPlayInfo->smChar->pY / SECTOR_SIZE; // -> FIM DA NOSSA ALTERAÇÃO } break;agora vamos usar o Sistema de Setores para Otimizar o game
Agora vem a parte mais gratificante. Vamos pegar numa das funções mais pesadas do servidor, a
SendPlayerViews()
, e torná-la centenas de vezes mais rápida.
Ainda no ficheiro
SrcServer/OnSever.cpp
, encontre a funçãoSendPlayerViews(rsPLAYINFO *lpPlayInfo)
.Substitua o conteúdo inteiro desta função pela nossa nova versão otimizada.
// Ficheiro: SrcServer/OnSever.cpp // VERSÃO ANTIGA E LENTA (NÃO USAR) /* void SendPlayerViews(rsPLAYINFO *lpPlayInfo) { for(int cnt=0; cnt<MAX_PLAYERS; cnt++) { if(rsPlayInfo[cnt].Mode == SMODE_PLAYER) { // ... código que envia pacotes para TODOS os jogadores ... } } } */ // VERSÃO NOVA E OTIMIZADA (USAR ESTA) void SendPlayerViews(rsPLAYINFO *lpPlayInfo) { // Pega no "endereço de bairro" do nosso jogador principal. int playerSectorX = lpPlayInfo->sectorX; int playerSectorY = lpPlayInfo->sectorY; // Continua a percorrer todos os jogadores, mas a verificação será muito mais rápida. for (int i = 0; i < MAX_PLAYERS; i++) { rsPLAYINFO *pOtherPlayer = &rsPlayInfo[i]; // Ignora jogadores que não estão ativos ou se for o próprio jogador. if (pOtherPlayer->Mode != SMODE_PLAYER || pOtherPlayer == lpPlayInfo) continue; // --- AQUI ESTÁ A GRANDE OTIMIZAÇÃO! --- // Em vez de calcular a distância para todos, primeiro fazemos uma verificação de setor // que é extremamente rápida (apenas duas subtrações e comparações). // A função 'abs' calcula a diferença absoluta (sempre positiva). // Só continuamos se o outro jogador estiver no mesmo setor ou num setor vizinho. if (abs(pOtherPlayer->sectorX - playerSectorX) <= 1 && abs(pOtherPlayer->sectorY - playerSectorY) <= 1) { // SÓ PARA OS JOGADORES QUE PASSARAM NA VERIFICAÇÃO RÁPIDA, // é que fazemos o cálculo de distância, que é mais lento. int distance = GetDistance(lpPlayInfo->smChar->pX, lpPlayInfo->smChar->pY, pOtherPlayer->smChar->pX, pOtherPlayer->smChar->pY); // Se a distância for menor que o alcance da visão... if (distance < VIEW_RANGE) // VIEW_RANGE é uma constante que define a sua distância de visão. { // ...então enviamos o pacote para que os jogadores se vejam. rsSendSamePlayer(pOtherPlayer, lpPlayInfo->dwObjectSerial); rsSendSamePlayer(lpPlayInfo, pOtherPlayer->dwObjectSerial); } } } }Após implementar estas alterações, recompile o seu servidor. A diferença de desempenho será enorme, especialmente com muitos jogadores online. com isso encerramos ... não é um sync mas é uma forma alternativa de eliminar os lags, principalmente em boss, bc etc etc... Agradeça não vai cair o dedo.

Boa boa, só não encontrei como ref o cmd_move e nem o sendplayerview =(
- 20 Fóruns
- 222 Tópicos
- 1,245 Posts
- 4 Online
- 213 Membros