Feature #539 ยป 0004-generate_packets.py-add-specvec-types.patch
| common/generate_packets.py | ||
|---|---|---|
|     def array(self, size: SizeInfo) -> "FieldType": | ||
|         """Construct a FieldType for an array with element type self and the | ||
|         given size""" | ||
|         if f"{size.declared}" == "*": | ||
|             raise ValueError(f"vectors not supported for field type {self}") | ||
|         return ArrayType(self, size) | ||
|     @abstractmethod | ||
| ... | ... | |
| class StructType(BasicType): | ||
|     """Type information for a field of some general struct type""" | ||
|     TYPE_PATTERN = re.compile(r"^struct \w+$") | ||
|     """Matches a struct public type""" | ||
|     TYPE_PATTERN = re.compile(r"^struct\s+(\w+)$") | ||
|     """Matches a struct public type | ||
|     Groups: | ||
|     - the struct name (without the `struct ` prefix)""" | ||
|     struct_type: str | ||
|     """The struct name (without the `struct ` prefix)""" | ||
|     @typing.overload | ||
|     def __init__(self, dataio_type: str, public_info: str): ... | ||
| ... | ... | |
|             if mo is None: | ||
|                 raise ValueError("not a valid struct type") | ||
|             public_info = mo | ||
|         public_type = public_info.group(0) | ||
|         struct_type = public_info.group(1) | ||
|         super().__init__(dataio_type, public_type) | ||
|         super().__init__(dataio_type, f"struct {struct_type}") | ||
|         self.struct_type = struct_type | ||
|     @cache | ||
|     def array(self, size: SizeInfo) -> "FieldType": | ||
|         """Construct a FieldType for an array or vector with element type | ||
|         self and the given size""" | ||
|         if f"{size.declared}" == "*": | ||
|             return SpecvecType(self) | ||
|         return super().array(size) | ||
|     def get_code_param(self, location: Location) -> str: | ||
|         if not location.depth: | ||
| ... | ... | |
|     def _get_code_get_diff(self, location: Location, packet: str) -> str: | ||
|         """Helper method. Generate array-diff get code.""" | ||
|         # we're nested two levels deep in the JSON structure | ||
|         value_get = prefix("  ", self.elem.get_code_get(location.sub_full(2), packet, True)) | ||
|         # Note: At the moment, we're only deep-diffing our elements | ||
|         # if our array size is constant | ||
|         value_get = prefix("  ", self.elem.get_code_get(location.sub_full(2), packet, self.size.constant)) | ||
|         index_get = prefix("  ", self.size.index_get(packet, location)) | ||
|         return f"""\ | ||
| {self.size.size_check_get(location.name, packet)}\ | ||
| ... | ... | |
|         return f"{self.elem}[{self.size}]" | ||
| class SpecvecType(StructType): | ||
|     """Type information for a specialized vector field""" | ||
|     elem: FieldType | ||
|     """The type of the vector elements""" | ||
|     complex: bool = True | ||
|     def __init__(self, elem: StructType): | ||
|         if elem.complex: | ||
|             raise ValueError("vectors with complex fields are not supported") | ||
|         super().__init__(elem.dataio_type, f"struct {elem.struct_type}_vector") | ||
|         self.elem = elem | ||
|     @cache | ||
|     def _size(self, location: Location) -> SizeInfo: | ||
|         return SizeInfo("GENERATE_PACKETS_ERROR", location.replace(f"{self.struct_type}_size(&{location})")) | ||
|     def get_code_init(self, location: Location, packet: str) -> str: | ||
|         return f"""\ | ||
| {self.struct_type}_init(&{location @ packet}); | ||
| """ | ||
|     def get_code_copy(self, location: Location, dest: str, src: str) -> str: | ||
|         return f"""\ | ||
| {self.struct_type}_copy(&{location @ dest}, &{location @ src}); | ||
| """ | ||
|     def get_code_free(self, location: Location, packet: str) -> str: | ||
|         return f"""\ | ||
| {self.struct_type}_free(&{location @ packet}); | ||
| """ | ||
|     def get_code_cmp(self, location: Location, new: str, old: str) -> str: | ||
|         size = self._size(location) | ||
|         ## sub = location.deeper(f"(*{self.struct_type}_get(&{location}, {location.index}))") | ||
|         sub = location.deeper(f"{location}.p[{location.index}]") | ||
|         inner_cmp = prefix("    ", self.elem.get_code_cmp(sub, new, old)) | ||
|         return f"""\ | ||
| differ = ({size.actual @ old} != {size.actual @ new}); | ||
| if (!differ) {{ | ||
|   int {location.index}; | ||
|   for ({location.index} = 0; {location.index} < {size.actual @ new}; {location.index}++) {{ | ||
| {inner_cmp}\ | ||
|     if (differ) {{ | ||
|       break; | ||
|     }} | ||
|   }} | ||
| }} | ||
| """ | ||
|     def _get_code_put_full(self, location: Location, packet: str) -> str: | ||
|         size = self._size(location) | ||
|         ## sub = location.deeper(f"(*{self.struct_type}_get(&{location}, {location.index}))") | ||
|         sub = location.deeper(f"{location}.p[{location.index}]") | ||
|         inner_put = prefix("    ", self.elem.get_code_put(sub, packet)) | ||
|         # Note: strictly speaking, we could allow size == MAX_UINT16, | ||
|         # but we might want to use that in the future to signal overlong | ||
|         # vectors (like with jumbo packets) | ||
|         # Though that would also mean packets larger than 64 KiB, | ||
|         # which we're a long way from | ||
|         return f"""\ | ||
| fc_assert({size.actual @ packet} < MAX_UINT16); | ||
| {{ | ||
|   int {location.index}; | ||
|   e |= DIO_PUT(arraylen, &dout, &field_addr, {size.actual @ packet}); | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|   /* Enter the array. */ | ||
|   {location.json_subloc} = plocation_elem_new(0); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
|   for ({location.index} = 0; {location.index} < {size.actual @ packet}; {location.index}++) {{ | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|     /* Next array element. */ | ||
|     {location.json_subloc}->number = {location.index}; | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| {inner_put}\ | ||
|   }} | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|   /* Exit array. */ | ||
|   FC_FREE({location.json_subloc}); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| }} | ||
| """ | ||
|     def _get_code_put_diff(self, location: Location, packet: str, diff_packet: str) -> str: | ||
|         size = self._size(location) | ||
|         # we're nesting two levels deep in the JSON structure | ||
|         ## sub = location.deeper(f"(*{self.struct_type}_get(&{location}, {location.index}))", 2) | ||
|         sub = location.deeper(f"{location}.p[{location.index}]", 2) | ||
|         # Note: At the moment, we're only deep-diffing our elements | ||
|         # if our array size is constant | ||
|         value_put = prefix("    ", self.elem.get_code_put(sub, packet)) | ||
|         inner_cmp = prefix("      ", self.elem.get_code_cmp(sub, packet, diff_packet)) | ||
|         index_put = prefix("    ", size.index_put(packet, location.index)) | ||
|         index_put_sentinel = prefix("  ", size.index_put(packet, size.actual @ packet)) | ||
|         return f"""\ | ||
| {size.size_check_index(location.name, packet)}\ | ||
| {{ | ||
|   int {location.index}; | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|   size_t count_{location.index} = 0; | ||
|   /* Create the object to hold new size and delta. */ | ||
|   e |= DIO_PUT(object, &dout, &field_addr); | ||
|   /* Enter object (start at size address). */ | ||
|   {location.json_subloc} = plocation_field_new("size"); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
|   /* Write the new size */ | ||
|   e |= DIO_PUT(uint16, &dout, &field_addr, {size.actual @ packet}); | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|   /* Delta address. */ | ||
|   {location.json_subloc}->name = "delta"; | ||
|   /* Create the array. */ | ||
|   e |= DIO_PUT(farray, &dout, &field_addr, 0); | ||
|   /* Enter array. */ | ||
|   {location.json_subloc}->sub_location = plocation_elem_new(0); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
|   for ({location.index} = 0; {location.index} < {size.actual @ packet}; {location.index}++) {{ | ||
|     if ({location.index} < {size.actual @ diff_packet}) {{ | ||
| {inner_cmp}\ | ||
|     }} else {{ | ||
|       /* Always transmit new elements */ | ||
|       differ = TRUE; | ||
|     }} | ||
|     if (!differ) {{ | ||
|       continue; | ||
|     }} | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|     /* Append next diff array element. */ | ||
|     {location.json_subloc}->sub_location->number = -1; | ||
|     /* 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"); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
|     /* Write the index */ | ||
| {index_put}\ | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|     /* Content address. */ | ||
|     {location.json_subloc}->sub_location->sub_location->name = "data"; | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| {value_put}\ | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|     /* Exit diff array element. */ | ||
|     FC_FREE({location.json_subloc}->sub_location->sub_location); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
|   }} | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|   /* Append diff array element. */ | ||
|   {location.json_subloc}->sub_location->number = -1; | ||
|   /* Create the terminating 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"); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
|   /* Write the sentinel value */ | ||
| {index_put_sentinel}\ | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|   /* Exit diff array element, array, and object. */ | ||
|   FC_FREE({location.json_subloc}->sub_location->sub_location); | ||
|   FC_FREE({location.json_subloc}->sub_location); | ||
|   FC_FREE({location.json_subloc}); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| }} | ||
| """ | ||
|     def get_code_put(self, location: Location, packet: str, diff_packet: "str | None" = None) -> str: | ||
|         if diff_packet is not None: | ||
|             return self._get_code_put_diff(location, packet, diff_packet) | ||
|         else: | ||
|             return self._get_code_put_full(location, packet) | ||
|     def _get_code_get_full(self, location: Location, packet: str) -> str: | ||
|         size = self._size(location) | ||
|         ## sub = location.deeper(f"(*{self.struct_type}_get(&{location}, {location.index}))") | ||
|         sub = location.deeper(f"{location}.p[{location.index}]") | ||
|         inner_get = prefix("    ", self.elem.get_code_get(sub, packet)) | ||
|         # if elem is complex, adjusting vector size takes extra work | ||
|         # not currently supported; enforced in self.__init__() | ||
|         assert not self.elem.complex | ||
|         return f"""\ | ||
| {{ | ||
|   int {location.index}; | ||
|   if (!DIO_GET(arraylen, &din, &field_addr, &{location.index})) {{ | ||
|     RECEIVE_PACKET_FIELD_ERROR({location.name}); | ||
|   }} | ||
|   {self.struct_type}_reserve(&{location @ packet}, {location.index}); | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|   /* Enter array. */ | ||
|   {location.json_subloc} = plocation_elem_new(0); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
|   for ({location.index} = 0; {location.index} < {size.actual @ packet}; {location.index}++) {{ | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|     {location.json_subloc}->number = {location.index}; | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| {inner_get}\ | ||
|   }} | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|   /* Exit array. */ | ||
|   FC_FREE({location.json_subloc}); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| }} | ||
| """ | ||
|     def _get_code_get_diff(self, location: Location, packet: str) -> str: | ||
|         size = self._size(location) | ||
|         # we're nested two levels deep in the JSON structure | ||
|         ## sub = location.deeper(f"(*{self.struct_type}_get(&{location}, {location.index}))", 2) | ||
|         sub = location.deeper(f"{location}.p[{location.index}]", 2) | ||
|         # Note: At the moment, we're only deep-diffing our elements | ||
|         # if our array size is constant | ||
|         value_get = prefix("  ", self.elem.get_code_get(sub, packet)) | ||
|         index_get = prefix("  ", size.index_get(packet, location)) | ||
|         # if elem is complex, adjusting vector size takes extra work | ||
|         # not currently supported; enforced in self.__init__() | ||
|         assert not self.elem.complex | ||
|         return f"""\ | ||
| {size.size_check_index(location.name, packet)}\ | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
| /* Enter object (start at size address). */ | ||
| {location.json_subloc} = plocation_field_new("size"); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| {{ | ||
|   int readin; | ||
|   if (!DIO_GET(uint16, &din, &field_addr, &readin)) {{ | ||
|     RECEIVE_PACKET_FIELD_ERROR({location.name}); | ||
|   }} | ||
|   {self.struct_type}_reserve(&{location @ packet}, readin); | ||
| }} | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
| /* Delta address. */ | ||
| {location.json_subloc}->name = "delta"; | ||
| /* Enter array (start at initial element). */ | ||
| {location.json_subloc}->sub_location = plocation_elem_new(0); | ||
| /* Enter diff array element (start at the index address). */ | ||
| {location.json_subloc}->sub_location->sub_location = plocation_field_new("index"); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| while (TRUE) {{ | ||
|   int {location.index}; | ||
|   /* Read next index */ | ||
| {index_get}\ | ||
|   if ({location.index} == {size.actual @ packet}) {{ | ||
|     break; | ||
|   }} | ||
|   if ({location.index} > {size.actual @ packet}) {{ | ||
|     RECEIVE_PACKET_FIELD_ERROR({location.name}, | ||
|                                ": unexpected value %d " | ||
|                                "(> vector length) in array diff", | ||
|                                {location.index}); | ||
|   }} | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|   /* Content address. */ | ||
|   {location.json_subloc}->sub_location->sub_location->name = "data"; | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| {value_get}\ | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
|   /* Move to the next diff array element. */ | ||
|   {location.json_subloc}->sub_location->number++; | ||
|   /* Back to the index address. */ | ||
|   {location.json_subloc}->sub_location->sub_location->name = "index"; | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| }} | ||
| #ifdef FREECIV_JSON_CONNECTION | ||
| /* Exit diff array element, array, and object. */ | ||
| FC_FREE({location.json_subloc}->sub_location->sub_location); | ||
| FC_FREE({location.json_subloc}->sub_location); | ||
| FC_FREE({location.json_subloc}); | ||
| #endif /* FREECIV_JSON_CONNECTION */ | ||
| """ | ||
|     def get_code_get(self, location: Location, packet: str, deep_diff: bool = False) -> str: | ||
|         if deep_diff: | ||
|             return self._get_code_get_diff(location, packet) | ||
|         else: | ||
|             return self._get_code_get_full(location, packet) | ||
|     def __str__(self) -> str: | ||
|         return f"{self.elem}[*]" | ||
| class StrvecType(FieldType): | ||
|     """Type information for a string vector field""" | ||
| common/networking/packets.def | ||
|---|---|---|
|     Fields: | ||
|     ------- | ||
|       Comma separated list of names. Each name can have zero, one or | ||
|       two array declarations. So "x", "x[10]" and "x[20][10]" is | ||
|       Comma separated list of names. Each name can have any number of | ||
|       array size declarations. So "x", "x[10]", "x[20][10]" etc. is | ||
|       possible. The array size in the "[]" can be specified plain as a | ||
|       term. In this case all elements will be transmitted. If this is | ||
|       not-desired you can specify the amount of elements to be | ||
|       transferred by given the number. So the extended format is | ||
|       "[<full-array-size>:<elements-to-transfer>]". elements-to-transfer | ||
|       is relative to the packet. | ||
|       Alternatively, an array size of [*] denotes a vector of arbitrary | ||
|       size, where the current size is stored and transmitted as part of | ||
|       the field itself. Only some types support this. | ||
|    Field flags: | ||
|    ------------ | ||