Skip to content

Commit 2d35286

Browse files
committed
Refactor: Initial work
1 parent 6a6063e commit 2d35286

22 files changed

Lines changed: 808 additions & 0 deletions

File tree

arcade_perf/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from pathlib import Path
2+
import arcade
3+
4+
# Root of the repository
5+
PACKAGE_ROOT= Path(__file__).parent.resolve()
6+
# Package directory
7+
PROJECT_ROOT = PACKAGE_ROOT.parent
8+
# Resources directory
9+
RESOURCES_ROOT = PACKAGE_ROOT / "resources"
10+
# Output directory
11+
OUT_DIR = PROJECT_ROOT / "output"
12+
13+
arcade.resources.add_resource_handle("textures", RESOURCES_ROOT)

arcade_perf/__main__.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import sys
2+
import argparse
3+
from datetime import datetime
4+
from . import OUT_DIR
5+
from arcade_perf.tests.arcade import collision
6+
from arcade_perf import manager
7+
8+
# Create test instances
9+
collision_0 = collision.Test(method=0)
10+
collision_1 = collision.Test(method=1)
11+
collision_2 = collision.Test(method=2)
12+
collision_3 = collision.Test(method=3)
13+
14+
15+
def main():
16+
session_name = "test"
17+
session_dir = OUT_DIR / session_name
18+
session_dir.mkdir(parents=True, exist_ok=True)
19+
20+
# Run tests
21+
# collision_0.run(session_dir)
22+
# collision_1.run(session_dir)
23+
# collision_2.run(session_dir)
24+
# collision_3.run(session_dir)
25+
26+
# -- Graphs --
27+
# draw_stationary_sprites [pygame, arcade]
28+
29+
# draw_moving_sprites [pygame, arcade[basic, sprite]]
30+
31+
# collision
32+
# Time To Detect Collisions
33+
# - arcade.collision-2 Arcade GPU
34+
# - arcade.collision-3 Arcade Simple
35+
36+
# shapes
37+
38+
39+
40+
# run -s test -t arcade, -n collision
41+
def parse_args(args):
42+
parser = argparse.ArgumentParser()
43+
parser.add_argument(
44+
"-s",
45+
"--session",
46+
help="Session name",
47+
type=str,
48+
default=datetime.now().strftime("%Y-%m-%dT%H-%M-%S"),
49+
)
50+
parser.add_argument("-t", "--type", help="Test type", type=str)
51+
parser.add_argument("-n", "--name", help="Test name", type=str)
52+
return parser.parse_args(args)
53+
54+
55+
if __name__ == "__main__":
56+
args = parse_args(sys.argv[1:])

arcade_perf/graph.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import csv
2+
from pathlib import Path
3+
import matplotlib.pyplot as plt
4+
import seaborn as sns
5+
6+
sns.set_style("whitegrid")
7+
8+
FPS = 1
9+
SPRITE_COUNT = 2
10+
DRAWING_TIME = 3
11+
PROCESSING_TIME = 4
12+
13+
class DataSeries:
14+
15+
def __init__(self, name: str, path: Path) -> None:
16+
self.name = name
17+
self.path = path
18+
# Data
19+
self.count = []
20+
self.processing_time = []
21+
self.draw_time = []
22+
self.fps = []
23+
# Process data
24+
self._process_data()
25+
26+
def _process_data(self):
27+
rows = self._read_file(self.path)
28+
for row in rows:
29+
self.count.append(row[SPRITE_COUNT])
30+
self.fps.append(row[FPS])
31+
self.processing_time.append(row[PROCESSING_TIME])
32+
self.draw_time.append(row[DRAWING_TIME])
33+
34+
def _read_file(self, path: Path):
35+
results = []
36+
with open(path) as csv_file:
37+
csv_reader = csv.reader(csv_file, delimiter=',')
38+
first_row = True
39+
for row in csv_reader:
40+
if first_row:
41+
first_row = False
42+
else:
43+
results.append([float(cell) for cell in row])
44+
45+
return results
46+
47+
class PerfGraph:
48+
49+
def __init__(self, title: str, label_x: str, label_y: str) -> None:
50+
self.title = title
51+
self.label_x = label_x
52+
self.label_y = label_y
53+
self.series = []
54+
55+
def add_series(self, series: DataSeries):
56+
self.series.append(series)
57+
58+
def create(self, output_path: Path):
59+
plt.title(self.title)
60+
61+
for series in self.series:
62+
plt.plot(series.count, series.processing_time, label=series.name)
63+
64+
plt.legend(loc='upper left', shadow=True, fontsize='large')
65+
plt.xlabel(self.label_x)
66+
plt.ylabel(self.label_y)
67+
68+
plt.savefig(output_path)
69+
plt.clf()
70+
71+
72+
if __name__ == "__main__":
73+
from arcade_perf import OUT_DIR
74+
OUTPUT_ROOT = OUT_DIR / "test" / "graphs"
75+
OUTPUT_ROOT.mkdir(parents=True, exist_ok=True)
76+
path = OUT_DIR / "test" / "data"
77+
78+
graph = PerfGraph("Time To Detect Collisions", label_x="Sprite Count", label_y="Time")
79+
graph.add_series(DataSeries("Arcade 0", path / "arcade_collision-0.csv"))
80+
graph.add_series(DataSeries("Arcade 1", path / "arcade_collision-1.csv"))
81+
graph.add_series(DataSeries("Arcade 2", path / "arcade_collision-2.csv"))
82+
graph.add_series(DataSeries("Arcade 3", path / "arcade_collision-3.csv"))
83+
graph.create(OUTPUT_ROOT / "arcade_collision.png")

arcade_perf/manager.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import importlib
2+
import pkgutil
3+
from typing import List, Tuple, Type
4+
5+
from arcade_perf.graph import DataSeries, PerfGraph
6+
from arcade_perf import OUT_DIR
7+
from arcade_perf.tests.base import PerfTest
8+
9+
10+
def find_test_classes(path: str) -> List[Type[PerfTest]]:
11+
"""Find all test classes in submodules"""
12+
target_module = importlib.import_module(f"arcade_perf.tests.{path}")
13+
14+
classes = []
15+
for v in pkgutil.iter_modules(target_module.__path__):
16+
module = importlib.import_module(f"arcade_perf.tests.{path}.{v.name}")
17+
if hasattr(module, "Test"):
18+
classes.append(module.Test)
19+
20+
return classes
21+
22+
23+
class TestManager:
24+
25+
def __init__(self, session: str):
26+
self.session = session
27+
self.test_classes = find_test_classes("arcade")
28+
self.test_classes += find_test_classes("pygame")
29+
30+
def run(self, session_dir):
31+
"""Run all tests"""
32+
33+
# Run arcade tests first
34+
for test in self.test_classes:
35+
test_instance: PerfTest = test()
36+
test_instance.run(session_dir)
37+
38+
def create_graph(
39+
self,
40+
title: str,
41+
x_label: str,
42+
y_label: str,
43+
series_names = [],
44+
):
45+
"""Create a graph using matplotlib"""
46+
series = []
47+
for _series in series_names:
48+
s = DataSeries()
49+
series.append(s)
50+
51+
def _filter_tests(self, test_classes, type, name):
52+
"""Filter test classes based on type and name"""
53+
filtered = []
54+
for test_cls in test_classes:
55+
if type and test_cls.type != type:
56+
continue
57+
if name and test_cls.name != name:
58+
continue
59+
filtered.append(test_cls)
60+
61+
return filtered
File renamed without changes.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import arcade
2+
import random
3+
from arcade_perf.tests.base import ArcadePerfTest
4+
5+
SPRITE_SCALING_COIN = 0.09
6+
SPRITE_SCALING_PLAYER = 0.5
7+
SPRITE_NATIVE_SIZE = 128
8+
SPRITE_SIZE = int(SPRITE_NATIVE_SIZE * SPRITE_SCALING_COIN)
9+
SCREEN_WIDTH = 1800
10+
SCREEN_HEIGHT = 1000
11+
SCREEN_TITLE = "Moving Sprite Stress Test - Arcade"
12+
USE_SPATIAL_HASHING = True
13+
DEFAULT_METHOD = 3
14+
NAMES = {
15+
0: "Arcade Auto",
16+
1: "Arcade Spatial",
17+
2: "Arcade GPU",
18+
3: "Arcade Simple (No Spatial Hashing, No Sprite Lists)",
19+
}
20+
21+
22+
class Test(ArcadePerfTest):
23+
name = "collision"
24+
25+
def __init__(self, method: int = DEFAULT_METHOD):
26+
super().__init__(
27+
size=(SCREEN_WIDTH, SCREEN_HEIGHT),
28+
title=SCREEN_TITLE,
29+
start_count=0,
30+
increment_count=100,
31+
duration=60.0,
32+
)
33+
# Collision method
34+
self.method = method
35+
self.name = f"collision-{self.method}"
36+
self.series_name = NAMES[self.method]
37+
38+
# Variables that will hold sprite lists
39+
self.coin_list = None
40+
self.player_list = None
41+
self.player = None
42+
43+
def setup(self):
44+
self.window.background_color = arcade.color.AMAZON
45+
# Sprite lists
46+
self.coin_list = arcade.SpriteList(use_spatial_hash=USE_SPATIAL_HASHING)
47+
self.player_list = arcade.SpriteList()
48+
self.player = arcade.Sprite(
49+
":resources:images/animated_characters/female_person/femalePerson_idle.png",
50+
scale=SPRITE_SCALING_PLAYER,
51+
)
52+
self.player.center_x = random.randrange(SCREEN_WIDTH)
53+
self.player.center_y = random.randrange(SCREEN_HEIGHT)
54+
self.player.change_x = 3
55+
self.player.change_y = 5
56+
self.player_list.append(self.player)
57+
58+
def add_coins(self, amount):
59+
"""Add a new set of coins"""
60+
for i in range(amount):
61+
coin = arcade.Sprite(":resources:images/items/coinGold.png", SPRITE_SCALING_COIN)
62+
coin.position = (
63+
random.randrange(SPRITE_SIZE, SCREEN_WIDTH - SPRITE_SIZE),
64+
random.randrange(SPRITE_SIZE, SCREEN_HEIGHT - SPRITE_SIZE),
65+
)
66+
self.coin_list.append(coin)
67+
68+
def on_draw(self):
69+
super().on_draw()
70+
self.coin_list.draw()
71+
self.player_list.draw()
72+
73+
def on_update(self, delta_time: float):
74+
super().on_update(delta_time)
75+
76+
self.player_list.update()
77+
if self.player.center_x < 0 and self.player.change_x < 0:
78+
self.player.change_x *= -1
79+
if self.player.center_y < 0 and self.player.change_y < 0:
80+
self.player.change_y *= -1
81+
82+
if self.player.center_x > SCREEN_WIDTH and self.player.change_x > 0:
83+
self.player.change_x *= -1
84+
if self.player.center_y > SCREEN_HEIGHT and self.player.change_y > 0:
85+
self.player.change_y *= -1
86+
87+
coin_hit_list = arcade.check_for_collision_with_list(self.player, self.coin_list, method=self.method)
88+
for coin in coin_hit_list:
89+
coin.center_x = random.randrange(SCREEN_WIDTH)
90+
coin.center_y = random.randrange(SCREEN_HEIGHT)
91+
92+
def update_state(self):
93+
# Figure out if we need more coins
94+
if self.timing.target_n > len(self.coin_list):
95+
new_coin_amount = self.timing.target_n - len(self.coin_list)
96+
self.add_coins(new_coin_amount)
97+
self.coin_list.write_sprite_buffers_to_gpu()
98+
99+
100+
if __name__ == "__main__":
101+
Test().run_test()

0 commit comments

Comments
 (0)