[pygobject/wip/gio-async-awaitable-return: 2/4] cache: Resolve and store information about async functions




commit d7f1a0a77af52b5d169976f41b8af6159c4a79f3
Author: Benjamin Berg <bberg redhat com>
Date:   Fri Nov 13 00:21:49 2020 +0100

    cache: Resolve and store information about async functions
    
    This also resolves information about async functions in a generic and
    safe way. The corresponding _finish function will be stored for later
    reference and an attribute is added to cache the return object type for
    an async function.

 gi/pygi-cache.c   | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gi/pygi-cache.h   |  9 +++++++
 gi/pygi-closure.c |  3 +++
 3 files changed, 87 insertions(+)
---
diff --git a/gi/pygi-cache.c b/gi/pygi-cache.c
index c6630de0..d25e4b39 100644
--- a/gi/pygi-cache.c
+++ b/gi/pygi-cache.c
@@ -172,6 +172,11 @@ pygi_arg_interface_setup (PyGIInterfaceCache *iface_cache,
     iface_cache->g_type = g_registered_type_info_get_g_type ( (GIRegisteredTypeInfo *)iface_info);
     iface_cache->py_type = pygi_type_import_by_gi_info ( (GIBaseInfo *) iface_info);
 
+    if (g_type_is_a (iface_cache->g_type, G_TYPE_OBJECT)) {
+        if (g_str_equal (g_type_name (iface_cache->g_type), "GCancellable"))
+            iface_cache->arg_cache.async_context = PYGI_ASYNC_CONTEXT_CANCELLABLE;
+    }
+
     if (iface_cache->py_type == NULL) {
         return FALSE;
     }
@@ -787,8 +792,11 @@ _function_cache_invoke_real (PyGIFunctionCache *function_cache,
 static void
 _function_cache_deinit_real (PyGICallableCache *callable_cache)
 {
+    PyGIFunctionCache *function_cache = (PyGIFunctionCache *) callable_cache;
     g_function_invoker_destroy (&((PyGIFunctionCache *) callable_cache)->invoker);
 
+    g_clear_pointer ((GIBaseInfo **) &function_cache->async_finish, g_base_info_unref);
+
     _callable_cache_deinit_real (callable_cache);
 }
 
@@ -799,6 +807,7 @@ _function_cache_init (PyGIFunctionCache *function_cache,
     PyGICallableCache *callable_cache = (PyGICallableCache *) function_cache;
     GIFunctionInvoker *invoker = &function_cache->invoker;
     GError *error = NULL;
+    guint i;
 
     callable_cache->calling_context = PYGI_CALLING_CONTEXT_IS_FROM_PY;
 
@@ -811,6 +820,72 @@ _function_cache_init (PyGIFunctionCache *function_cache,
     if (!_callable_cache_init (callable_cache, callable_info))
         return FALSE;
 
+    /* Check if this function is an async routine that is capable of returning
+     * an async awaitable object.
+     */
+    if (!callable_cache->has_return && callable_cache->n_to_py_args == 0) {
+        PyGIArgCache *cancellable = NULL;
+        PyGIArgCache *async_callback = NULL;
+
+        for (i = 0; i < _pygi_callable_cache_args_len (callable_cache); i++) {
+            PyGIArgCache *arg_cache = _pygi_callable_cache_get_arg (callable_cache, i);
+
+            /* Ignore any out or in/out parameters. */
+            if (arg_cache->async_context == PYGI_ASYNC_CONTEXT_CALLBACK) {
+                if (async_callback) {
+                    async_callback = NULL;
+                    break;
+                }
+                async_callback = arg_cache;
+            } else if (arg_cache->async_context == PYGI_ASYNC_CONTEXT_CANCELLABLE) {
+                if (cancellable) {
+                    cancellable = NULL;
+                    break;
+                }
+                cancellable = arg_cache;
+            }
+        }
+
+        if (cancellable && async_callback) {
+            GIBaseInfo *container = g_base_info_get_container ((GIBaseInfo*) callable_info);
+            GIBaseInfo *async_finish = NULL;
+            gint name_len;
+            gchar *finish_name = NULL;
+
+            /* This appears to be an async routine. As we have the
+             * GCallableInfo at this point, so guess the finish name and look
+             * up that information.
+             */
+            name_len = strlen (callable_cache->name);
+            if (g_str_has_suffix (callable_cache->name, "_async"))
+                name_len -= 6;
+
+            /* Original name without _async if it is there, _finish + NUL byte */
+            finish_name = g_malloc0 (name_len + 7 + 1);
+            strncat (finish_name, callable_cache->name, name_len);
+            strcat (finish_name, "_finish");
+
+            if (container && g_base_info_get_type (container) == GI_INFO_TYPE_OBJECT) {
+                async_finish = g_object_info_find_method ((GIObjectInfo *) container, finish_name);
+            } else if (container && g_base_info_get_type (container) == GI_INFO_TYPE_INTERFACE) {
+                async_finish = g_interface_info_find_method ((GIInterfaceInfo *) container, finish_name);
+            } else if (!container) {
+                async_finish = g_irepository_find_by_name (NULL,
+                                                           callable_cache->namespace,
+                                                           finish_name);
+            } else {
+                g_debug ("Awaitable async functions only work on GObjects and as toplevel functions.");
+            }
+
+            if (async_finish && g_base_info_get_type (async_finish))
+                function_cache->async_finish = (GIFunctionInfo *) async_finish;
+            else if (async_finish)
+                g_base_info_unref (async_finish);
+
+            g_free (finish_name);
+        }
+    }
+
     /* Set by PyGICCallbackCache and PyGIVFuncCache */
     if (invoker->native_address == NULL) {
         if (g_function_info_prep_invoker ((GIFunctionInfo *) callable_info,
diff --git a/gi/pygi-cache.h b/gi/pygi-cache.h
index fc5a6162..0371df91 100644
--- a/gi/pygi-cache.h
+++ b/gi/pygi-cache.h
@@ -104,12 +104,18 @@ typedef enum {
     PYGI_CALLING_CONTEXT_IS_FROM_PY
 } PyGICallingContext;
 
+typedef enum {
+    PYGI_ASYNC_CONTEXT_NONE = 0,
+    PYGI_ASYNC_CONTEXT_CALLBACK,
+    PYGI_ASYNC_CONTEXT_CANCELLABLE,
+} PyGIAsyncContext;
 
 struct _PyGIArgCache
 {
     const gchar *arg_name;
 
     PyGIMetaArgType meta_type;
+    PyGIAsyncContext async_context;
     gboolean is_pointer;
     gboolean is_caller_allocates;
     gboolean is_skipped;
@@ -218,6 +224,9 @@ struct _PyGICallableCache
 struct _PyGIFunctionCache {
     PyGICallableCache callable_cache;
 
+    /* Information about async functions. */
+    GIFunctionInfo *async_finish;
+
     /* An invoker with ffi_cif already setup */
     GIFunctionInvoker invoker;
 
diff --git a/gi/pygi-closure.c b/gi/pygi-closure.c
index 136eec64..b9933949 100644
--- a/gi/pygi-closure.c
+++ b/gi/pygi-closure.c
@@ -917,6 +917,9 @@ pygi_arg_callback_setup_from_info (PyGICallbackCache  *arg_cache,
         arg_cache->closure_cache = pygi_closure_cache_new (arg_cache->interface_info);
         cache->from_py_marshaller = _pygi_marshal_from_py_interface_callback;
         cache->from_py_cleanup = _pygi_marshal_cleanup_from_py_interface_callback;
+
+        if (arg_cache->scope == GI_SCOPE_TYPE_ASYNC)
+            arg_cache->arg_cache.async_context = PYGI_ASYNC_CONTEXT_CALLBACK;
     }
 
     if (direction & PYGI_DIRECTION_TO_PYTHON) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]