forked from freeciv/freeciv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.delta
164 lines (138 loc) · 7.31 KB
/
README.delta
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
=========================================================================
Delta
=========================================================================
If delta is enabled for this packet the packet-payload (after the bytes
used by the packet-header) is followed by the delta-header. (See HACKING
to learn how to understand the packet-header.) The delta-header
is a bitvector which represents all non-key fields of the packet. If
the field has changed the corresponding bit is set and the field
value is so included in delta-body. The values of the unchanged fields
will be filled in from an old version at the receiving side. The old
version filled in from is the previous packet of the same kind that has
the same value in each key field. (If the packet's kind don't have any
key fields the previous packet of the same kind is used) If no old
version exists the unchanged fields will be assumed to be zero.
For bool field another optimization called bool-header-folding is
applied. Instead of sending an indicator in the bitvector if the given
bool values has changed (and so using 1 byte for the real value) the
actual value of the bool is transferred in the bitvector bit of this
bool field.
Another optimization called array-diff is used to reduce the amount of
elements transferred if an array is changed. This is independent of the
delta-header bit i.e. it will only be used if the array has changed
its value and the bit indicates this. Instead of transferring the
whole array only a list of (index, new value of this index) pairs are
transferred. The index is 8bit and the end of this pair list is
denoted by an index of 255.
For fields of struct type (or arrays of struct) the following function
is used to compare entries, where foo stands for the name of the struct:
bool are_foo_equal(const struct foo *a, const struct foo *b);
The declaration of this function must be made available to the generated
code by having it #include the correct header. The includes are hard-
coded in generate_packets.py.
=========================================================================
Compression
=========================================================================
To further reduce the network traffic the (delta) packets are
compressed using the DEFLATE compression algorithm.
To get better compression results multiple packets are
grouped together and compressed into a chunk. This chunk is then
transferred as a normal packet. A chunk packet starts with the 2 byte
length field which every packet has. A chunk packet has no type. A
chunk packet is identified by having a too large length field. If the
length of the packet is over COMPRESSION_BORDER it is a chunk
packet. It will be uncompressed at the receiving side and re-feed into
the receiving queue.
If the length of the chunk packet can't be expressed in the available
space of the 16bit length field (>48kb) the chunk is sent as a jumbo
packet. The difference between a normal chunk packet and a jumbo chunk
packet is that the jumbo packet has JUMBO_SIZE in the size field and
has an additional 4 byte len field after the 2 byte len field. The
second len field contains the size of the whole packet (2 byte
first length field + 4 byte second length field + compressed data).
The size field of a normal chunk packet is its size + COMPRESSION_BORDER.
Packets are grouped for the compression based on the
PACKET_PROCESSING_STARTED/PACKET_PROCESSING_FINISHED and
PACKET_FREEZE_HINT/PACKET_THAW_HINT packet pairs. If the first
(freeze) packet is encountered the packets till the second (thaw)
packet are put into a queue. This queue is then compressed and sent as
a chunk packet. If the compression would expand in size the queued
packets are sent uncompressed as "normal" packets.
The compression level can be controlled by the
FREECIV_COMPRESSION_LEVEL environment variable.
=========================================================================
Files
=========================================================================
There are four file/filesets involved in the delta protocol:
1) the definition file (common/networking/packets.def).
2) the generator (common/generate_packets.py).
3) the generated files (*/*_gen.[ch] or as a list
client/packhand_gen.c, client/packhand_gen.h, common/packets_gen.c,
common/packets_gen.h, server/hand_gen.c and server/hand_gen.h).
4) the overview (README.delta, this file)
The definition file lists all valid packet types with their
fields. The generator takes this as input and creates the generated
files.
For adding and/or removing packets and/or fields you only have to
touch the definition file. If you however plan to change the generated
code (adding more statistics for example) you have to change the
generator.
=========================================================================
Changing the definition file
=========================================================================
Adding a packet:
1) choose an unused packet number. The generator will make sure that
you don't use the same number two times.
2) choose a packet name. It should follow the naming style of the
other packets: PACKET_<group>_<remaining>. <group> may be SERVER,
CITY, UNIT, PLAYER, DIPLOMACY and so on.
3) decide if this packet goes from server to client or client to server
4) choose the field names and types
5) choose packet and field flags
6) write the entry into the corresponding section of
common/networking/packets.def
If you add a field which is a struct (say "foobar") you have to write
the following functions: dio_get_foobar, dio_put_foobar and
are_foobars_equal.
Removing a packet:
1) add a mandatory capability
2) remove the entry from common/networking/packets.def
Adding a field:
Option A:
1) add a mandatory capability
2) add a normal field line:
COORD x
Option B:
1) add a non-mandatory capability (say "new_version")
2) add a normal field line containing this capability in an add-cap
flag:
COORD x; add-cap(new_version)
Removing a field:
Option A:
1) add a mandatory capability
2) remove the corresponding field line
Option B:
1) add a non-mandatory capability (say "cleanup")
2) add to the corresponding field line a remove-cap flag
After changing the definition file the generator has to be run. The
common/Makefile will take care of this. You don't need to run
autoconf/automake/configure.
=========================================================================
Capabilities and variants
=========================================================================
The generator has to generate code which supports different
capabilities at runtime according to the specification given in the
definitions with add-cap and remove-cap. The generator will find the
set of used capabilities for a given packet. Lets say there are two
fields with "add-cap(cap1)" and one field with an "remove-cap(cap2)"
flag. So the set of capabilities are cap1, cap2. At runtime the
generated code may run under 4 different capabilities:
- neither cap1 nor cap2 are set
- cap1 is set but cap2 isn't
- cap1 is not set but cap2 is
- cap1 and cap2 are set
Each of these combinations is called a variant. If n is the number of
capabilities used by the packet the number of variants is 2^n.
For each of these variant a separate send and receive function will be
generated. The variant for a packet and a connection are calculated
once and then saved in the connection struct.