mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-04 03:11:40 +00:00
Auto-generate packet dataclasses with Jinja (#1374)
This commit is contained in:
24
photon-serde/README.md
Normal file
24
photon-serde/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Photon Serde Autocode
|
||||
|
||||
Like Rosmsg. But worse.
|
||||
|
||||

|
||||
|
||||
## Goals
|
||||
|
||||
- As fast as possible (only slightly slower than packed structs, ideally)
|
||||
- Support for variable length arrays and optional types
|
||||
- Allow deserialization into user-defined, possibly nested, types. See [ResultList](src/targeting/resultlist.h) for an example of this.
|
||||
|
||||
## Design
|
||||
|
||||
The code for a single type is split across 3 files. Let's look at PnpResult:
|
||||
- [The struct definition](src/struct/pnpresult_struct.h): This is the data the object holds. Auto-generated. The data this object holds can be primitives or other, fully-deserialized types (like Vec2)
|
||||
- [The user class](src/targeting/pnpresult_struct.h): This is the fully-deserialized PnpResult type. This contains extra functions users might need to expose like `Amgiguity`, or other computed helper things.
|
||||
- [The serde interface](src/serde/pnpresult_struct.h): This is a template specilization for converting the user class to/from bytes
|
||||
|
||||
## Prior art
|
||||
|
||||
- Protobuf: slow on embedded platforms (at least quickbuf is)
|
||||
- Wpi's struct: no VLAs/optionals
|
||||
- Rosmsg: I'm not using ros, but I'm stealing their message hash idea
|
||||
314
photon-serde/generate_messages.py
Normal file
314
photon-serde/generate_messages.py
Normal file
@@ -0,0 +1,314 @@
|
||||
#!/usr/bin/env python3
|
||||
###############################################################################
|
||||
## Copyright (C) Photon Vision.
|
||||
###############################################################################
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
###############################################################################
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, TypedDict, cast
|
||||
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
|
||||
class SerdeField(TypedDict):
|
||||
name: str
|
||||
type: str
|
||||
# optional extra args
|
||||
optional: bool
|
||||
vla: bool
|
||||
|
||||
|
||||
class MessageType(TypedDict):
|
||||
name: str
|
||||
fields: List[SerdeField]
|
||||
# will be 'shim' if shimmed, and the shims will be set
|
||||
shimmed: bool
|
||||
java_decode_shim: str
|
||||
java_encode_shim: str
|
||||
# C++ helpers
|
||||
cpp_include: str
|
||||
# python shim types
|
||||
python_decode_shim: str
|
||||
|
||||
|
||||
def yaml_to_dict(path: str):
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
yaml_file_path = os.path.join(script_dir, path)
|
||||
|
||||
with open(yaml_file_path, "r") as file:
|
||||
file_dict: dict = yaml.safe_load(file)
|
||||
|
||||
return file_dict
|
||||
|
||||
|
||||
data_types = yaml_to_dict("message_data_types.yaml")
|
||||
|
||||
|
||||
# Helper to check if we need to use our own decoder
|
||||
def is_intrinsic_type(type_str: str):
|
||||
ret = type_str in data_types.keys()
|
||||
return ret
|
||||
|
||||
|
||||
# Deal with shimmed types
|
||||
def get_shimmed_filter(message_db):
|
||||
def is_shimmed(message_name: str):
|
||||
# We don't (yet) support shimming intrinsic types
|
||||
if is_intrinsic_type(message_name):
|
||||
return False
|
||||
|
||||
message = get_message_by_name(message_db, message_name)
|
||||
return "shimmed" in message and message["shimmed"] == True
|
||||
|
||||
return is_shimmed
|
||||
|
||||
|
||||
def get_qualified_cpp_name(
|
||||
message_db: List[MessageType], data_types, field: SerdeField
|
||||
):
|
||||
"""
|
||||
Get the full name of the type encoded. Eg:
|
||||
std::optional<photon::TargetCorner>
|
||||
std::array<frc::Transform3d>
|
||||
"""
|
||||
|
||||
if get_shimmed_filter(message_db)(field["type"]):
|
||||
base_type = get_message_by_name(message_db, field["type"])["cpp_type"]
|
||||
else:
|
||||
base_type = data_types[field["type"]]["cpp_type"]
|
||||
|
||||
if "optional" in field and field["optional"] == True:
|
||||
typestr = f"std::optional<{base_type}>"
|
||||
elif "vla" in field and field["vla"] == True:
|
||||
typestr = f"std::vector<{base_type}>"
|
||||
else:
|
||||
typestr = base_type
|
||||
|
||||
return typestr
|
||||
|
||||
|
||||
def get_message_by_name(message_db: List[MessageType], message_name: str):
|
||||
try:
|
||||
return next(
|
||||
message for message in message_db if message["name"] == message_name
|
||||
)
|
||||
except StopIteration as e:
|
||||
raise Exception("Could not find " + message_name) from e
|
||||
|
||||
|
||||
def get_field_by_name(message: MessageType, field_name: str):
|
||||
return next(f for f in message["fields"] if f["name"] == field_name)
|
||||
|
||||
|
||||
def get_message_hash(message_db: List[MessageType], message: MessageType):
|
||||
"""
|
||||
Calculate a unique message hash via MD5 sum. This is a very similar approach to rosmsg, documented:
|
||||
http://wiki.ros.org/ROS/Technical%20Overview#Message_serialization_and_msg_MD5_sums
|
||||
|
||||
For non-intrinsic (user-defined) types, replace its type-string with the md5sum of the submessage definition
|
||||
"""
|
||||
|
||||
# replace the non-intrinsic typename with its hash
|
||||
modified_message = copy.deepcopy(message)
|
||||
fields_to_hash = [
|
||||
field
|
||||
for field in modified_message["fields"]
|
||||
if not is_intrinsic_type(field["type"])
|
||||
]
|
||||
|
||||
for field in fields_to_hash:
|
||||
sub_message = get_message_by_name(message_db, field["type"])
|
||||
subhash = get_message_hash(message_db, sub_message)
|
||||
|
||||
# change the type to be our new md5sum
|
||||
field["type"] = subhash.hexdigest()
|
||||
|
||||
# base case: message is all intrinsic types
|
||||
# Hash a comments-stripped version for message integrity checking
|
||||
cleaned_yaml = yaml.dump(modified_message, default_flow_style=False).strip()
|
||||
message_hash = hashlib.md5(cleaned_yaml.encode("ascii"))
|
||||
return message_hash
|
||||
|
||||
|
||||
def get_includes(db, message: MessageType) -> str:
|
||||
includes = []
|
||||
for field in message["fields"]:
|
||||
if not is_intrinsic_type(field["type"]):
|
||||
field_msg = get_message_by_name(db, field["type"])
|
||||
|
||||
if "shimmed" in field_msg and field_msg["shimmed"] == True:
|
||||
includes.append(field_msg["cpp_include"])
|
||||
else:
|
||||
# must be a photon type.
|
||||
includes.append(f"\"photon/targeting/{field_msg['name']}.h\"")
|
||||
|
||||
if "optional" in field and field["optional"] == True:
|
||||
includes.append("<optional>")
|
||||
if "vla" in field and field["vla"] == True:
|
||||
includes.append("<vector>")
|
||||
|
||||
# stdint types
|
||||
includes.append("<stdint.h>")
|
||||
|
||||
return sorted(set(includes))
|
||||
|
||||
|
||||
def parse_yaml():
|
||||
Path(__file__).resolve().parent
|
||||
config = yaml_to_dict("messages.yaml")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def get_struct_schema_str(message: MessageType):
|
||||
ret = ""
|
||||
|
||||
for field in message["fields"]:
|
||||
typestr = field["type"]
|
||||
if "optional" in field and field["optional"] == True:
|
||||
typestr += "?"
|
||||
if "vla" in field and field["vla"] == True:
|
||||
typestr += "[?]"
|
||||
ret += f"{typestr} {field['name']};"
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def generate_photon_messages(cpp_java_root, py_root, template_root):
|
||||
messages = parse_yaml()
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(str(template_root)),
|
||||
# autoescape=False,
|
||||
# keep_trailing_newline=False,
|
||||
)
|
||||
|
||||
env.filters["is_intrinsic"] = is_intrinsic_type
|
||||
env.filters["is_shimmed"] = get_shimmed_filter(messages)
|
||||
|
||||
# add our custom types
|
||||
extended_data_types = data_types.copy()
|
||||
for message in messages:
|
||||
name = message["name"]
|
||||
extended_data_types[name] = {
|
||||
"len": -1,
|
||||
"java_type": name,
|
||||
"cpp_type": "photon::" + name,
|
||||
}
|
||||
|
||||
java_output_dir = Path(cpp_java_root) / "main/java/org/photonvision/struct"
|
||||
java_output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
cpp_serde_header_dir = Path(cpp_java_root) / "main/native/include/photon/serde/"
|
||||
cpp_serde_header_dir.mkdir(parents=True, exist_ok=True)
|
||||
cpp_serde_source_dir = Path(cpp_java_root) / "main/native/cpp/photon/serde/"
|
||||
cpp_serde_source_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
cpp_struct_header_dir = Path(cpp_java_root) / "main/native/include/photon/struct/"
|
||||
cpp_struct_header_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
py_serde_source_dir = Path(py_root)
|
||||
py_serde_source_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
env.filters["get_qualified_name"] = lambda field: get_qualified_cpp_name(
|
||||
messages, extended_data_types, field
|
||||
)
|
||||
|
||||
for message in messages:
|
||||
# don't generate shimmed types
|
||||
if get_shimmed_filter(messages)(message["name"]):
|
||||
continue
|
||||
|
||||
message = cast(MessageType, message)
|
||||
|
||||
java_name = f"{message['name']}Serde.java"
|
||||
cpp_serde_header_name = f"{message['name']}Serde.h"
|
||||
cpp_serde_source_name = f"{message['name']}Serde.cpp"
|
||||
cpp_struct_header_name = f"{message['name']}Struct.h"
|
||||
py_name = f"{message['name']}Serde.py"
|
||||
|
||||
java_template = env.get_template("Message.java.jinja")
|
||||
|
||||
cpp_serde_header_template = env.get_template("ThingSerde.h.jinja")
|
||||
cpp_serde_source_template = env.get_template("ThingSerde.cpp.jinja")
|
||||
cpp_struct_header_template = env.get_template("ThingStruct.h.jinja")
|
||||
|
||||
py_template = env.get_template("ThingSerde.py.jinja")
|
||||
|
||||
message_hash = get_message_hash(messages, message)
|
||||
|
||||
for output_name, template, output_folder in [
|
||||
[java_name, java_template, java_output_dir],
|
||||
[cpp_serde_header_name, cpp_serde_header_template, cpp_serde_header_dir],
|
||||
[cpp_serde_source_name, cpp_serde_source_template, cpp_serde_source_dir],
|
||||
[cpp_struct_header_name, cpp_struct_header_template, cpp_struct_header_dir],
|
||||
[py_name, py_template, py_serde_source_dir],
|
||||
]:
|
||||
# Hack in our message getter
|
||||
template.globals["get_message_by_name"] = lambda name: get_message_by_name(
|
||||
messages, name
|
||||
)
|
||||
|
||||
output_file = output_folder / output_name
|
||||
output_file.write_text(
|
||||
template.render(
|
||||
message,
|
||||
type_map=extended_data_types,
|
||||
message_fmt=get_struct_schema_str(message),
|
||||
message_hash=message_hash.hexdigest(),
|
||||
cpp_includes=get_includes(messages, message),
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def main(argv):
|
||||
script_path = Path(__file__).resolve()
|
||||
dirname = script_path.parent
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--cpp_java_output_dir",
|
||||
help="Optional. If set, will output the generated files to this directory, otherwise it will use a path relative to the script",
|
||||
default=dirname.parent / "photon-targeting/src/generated",
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--py_output_dir",
|
||||
help="Optional. If set, will spit Python serde files here",
|
||||
default=dirname.parent / "photon-lib/py/photonlibpy/generated",
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--template_root",
|
||||
help="Optional. If set, will use this directory as the root for the jinja templates",
|
||||
default=dirname / "templates",
|
||||
type=Path,
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
generate_photon_messages(
|
||||
args.cpp_java_output_dir, args.py_output_dir, args.template_root
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
||||
33
photon-serde/message_data_types.yaml
Normal file
33
photon-serde/message_data_types.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
bool:
|
||||
# length in bytes
|
||||
len: 1
|
||||
java_type: bool
|
||||
cpp_type: bool
|
||||
java_decode_method: decodeBoolean
|
||||
int16:
|
||||
len: 2
|
||||
java_type: short
|
||||
cpp_type: int16_t
|
||||
java_decode_method: decodeShort
|
||||
java_list_decode_method: decodeShortList
|
||||
int32:
|
||||
len: 4
|
||||
java_type: int
|
||||
cpp_type: int32_t
|
||||
java_decode_method: decodeInt
|
||||
int64:
|
||||
len: 8
|
||||
java_type: long
|
||||
cpp_type: int64_t
|
||||
java_decode_method: decodeLong
|
||||
float32:
|
||||
len: 4
|
||||
java_type: float
|
||||
cpp_type: float
|
||||
java_decode_method: decodeFloat
|
||||
float64:
|
||||
len: 8
|
||||
java_type: double
|
||||
cpp_type: double
|
||||
java_decode_method: decodeDouble
|
||||
90
photon-serde/messages.yaml
Normal file
90
photon-serde/messages.yaml
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
- name: PhotonPipelineMetadata
|
||||
fields:
|
||||
- name: sequenceID
|
||||
type: int64
|
||||
- name: captureTimestampMicros
|
||||
type: int64
|
||||
- name: publishTimestampMicros
|
||||
type: int64
|
||||
|
||||
- name: Transform3d
|
||||
shimmed: True
|
||||
java_decode_shim: PacketUtils.unpackTransform3d
|
||||
java_encode_shim: PacketUtils.packTransform3d
|
||||
cpp_type: frc::Transform3d
|
||||
cpp_include: "<frc/geometry/Transform3d.h>"
|
||||
python_decode_shim: packet.decodeTransform
|
||||
# shim since we expect fields to at least exist
|
||||
fields: []
|
||||
|
||||
|
||||
- name: TargetCorner
|
||||
fields:
|
||||
- name: x
|
||||
type: float64
|
||||
- name: y
|
||||
type: float64
|
||||
|
||||
- name: PhotonTrackedTarget
|
||||
fields:
|
||||
- name: yaw
|
||||
type: float64
|
||||
- name: pitch
|
||||
type: float64
|
||||
- name: area
|
||||
type: float64
|
||||
- name: skew
|
||||
type: float64
|
||||
- name: fiducialId
|
||||
type: int32
|
||||
- name: objDetectId
|
||||
type: int32
|
||||
- name: objDetectConf
|
||||
type: float32
|
||||
- name: bestCameraToTarget
|
||||
type: Transform3d
|
||||
- name: altCameraToTarget
|
||||
type: Transform3d
|
||||
- name: poseAmbiguity
|
||||
type: float64
|
||||
- name: minAreaRectCorners
|
||||
type: TargetCorner
|
||||
vla: True
|
||||
- name: detectedCorners
|
||||
type: TargetCorner
|
||||
vla: True
|
||||
|
||||
- name: PnpResult
|
||||
fields:
|
||||
- name: best
|
||||
type: Transform3d
|
||||
comment: "This is a comment"
|
||||
- name: alt
|
||||
type: Transform3d
|
||||
- name: bestReprojErr
|
||||
type: float64
|
||||
- name: altReprojErr
|
||||
type: float64
|
||||
- name: ambiguity
|
||||
type: float64
|
||||
|
||||
- name: MultiTargetPNPResult
|
||||
fields:
|
||||
- name: estimatedPose
|
||||
type: PnpResult
|
||||
- name: fiducialIDsUsed
|
||||
type: int16
|
||||
vla: True
|
||||
|
||||
|
||||
- name: PhotonPipelineResult
|
||||
fields:
|
||||
- name: metadata
|
||||
type: PhotonPipelineMetadata
|
||||
- name: targets
|
||||
type: PhotonTrackedTarget
|
||||
vla: True
|
||||
- name: multitagResult
|
||||
type: MultiTargetPNPResult
|
||||
optional: True
|
||||
103
photon-serde/templates/Message.java.jinja
Normal file
103
photon-serde/templates/Message.java.jinja
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||
|
||||
package org.photonvision.struct;
|
||||
|
||||
import org.photonvision.common.dataflow.structures.Packet;
|
||||
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||
import org.photonvision.utils.PacketUtils;
|
||||
|
||||
// Assume that the base class lives here and we can import it
|
||||
import org.photonvision.targeting.*;
|
||||
|
||||
|
||||
/**
|
||||
* Auto-generated serialization/deserialization helper for {{name}}
|
||||
*/
|
||||
public class {{ name }}Serde implements PacketSerde<{{name}}> {
|
||||
// Message definition md5sum. See photon_packet.adoc for details
|
||||
public static final String MESSAGE_VERSION = "{{ message_hash }}";
|
||||
public static final String MESSAGE_FORMAT = "{{ message_fmt }}";
|
||||
|
||||
public final String getTypeString() { return MESSAGE_FORMAT; }
|
||||
public final String getInterfaceUUID() { return MESSAGE_VERSION; }
|
||||
|
||||
@Override
|
||||
public int getMaxByteSize() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pack(Packet packet, {{ name }} value) {
|
||||
{%- for field in fields -%}
|
||||
{%- if field.type | is_shimmed %}
|
||||
// field is shimmed!
|
||||
{{ get_message_by_name(field.type).java_encode_shim }}(packet, value.{{ field.name }});
|
||||
{%- elif field.optional == True %}
|
||||
// {{ field.name }} is optional! it better not be a VLA too
|
||||
packet.encodeOptional(value.{{ field.name }});
|
||||
{%- elif field.vla == True and field.type | is_intrinsic %}
|
||||
// {{ field.name }} is a intrinsic VLA!
|
||||
packet.encode(value.{{ field.name }});
|
||||
{%- elif field.vla == True %}
|
||||
// {{ field.name }} is a custom VLA!
|
||||
packet.encodeList(value.{{ field.name }});
|
||||
{%- elif field.type | is_intrinsic %}
|
||||
// field {{ field.name }} is of intrinsic type {{ field.type }}
|
||||
packet.encode(({{ type_map[field.type].java_type }}) value.{{ field.name }});
|
||||
{%- else %}
|
||||
// field {{ field.name }} is of non-intrinsic type {{ field.type }}
|
||||
{{ field.type }}.photonStruct.pack(packet, value.{{ field.name }});
|
||||
{%- endif %}
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor%}
|
||||
}
|
||||
|
||||
@Override
|
||||
public {{ name }} unpack(Packet packet) {
|
||||
var ret = new {{ name }}();
|
||||
{% for field in fields -%}
|
||||
{%- if field.type | is_shimmed %}
|
||||
// field is shimmed!
|
||||
ret.{{ field.name }} = {{ get_message_by_name(field.type).java_decode_shim }}(packet);
|
||||
{%- elif field.optional == True %}
|
||||
// {{ field.name }} is optional! it better not be a VLA too
|
||||
ret.{{ field.name }} = packet.decodeOptional({{ field.type }}.photonStruct);
|
||||
{%- elif field.vla == True and not field.type | is_intrinsic %}
|
||||
// {{ field.name }} is a custom VLA!
|
||||
ret.{{ field.name }} = packet.decodeList({{ field.type }}.photonStruct);
|
||||
{%- elif field.vla == True and field.type | is_intrinsic %}
|
||||
// {{ field.name }} is a custom VLA!
|
||||
ret.{{ field.name }} = packet.decode{{ type_map[field.type].java_type.title() }}List();
|
||||
{%- elif field.type | is_intrinsic %}
|
||||
// {{ field.name }} is of intrinsic type {{ field.type }}
|
||||
ret.{{field.name}} = packet.{{ type_map[field.type].java_decode_method }}();
|
||||
{%- else %}
|
||||
// {{ field.name }} is of non-intrinsic type {{ field.type }}
|
||||
ret.{{field.name}} = {{ field.type }}.photonStruct.unpack(packet);
|
||||
{%- endif %}
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor%}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
44
photon-serde/templates/ThingSerde.cpp.jinja
Normal file
44
photon-serde/templates/ThingSerde.cpp.jinja
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||
|
||||
#include "photon/serde/{{ name }}Serde.h"
|
||||
|
||||
namespace photon {
|
||||
|
||||
using StructType = SerdeType<{{ name }}>;
|
||||
|
||||
void StructType::Pack(Packet& packet, const {{ name }}& value) {
|
||||
{% for field in fields -%}
|
||||
packet.Pack<{{ field | get_qualified_name }}>(value.{{ field.name }});
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
{{ name }} StructType::Unpack(Packet& packet) {
|
||||
return {{ name }}{ {{ name }}_PhotonStruct{
|
||||
{% for field in fields -%}
|
||||
.{{ field.name}} = packet.Unpack<{{ field | get_qualified_name }}>(),
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
}};
|
||||
}
|
||||
|
||||
} // namespace photon
|
||||
51
photon-serde/templates/ThingSerde.h.jinja
Normal file
51
photon-serde/templates/ThingSerde.h.jinja
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||
|
||||
#include <wpi/SymbolExports.h>
|
||||
|
||||
// Include myself
|
||||
#include "photon/dataflow/structures/Packet.h"
|
||||
#include "photon/targeting/{{ name }}.h"
|
||||
|
||||
// Includes for dependant types
|
||||
{% for include in cpp_includes -%}
|
||||
#include {{ include }}
|
||||
{% endfor %}
|
||||
|
||||
namespace photon {
|
||||
|
||||
template <>
|
||||
struct WPILIB_DLLEXPORT SerdeType<{{ name }}> {
|
||||
static constexpr std::string_view GetSchemaHash() {
|
||||
return "{{ message_hash }}";
|
||||
}
|
||||
|
||||
static constexpr std::string_view GetSchema() {
|
||||
return "{{ message_fmt }}";
|
||||
}
|
||||
|
||||
static photon::{{ name }} Unpack(photon::Packet& packet);
|
||||
static void Pack(photon::Packet& packet, const photon::{{ name }}& value);
|
||||
};
|
||||
|
||||
static_assert(photon::PhotonStructSerializable<photon::{{ name }}>);
|
||||
|
||||
} // namespace photon
|
||||
62
photon-serde/templates/ThingSerde.py.jinja
Normal file
62
photon-serde/templates/ThingSerde.py.jinja
Normal file
@@ -0,0 +1,62 @@
|
||||
###############################################################################
|
||||
## Copyright (C) Photon Vision.
|
||||
###############################################################################
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from ..targeting import *
|
||||
|
||||
class {{ name }}Serde:
|
||||
|
||||
# Message definition md5sum. See photon_packet.adoc for details
|
||||
MESSAGE_VERSION = "{{ message_hash }}"
|
||||
MESSAGE_FORMAT = "{{ message_fmt }}"
|
||||
|
||||
@staticmethod
|
||||
def unpack(packet: 'Packet') -> '{{ name }}':
|
||||
ret = {{ name }}()
|
||||
{% for field in fields -%}
|
||||
{%- if field.type | is_shimmed %}
|
||||
# field is shimmed!
|
||||
ret.{{ field.name }} = {{ get_message_by_name(field.type).python_decode_shim }}()
|
||||
{%- elif field.optional == True %}
|
||||
# {{ field.name }} is optional! it better not be a VLA too
|
||||
ret.{{ field.name }} = packet.decodeOptional({{ field.type }}.photonStruct)
|
||||
{%- elif field.vla == True and not field.type | is_intrinsic %}
|
||||
# {{ field.name }} is a custom VLA!
|
||||
ret.{{ field.name }} = packet.decodeList({{ field.type }}.photonStruct)
|
||||
{%- elif field.vla == True and field.type | is_intrinsic %}
|
||||
# {{ field.name }} is a custom VLA!
|
||||
ret.{{ field.name }} = packet.decode{{ type_map[field.type].java_type.title() }}List()
|
||||
{%- elif field.type | is_intrinsic %}
|
||||
# {{ field.name }} is of intrinsic type {{ field.type }}
|
||||
ret.{{field.name}} = packet.{{ type_map[field.type].java_decode_method }}()
|
||||
{%- else %}
|
||||
# {{ field.name }} is of non-intrinsic type {{ field.type }}
|
||||
ret.{{field.name}} = {{ field.type }}.photonStruct.unpack(packet)
|
||||
{%- endif %}
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor%}
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# Hack ourselves into the base class
|
||||
{{ name }}.photonStruct = {{ name }}Serde()
|
||||
39
photon-serde/templates/ThingStruct.h.jinja
Normal file
39
photon-serde/templates/ThingStruct.h.jinja
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||
|
||||
// Includes for dependant types
|
||||
{% for include in cpp_includes -%}
|
||||
#include {{ include }}
|
||||
{% endfor %}
|
||||
|
||||
namespace photon {
|
||||
|
||||
struct {{ name }}_PhotonStruct {
|
||||
{% for field in fields -%}
|
||||
{{ field | get_qualified_name }} {{ field.name }};
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
|
||||
friend bool operator==({{ name }}_PhotonStruct const&, {{ name }}_PhotonStruct const&) = default;
|
||||
};
|
||||
|
||||
} // namespace photon
|
||||
Reference in New Issue
Block a user