Skip to content
This repository was archived by the owner on Oct 30, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions binding/emit_c_cpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,142 @@ def emit_c(ctx):
if (rc != 0) return NULL;
return arr;
}

/*
* Fixed-size struct arrays inside a struct (lv_point_precise_t p[3]).
*
* Reading the field gives a view over the parent struct's memory, so
* d.p[1].x = 7 and d.p[1] = point write through, and len(d.p) works. The view
* holds the parent so the memory stays alive. Assigning the field takes a
* sequence of exactly that many structs (or dicts), or a packed buffer of the
* array's size; anything else raises and leaves the field unchanged
* (lvgl-bindings#38).
*/

typedef PyObject *(*lvpy_struct_reader_t)(void *);

typedef struct {
PyObject_HEAD
PyObject *owner;
char *data;
Py_ssize_t len;
size_t elem_size;
lvpy_struct_reader_t read;
lvpy_struct_writer_t write;
} lvpy_struct_array_t;

static void lvpy_struct_array_dealloc(PyObject *self)
{
Py_XDECREF(((lvpy_struct_array_t *)self)->owner);
Py_TYPE(self)->tp_free(self);
}

static Py_ssize_t lvpy_struct_array_len(PyObject *self)
{
return ((lvpy_struct_array_t *)self)->len;
}

static PyObject *lvpy_struct_array_item(PyObject *self_in, Py_ssize_t i)
{
lvpy_struct_array_t *self = (lvpy_struct_array_t *)self_in;
if (i < 0 || i >= self->len) {
PyErr_SetString(PyExc_IndexError, "struct array index out of range");
return NULL;
}
return self->read(self->data + (size_t)i * self->elem_size);
}

static int lvpy_struct_array_ass_item(PyObject *self_in, Py_ssize_t i, PyObject *value)
{
lvpy_struct_array_t *self = (lvpy_struct_array_t *)self_in;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "can't delete struct array items");
return -1;
}
if (i < 0 || i >= self->len) {
PyErr_SetString(PyExc_IndexError, "struct array assignment index out of range");
return -1;
}
void *p = self->write(value);
if (PyErr_Occurred()) return -1;
memcpy(self->data + (size_t)i * self->elem_size, p, self->elem_size);
return 0;
}

static PySequenceMethods lvpy_struct_array_as_sequence = {
.sq_length = lvpy_struct_array_len,
.sq_item = lvpy_struct_array_item,
.sq_ass_item = lvpy_struct_array_ass_item,
};

static PyTypeObject lvpy_struct_array_type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "lvgl.StructArray",
.tp_basicsize = sizeof(lvpy_struct_array_t),
.tp_dealloc = lvpy_struct_array_dealloc,
.tp_as_sequence = &lvpy_struct_array_as_sequence,
.tp_flags = Py_TPFLAGS_DEFAULT,
};

static inline PyObject *lvpy_struct_array_new(PyObject *owner, void *data,
Py_ssize_t len, size_t elem_size, lvpy_struct_reader_t read, lvpy_struct_writer_t write)
{
if (!(lvpy_struct_array_type.tp_flags & Py_TPFLAGS_READY) &&
PyType_Ready(&lvpy_struct_array_type) < 0)
return NULL;
lvpy_struct_array_t *self = PyObject_New(lvpy_struct_array_t, &lvpy_struct_array_type);
if (!self) return NULL;
Py_XINCREF(owner);
self->owner = owner;
self->data = (char *)data;
self->len = len;
self->elem_size = elem_size;
self->read = read;
self->write = write;
return (PyObject *)self;
}

/* Sets an exception and leaves data unchanged on any mismatch. */
static inline void lvpy_struct_array_assign(void *data, Py_ssize_t len,
size_t elem_size, lvpy_struct_writer_t write, PyObject *value)
{
size_t total = (size_t)len * elem_size;
if (PyBytes_Check(value) || PyByteArray_Check(value) || PyMemoryView_Check(value)) {
Py_buffer view;
if (PyObject_GetBuffer(value, &view, PyBUF_C_CONTIGUOUS) < 0) return;
if ((size_t)view.len != total)
PyErr_Format(PyExc_ValueError, "expected a buffer of %zu bytes, got %zd",
total, view.len);
else
memcpy(data, view.buf, total);
PyBuffer_Release(&view);
return;
}
if (!PySequence_Check(value) || PyUnicode_Check(value)) {
PyErr_Format(PyExc_TypeError, "expected a sequence of %zd structs, got '%s'",
len, Py_TYPE(value)->tp_name);
return;
}
PyObject *items = PySequence_Tuple(value);
if (!items) return;
if (PyTuple_GET_SIZE(items) != len) {
PyErr_Format(PyExc_ValueError, "expected %zd items, got %zd",
len, PyTuple_GET_SIZE(items));
Py_DECREF(items);
return;
}
/* Convert every item before writing any, so a bad item changes nothing. */
char *tmp = (char *)PyMem_Malloc(total ? total : 1);
if (!tmp) { Py_DECREF(items); PyErr_NoMemory(); return; }
for (Py_ssize_t i = 0; i < len; i++) {
void *p = write(PyTuple_GET_ITEM(items, i));
if (PyErr_Occurred()) { PyMem_Free(tmp); Py_DECREF(items); return; }
memcpy(tmp + (size_t)i * elem_size, p, elem_size);
}
memcpy(data, tmp, total);
PyMem_Free(tmp);
Py_DECREF(items);
}
"""
)

Expand Down
164 changes: 163 additions & 1 deletion binding/emit_c_micropython_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def emit_c(ctx):
#include "py/obj.h"
#include "py/objint.h"
#include "py/objstr.h"
#include "py/objtuple.h"
#include "py/runtime.h"
#include "py/binary.h"
#include "py/objarray.h"
Expand Down Expand Up @@ -1131,6 +1132,124 @@ def emit_c(ctx):
locals_dict, &mp_base_struct_locals_dict
);

/*
* Fixed-size struct arrays inside a struct (lv_point_precise_t p[3]).
*
* Reading the field gives a view over the parent struct's memory, so
* d.p[1].x = 7 and d.p[1] = point write through, and len(d.p) works. The view
* holds the parent so the memory stays alive. Assigning the field takes a
* sequence of exactly that many structs (or dicts), or a packed buffer of the
* array's size; anything else raises and leaves the field unchanged
* (lvgl-bindings#38).
*/

typedef struct {
mp_obj_base_t base;
mp_obj_t owner;
char *data;
size_t len;
size_t elem_size;
mp_obj_t (*read)(void *elem);
void *(*write)(mp_obj_t value);
} mp_lv_struct_array_t;

static mp_obj_t lv_struct_array_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value)
{
mp_lv_struct_array_t *self = MP_OBJ_TO_PTR(self_in);
if (value == MP_OBJ_NULL) {
mp_raise_TypeError(MP_ERROR_TEXT("can't delete struct array items"));
}
size_t i = mp_get_index(self->base.type, self->len, index, false);
char *elem = self->data + i * self->elem_size;
if (value == MP_OBJ_SENTINEL) {
return self->read(elem);
}
memcpy(elem, self->write(value), self->elem_size);
return mp_const_none;
}

static mp_obj_t lv_struct_array_unary_op(mp_unary_op_t op, mp_obj_t self_in)
{
mp_lv_struct_array_t *self = MP_OBJ_TO_PTR(self_in);
switch (op) {
case MP_UNARY_OP_LEN: return MP_OBJ_NEW_SMALL_INT(self->len);
case MP_UNARY_OP_BOOL: return mp_obj_new_bool(self->len != 0);
default: return MP_OBJ_NULL;
}
}

// Iterates views of the elements, the same objects indexing returns.
static mp_obj_t lv_struct_array_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf)
{
mp_lv_struct_array_t *self = MP_OBJ_TO_PTR(self_in);
mp_obj_t items = mp_obj_new_tuple(self->len, NULL);
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(items);
for (size_t i = 0; i < self->len; i++) {
t->items[i] = self->read(self->data + i * self->elem_size);
}
return mp_getiter(items, iter_buf);
}

static MP_DEFINE_CONST_OBJ_TYPE(
mp_lv_struct_array_type,
MP_QSTR_StructArray,
MP_TYPE_FLAG_ITER_IS_GETITER,
unary_op, lv_struct_array_unary_op,
subscr, lv_struct_array_subscr,
iter, lv_struct_array_getiter
);

GENMPY_UNUSED static mp_obj_t mp_lv_struct_array_new(mp_obj_t owner, void *data,
size_t len, size_t elem_size, mp_obj_t (*read)(void *), void *(*write)(mp_obj_t))
{
mp_lv_struct_array_t *self = m_new_obj(mp_lv_struct_array_t);
self->base.type = &mp_lv_struct_array_type;
self->owner = owner;
self->data = (char *)data;
self->len = len;
self->elem_size = elem_size;
self->read = read;
self->write = write;
return MP_OBJ_FROM_PTR(self);
}

GENMPY_UNUSED static void mp_lv_struct_array_assign(void *data, size_t len,
size_t elem_size, void *(*write)(mp_obj_t), mp_obj_t value)
{
if (MP_OBJ_IS_STR_OR_BYTES(value) ||
MP_OBJ_IS_TYPE(value, &mp_type_bytearray) ||
MP_OBJ_IS_TYPE(value, &mp_type_memoryview)) {
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(value, &bufinfo, MP_BUFFER_READ);
if (bufinfo.len != len * elem_size) {
mp_raise_msg_varg(&mp_type_ValueError,
MP_ERROR_TEXT("expected a buffer of %d bytes, got %d"),
(int)(len * elem_size), (int)bufinfo.len);
}
memcpy(data, bufinfo.buf, bufinfo.len);
return;
}
mp_obj_t mp_len = mp_obj_len_maybe(value);
if (mp_len == MP_OBJ_NULL) {
mp_raise_msg_varg(&mp_type_TypeError,
MP_ERROR_TEXT("expected a sequence of %d structs, got '%s'"),
(int)len, mp_obj_get_type_str(value));
}
if ((size_t)mp_obj_get_int(mp_len) != len) {
mp_raise_msg_varg(&mp_type_ValueError,
MP_ERROR_TEXT("expected %d items, got %d"),
(int)len, (int)mp_obj_get_int(mp_len));
}
// Convert every item before writing any, so a bad item changes nothing.
char *tmp = m_new(char, len * elem_size);
for (size_t i = 0; i < len; i++) {
mp_obj_t item = mp_obj_subscr(value, MP_OBJ_NEW_SMALL_INT(i), MP_OBJ_SENTINEL);
memcpy(tmp + i * elem_size, write(item), elem_size);
}
memcpy(data, tmp, len * elem_size);
m_del(char, tmp, len * elem_size);
}

GENMPY_UNUSED static mp_obj_t mp_array_from_ptr(void *lv_arr, size_t element_size, bool is_signed)
{
mp_lv_array_t *self = m_new_obj(mp_lv_array_t);
Expand Down Expand Up @@ -1440,6 +1559,18 @@ def flatten_struct(struct_decls):
return result


def _struct_array_elem_rw(decl):
"""(reader, writer) by pointer for a struct-element array field, else None."""
elem_ast = decl.type.type
if not isinstance(elem_ast, c_ast.TypeDecl):
return None
ptr_type = "%s *" % get_type(elem_ast, remove_quals=True)
read = lv_to_mp.get(ptr_type) or ""
write = mp_to_lv.get(ptr_type) or ""
if read.startswith("mp_read_ptr_") and write.startswith("mp_write_ptr_"):
return read, write
return None

def try_generate_struct(struct_name, struct):
if struct_name in generated_structs:
return None
Expand Down Expand Up @@ -1581,7 +1712,38 @@ def try_generate_struct(struct_name, struct):
not hasattr(decl.type, "quals")
) or "const" not in decl.type.quals
# Arrays must be handled by memcpy, otherwise we would get "assignment to expression with array type" error
if isinstance(decl.type, c_ast.ArrayDecl):
elem_rw = (
_struct_array_elem_rw(decl)
if isinstance(decl.type, c_ast.ArrayDecl)
else None
)
if elem_rw:
# A view, not a list of copies (lvgl-bindings#38).
elem_read, elem_write = elem_rw
shape = "data->{decl_name}, {dim}, sizeof({elem})".format(
decl_name=decl.name,
dim=gen.visit(decl.type.dim),
elem=gen.visit(decl.type.type),
)
if is_writeable:
write_cases.append(
"case MP_QSTR_{field}: mp_lv_struct_array_assign({shape}, {write}, dest[1]); break; // converting to {type_name}".format(
field=sanitize(decl.name),
shape=shape,
write=elem_write,
type_name=type_name,
)
)
read_cases.append(
"case MP_QSTR_{field}: dest[0] = mp_lv_struct_array_new(self_in, {shape}, {read}, {write}); break; // converting from {type_name}".format(
field=sanitize(decl.name),
shape=shape,
read=elem_read,
write=elem_write,
type_name=type_name,
)
)
elif isinstance(decl.type, c_ast.ArrayDecl):
memcpy_size = "sizeof(%s)*%s" % (
gen.visit(decl.type.type),
gen.visit(decl.type.dim),
Expand Down
47 changes: 46 additions & 1 deletion binding/emit_cpython_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,33 @@ def try_generate_struct_cpython(struct_name, struct):
)
else:
is_writeable = (not hasattr(decl.type, "quals")) or "const" not in decl.type.quals
if isinstance(decl.type, c_ast.ArrayDecl):
elem = (
_struct_array_elem(decl, get_type_fn, lv_to_mp)
if isinstance(decl.type, c_ast.ArrayDecl)
else None
)
if elem:
# A view, not a raw Blob, and a checked whole-array write
# instead of a memcpy from whatever mp_to_ptr returned
# (lvgl-bindings#38).
shape = "data->{decl_name}, {dim}, sizeof({ctype})".format(
decl_name=decl.name,
dim=gen.visit(decl.type.dim),
ctype=gen.visit(decl.type.type),
)
san = sanitize(elem)
if is_writeable:
write_cases.append(
'if (strcmp(attr, "{field}") == 0) {{ lvpy_struct_array_assign({shape}, mp_write_value_ptr_{san}, value); result = 0; }}'.format(
field=sanitize(decl.name), shape=shape, san=san
)
)
read_cases.append(
'if (strcmp(attr, "{field}") == 0) return lvpy_struct_array_new(self, {shape}, mp_read_ptr_{san}, mp_write_value_ptr_{san});'.format(
field=sanitize(decl.name), shape=shape, san=san
)
)
elif isinstance(decl.type, c_ast.ArrayDecl):
memcpy_size = "sizeof(%s)*%s" % (
gen.visit(decl.type.type),
gen.visit(decl.type.dim),
Expand Down Expand Up @@ -929,6 +955,25 @@ def _is_char_ptr_array_arg(arg):
)


def _struct_array_elem(decl, get_type_fn, lv_to_mp):
"""Element struct name for a fixed-size struct array field, else None.

Only complete structs qualify: their mp_write_value_ptr_* writer and
mp_read_ptr_* reader exist.
"""
elem_ast = decl.type.type
if not isinstance(elem_ast, c_ast.TypeDecl):
return None
elem = get_type_fn(elem_ast, remove_quals=True)
if not runtime.get("cpython_struct_sizes", {}).get(elem):
return None
if elem in (_h("structs_without_typedef") or {}):
return None
if not (lv_to_mp.get("%s *" % elem) or "").startswith("mp_read_ptr_"):
return None
return elem


def _const_struct_array_elem(arg, get_type_fn):
"""Element struct name for a ``const T name[]`` parameter, else None.

Expand Down
Loading
Loading