Skip to content

Commit

Permalink
A bit more docs on async (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
almarklein authored Dec 10, 2024
1 parent 959cddb commit a6d7c02
Show file tree
Hide file tree
Showing 11 changed files with 53 additions and 19 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 20 additions & 0 deletions docs/start.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@ 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 <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.

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
-------------

Expand Down
1 change: 1 addition & 0 deletions docs/utils.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Utils
:maxdepth: 2
:caption: Contents:

utils_asyncs
utils_cube
utils_bitmappresentadapter.rst
utils_bitmaprenderingcontext.rst
5 changes: 5 additions & 0 deletions docs/utils_asyncs.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
``utils.asyncs``
================

.. automodule:: rendercanvas.utils.asyncs
:members:
4 changes: 2 additions & 2 deletions docs/utils_bitmappresentadapter.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Bitmap present adapter
======================
``utils.bitmappresentadapter``
==============================

.. automodule:: rendercanvas.utils.bitmappresentadapter
:members:
4 changes: 2 additions & 2 deletions docs/utils_bitmaprenderingcontext.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Bitmap rendering context
========================
``utils.bitmaprenderingcontext``
================================

.. automodule:: rendercanvas.utils.bitmaprenderingcontext
:members:
2 changes: 1 addition & 1 deletion docs/utils_cube.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Code for wgpu cube example
``utils.cube``
==========================

.. automodule:: rendercanvas.utils.cube
Expand Down
12 changes: 3 additions & 9 deletions rendercanvas/_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions rendercanvas/_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
5 changes: 2 additions & 3 deletions rendercanvas/utils/asyncadapter.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,8 +12,6 @@


class Sleeper:
"""Awaitable to implement sleep."""

def __init__(self, delay):
self.delay = delay

Expand Down
12 changes: 10 additions & 2 deletions rendercanvas/utils/asyncs.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit a6d7c02

Please sign in to comment.