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 + -
显示快捷键?