Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
gottadiveintopython committed Dec 11, 2023
1 parent 5432a99 commit d91fa24
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 101 deletions.
14 changes: 0 additions & 14 deletions src/asyncpygame/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
__all__ = (
'DEFAULT_PRIORITY', 'STOP_DISPATCHING',
'Timer', 'sleep', 'move_on_after',
'anim_with_dt', 'anim_with_dt_et', 'anim_with_dt_et_ratio', 'anim_with_et', 'anim_with_ratio',
'PriorityDispatcher',
'sdl_event', 'sdl_event_repeatedly',
'run_in_thread', 'run_in_executor',
# 'PrefilledAPIs',
)

from asyncgui import *
from .constants import DEFAULT_PRIORITY, STOP_DISPATCHING
from ._timer import Timer, sleep, move_on_after
from ._animation import (
anim_with_dt, anim_with_dt_et, anim_with_dt_et_ratio, anim_with_et, anim_with_ratio,
)
from ._priority_dispatcher import PriorityDispatcher
from ._sdl_event import sdl_event, sdl_event_repeatedly
from ._threads import run_in_thread, run_in_executor
# from ._pre_filled_apis import PrefilledAPIs
Empty file.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections.abc import Callable, Awaitable, AsyncIterator
from asyncgui import Cancelled, ISignal, _current_task, _sleep_forever, wait_any_cm, Task

from ._timer import Timer, TimeUnit, repeat_sleeping
from ._timer import TimeUnit, repeat_sleeping



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from heapq import merge as heapq_merge


from .constants import DEFAULT_PRIORITY, STOP_DISPATCHING
from ..constants import DEFAULT_PRIORITY, STOP_DISPATCHING


@dataclass(slots=True)
Expand Down
93 changes: 93 additions & 0 deletions src/asyncpygame/_lowlevel/_priority_drawing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from typing import TypeAlias
from collections.abc import Callable
from dataclasses import dataclass
from heapq import merge as heapq_merge

from pygame.surface import Surface

from ..constants import DEFAULT_PRIORITY


DrawFunc: TypeAlias = Callable[[Surface], None]


@dataclass(slots=True)
class Drawer:
_priority: int

callback: DrawFunc
'''
The callback function registered using the :meth:`PrioritizedDrawing.add_drawer` call that returned this
instance. You can replace it with another one by simply assigning to this attribute.
.. code-block::
dr = priority_drawing.add_drawer(...)
dr.callback = another_function
'''

_cancelled: bool = False

def cancel(self):
self._cancelled = True

def __eq__(self, other):
return self._priority == other._priority

def __ne__(self, other):
return self._priority != other._priority

def __lt__(self, other):
return self._priority < other._priority

def __le__(self, other):
return self._priority <= other._priority

def __gt__(self, other):
return self._priority > other._priority

def __ge__(self, other):
return self._priority >= other._priority


class PrioritizedDrawing:
__slots__ = ('_drs', '_drs_2', '_drs_to_be_added', '_drs_to_be_added_2', )

def __init__(self):
self._drs: list[Drawer] = []
self._drs_2: list[Drawer] = [] # double buffering
self._drs_to_be_added: list[Drawer] = []
self._drs_to_be_added_2: list[Drawer] = [] # double buffering

def draw(self, draw_target: Surface):
drs = self._drs
drs_tba = self._drs_to_be_added
if drs_tba:
drs_tba.sort()
dr_iter = heapq_merge(drs, drs_tba)
drs_tba2 = self._drs_to_be_added_2
drs_tba2.clear()
self._drs_to_be_added = drs_tba2
self._drs_to_be_added_2 = drs_tba
del drs_tba2
else:
dr_iter = iter(drs)
del drs_tba

drs2 = self._drs_2
drs2_append = drs2.append
try:
for dr in dr_iter:
if dr._cancelled:
continue
drs2_append(dr)
dr.callback(draw_target)
finally:
drs.clear()
self._drs = drs2
self._drs_2 = drs

def add_drawer(self, func: DrawFunc, priority=DEFAULT_PRIORITY) -> Drawer:
dr = Drawer(priority, func)
self._drs_to_be_added.append(dr)
return dr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from asyncgui import _current_task, _sleep_forever
from pygame.event import Event

from .constants import DEFAULT_PRIORITY
from ..constants import DEFAULT_PRIORITY


def _resume_task(task_step, filter, event: Event):
Expand Down Expand Up @@ -40,13 +40,13 @@ def sdl_event(dispatcher, *, priority=DEFAULT_PRIORITY, filter=lambda event: Tru
sub.cancel()


class sdl_event_repeatedly:
class sdl_frequent_event:
'''
Returns an async context manager that provides an efficient way to repeat waiting for a SDL event to occur.
.. code-block::
async with sdl_event_repeatedly(dispatcher) as sdl_event:
async with sdl_frequent_event(dispatcher) as sdl_event:
while True:
e = await sdl_event()
print(f"A {pygame.event.event_name(e.type)} event occurred.")
Expand All @@ -58,7 +58,7 @@ class sdl_event_repeatedly:
def filter(event, allowed_list=(MOUSEMOTION, MOUSEBUTTONUP)):
return event.type in allowed_list
async with sdl_event_repeatedly(dispatcher, filter=filter) as sdl_event:
async with sdl_frequent_event(dispatcher, filter=filter) as sdl_event:
while True:
e = await sdl_event()
if e.type == MOUSEBUTTONUP:
Expand All @@ -71,7 +71,7 @@ def filter(event, allowed_list=(MOUSEMOTION, MOUSEBUTTONUP)):
.. code-block::
async with sdl_event_repeatedly(dispatcher) as sdl_event:
async with sdl_frequent_event(dispatcher) as sdl_event:
await sdl_event() # OK
await something_else # NOT ALLOWED
async with async_context_manager: # NOT ALLOWED
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from collections.abc import Awaitable
from threading import Thread
from concurrent.futures import ThreadPoolExecutor

from asyncgui import Cancelled

from ._animation import repeat_sleeping
from ._timer import Timer, repeat_sleeping


async def run_in_thread(timer, func, *, daemon=None, polling_interval=2000) -> Awaitable:
async def run_in_thread(timer: Timer, func, *, daemon=None, polling_interval=2000) -> Awaitable:
'''
Creates a new thread, runs a function within it, then waits for the completion of that function.
Expand Down Expand Up @@ -36,7 +37,7 @@ def wrapper():
return return_value


async def run_in_executor(timer, executer, func, *, polling_interval=2000) -> Awaitable:
async def run_in_executor(timer: Timer, executer: ThreadPoolExecutor, func, *, polling_interval=2000) -> Awaitable:
'''
Runs a function within a :class:`concurrent.futures.ThreadPoolExecutor` instance, and waits for the completion of
the function.
Expand Down
File renamed without changes.
39 changes: 9 additions & 30 deletions src/asyncpygame/_pre_filled_apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,13 @@

from asyncgui import Task

from ._timer import TimeUnit, TimerEvent, Timer
from ._lowlevel._timer import TimeUnit


class PrefilledAPIs:
def schedule_once(self, func, delay=0) -> TimerEvent:
...

def schedule_interval(self, func, interval) -> TimerEvent:
...

def sleep(self, duration) -> Awaitable:
...

def repeat_sleeping(self, *, interval=0) -> AbstractAsyncContextManager:
''':meta private:'''

def move_on_after(self, timeout) -> AbstractAsyncContextManager[Task]:
...

Expand All @@ -44,39 +35,27 @@ def anim_with_ratio(self, *, step=0) -> AsyncIterator[float]:
def anim_with_dt_et_ratio(self, *, step=0) -> AsyncIterator[tuple[TimeUnit, TimeUnit, float]]:
...

def __init__(self, *, timer, executer=None):
...

def __new__(self, *, timer: Timer, executor: ThreadPoolExecutor=None):
def __new__(self, *, timer, dispatcher, executor: ThreadPoolExecutor=None):
from functools import partial as p
import types

from ._animation import (
sleep, repeat_sleeping,
from ._lowlevel._timer import sleep, move_on_after
from ._lowlevel._threads import run_in_executor, run_in_thread
from ._lowlevel._sdl_event import sdl_event, sdl_frequent_event
from ._lowlevel._animation import (
anim_with_dt, anim_with_dt_et, anim_with_dt_et_ratio, anim_with_et, anim_with_ratio,
)
from ._threads import run_in_executor, run_in_thread

return types.SimpleNamespace(
timer=timer,
executor=executor,
schedule_once=timer.schedule_once,
schedule_interval=timer.schedule_interval,
sleep=p(sleep, timer),
repeat_sleeping=p(repeat_sleeping, timer),
move_on_after=p(move_on_after, timer),
run_in_thread=p(run_in_thread, timer),
run_in_executor=p(run_in_executor, timer, executor),
sdl_event=p(sdl_event, dispatcher),
sdl_frequent_event=p(sdl_frequent_event, dispatcher),
anim_with_dt=p(anim_with_dt, timer),
anim_with_et=p(anim_with_et, timer),
anim_with_dt_et=p(anim_with_dt_et, timer),
anim_with_ratio=p(anim_with_ratio, timer),
anim_with_dt_et_ratio=p(anim_with_dt_et_ratio, timer),
)

@property
def timer(self) -> Timer:
...

@property
def executor(self) -> ThreadPoolExecutor | None:
...
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
def test_same_priorities():
from asyncpygame import PriorityDispatcher
from asyncpygame._lowlevel._priority_dispatcher import PriorityDispatcher

d = PriorityDispatcher()
value_list = []
Expand All @@ -15,7 +15,7 @@ def test_same_priorities():


def test_various_priorities():
from asyncpygame import PriorityDispatcher, DEFAULT_PRIORITY
from asyncpygame._lowlevel._priority_dispatcher import PriorityDispatcher, DEFAULT_PRIORITY

d = PriorityDispatcher()
value_list = []
Expand All @@ -32,7 +32,7 @@ def test_various_priorities():


def test_STOP_DISPATCHING():
from asyncpygame import PriorityDispatcher, DEFAULT_PRIORITY, STOP_DISPATCHING
from asyncpygame._lowlevel._priority_dispatcher import PriorityDispatcher, DEFAULT_PRIORITY, STOP_DISPATCHING

d = PriorityDispatcher()
value_list = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,66 @@


def test_thread_id():
import asyncpygame as ap
from asyncgui import start
from asyncpygame._lowlevel._threads import Timer, run_in_executor

async def job():
before = threading.get_ident()
await ap.run_in_executor(timer, executor, lambda: None, polling_interval=0)
await run_in_executor(timer, executor, lambda: None, polling_interval=0)
after = threading.get_ident()
assert before == after

timer = ap.Timer()
timer = Timer()
with ThreadPoolExecutor() as executor:
task = ap.start(job())
task = start(job())
timer.progress(0)
assert task.finished


def test_propagate_exception():
import asyncpygame as ap
from asyncgui import start
from asyncpygame._lowlevel._threads import Timer, run_in_executor

async def job():
with pytest.raises(ZeroDivisionError):
await ap.run_in_executor(timer, executor, lambda: 1 / 0, polling_interval=0)
await run_in_executor(timer, executor, lambda: 1 / 0, polling_interval=0)

timer = ap.Timer()
timer = Timer()

with ThreadPoolExecutor() as executor:
task = ap.start(job())
task = start(job())
timer.progress(0)
assert task.finished


def test_no_exception():
import asyncpygame as ap
from asyncgui import start
from asyncpygame._lowlevel._threads import Timer, run_in_executor

async def job():
assert 'A' == await ap.run_in_executor(timer, executor, lambda: 'A', polling_interval=0)
assert 'A' == await run_in_executor(timer, executor, lambda: 'A', polling_interval=0)

timer = ap.Timer()
timer = Timer()
with ThreadPoolExecutor() as executor:
task = ap.start(job())
task = start(job())
timer.progress(0)
assert task.finished


def test_cancel_before_getting_excuted():
import time
import asyncpygame as ap
from asyncgui import Event, start
from asyncpygame._lowlevel._threads import Timer, run_in_executor

flag = ap.Event()
flag = Event()

async def job():
await ap.run_in_executor(timer, executor, flag.set, polling_interval=0)
await run_in_executor(timer, executor, flag.set, polling_interval=0)

timer = ap.Timer()
timer = Timer()
with ThreadPoolExecutor(max_workers=1) as executor:
executor.submit(time.sleep, .1)
task = ap.start(job())
task = start(job())
time.sleep(.02)
assert not task.finished
assert not flag.is_set
Expand Down
Loading

0 comments on commit d91fa24

Please sign in to comment.