Networking Entities/pt-br

De Valve Developer Community
Ir para: navegação, pesquisa

O Source permite que até 255 jogadores joguem ao mesmo tempo em um mundo virtual compartilhado e em tempo real. Para sincronizar a entrada do usuário e as mudanças no mundo entre os jogadores, o Source utiliza uma arquitetura cliente-servidor que se comunica por meio de pacotes de rede UDP/IP. O servidor, como autoridade central, processa a entrada do jogador e atualiza o mundo de acordo com as regras do jogo e da física. O servidor frequentemente transmite atualizações do mundo para todos os clientes conectados.

Objetos lógicos e físicos no mundo do jogo são chamados de 'entidades' e são representados no código-fonte como classes derivadas de uma classe base de entidade compartilhada. Alguns objetos existem apenas no servidor (entidades apenas do lado do servidor) e alguns objetos existem apenas no cliente (entidades apenas do lado do cliente), mas a maioria se cruza. O sistema de rede de entidades do motor garante que esses objetos permaneçam sincronizados para todos os jogadores.

O código de rede deve:

  1. Detectar alterações no objeto do lado do servidor.
  2. Serializar (transformar em um fluxo de bits) as alterações.
  3. Enviar o fluxo de bits como um pacote de rede.
  4. Desserializar os dados no cliente e atualizar o objeto correspondente do lado do cliente.
    Note.pngNotar:Se o objeto do lado do cliente não existir, o cliente o criará automaticamente.

Os pacotes de dados não são enviados a cada alteração feita em algum objeto; em vez disso, são feitas instantâneas (geralmente 20/segundo) que contêm todas as mudanças de entidade desde a última atualização. Além disso, nem todas as alterações de entidade são enviadas para todos os clientes o tempo todo: para manter a largura de banda da rede o mais baixa possível, apenas entidades que possam interessar a um cliente (visíveis, audíveis etc.) são atualizadas com frequência.

Um servidor Source pode lidar com até 2048 entidades em rede ao mesmo tempo, cada entidade pode ter 1024 variáveis de membro diferentes que são transmitidas para os clientes, incluindo membros individuais de uma matriz, e cada entidade pode transmitir até 2KB de dados serializados por atualização (por exemplo, 2048 caracteres ASCII).

Um Exemplo

Tudo isso parece bastante complexo e difícil de lidar, mas a maior parte do trabalho é feita nos bastidores pelo motor. Para um autor de modificações, é bastante simples criar uma nova classe de entidade em rede (na verdade, para entidades simples, muitas vezes você não precisa fazer nada).

Para um exemplo no SDK, procure por m_iPing. Isso ilustra a rede de uma matriz, e a variável m_iPing aparece no código apenas algumas vezes, então é fácil encontrar todas as partes.

Este exemplo lhe dá uma ideia de como pode ser fácil - embora as coisas possam se tornar muito complexas uma vez que você começa a otimizar para reduzir os requisitos de largura de banda.

Lado do Servidor

class CMyEntity : public CBaseEntity
{
public:
	DECLARE_CLASS(CMyEntity, CBaseEntity );	// configurar alguns macros
	DECLARE_SERVERCLASS();  // tornar esta entidade em rede
	int UpdateTransmitState()	// sempre enviar para todos os clientes
{
	return SetTransmitState( FL_EDICT_ALWAYS );
}
public:
// variáveis de membro em rede públicas:
CNetworkVar( int, m_nMyInteger ); // inteiro
CNetworkVar( float, m_fMyFloat ); // ponto flutuante
};

//Vincular um nome de entidade global a esta classe (nome usado no Hammer etc.)
LINK_ENTITY_TO_CLASS( myentity, CMyEntity );

// Tabela de dados do servidor descrevendo variáveis de membro em rede (SendProps)
// NÃO crie isso no cabeçalho! Coloque-o no arquivo CPP principal.
IMPLEMENT_SERVERCLASS_ST( CMyEntity, DT_MyEntity )
SendPropInt( SENDINFO( m_nMyInteger ), 8, SPROP_UNSIGNED ),
SendPropFloat( SENDINFO( m_fMyFloat ), 0, SPROP_NOSCALE),
END_SEND_TABLE()

void EmAlgumLugarNoSeuCodigoDoJogo()
{
CreateEntityByName( "myentity" ); // criar um objeto desta classe de entidade
}

Lado do Cliente

class C_MyEntity : public C_BaseEntity
{
public:
	DECLARE_CLASS( C_MyEntity, C_BaseEntity ); // macro de classe de entidade genérica
	DECLARE_CLIENTCLASS(); // esta é uma representação do cliente de uma classe de servidor
public:
// variáveis em rede conforme definido na classe do servidor
int m_nMyInteger;
float m_fMyFloat;
};

//Vincular um nome de entidade global a esta classe (nome usado no Hammer etc.)
LINK_ENTITY_TO_CLASS( myentity, C_MyEntity );

// Vincular tabela de dados DT_MyEntity à classe cliente e mapear variáveis (RecvProps)
// NÃO crie isso no cabeçalho! Coloque-o no arquivo CPP principal.
IMPLEMENT_CLIENTCLASS_DT( C_MyEntity, DT_MyEntity, CMyEntity )
RecvPropInt( RECVINFO( m_nMyInteger ) ),
RecvPropFloat( RECVINFO( m_fMyFloat )),
END_RECV_TABLE()

Entidades de Rede

Há várias etapas para vincular uma entidade no servidor com uma entidade no cliente. A primeira é vincular ambas as classes C++ à mesma "classe Hammer" com LINK_ENTITY_TO_CLASS().

Note.pngNotar:Implementações no lado do servidor de uma entidade devem ser chamadas de CMyEntity, enquanto suas equivalentes do lado do cliente devem ser C_MyEntity. Teoricamente, elas poderiam ser chamadas de qualquer coisa, mas parte do código da Valve assume que você seguiu esta convenção.

Agora você deve informar ao servidor que a classe da entidade deve ser em rede e que existe uma classe cliente correspondente com o macro DECLARE_SERVERCLASS(), que registrará a classe em uma lista global de classes do servidor e reservará um ID de classe exclusivo. Coloque-o na definição da classe (ou seja, arquivo H). Os macros correspondentes IMPLEMENT_SERVERCLASS_ST e END_SEND_TABLE() devem ser colocados na implementação da classe (ou seja, arquivo CPP) um após o outro para registrar a classe do servidor e sua SendTable (estas são cobertas na próxima seção).

Finalmente, você deve fazer o mesmo no cliente, desta vez com DECLARE_CLIENTCLASS() no arquivo H e IMPLEMENT_CLIENTCLASS_DT e END_RECV_TABLE().

Quando um cliente se conecta a um servidor, eles trocam uma lista de classes conhecidas e se o cliente não implementa todas as classes do servidor, a conexão é interrompida com uma mensagem "Cliente ausente da classe DT <qualquer>".

Variáveis de Rede

As classes de entidade têm variáveis de membro como qualquer outra classe. Algumas dessas variáveis de membro podem ser apenas do lado do servidor, o que significa que elas não são replicadas nos clientes. Mais interessantes são as variáveis de membro que precisam ser replicadas para a cópia do cliente desta entidade. As variáveis de rede são propriedades essenciais da entidade, como posição, ângulo ou saúde. Tudo o que é necessário para exibir uma entidade em seu estado atual no cliente deve ser em rede.

Sempre que uma variável de membro em rede muda, o motor Source deve saber disso para incluir uma mensagem de atualização para esta entidade no próximo snapshot transmitido. Para sinalizar uma mudança de uma variável de rede, a função NetworkStateChanged() desta entidade deve ser chamada para definir a bandeira interna FL_EDICT_CHANGED. Uma vez que o motor tenha enviado a próxima atualização, esta bandeira será apagada novamente. Agora, não seria muito conveniente chamar NetworkStateChanged() toda vez que você muda uma variável de membro, portanto, as variáveis de rede usam um macro auxiliar especial CNetworkVar que substituirá o tipo de variável original (int, float, bool etc) por um tipo modificado que sinaliza automaticamente uma mudança de estado para sua entidade pai. Existem macros especiais para a classe Vector e QAngle, bem como para matrizes e EHANDLES. O uso prático desses CNetworkVars não muda, então você pode usá-los como os tipos de dados originais (exceto matrizes em rede, você deve usar Set() ou GetForModify() ao alterar elementos). O exemplo a seguir mostra como os diferentes macros CNetwork* são usados ao definir variáveis de membro (os comentários mostram a versão não em rede):

CNetworkVar( int, m_iMyInt ); // int m_iMyInt; CNetworkVar( float, m_fMyFloat ); // float m_fMyFloat; CNetworkVector( m_vecMyVector ); // Vector m_vecMyVector; CNetworkQAngle( m_angMyAngle ); // QAngle m_angMyAngle; CNetworkArray( int, m_iMyArray, 64 ); // int m_iMyArray[64]; CNetworkHandle( CBaseEntity, m_hMyEntity ); // EHANDLE CNetworkString( m_szMyString ); // const char *m_szMyString;


Warning.pngAtenção:Um CNetworkArray não pode ser atribuído com a sintaxe normal. Use Set(slot,value) em vez disso. Além disso, para compatibilidade futura, considere usar Get(slot) ao retornar.

Tabelas de Dados em Rede

Quando uma entidade sinaliza uma alteração e o motor está construindo a atualização do instantâneo, ele precisa saber como converter um valor de variável em um fluxo de bits. Claro, ele poderia simplesmente transferir a pegada de memória da variável de membro, mas isso seria muito dados na maioria dos casos e não muito eficiente em termos de uso de largura de banda. Portanto, cada classe de entidade mantém uma tabela de dados que descreve como codificar cada uma de suas variáveis de membro. Essas tabelas são chamadas de Tabelas de Envio e devem ter um nome único, geralmente como DT_NomeDaClasseEntidade.

As entradas desta tabela são SendProps, objetos que mantêm a descrição de codificação para uma variável de membro. O motor Source fornece vários codificadores de dados diferentes para os tipos de dados comumente usados, como inteiro, float, vetor e string de texto. SendProps também armazenam informações sobre quantos bits devem ser usados, valores mínimo e máximo, flags de codificação especiais e funções de proxy de envio (explicadas posteriormente).

Normalmente, você não cria e preenche SendProps por conta própria, mas sim usa uma das funções auxiliares SendProp* ( SendPropInt(), SendPropFloat(), etc). Essas funções ajudam a configurar todas as propriedades de codificação importantes em uma única linha. O macro SENDINFO ajuda a calcular o tamanho da variável de membro e o deslocamento relativo para o endereço da entidade. Aqui está um exemplo de uma SendTable para as variáveis de rede definidas anteriormente.

IMPLEMENT_SERVERCLASS_ST(CMyClass, DT_MyClass) SendPropInt( SENDINFO(m_iMyInt), 4, SPROP_UNSIGNED ), SendPropFloat( SENDINFO(m_fMyFloat), -1, SPROP_COORD), SendPropVector( SENDINFO(m_vecMyVector), -1, SPROP_COORD ), SendPropQAngles( SENDINFO(m_angMyAngle), 13, SPROP_CHANGES_OFTEN ), SendPropArray3( SENDINFO_ARRAY3(m_iMyArray), SendPropInt( SENDINFO_ARRAY(m_iMyArray), 10, SPROP_UNSIGNED ) ), SendPropEHandle( SENDINFO(m_hMyEntity)), SendPropString( SENDINFO(m_szMyString) ), END_SEND_TABLE()

O macro IMPLEMENT_SERVERCLASS_ST vincula automaticamente a Tabela de Envio da classe base à entidade da qual é derivada, portanto, todas as propriedades herdadas já estão incluídas.

Tip.pngDica:Se por algum motivo você não quiser incluir as propriedades da classe base, use IMPLEMENT_SERVERCLASS_ST_NOBASE(). Caso contrário, propriedades individuais da classe base podem ser excluídas usando SendPropExclude(). Em vez de adicionar um novo SendProp, ele remove um existente de uma Tabela de Envio herdada.

O primeiro lugar para começar a otimizar o tamanho do fluxo de bits é, é claro, o número de bits que devem ser usados para a transmissão (-1=padrão). Quando você sabe que um valor inteiro só pode ser um número entre 0 e 15, você só precisa de 4 bits em vez de 32 (defina também a flag SPROP_UNSIGNED). Outras otimizações podem ser alcançadas usando as flags apropriadas de SendProps:

SPROP_UNSIGNED
Codifica um inteiro como um inteiro sem sinal, não envia um bit de sinal.
SPROP_COORD
Codifica os componentes float ou Vector como uma coordenada mundial. Os dados são comprimidos, um 0.0 só precisa de 2 bits e outros valores podem usar até 21 bits.
SPROP_NOSCALE
Escreva os componentes float ou Vector como valor completo de 32 bits para garantir que não ocorra perda de dados devido à compressão.
SPROP_ROUNDDOWN
Limita o valor float alto ao intervalo menos uma unidade de bit.
SPROP_ROUNDUP
Limita o valor float baixo ao intervalo menos uma unidade de bit.
SPROP_NORMAL
O valor float é um normal no intervalo entre -1 e +1, usa 12 bits para codificar.
SPROP_EXCLUDE
Exclui novamente um SendProp que foi adicionado por uma Tabela de Envio da classe base.
Note.pngNotar:Não defina esta flag manualmente; use SendPropExclude() em vez disso.
SPROP_CHANGES_OFTEN
Algumas propriedades mudam com muita frequência, como posição do jogador e ângulo de visão (quase em cada instantâneo). Adicione esta flag para SendProps que mudam frequentemente, para que o motor possa otimizar os índices das Tabelas de Envio para reduzir a sobrecarga de rede.

No lado do cliente, você deve declarar uma Tabela de Recebimento semelhante à Tabela de Envio, para que o cliente saiba onde armazenar as propriedades da entidade transmitidas. Se os nomes das variáveis permanecerem os mesmos na classe do lado do cliente, as Tabelas de Recebimento são apenas uma lista simples de propriedades recebidas (a ordem das propriedades não precisa coincidir com a ordem da Tabela de Envio). O macro IMPLEMENT_CLIENTCLASS_DT é usado para definir a Tabela de Recebimento e também vincula o cliente às classes do servidor e seus nomes de Tabela de Envio.

IMPLEMENT_CLIENTCLASS_DT(C_MyClass, DT_MyClass, CMyClass )
	RecvPropInt( RECVINFO ( m_iMyInt ) ),
	RecvPropFloat( RECVINFO ( m_fMyFloat ) ),
	RecvPropVector( RECVINFO ( m_vecMyVector ) ),		
	RecvPropQAngles( RECVINFO ( m_angMyAngle ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_iMyArray), RecvPropInt( RECVINFO(m_iMyArray [0]))),
	RecvPropEHandle( RECVINFO (m_hMyEntity) ),
	RecvPropString( RECVINFO (m_szMyString) ),
END_RECV_TABLE()

The offset relativo e o tamanho de uma variável de membro são calculados pelo macro RECVINFO. Se o nome da variável no servidor e no cliente for diferente, deve-se usar RECVINFO_NAME.

Filtros de Transmissão

Note.pngNotar:Se uma entidade transmitir, forçará todos os seus pais a transmitirem também.

Transmitir todas as atualizações da entidade para todos os clientes seria um desperdício desnecessário de largura de banda, já que um jogador geralmente vê apenas um pequeno subconjunto do mundo. Em geral, um servidor precisa atualizar apenas as entidades que estão nas proximidades locais de um jogador, mas também há casos em que uma entidade só interessa aos jogadores de um determinado time ou de uma determinada classe de combate.

Áreas ou salas visíveis a partir da posição de um jogador são chamadas de Conjunto de Visibilidade Potencial. O PVS de um jogador geralmente é usado para filtrar entidades antes de transmiti-las para o cliente, mas regras de filtro mais complexas também podem ser definidas nas funções virtuais UpdateTransmitState() e ShouldTransmit() de uma entidade.

UpdateTransmitState()

Uma entidade define seu estado global de transmissão em UpdateTransmitState(), onde pode escolher um dos seguintes estados:

FL_EDICT_ALWAYS
Sempre transmitir.
FL_EDICT_DONTSEND
Nunca transmitir.
FL_EDICT_PVSCHECK
Fazer o motor verificar o PVS (isso também poderia ser feito manualmente).
FL_EDICT_FULLCHECK
Chamar ShouldTransmit() para decidir se deve ou não transmitir. Isso gera muitas chamadas de função extras, então use apenas quando necessário.

Se uma entidade alterar seu estado, de modo que o estado de transmissão também seja alterado (por exemplo, se tornar invisível etc.), UpdateTransmitState() será chamado.

ShouldTransmit()

Algumas entidades têm regras de transmissão complexas e o impacto de desempenho ao chamar ShouldTransmit() é inevitável. Uma implementação derivada de CBaseEntity::ShouldTransmit(const CCheckTransmitInfo *pInfo) deve retornar uma das seguintes flags de transmissão:

FL_EDICT_ALWAYS
Transmitir desta vez.
FL_EDICT_DONTSEND
Não transmitir desta vez.
FL_EDICT_PVSCHECK
Transmitir desta vez se a entidade estiver dentro do PVS.

A estrutura de argumentos passada para CCheckTransmitInfo fornece informações sobre o cliente receptor, seu PVS atual e quais outras entidades já estão marcadas para transmissão.

Proxies de Envio e Recebimento

Os proxis de envio e recebimento são funções de retorno de chamada implementadas em Send/ReceiveProps. Eles são executados sempre que a propriedade é transmitida e são comumente usados para comprimir um valor para transmissão (nesse caso, um é necessário em cada extremidade), para enviar um valor para muitos locais ou simplesmente para detectar quando uma variável em rede muda.

Warning.pngAtenção:Não execute lógica de entidade dos proxis sob nenhuma circunstância. Eles podem ser executados em momentos inesperados (por exemplo, durante a validação da previsão) ou não serem executados de todo (por exemplo, em atualizações completas). Use PostDataUpdate() em vez disso.

Proxis em tabelas de dados

Você pode filtrar quais jogadores recebem uma tabela de dados incluindo-a em uma tabela pai usando SendPropDataTable(), e depois aplicando um SendProxy.

Neste caso, substitua DVariant* pOut da lista de argumentos do proxy de envio abaixo por CSendProxyRecipients* pRecipients, um objeto que contém uma lista de clientes que receberão a tabela. Ele assume o índice do cliente(s), com o primeiro jogador sendo 0.

Já existem dois proxis configurados para o cenário mais comum de enviar dados de alta precisão para o jogador local para uso na previsão:

  • SendProxy_SendLocalDataTable
  • SendProxy_SendNonLocalDataTable

Argumentos

SendProxy (Server) RecvProxy (Client)
SendProp* pProp
O SendProp que está utilizando este proxy.
void* pStructBase / pStruct
A entidade que possui o SendProp que está utilizando esse proxy. Faça uma conversão de tipo conforme necessário.
void* pData
The raw data that is to be manipulated. Again, cast as required.
DVariant* pOut
Object that receives the output data to be sent to clients. Assign using one of its function.
int iElement
The array element index, or 0 if this isn't an array.
int objectID
The entity index of the object being referred to.
CRecvProxyData* pData
An object containing data from the SendProp:
  • RecvProp* m_pRecvProp
  • DVariant* m_Value
  • int m_iElement
  • int m_ObjectID
See the left column for details on what these are.
void* pStruct
The client-side entity that is currently being dealt with. Cast it to fit your requirements.
void* pOut
The value that will be applied to the client entity when the proxy has finished running. Cast it to the type you require, or alternatively cast values you assign to it to void*.

Um exemplo

Este exemplo remove os dois bits inferiores de um inteiro, o que economiza largura de banda, mas causa perda de precisão.

void SendProxy_MyProxy(const SendProp* pProp, const void* pStruct,
	const void* pData, DVariant* pOut, int iElement, int objectID)
{
	// obter valor bruto
	int value = *(int*)pData;

	// preparar valor para transmissão, perderá precisão
	*((unsigned int*)&pOut->m_Int) = value >> 2;
}

IMPLEMENT_SERVERCLASS_ST(CMyClass, DT_MyClass)
	SendPropInt(SENDINFO(m_iMyInt), 4, SPROP_UNSIGNED, SendProxy_MyProxy),
END_SEND_TABLE()


IMPLEMENT_SERVERCLASS_ST(CMyClass, DT_MyClass)
	SendPropInt( SENDINFO(m_iMyInt ), 4, SPROP_UNSIGNED, SendProxy_MyProxy ),
END_SEND_TABLE()
void RecvProxy_MyProxy(const CRecvProxyData* pData, void* pStruct, void* pOut)
{
	// obter o valor transmitido
	int value = *((unsigned long*)&pData->m_Value.m_Int);

	// restaurar valor para a magnitude original
	*((unsigned long*)pOut) = value << 2;
}

IMPLEMENT_CLIENTCLASS_DT(C_MyClass, DT_MyClass, CMyClass)
	RecvPropInt(RECVINFO(m_iMyInt), 0, RecvProxy_MyProxy),
END_RECV_TABLE()

Este exemplo converte um valor de matiz recebido em duas variáveis de cor separadas na entidade anexada à propriedade.

Tip.pngDica: Como a função de proxy não faz parte da classe de entidade de destino, ela pode ser incapaz de acessar membros privados ou protegidos. Declare-a na classe com a palavra-chave friend, por exemplo, friend void RecyProxy_MyProxy(args), para conceder permissão.
void RecvProxy_PlayerHue( const CRecvProxyData* pData, void* pStruct, void* pOut )
{
	HSVtoRGB( Vector(pData->m_Value.m_Int,.9,.6), static_cast<C_DeathmatchPlayer*>(pStruct)->m_vPlayerColour_Dark );
	HSVtoRGB( Vector(pData->m_Value.m_Int,.4,.9), static_cast<C_DeathmatchPlayer*>(pStruct)->m_vPlayerColour_Light );
}

IMPLEMENT_CLIENTCLASS_DT(C_DeathmatchPlayer, DT_DeathmatchPlayer, CDeathmatchPlayer )
	RecvPropInt( RECVINFO(m_PlayerHue),0, RecvProxy_PlayerHue ),
END_NETWORK_TABLE()

Otimizando a Banda Larga

Quando a variável na rede e os data tables estarem prontos e todas as entidades estarem funcionando como desejado, agora a parte divertida do código vem: Otimização. A source engine tem varias ferramentas para analisar e ver o trafico da rede. Sua meta com as otimizações é poder diminuir a o trafego e os seus maiores pontos( singular, extra mente grande packets)

Netgraph

Uma ferramenta muito conhecida é o Netgraph, que pode ser ativado executando net_graph 2 no console de desenvolver. O Netgraph mostra os dados de rede mais importantes de forma compacta e em tempo real. Cada pacote recebido é exibido como uma linha codificada por cores viajando da direita para a esquerda, onde a altura da linha representa o tamanho do pacote (NÃO é o ping). As cores das linhas representam diferentes grupos de dados, conforme mostrado neste diagrama:

net_graph
fps
A quantidade de fps que a tela esta sendo atualizada
ping
Também conhecido como a latencia. Os packets viajem do server ao client in ms.
in
Tamanho dos ultimos packets em bytes, normalmente vem kbs/segundos e ( no lado direito), o valor do cl_updaterate
Size of last received packet in bytes, average incoming kB/second and (on the far right), the value of cl_updaterate acima da média real de pacotes/segundo recebidos.
out
Tamanho do último pacote enviado, média de kB/segundo de saída e (na extrema direita) média de pacotes/segundo enviados acima do valor de cl_cmdrate.
lerp
A maior quantidade de latência que o cliente está configurado para compensar. O padrão geralmente é 100ms.

A posição do Netgraph na tela pode ser alterada com as convars net_graphheight pixels e net_graphpos 1|2|3.

Tip.pngDica:Use net_graphshowlatency para exibir a latência no netgraph.

cl_entityreport

Outra ferramenta visual para mostrar dados de rede de entidades em tempo real é cl_entityreport startindex. Ao habilitar esta variável do console, será mostrada uma célula para cada entidade da rede, contendo o índice da entidade, o nome da classe e um indicador de tráfego. Dependendo da resolução da sua tela, centenas de entidades e suas atividades podem ser exibidas ao mesmo tempo. O indicador de tráfego é uma pequena barra que mostra os bits transferidos que chegaram com o último pacote. A linha vermelha acima da barra mostra picos recentes. O texto da entidade é codificado por cores, representando o status de transmissão atual:

cl_entityreport

nenhum
Índice de entidade nunca usado/transmitido.
piscando
Mudança de estado do PVS da entidade.
verde
Entidade no PVS, mas não atualizada recentemente.
azul
Entidade no PVS gerando tráfego contínuo.
vermelho
A entidade ainda existe fora do PVS, mas não é mais atualizada.

dtwatchent

Depois de identificar uma única entidade que gera tráfego ou picos constantemente, você pode observar essa entidade um pouco mais de perto usando a ferramenta de console dtwatchententityindex. Isto irá gerar uma saída de console com cada atualização recebida mostrando todas as variáveis ​​de membro alteradas. Para cada propriedade alterada são listados o nome da variável, tipo, índice SendTable, bits usados ​​para transmissão e novo valor. Aqui está um exemplo de saída para a entidade do player local dtwatchent 1:

entidade delta: 1
+ m_flSimulationTime, DPT_Int, índice 0, bits 8, valor 17
+ m_vecOrigin, DPT_Vector, índice 1, bits 52, valor (171.156,-83.656,0.063)
+ m_nTickBase, DPT_Int, índice 7, bits 32, valor 5018
+ m_vecVelocity[0], DPT_Float, índice 8, bits 20, valor 11,865
+ m_vecVelocity[1], DPT_Float, índice 9, bits 20, valor -50,936
= 146 bits (19 bytes)

DTI

Uma análise ainda mais profunda de todas as classes de entidades e do uso médio de largura de banda de suas propriedades é possível usando o Data Table Instrumentation (DTI). Para ativar o DTI, o mecanismo de origem (cliente) deve ser iniciado com um parâmetro de linha de comando especial -dti, por exemplo, hl2.exe -game mymod -dti. Em seguida, o DTI é executado automaticamente em segundo plano, coletando dados sobre todas as atividades de transmissão. Para coletar uma boa amostra de dados, basta conectar-se a um servidor de jogo do seu mod e jogar por alguns minutos. Em seguida, saia do jogo ou execute o comando do console dti_flush e o mecanismo gravará todos os dados coletados em arquivos no diretório do seu mod. Os arquivos criados contêm os dados em formato de texto separado por tabulação, que pode ser importado por ferramentas de processamento de dados como o MS Excel para análise posterior. O arquivo mais interessante aqui é dti_client.txt onde cada registro de dados contém os seguintes campos:

  • Classe de entidade
  • Nome da propriedade
  • Contagem de decodificação
  • Total de bits
  • Média de Bits
  • Total de bits de índice
  • Bits de índice médio

Uma boa maneira de pesquisar as propriedades de entidade mais caras é classificar os dados por "Total de bits" ou "Contagem de decodificação".

Tabelas de classes incompatíveis

Para funcionar, o servidor e o cliente devem utilizar as mesmas tabelas de classes, para que cada um saiba como enviar e receber dados.

Se um servidor for atualizado com uma alteração na tabela de classes, qualquer cliente conectado receberá uma mensagem de erro "O servidor usa tabelas de classes diferentes" e será descartado imediatamente. Atualmente não se sabe se existe alguma maneira de dizer ao núcleo HL2.exe para exibir alguma outra mensagem ou de alguma forma tornar a atualização do servidor mais fácil para um jogador novato.

Outras referencias

Source Multiplayer Networking