From d76a8cf6291127b4d63e385b3e1a6d3132e8be59 Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Fri, 28 Mar 2025 21:06:51 +0100 Subject: [PATCH 1/3] Make SpriteList.pop() O(1) instead of O(n) --- arcade/sprite_list/sprite_list.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/arcade/sprite_list/sprite_list.py b/arcade/sprite_list/sprite_list.py index d06303a784..af24e218ed 100644 --- a/arcade/sprite_list/sprite_list.py +++ b/arcade/sprite_list/sprite_list.py @@ -600,7 +600,11 @@ def clear(self, *, capacity: int | None = None, deep: bool = True) -> None: self._init_deferred() def pop(self, index: int = -1) -> SpriteType: - """Attempt to pop a sprite from the list. + """ + Attempt to pop a sprite from the list. + + This is the most efficient way to remove a sprite from the list. + The complexity of this method is ``O(1)``. This works like :external:ref:`popping from ` a standard Python :py:class:`list`: @@ -616,8 +620,24 @@ def pop(self, index: int = -1) -> SpriteType: if len(self.sprite_list) == 0: raise IndexError("pop from empty list") - sprite = self.sprite_list[index] - self.remove(sprite) + sprite = self.sprite_list.pop(index) + try: + slot = self.sprite_slot[sprite] + except KeyError: + raise ValueError("Sprite is not in the SpriteList") + + sprite.sprite_lists.remove(self) + del self.sprite_slot[sprite] + self._sprite_buffer_free_slots.append(slot) + + _ = self._sprite_index_data.pop(index) + self._sprite_index_data.append(0) + self._sprite_index_slots -= 1 + self._sprite_index_changed = True + + if self.spatial_hash is not None: + self.spatial_hash.remove(sprite) + return sprite def append(self, sprite: SpriteType) -> None: From 60155e99f4f643bedd06b0fdd507924598f84ef9 Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Fri, 28 Mar 2025 21:54:04 +0100 Subject: [PATCH 2/3] Make SpriteList.remove() 3 x faster Because the index buffer and spritelist share the same index we only need to resolve the index once. Removing by value in an array is a lot more expensive than removing by value in a list. --- arcade/sprite_list/sprite_list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arcade/sprite_list/sprite_list.py b/arcade/sprite_list/sprite_list.py index af24e218ed..c6061bb42a 100644 --- a/arcade/sprite_list/sprite_list.py +++ b/arcade/sprite_list/sprite_list.py @@ -709,14 +709,14 @@ def remove(self, sprite: SpriteType) -> None: except KeyError: raise ValueError("Sprite is not in the SpriteList") - self.sprite_list.remove(sprite) + index = self.sprite_list.index(sprite) + self.sprite_list.pop(index) sprite.sprite_lists.remove(self) del self.sprite_slot[sprite] self._sprite_buffer_free_slots.append(slot) - # Brutal resize for now. Optimize later - self._sprite_index_data.remove(slot) + self._sprite_index_data.pop(index) self._sprite_index_data.append(0) self._sprite_index_slots -= 1 self._sprite_index_changed = True From 834a91777f4910fc8031796921a01bcd56cc15ef Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Fri, 28 Mar 2025 22:06:29 +0100 Subject: [PATCH 3/3] Add complexity info in docstrings --- arcade/sprite_list/sprite_list.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/arcade/sprite_list/sprite_list.py b/arcade/sprite_list/sprite_list.py index c6061bb42a..2f39ce3ff1 100644 --- a/arcade/sprite_list/sprite_list.py +++ b/arcade/sprite_list/sprite_list.py @@ -603,9 +603,6 @@ def pop(self, index: int = -1) -> SpriteType: """ Attempt to pop a sprite from the list. - This is the most efficient way to remove a sprite from the list. - The complexity of this method is ``O(1)``. - This works like :external:ref:`popping from ` a standard Python :py:class:`list`: @@ -613,6 +610,9 @@ def pop(self, index: int = -1) -> SpriteType: #. If no ``index`` is passed, try to pop the last :py:class:`Sprite` in the list + This is the most efficient way to remove a sprite from the list. + The complexity of this method is ``O(1)``. + Args: index: Index of sprite to remove (defaults to ``-1`` for the last item) @@ -701,6 +701,10 @@ def remove(self, sprite: SpriteType) -> None: """ Remove a specific sprite from the list. + Note that this method is ``O(N)`` in complexity and will have + and increased cost the more sprites you have in the list. + A faster option is to use :py:meth:`pop` or :py:meth:`swap`. + Args: sprite: Item to remove from the list """