feat(discretemap): buffer protocol for zero-copy numpy views; closes #334
DiscreteMap now implements tp_as_buffer/bf_getbuffer, exposing its dense uint8 storage as a 2-D (height, width) C-contiguous writable view. memoryview(dmap) and np.asarray(dmap) share memory with the map -- no copy -- so per-entity FOV memory (#294 perspective_map) and other DiscreteMaps can be inspected/edited with numpy at native speed. The exporter owns the buffer via its shared_ptr<DiscreteMap>; getbuffer INCREFs it for the view's lifetime (balanced by the default PyBuffer_Release DECREF, so no bf_releasebuffer). shape/strides are stored on the object -- dimensions are immutable after construction, so concurrent views safely share them. Independent of the GridData SoA work (#332): DiscreteMap already owned a dense contiguous uint8_t* with no libtcod coupling. Regression test issue_334_discretemap_buffer_test.py: shape/format/strides, row-major [x,y]->[y,x] mapping, zero-copy writeback both directions, optional numpy aliasing (skipped when not bundled), and a buffer over a live entity perspective_map. Suite 309/309. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0c61c70ccd
commit
8af3bf45b6
3 changed files with 115 additions and 0 deletions
|
|
@ -1317,6 +1317,49 @@ PyObject* PyDiscreteMap::mask(PyDiscreteMapObject* self, PyObject* Py_UNUSED(arg
|
|||
return PyMemoryView_FromMemory(reinterpret_cast<char*>(self->values), len, PyBUF_WRITE);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Buffer protocol (#334) - zero-copy 2D (h, w) uint8 view.
|
||||
// np.asarray(dmap) -> shape (h, w), dtype uint8, C-contiguous, writable.
|
||||
// The exporter (this object) owns the buffer via its shared_ptr<DiscreteMap>;
|
||||
// INCREF keeps it alive for the view's lifetime (balanced by the default
|
||||
// PyBuffer_Release DECREF -- no bf_releasebuffer needed). shape/strides live on
|
||||
// the object (dimensions are immutable after construction, so concurrent views
|
||||
// safely share them).
|
||||
// ============================================================================
|
||||
int PyDiscreteMap::getbuffer(PyObject* exporter, Py_buffer* view, int flags)
|
||||
{
|
||||
PyDiscreteMapObject* self = reinterpret_cast<PyDiscreteMapObject*>(exporter);
|
||||
if (!self->values) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "DiscreteMap not initialized");
|
||||
view->obj = nullptr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
self->buf_shape[0] = self->h; // rows
|
||||
self->buf_shape[1] = self->w; // cols
|
||||
self->buf_strides[0] = self->w; // itemsize == 1, so byte strides == element strides
|
||||
self->buf_strides[1] = 1;
|
||||
|
||||
view->buf = self->values;
|
||||
view->obj = exporter;
|
||||
Py_INCREF(exporter);
|
||||
view->len = static_cast<Py_ssize_t>(self->w) * static_cast<Py_ssize_t>(self->h);
|
||||
view->readonly = 0;
|
||||
view->itemsize = 1;
|
||||
view->format = (flags & PyBUF_FORMAT) ? const_cast<char*>("B") : nullptr;
|
||||
view->ndim = 2;
|
||||
view->shape = self->buf_shape;
|
||||
view->strides = (flags & PyBUF_STRIDES) ? self->buf_strides : nullptr;
|
||||
view->suboffsets = nullptr;
|
||||
view->internal = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyBufferProcs PyDiscreteMap::as_buffer = {
|
||||
.bf_getbuffer = PyDiscreteMap::getbuffer,
|
||||
.bf_releasebuffer = nullptr,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Serialization
|
||||
// ============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue