Skip to content

Commit f731daf

Browse files
authored
Merge pull request #143 from robotpy/struct-array
wpistruct: Add packArray/unpackArray
2 parents 10f45f7 + 50d82fa commit f731daf

File tree

6 files changed

+96
-0
lines changed

6 files changed

+96
-0
lines changed

subprojects/robotpy-wpiutil/gen/WPyStruct.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@ functions:
1414
pack:
1515
subpackage: wpistruct
1616
no_release_gil: true
17+
packArray:
18+
subpackage: wpistruct
19+
no_release_gil: true
1720
packInto:
1821
subpackage: wpistruct
1922
no_release_gil: true
2023
unpack:
2124
subpackage: wpistruct
2225
no_release_gil: true
26+
unpackArray:
27+
subpackage: wpistruct
28+
no_release_gil: true
2329
unpackInto:
2430
subpackage: wpistruct
2531
no_release_gil: true

subprojects/robotpy-wpiutil/tests/test_struct.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ def test_pack():
4646
assert wpistruct.pack(module.ThingA(1)) == b"\x01"
4747

4848

49+
def test_pack_array():
50+
assert wpistruct.packArray([module.ThingA(1), module.ThingA(2)]) == b"\x01\x02"
51+
52+
4953
def test_pack_into():
5054
buf = bytearray(1)
5155
wpistruct.packInto(module.ThingA(1), buf)
@@ -62,6 +66,13 @@ def test_unpack():
6266
assert wpistruct.unpack(module.ThingA, b"\x01") == module.ThingA(1)
6367

6468

69+
def test_unpack_array():
70+
assert wpistruct.unpackArray(module.ThingA, b"\x01\x02") == [
71+
module.ThingA(1),
72+
module.ThingA(2),
73+
]
74+
75+
6576
# def test_unpack_into():
6677
# r1 = module.ThingA(1)
6778
# r2 = module.ThingA(2)

subprojects/robotpy-wpiutil/wpiutil/src/wpistruct/wpystruct.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <wpi/struct/Struct.h>
1010

1111
#include <pybind11/functional.h>
12+
#include <pybind11/typing.h>
1213
#include <robotpy_build.h>
1314

1415
static inline std::string pytypename(const py::type &t) {

subprojects/robotpy-wpiutil/wpiutil/src/wpistruct/wpystruct_fns.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,40 @@ py::bytes pack(const WPyStruct &v) {
4545
return py::reinterpret_steal<py::bytes>(b);
4646
}
4747

48+
py::bytes packArray(const py::sequence &seq) {
49+
auto len = seq.size();
50+
if (len == 0) {
51+
return {};
52+
}
53+
54+
WPyStructInfo info(py::type::of(seq[0]));
55+
auto sz = wpi::GetStructSize<WPyStruct>(info);
56+
auto total = sz*len;
57+
58+
PyObject *b = PyBytes_FromStringAndSize(NULL, total);
59+
if (b == NULL) {
60+
throw py::error_already_set();
61+
}
62+
63+
char *pybuf;
64+
py::ssize_t pysz;
65+
if (PyBytes_AsStringAndSize(b, &pybuf, &pysz) != 0) {
66+
Py_DECREF(b);
67+
throw py::error_already_set();
68+
}
69+
70+
auto bytes_obj = py::reinterpret_steal<py::bytes>(b);
71+
72+
for (const auto &v: seq) {
73+
WPyStruct wv(v);
74+
auto s = std::span((uint8_t *)pybuf, sz);
75+
wpi::PackStruct(s, wv, info);
76+
pybuf += sz;
77+
}
78+
79+
return bytes_obj;
80+
}
81+
4882
void packInto(const WPyStruct &v, py::buffer &b) {
4983
WPyStructInfo info(v);
5084
py::ssize_t sz = wpi::GetStructSize<WPyStruct>(info);
@@ -83,6 +117,35 @@ WPyStruct unpack(const py::type &t, const py::buffer &b) {
83117
return wpi::UnpackStruct<WPyStruct, WPyStructInfo>(s, info);
84118
}
85119

120+
py::typing::List<WPyStruct> unpackArray(const py::type &t, const py::buffer &b) {
121+
WPyStructInfo info(t);
122+
py::ssize_t sz = wpi::GetStructSize<WPyStruct>(info);
123+
124+
auto req = b.request();
125+
if (req.itemsize != 1) {
126+
throw py::value_error("buffer must only contain bytes");
127+
} else if (req.ndim != 1) {
128+
throw py::value_error("buffer must only have a single dimension");
129+
}
130+
131+
if (req.size % sz != 0) {
132+
throw py::value_error("buffer must be multiple of " + std::to_string(sz) + " bytes");
133+
}
134+
135+
auto items = req.size / sz;
136+
py::list a(items);
137+
const uint8_t *ptr = (const uint8_t *)req.ptr;
138+
for (py::ssize_t i = 0; i < items; i++) {
139+
auto s = std::span(ptr, sz);
140+
auto v = wpi::UnpackStruct<WPyStruct, WPyStructInfo>(s, info);
141+
// steals a reference
142+
PyList_SET_ITEM(a.ptr(), i, v.py.inc_ref().ptr());
143+
ptr += sz;
144+
}
145+
146+
return a;
147+
}
148+
86149
// void unpackInto(const py::buffer &b, WPyStruct *v) {
87150
// WPyStructInfo info(*v);
88151
// py::ssize_t sz = wpi::GetStructSize<WPyStruct>(info);

subprojects/robotpy-wpiutil/wpiutil/src/wpistruct/wpystruct_fns.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ size_t getSize(const py::type &t);
3030
*/
3131
py::bytes pack(const WPyStruct &v);
3232

33+
/**
34+
Serialize objects into byte buffer
35+
*/
36+
py::bytes packArray(const py::sequence &seq);
37+
3338
/**
3439
Serialize object into byte buffer. Buffer must be exact size.
3540
*/
@@ -41,6 +46,12 @@ void packInto(const WPyStruct &v, py::buffer &b);
4146
*/
4247
WPyStruct unpack(const py::type &t, const py::buffer &b);
4348

49+
/**
50+
Convert byte buffer into list of objects of specified type. Buffer must be
51+
exact size.
52+
*/
53+
py::typing::List<WPyStruct> unpackArray(const py::type &t, const py::buffer &b);
54+
4455
// /**
4556
// Convert byte buffer into passed in object. Buffer must be exact
4657
// size.

subprojects/robotpy-wpiutil/wpiutil/wpistruct/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
getSize,
1111
getTypeName,
1212
pack,
13+
packArray,
1314
packInto,
1415
unpack,
16+
unpackArray,
1517
)
1618

1719
__all__ = [
@@ -20,8 +22,10 @@
2022
"getSize",
2123
"getTypeName",
2224
"pack",
25+
"packArray",
2326
"packInto",
2427
"unpack",
28+
"unpackArray",
2529
]
2630

2731
from .desc import StructDescriptor

0 commit comments

Comments
 (0)