Project

General

Profile

Feature #500 ยป 0001-generate_packets.py-Improve-coding-style.patch

main - Alina Lenk, 04/27/2024 03:39 AM

View differences:

common/generate_packets.py
help = "enable log messages during code generation")
# When enabled: Only overwrite existing output files when they
# actually changed. This prevents make from rebuilding all dependents
# in cases where that wouldn't even be necessary.
# actually changed. This prevents the build system from rebuilding all
# dependents in cases where that wouldn't even be necessary.
script.add_argument("--lazy-overwrite", action = "store_true",
help = "only overwrite output files when their"
" contents actually changed")
......
logs = output.add_mutually_exclusive_group()
logs.add_argument("-l", "--log-macro", default = "log_packet_detailed",
help = "use the given macro for generated log calls")
help = "use the given macro for generated log calls")
logs.add_argument("-L", "--no-logs", dest = "log_macro",
action = "store_const", const = None,
help = "disable generating log calls")
action = "store_const", const = None,
help = "disable generating log calls")
protocol.add_argument("-B", "--no-fold-bool",
dest = "fold_bool", action = "store_false",
help = "explicitly encode boolean values in the"
" packet body, rather than folding them into the"
" packet header")
dest = "fold_bool", action = "store_false",
help = "explicitly encode boolean values in the"
" packet body, rather than folding them into the"
" packet header")
output_path_args = (
# (dest, option, canonical path)
......
* Input: {self._relative_path(path)!s:63} *
""")
f.write("""\
f.write(f"""\
* DO NOT CHANGE THIS FILE *
**************************************************************************/
......
@contextmanager
def _wrap_cplusplus(self, file: typing.TextIO) -> typing.Iterator[None]:
"""Add code for `extern "C" {}` wrapping"""
file.write("""\
file.write(f"""\
#ifdef __cplusplus
extern "C" {
extern "C" {{
#endif /* __cplusplus */
""")
yield
file.write("""\
file.write(f"""\
#ifdef __cplusplus
}
}}
#endif /* __cplusplus */
""")
......
def files_equal(path_a: "str | Path", path_b: "str | Path") -> bool:
"""Return whether the contents of two text files are identical"""
with Path(path_a).open() as file_a, Path(path_b).open() as file_b:
with open(path_a) as file_a, open(path_b) as file_b:
return all(a == b for a, b in zip_longest(file_a, file_b))
# Taken from https://docs.python.org/3.4/library/itertools.html#itertools-recipes
......
return self.location
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.name!r}, {self.location!r}, {self.depth!r})"
return f"{self.__class__.__name__}({self.name!r}, {self.location!r}, {self.depth!r}, {self.json_depth!r})"
#################### Components of a packets definition ####################
......
return ""
return f"""\
if ({self.real} > {self.declared}) {{
RECEIVE_PACKET_FIELD_ERROR({field_name}, ": truncation array");
RECEIVE_PACKET_FIELD_ERROR({field_name}, ": array truncated");
}}
"""
......
"""Fallback constructor used when there are no matches for either
dataio type or public type."""
@staticmethod
def _resolve(
key: str,
direct: "dict[str, FieldTypeConstructor]",
patterns: "dict[typing.Pattern[str], FieldTypeConstructor]",
fallback: FieldTypeConstructor
) -> FieldTypeConstructor:
"""Helper function. Figures out which constructor to use for a given
key (dataio type or public type), and caches the result."""
try:
return direct[key]
except KeyError:
pass
for pat, ctor in patterns.items():
mo = pat.fullmatch(key)
if mo is not None:
# cache the match
direct[key] = ctor
return ctor
# cache that there was no match
direct[key] = fallback
return fallback
def __init__(self, fallback: FieldTypeConstructor):
self.dataio_types = {}
self.dataio_patterns = {}
......
return self(*mo.groups())
def __call__(self, dataio_type: str, public_type: str) -> RawFieldType:
try:
ctor = self.dataio_types[dataio_type]
except KeyError:
pass
else:
return ctor(dataio_type, public_type)
for pat, ctor in self.dataio_patterns.items():
mo = pat.fullmatch(dataio_type)
if mo is not None:
self.dataio_types[dataio_type] = ctor
return ctor(dataio_type, public_type)
self.dataio_types[dataio_type] = self._by_public
return self._by_public(dataio_type, public_type)
ctor = self._resolve(dataio_type, self.dataio_types, self.dataio_patterns, self._by_public)
return ctor(dataio_type, public_type)
def _by_public(self, dataio_type: str, public_type: str) -> RawFieldType:
try:
ctor = self.public_types[public_type]
except KeyError:
pass
else:
return ctor(dataio_type, public_type)
for pat, ctor in self.public_patterns.items():
mo = pat.fullmatch(public_type)
if mo is not None:
self.public_types[public_type] = ctor
return ctor(dataio_type, public_type)
self.public_types[public_type] = self.fallback
return self.fallback(dataio_type, public_type)
ctor = self._resolve(public_type, self.public_types, self.public_patterns, self.fallback)
return ctor(dataio_type, public_type)
class NeedSizeType(RawFieldType):
......
raise NotImplementedError
@abstractmethod
def get_code_handle_param(self, location: Location) -> str:
def get_code_param(self, location: Location) -> str:
"""Generate a code fragment declaring a parameter with this type for
a handle function.
......
"""Generate a code fragment passing an argument with this type to a
handle function.
See also self.get_code_handle_param()"""
See also self.get_code_param()"""
return str(location)
def get_code_init(self, location: Location) -> str:
......
Subclasses must override this if self.complex is True"""
if self.complex:
raise ValueError(f"default get_code_init implementation called for field {location.name} with complex type {self!r}")
return f"""\
/* no work needed for {location} */
"""
# no work needed
return ""
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
"""Generate a code snippet deep-copying a field of this type from
......
Subclasses must override this if self.complex is True"""
if self.complex:
raise ValueError(f"default get_code_free implementation called for field {location.name} with complex type {self!r}")
return f"""\
/* no work needed for {location} */
"""
# no work needed
return ""
@abstractmethod
def get_code_hash(self, location: Location) -> str:
......
"""Internal helper function. Yield keys to compare for
type compatibility. See is_type_compatible()"""
yield self.get_code_declaration(location)
yield self.get_code_handle_param(location)
yield self.get_code_param(location)
yield self.get_code_handle_arg(location)
yield self.get_code_fill(location)
yield self.complex
......
{self.public_type} {location};
"""
def get_code_handle_param(self, location: Location) -> str:
def get_code_param(self, location: Location) -> str:
return f"{self.public_type} {location}"
def get_code_hash(self, location: Location) -> str:
......
super().__init__(dataio_type, public_type)
def get_code_handle_param(self, location: Location) -> str:
def get_code_param(self, location: Location) -> str:
if not location.depth:
# top level: pass by-reference
return "const " + super().get_code_handle_param(location.deeper(f"*{location}"))
return super().get_code_handle_param(location)
return "const " + super().get_code_param(location.deeper(f"*{location}"))
return super().get_code_param(location)
def get_code_handle_arg(self, location: Location) -> str:
if not location.depth:
......
location.deeper(f"{location}[{self.size.declared}]")
)
def get_code_handle_param(self, location: Location) -> str:
def get_code_param(self, location: Location) -> str:
# add "const" if top level
pre = "" if location.depth else "const "
return pre + super().get_code_handle_param(location.deeper(f"*{location}"))
return pre + super().get_code_param(location.deeper(f"*{location}"))
@abstractmethod
def get_code_fill(self, location: Location) -> str:
......
location.deeper(f"{location}[{self.size.declared}]")
)
def get_code_handle_param(self, location: Location) -> str:
def get_code_param(self, location: Location) -> str:
# Note: If we're fine with writing `foo_t const *fieldname`,
# we'd only need one case, .deeper(f"const *{location}")
if not location.depth:
# foo_t fieldname ~> const foo_t *fieldname
return "const " + self.elem.get_code_handle_param(location.deeper(f"*{location}"))
return "const " + self.elem.get_code_param(location.deeper(f"*{location}"))
else:
# const foo_t *fieldname ~> const foo_t *const *fieldname
# the final * is already part of {location}
return self.elem.get_code_handle_param(location.deeper(f"*const {location}"))
return self.elem.get_code_param(location.deeper(f"*const {location}"))
def get_code_init(self, location: Location) -> str:
if not self.complex:
......
def get_code_cmp(self, location: Location) -> str:
if not self.size.constant:
# ends mid-line
head = f"""\
differ = ({self.size.old} != {self.size.real});
if (!differ) {{
"""
if (!differ) """
else:
head = """\
head = f"""\
differ = FALSE;
{
"""
inner_cmp = prefix(" ", self.elem.get_code_cmp(location.sub))
return head + f"""\
return f"""\
{head}{{
int {location.index};
for ({location.index} = 0; {location.index} < {self.size.real}; {location.index}++) {{
......
sub = location.sub_full(2)
# Note: At the moment, we're only deep-diffing our elements
# if our array size is constant
inner_put = prefix(" ", self.elem.get_code_put(sub, self.size.constant))
value_put = prefix(" ", self.elem.get_code_put(sub, self.size.constant))
inner_cmp = prefix(" ", self.elem.get_code_cmp(sub))
index_put = prefix(" ", self.size.index_put(location.index))
index_put = prefix(" ", self.size.index_put(location.index))
index_put_sentinel = prefix(" ", self.size.index_put(self.size.real))
if not self.size.constant:
......
for ({location.index} = 0; {location.index} < {self.size.real}; {location.index}++) {{
{inner_cmp}\
if (differ) {{
if (!differ) {{
continue;
}}
#ifdef FREECIV_JSON_CONNECTION
/* Append next diff array element. */
{location.json_subloc}->number = -1;
/* Append next diff array element. */
{location.json_subloc}->number = -1;
/* Create the diff array element. */
e |= DIO_PUT(object, &dout, &field_addr);
/* Create the diff array element. */
e |= DIO_PUT(object, &dout, &field_addr);
/* Enter diff array element (start at the index address). */
{location.json_subloc}->number = count_{location.index}++;
{location.json_subloc}->sub_location = plocation_field_new("index");
/* Enter diff array element (start at the index address). */
{location.json_subloc}->number = count_{location.index}++;
{location.json_subloc}->sub_location = plocation_field_new("index");
#endif /* FREECIV_JSON_CONNECTION */
/* Write the index */
/* Write the index */
{index_put}\
#ifdef FREECIV_JSON_CONNECTION
/* Content address. */
{location.json_subloc}->sub_location->name = "data";
/* Content address. */
{location.json_subloc}->sub_location->name = "data";
#endif /* FREECIV_JSON_CONNECTION */
{inner_put}\
{value_put}\
#ifdef FREECIV_JSON_CONNECTION
/* Exit diff array element. */
FC_FREE({location.json_subloc}->sub_location);
/* Exit diff array element. */
FC_FREE({location.json_subloc}->sub_location);
#endif /* FREECIV_JSON_CONNECTION */
}}
}}
#ifdef FREECIV_JSON_CONNECTION
......
{index_put_sentinel}\
#ifdef FREECIV_JSON_CONNECTION
/* Exit diff array element. */
/* Exit diff array element and array. */
FC_FREE({location.json_subloc}->sub_location);
/* Exit array. */
FC_FREE({location.json_subloc});
#endif /* FREECIV_JSON_CONNECTION */
}}
......
def _get_code_get_diff(self, location: Location) -> str:
"""Helper method. Generate array-diff get code."""
# we're nested two levels deep in the JSON structure
inner_get = prefix(" ", self.elem.get_code_get(location.sub_full(2), True))
value_get = prefix(" ", self.elem.get_code_get(location.sub_full(2), True))
index_get = prefix(" ", self.size.index_get(location))
return f"""\
{self.size.size_check_get(location.name)}\
......
{location.json_subloc}->sub_location->name = "data";
#endif /* FREECIV_JSON_CONNECTION */
{inner_get}\
{value_get}\
#ifdef FREECIV_JSON_CONNECTION
/* Move to the next diff array element. */
......
}}
#ifdef FREECIV_JSON_CONNECTION
/* Exit diff array element. */
/* Exit diff array element and array. */
FC_FREE({location.json_subloc}->sub_location);
/* Exit array. */
FC_FREE({location.json_subloc});
#endif /* FREECIV_JSON_CONNECTION */
"""
......
{self.public_type} *{location};
"""
def get_code_handle_param(self, location: Location) -> str:
def get_code_param(self, location: Location) -> str:
if not location.depth:
return f"const {self.public_type} *{location}"
else:
......
# vectors (like with jumbo packets)
# Though that would also mean packets larger than 64 KiB,
# which we're a long way from
size = __class__._VecSize(location)
return f"""\
if (!real_packet->{location}) {{
/* Transmit null vector as empty vector */
......
}} else {{
int {location.index};
fc_assert(strvec_size(real_packet->{location}) < MAX_UINT16);
e |= DIO_PUT(arraylen, &dout, &field_addr, strvec_size(real_packet->{location}));
fc_assert({size.real} < MAX_UINT16);
e |= DIO_PUT(arraylen, &dout, &field_addr, {size.real});
#ifdef FREECIV_JSON_CONNECTION
/* Enter array. */
{location.json_subloc} = plocation_elem_new(0);
#endif /* FREECIV_JSON_CONNECTION */
for ({location.index} = 0; {location.index} < strvec_size(real_packet->{location}); {location.index}++) {{
for ({location.index} = 0; {location.index} < {size.real}; {location.index}++) {{
const char *pstr = strvec_get(real_packet->{location}, {location.index});
if (!pstr) {{
......
}}
#ifdef FREECIV_JSON_CONNECTION
/* Exit array. */
FC_FREE({location.json_subloc});
#endif /* FREECIV_JSON_CONNECTION */
}}
......
def _get_code_put_diff(self, location: Location) -> str:
size = __class__._VecSize(location)
size_check = prefix(" ", size.size_check_index(location.name))
index_put = prefix(" ", size.index_put(location.index))
index_put = prefix(" ", size.index_put(location.index))
index_put_sentinel = prefix(" ", size.index_put(size.real))
return f"""\
if (!real_packet->{location} || 0 == strvec_size(real_packet->{location})) {{
if (!real_packet->{location} || 0 == {size.real}) {{
/* Special case for empty vector. */
#ifdef FREECIV_JSON_CONNECTION
/* Create the object to hold the new size. */
e |= DIO_PUT(object, &dout, &field_addr);
/* Enter object (start at size address). */
{location.json_subloc} = plocation_field_new("size");
#endif /* FREECIV_JSON_CONNECTION */
......
#endif /* FREECIV_JSON_CONNECTION */
/* Write the new size */
e |= DIO_PUT(uint16, &dout, &field_addr, strvec_size(real_packet->{location}));
e |= DIO_PUT(uint16, &dout, &field_addr, {size.real});
#ifdef FREECIV_JSON_CONNECTION
/* Delta address. */
......
{location.json_subloc}->sub_location = plocation_elem_new(0);
#endif /* FREECIV_JSON_CONNECTION */
for ({location.index} = 0; {location.index} < strvec_size(real_packet->{location}); {location.index}++) {{
for ({location.index} = 0; {location.index} < {size.real}; {location.index}++) {{
const char *pstr = strvec_get(real_packet->{location}, {location.index});
if (!pstr) {{
......
pstr = "";
}}
if ({location.index} < strvec_size(old->{location})) {{
if ({location.index} < {size.old}) {{
const char *pstr_old = strvec_get(old->{location}, {location.index});
differ = (strcmp(pstr_old ? pstr_old : "", pstr) != 0);
......
differ = TRUE;
}}
if (differ) {{
if (!differ) {{
continue;
}}
#ifdef FREECIV_JSON_CONNECTION
/* Append next diff array element. */
{location.json_subloc}->sub_location->number = -1;
/* Append next diff array element. */
{location.json_subloc}->sub_location->number = -1;
/* Create the diff array element. */
e |= DIO_PUT(object, &dout, &field_addr);
/* Create the diff array element. */
e |= DIO_PUT(object, &dout, &field_addr);
/* Enter diff array element (start at the index address). */
{location.json_subloc}->sub_location->number = count_{location.index}++;
{location.json_subloc}->sub_location->sub_location = plocation_field_new("index");
/* Enter diff array element (start at the index address). */
{location.json_subloc}->sub_location->number = count_{location.index}++;
{location.json_subloc}->sub_location->sub_location = plocation_field_new("index");
#endif /* FREECIV_JSON_CONNECTION */
/* Write the index */
/* Write the index */
{index_put}\
#ifdef FREECIV_JSON_CONNECTION
/* Content address. */
{location.json_subloc}->sub_location->sub_location->name = "data";
/* Content address. */
{location.json_subloc}->sub_location->sub_location->name = "data";
#endif /* FREECIV_JSON_CONNECTION */
e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, pstr);
e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, pstr);
#ifdef FREECIV_JSON_CONNECTION
/* Exit diff array element. */
FC_FREE({location.json_subloc}->sub_location->sub_location);
/* Exit diff array element. */
FC_FREE({location.json_subloc}->sub_location->sub_location);
#endif /* FREECIV_JSON_CONNECTION */
}}
}}
#ifdef FREECIV_JSON_CONNECTION
......
{index_put_sentinel}\
#ifdef FREECIV_JSON_CONNECTION
/* Exit diff array element. */
/* Exit diff array element, array, and object. */
FC_FREE({location.json_subloc}->sub_location->sub_location);
/* Exit array. */
FC_FREE({location.json_subloc}->sub_location);
/* Exit object. */
FC_FREE({location.json_subloc});
#endif /* FREECIV_JSON_CONNECTION */
}}
......
return self._get_code_put_full(location)
def _get_code_get_full(self, location: Location) -> str:
size = __class__._VecSize(location)
return f"""\
{{
int {location.index};
......
{location.json_subloc} = plocation_elem_new(0);
#endif /* FREECIV_JSON_CONNECTION */
for ({location.index} = 0; {location.index} < strvec_size(real_packet->{location}); {location.index}++) {{
for ({location.index} = 0; {location.index} < {size.real}; {location.index}++) {{
char readin[MAX_LEN_PACKET];
#ifdef FREECIV_JSON_CONNECTION
......
{location.json_subloc}->sub_location->sub_location = plocation_field_new("index");
#endif /* FREECIV_JSON_CONNECTION */
/* if (strvec_size(real_packet->{location}) > 0) while (TRUE) */
while (strvec_size(real_packet->{location}) > 0) {{
/* if ({size.real} > 0) while (TRUE) */
while ({size.real} > 0) {{
int {location.index};
char readin[MAX_LEN_PACKET];
/* Read next index */
{index_get}\
if ({location.index} == strvec_size(real_packet->{location})) {{
if ({location.index} == {size.real}) {{
break;
}}
if ({location.index} > strvec_size(real_packet->{location})) {{
if ({location.index} > {size.real}) {{
RECEIVE_PACKET_FIELD_ERROR({location.name},
": unexpected value %d "
"(> vector length) in array diff",
......
}}
#ifdef FREECIV_JSON_CONNECTION
/* Exit diff array element. */
/* Exit diff array element, array, and object. */
FC_FREE({location.json_subloc}->sub_location->sub_location);
/* Exit array. */
FC_FREE({location.json_subloc}->sub_location);
/* Exit object. */
FC_FREE({location.json_subloc});
#endif /* FREECIV_JSON_CONNECTION */
"""
......
"""A single field of a packet. Consists of a name, type information
(including array sizes) and flags."""
FIELDS_LINE_PATTERN = re.compile(r"^\s*(\w+(?:\([^()]*\))?)\s+([^;()]*?)\s*;\s*(.*?)\s*$")
FIELDS_LINE_PATTERN = re.compile(r"""
^\s*
( # field type; see also TypeRegistry.TYPE_INFO_PATTERN
\w+ # alias or dataio type
(?: # optionally, public type (if this is not an alias)
\(
[^()]*
\)
)?
)
\s+
( # zero or more field declarations
[^;()]*?
)
\s*;\s*
(.*?) # flags
\s*$
""", re.VERBOSE)
"""Matches an entire field definition line.
Groups:
......
all(cap not in caps for cap in self.flags.remove_caps)
)
def get_declar(self) -> str:
def get_declaration(self) -> str:
"""Generate the way this field is declared in the packet struct"""
return self.type_info.get_code_declaration(Location(self.name))
def get_handle_param(self) -> str:
def get_param(self) -> str:
"""Generate the way this field is declared as a parameter of a
handle function.
handle or dsend function.
See also self.get_handle_arg()"""
return self.type_info.get_code_handle_param(Location(self.name))
return self.type_info.get_code_param(Location(self.name))
def get_handle_arg(self, packet_arrow: str) -> str:
"""Generate the way this field is passed as an argument to a handle
......
assert self.is_key
return self.type_info.get_code_hash(Location(self.name))
def get_cmp(self) -> str:
"""Generate code checking whether this field changed.
This code is primarily used by self.get_cmp_wrapper()"""
return self.type_info.get_code_cmp(Location(self.name))
@property
def folded_into_head(self) -> bool:
"""Whether this field is folded into the packet header.
......
See also self.get_cmp()"""
if self.folded_into_head:
if pack.is_info != "no":
cmp = self.get_cmp()
differ_part = """\
if (differ) {
info_part = f"""\
{self.get_cmp()}\
if (differ) {{
different++;
}
"""
else:
cmp = ""
differ_part = ""
}}
""" if pack.is_info != "no" else ""
return f"""\
{cmp}\
{differ_part}\
{info_part}\
/* folded into head */
if (packet->{self.name}) {{
BV_SET(fields, {i:d});
}}
"""
else:
cmp = self.get_cmp()
if pack.is_info != "no":
return f"""\
{cmp}\
if (differ) {{
info_part = f"""\
different++;
BV_SET(fields, {i:d});
}}
""" if pack.is_info != "no" else ""
"""
else:
return f"""\
{cmp}\
return f"""\
{self.get_cmp()}\
if (differ) {{
{info_part}\
BV_SET(fields, {i:d});
}}
"""
def get_put_wrapper(self, packet: "Variant", i: int, deltafragment: bool) -> str:
def get_cmp(self) -> str:
"""Generate code checking whether this field changed.
This code is primarily used by self.get_cmp_wrapper()"""
return self.type_info.get_code_cmp(Location(self.name))
def get_put_wrapper(self, packet: "Variant", index: int, deltafragment: bool) -> str:
"""Generate code conditionally putting this field iff its bit in the
`fields` bitvector is set.
......
See also self.get_put()"""
if self.folded_into_head:
return f"""\
/* field {i:d} is folded into the header */
/* field {index:d} is folded into the header */
"""
log = f"""\
{packet.log_macro}(" field '{self.name}' has changed");
""" if packet.gen_log else ""
stats = f"""\
stats_{packet.name}_counters[{index:d}]++;
""" if packet.gen_stats else ""
put = prefix(" ", self.get_put(deltafragment))
if packet.gen_log:
f = f"""\
{packet.log_macro}(" field \'{self.name}\' has changed");
"""
else:
f=""
if packet.gen_stats:
s = f"""\
stats_{packet.name}_counters[{i:d}]++;
"""
else:
s=""
return f"""\
if (BV_ISSET(fields, {i:d})) {{
{f}\
{s}\
if (BV_ISSET(fields, {index:d})) {{
{log}\
{stats}\
{put}\
}}
"""
......
This does not include delta-related code checking whether to
transmit the field in the first place; see self.get_put_wrapper()"""
real = self.get_put_real(deltafragment)
return f"""\
#ifdef FREECIV_JSON_CONNECTION
field_addr.name = "{self.name}";
#endif /* FREECIV_JSON_CONNECTION */
e = 0;
{real}\
{self.get_put_real(deltafragment)}\
if (e) {{
log_packet_detailed("'{self.name}' field error detected");
}}
......
return f"""\
real_packet->{self.name} = BV_ISSET(fields, {i:d});
"""
get = prefix(" ", self.get_get(deltafragment))
if packet.gen_log:
f = f"""\
log = f"""\
{packet.log_macro}(" got field '{self.name}'");
"""
else:
f=""
""" if packet.gen_log else ""
return f"""\
if (BV_ISSET(fields, {i:d})) {{
{f}\
{log}\
{get}\
}}
"""
......
was transmitted in the first place; see self.get_get_wrapper()"""
return f"""\
#ifdef FREECIV_JSON_CONNECTION
field_addr.name = \"{self.name}\";
field_addr.name = "{self.name}";
#endif /* FREECIV_JSON_CONNECTION */
{self.get_get_real(deltafragment)}\
"""
......
packet: "Packet"
"""The packet this is a variant of"""
no: int
var_number: int
"""The numeric variant ID (not packet ID) of this variant"""
name: str
......
See also self.keys_format"""
def __init__(self, poscaps: typing.Iterable[str], negcaps: typing.Iterable[str],
packet: "Packet", no: int):
packet: "Packet", var_number: int):
self.packet = packet
self.no = no
self.name = f"{packet.name}_{no:d}"
self.var_number = var_number
self.name = f"{packet.name}_{var_number:d}"
self.poscaps = set(poscaps)
self.negcaps = set(negcaps)
......
See get_send()"""
return (
(not self.no_packet)
and self.delta
self.delta
and (
self.is_info != "no"
or any(
......
@property
def send_prototype(self) -> str:
"""The prototype of this variant's send function"""
return f"static int send_{self.name}(struct connection *pc{self.packet.extra_send_args})"
return f"static int send_{self.name}(struct connection *pc{self.packet.send_params})"
@property
def send_handler(self) -> str:
def fill_send_handler(self) -> str:
"""Code to set the send handler for this variant
See get_packet_handlers_fill_initial and
......
"""
@property
def receive_handler(self) -> str:
def fill_receive_handler(self) -> str:
"""Code to set the receive handler for this variant
See get_packet_handlers_fill_initial and
......
static int stats_{self.name}_sent;
static int stats_{self.name}_discarded;
static int stats_{self.name}_counters[{self.bits:d}];
static char *stats_{self.name}_names[] = {{{names}}};
static const char *stats_{self.name}_names[] = {{{names}}};
"""
......
"""
def get_report_part(self) -> str:
"""Generate the part of the delta_stats_report8) function specific
"""Generate the part of the delta_stats_report() function specific
to this packet variant"""
return f"""\
if (stats_{self.name}_sent > 0
&& stats_{self.name}_discarded != stats_{self.name}_sent) {{
log_test(\"{self.name} %d out of %d got discarded\",
log_test("{self.name} %d out of %d got discarded",
stats_{self.name}_discarded, stats_{self.name}_sent);
for (i = 0; i < {self.bits}; i++) {{
if (stats_{self.name}_counters[i] > 0) {{
log_test(\" %4d / %4d: %2d = %s\",
log_test(" %4d / %4d: %2d = %s",
stats_{self.name}_counters[i],
(stats_{self.name}_sent - stats_{self.name}_discarded),
i, stats_{self.name}_names[i]);
......
if not self.key_fields:
return f"""\
#define hash_{self.name} hash_const
"""
intro = f"""\
......
"""
body = """\
body = f"""\
result *= 5;
""".join(prefix(" ", field.get_hash()) for field in self.key_fields)
extro = """\
extro = f"""\
result &= 0xFFFFFFFF;
return result;
}
}}
"""
......
if not self.key_fields:
return f"""\
#define cmp_{self.name} cmp_const
"""
# note: the names `old` and `real_packet` allow reusing
......
"""
body = """\
body = f"""\
if (differ) {
if (differ) {{
return !differ;
}
}}
""".join(prefix(" ", field.get_cmp()) for field in self.key_fields)
extro = """\
extro = f"""\
return !differ;
}
}}
"""
return intro + body + extro
def get_send(self) -> str:
"""Generate the send function for this packet variant"""
if self.gen_stats:
report = f"""\
stats_total_sent++;
stats_{self.name}_sent++;
"""
else:
report=""
if self.gen_log:
log = f"""\
{self.log_macro}("{self.name}: sending info about ({self.keys_format})"{self.keys_arg});
"""
else:
log=""
if self.no_packet:
# empty packet, don't need anything
main_header = ""
after_header = ""
before_return = ""
main_header = after_header = before_return = ""
elif not self.packet.want_pre_send:
# no pre-send, don't need to copy the packet
main_header = f"""\
const struct {self.packet_name} *real_packet = packet;
int e;
"""
after_header = ""
before_return = ""
after_header = before_return = ""
elif not self.complex:
# bit-copy the packet
# bit-copy the packet, no cleanup needed
main_header = f"""\
/* copy packet for pre-send */
struct {self.packet_name} packet_buf = *packet;
const struct {self.packet_name} *real_packet = &packet_buf;
int e;
"""
after_header = ""
before_return = ""
after_header = before_return = ""
else:
# deep-copy the packet for pre-send, have to destroy the copy
# Note: SEND_PACKET_START has both declarations and statements,
# so we have to break the pre-send copying up across either side
copy = prefix(" ", self.get_copy("(&packet_buf)", "packet"))
main_header = f"""\
/* buffer to hold packet copy for pre-send */
......
free_{self.packet_name}(&packet_buf);
"""
report = f"""\
stats_total_sent++;
stats_{self.name}_sent++;
""" if self.gen_stats else ""
log_key = f"""\
{self.log_macro}("{self.name}: sending info about ({self.keys_format})"{self.keys_arg});
""" if self.gen_log else ""
if not self.packet.want_pre_send:
pre = ""
pre_send = ""
elif self.no_packet:
pre = f"""\
pre_send = f"""\
pre_send_{self.packet_name}(pc, nullptr);
"""
else:
pre = f"""\
pre_send = f"""\
pre_send_{self.packet_name}(pc, &packet_buf);
"""
if not self.no_packet:
if self.delta:
if self.want_force:
diff = "force_to_send"
else:
diff = "0"
delta_header = f"""\
#ifdef FREECIV_DELTA_PROTOCOL
{self.name}_fields fields;
struct {self.packet_name} *old;
"""
if self.differ_used:
delta_header += """\
init_field_addr = f"""\
#ifdef FREECIV_JSON_CONNECTION
struct plocation field_addr;
{{
struct plocation *field_addr_tmp = plocation_field_new(nullptr);
field_addr = *field_addr_tmp;
FC_FREE(field_addr_tmp);
}}
#endif /* FREECIV_JSON_CONNECTION */
""" if self.fields else ""
nondelta_body = "\n".join(
prefix(" ", field.get_put(False))
for field in self.fields
)
if self.delta:
declare_differ = f"""\
bool differ;
"""
delta_header += f"""\
struct genhash **hash = pc->phs.sent + {self.type};
"""
if self.is_info != "no":
delta_header += f"""\
int different = {diff};
"""
delta_header += """\
#endif /* FREECIV_DELTA_PROTOCOL */
"""
body = prefix(" ", self.get_delta_send_body(before_return)) + """\
#ifndef FREECIV_DELTA_PROTOCOL
""" if self.differ_used else ""
if self.is_info == "no":
declare_different = ""
elif self.want_force:
declare_different = f"""\
int different = force_to_send;
"""
else:
delta_header=""
body = """\
#if 1 /* To match endif */
declare_different = f"""\
int different = 0;
"""
body += "".join(
prefix(" ", field.get_put(False))
for field in self.fields
)
body += """\
delta_body = prefix(" ", self.get_delta_send_body(before_return))
body = f"""\
#ifdef FREECIV_DELTA_PROTOCOL
{self.name}_fields fields;
struct {self.packet_name} *old;
{declare_differ}\
{declare_different}\
struct genhash **hash = pc->phs.sent + {self.type};
{delta_body}\
#else /* FREECIV_DELTA_PROTOCOL */
{nondelta_body}\
#endif
"""
else:
body=""
delta_header=""
body = nondelta_body
if self.packet.want_post_send:
if self.no_packet:
post = f"""\
if not self.packet.want_post_send:
post_send = ""
elif self.no_packet:
post_send = f"""\
post_send_{self.packet_name}(pc, nullptr);
"""
else:
post = f"""\
post_send_{self.packet_name}(pc, real_packet);
"""
else:
post=""
if self.fields:
faddr = """\
#ifdef FREECIV_JSON_CONNECTION
struct plocation field_addr;
{
struct plocation *field_addr_tmp = plocation_field_new(nullptr);
field_addr = *field_addr_tmp;
FC_FREE(field_addr_tmp);
}
#endif /* FREECIV_JSON_CONNECTION */
post_send = f"""\
post_send_{self.packet_name}(pc, real_packet);
"""
else:
faddr = ""
return "".join((
f"""\
return f"""\
{self.send_prototype}
{{
""",
main_header,
delta_header,
f"""\
{main_header}\
SEND_PACKET_START({self.type});
""",
faddr,
after_header,
log,
report,
pre,
body,
post,
before_return,
f"""\
{after_header}\
{log_key}\
{report}\
{pre_send}\
{init_field_addr}\
{body}\
{post_send}\
{before_return}\
SEND_PACKET_END({self.type});
}}
""",
))
"""
def get_delta_send_body(self, before_return: str = "") -> str:
"""Helper for get_send(). Generate the part of the send function
that computes and transmits the delta between the real packet and
the last cached packet."""
intro = f"""\
#ifdef FREECIV_DELTA_PROTOCOL
if (nullptr == *hash) {{
*hash = genhash_new_full(hash_{self.name}, cmp_{self.name},
nullptr, nullptr, nullptr, destroy_{self.packet_name});
}}
BV_CLR_ALL(fields);
if (!genhash_lookup(*hash, real_packet, (void **) &old)) {{
old = fc_malloc(sizeof(*old));
/* temporary bitcopy just to insert correctly */
*old = *real_packet;
genhash_insert(*hash, old, old);
init_{self.packet_name}(old);
"""
if self.is_info != "no":
intro += """\
force_info = """\
different = 1; /* Force to send. */
"""
intro += """\
}
"""
body = "".join(
""" if self.is_info != "no" else ""
cmp_part = "\n".join(
field.get_cmp_wrapper(i, self)
for i, field in enumerate(self.other_fields)
)
if self.gen_log:
fl = f"""\
if self.is_info != "no":
log_discard = f"""\
{self.log_macro}(" no change -> discard");
"""
else:
fl=""
if self.gen_stats:
s = f"""\
""" if self.gen_log else ""
stats_discard = f"""\
stats_{self.name}_discarded++;
"""
else:
s=""
""" if self.gen_stats else ""
discard_part = f"""\
if self.is_info != "no":
body += f"""\
if (different == 0) {{
{fl}\
{s}\
{log_discard}\
{stats_discard}\
{before_return}\
return 0;
}}
"""
else:
discard_part = ""
body += """\
#ifdef FREECIV_JSON_CONNECTION
field_addr.name = "fields";
#endif /* FREECIV_JSON_CONNECTION */
e = 0;
e |= DIO_BV_PUT(&dout, &field_addr, fields);
if (e) {
log_packet_detailed("fields bitvector error detected");
}
"""
body += "".join(
field.get_put(True)
put_key = "".join(
field.get_put(True) + "\n"
for field in self.key_fields
)
body += "\n"
body += "".join(
put_other = "\n".join(
field.get_put_wrapper(self, i, True)
for i, field in enumerate(self.other_fields)
)
body += "\n"
body += self.get_copy("old", "real_packet")
copy_to_old = self.get_copy("old", "real_packet")
# Cancel some is-info packets.
for i in self.cancel:
body += f"""\
cancel_part = "".join(
f"""\
hash = pc->phs.sent + {i};
hash = pc->phs.sent + {cancel_packet_type};
if (nullptr != *hash) {{
genhash_remove(*hash, real_packet);
}}
"""
body += """\
#endif /* FREECIV_DELTA_PROTOCOL */
"""
for cancel_packet_type in self.cancel
)
return f"""\
if (nullptr == *hash) {{
*hash = genhash_new_full(hash_{self.name}, cmp_{self.name},
nullptr, nullptr, nullptr, destroy_{self.packet_name});
}}
BV_CLR_ALL(fields);
if (!genhash_lookup(*hash, real_packet, (void **) &old)) {{
old = fc_malloc(sizeof(*old));
/* temporary bitcopy just to insert correctly */
*old = *real_packet;
genhash_insert(*hash, old, old);
init_{self.packet_name}(old);
{force_info}\
}}
{cmp_part}\
{discard_part}\
#ifdef FREECIV_JSON_CONNECTION
field_addr.name = "fields";
#endif /* FREECIV_JSON_CONNECTION */
e = 0;
e |= DIO_BV_PUT(&dout, &field_addr, fields);
if (e) {{
log_packet_detailed("fields bitvector error detected");
}}
return intro+body
{put_key}\
{put_other}\
{copy_to_old}\
{cancel_part}\
"""
def get_receive(self) -> str:
"""Generate the receive function for this packet variant"""
if self.delta:
delta_header = f"""\
delta_header = f"""\
#ifdef FREECIV_DELTA_PROTOCOL
{self.name}_fields fields;
struct {self.packet_name} *old;
struct genhash **hash = pc->phs.received + {self.type};
#endif /* FREECIV_DELTA_PROTOCOL */
"""
delta_body1 = """\
""" if self.delta else ""
init_field_addr = f"""\
#ifdef FREECIV_JSON_CONNECTION
struct plocation field_addr;
{{
struct plocation *field_addr_tmp = plocation_field_new(nullptr);
field_addr = *field_addr_tmp;
FC_FREE(field_addr_tmp);
}}
#endif /* FREECIV_JSON_CONNECTION */
""" if self.fields else ""
get_delta_bv = f"""\
#ifdef FREECIV_DELTA_PROTOCOL
#ifdef FREECIV_JSON_CONNECTION
field_addr.name = "fields";
#endif /* FREECIV_JSON_CONNECTION */
DIO_BV_GET(&din, &field_addr, fields);
"""
body1 = "".join(
prefix(" ", field.get_get(True))
for field in self.key_fields
)
body1 += """\
#endif /* FREECIV_DELTA_PROTOCOL */
#else /* FREECIV_DELTA_PROTOCOL */
"""
body2 = prefix(" ", self.get_delta_receive_body())
else:
delta_header=""
delta_body1=""
body1 = """\
#if 1 /* To match endif */
"""
body2=""
nondelta = "".join(
""" if self.delta else ""
get_key = "".join(
prefix(" ", field.get_get(True)) + "\n"
for field in self.key_fields
)
log_key = f"""\
{self.log_macro}("{self.name}: got info about ({self.keys_format})"{self.keys_arg});
""" if self.gen_log else ""
nondelta_body = "\n".join(
prefix(" ", field.get_get(False))
for field in self.fields
) or """\
for field in self.other_fields
) or f"""\
real_packet->__dummy = 0xff;
"""
body1 += nondelta + """\
#endif
"""
if self.gen_log:
log = f"""\
{self.log_macro}("{self.name}: got info about ({self.keys_format})"{self.keys_arg});
"""
else:
log=""
if self.delta:
delta_body = prefix(" ", self.get_delta_receive_body())
get_body = f"""\
#ifdef FREECIV_DELTA_PROTOCOL
{delta_body}\
if self.packet.want_post_recv:
post = f"""\
post_receive_{self.packet_name}(pc, real_packet);
#else /* FREECIV_DELTA_PROTOCOL */
{nondelta_body}\
#endif /* FREECIV_DELTA_PROTOCOL */
"""
else:
post=""
get_body = nondelta_body
if self.fields:
faddr = """\
#ifdef FREECIV_JSON_CONNECTION
struct plocation field_addr;
{
struct plocation *field_addr_tmp = plocation_field_new(nullptr);
field_addr = *field_addr_tmp;
FC_FREE(field_addr_tmp);
}
#endif /* FREECIV_JSON_CONNECTION */
... This diff was truncated because it exceeds the maximum size that can be displayed.
    (1-1/1)