nvs_3_svc_versioning.txt

来自「quake1 dos源代码最新版本」· 文本 代码 · 共 312 行

TXT
312
字号
Network Versioning System
=========================

Part III - SVC Versioning (the hardest part)

SVC Versioning
--------------
The engine should keep the compatibiltity between a NVS server and non-NVS
clients, hence it must check the to-be-send-messages from the engine and QuakeC.
It would be too complicated for QC coders to handle each and every client (=no
more broadcasts) and they already have to maintain server compatibility,
otherwise this would result in PROGS.DAT which set the required and recommended
NVS version to the same value. Not the intention for this system :(
Hence the engine has to track the messages and hold back some parts
corresponding to the clients CSVC version, especially broadcasts to all clients
seem like a lot work (many different CSVC versions).


There are four different types of SVC messages:
1. MSG_INIT (reliable)
   stored in sv.signon for all clients
2. MSG_ONE (reliable)
   stored in client.message separately for each client
4. MSG_ALL (reliable)
   stored in sv.reliable_datagram for all clients
   The reliable datagram is copied to all client messages in
   SV_UpdateToReliableMessages() of sv_main.c which is called by
   SV_SendClientMessages()
3. MSG_BROADCAST (unreliable)
   stored in sv.datagram for all clients
   The unreliable datagram is copied to a message buffer in
   SV_SendClientDatagram() of sv_main.c which is called by
   SV_SendClientMessages()

All real sent messages are reliable in WinQuake.
The real sent message is build by appending the reliable MSG_ALL message to the
MSG_ONE buffer. If there's not enough space then the server stops.
The unreliable MSG_BROADCAST message is also appended to the MSG_ONE buffer if
there's still enough place available, if not the BROADCAST data is dropped
(hence unreliable).


There are several Write Functions:
1. MSG_WriteChar()
2. MSG_WriteByte()
3. MSG_WriteShort()
4. MSG_WriteLong()
5. MSG_WriteFloat()
   calls SZ_Write()
6. MSG_WriteString()
   calls SZ_Write()
7. MSG_WriteCoord()
   calls MSG_WriteShort()
8. MSG_WriteAngle()
   calls MSG_WriteByte

These functions are defined in COMMON.C and use SZ_GetSpace() and SZ_Write().
They are used by clients (CLC messages which are parsed by the server in
SV_ReadClientMessage() of SV_USER.C), by servers (SVC messages which are parsed
by the clients in CL_ParseServerMessage() of CL_PARSE.C) and for net messages.


The solution should:
1. only need new code where enhanced/new messages are called/send
2. make it easy to add new changes to the system
   (the change itself + only a few additional lines for the system)
3. only changes are added to the code and/or data
   (avoid redundancies for unchanged messages between several NVS versions)
4. be the same system for the engine and QuakeC



The Concept
-----------
A.) Handling the different SVC versions
    The different versions of the SVC messages are kept in lookup tables. Only
    data for modified SVC messages are stored there. If no data is available for
    a SVC message it is assumed unmodified.

    There can also be lookup tables for SVC messages with different sub
    messages, like SVC_TEMP_ENTITY with TE_EXPLOSION etc. These tables can
    easily be selected with simple if-clauses.

    Each line of a lookup table stands for a SVC message or a SVC sub message,
    and points to a version table for this message type.
    The version table stores the definition for all the different NVS versions.
    Only when a SVC message is changed a new line is added. Hence the tables
    should keep very small even when a lot of NVS versions are released.
    It stores the NVS version number since the definition is valid from, the
    number of write commands the message consists of in this version and how
    many bytes the message needs in the buffer for this version. Last but not
    least it points to a boolean conversion array which tells which write
    command is used or ignored for this version.

    Structure of a lookup table:
    typedef struct msg_lookup_s
    {
    	int			cmd;		// SVC byte
    	struct msg_version_s	*version_tab;	// pointer to version table
    } msg_lookup_t;

    Structure of a version table:
    typedef struct msg_version_s
    {
    	float		nvs_version;		// valid from NVS version
    	int		numwrites;		// number of write command
    	int		numbytes;		// message size in bytes for MSG_BROADCAST
    	qboolean	*conversion_tab;	// pointer to conversion table
    } msg_version_t;

    You can see examples of the implementation in the test cases.


B.) Accessing the tables for preparing a message conversion
    To access the tables and convert a SVC message you need to know the CSVC of
    the corresponding client, the server's SSVC and which SVC message is going
    to be send. As the CSVC is always the same or less than the SSVC you do not
    need to convert a message when SSVC is zero.

    Step 1:
    First you determine which lookup table you have to use for this message,
    this is a simple switch/case situation where you check for SVC messages with
    sub messages first and default back to the general SVC lookup table if none
    is found.
    Note that you have to search for the SVC sub message byte in lookup tables
    for SVC messages with sub messages, you only search for the SVC message byte
    in the general SVC lookup table.
    Now you know where to look for the corresponding version table.

    Step 2:
    First we search for the corresponding version line of the server's SSVC.
    Just loop through the table until the version of the table line is the same
    or less than the SSVC. You can not loop out of the table as the last line
    has always version 0.00, which is the lowest possible version. Store a
    pointer to this table line for later reference.

    Step 3:
    At last we need to find the corresponding version for the client's CSVC.
    Just loop through the table again in the exact same manner, but start at the
    table line you found for the server's SSVC. Store a pointer to the found
    conversion table.

    Now you have all the data at your hand you need to know, this is...
    ... how many writes are going to be handled by the server for this message and SSVC
    ... how many writes are important for the client
    ... how many bytes will be send to the client
    ... which writes are send to the client (conversion table = true) or ignored (conversion table = false)

C.) Engine server implementation
    Before sending a supported NVS enhanced SVC message the server calls the
    function NVS_InitSVCMsg() with the SVC and possible sub message byte. This
    function will process all connected clients accordingly to B during runtime.

    The engine then uses NVS_Write functions to convert and send the SVC data.
    These functions will make use of the original Write functions, but they
    check first if the write command for this client or even the client itself
    should be ignored.

    First change the existing code to use the NVS write functions and add a
    NVS_InitSVCMsg() before. Then add the new NVS writes after the existing
    code, remember that you always add new writes behind the existing ones, not
    in between.

    There's also the option to write the enhanced data to a separate buffer, not
    to a client buffer, but this only works and is reasonable for MSG_ONE.

    As the unreliable server datagram is copied for each client before sending
    the message, there is no problem to copy the unreliable datagram of the
    client structure too.
    The unreliable server datagram is size-checked within the engine to avoid
    buffer overflows through sounds and/or particles (SV_MAIN.C:
    SV_StartParticle(), SV_StartSound()). PROGS.DAT/QuakeC writes
    (MSG_BROADCAST) are not size-checked. By using NVS_InitSVCMsg() engine and
    PROGS.DAT/QuakeC writes are size-checked. It's now even possible to send an
    unreliable message to only one client by using MSG_BROADCAST and stating the
    client in NVS_InitSVCMsg(). This also works for non-enhanced messages but
    only in the engine itself.


D.) MSG_INIT -> sv.signon (reliable)
    Can not rebuild sv.signon for every client as it is created when server is
    spawned => no client connected.
    SV_SendServerinfo() is done before client sends his NVS version, signon #0 (to #1)
    sv.signon is then copied to client's message in prespawn, signon #1 (to 2)

    Warning!!! All SignOn messages must currently remain unmodified:
    - svc_print (SV_SendServerinfo() of SV_MAIN.C)
    - svc_serverinfo (SV_SendServerinfo() of SV_MAIN.C)
    - svc_cdtrack (SV_SendServerinfo() of SV_MAIN.C)
    - svc_setview (SV_SendServerinfo() of SV_MAIN.C)
    - svc_signonnum (SV_SendServerinfo() of SV_MAIN.C)
    - svc_spawnstatic (PF_makestatic() of PR_CMDS.C)
    - svc_spawnstaticsound (PF_ambientsound() of PR_CMDS.C)
    - svc_spawnbaseline (SV_CreateBaseline() of SV_MAIN.C called from SV_SpawnServer())

    The client can check for SignOn with cls.signon < 2 and should await
    unmodified messages then. This is the easiest way to accomplish this by
    adding a signon check to all messages in CL_PARSE.C/CL_TENT.C.

    A way to get unmodified versions of enhanced messages for sv.signon/MSG_INIT
    is to define SV.NVS_MSGSIGNON, which always points to the version table line
    of v0.00 and is checked in NVS Write functions for MSG_INIT. So it is
    possible to create unmodified messages from enhanced messages, this is only
    useful for PROGS.DAT/QuakeC generated signon messages, as the engine should
    handle this correctly.


E.) PROGS.DAT/QuakeC Implementation
    Depending on world.nvs_svc the PROGS.DAT just calls NVS_InitSVCMsg() from
    QuakeC and uses the normal QuakeC Write functions. The engine will check
    which internal functions to use: normal or NVS Write functions.
    This can be done with hard-coded IF statements as this is totally static.


F.) Engine client implementation
    Depending on the value of NVS_CSVC_VERSION and cls.signon the client reads
    the corresponding data.
    This can be done with hard-coded IF statements as this is totally static.



Tests
-----
1. Precise Angles (svc_setangle, server -> single client)
   Use floats instead of angles (bytes) to increase precision. Three WriteFloats
   are added to the message and the previous WriteAngles are ignored for newer
   clients. This means the old bytes are "replaced" by the new floats.
   I choosed to enhance SVC_SETANGLE being more precise as it also is a special
   case in SV_MAIN.C

   Otherwise there's no use for it, just an example
   Files:
   CL_PARSE.C (CL_ParseServerMessage())
   HOST_CMD.C (MSG_ONE in Host_Spawn_f())
   SV_MAIN.C (MSG_ONE with separate buffer in SV_WriteClientdataToMessage())
   NVS_DATA.C (NVS versioning data)
   search for NVS SVC_SETANGLE


2. MSG_BROADCAST/SV.DATAGRAM size check (svc_particle)
   Files:
   SV_MAIN.C (SV_StartParticle())
   NVS_DATA.C (NVS versioning data)
   search for NVS SVC_PARTICLE


3. "colored dynamic lights" (QuakeC broadcast) a QSG Tutorial from SaRcaZm
   a) non-NVS PROGS.DAT with NVS and non-NVS clients
      -> NVS clients do not expect colour bytes (check nvs_csvc_version.value)
   b) NVS PROGS.DAT with NVS and non-NVS clients
      -> NVS clients expect colour bytes (check nvs_csvc_version.value)
      -> NVS server does not sent colour bytes to non-NVS clients (check host_client.nvs_csvc)

   Files:
   CL_TENT.C (CL_ParseTEnt())
   NVS_DATA.C (NVS versioning data)
   CLIENT.H (dlight structure)
   GL_RLIGHT.C (for GL_FLASHBLEND 1)
   GL_RSURF.C (for GL_FLASHBLEND 0 with paramter -lm_4)
   PROGS\OGRE.QC
   PROGS\SHALRATH.QC
   PROGS\WEAPONS.QC
   search for NVS SVC_TE TE_EXPLOSION and SaRcaZm


Known Problems
--------------
Client's versions not initialized on level change (or kill in SinglePlayer)
  seems to be of no effect as signon is still done (=NVS version send by client)
  a new client should be new a connect to the engine, hence should get initialized
Message Order (id problem)
  As the real sent message is build through copying different buffers, which are
  build separately, the data in the resulting message is not chronological
  correct between the different types (MSG_ONE, MSG_ALL, MSG_BROADCAST).
  A concatened list with buffer, message size and type should avoid this.
Why did id checked for 16 bytes in SV_StartParticle(), SV_StartSound() instead of 12?


File Changes
------------
Completly redone SVC versioning (search for NVS SVC)

* 2000-05-01:
HOST.C
  added initialization of connected clients' datagram message buffer in Host_ServerFrame() (both routines)
PR_CMDS.C
  moved MSG definitions to NVS.H
  added if-clause for enhanced messages in PF_Write functions for PROGS.DAT/QuakeC
  added PF_NVS_InitSVCMsg() into table for PROGS.DAT/QuakeC functions #79
NVS.C
  added functions NVS_InitSVCMsg(), PF_NVS_InitSVCMsg(), NVS_CheckClient(), NVS_CheckCounter() and NVS Write functions
NVS.H
  added declarations of functions and lookup table
  moved MSG definitions from PR_CMDS.C
NVS_DATA.C
  added file
  added empty lookup table for enhanced SVC messages
SERVER.H
  added structure definition for lookup and version table
  added NVS_MSGSERVER, NVS_MSGWRITES, NVS_MSGSIGNON to server structure
  added NVS_MSGCONVERSION, NVS_MSGIGNORE to client structure
  added DATAGRAM message buffer to client structure for MSG_BROADCAST
SV_MAIN.C
  added initialization of new client's datagram message buffer, NVS_MSGCONVERSION, NVS_MSGIGNORE in SV_ConnectClient()
  added copying client's datagram message buffer to his message buffer in SV_SendClientDatagram()
  added initialization of clients' datagram in SV_SpawnServer()
PROGS\DEFS.QC
  added PROGS.DAT/QuakeC declaration of NVS_INITSVCMSG()



⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?