Implement buffer protocol for RPNIFS class

This commit is contained in:
Yann Weber 2023-10-05 11:16:41 +02:00
commit d25529c5c6
4 changed files with 145 additions and 62 deletions

View file

@ -889,71 +889,22 @@ int rpnif_getbuffer(PyObject *self, Py_buffer *view, int flags)
PyRPNIterExpr_t *expr_self;
expr_self = (PyRPNIterExpr_t*)self;
return PyObject_GetBuffer(expr_self->mmap, view, flags);
rpn_if_default_data_t *data = (rpn_if_default_data_t*)expr_self->rif->params->data;
view->buf = expr_self->rif->mem;
view->obj = self;
view->len = expr_self->rif->params->mem_sz * expr_self->rif->params->value_sz;
view->readonly = 0;
//view->itemsize = expr_self->rif->params->value_sz;
view->itemsize = sizeof(rpn_value_t);
if(flags & PyBUF_FORMAT)
if(expr_self->mmap)
{
view->format = "L";
return PyObject_GetBuffer(expr_self->mmap, view, flags);
}
else
{
view->format = NULL;
}
view->ndim = 1;
view->shape = NULL;
if(flags & PyBUF_ND)
{
view->ndim = expr_self->ndim;
// !! Error if value_sz < sizeof(rpn_value_t) !!
short nval = view->itemsize / sizeof(rpn_value_t);
if(nval > 1)
{
view->ndim++;
}
view->shape = malloc(sizeof(Py_ssize_t) * view->ndim);
if(!view->shape)
{
goto buff_error;
}
for(size_t i=0; i<expr_self->ndim; i++)
{
int idx = i;
if(data->pos_flag == RPN_IF_POSITION_XDIM)
{
idx++;
}
view->shape[i] = data->size_lim[idx];
}
if(nval>1)
{
view->shape[expr_self->ndim] = nval;
}
}
view->strides = NULL;
view->suboffsets = NULL;
Py_INCREF(self);
return 0;
buff_error:
PyErr_Format(PyExc_BufferError,
"Unable to provide buffer : %s", strerror(errno));
view->obj = NULL;
return -1;
return _rpnif_getbuffer_nopymap(self, expr_self->rif->params,
expr_self->_mmap, view, flags);
}
void rpnif_releasebuffer(PyObject *self, Py_buffer *view)
{
free(view->shape);
PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self;
if(expr_self->mmap)
{
return PyBuffer_Release(view);
}
return _rpnif_releasebuffer_nopymap(self, view);
}
PyObject* rpnif_str(PyObject *self)
@ -1676,6 +1627,76 @@ err:
}
int _rpnif_getbuffer_nopymap(PyObject *self, const rpn_if_param_t *params,
void *buf, Py_buffer *view, int flags)
{
rpn_if_default_data_t *data = (rpn_if_default_data_t*)params->data;
view->buf = buf;
view->obj = self;
view->len = params->mem_sz * params->value_sz;
view->readonly = 0;
view->itemsize = sizeof(rpn_value_t);
view->format = (flags & PyBUF_FORMAT)?"L":NULL;
view->ndim = 1;
view->shape = NULL;
if(flags & PyBUF_ND)
{
short nval;
switch(data->res_flag)
{
case RPN_IF_RES_BOOL:
case RPN_IF_RES_CONST:
case RPN_IF_RES_COUNT:
nval = 1;
break;
case RPN_IF_RES_RGBA:
case RPN_IF_RES_CONST_RGBA:
nval = 4;
break;
case RPN_IF_RES_RGB:
nval = 3;
break;
default:
PyErr_SetString(PyExc_BufferError,
"Unsupported result flag");
return -1;
}
view->ndim += (nval>1)?1:0;
if(!(view->shape = malloc(sizeof(Py_ssize_t) * view->ndim)))
{
PyErr_Format(PyExc_BufferError,
"Buffer's shape allocation error : %s",
strerror(errno));
return -1;
}
for(size_t i=0; i<data->ndim; i++)
{
const int idx = i + \
(data->pos_flag == RPN_IF_POSITION_XDIM)?1:0;
view->shape[i] = data->size_lim[idx];
}
if(nval>1)
{
view->shape[data->ndim] = nval;
}
}
view->strides = NULL;
view->suboffsets = NULL;
Py_INCREF(self);
return 0;
}
void _rpnif_releasebuffer_nopymap(PyObject *self, Py_buffer *view)
{
if(view->shape) { free(view->shape); }
view->buf = NULL;
Py_DECREF(self);
}
int _rpnif_init_expr_tuple(PyRPNIterExpr_t *expr_self)
{
expr_self->expr = PyTuple_New(expr_self->rif->params->rpn_sz);

View file

@ -153,6 +153,7 @@ void rpnif_del(PyObject *self);
* @param view A memoryview to fill depending on flags
* @param flags Indicates the request type
* @return -1 on error
* @note Used by RPNIterExpr and RPNIFS (proper way to implement it is base class)
* See python documentation at
* https://docs.python.org/3/c-api/buffer.html#buffer-request-types
* @ingroup pymod_pyrpn_RPNExprIter
@ -332,6 +333,23 @@ PyObject *_rpnif_to_pos(rpn_if_default_data_t *rif_data, PyObject** argv, Py_ssi
/**@brief Implementation of bot RPNIterExpr.to_pos and RPNIFS.from_pos */
PyObject *_rpnif_from_pos(rpn_if_default_data_t *rif_data, PyObject* _pos);
/**@brief Buffer protocol request handler method when no python mmap exist
* (deserialized instance)
* @param self RPNIterExpr instance
* @param view A memoryview to fill depending on flags
* @param flags Indicates the request type
* @return -1 on error
* @note Used by RPNIterExpr and RPNIFS (proper way to implement it is base class)
* See python documentation at
* https://docs.python.org/3/c-api/buffer.html#buffer-request-types
* @ingroup pymod_pyrpn_RPNExprIter
*/
int _rpnif_getbuffer_nopymap(PyObject *self, const rpn_if_param_t *params,
void *buf, Py_buffer *view, int flags);
void _rpnif_releasebuffer_nopymap(PyObject *self, Py_buffer *view);
/**@todo doc */
int _rpnif_init_expr_tuple(PyRPNIterExpr_t *expr_self);
#endif

View file

@ -456,13 +456,24 @@ int rpnifs_expr_ass_item(PyObject *self, Py_ssize_t idx, PyObject *value)
int rpnifs_getbuffer(PyObject *self, Py_buffer *view, int flags)
{
PyErr_SetString(PyExc_NotImplementedError, "TODO");
return -1;
PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self;
if(ifs_self->mmap)
{
return PyObject_GetBuffer(ifs_self->mmap, view, flags);
}
return _rpnif_getbuffer_nopymap(self, &ifs_self->ifs->params,
ifs_self->_mmap, view, flags);
}
void rpnifs_releasebuffer(PyObject *self, Py_buffer *view)
{
PyErr_SetString(PyExc_NotImplementedError, "TODO");
PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self;
if(ifs_self->mmap)
{
return PyBuffer_Release(view);
}
return _rpnif_releasebuffer_nopymap(self, view);
}
int _rpnifs_update_if_tuple(PyRPNIFS_t *ifs_self)

View file

@ -25,6 +25,16 @@ import sys
import unittest
try:
import numpy as np
except (ImportError, NameError) as e:
np = None
warnings.warn("Numpy not found, some tests skipped", warnings.ImportWarning)
def skipIfNoNumpy():
if np is not None:
return lambda func: func
return unittest.skip("Numpy not found")
try:
import pyrpn
@ -124,6 +134,29 @@ class TestRPNIFS(unittest.TestCase):
percent = steps / (dist * 100)
self.assertLess(percent, 1)
@skipIfNoNumpy()
def test_buffer_protocol(self):
""" Testing buffer protocol on ifs's mmaps """
sz = [512,512]
ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY,
pyrpn.const.RESULT_COUNT,
sz)
ifs.weights([1,2])
for rif in ifs:
for expr in rif:
expr.mutate(n_mutations=20)
buf = np.frombuffer(ifs, dtype=np.uint64)
buf2 = np.frombuffer(ifs[0], dtype=np.uint64)
for _ in range(512):
pos = random.randint(0, len(buf))
buf[pos] = random.randint(0, 0xFFFFFFFF)
pos = random.randint(0, len(buf))
buf2[pos] = random.randint(0, 0xFFFFFFFF)
self.assertTrue((buf == buf2).all())
class TestRPNIFSCoordinates(unittest.TestCase):
""" Testing methods for coordinates <-> position convertions """