From 2c0e4d2563cd4bbee84171d56fbda7240795eb4f Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Mon, 9 Dec 2024 16:07:59 +0100 Subject: [PATCH 1/2] A bit more docs on async --- README.md | 6 ++++++ docs/start.rst | 17 +++++++++++++++++ docs/utils.rst | 1 + docs/utils_asyncs.rst | 5 +++++ docs/utils_bitmappresentadapter.rst | 4 ++-- docs/utils_bitmaprenderingcontext.rst | 4 ++-- docs/utils_cube.rst | 2 +- rendercanvas/_events.py | 12 +++--------- rendercanvas/_loop.py | 1 + rendercanvas/utils/asyncadapter.py | 5 ++--- rendercanvas/utils/asyncs.py | 12 ++++++++++-- 11 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 docs/utils_asyncs.rst diff --git a/README.md b/README.md index bf40a1d..c8fbef2 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,12 @@ main = Main() app.exec() ``` +## Async or not async + +We support both; a render canvas can be used in a fully async setting using e.g. Asyncio or Trio, or in an event-drived framework like Qt. +If you like callbacks, ``loop.call_later()`` always works. If you like async, use ``loop.add_task()``. Event handlers can always be async. +See the [docs on async](https://rendercanvas.readthedocs.io/stable/start.html#async) for details. + ## License diff --git a/docs/start.rst b/docs/start.rst index d1229a8..3af307a 100644 --- a/docs/start.rst +++ b/docs/start.rst @@ -70,6 +70,23 @@ Rendering with wgpu: # ... wgpu code + +.. _async: + +Async +----- + +A render canvas can be used in a fully async setting using e.g. Asyncio or Trio, or in an event-drived framework like Qt. +If you like callbacks, ``loop.call_later()`` always works. If you like async, use ``loop.add_task()``. Event handlers can always be async. + +If you make use of async functions (co-routines), and want to keep your code portable accross +different canvas backends, restrict your use of async features to ``sleep`` and ``Event``; +these are the only features currently implemened in our async adapter utility. +We recommend importing these from :doc:`rendercanvas.utils.asyncs ` or use ``sniffio`` to detect the library that they can be imported from. + +On the other hand, if you know your code always runs on the asyncio loop, you can fully make use of ``asyncio``. Dito for Trio. + + Freezing apps ------------- diff --git a/docs/utils.rst b/docs/utils.rst index 5cb0ecb..efaeafe 100644 --- a/docs/utils.rst +++ b/docs/utils.rst @@ -5,6 +5,7 @@ Utils :maxdepth: 2 :caption: Contents: + utils_asyncs utils_cube utils_bitmappresentadapter.rst utils_bitmaprenderingcontext.rst diff --git a/docs/utils_asyncs.rst b/docs/utils_asyncs.rst new file mode 100644 index 0000000..3f6a44a --- /dev/null +++ b/docs/utils_asyncs.rst @@ -0,0 +1,5 @@ +``utils.asyncs`` +================ + +.. automodule:: rendercanvas.utils.asyncs + :members: diff --git a/docs/utils_bitmappresentadapter.rst b/docs/utils_bitmappresentadapter.rst index 31b8ddc..2889384 100644 --- a/docs/utils_bitmappresentadapter.rst +++ b/docs/utils_bitmappresentadapter.rst @@ -1,5 +1,5 @@ -Bitmap present adapter -====================== +``utils.bitmappresentadapter`` +============================== .. automodule:: rendercanvas.utils.bitmappresentadapter :members: diff --git a/docs/utils_bitmaprenderingcontext.rst b/docs/utils_bitmaprenderingcontext.rst index 1b76c4e..8bad64c 100644 --- a/docs/utils_bitmaprenderingcontext.rst +++ b/docs/utils_bitmaprenderingcontext.rst @@ -1,5 +1,5 @@ -Bitmap rendering context -======================== +``utils.bitmaprenderingcontext`` +================================ .. automodule:: rendercanvas.utils.bitmaprenderingcontext :members: diff --git a/docs/utils_cube.rst b/docs/utils_cube.rst index 308726b..4086f47 100644 --- a/docs/utils_cube.rst +++ b/docs/utils_cube.rst @@ -1,4 +1,4 @@ -Code for wgpu cube example +``utils.cube`` ========================== .. automodule:: rendercanvas.utils.cube diff --git a/rendercanvas/_events.py b/rendercanvas/_events.py index 263798c..b262477 100644 --- a/rendercanvas/_events.py +++ b/rendercanvas/_events.py @@ -77,21 +77,15 @@ def add_handler(self, *args, order: float = 0): Arguments: callback (callable): The event handler. Must accept a single event argument. Can be a plain function or a coroutine function. + If you use async callbacks, see :ref:`async` for the limitations. *types (list of strings): A list of event types. order (float): Set callback priority order. Callbacks with lower priorities - are called first. Default is 0. + are called first. Default is 0. When an event is emitted, callbacks with + the same priority are called in the order that they were added. For the available events, see https://jupyter-rfb.readthedocs.io/en/stable/events.html. - When an event is emitted, callbacks with the same priority are called in - the order that they were added. - - If you use async callbacks and want to keep your code portable accross - different canvas backends, we recommend using ``sleep`` and ``Event`` from - ``rendercanvas.utils.asyncs``. If you know your code always runs on e.g. the - asyncio loop, you can fully make use of ``asyncio``. - The callback is stored, so it can be a lambda or closure. This also means that if a method is given, a reference to the object is held, which may cause circular references or prevent the Python GC from diff --git a/rendercanvas/_loop.py b/rendercanvas/_loop.py index e981c5e..ddb7755 100644 --- a/rendercanvas/_loop.py +++ b/rendercanvas/_loop.py @@ -143,6 +143,7 @@ def add_task(self, async_func, *args, name="unnamed"): """Run an async function in the event-loop. All tasks are stopped when the loop stops. + See :ref:`async` for the limitations of async code in rendercanvas. """ if not (callable(async_func) and iscoroutinefunction(async_func)): raise TypeError("add_task() expects an async function.") diff --git a/rendercanvas/utils/asyncadapter.py b/rendercanvas/utils/asyncadapter.py index 80461b3..e1f44ab 100644 --- a/rendercanvas/utils/asyncadapter.py +++ b/rendercanvas/utils/asyncadapter.py @@ -1,5 +1,6 @@ """ -A micro async framework that only support sleep() and Event. Behaves well with sniffio. +A micro async framework that only support ``sleep()`` and ``Event``. Behaves well with ``sniffio``. +Intended for internal use, but is fully standalone. """ import logging @@ -11,8 +12,6 @@ class Sleeper: - """Awaitable to implement sleep.""" - def __init__(self, delay): self.delay = delay diff --git a/rendercanvas/utils/asyncs.py b/rendercanvas/utils/asyncs.py index 398c867..3307885 100644 --- a/rendercanvas/utils/asyncs.py +++ b/rendercanvas/utils/asyncs.py @@ -1,6 +1,14 @@ """ -This module implements all async functionality that one can use in any backend. -This uses sniffio to detect the async framework in use. +This module implements all async functionality that one can use in any ``rendercanvas`` backend. +This uses ``sniffio`` to detect the async framework in use. + +To give an idea how to use ``sniffio`` to get a generic async sleep function: + +.. code-block:: py + + libname = sniffio.current_async_library() + sleep = sys.modules[libname].sleep + """ import sys From e9ba8f81934d798b383717a7a8d9cf6d4fd41ae1 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Mon, 9 Dec 2024 16:51:55 +0100 Subject: [PATCH 2/2] Added a little comment for Mark --- docs/start.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/start.rst b/docs/start.rst index 3af307a..609f72d 100644 --- a/docs/start.rst +++ b/docs/start.rst @@ -86,6 +86,9 @@ We recommend importing these from :doc:`rendercanvas.utils.asyncs On the other hand, if you know your code always runs on the asyncio loop, you can fully make use of ``asyncio``. Dito for Trio. +If you use Qt and get nervous from async code, no worries, when running on Qt, ``asyncio`` is not even imported. You can regard most async functions +as syntactic sugar for pieces of code chained with ``call_later``. That's more or less how our async adapter works :) + Freezing apps -------------