Skip to content

Commit

Permalink
Optimized Rect multi-collision methods (#2786)
Browse files Browse the repository at this point in the history
* optimized rect multi-collision methods

* fix

* Addressed reviews, added more comments, swapped Py_BuildValue for PyTuple_Pack.

* addressed review

* addressed reviews
  • Loading branch information
itzpr3d4t0r authored Apr 21, 2024
1 parent dfaa76e commit 0209311
Showing 1 changed file with 89 additions and 18 deletions.
107 changes: 89 additions & 18 deletions src_c/rect_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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);
Expand All @@ -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);
}
}
Expand All @@ -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)) {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1485,14 +1524,22 @@ 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);
return NULL;
}

for (loop = 0; loop < size; ++loop) {
obj = PySequence_GetItem(list, loop);
obj = PySequence_ITEM(list, loop);

if (!obj) {
Py_DECREF(ret);
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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))) {
Expand All @@ -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;
}
}
Expand All @@ -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;
Expand All @@ -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))) {
Expand All @@ -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;
Expand Down

0 comments on commit 0209311

Please sign in to comment.