Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A bit more docs on async #44

Merged
merged 2 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading