From 02093111ef067a25d0908e21d533d3378e596be1 Mon Sep 17 00:00:00 2001 From: Alberto <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Sun, 21 Apr 2024 17:14:58 +0200 Subject: [PATCH] Optimized Rect multi-collision methods (#2786) * optimized rect multi-collision methods * fix * Addressed reviews, added more comments, swapped Py_BuildValue for PyTuple_Pack. * addressed review * addressed reviews --- src_c/rect_impl.h | 107 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 18 deletions(-) diff --git a/src_c/rect_impl.h b/src_c/rect_impl.h index e10947cac6..eeea78af8f 100644 --- a/src_c/rect_impl.h +++ b/src_c/rect_impl.h @@ -1292,17 +1292,47 @@ RectExport_colliderect(RectObject *self, PyObject *const *args, return PyBool_FromLong(_pg_do_rects_intersect(&self->r, argrect)); } +#ifndef OPTIMIZED_COLLIDERECT_SETUP +/* This macro is used to optimize the colliderect function. It calculates + * the left, top, right and bottom values of the calling rect only once + * and uses them in the OPTIMIZED_COLLIDERECT macro. */ +#define OPTIMIZED_COLLIDERECT_SETUP \ + const PrimitiveType left = MIN(srect->x, srect->x + srect->w); \ + const PrimitiveType top = MIN(srect->y, srect->y + srect->h); \ + const PrimitiveType right = MAX(srect->x, srect->x + srect->w); \ + const PrimitiveType bottom = MAX(srect->y, srect->y + srect->h); +#endif + +#ifndef OPTIMIZED_COLLIDERECT +/* This macro is used to optimize the colliderect function. Makes use of + * precalculated values to avoid unnecessary calculations. It also checks + * whether the other rect has 0 width or height, in which case we don't + * collide. */ +#define OPTIMIZED_COLLIDERECT(r) \ + (r->w && r->h && left < MAX(r->x, r->x + r->w) && \ + top < MAX(r->y, r->y + r->h) && right > MIN(r->x, r->x + r->w) && \ + bottom > MIN(r->y, r->y + r->h)) +#endif + static PyObject * RectExport_collidelist(RectObject *self, PyObject *arg) { InnerRect *argrect, *srect = &self->r, temp; int loop; + /* If the calling rect has 0 width or height, it cannot collide with + * anything, hence return -1 directly. */ + if (srect->w == 0 || srect->h == 0) { + return PyLong_FromLong(-1); + } + if (!PySequence_Check(arg)) { return RAISE(PyExc_TypeError, "Argument must be a sequence of rectstyle objects."); } + OPTIMIZED_COLLIDERECT_SETUP; + /* If the sequence is a fast sequence, we can use the faster * PySequence_Fast_ITEMS() function to get the items. */ if (pgSequenceFast_Check(arg)) { @@ -1313,16 +1343,17 @@ RectExport_collidelist(RectObject *self, PyObject *arg) PyExc_TypeError, "Argument must be a sequence of rectstyle objects."); } - if (_pg_do_rects_intersect(srect, argrect)) { + + if (OPTIMIZED_COLLIDERECT(argrect)) { return PyLong_FromLong(loop); } } } /* If the sequence is not a fast sequence, we have to use the slower - * PySequence_GetItem() function to get the items. */ + * PySequence_ITEM() function to get the items. */ else { for (loop = 0; loop < PySequence_Length(arg); loop++) { - PyObject *obj = PySequence_GetItem(arg, loop); + PyObject *obj = PySequence_ITEM(arg, loop); if (!obj || !(argrect = RectFromObject(obj, &temp))) { Py_XDECREF(obj); @@ -1333,7 +1364,7 @@ RectExport_collidelist(RectObject *self, PyObject *arg) Py_DECREF(obj); - if (_pg_do_rects_intersect(srect, argrect)) { + if (OPTIMIZED_COLLIDERECT(argrect)) { return PyLong_FromLong(loop); } } @@ -1358,6 +1389,14 @@ RectExport_collidelistall(RectObject *self, PyObject *arg) return NULL; } + /* If the calling rect has 0 width or height, it cannot collide with + * anything, hence return an empty list directly. */ + if (srect->w == 0 || srect->h == 0) { + return ret; + } + + OPTIMIZED_COLLIDERECT_SETUP; + /* If the sequence is a fast sequence, we can use the faster * PySequence_Fast_ITEMS() function to get the items. */ if (pgSequenceFast_Check(arg)) { @@ -1370,7 +1409,7 @@ RectExport_collidelistall(RectObject *self, PyObject *arg) "Argument must be a sequence of rectstyle objects."); } - if (_pg_do_rects_intersect(srect, argrect)) { + if (OPTIMIZED_COLLIDERECT(argrect)) { PyObject *num = PyLong_FromLong(loop); if (!num) { Py_DECREF(ret); @@ -1400,7 +1439,7 @@ RectExport_collidelistall(RectObject *self, PyObject *arg) Py_DECREF(obj); - if (_pg_do_rects_intersect(srect, argrect)) { + if (OPTIMIZED_COLLIDERECT(argrect)) { PyObject *num = PyLong_FromLong(loop); if (!num) { Py_DECREF(ret); @@ -1452,7 +1491,7 @@ static PyObject * RectExport_collideobjectsall(RectObject *self, PyObject *args, PyObject *kwargs) { - InnerRect *argrect; + InnerRect *argrect, *srect = &self->r; InnerRect temp; Py_ssize_t size; int loop; @@ -1485,6 +1524,14 @@ RectExport_collideobjectsall(RectObject *self, PyObject *args, return NULL; } + /* If the calling rect has 0 width or height, it cannot collide with + * anything, hence return an empty list directly. */ + if (srect->w == 0 || srect->h == 0) { + return ret; + } + + OPTIMIZED_COLLIDERECT_SETUP; + size = PySequence_Length(list); if (size == -1) { Py_DECREF(ret); @@ -1492,7 +1539,7 @@ RectExport_collideobjectsall(RectObject *self, PyObject *args, } for (loop = 0; loop < size; ++loop) { - obj = PySequence_GetItem(list, loop); + obj = PySequence_ITEM(list, loop); if (!obj) { Py_DECREF(ret); @@ -1506,7 +1553,7 @@ RectExport_collideobjectsall(RectObject *self, PyObject *args, return NULL; } - if (_pg_do_rects_intersect(&self->r, argrect)) { + if (OPTIMIZED_COLLIDERECT(argrect)) { if (0 != PyList_Append(ret, obj)) { Py_DECREF(ret); Py_DECREF(obj); @@ -1522,7 +1569,7 @@ RectExport_collideobjectsall(RectObject *self, PyObject *args, static PyObject * RectExport_collideobjects(RectObject *self, PyObject *args, PyObject *kwargs) { - InnerRect *argrect; + InnerRect *argrect, *srect = &self->r; InnerRect temp; Py_ssize_t size; int loop; @@ -1549,13 +1596,21 @@ RectExport_collideobjects(RectObject *self, PyObject *args, PyObject *kwargs) "Key function must be callable with one argument."); } + /* If the calling rect has 0 width or height, it cannot collide with + * anything, hence return None directly. */ + if (srect->w == 0 || srect->h == 0) { + Py_RETURN_NONE; + } + + OPTIMIZED_COLLIDERECT_SETUP; + size = PySequence_Length(list); if (size == -1) { return NULL; } for (loop = 0; loop < size; ++loop) { - obj = PySequence_GetItem(list, loop); + obj = PySequence_ITEM(list, loop); if (!obj) { return NULL; @@ -1567,7 +1622,7 @@ RectExport_collideobjects(RectObject *self, PyObject *args, PyObject *kwargs) return NULL; } - if (_pg_do_rects_intersect(&self->r, argrect)) { + if (OPTIMIZED_COLLIDERECT(argrect)) { return obj; } Py_DECREF(obj); @@ -1579,7 +1634,7 @@ RectExport_collideobjects(RectObject *self, PyObject *args, PyObject *kwargs) static PyObject * RectExport_collidedict(RectObject *self, PyObject *args, PyObject *kwargs) { - InnerRect *argrect, temp; + InnerRect *argrect, temp, *srect = &self->r; Py_ssize_t loop = 0; Py_ssize_t values = 0; /* Defaults to expecting keys as rects. */ PyObject *dict, *key, *val; @@ -1596,6 +1651,14 @@ RectExport_collidedict(RectObject *self, PyObject *args, PyObject *kwargs) return RAISE(PyExc_TypeError, "first argument must be a dict"); } + /* If the calling rect has 0 width or height, it cannot collide with + * anything, hence return None directly. */ + if (srect->w == 0 || srect->h == 0) { + Py_RETURN_NONE; + } + + OPTIMIZED_COLLIDERECT_SETUP; + while (PyDict_Next(dict, &loop, &key, &val)) { if (values) { if (!(argrect = RectFromObject(val, &temp))) { @@ -1609,8 +1672,8 @@ RectExport_collidedict(RectObject *self, PyObject *args, PyObject *kwargs) } } - if (_pg_do_rects_intersect(&self->r, argrect)) { - ret = Py_BuildValue("(OO)", key, val); + if (OPTIMIZED_COLLIDERECT(argrect)) { + ret = PyTuple_Pack(2, key, val); break; } } @@ -1624,7 +1687,7 @@ RectExport_collidedict(RectObject *self, PyObject *args, PyObject *kwargs) static PyObject * RectExport_collidedictall(RectObject *self, PyObject *args, PyObject *kwargs) { - InnerRect *argrect, temp; + InnerRect *argrect, temp, *srect = &self->r; Py_ssize_t loop = 0; Py_ssize_t values = 0; /* Defaults to expecting keys as rects. */ PyObject *dict, *key, *val; @@ -1645,6 +1708,14 @@ RectExport_collidedictall(RectObject *self, PyObject *args, PyObject *kwargs) if (!ret) return NULL; + /* If the calling rect has 0 width or height, it cannot collide with + * anything, hence return an empty list directly. */ + if (srect->w == 0 || srect->h == 0) { + return ret; + } + + OPTIMIZED_COLLIDERECT_SETUP; + while (PyDict_Next(dict, &loop, &key, &val)) { if (values) { if (!(argrect = RectFromObject(val, &temp))) { @@ -1660,8 +1731,8 @@ RectExport_collidedictall(RectObject *self, PyObject *args, PyObject *kwargs) } } - if (_pg_do_rects_intersect(&self->r, argrect)) { - PyObject *num = Py_BuildValue("(OO)", key, val); + if (OPTIMIZED_COLLIDERECT(argrect)) { + PyObject *num = PyTuple_Pack(2, key, val); if (!num) { Py_DECREF(ret); return NULL;