From f66c95cdaf18eab5391fd56d2fefcdb25ecb8a58 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Thu, 3 Aug 2023 00:24:23 -0400 Subject: [PATCH 01/32] New monkeypatch method and removal of Sprite (#29) * Use python's sys.meta_path to apply module patches as soon as they've executed * Linting and minor fixes, removal of Sprite classes --------- Co-authored-by: Darren Eberly --- python/arcade_accelerate/__init__.py | 87 ++-- python/arcade_accelerate/module_patcher.py | 53 +++ src/lib.rs | 5 - src/sprite.rs | 530 --------------------- 4 files changed, 81 insertions(+), 594 deletions(-) create mode 100644 python/arcade_accelerate/module_patcher.py delete mode 100644 src/sprite.rs diff --git a/python/arcade_accelerate/__init__.py b/python/arcade_accelerate/__init__.py index 313071d..7c00d00 100644 --- a/python/arcade_accelerate/__init__.py +++ b/python/arcade_accelerate/__init__.py @@ -1,73 +1,42 @@ import sys -import arcade -from arcade_accelerate import arcade_accelerate # type: ignore +from arcade_accelerate import arcade_accelerate +from arcade_accelerate.module_patcher import ( + AutoPopulatingDictionary, + PatchingMetaPathFinder, +) def bootstrap(): - """Replace arcade math functions with rust accelerated versions.""" - patch_math() - patch_geometry() - patch_hitboxes() - patch_spritelist_collision() - patch_sprite() - - exclude = [ - "arcade.hitbox.base", - "arcade.math", - "arcade.geometry", - "arcade.sprite_list.collision", - "arcade.sprite.base", - ] - - pkgs = [] - for mod in exclude: - pkg = mod.split(".", 1)[0] - pkgs.append(pkg) - - to_uncache = [] - for mod in sys.modules: - if mod in exclude: - continue - - if mod in pkgs: - to_uncache.append(mod) - continue - - for pkg in pkgs: - if mod.startswith(pkg + "."): - to_uncache.append(mod) - break - - for mod in to_uncache: - del sys.modules[mod] - - -def patch_hitboxes(): - arcade.hitbox.base.HitBox = arcade_accelerate.HitBox - arcade.hitbox.base.RotatableHitBox = arcade_accelerate.RotatableHitBox + patches = AutoPopulatingDictionary() + sys.meta_path.insert(0, PatchingMetaPathFinder(patches)) + """Replace arcade math functions with rust accelerated versions.""" + patch_math(patches) + patch_geometry(patches) + patch_hitboxes(patches) + patch_spritelist_collision(patches) -def patch_spritelist_collision(): - arcade.sprite_list.collision.check_for_collision_with_list = ( - arcade_accelerate.check_for_collision_with_list - ) - arcade.sprite_list.collision.check_for_collision_with_lists = ( - arcade_accelerate.check_for_collision_with_lists - ) +def patch_hitboxes(patches): + patches["arcade.hitbox.base"].HitBox = arcade_accelerate.HitBox + patches["arcade.hitbox.base"].RotatableHitBox = arcade_accelerate.RotatableHitBox -def patch_math(): - arcade.math.rotate_point = arcade_accelerate.rotate_point +def patch_spritelist_collision(patches): + patches[ + "arcade.sprite_list.collision" + ].check_for_collision_with_list = arcade_accelerate.check_for_collision_with_list + patches[ + "arcade.sprite_list.collision" + ].check_for_collision_with_lists = arcade_accelerate.check_for_collision_with_lists -def patch_geometry(): - arcade.geometry.are_polygons_intersecting = ( - arcade_accelerate.are_polygons_intersecting - ) +def patch_math(patches): + patches["arcade.math"].rotate_point = arcade_accelerate.rotate_point -def patch_sprite(): - import arcade.sprite.base - arcade.sprite.base.BasicSprite = arcade_accelerate.BasicSprite +def patch_geometry(patches): + patches[ + "arcade.geometry" + ].are_polygons_intersecting = arcade_accelerate.are_polygons_intersecting diff --git a/python/arcade_accelerate/module_patcher.py b/python/arcade_accelerate/module_patcher.py new file mode 100644 index 0000000..478aa1d --- /dev/null +++ b/python/arcade_accelerate/module_patcher.py @@ -0,0 +1,53 @@ +import sys + +from importlib.abc import Loader, MetaPathFinder +from importlib.machinery import ModuleSpec +from types import ModuleType, SimpleNamespace +from typing import Sequence + + +class AutoPopulatingDictionary(dict): + def __missing__(self, key): + self[key] = item = SimpleNamespace() + return item + + +class PatchingMetaPathFinder(MetaPathFinder): + def __init__(self, patches): + self._patches = patches + + def _remaining_meta_path(self): + index_of_self = sys.meta_path.index(self) + remaining_meta_path = sys.meta_path[index_of_self + 1 :] + return remaining_meta_path + + def find_spec( + self, fullname: str, path: Sequence[str] | None, target: ModuleType | None = ... + ) -> ModuleSpec | None: + spec = None + for finder in self._remaining_meta_path(): + spec = finder.find_spec(fullname, path, target) + if spec is not None: + if spec.name in self._patches: + if type(spec) is not ModuleSpec: + raise Exception( + "Type of module spec to be ModuleSpec, not a subtype" + ) + spec.loader = PatchingLoader( + spec.loader, self._patches[spec.name].__dict__ + ) + break + return spec + + +class PatchingLoader(Loader): + def __init__(self, loader: Loader, patches: dict): + self._loader = loader + self._patches = patches + + def create_module(self, spec: ModuleSpec) -> ModuleType | None: + return self._loader.create_module(spec) + + def exec_module(self, module: ModuleType) -> None: + self._loader.exec_module(module) + module.__dict__.update(self._patches) diff --git a/src/lib.rs b/src/lib.rs index e0395d5..6c6e028 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,9 +11,6 @@ pub use geometry::{ is_point_in_polygon, }; -mod sprite; -pub use sprite::{BasicSprite, Sprite}; - mod sprite_list; pub use sprite_list::{check_for_collision_with_list, check_for_collision_with_lists}; @@ -22,8 +19,6 @@ pub use sprite_list::{check_for_collision_with_list, check_for_collision_with_li fn arcade_accelerate(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; - m.add_class::()?; - m.add_class::()?; m.add_function(wrap_pyfunction!(math::rotate_point, m)?)?; m.add_function(wrap_pyfunction!(math::clamp, m)?)?; m.add_function(wrap_pyfunction!(math::lerp, m)?)?; diff --git a/src/sprite.rs b/src/sprite.rs deleted file mode 100644 index f355251..0000000 --- a/src/sprite.rs +++ /dev/null @@ -1,530 +0,0 @@ -use pyo3::types::{PyDict, PyTuple}; -use pyo3::{intern, prelude::*}; - -use crate::hitbox::HitBox; - -#[pyclass(subclass, module = "arcade.sprite.base")] -pub struct BasicSprite { - #[pyo3(get, name = "_texture")] - texture: PyObject, - position: (f32, f32), - #[pyo3(get, name = "_depth")] - depth: f32, - #[pyo3(get, name = "_scale")] - scale: (f32, f32), - #[pyo3(get, name = "_width")] - width: f32, - #[pyo3(get, name = "_height")] - height: f32, - hitbox: HitBox, - #[pyo3(get, name = "_color")] - color: (u8, u8, u8, u8), - sprite_lists: Vec, - #[pyo3(get, name = "_angle")] - angle: f32, -} - -#[pymethods] -impl BasicSprite { - #[new] - #[pyo3(signature = (texture, scale=1.0, center_x=0.0, center_y=0.0, **_kwargs))] - fn new( - py: Python<'_>, - texture: PyObject, - scale: Option, - center_x: Option, - center_y: Option, - _kwargs: Option<&PyDict>, - ) -> Self { - let final_scale = (scale.unwrap_or(1.0), scale.unwrap_or(1.0)); - let final_position = (center_x.unwrap_or(0.0), center_y.unwrap_or(0.0)); - let points: Vec<(f32, f32)> = texture - .getattr(py, intern!(py, "hit_box_points")) - .unwrap() - .extract(py) - .expect("Failed to Load Hit Box Points from Texture"); - let mut width: f32 = texture - .getattr(py, intern!(py, "width")) - .unwrap() - .extract(py) - .expect("Failed to Load Width from Texture"); - let mut height: f32 = texture - .getattr(py, intern!(py, "height")) - .unwrap() - .extract(py) - .expect("Failed to Load Height From Texture"); - width *= final_scale.0; - height *= final_scale.1; - - BasicSprite { - texture, - scale: final_scale, - position: final_position, - depth: 0.0, - width, - height, - color: (255, 255, 255, 255), - hitbox: HitBox { - points, - position: final_position, - scale: final_scale, - angle: 0.0, - }, - sprite_lists: Vec::new(), - angle: 0.0, - } - } - - #[getter] - fn get_position(&self) -> PyResult<(f32, f32)> { - Ok(self.position) - } - - #[setter] - fn set_position( - mut self_: PyRefMut<'_, BasicSprite>, - py: Python<'_>, - new_value: (f32, f32), - ) -> PyResult<()> { - if new_value == self_.position { - return Ok(()); - } - - self_.position = new_value; - self_.hitbox.position = new_value; - - let sprite_lists = self_.sprite_lists.clone(); - let s = Py::from(self_); - for sprite_list in sprite_lists.iter() { - sprite_list.call_method1(py, intern!(py, "_update_position"), (&s,))?; - } - - Ok(()) - } - - #[getter] - fn get_scale(&self) -> PyResult { - Ok(self.scale.0) - } - - #[setter] - fn set_scale(self_: PyRefMut, py: Python<'_>, new_value: f32) -> PyResult<()> { - BasicSprite::set_scalexy(self_, py, (new_value, new_value)) - } - - #[getter] - fn get_scalexy(&self) -> PyResult<(f32, f32)> { - Ok(self.scale) - } - - #[setter] - fn set_scalexy( - mut self_: PyRefMut, - py: Python<'_>, - new_value: (f32, f32), - ) -> PyResult<()> { - if new_value == self_.scale { - return Ok(()); - } - - self_.scale = new_value; - self_.hitbox.scale = new_value; - - let tex_width: f32 = self_ - .texture - .getattr(py, intern!(py, "width")) - .unwrap() - .extract(py) - .expect("Failed to Load Width From Texture"); - - let tex_height: f32 = self_ - .texture - .getattr(py, intern!(py, "height")) - .unwrap() - .extract(py) - .expect("Failed to Load Height From Texture"); - - self_.width = tex_width * self_.scale.0; - self_.height = tex_height * self_.scale.1; - - for sprite_list in self_.sprite_lists.iter() { - sprite_list.call_method1(py, intern!(py, "_update_size"), (&self_,))?; - } - - Ok(()) - } - - #[getter] - fn get_depth(&self) -> PyResult { - Ok(self.depth) - } - - #[setter] - fn set_depth(mut self_: PyRefMut, py: Python<'_>, new_value: f32) -> PyResult<()> { - if new_value == self_.depth { - return Ok(()); - } - - self_.depth = new_value; - - for sprite_list in self_.sprite_lists.iter() { - sprite_list.call_method1(py, intern!(py, "_update_depth"), (&self_,))?; - } - - Ok(()) - } - - #[getter] - fn get_texture(&self, py: Python<'_>) -> PyResult { - Ok(self.texture.clone_ref(py)) - } - - #[setter] - fn set_texture( - mut self_: PyRefMut, - py: Python<'_>, - new_value: PyObject, - ) -> PyResult<()> { - if new_value.is(&self_.texture) { - return Ok(()); - } - - self_.texture = new_value; - - let new_width: f32 = self_ - .texture - .getattr(py, intern!(py, "width")) - .unwrap() - .extract(py) - .expect("Failed to Load Width from Texture"); - self_.width = new_width * self_.scale.0; - - let new_height: f32 = self_ - .texture - .getattr(py, intern!(py, "height")) - .unwrap() - .extract(py) - .expect("Failed to Load Height From Texture"); - self_.height = new_height * self_.scale.1; - - for sprite_list in self_.sprite_lists.iter() { - sprite_list.call_method1(py, intern!(py, "_update_texture"), (&self_,))?; - } - - Ok(()) - } - - #[getter] - fn get_color(&self) -> PyResult<(u8, u8, u8, u8)> { - Ok(self.color) - } - - #[setter] - fn set_color( - mut self_: PyRefMut, - py: Python<'_>, - new_value: Vec, - ) -> PyResult<()> { - let new_color: (u8, u8, u8, u8) = match new_value.len() { - 4 => (new_value[0], new_value[1], new_value[2], new_value[3]), - 3 => (new_value[0], new_value[1], new_value[2], self_.color.3), - _ => panic!("Color must be 3 or 4 ints from 0-255"), - }; - - if new_color == self_.color { - return Ok(()); - } - - self_.color = new_color; - - for sprite_list in self_.sprite_lists.iter() { - sprite_list.call_method1(py, intern!(py, "_update_color"), (&self_,))?; - } - - Ok(()) - } - - #[getter] - fn get_alpha(&self) -> PyResult { - Ok(self.color.3) - } - - #[setter] - fn set_alpha(mut self_: PyRefMut, py: Python<'_>, new_value: u8) -> PyResult<()> { - if self_.color.3 == new_value { - return Ok(()); - } - - self_.color.3 = new_value; - - for sprite_list in self_.sprite_lists.iter() { - sprite_list.call_method1(py, intern!(py, "_update_color"), (&self_,))?; - } - - Ok(()) - } - - #[getter] - fn get_visible(&self) -> PyResult { - Ok(self.color.3 > 0) - } - - #[setter] - fn set_visible( - mut self_: PyRefMut, - py: Python<'_>, - new_value: bool, - ) -> PyResult<()> { - self_.color.3 = if new_value { 255 } else { 0 }; - - for sprite_list in self_.sprite_lists.iter() { - sprite_list.call_method1(py, intern!(py, "_update_color"), (&self_,))?; - } - - Ok(()) - } - - #[getter] - fn get_width(&self) -> PyResult { - Ok(self.width) - } - - #[setter] - fn set_width(mut self_: PyRefMut, py: Python<'_>, new_value: f32) -> PyResult<()> { - if self_.width == new_value { - return Ok(()); - } - - let tex_width: f32 = self_ - .texture - .getattr(py, intern!(py, "width")) - .unwrap() - .extract(py) - .expect("Failed to Load Width From Texture"); - - self_.scale = (new_value / tex_width, self_.scale.1); - self_.width = new_value; - self_.hitbox.scale = self_.scale; - - for sprite_list in self_.sprite_lists.iter() { - sprite_list.call_method1(py, intern!(py, "_update_width"), (&self_,))?; - } - - Ok(()) - } - - #[getter] - fn get_height(&self) -> PyResult { - Ok(self.height) - } - - #[setter] - fn set_height( - mut self_: PyRefMut, - py: Python<'_>, - new_value: f32, - ) -> PyResult<()> { - if self_.height == new_value { - return Ok(()); - } - - let tex_height: f32 = self_ - .texture - .getattr(py, intern!(py, "height")) - .unwrap() - .extract(py) - .expect("Failed to Load Height From Texture"); - - self_.scale = (self_.scale.0, new_value / tex_height); - self_.height = new_value; - self_.hitbox.scale = self_.scale; - - for sprite_list in self_.sprite_lists.iter() { - sprite_list.call_method1(py, intern!(py, "_update_height"), (&self_,))?; - } - - Ok(()) - } - - #[getter] - fn get_center_x(&self) -> PyResult { - Ok(self.position.0) - } - - #[setter] - fn set_center_x(self_: PyRefMut, py: Python<'_>, new_value: f32) -> PyResult<()> { - let y = self_.position.1; - BasicSprite::set_position(self_, py, (new_value, y)) - } - - #[getter] - fn get_center_y(&self) -> PyResult { - Ok(self.position.1) - } - - #[setter] - fn set_center_y(self_: PyRefMut, py: Python<'_>, new_value: f32) -> PyResult<()> { - let x = self_.position.0; - BasicSprite::set_position(self_, py, (x, new_value)) - } - - #[getter] - fn get_left(&self) -> PyResult { - Ok(self.hitbox.left_native()) - } - - #[setter] - fn set_left(self_: PyRefMut, py: Python<'_>, new_value: f32) -> PyResult<()> { - let s = &*self_; - let leftmost = s.hitbox.left_native(); - let diff = new_value - leftmost; - let x = s.position.0; - BasicSprite::set_center_x(self_, py, x + diff) - } - - #[getter] - fn get_right(&self) -> PyResult { - Ok(self.hitbox.right_native()) - } - - #[setter] - fn set_right(self_: PyRefMut, py: Python<'_>, new_value: f32) -> PyResult<()> { - let rightmost = self_.hitbox.right_native(); - let diff = rightmost - new_value; - let x = self_.position.0; - BasicSprite::set_center_x(self_, py, x - diff) - } - - #[getter] - fn get_bottom(&self) -> PyResult { - Ok(self.hitbox.bottom_native()) - } - - #[setter] - fn set_bottom(self_: PyRefMut, py: Python<'_>, new_value: f32) -> PyResult<()> { - let lowest = self_.hitbox.bottom_native(); - let diff = lowest - new_value; - let y = self_.position.1; - BasicSprite::set_center_y(self_, py, y - diff) - } - - #[getter] - fn get_top(&self) -> PyResult { - Ok(self.hitbox.top_native()) - } - - #[setter] - fn set_top(self_: PyRefMut, py: Python<'_>, new_value: f32) -> PyResult<()> { - let highest = self_.hitbox.top_native(); - let diff = highest - new_value; - let y = self_.position.1; - BasicSprite::set_center_y(self_, py, y - diff) - } - - fn update_spatial_hash(self_: PyRef, py: Python<'_>) -> PyResult<()> { - for sprite_list in self_.sprite_lists.iter() { - let spatial_hash: PyObject = sprite_list - .getattr(py, intern!(py, "spatial_hash")) - .unwrap() - .extract(py) - .unwrap(); - - if spatial_hash.is(&py.None()) { - return Ok(()); - } - - spatial_hash.call_method1(py, intern!(py, "move"), (&self_,))?; - } - - Ok(()) - } - - fn register_sprite_list(&mut self, new_list: PyObject) { - self.sprite_lists.push(new_list); - } - - fn remove_from_sprite_lists(mut self_: PyRefMut, py: Python<'_>) -> PyResult<()> { - while !self_.sprite_lists.is_empty() { - self_.sprite_lists[0].call_method1(py, intern!(py, "remove"), (&self_,))?; - } - - self_.sprite_lists.clear(); - - Ok(()) - } - - fn update(&self) {} -} - -#[derive(FromPyObject)] -enum PathOrTexture { - First(String), - Second(PyObject), -} - -#[pyclass(subclass, extends=BasicSprite, module="arcade.sprite.sprite")] -pub struct Sprite {} - -#[pymethods] -impl Sprite { - #[new] - #[pyo3(signature = (path_or_texture, scale=1.0, center_x=0.0, center_y=0.0, angle=0.0, **_kwargs))] - fn new( - py: Python<'_>, - path_or_texture: PathOrTexture, - scale: Option, - center_x: Option, - center_y: Option, - angle: Option, - _kwargs: Option<&PyDict>, - ) -> (Self, BasicSprite) { - let texture: PyObject = match path_or_texture { - PathOrTexture::First(path_string) => { - let arcade = PyModule::import(py, "arcade").expect("Failed to import arcade"); - arcade - .getattr(intern!(py, "load_texture")) - .expect("No arcade.load_texture function found") - .call1(PyTuple::new(py, vec![path_string])) - .expect("Failed to execute arcade.load_texture") - .extract() - .expect("Failed to extract PyObject from arcade.load_texture") - } - PathOrTexture::Second(object) => { - let cls: &str = object.clone().into_ref(py).get_type().name().unwrap(); - let final_object: PyObject = match cls { - "Texture" => object, - "Path" => panic!("Handle pathlib here"), - _ => panic!("Unknown Type Passed to sprite constructor"), - }; - final_object - } - }; - let mut basic = BasicSprite::new(py, texture, scale, center_x, center_y, _kwargs); - basic.angle = angle.unwrap_or(0.0); - (Self {}, basic) - } - - #[getter] - fn get_angle(self_: PyRef<'_, Self>) -> PyResult { - Ok(self_.into_super().angle) - } - - #[setter] - fn set_angle(mut self_: PyRefMut<'_, Self>, py: Python<'_>, new_value: f32) -> PyResult<()> { - let super_ = self_.as_mut(); - - if super_.angle == new_value { - return Ok(()); - } - - super_.angle = new_value; - super_.hitbox.angle = new_value; - - let sprite_lists = super_.sprite_lists.clone(); - for sprite_list in sprite_lists.iter() { - sprite_list.call_method1(py, intern!(py, "_update_height"), (&self_,))?; - } - - Ok(()) - } -} From 7ac994856891a9bc278b9b17de9ca52437a78b0a Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Thu, 3 Aug 2023 01:15:24 -0400 Subject: [PATCH 02/32] Multiprocessing re-work for benchmark utility (#30) --- benchmark/__main__.py | 2 + benchmark/manager.py | 7 ++-- benchmark/tests/arcade/collision.py | 4 +- .../tests/arcade_accelerate/collision.py | 7 +++- benchmark/tests/base.py | 40 ++++++------------- src/sprite_list.rs | 2 + 6 files changed, 30 insertions(+), 32 deletions(-) diff --git a/benchmark/__main__.py b/benchmark/__main__.py index 09e8450..d37662f 100644 --- a/benchmark/__main__.py +++ b/benchmark/__main__.py @@ -1,3 +1,4 @@ +import multiprocessing import argparse import sys from datetime import datetime @@ -39,4 +40,5 @@ def parse_args(args): if __name__ == "__main__": + multiprocessing.set_start_method("spawn") main() diff --git a/benchmark/manager.py b/benchmark/manager.py index a29ca6e..018762c 100644 --- a/benchmark/manager.py +++ b/benchmark/manager.py @@ -90,9 +90,9 @@ def create_test_instances(self): # If a test have multiple instances, create one instance for each if cls.instances: for params, _ in cls.instances: - self.add_test_instance(cls(**params)) + self.add_test_instance(cls(self.session_dir, **params)) else: - self.add_test_instance(cls()) + self.add_test_instance(cls(self.session_dir)) if self.debug: num_instances = len(self.test_instances) @@ -120,7 +120,8 @@ def get_test_instance(self, name: str) -> Optional[PerfTest]: def run(self): """Run all tests""" for instance in self.test_instances: - instance.run(self.session_dir) + instance.start() + instance.join() def create_graph( self, diff --git a/benchmark/tests/arcade/collision.py b/benchmark/tests/arcade/collision.py index 31cccec..b8c64bf 100644 --- a/benchmark/tests/arcade/collision.py +++ b/benchmark/tests/arcade/collision.py @@ -1,4 +1,5 @@ import random +from pathlib import Path import arcade @@ -20,8 +21,9 @@ class Test(ArcadePerfTest): name = "collision" instances = (({"method": 3}, "Simple"),) - def __init__(self, method: int = DEFAULT_METHOD): + def __init__(self, session_dir: Path, method: int = DEFAULT_METHOD): super().__init__( + session_dir, size=(SCREEN_WIDTH, SCREEN_HEIGHT), title=SCREEN_TITLE, start_count=0, diff --git a/benchmark/tests/arcade_accelerate/collision.py b/benchmark/tests/arcade_accelerate/collision.py index 062d61d..aa65b7e 100644 --- a/benchmark/tests/arcade_accelerate/collision.py +++ b/benchmark/tests/arcade_accelerate/collision.py @@ -1,4 +1,8 @@ import random +from pathlib import Path + +import arcade_accelerate +arcade_accelerate.bootstrap() import arcade @@ -20,8 +24,9 @@ class Test(AcceleratedPerfTest): name = "collision" instances = (({"method": 3}, "Simple"),) - def __init__(self, method: int = DEFAULT_METHOD): + def __init__(self, session_dir: Path, method: int = DEFAULT_METHOD): super().__init__( + session_dir, size=(SCREEN_WIDTH, SCREEN_HEIGHT), title=SCREEN_TITLE, start_count=0, diff --git a/benchmark/tests/base.py b/benchmark/tests/base.py index fbaa27b..1888b79 100644 --- a/benchmark/tests/base.py +++ b/benchmark/tests/base.py @@ -1,13 +1,11 @@ -import sys +import multiprocessing from pathlib import Path from typing import Tuple -import arcade - from benchmark.timing import PerformanceTiming -class PerfTest: +class PerfTest(multiprocessing.Process): name = "default" type = "default" series_name = "default" @@ -15,6 +13,7 @@ class PerfTest: def __init__( self, + session_dir: Path, size: Tuple[int, int], title: str = "Perf Test", start_count: int = 0, @@ -22,6 +21,8 @@ def __init__( duration: float = 60.0, **kwargs, ): + super().__init__() + self.session_dir = session_dir self.size = size self.title = title self.start_count = start_count @@ -43,9 +44,9 @@ def on_update(self, delta_time: float): def update_state(self): pass - def run(self, session_dir: Path): + def run(self): self.frame = 0 - out_path = session_dir / "data" + out_path = self.session_dir / "data" out_path.mkdir(parents=True, exist_ok=True) self.timing = PerformanceTiming( @@ -61,6 +62,7 @@ class ArcadePerfTest(PerfTest): def __init__( self, + session_dir: Path, size: Tuple[int, int], title: str = "Perf Test", start_count: int = 0, @@ -69,6 +71,7 @@ def __init__( **kwargs, ): super().__init__( + session_dir, size=size, title=title, start_count=start_count, @@ -99,9 +102,9 @@ def run_test(self): self.update_state() self.window.flip() - def run(self, session_dir: Path): + def run(self): """Run the test collecting data.""" - super().run(session_dir) + super().run() self.create_window() self.setup() @@ -129,6 +132,7 @@ def run(self, session_dir: Path): self.timing.write() def create_window(self): + import arcade try: self.window = arcade.get_window() self.window.set_size(*self.size) @@ -142,22 +146,4 @@ def create_window(self): class AcceleratedPerfTest(ArcadePerfTest): - type = "arcade-accelerate" - - def run(self, session_dir: Path): - # This is necessary to unload arcade and ensure that we have the arcade-accelerate bootstrap applied - # The test module itself is responsbile for applying the bootstrap, but arcade needs to be fully unloaded before then - to_uncache = [] - for mod in sys.modules: - if mod.startswith("arcade."): - to_uncache.append(mod) - - for mod in to_uncache: - del sys.modules[mod] - - import arcade_accelerate - - arcade_accelerate.bootstrap() - import arcade - - super().run(session_dir) + type = "accelerate" \ No newline at end of file diff --git a/src/sprite_list.rs b/src/sprite_list.rs index cfd43c5..64a30ce 100644 --- a/src/sprite_list.rs +++ b/src/sprite_list.rs @@ -7,7 +7,9 @@ pub fn check_for_collision_with_list( py: Python<'_>, sprite: &PyAny, // sprite_list: &PyAny, + method: Option, ) -> Vec { + let _final_method = method.unwrap_or(3); let mut final_sprites: Vec = Vec::new(); let mut hitbox1: Option = None; From 1009f5d4cdbb4b22b0fbcb802409d1a542c44bcd Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 18:13:37 -0500 Subject: [PATCH 03/32] Benchmark work (#31) * Multiprocessing re-work for benchmark utility * Update pyo3 and maturin * Math fixes and some tests to check them * Formatting --- Cargo.lock | 51 ++++++++++++++++--------- Cargo.toml | 2 +- pyproject.toml | 2 +- python/arcade_accelerate/__init__.py | 18 ++++----- src/geometry.rs | 12 ++++++ src/hitbox.rs | 4 +- src/math.rs | 57 +++++++++++++++++++++++++++- 7 files changed, 113 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a693f8e..9462a79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,11 +46,17 @@ dependencies = [ "wasi", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "indoc" -version = "1.0.9" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "libc" @@ -70,9 +76,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -106,6 +112,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -123,15 +135,16 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.18.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06a3d8e8a46ab2738109347433cb7b96dffda2e4a218b03ef27090238886b147" +checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "parking_lot", + "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", @@ -140,9 +153,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.18.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75439f995d07ddfad42b192dfcf3bc66a7ecfd8b4a1f5f6f046aa5c2c5d7677d" +checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" dependencies = [ "once_cell", "target-lexicon", @@ -150,9 +163,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.18.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839526a5c07a17ff44823679b68add4a58004de00512a95b6c1c98a6dcac0ee5" +checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" dependencies = [ "libc", "pyo3-build-config", @@ -160,9 +173,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.18.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd44cf207476c6a9760c4653559be4f206efafb924d3e4cbf2721475fc0d6cc5" +checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -172,11 +185,13 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.18.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1f43d8e30460f36350d18631ccf85ded64c059829208fe680904c65bcd0a4c" +checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" dependencies = [ + "heck", "proc-macro2", + "pyo3-build-config", "quote", "syn", ] @@ -243,9 +258,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" dependencies = [ "proc-macro2", "quote", @@ -266,9 +281,9 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unindent" -version = "0.1.11" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "wasi" diff --git a/Cargo.toml b/Cargo.toml index e2ba62f..0d20614 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,5 @@ crate-type = ["cdylib"] [dependencies] float_eq = "1" -pyo3 = "0.18.1" +pyo3 = "0.20.3" rand = "0.8.5" diff --git a/pyproject.toml b/pyproject.toml index 8b6a363..89b79fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["maturin>=0.14,<0.15"] +requires = ["maturin>=1.4.0,<1.5.0"] build-backend = "maturin" [project] diff --git a/python/arcade_accelerate/__init__.py b/python/arcade_accelerate/__init__.py index 7c00d00..e74fa08 100644 --- a/python/arcade_accelerate/__init__.py +++ b/python/arcade_accelerate/__init__.py @@ -24,12 +24,12 @@ def patch_hitboxes(patches): def patch_spritelist_collision(patches): - patches[ - "arcade.sprite_list.collision" - ].check_for_collision_with_list = arcade_accelerate.check_for_collision_with_list - patches[ - "arcade.sprite_list.collision" - ].check_for_collision_with_lists = arcade_accelerate.check_for_collision_with_lists + patches["arcade.sprite_list.collision"].check_for_collision_with_list = ( + arcade_accelerate.check_for_collision_with_list + ) + patches["arcade.sprite_list.collision"].check_for_collision_with_lists = ( + arcade_accelerate.check_for_collision_with_lists + ) def patch_math(patches): @@ -37,6 +37,6 @@ def patch_math(patches): def patch_geometry(patches): - patches[ - "arcade.geometry" - ].are_polygons_intersecting = arcade_accelerate.are_polygons_intersecting + patches["arcade.geometry"].are_polygons_intersecting = ( + arcade_accelerate.are_polygons_intersecting + ) diff --git a/src/geometry.rs b/src/geometry.rs index eb512f5..6d8b33e 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -2,6 +2,10 @@ use pyo3::prelude::*; #[pyfunction] pub fn are_polygons_intersecting(poly_a: Vec<(f32, f32)>, poly_b: Vec<(f32, f32)>) -> bool { + // If either polygon is empty, we should just return False + if poly_a.is_empty() || poly_b.is_empty() { + return false; + } let polygons = [poly_a, poly_b]; for polygon in &polygons { for i1 in 0..polygon.len() { @@ -171,6 +175,14 @@ mod tests { assert!(!result); } + #[test] + fn test_empty_polygons_intersecting() { + let poly_a: Vec<(f32, f32)> = vec![]; + let poly_b: Vec<(f32, f32)> = vec![]; + let result = are_polygons_intersecting(poly_a, poly_b); + assert!(!result); + } + #[test] fn test_is_point_in_box() { // point inside diff --git a/src/hitbox.rs b/src/hitbox.rs index 5b35dfc..34b3c84 100644 --- a/src/hitbox.rs +++ b/src/hitbox.rs @@ -158,8 +158,8 @@ impl RotatableHitBox { let rad_cos = rad.cos(); let rad_sin = rad.sin(); for point in super_.points.iter() { - let x = ((point.0 * rad_cos - point.1 * rad_sin) * super_.scale.0) + super_.position.0; - let y = ((point.0 * rad_sin + point.1 * rad_cos) * super_.scale.1) + super_.position.1; + let x = ((point.0 * rad_cos + point.1 * rad_sin) * super_.scale.0) + super_.position.0; + let y = ((-point.0 * rad_sin + point.1 * rad_cos) * super_.scale.1) + super_.position.1; new_points.push((x, y)); } diff --git a/src/math.rs b/src/math.rs index 385336f..8637e93 100644 --- a/src/math.rs +++ b/src/math.rs @@ -14,8 +14,8 @@ pub fn rotate_point(x: f32, y: f32, cx: f32, cy: f32, angle: f32) -> (f32, f32) let temp_y = y - cy; // rotate point - let xnew = (temp_x * c - temp_y * s) + cx; - let ynew = (temp_x * s + temp_y * c) + cy; + let xnew = (temp_x * c + temp_y * s) + cx; + let ynew = (-temp_x * s + temp_y * c) + cy; let precision = 10i32.pow(_PRECISION) as f32; let x_rounded = (xnew * precision).round() / precision; @@ -232,6 +232,11 @@ mod tests { use super::*; use float_eq::assert_float_eq; + fn round_float(n: f32, decimals: u32) -> f32 { + let nn = 10i32.pow(decimals) as f32; + (n * nn).round() / nn + } + #[test] fn test_clamp() { let mut result = clamp(1.2, 1.0, 2.0); @@ -392,4 +397,52 @@ mod tests { assert_eq!(result.x, 0.0); assert_eq!(result.y, 0.0); } + + #[test] + fn test_rotate_point() { + let mut x: f32 = 0.0; + let mut y: f32 = 0.0; + let mut cx: f32 = 0.0; + let mut cy: f32 = 0.0; + let mut angle: f32 = 0.0; + let mut point: (f32, f32) = rotate_point(x, y, cx, cy, angle); + assert_float_eq!(round_float(point.0, 2), 0.0, abs <= 1.0e-3); + assert_float_eq!(round_float(point.1, 2), 0.0, abs <= 1.0e-3); + + x = 0.0; + y = 0.0; + cx = 0.0; + cy = 0.0; + angle = 90.0; + point = rotate_point(x, y, cx, cy, angle); + assert_float_eq!(round_float(point.0, 2), 0.0, abs <= 1.0e-3); + assert_float_eq!(round_float(point.1, 2), 0.0, abs <= 1.0e-3); + + x = 50.0; + y = 50.0; + cx = 0.0; + cy = 0.0; + angle = 0.0; + point = rotate_point(x, y, cx, cy, angle); + assert_float_eq!(round_float(point.0, 2), 50.0, abs <= 1.0e-3); + assert_float_eq!(round_float(point.1, 2), 50.0, abs <= 1.0e-3); + + x = 50.0; + y = 0.0; + cx = 0.0; + cy = 0.0; + angle = 90.0; + point = rotate_point(x, y, cx, cy, angle); + assert_float_eq!(round_float(point.0, 2), 0.0, abs <= 1.0e-3); + assert_float_eq!(round_float(point.1, 2), -50.0, abs <= 1.0e-3); + + x = 20.0; + y = 10.0; + cx = 10.0; + cy = 10.0; + angle = 180.0; + point = rotate_point(x, y, cx, cy, angle); + assert_float_eq!(round_float(point.0, 2), 0.0, abs <= 1.0e-3); + assert_float_eq!(round_float(point.1, 2), 10.0, abs <= 1.0e-3); + } } From a54571de6567c01d2e9ceaad107f60e4ae9fbe09 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 18:27:18 -0500 Subject: [PATCH 04/32] Update actions versions --- .github/workflows/release.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 387b2a1..5228124 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,8 +13,8 @@ jobs: target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] if: "startsWith(github.ref, 'refs/tags/')" steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - name: Build wheels @@ -25,7 +25,7 @@ jobs: sccache: 'true' manylinux: auto - name: Upload wheels - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: wheels path: dist @@ -37,8 +37,8 @@ jobs: target: [x64, x86] if: "startsWith(github.ref, 'refs/tags/')" steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' architecture: ${{ matrix.target }} @@ -49,7 +49,7 @@ jobs: args: --release --out dist --find-interpreter sccache: 'true' - name: Upload wheels - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: wheels path: dist @@ -61,8 +61,8 @@ jobs: target: [x86_64, aarch64] if: "startsWith(github.ref, 'refs/tags/')" steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - name: Build wheels @@ -72,7 +72,7 @@ jobs: args: --release --out dist --find-interpreter sccache: 'true' - name: Upload wheels - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: wheels path: dist @@ -81,14 +81,14 @@ jobs: runs-on: ubuntu-latest if: "startsWith(github.ref, 'refs/tags/')" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build sdist uses: PyO3/maturin-action@v1 with: command: sdist args: --out dist - name: Upload sdist - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: wheels path: dist @@ -99,7 +99,7 @@ jobs: if: "startsWith(github.ref, 'refs/tags/')" needs: [linux, windows, macos, sdist] steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: wheels - name: Publish to PyPI From 714b0491fa14ea836f5850d367868d36c261992b Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 18:28:13 -0500 Subject: [PATCH 05/32] Update GitHub Actions versions (#32) From c101e2278f37e6124c5443c557b3d9609082b2ac Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 18:33:39 -0500 Subject: [PATCH 06/32] Revert to upload/download artifact v3 (#33) --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5228124..0409812 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,7 +49,7 @@ jobs: args: --release --out dist --find-interpreter sccache: 'true' - name: Upload wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: wheels path: dist @@ -72,7 +72,7 @@ jobs: args: --release --out dist --find-interpreter sccache: 'true' - name: Upload wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: wheels path: dist @@ -88,7 +88,7 @@ jobs: command: sdist args: --out dist - name: Upload sdist - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: wheels path: dist @@ -99,7 +99,7 @@ jobs: if: "startsWith(github.ref, 'refs/tags/')" needs: [linux, windows, macos, sdist] steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v3 with: name: wheels - name: Publish to PyPI From 393b896ba61b56b9c082cb1713ea59cbac430888 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 18:44:40 -0500 Subject: [PATCH 07/32] Remove explicit setup-python use (#34) --- .github/workflows/release.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0409812..681f1b2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,9 +14,6 @@ jobs: if: "startsWith(github.ref, 'refs/tags/')" steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.10' - name: Build wheels uses: PyO3/maturin-action@v1 with: @@ -38,9 +35,6 @@ jobs: if: "startsWith(github.ref, 'refs/tags/')" steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.10' architecture: ${{ matrix.target }} - name: Build wheels uses: PyO3/maturin-action@v1 @@ -62,9 +56,6 @@ jobs: if: "startsWith(github.ref, 'refs/tags/')" steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.10' - name: Build wheels uses: PyO3/maturin-action@v1 with: From a49c137133fc6dcea71bfe503a430ffdbcc589b2 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 18:47:31 -0500 Subject: [PATCH 08/32] Fix actions file (#35) --- .github/workflows/release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 681f1b2..65222fe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,6 @@ jobs: if: "startsWith(github.ref, 'refs/tags/')" steps: - uses: actions/checkout@v4 - architecture: ${{ matrix.target }} - name: Build wheels uses: PyO3/maturin-action@v1 with: From 999dce5ea7dac15f5105a724cc0a11735ccb5700 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 19:20:21 -0500 Subject: [PATCH 09/32] Initial actions re-write. Explicit python versions and artifacts v4 (#36) --- .github/workflows/release.yml | 206 ++++++++++++++++++++++++---------- 1 file changed, 148 insertions(+), 58 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65222fe..03eec28 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,96 +6,186 @@ permissions: contents: read jobs: - linux: + + build-sdist: + name: build sdist runs-on: ubuntu-latest - strategy: - matrix: - target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] - if: "startsWith(github.ref, 'refs/tags/')" steps: - uses: actions/checkout@v4 - - name: Build wheels - uses: PyO3/maturin-action@v1 + - uses: PyO3/maturin-action@v1 with: - target: ${{ matrix.target }} - args: --release --out dist --find-interpreter - sccache: 'true' - manylinux: auto - - name: Upload wheels - uses: actions/upload-artifact@v4 + command: sdist + args: --out dist + rust-toolchain: stable + - uses: actions/upload-artifact@v4 with: - name: wheels + name: pypi_files_sdist path: dist - windows: - runs-on: windows-latest + build: + name: build on ${{ matrix.os }} (${{ matrix.target }} - ${{ matrix.interpreter || 'all' }}${{ matrix.os == 'linux' && format(' - {0}', matrix.manylinux == 'auto' && 'manylinux' || matrix.manylinux) || '' }}) strategy: + fail-fast: false matrix: - target: [x64, x86] + os: [linux, macos, windows] + target: [x86_64, aarch64] + manylinux: [auto] + include: + - os: linux + manylinux: auto + target: i686 + - os: linux + manylinux: auto + target: aarch64 + - os: linux + manylinux: auto + target: armv7 + interpreter: 3.8 3.9 3.10 3.11 3.12 + - os: linux + manylinux: auto + target: ppc64le + interpreter: 3.8 3.9 3.10 3.11 3.12 + - os: linux + manylinux: auto + target: s390x + interpreter: 3.8 3.9 3.10 3.11 3.12 + - os: linux + manylinux: auto + target: x86_64 + interpreter: pypy3.9 pypy3.10 + + # macos; + # all versions x86_64 + # arm pypy and older pythons which can't be run on the arm hardware for PGO + - os: macos + target: x86_64 + - os: macos + target: aarch64 + interpreter: 3.8 3.9 pypy3.9 pypy3.10 + + # windows; + # x86_64 pypy builds are not PGO optimized + # i686 not supported by pypy + # aarch64 only 3.11 and up, also not PGO optimized + - os: windows + target: x86_64 + interpreter: pypy3.9 pypy3.10 + - os: windows + target: i686 + python-architecture: x86 + interpreter: 3.8 3.9 3.10 3.11 3.12 + - os: windows + target: aarch64 + interpreter: 3.11 3.12 + runs-on: ${{ (matrix.os == 'linux' && 'ubuntu') || matrix.os }}-latest if: "startsWith(github.ref, 'refs/tags/')" steps: - uses: actions/checkout@v4 - - name: Build wheels - uses: PyO3/maturin-action@v1 - with: - target: ${{ matrix.target }} - args: --release --out dist --find-interpreter - sccache: 'true' - - name: Upload wheels - uses: actions/upload-artifact@v3 + + - name: set up python + uses: actions/setup-python@v5 with: - name: wheels - path: dist + python-version: '3.11' + architecture: ${{ matrix.python-architecture || 'x64' }} + + - run: pip install -U twine - macos: - runs-on: macos-latest - strategy: - matrix: - target: [x86_64, aarch64] - if: "startsWith(github.ref, 'refs/tags/')" - steps: - - uses: actions/checkout@v4 - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --find-interpreter - sccache: 'true' + manylinux: ${{ matrix.manylinux }} + args: --release --out dist --interpreter ${{ matrix.interpreter || '3.8 3.9 3.10 3.11 3.12 pypy3.9 pypy3.10' }} + rust-toolchain: stable + docker-options: -e CI + + - run: ${{ (matrix.os == 'windows' && 'dir') || 'ls -lh' }} dist/ + + - run: twine check --strtict dist/* + + - uses: actions/upload-artifact@v4 + with: + name: pypi_files_${{ matrix.os }}_${{ matrix.target }}_${{ matrix.interpreter || 'all' }}_${{ matrix.manylinux }} + path: dist - name: Upload wheels - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: wheels path: dist - sdist: + inspect-pypi-assets: + needs: [build, build-sdist, build-pgo] runs-on: ubuntu-latest - if: "startsWith(github.ref, 'refs/tags/')" + steps: - uses: actions/checkout@v4 - - name: Build sdist - uses: PyO3/maturin-action@v1 - with: - command: sdist - args: --out dist - - name: Upload sdist - uses: actions/upload-artifact@v3 + + - name: get dist artifacts + uses: actions/download-artifact@v4 with: - name: wheels + pattern: pypi_files_* + merge-multiple: true path: dist + - name: list dist files + run: | + ls -lh dist/ + ls -l dist/ + echo "`ls dist | wc -l` files" + + - name: extract and list sdist file + run: | + mkdir sdist-files + tar -xvf dist/*.tar.gz -C sdist-files + tree -a sdist-files + + - name: extract and list wheel file + run: | + ls dist/*cp310-manylinux*x86_64.whl | head -n 1 + python -m zipfile --list `ls dist/*cp310-manylinux*x86_64.whl | head -n 1` + release: - name: Release + needs: [build, build-sdist, check] + if: success() && startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest - if: "startsWith(github.ref, 'refs/tags/')" - needs: [linux, windows, macos, sdist] + steps: - - uses: actions/download-artifact@v3 + - uses: actions/checkout@v4 + + - name: set up python + uses: actions/setup-python@v5 with: - name: wheels - - name: Publish to PyPI - uses: PyO3/maturin-action@v1 + python-version: '3.10' + + - run: pip install -U twine + + - name: check package version + run: python .github/check_version.py + + - name: get dist artifacts + uses: actions/download-artifact@v4 + with: + pattern: pypi_files_* + merge-multiple: true + path: dist + + - run: twine check --strict dist/* + + - name: upload to pypi + run: twine upload dist/* env: - MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + + - name: get wasm dist artifacts + uses: actions/download-artifact@v4 + with: + name: wasm_wheels + path: wasm + + - name: upload to github release + uses: softprops/action-gh-release@v1 with: - command: upload - args: --skip-existing * \ No newline at end of file + files: | + wasm/*.whl + prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }} \ No newline at end of file From 0db4a0edcb21b75ebecb37f631e4d38405fa412f Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 19:44:28 -0500 Subject: [PATCH 10/32] Actions fixes --- .github/workflows/release.yml | 31 ++++--------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03eec28..37d6312 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,7 @@ jobs: matrix: os: [linux, macos, windows] target: [x86_64, aarch64] + interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] manylinux: [auto] include: - os: linux @@ -40,40 +41,16 @@ jobs: - os: linux manylinux: auto target: armv7 - interpreter: 3.8 3.9 3.10 3.11 3.12 - os: linux manylinux: auto target: ppc64le - interpreter: 3.8 3.9 3.10 3.11 3.12 - os: linux manylinux: auto target: s390x - interpreter: 3.8 3.9 3.10 3.11 3.12 - - os: linux - manylinux: auto - target: x86_64 - interpreter: pypy3.9 pypy3.10 - - # macos; - # all versions x86_64 - # arm pypy and older pythons which can't be run on the arm hardware for PGO - - os: macos - target: x86_64 - - os: macos - target: aarch64 - interpreter: 3.8 3.9 pypy3.9 pypy3.10 - - # windows; - # x86_64 pypy builds are not PGO optimized - # i686 not supported by pypy - # aarch64 only 3.11 and up, also not PGO optimized - - os: windows - target: x86_64 - interpreter: pypy3.9 pypy3.10 - os: windows target: i686 python-architecture: x86 - interpreter: 3.8 3.9 3.10 3.11 3.12 + # Windows aarch64 only supported by Python 3.11+ - os: windows target: aarch64 interpreter: 3.11 3.12 @@ -95,7 +72,7 @@ jobs: with: target: ${{ matrix.target }} manylinux: ${{ matrix.manylinux }} - args: --release --out dist --interpreter ${{ matrix.interpreter || '3.8 3.9 3.10 3.11 3.12 pypy3.9 pypy3.10' }} + args: --release --out dist --interpreter ${{ matrix.interpreter || '3.8 3.9 3.10 3.11 3.12' }} rust-toolchain: stable docker-options: -e CI @@ -114,7 +91,7 @@ jobs: path: dist inspect-pypi-assets: - needs: [build, build-sdist, build-pgo] + needs: [build, build-sdist] runs-on: ubuntu-latest steps: From e68aefcc38c4abe48f4d9e4c09a32bc3dae9eab9 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 19:45:53 -0500 Subject: [PATCH 11/32] More actions fixes --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 37d6312..eefa21f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -122,7 +122,7 @@ jobs: python -m zipfile --list `ls dist/*cp310-manylinux*x86_64.whl | head -n 1` release: - needs: [build, build-sdist, check] + needs: [build, build-sdist] if: success() && startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest From 5bb7b5715100c2577a2a114a15d897385c438f26 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 19:48:13 -0500 Subject: [PATCH 12/32] More actions fixes --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eefa21f..d3dbfcb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,7 +78,7 @@ jobs: - run: ${{ (matrix.os == 'windows' && 'dir') || 'ls -lh' }} dist/ - - run: twine check --strtict dist/* + - run: twine check --strict dist/* - uses: actions/upload-artifact@v4 with: From c167d6076cbc08822bb244e941de5f29705461c4 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 19:51:22 -0500 Subject: [PATCH 13/32] Even more actions work --- .github/workflows/release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3dbfcb..71503cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,21 +35,27 @@ jobs: - os: linux manylinux: auto target: i686 + interpreter: 3.8 3.9 3.10 3.11 3.12 - os: linux manylinux: auto target: aarch64 + interpreter: 3.8 3.9 3.10 3.11 3.12 - os: linux manylinux: auto target: armv7 + interpreter: 3.8 3.9 3.10 3.11 3.12 - os: linux manylinux: auto target: ppc64le + interpreter: 3.8 3.9 3.10 3.11 3.12 - os: linux manylinux: auto target: s390x + interpreter: 3.8 3.9 3.10 3.11 3.12 - os: windows target: i686 python-architecture: x86 + interpreter: 3.8 3.9 3.10 3.11 3.12 # Windows aarch64 only supported by Python 3.11+ - os: windows target: aarch64 From b1bd2f48df52eba8171254adc1e9e51a5711412c Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 19:53:29 -0500 Subject: [PATCH 14/32] More actions work --- .github/workflows/release.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 71503cc..e46a6b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,31 +35,31 @@ jobs: - os: linux manylinux: auto target: i686 - interpreter: 3.8 3.9 3.10 3.11 3.12 + interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] - os: linux manylinux: auto target: aarch64 - interpreter: 3.8 3.9 3.10 3.11 3.12 + interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] - os: linux manylinux: auto target: armv7 - interpreter: 3.8 3.9 3.10 3.11 3.12 + interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] - os: linux manylinux: auto target: ppc64le - interpreter: 3.8 3.9 3.10 3.11 3.12 + interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] - os: linux manylinux: auto target: s390x - interpreter: 3.8 3.9 3.10 3.11 3.12 + interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] - os: windows target: i686 python-architecture: x86 - interpreter: 3.8 3.9 3.10 3.11 3.12 + interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] # Windows aarch64 only supported by Python 3.11+ - os: windows target: aarch64 - interpreter: 3.11 3.12 + interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] runs-on: ${{ (matrix.os == 'linux' && 'ubuntu') || matrix.os }}-latest if: "startsWith(github.ref, 'refs/tags/')" steps: From fddc69e02e83b3c1d49acbc17bea6b4528683bf2 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 19:59:45 -0500 Subject: [PATCH 15/32] More actions work --- .github/workflows/release.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e46a6b1..56de5f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,37 +29,30 @@ jobs: matrix: os: [linux, macos, windows] target: [x86_64, aarch64] - interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] manylinux: [auto] include: - os: linux manylinux: auto target: i686 - interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] - os: linux manylinux: auto target: aarch64 - interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] - os: linux manylinux: auto target: armv7 - interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] - os: linux manylinux: auto target: ppc64le - interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] - os: linux manylinux: auto target: s390x - interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] - os: windows target: i686 python-architecture: x86 - interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] # Windows aarch64 only supported by Python 3.11+ - os: windows target: aarch64 - interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12'] + interpreter: 3.11 3.12 runs-on: ${{ (matrix.os == 'linux' && 'ubuntu') || matrix.os }}-latest if: "startsWith(github.ref, 'refs/tags/')" steps: From b8a6931610416dfe4bfc53f4f4eec9b1a857d205 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 20:10:30 -0500 Subject: [PATCH 16/32] More actions work --- .github/workflows/release.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 56de5f3..9b47cbe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,11 +83,6 @@ jobs: with: name: pypi_files_${{ matrix.os }}_${{ matrix.target }}_${{ matrix.interpreter || 'all' }}_${{ matrix.manylinux }} path: dist - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: wheels - path: dist inspect-pypi-assets: needs: [build, build-sdist] From b74ad45af87961b5236fd40ce26bbf5e3b88eb0a Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 20:15:47 -0500 Subject: [PATCH 17/32] Remove aarch64 for windows, for now --- .github/workflows/release.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9b47cbe..efbff8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,10 +49,6 @@ jobs: - os: windows target: i686 python-architecture: x86 - # Windows aarch64 only supported by Python 3.11+ - - os: windows - target: aarch64 - interpreter: 3.11 3.12 runs-on: ${{ (matrix.os == 'linux' && 'ubuntu') || matrix.os }}-latest if: "startsWith(github.ref, 'refs/tags/')" steps: From 0fcf9274af3a040f07d2541f5ee9a2eaf63127aa Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 20:20:11 -0500 Subject: [PATCH 18/32] Actually remove windows aarch64 --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index efbff8d..05f909f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,6 +49,9 @@ jobs: - os: windows target: i686 python-architecture: x86 + exclude: + - os: windows + target: aarch64 runs-on: ${{ (matrix.os == 'linux' && 'ubuntu') || matrix.os }}-latest if: "startsWith(github.ref, 'refs/tags/')" steps: From 8ffcbc67987daa486afb9cab2cc08ff07a359f35 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 20:28:20 -0500 Subject: [PATCH 19/32] Adjust Rust library imports to include all functions --- src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6c6e028..c994a82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,16 @@ use pyo3::prelude::*; mod hitbox; -pub use hitbox::{HitBox, RotatableHitBox}; +pub use hitbox::*; mod math; +pub use math::*; mod geometry; -pub use geometry::{ - are_lines_intersecting, are_polygons_intersecting, get_triangle_orientation, is_point_in_box, - is_point_in_polygon, -}; +pub use geometry::*; mod sprite_list; -pub use sprite_list::{check_for_collision_with_list, check_for_collision_with_lists}; +pub use sprite_list::*; /// A Python module implemented in Rust. #[pymodule] From d93367cc2328edc054e07636270fc7143be8941a Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 20:28:47 -0500 Subject: [PATCH 20/32] More actions work --- .github/workflows/release.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 05f909f..4f27f06 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,9 +129,6 @@ jobs: - run: pip install -U twine - - name: check package version - run: python .github/check_version.py - - name: get dist artifacts uses: actions/download-artifact@v4 with: @@ -147,12 +144,6 @@ jobs: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - - name: get wasm dist artifacts - uses: actions/download-artifact@v4 - with: - name: wasm_wheels - path: wasm - - name: upload to github release uses: softprops/action-gh-release@v1 with: From d240f60d2e11d9515151d0289cf7a1be342ca169 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 20:47:24 -0500 Subject: [PATCH 21/32] Handle extreme sizes better, use max f32 value for line segment in is_point_in_polygon --- src/geometry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/geometry.rs b/src/geometry.rs index 6d8b33e..b8906aa 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -112,7 +112,7 @@ pub fn is_point_in_polygon(x: f32, y: f32, polygon: Vec<(f32, f32)>) -> bool { // Create a point for line segment // from p to infinite - let extreme = (10000.0, p.1); + let extreme = (f32::MAX, p.1); // To count number of points in polygon // whose y-coordinate is equal to From f98b9aa9cbc7d2d241cb498e6e6bacbe594df9d2 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 20:47:45 -0500 Subject: [PATCH 22/32] Add missing functions to Python extension and bootstrap process --- python/arcade_accelerate/__init__.py | 29 ++++++++++++++++++++++++++++ src/lib.rs | 22 +++++++++++++++------ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/python/arcade_accelerate/__init__.py b/python/arcade_accelerate/__init__.py index e74fa08..1f7fa81 100644 --- a/python/arcade_accelerate/__init__.py +++ b/python/arcade_accelerate/__init__.py @@ -34,9 +34,38 @@ def patch_spritelist_collision(patches): def patch_math(patches): patches["arcade.math"].rotate_point = arcade_accelerate.rotate_point + patches["arcade.math"].clamp = arcade_accelerate.clamp + patches["arcade.math"].lerp = arcade_accelerate.lerp + patches["arcade.math"].lerp_vec = arcade_accelerate.lerp_vec + patches["arcade.math"].lerp_angle = arcade_accelerate.lerp_angle + patches["arcade.math"].get_distance = arcade_accelerate.get_distance + patches["arcade.math"].get_angle_degrees = arcade_accelerate.get_angle_degrees + patches["arcade.math"].get_angle_radians = arcade_accelerate.get_angle_radians + patches["arcade.math"].rand_in_rect = arcade_accelerate.rand_in_rect + patches["arcade.math"].rand_in_circle = arcade_accelerate.rand_in_circle + patches["arcade.math"].rand_on_circle = arcade_accelerate.rand_on_circle + patches["arcade.math"].rand_on_line = arcade_accelerate.rand_on_line + patches["arcade.math"].rand_angle_360_deg = arcade_accelerate.rand_angle_360_deg + patches["arcade.math"].rand_angle_spread_deg = ( + arcade_accelerate.rand_angle_spread_deg + ) + patches["arcade.math"].rand_vec_degree_spread = ( + arcade_accelerate.rand_vec_degree_spread + ) + patches["arcade.math"].rand_vec_magnitude = arcade_accelerate.rand_vec_magnitude def patch_geometry(patches): patches["arcade.geometry"].are_polygons_intersecting = ( arcade_accelerate.are_polygons_intersecting ) + patches["arcade.geometry"].is_point_in_box = arcade_accelerate.is_point_in_box + patches["arcade.geometry"].get_triangle_orientation = ( + arcade_accelerate.get_triangle_orientation + ) + patches["arcade.geometry"].are_lines_intersecting = ( + arcade_accelerate.are_lines_intersecting + ) + patches["arcade.geometry"].is_point_in_polygon = ( + arcade_accelerate.is_point_in_polygon + ) diff --git a/src/lib.rs b/src/lib.rs index c994a82..40917db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,8 +15,8 @@ pub use sprite_list::*; /// A Python module implemented in Rust. #[pymodule] fn arcade_accelerate(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_class::()?; - m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_function(wrap_pyfunction!(math::rotate_point, m)?)?; m.add_function(wrap_pyfunction!(math::clamp, m)?)?; m.add_function(wrap_pyfunction!(math::lerp, m)?)?; @@ -33,9 +33,19 @@ fn arcade_accelerate(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(math::rand_angle_spread_deg, m)?)?; m.add_function(wrap_pyfunction!(math::rand_vec_degree_spread, m)?)?; m.add_function(wrap_pyfunction!(math::rand_vec_magnitude, m)?)?; - m.add_function(wrap_pyfunction!(are_polygons_intersecting, m)?)?; - m.add_function(wrap_pyfunction!(check_for_collision_with_list, m)?)?; - m.add_function(wrap_pyfunction!(check_for_collision_with_lists, m)?)?; - m.add_function(wrap_pyfunction!(is_point_in_polygon, m)?)?; + m.add_function(wrap_pyfunction!(geometry::are_polygons_intersecting, m)?)?; + m.add_function(wrap_pyfunction!(geometry::is_point_in_polygon, m)?)?; + m.add_function(wrap_pyfunction!(geometry::is_point_in_box, m)?)?; + m.add_function(wrap_pyfunction!(geometry::get_triangle_orientation, m)?)?; + m.add_function(wrap_pyfunction!(geometry::are_lines_intersecting, m)?)?; + m.add_function(wrap_pyfunction!( + sprite_list::check_for_collision_with_list, + m + )?)?; + m.add_function(wrap_pyfunction!( + sprite_list::check_for_collision_with_lists, + m + )?)?; + Ok(()) } From 2e0434143d8c6ff97f734f3a3c1d3c92dc77fa67 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Fri, 1 Mar 2024 21:00:00 -0500 Subject: [PATCH 23/32] Update version to 1.0.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9462a79..079a9b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "arcade-accelerate" -version = "0.1.0" +version = "1.0.0" dependencies = [ "float_eq", "pyo3", diff --git a/Cargo.toml b/Cargo.toml index 0d20614..4208f37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "arcade-accelerate" -version = "0.1.0" +version = "1.0.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/pyproject.toml b/pyproject.toml index 89b79fd..fae5656 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "arcade-accelerate" -version = "0.1.0" +version = "1.0.0" description = "A companion library for Arcade providing accelerated Rust functions" readme = "README.md" authors = [ From 4bea1d149ed9bae3ffb993cd3808845e931d1397 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Sat, 2 Mar 2024 02:44:11 -0500 Subject: [PATCH 24/32] Native polygon intersection function to accept references that pyfunction cannot --- src/geometry.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++ src/sprite_list.rs | 6 ++--- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/geometry.rs b/src/geometry.rs index b8906aa..3ea7a95 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -1,5 +1,66 @@ use pyo3::prelude::*; +pub fn are_polygons_intersecting_native( + poly_a: &Vec<(f32, f32)>, + poly_b: &Vec<(f32, f32)>, +) -> bool { + // If either polygon is empty, we should just return False + if poly_a.is_empty() || poly_b.is_empty() { + return false; + } + let polygons = [poly_a, poly_b]; + for polygon in &polygons { + for i1 in 0..polygon.len() { + let i2 = (i1 + 1) % polygon.len(); + let projection_1 = polygon[i1]; + let projection_2 = polygon[i2]; + + let normal = ( + projection_2.1 - projection_1.1, + projection_1.0 - projection_2.0, + ); + + let mut min_a: Option = None; + let mut max_a: Option = None; + let mut min_b: Option = None; + let mut max_b: Option = None; + + for point in polygons[0] { + let projected = normal.0 * point.0 + normal.1 * point.1; + match min_a { + Some(x) if projected < x => min_a = Some(projected), + Some(_x) => {} + None => min_a = Some(projected), + } + match max_a { + Some(x) if projected > x => max_a = Some(projected), + Some(_x) => {} + None => max_a = Some(projected), + } + } + + for point in polygons[1] { + let projected = normal.0 * point.0 + normal.1 * point.1; + match min_b { + Some(x) if projected < x => min_b = Some(projected), + Some(_x) => {} + None => min_b = Some(projected), + } + match max_b { + Some(x) if projected > x => max_b = Some(projected), + Some(_x) => {} + None => max_b = Some(projected), + } + } + + if max_a <= min_b || max_b <= min_a { + return false; + } + } + } + true +} + #[pyfunction] pub fn are_polygons_intersecting(poly_a: Vec<(f32, f32)>, poly_b: Vec<(f32, f32)>) -> bool { // If either polygon is empty, we should just return False diff --git a/src/sprite_list.rs b/src/sprite_list.rs index 64a30ce..89e2807 100644 --- a/src/sprite_list.rs +++ b/src/sprite_list.rs @@ -1,4 +1,4 @@ -use crate::geometry::are_polygons_intersecting; +use crate::geometry::are_polygons_intersecting_native; use crate::hitbox::{HitBox, RotatableHitBox}; use pyo3::prelude::*; @@ -72,7 +72,7 @@ pub fn check_for_collision_with_list( panic!("unknown hitbox type"); }; - let check_2 = are_polygons_intersecting(main_points.to_vec(), other_points); + let check_2 = are_polygons_intersecting_native(&main_points, &other_points); if check_2 { final_sprites.push(sprite2.to_object(py)); @@ -149,7 +149,7 @@ pub fn check_for_collision_with_lists( panic!("unknown hitbox type"); }; - let check_2 = are_polygons_intersecting(main_points.to_vec(), other_points); + let check_2 = are_polygons_intersecting_native(&main_points, &other_points); if check_2 { final_sprites.push(sprite2.to_object(py)); From aa75d1c728db6685a273410cc6f786efa1232e11 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Sat, 2 Mar 2024 02:50:44 -0500 Subject: [PATCH 25/32] Native Polygon Intersection (#37) From 1b1da04d4286479c34e4590b9c2160bf74de3449 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Sat, 2 Mar 2024 22:55:01 -0500 Subject: [PATCH 26/32] HitBox/Collision Improvements (#38) --- src/hitbox.rs | 301 +++++++++++++++++++++++++++++++++------------ src/sprite_list.rs | 156 +++++++++-------------- 2 files changed, 279 insertions(+), 178 deletions(-) diff --git a/src/hitbox.rs b/src/hitbox.rs index 34b3c84..ed80446 100644 --- a/src/hitbox.rs +++ b/src/hitbox.rs @@ -1,15 +1,22 @@ use pyo3::prelude::*; +pub trait NativeAdjustedPoints { + fn get_adjusted_points_native(&mut self) -> &Vec<(f32, f32)>; +} + #[derive(Clone)] -#[pyclass(subclass, module = "arcade.hitbox.base")] +#[pyclass(module = "arcade.hitbox.base")] pub struct HitBox { #[pyo3(get, set)] pub points: Vec<(f32, f32)>, - #[pyo3(get, set)] + #[pyo3(get)] pub position: (f32, f32), - #[pyo3(get, set)] + #[pyo3(get)] pub scale: (f32, f32), pub angle: f32, + + pub adjusted_cache: Vec<(f32, f32)>, + pub cache_dirty: bool, } #[pymethods] @@ -27,6 +34,8 @@ impl HitBox { position: final_position, scale: final_scale, angle: 0.0, + adjusted_cache: vec![], + cache_dirty: true, } } @@ -48,90 +57,93 @@ impl HitBox { Ok(adjustable) } - fn get_adjusted_points(self_: PyRef<'_, Self>) -> Vec<(f32, f32)> { - let mut new_points: Vec<(f32, f32)> = Vec::with_capacity(self_.points.len()); - - for point in self_.points.iter() { - let x = (point.0 * self_.scale.0) + self_.position.0; - let y = (point.1 * self_.scale.1) + self_.position.1; - new_points.push((x, y)); + pub fn get_adjusted_points(&mut self) -> Vec<(f32, f32)> { + if self.cache_dirty { + self.adjusted_cache = Vec::with_capacity(self.points.len()); + for point in self.points.iter() { + let x = (point.0 * self.scale.0) + self.position.0; + let y = (point.1 * self.scale.1) + self.position.1; + self.adjusted_cache.push((x, y)); + } + self.cache_dirty = false; } - new_points + self.adjusted_cache.to_vec() + } + + #[setter] + pub fn set_position(&mut self, value: (f32, f32)) -> PyResult<()> { + self.position = value; + self.cache_dirty = true; + Ok(()) + } + + #[setter] + pub fn set_scale(&mut self, value: (f32, f32)) -> PyResult<()> { + self.scale = value; + self.cache_dirty = true; + Ok(()) } #[getter] - pub fn left(self_: PyRef<'_, Self>) -> PyResult { - let mut converted = HitBox::get_adjusted_points(self_); + pub fn left(&mut self) -> PyResult { + let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native().to_vec(); converted.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); Ok(converted[0].0) } #[getter] - pub fn right(self_: PyRef<'_, Self>) -> PyResult { - let mut converted: Vec<(f32, f32)> = HitBox::get_adjusted_points(self_); + pub fn right(&mut self) -> PyResult { + let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native().to_vec(); converted.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); Ok(converted[0].0) } #[getter] - pub fn bottom(self_: PyRef<'_, Self>) -> PyResult { - let mut converted: Vec<(f32, f32)> = HitBox::get_adjusted_points(self_); + pub fn bottom(&mut self) -> PyResult { + let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native().to_vec(); converted.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); Ok(converted[0].1) } #[getter] - pub fn top(self_: PyRef<'_, Self>) -> PyResult { - let mut converted: Vec<(f32, f32)> = HitBox::get_adjusted_points(self_); + pub fn top(&mut self) -> PyResult { + let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native().to_vec(); converted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); Ok(converted[0].1) } } -impl HitBox { - pub fn get_adjusted_points_native(&self) -> Vec<(f32, f32)> { - let mut new_points: Vec<(f32, f32)> = Vec::with_capacity(self.points.len()); - - for point in self.points.iter() { - let x = (point.0 * self.scale.0) + self.position.0; - let y = (point.1 * self.scale.1) + self.position.1; - new_points.push((x, y)); +impl NativeAdjustedPoints for HitBox { + fn get_adjusted_points_native(&mut self) -> &Vec<(f32, f32)> { + if self.cache_dirty { + self.adjusted_cache = Vec::with_capacity(self.points.len()); + for point in self.points.iter() { + let x = (point.0 * self.scale.0) + self.position.0; + let y = (point.1 * self.scale.1) + self.position.1; + self.adjusted_cache.push((x, y)); + } + self.cache_dirty = false; } - new_points - } - - pub fn left_native(&self) -> f32 { - let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native(); - converted.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); - converted[0].0 - } - - pub fn right_native(&self) -> f32 { - let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native(); - converted.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); - converted[0].0 - } - - pub fn bottom_native(&self) -> f32 { - let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native(); - converted.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); - converted[0].1 - } - - pub fn top_native(&self) -> f32 { - let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native(); - converted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); - converted[0].1 + &self.adjusted_cache } } #[derive(Clone)] -#[pyclass(extends=HitBox, module = "arcade.hitbox.base")] +#[pyclass(module = "arcade.hitbox.base")] pub struct RotatableHitBox { #[pyo3(get, set)] - angle: f32, + pub points: Vec<(f32, f32)>, + #[pyo3(get)] + pub position: (f32, f32), + #[pyo3(get)] + pub scale: (f32, f32), + #[pyo3(get)] + pub angle: f32, + + pub adjusted_cache: Vec<(f32, f32)>, + pub cache_dirty: bool, } #[pymethods] @@ -142,59 +154,194 @@ impl RotatableHitBox { position: Option<(f32, f32)>, scale: Option<(f32, f32)>, angle: Option, - ) -> (Self, HitBox) { + ) -> RotatableHitBox { + let final_position = position.unwrap_or((0.0, 0.0)); + let final_scale = scale.unwrap_or((1.0, 1.0)); let final_angle = angle.unwrap_or(0.0); - ( - RotatableHitBox { angle: final_angle }, - HitBox::new(points, position, scale), + RotatableHitBox { + points, + position: final_position, + scale: final_scale, + angle: final_angle, + adjusted_cache: vec![], + cache_dirty: true, + } + } + + fn create_rotatable( + self_: PyRef<'_, Self>, + py: Python<'_>, + angle: Option, + ) -> PyResult> { + let adjustable: Py = Py::new( + py, + RotatableHitBox::new( + self_.points.to_vec(), + Some(self_.position), + Some(self_.scale), + angle, + ), ) + .unwrap(); + Ok(adjustable) } - pub fn get_adjusted_points(self_: PyRef<'_, Self>) -> Vec<(f32, f32)> { - let super_: &HitBox = self_.as_ref(); - let mut new_points: Vec<(f32, f32)> = Vec::with_capacity(super_.points.len()); + pub fn get_adjusted_points(&mut self) -> Vec<(f32, f32)> { + if self.cache_dirty { + self.adjusted_cache = Vec::with_capacity(self.points.len()); - let rad = self_.angle.to_radians(); - let rad_cos = rad.cos(); - let rad_sin = rad.sin(); - for point in super_.points.iter() { - let x = ((point.0 * rad_cos + point.1 * rad_sin) * super_.scale.0) + super_.position.0; - let y = ((-point.0 * rad_sin + point.1 * rad_cos) * super_.scale.1) + super_.position.1; - new_points.push((x, y)); + let rad = self.angle.to_radians(); + let rad_cos = rad.cos(); + let rad_sin = rad.sin(); + for point in self.points.iter() { + let x = ((point.0 * rad_cos + point.1 * rad_sin) * self.scale.0) + self.position.0; + let y = ((-point.0 * rad_sin + point.1 * rad_cos) * self.scale.1) + self.position.1; + self.adjusted_cache.push((x, y)); + } + self.cache_dirty = false; } - new_points + self.adjusted_cache.to_vec() + } + + #[setter] + pub fn set_position(&mut self, value: (f32, f32)) -> PyResult<()> { + self.position = value; + self.cache_dirty = true; + Ok(()) + } + + #[setter] + pub fn set_scale(&mut self, value: (f32, f32)) -> PyResult<()> { + self.scale = value; + self.cache_dirty = true; + Ok(()) + } + + #[setter] + pub fn set_angle(&mut self, value: f32) -> PyResult<()> { + self.angle = value; + self.cache_dirty = true; + Ok(()) } #[getter] - fn left(self_: PyRef<'_, Self>) -> PyResult { - let mut converted: Vec<(f32, f32)> = RotatableHitBox::get_adjusted_points(self_); + pub fn left(&mut self) -> PyResult { + let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native().to_vec(); converted.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); Ok(converted[0].0) } #[getter] - fn right(self_: PyRef<'_, Self>) -> PyResult { - let mut converted: Vec<(f32, f32)> = RotatableHitBox::get_adjusted_points(self_); + pub fn right(&mut self) -> PyResult { + let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native().to_vec(); converted.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); Ok(converted[0].0) } #[getter] - fn bottom(self_: PyRef<'_, Self>) -> PyResult { - let mut converted: Vec<(f32, f32)> = RotatableHitBox::get_adjusted_points(self_); + pub fn bottom(&mut self) -> PyResult { + let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native().to_vec(); converted.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); Ok(converted[0].1) } #[getter] - fn top(self_: PyRef<'_, Self>) -> PyResult { - let mut converted: Vec<(f32, f32)> = RotatableHitBox::get_adjusted_points(self_); + pub fn top(&mut self) -> PyResult { + let mut converted: Vec<(f32, f32)> = self.get_adjusted_points_native().to_vec(); converted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); Ok(converted[0].1) } } +impl NativeAdjustedPoints for RotatableHitBox { + fn get_adjusted_points_native(&mut self) -> &Vec<(f32, f32)> { + if self.cache_dirty { + self.adjusted_cache = Vec::with_capacity(self.points.len()); + + let rad = self.angle.to_radians(); + let rad_cos = rad.cos(); + let rad_sin = rad.sin(); + for point in self.points.iter() { + let x = ((point.0 * rad_cos + point.1 * rad_sin) * self.scale.0) + self.position.0; + let y = ((-point.0 * rad_sin + point.1 * rad_cos) * self.scale.1) + self.position.1; + self.adjusted_cache.push((x, y)); + } + self.cache_dirty = false; + } + + &self.adjusted_cache + } +} + +// #[derive(Clone)] +// #[pyclass(extends=HitBox, module = "arcade.hitbox.base")] +// pub struct RotatableHitBox { +// #[pyo3(get, set)] +// angle: f32, +// } + +// #[pymethods] +// impl RotatableHitBox { +// #[new] +// fn new( +// points: Vec<(f32, f32)>, +// position: Option<(f32, f32)>, +// scale: Option<(f32, f32)>, +// angle: Option, +// ) -> (Self, HitBox) { +// let final_angle = angle.unwrap_or(0.0); +// ( +// RotatableHitBox { angle: final_angle }, +// HitBox::new(points, position, scale), +// ) +// } + +// pub fn get_adjusted_points(self_: PyRef<'_, Self>) -> Vec<(f32, f32)> { +// let super_: &HitBox = self_.as_ref(); +// let mut new_points: Vec<(f32, f32)> = Vec::with_capacity(super_.points.len()); + +// let rad = self_.angle.to_radians(); +// let rad_cos = rad.cos(); +// let rad_sin = rad.sin(); +// for point in super_.points.iter() { +// let x = ((point.0 * rad_cos + point.1 * rad_sin) * super_.scale.0) + super_.position.0; +// let y = ((-point.0 * rad_sin + point.1 * rad_cos) * super_.scale.1) + super_.position.1; +// new_points.push((x, y)); +// } + +// new_points +// } + +// #[getter] +// fn left(self_: PyRef<'_, Self>) -> PyResult { +// let mut converted: Vec<(f32, f32)> = RotatableHitBox::get_adjusted_points(self_); +// converted.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); +// Ok(converted[0].0) +// } + +// #[getter] +// fn right(self_: PyRef<'_, Self>) -> PyResult { +// let mut converted: Vec<(f32, f32)> = RotatableHitBox::get_adjusted_points(self_); +// converted.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +// Ok(converted[0].0) +// } + +// #[getter] +// fn bottom(self_: PyRef<'_, Self>) -> PyResult { +// let mut converted: Vec<(f32, f32)> = RotatableHitBox::get_adjusted_points(self_); +// converted.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); +// Ok(converted[0].1) +// } + +// #[getter] +// fn top(self_: PyRef<'_, Self>) -> PyResult { +// let mut converted: Vec<(f32, f32)> = RotatableHitBox::get_adjusted_points(self_); +// converted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); +// Ok(converted[0].1) +// } +// } + // impl RotatableHitBox { // pub fn get_adjusted_points_native(self) -> Vec<(f32, f32)> { // let mut new_points: Vec<(f32, f32)> = Vec::with_capacity(self.parent.points.len()); diff --git a/src/sprite_list.rs b/src/sprite_list.rs index 89e2807..8467592 100644 --- a/src/sprite_list.rs +++ b/src/sprite_list.rs @@ -1,5 +1,5 @@ use crate::geometry::are_polygons_intersecting_native; -use crate::hitbox::{HitBox, RotatableHitBox}; +use crate::hitbox::{HitBox, NativeAdjustedPoints, RotatableHitBox}; use pyo3::prelude::*; #[pyfunction] @@ -12,67 +12,43 @@ pub fn check_for_collision_with_list( let _final_method = method.unwrap_or(3); let mut final_sprites: Vec = Vec::new(); - let mut hitbox1: Option = None; - let mut hitbox2: Option> = None; - - let cls: &str = sprite - .getattr("_hit_box") - .unwrap() - .get_type() - .name() - .unwrap(); - - match cls { - "HitBox" => { - hitbox1 = sprite.getattr("_hit_box").unwrap().extract().unwrap(); - } - "RotatableHitBox" => { - hitbox2 = sprite.getattr("_hit_box").unwrap().extract().unwrap(); - } - _ => panic!(), - } - - let main_points: Vec<(f32, f32)> = if let Some(value) = hitbox1 { - value.get_adjusted_points_native() - } else if let Some(value) = hitbox2 { - RotatableHitBox::get_adjusted_points(value) + let main_points: &Vec<(f32, f32)>; + let mut hitbox1: HitBox; + let mut hitbox2: RotatableHitBox; + let hitbox_py_object: &PyAny = sprite.getattr("_hit_box").unwrap(); + + if hitbox_py_object.is_instance_of::() { + hitbox1 = hitbox_py_object.extract::().unwrap(); + main_points = hitbox1.get_adjusted_points_native(); + } else if hitbox_py_object.is_instance_of::() { + hitbox2 = hitbox_py_object.extract::().unwrap(); + main_points = hitbox2.get_adjusted_points_native(); } else { - panic!("unknown hitbox type"); - }; + panic!("Unknown Hitbox Type") + } let sprite_list_list = sprite_list.getattr("sprite_list").unwrap(); let sprites_to_check: Vec = sprite_list_list.extract().unwrap(); for sprite2 in sprites_to_check.iter() { let other_sprite: &PyAny = sprite2.as_ref(py); - let mut other_hitbox1: Option = None; - let mut other_hitbox2: Option> = None; - let other_cls: &str = other_sprite - .getattr("_hit_box") - .unwrap() - .get_type() - .name() - .unwrap(); - - match other_cls { - "HitBox" => { - other_hitbox1 = other_sprite.getattr("_hit_box").unwrap().extract().unwrap(); - } - "RotatableHitBox" => { - other_hitbox2 = other_sprite.getattr("_hit_box").unwrap().extract().unwrap(); - } - _ => panic!(), - } - let other_points: Vec<(f32, f32)> = if let Some(value) = other_hitbox1 { - value.get_adjusted_points_native() - } else if let Some(value) = other_hitbox2 { - RotatableHitBox::get_adjusted_points(value) + let other_points: &Vec<(f32, f32)>; + let mut other_hitbox1: HitBox; + let mut other_hitbox2: RotatableHitBox; + let other_hitbox_py_object: &PyAny = other_sprite.getattr("_hit_box").unwrap(); + + if other_hitbox_py_object.is_instance_of::() { + other_hitbox1 = other_hitbox_py_object.extract::().unwrap(); + other_points = other_hitbox1.get_adjusted_points_native(); + } else if other_hitbox_py_object.is_instance_of::() { + other_hitbox2 = other_hitbox_py_object.extract::().unwrap(); + other_points = other_hitbox2.get_adjusted_points_native(); } else { - panic!("unknown hitbox type"); - }; + panic!("Unknown Hitbox Type") + } - let check_2 = are_polygons_intersecting_native(&main_points, &other_points); + let check_2 = are_polygons_intersecting_native(main_points, other_points); if check_2 { final_sprites.push(sprite2.to_object(py)); @@ -89,33 +65,21 @@ pub fn check_for_collision_with_lists( sprite_lists: Vec<&PyAny>, ) -> Vec { let mut final_sprites: Vec = Vec::new(); - let mut hitbox1: Option = None; - let mut hitbox2: Option> = None; - - let cls: &str = sprite - .getattr("_hit_box") - .unwrap() - .get_type() - .name() - .unwrap(); - - match cls { - "HitBox" => { - hitbox1 = sprite.getattr("_hit_box").unwrap().extract().unwrap(); - } - "RotatableHitBox" => { - hitbox2 = sprite.getattr("_hit_box").unwrap().extract().unwrap(); - } - _ => panic!(), - } - let main_points: Vec<(f32, f32)> = if let Some(value) = hitbox1 { - value.get_adjusted_points_native() - } else if let Some(value) = hitbox2 { - RotatableHitBox::get_adjusted_points(value) + let main_points: &Vec<(f32, f32)>; + let mut hitbox1: HitBox; + let mut hitbox2: RotatableHitBox; + let hitbox_py_object: &PyAny = sprite.getattr("_hit_box").unwrap(); + + if hitbox_py_object.is_instance_of::() { + hitbox1 = hitbox_py_object.extract::().unwrap(); + main_points = hitbox1.get_adjusted_points_native(); + } else if hitbox_py_object.is_instance_of::() { + hitbox2 = hitbox_py_object.extract::().unwrap(); + main_points = hitbox2.get_adjusted_points_native(); } else { - panic!("unknown hitbox type") - }; + panic!("Unknown Hitbox Type") + } for sprite_list in sprite_lists.iter() { let sprite_list_list = sprite_list.getattr("sprite_list").unwrap(); @@ -123,33 +87,23 @@ pub fn check_for_collision_with_lists( for sprite2 in sprites_to_check.iter() { let other_sprite: &PyAny = sprite2.as_ref(py); - let mut other_hitbox1: Option = None; - let mut other_hitbox2: Option> = None; - let other_cls: &str = other_sprite - .getattr("_hit_box") - .unwrap() - .get_type() - .name() - .unwrap(); - match other_cls { - "HitBox" => { - other_hitbox1 = other_sprite.getattr("_hit_box").unwrap().extract().unwrap(); - } - "RotatableHitBox" => { - other_hitbox2 = other_sprite.getattr("_hit_box").unwrap().extract().unwrap(); - } - _ => panic!(), - } - let other_points: Vec<(f32, f32)> = if let Some(value) = other_hitbox1 { - value.get_adjusted_points_native() - } else if let Some(value) = other_hitbox2 { - RotatableHitBox::get_adjusted_points(value) + let other_points: &Vec<(f32, f32)>; + let mut other_hitbox1: HitBox; + let mut other_hitbox2: RotatableHitBox; + let other_hitbox_py_object: &PyAny = other_sprite.getattr("_hit_box").unwrap(); + + if other_hitbox_py_object.is_instance_of::() { + other_hitbox1 = other_hitbox_py_object.extract::().unwrap(); + other_points = other_hitbox1.get_adjusted_points_native(); + } else if other_hitbox_py_object.is_instance_of::() { + other_hitbox2 = other_hitbox_py_object.extract::().unwrap(); + other_points = other_hitbox2.get_adjusted_points_native(); } else { - panic!("unknown hitbox type"); - }; + panic!("Unknown Hitbox Type") + } - let check_2 = are_polygons_intersecting_native(&main_points, &other_points); + let check_2 = are_polygons_intersecting_native(main_points, other_points); if check_2 { final_sprites.push(sprite2.to_object(py)); From 0b7d563c82f379fa5f076ffcc87d6025c127ff32 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Sun, 3 Mar 2024 01:12:18 -0500 Subject: [PATCH 27/32] Use intern macro for getattr strings (#39) --- src/sprite_list.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/sprite_list.rs b/src/sprite_list.rs index 8467592..6777db8 100644 --- a/src/sprite_list.rs +++ b/src/sprite_list.rs @@ -1,5 +1,6 @@ use crate::geometry::are_polygons_intersecting_native; use crate::hitbox::{HitBox, NativeAdjustedPoints, RotatableHitBox}; +use pyo3::intern; use pyo3::prelude::*; #[pyfunction] @@ -15,7 +16,7 @@ pub fn check_for_collision_with_list( let main_points: &Vec<(f32, f32)>; let mut hitbox1: HitBox; let mut hitbox2: RotatableHitBox; - let hitbox_py_object: &PyAny = sprite.getattr("_hit_box").unwrap(); + let hitbox_py_object: &PyAny = sprite.getattr(intern!(py, "_hit_box")).unwrap(); if hitbox_py_object.is_instance_of::() { hitbox1 = hitbox_py_object.extract::().unwrap(); @@ -27,7 +28,7 @@ pub fn check_for_collision_with_list( panic!("Unknown Hitbox Type") } - let sprite_list_list = sprite_list.getattr("sprite_list").unwrap(); + let sprite_list_list = sprite_list.getattr(intern!(py, "sprite_list")).unwrap(); let sprites_to_check: Vec = sprite_list_list.extract().unwrap(); for sprite2 in sprites_to_check.iter() { @@ -36,7 +37,7 @@ pub fn check_for_collision_with_list( let other_points: &Vec<(f32, f32)>; let mut other_hitbox1: HitBox; let mut other_hitbox2: RotatableHitBox; - let other_hitbox_py_object: &PyAny = other_sprite.getattr("_hit_box").unwrap(); + let other_hitbox_py_object: &PyAny = other_sprite.getattr(intern!(py, "_hit_box")).unwrap(); if other_hitbox_py_object.is_instance_of::() { other_hitbox1 = other_hitbox_py_object.extract::().unwrap(); @@ -69,7 +70,7 @@ pub fn check_for_collision_with_lists( let main_points: &Vec<(f32, f32)>; let mut hitbox1: HitBox; let mut hitbox2: RotatableHitBox; - let hitbox_py_object: &PyAny = sprite.getattr("_hit_box").unwrap(); + let hitbox_py_object: &PyAny = sprite.getattr(intern!(py, "_hit_box")).unwrap(); if hitbox_py_object.is_instance_of::() { hitbox1 = hitbox_py_object.extract::().unwrap(); @@ -82,7 +83,7 @@ pub fn check_for_collision_with_lists( } for sprite_list in sprite_lists.iter() { - let sprite_list_list = sprite_list.getattr("sprite_list").unwrap(); + let sprite_list_list = sprite_list.getattr(intern!(py, "sprite_list")).unwrap(); let sprites_to_check: Vec = sprite_list_list.extract().unwrap(); for sprite2 in sprites_to_check.iter() { @@ -91,7 +92,8 @@ pub fn check_for_collision_with_lists( let other_points: &Vec<(f32, f32)>; let mut other_hitbox1: HitBox; let mut other_hitbox2: RotatableHitBox; - let other_hitbox_py_object: &PyAny = other_sprite.getattr("_hit_box").unwrap(); + let other_hitbox_py_object: &PyAny = + other_sprite.getattr(intern!(py, "_hit_box")).unwrap(); if other_hitbox_py_object.is_instance_of::() { other_hitbox1 = other_hitbox_py_object.extract::().unwrap(); From 64d824e48127bc568492f87780b2678b3d3efbfb Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Sun, 3 Mar 2024 01:42:04 -0500 Subject: [PATCH 28/32] Bump version to 1.0.1 (#40) --- Cargo.lock | 2 +- Cargo.toml | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 079a9b0..262ad14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "arcade-accelerate" -version = "1.0.0" +version = "1.0.1" dependencies = [ "float_eq", "pyo3", diff --git a/Cargo.toml b/Cargo.toml index 4208f37..560a214 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "arcade-accelerate" -version = "1.0.0" +version = "1.0.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/pyproject.toml b/pyproject.toml index fae5656..4f6991e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "arcade-accelerate" -version = "1.0.0" +version = "1.0.1" description = "A companion library for Arcade providing accelerated Rust functions" readme = "README.md" authors = [ From ca2b6ee39fbd7d42e02f9029da920592ce1ce085 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Sun, 21 Apr 2024 01:05:53 -0400 Subject: [PATCH 29/32] Math updates. Renamed lerp functions, added quaternion_rotation --- python/arcade_accelerate/__init__.py | 4 ++- src/lib.rs | 4 ++- src/math.rs | 51 +++++++++++++++++++++++++--- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/python/arcade_accelerate/__init__.py b/python/arcade_accelerate/__init__.py index 1f7fa81..86de5bd 100644 --- a/python/arcade_accelerate/__init__.py +++ b/python/arcade_accelerate/__init__.py @@ -36,7 +36,8 @@ def patch_math(patches): patches["arcade.math"].rotate_point = arcade_accelerate.rotate_point patches["arcade.math"].clamp = arcade_accelerate.clamp patches["arcade.math"].lerp = arcade_accelerate.lerp - patches["arcade.math"].lerp_vec = arcade_accelerate.lerp_vec + patches["arcade.math"].lerp_2d = arcade_accelerate.lerp_2d + patches["arcade.math"].lerp_3d = arcade_accelerate.lerp_3d patches["arcade.math"].lerp_angle = arcade_accelerate.lerp_angle patches["arcade.math"].get_distance = arcade_accelerate.get_distance patches["arcade.math"].get_angle_degrees = arcade_accelerate.get_angle_degrees @@ -53,6 +54,7 @@ def patch_math(patches): arcade_accelerate.rand_vec_degree_spread ) patches["arcade.math"].rand_vec_magnitude = arcade_accelerate.rand_vec_magnitude + patches["arcade.math"].quaternion_rotation = arcade_accelerate.quaternion_rotation def patch_geometry(patches): diff --git a/src/lib.rs b/src/lib.rs index 40917db..ffc3a4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,8 @@ fn arcade_accelerate(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(math::rotate_point, m)?)?; m.add_function(wrap_pyfunction!(math::clamp, m)?)?; m.add_function(wrap_pyfunction!(math::lerp, m)?)?; - m.add_function(wrap_pyfunction!(math::lerp_vec, m)?)?; + m.add_function(wrap_pyfunction!(math::lerp_2d, m)?)?; + m.add_function(wrap_pyfunction!(math::lerp_3d, m)?)?; m.add_function(wrap_pyfunction!(math::lerp_angle, m)?)?; m.add_function(wrap_pyfunction!(math::get_distance, m)?)?; m.add_function(wrap_pyfunction!(math::get_angle_degrees, m)?)?; @@ -33,6 +34,7 @@ fn arcade_accelerate(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(math::rand_angle_spread_deg, m)?)?; m.add_function(wrap_pyfunction!(math::rand_vec_degree_spread, m)?)?; m.add_function(wrap_pyfunction!(math::rand_vec_magnitude, m)?)?; + m.add_function(wrap_pyfunction!(math::quaternion_rotation, m)?)?; m.add_function(wrap_pyfunction!(geometry::are_polygons_intersecting, m)?)?; m.add_function(wrap_pyfunction!(geometry::is_point_in_polygon, m)?)?; m.add_function(wrap_pyfunction!(geometry::is_point_in_box, m)?)?; diff --git a/src/math.rs b/src/math.rs index 8637e93..ffe95a5 100644 --- a/src/math.rs +++ b/src/math.rs @@ -41,10 +41,19 @@ pub fn lerp(v1: f32, v2: f32, u: f32) -> f32 { } #[pyfunction] -pub fn lerp_vec(v1: (f32, f32), v2: (f32, f32), u: f32) -> (f32, f32) { +pub fn lerp_2d(v1: (f32, f32), v2: (f32, f32), u: f32) -> (f32, f32) { (lerp(v1.0, v2.0, u), lerp(v1.1, v2.1, u)) } +#[pyfunction] +pub fn lerp_3d(v1: (f32, f32, f32), v2: (f32, f32, f32), u: f32) -> (f32, f32, f32) { + ( + lerp(v1.0, v2.0, u), + lerp(v1.1, v2.1, u), + lerp(v1.2, v2.2, u), + ) +} + #[pyfunction] pub fn lerp_angle(start_angle: f32, end_angle: f32, u: f32) -> f32 { let temp_start = start_angle % 360.0; @@ -130,7 +139,7 @@ pub fn rand_on_line(pos1: (f32, f32), pos2: (f32, f32)) -> (f32, f32) { let mut rng = thread_rng(); let u: f32 = rng.gen_range(0.0..1.0); - lerp_vec(pos1, pos2, u) + lerp_2d(pos1, pos2, u) } #[pyfunction] @@ -164,6 +173,29 @@ pub fn rand_vec_magnitude(angle: f32, lo_magnitude: f32, hi_magnitude: f32) -> ( vel.as_tuple() } +#[pyfunction] +pub fn quaternion_rotation( + axis: (f32, f32, f32), + vector: (f32, f32, f32), + angle: f32, +) -> (f32, f32, f32) { + let angle_rads = -angle.to_radians(); + let (c2, s2) = (f32::cos(angle_rads / 2.0), f32::sin(angle_rads / 2.0)); + + let (q0, q1, q2, q3) = (c2, s2 * axis.0, s2 * axis.1, s2 * axis.2); + let (q0_2, q1_2, q2_2, q3_2) = (q0.powi(2), q1.powi(2), q2.powi(2), q3.powi(2)); + let (q01, q02, q03, q12, q13, q23) = (q0 * q1, q0 * q2, q0 * q3, q1 * q2, q1 * q3, q2 * q3); + + let x = vector.0 * (q0_2 + q1_2 - q2_2 - q3_2) + + 2.0 * (vector.1 * (q12 - q03) + vector.2 * (q02 + q13)); + let y = vector.1 * (q0_2 - q1_2 + q2_2 - q3_2) + + 2.0 * (vector.0 * (q03 + q12) + vector.2 * (q23 - q01)); + let z = vector.2 * (q0_2 - q1_2 - q2_2 + q3_2) + + 2.0 * (vector.0 * (q13 + q02) + vector.1 * (q01 + q23)); + + (x, y, z) +} + // This is only a subset of _Vec2 methods defined in arcade.math.py struct _Vec2 { x: f32, @@ -262,14 +294,23 @@ mod tests { } #[test] - fn test_lerp_vec() { - let mut result = lerp_vec((0.0, 2.0), (8.0, 4.0), 0.25); + fn test_lerp_2d() { + let mut result = lerp_2d((0.0, 2.0), (8.0, 4.0), 0.25); assert_eq!(result, (2.0, 2.5)); - result = lerp_vec((0.0, 2.0), (8.0, 4.0), -0.25); + result = lerp_2d((0.0, 2.0), (8.0, 4.0), -0.25); assert_eq!(result, (-2.0, 1.5)); } + #[test] + fn test_lerp_3d() { + let mut result = lerp_3d((0.0, 2.0, 4.0), (8.0, 4.0, 8.0), 0.25); + assert_eq!(result, (2.0, 2.5, 5.0)); + + result = lerp_3d((0.0, 2.0, 4.0), (8.0, 4.0, 8.0), -0.25); + assert_eq!(result, (-2.0, 1.5, 3.0)); + } + #[test] fn test_lerp_angle_normal() { //normal From 2cdc2e4e4e3f1958d17b981b0f2df3e2836e2bdf Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Sun, 21 Apr 2024 01:17:19 -0400 Subject: [PATCH 30/32] Math Updates. Renamed lerp functions, added quaternion_rotation (#41) From 76cafd3595110b9ebf3d11122e634462ad51e47e Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Sat, 27 Apr 2024 02:48:03 -0400 Subject: [PATCH 31/32] Bump version to 1.0.2 (#42) --- Cargo.lock | 2 +- Cargo.toml | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 262ad14..252c875 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "arcade-accelerate" -version = "1.0.1" +version = "1.0.2" dependencies = [ "float_eq", "pyo3", diff --git a/Cargo.toml b/Cargo.toml index 560a214..e176b88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "arcade-accelerate" -version = "1.0.1" +version = "1.0.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/pyproject.toml b/pyproject.toml index 4f6991e..96e5cdf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "arcade-accelerate" -version = "1.0.1" +version = "1.0.2" description = "A companion library for Arcade providing accelerated Rust functions" readme = "README.md" authors = [ From ba33d96cf125698eb077bd1906a65b49a49ac163 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Sun, 6 Oct 2024 22:40:54 -0400 Subject: [PATCH 32/32] Updates for Arcade 3.0 API --- python/arcade_accelerate/__init__.py | 4 ++-- src/geometry.rs | 2 +- src/lib.rs | 2 +- src/math.rs | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/python/arcade_accelerate/__init__.py b/python/arcade_accelerate/__init__.py index 86de5bd..0fca670 100644 --- a/python/arcade_accelerate/__init__.py +++ b/python/arcade_accelerate/__init__.py @@ -50,8 +50,8 @@ def patch_math(patches): patches["arcade.math"].rand_angle_spread_deg = ( arcade_accelerate.rand_angle_spread_deg ) - patches["arcade.math"].rand_vec_degree_spread = ( - arcade_accelerate.rand_vec_degree_spread + patches["arcade.math"].rand_vec_spread_deg = ( + arcade_accelerate.rand_vec_spread_deg ) patches["arcade.math"].rand_vec_magnitude = arcade_accelerate.rand_vec_magnitude patches["arcade.math"].quaternion_rotation = arcade_accelerate.quaternion_rotation diff --git a/src/geometry.rs b/src/geometry.rs index 3ea7a95..8a15d14 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -197,7 +197,7 @@ pub fn is_point_in_polygon(x: f32, y: f32, polygon: Vec<(f32, f32)>) -> bool { // segment 'i-next', then check if it lies // on segment. If it lies, return true, otherwise false if get_triangle_orientation(polygon[i], p, polygon[next_item]) == 0 { - return !is_point_in_box(polygon[i], p, polygon[next_item]); + return is_point_in_box(polygon[i], p, polygon[next_item]); } count += 1 diff --git a/src/lib.rs b/src/lib.rs index ffc3a4f..51085eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ fn arcade_accelerate(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(math::rand_on_line, m)?)?; m.add_function(wrap_pyfunction!(math::rand_angle_360_deg, m)?)?; m.add_function(wrap_pyfunction!(math::rand_angle_spread_deg, m)?)?; - m.add_function(wrap_pyfunction!(math::rand_vec_degree_spread, m)?)?; + m.add_function(wrap_pyfunction!(math::rand_vec_spread_deg, m)?)?; m.add_function(wrap_pyfunction!(math::rand_vec_magnitude, m)?)?; m.add_function(wrap_pyfunction!(math::quaternion_rotation, m)?)?; m.add_function(wrap_pyfunction!(geometry::are_polygons_intersecting, m)?)?; diff --git a/src/math.rs b/src/math.rs index ffe95a5..166b859 100644 --- a/src/math.rs +++ b/src/math.rs @@ -97,11 +97,11 @@ pub fn get_angle_radians(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 { } #[pyfunction] -pub fn rand_in_rect(bottom_left: (f32, f32), width: f32, height: f32) -> (f32, f32) { +pub fn rand_in_rect(rect: (f32, f32, f32, f32, f32, f32, f32, f32)) -> (f32, f32) { let mut rng = thread_rng(); - let random_x: f32 = rng.gen_range(bottom_left.0..bottom_left.0 + width); - let random_y: f32 = rng.gen_range(bottom_left.1..bottom_left.1 + height); + let random_x: f32 = rng.gen_range(rect.0..=rect.1); + let random_y: f32 = rng.gen_range(rect.2..=rect.3); (random_x, random_y) } @@ -159,7 +159,7 @@ pub fn rand_angle_spread_deg(angle: f32, half_angle_spread: f32) -> f32 { } #[pyfunction] -pub fn rand_vec_degree_spread(angle: f32, half_angle_spread: f32, length: f32) -> (f32, f32) { +pub fn rand_vec_spread_deg(angle: f32, half_angle_spread: f32, length: f32) -> (f32, f32) { let a = rand_angle_spread_deg(angle, half_angle_spread); let vel = _Vec2::from_polar(a, length); vel.as_tuple()