Add DiscreteMap to_bytes/from_bytes serialization, closes #293
to_bytes() returns raw uint8 cell data as Python bytes object. from_bytes(data, size, enum=None) is a classmethod that constructs a new DiscreteMap from serialized data with dimension validation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
061b29a07a
commit
e0bcee12a3
3 changed files with 203 additions and 0 deletions
|
|
@ -320,6 +320,24 @@ PyMethodDef PyDiscreteMap::methods[] = {
|
|||
MCRF_DESC("Get raw uint8_t data as memoryview for libtcod compatibility."),
|
||||
MCRF_RETURNS("memoryview: Direct access to internal buffer (read/write)")
|
||||
)},
|
||||
// Serialization
|
||||
{"to_bytes", (PyCFunction)PyDiscreteMap::to_bytes, METH_NOARGS,
|
||||
MCRF_METHOD(DiscreteMap, to_bytes,
|
||||
MCRF_SIG("()", "bytes"),
|
||||
MCRF_DESC("Serialize map data to bytes (row-major, one byte per cell)."),
|
||||
MCRF_RETURNS("bytes: Raw cell data, length = width * height")
|
||||
)},
|
||||
{"from_bytes", (PyCFunction)PyDiscreteMap::from_bytes, METH_VARARGS | METH_KEYWORDS | METH_CLASS,
|
||||
MCRF_METHOD(DiscreteMap, from_bytes,
|
||||
MCRF_SIG("(data: bytes, size: tuple[int, int], *, enum: type = None)", "DiscreteMap"),
|
||||
MCRF_DESC("Create a DiscreteMap from raw byte data."),
|
||||
MCRF_ARGS_START
|
||||
MCRF_ARG("data", "Raw cell data (one byte per cell, row-major)")
|
||||
MCRF_ARG("size", "(width, height) dimensions")
|
||||
MCRF_ARG("enum", "Optional IntEnum class for value interpretation")
|
||||
MCRF_RETURNS("DiscreteMap: new map initialized from data")
|
||||
MCRF_RAISES("ValueError", "Data length does not match width * height")
|
||||
)},
|
||||
// HeightMap integration
|
||||
{"from_heightmap", (PyCFunction)PyDiscreteMap::from_heightmap, METH_VARARGS | METH_KEYWORDS | METH_CLASS,
|
||||
MCRF_METHOD(DiscreteMap, from_heightmap,
|
||||
|
|
@ -1299,6 +1317,97 @@ PyObject* PyDiscreteMap::mask(PyDiscreteMapObject* self, PyObject* Py_UNUSED(arg
|
|||
return PyMemoryView_FromMemory(reinterpret_cast<char*>(self->values), len, PyBUF_WRITE);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Serialization
|
||||
// ============================================================================
|
||||
|
||||
PyObject* PyDiscreteMap::to_bytes(PyDiscreteMapObject* self, PyObject* Py_UNUSED(args))
|
||||
{
|
||||
if (!self->values) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "DiscreteMap not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
Py_ssize_t len = static_cast<Py_ssize_t>(self->w) * static_cast<Py_ssize_t>(self->h);
|
||||
return PyBytes_FromStringAndSize(reinterpret_cast<const char*>(self->values), len);
|
||||
}
|
||||
|
||||
PyObject* PyDiscreteMap::from_bytes(PyTypeObject* type, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
static const char* kwlist[] = {"data", "size", "enum", nullptr};
|
||||
Py_buffer buffer;
|
||||
PyObject* size_obj = nullptr;
|
||||
PyObject* enum_type = nullptr;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "y*O|O", const_cast<char**>(kwlist),
|
||||
&buffer, &size_obj, &enum_type)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int w = 0, h = 0;
|
||||
if (!PyArg_ParseTuple(size_obj, "ii", &w, &h)) {
|
||||
PyErr_Clear();
|
||||
if (PyTuple_Check(size_obj) && PyTuple_GET_SIZE(size_obj) == 2) {
|
||||
w = (int)PyLong_AsLong(PyTuple_GET_ITEM(size_obj, 0));
|
||||
h = (int)PyLong_AsLong(PyTuple_GET_ITEM(size_obj, 1));
|
||||
if (PyErr_Occurred()) {
|
||||
PyBuffer_Release(&buffer);
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
PyBuffer_Release(&buffer);
|
||||
PyErr_SetString(PyExc_TypeError, "size must be a (width, height) tuple");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (w <= 0 || h <= 0 || w > 8192 || h > 8192) {
|
||||
PyBuffer_Release(&buffer);
|
||||
PyErr_SetString(PyExc_ValueError, "dimensions must be positive and <= 8192");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Py_ssize_t expected = static_cast<Py_ssize_t>(w) * static_cast<Py_ssize_t>(h);
|
||||
if (buffer.len != expected) {
|
||||
PyBuffer_Release(&buffer);
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"data length (%zd) does not match size %d x %d = %zd",
|
||||
buffer.len, w, h, expected);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (enum_type && enum_type != Py_None && !PyType_Check(enum_type)) {
|
||||
PyBuffer_Release(&buffer);
|
||||
PyErr_SetString(PyExc_TypeError, "enum must be a type (IntEnum subclass)");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto obj = (PyDiscreteMapObject*)type->tp_alloc(type, 0);
|
||||
if (!obj) {
|
||||
PyBuffer_Release(&buffer);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
obj->w = w;
|
||||
obj->h = h;
|
||||
obj->values = new (std::nothrow) uint8_t[expected];
|
||||
if (!obj->values) {
|
||||
PyBuffer_Release(&buffer);
|
||||
Py_DECREF(obj);
|
||||
return PyErr_NoMemory();
|
||||
}
|
||||
std::memcpy(obj->values, buffer.buf, expected);
|
||||
PyBuffer_Release(&buffer);
|
||||
|
||||
if (enum_type && enum_type != Py_None) {
|
||||
Py_INCREF(enum_type);
|
||||
obj->enum_type = enum_type;
|
||||
} else {
|
||||
obj->enum_type = nullptr;
|
||||
}
|
||||
|
||||
return (PyObject*)obj;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HeightMap Integration
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ public:
|
|||
static PyObject* to_bool(PyDiscreteMapObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* mask(PyDiscreteMapObject* self, PyObject* Py_UNUSED(args));
|
||||
|
||||
// Serialization
|
||||
static PyObject* to_bytes(PyDiscreteMapObject* self, PyObject* Py_UNUSED(args));
|
||||
static PyObject* from_bytes(PyTypeObject* type, PyObject* args, PyObject* kwds);
|
||||
|
||||
// HeightMap integration
|
||||
static PyObject* from_heightmap(PyTypeObject* type, PyObject* args, PyObject* kwds);
|
||||
static PyObject* to_heightmap(PyDiscreteMapObject* self, PyObject* args, PyObject* kwds);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue