[pygobject] Split the callable cache into the different types



commit e6d48b4eadbeb1014c4eb140317b579e69eb8d88
Author: Garrett Regier <Garrett Regier riftio com>
Date:   Fri Aug 1 11:40:08 2014 -0400

    Split the callable cache into the different types
    
    Instead of doing different things based on the various function types
    this adds vfuncs for generate_args_cache() and invoke() which are then
    specialized for the various function types. Also add a calling context
    to the callable cache which is then used to determine the direction
    when generating the arg caches.
    
    This is in preparation for adding closure caches.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=727004

 gi/pygi-cache.c               |  514 ++++++++++++++++++++++++++++++-----------
 gi/pygi-cache.h               |   96 ++++++---
 gi/pygi-ccallback.c           |   16 +-
 gi/pygi-info.c                |    3 +-
 gi/pygi-invoke-state-struct.h |    2 -
 gi/pygi-invoke.c              |  244 ++++++++------------
 gi/pygi-invoke.h              |    4 +
 gi/pygi.h                     |    2 +-
 8 files changed, 564 insertions(+), 317 deletions(-)
---
diff --git a/gi/pygi-cache.c b/gi/pygi-cache.c
index 5f60766..e6dc0e0 100644
--- a/gi/pygi-cache.c
+++ b/gi/pygi-cache.c
@@ -117,25 +117,6 @@ pygi_arg_cache_free (PyGIArgCache *cache)
         g_slice_free (PyGIArgCache, cache);
 }
 
-void
-pygi_callable_cache_free (PyGICallableCache *cache)
-{
-    if (cache == NULL)
-        return;
-
-    g_slist_free (cache->to_py_args);
-    g_slist_free (cache->arg_name_list);
-    g_hash_table_destroy (cache->arg_name_hash);
-    g_ptr_array_unref (cache->args_cache);
-
-    if (cache->return_cache != NULL)
-        pygi_arg_cache_free (cache->return_cache);
-
-    g_function_invoker_destroy (&cache->invoker);
-    g_slice_free (PyGICallableCache, cache);
-}
-
-
 /* PyGIInterfaceCache */
 
 static void
@@ -447,6 +428,8 @@ pygi_arg_cache_new (GITypeInfo *type_info,
     return arg_cache;
 }
 
+/* PyGICallableCache */
+
 static PyGIDirection
 _pygi_get_direction (PyGICallableCache *callable_cache, GIDirection gi_direction)
 {
@@ -454,11 +437,11 @@ _pygi_get_direction (PyGICallableCache *callable_cache, GIDirection gi_direction
     if (gi_direction == GI_DIRECTION_INOUT) {
         return PYGI_DIRECTION_BIDIRECTIONAL;
     } else if (gi_direction == GI_DIRECTION_IN) {
-        if (callable_cache->function_type == PYGI_FUNCTION_TYPE_CALLBACK)
+        if (callable_cache->calling_context != PYGI_CALLING_CONTEXT_IS_FROM_PY)
             return PYGI_DIRECTION_TO_PYTHON;
         return PYGI_DIRECTION_FROM_PYTHON;
     } else {
-        if (callable_cache->function_type == PYGI_FUNCTION_TYPE_CALLBACK)
+        if (callable_cache->calling_context != PYGI_CALLING_CONTEXT_IS_FROM_PY)
             return PYGI_DIRECTION_FROM_PYTHON;
         return PYGI_DIRECTION_TO_PYTHON;
     }
@@ -466,11 +449,11 @@ _pygi_get_direction (PyGICallableCache *callable_cache, GIDirection gi_direction
 
 /* Generate the cache for the callable's arguments */
 static gboolean
-_args_cache_generate (GICallableInfo *callable_info,
-                      PyGICallableCache *callable_cache)
+_callable_cache_generate_args_cache_real (PyGICallableCache *callable_cache,
+                                          GICallableInfo *callable_info)
 {
-    gssize arg_index = 0;
     gssize i;
+    gssize arg_index;
     GITypeInfo *return_info;
     GITransfer return_transfer;
     PyGIArgCache *return_cache;
@@ -500,41 +483,9 @@ _args_cache_generate (GICallableInfo *callable_info,
     callable_cache->return_cache = return_cache;
     g_base_info_unref (return_info);
 
-    /* first arg is the instance */
-    if (callable_cache->function_type == PYGI_FUNCTION_TYPE_METHOD ||
-            callable_cache->function_type == PYGI_FUNCTION_TYPE_VFUNC) {
-        GIInterfaceInfo *interface_info;
-        PyGIArgCache *instance_cache;
-
-        interface_info = g_base_info_get_container ( (GIBaseInfo *)callable_info);
-
-        instance_cache =
-            _arg_cache_new_for_interface (interface_info,
-                                          NULL,
-                                          NULL,
-                                          GI_TRANSFER_NOTHING,
-                                          PYGI_DIRECTION_FROM_PYTHON,
-                                          callable_cache);
-
-        g_base_info_unref ( (GIBaseInfo *)interface_info);
-
-        if (instance_cache == NULL)
-            return FALSE;
-
-        /* Because we are not supplied a GITypeInfo for instance arguments,
-         * assume some defaults. */
-        instance_cache->is_pointer = TRUE;
-        instance_cache->py_arg_index = 0;
-        instance_cache->c_arg_index = 0;
-
-        _pygi_callable_cache_set_arg (callable_cache, arg_index, instance_cache);
-
-        arg_index++;
-        callable_cache->n_py_args++;
-    }
-
-
-    for (i=0; arg_index < _pygi_callable_cache_args_len (callable_cache); arg_index++, i++) {
+    for (i = 0, arg_index = callable_cache->args_offset;
+         arg_index < _pygi_callable_cache_args_len (callable_cache);
+         i++, arg_index++) {
         PyGIArgCache *arg_cache = NULL;
         GIArgInfo *arg_info;
         PyGIDirection direction;
@@ -681,59 +632,32 @@ _args_cache_generate (GICallableInfo *callable_info,
     return TRUE;
 }
 
-static gboolean
-_setup_invoker (GICallableInfo *callable_info,
-                GIInfoType info_type,
-                GIFunctionInvoker *invoker,
-                GCallback function_ptr)
+static void
+_callable_cache_deinit_real (PyGICallableCache *cache)
 {
-    GError *error = NULL;
-
-    if (info_type == GI_INFO_TYPE_FUNCTION) {
-        if (g_function_info_prep_invoker ((GIFunctionInfo *)callable_info,
-                                          invoker,
-                                          &error)) {
-            return TRUE;
-        }
-        if (!pygi_error_check (&error)) {
-            PyErr_Format (PyExc_RuntimeError,
-                          "unknown error creating invoker for %s",
-                          g_base_info_get_name ((GIBaseInfo *)callable_info));
-        }
-        return FALSE;
+    g_slist_free (cache->to_py_args);
+    g_slist_free (cache->arg_name_list);
+    g_hash_table_destroy (cache->arg_name_hash);
+    g_ptr_array_unref (cache->args_cache);
 
-    } else {
-        if (!g_function_invoker_new_for_address (function_ptr,
-                                                 (GIFunctionInfo *)callable_info,
-                                                 invoker,
-                                                 &error)) {
-            if (!pygi_error_check (&error)) {
-                PyErr_Format (PyExc_RuntimeError,
-                              "unknown error creating invoker for %s",
-                              g_base_info_get_name ((GIBaseInfo *)callable_info));
-            }
-            return FALSE;
-        }
-    }
-    return TRUE;
+    if (cache->return_cache != NULL)
+        pygi_arg_cache_free (cache->return_cache);
 }
 
-PyGICallableCache *
-pygi_callable_cache_new (GICallableInfo *callable_info,
-                         GCallback function_ptr,
-                         gboolean is_ccallback)
+static gboolean
+_callable_cache_init (PyGICallableCache *cache,
+                      GICallableInfo *callable_info)
 {
     gint n_args;
-    PyGICallableCache *cache;
-    GIInfoType type = g_base_info_get_type ( (GIBaseInfo *)callable_info);
 
-    cache = g_slice_new0 (PyGICallableCache);
+    if (cache->deinit == NULL)
+        cache->deinit = _callable_cache_deinit_real;
 
-    if (cache == NULL)
-        return NULL;
+    if (cache->generate_args_cache == NULL)
+        cache->generate_args_cache = _callable_cache_generate_args_cache_real;
 
-    cache->name = g_base_info_get_name ((GIBaseInfo *)callable_info);
-    cache->throws = g_callable_info_can_throw_gerror ((GIBaseInfo *)callable_info);
+    cache->name = g_base_info_get_name ((GIBaseInfo *) callable_info);
+    cache->throws = g_callable_info_can_throw_gerror ((GIBaseInfo *) callable_info);
 
     if (g_base_info_is_deprecated (callable_info)) {
         const gchar *deprecated = g_base_info_get_attribute (callable_info, "deprecated");
@@ -745,51 +669,373 @@ pygi_callable_cache_new (GICallableInfo *callable_info,
         else
             warning = g_strdup_printf ("%s.%s is deprecated",
                                        g_base_info_get_namespace (callable_info), cache->name);
-        PyErr_WarnEx(PyExc_DeprecationWarning, warning, 0);
+        PyErr_WarnEx (PyExc_DeprecationWarning, warning, 0);
         g_free (warning);
     }
 
-    if (type == GI_INFO_TYPE_FUNCTION) {
-        GIFunctionInfoFlags flags;
+    n_args = cache->args_offset + g_callable_info_get_n_args (callable_info);
 
-        flags = g_function_info_get_flags ( (GIFunctionInfo *)callable_info);
+    if (n_args >= 0) {
+        cache->args_cache = g_ptr_array_new_full (n_args, (GDestroyNotify) pygi_arg_cache_free);
+        g_ptr_array_set_size (cache->args_cache, n_args);
+    }
 
-        if (flags & GI_FUNCTION_IS_CONSTRUCTOR)
-            cache->function_type = PYGI_FUNCTION_TYPE_CONSTRUCTOR;
-        else if (flags & GI_FUNCTION_IS_METHOD)
-            cache->function_type = PYGI_FUNCTION_TYPE_METHOD;
-    } else if (type == GI_INFO_TYPE_VFUNC) {
-        cache->function_type = PYGI_FUNCTION_TYPE_VFUNC;
-    } else if (type == GI_INFO_TYPE_CALLBACK) {
-        if (is_ccallback)
-            cache->function_type = PYGI_FUNCTION_TYPE_CCALLBACK;
-        else
-            cache->function_type = PYGI_FUNCTION_TYPE_CALLBACK;
+    if (!cache->generate_args_cache (cache, callable_info)) {
+        _callable_cache_deinit_real (cache);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+void
+pygi_callable_cache_free (PyGICallableCache *cache)
+{
+    cache->deinit (cache);
+    g_free (cache);
+}
+
+/* PyGIFunctionCache */
+
+static PyObject *
+_function_cache_invoke_real (PyGIFunctionCache *function_cache,
+                             PyGIInvokeState *state,
+                             PyObject *py_args,
+                             PyObject *py_kwargs)
+{
+    return pygi_invoke_c_callable (function_cache, state,
+                                   py_args, py_kwargs);
+}
+
+static void
+_function_cache_deinit_real (PyGICallableCache *callable_cache)
+{
+    g_function_invoker_destroy (&((PyGIFunctionCache *) callable_cache)->invoker);
+
+    _callable_cache_deinit_real (callable_cache);
+}
+
+static gboolean
+_function_cache_init (PyGIFunctionCache *function_cache,
+                      GICallableInfo *callable_info)
+{
+    PyGICallableCache *callable_cache = (PyGICallableCache *) function_cache;
+    GIFunctionInvoker *invoker = &function_cache->invoker;
+    GError *error = NULL;
+
+    callable_cache->calling_context = PYGI_CALLING_CONTEXT_IS_FROM_PY;
+
+    if (callable_cache->deinit == NULL)
+        callable_cache->deinit = _function_cache_deinit_real;
+
+    if (function_cache->invoke == NULL)
+        function_cache->invoke = _function_cache_invoke_real;
+
+    if (!_callable_cache_init (callable_cache, callable_info))
+        return FALSE;
+
+    /* Set by PyGICCallbackCache and PyGIVFuncCache */
+    if (invoker->native_address == NULL) {
+        if (g_function_info_prep_invoker ((GIFunctionInfo *) callable_info,
+                                          invoker,
+                                          &error)) {
+            return TRUE;
+        }
     } else {
-        cache->function_type = PYGI_FUNCTION_TYPE_METHOD;
+        if (g_function_invoker_new_for_address (invoker->native_address,
+                                                (GIFunctionInfo *) callable_info,
+                                                invoker,
+                                                &error)) {
+            return TRUE;
+        }
     }
 
-    /* if we are a method or vfunc make sure the instance parameter is counted */
-    if (cache->function_type == PYGI_FUNCTION_TYPE_METHOD ||
-            cache->function_type == PYGI_FUNCTION_TYPE_VFUNC)
-        cache->args_offset = 1;
+    if (!pygi_error_check (&error)) {
+        PyErr_Format (PyExc_RuntimeError,
+                      "unknown error creating invoker for %s",
+                      g_base_info_get_name ((GIBaseInfo *) callable_info));
+    }
 
-    n_args = cache->args_offset + g_callable_info_get_n_args (callable_info);
+    _callable_cache_deinit_real (callable_cache);
+    return FALSE;
+}
 
-    if (n_args >= 0) {
-        cache->args_cache = g_ptr_array_new_full (n_args, (GDestroyNotify) pygi_arg_cache_free);
-        g_ptr_array_set_size (cache->args_cache, n_args);
+PyGIFunctionCache *
+pygi_function_cache_new (GICallableInfo *info)
+{
+    PyGIFunctionCache *function_cache;
+
+    function_cache = g_new0 (PyGIFunctionCache, 1);
+
+    if (!_function_cache_init (function_cache, info)) {
+        g_free (function_cache);
+        return NULL;
     }
 
-    if (!_args_cache_generate (callable_info, cache))
-        goto err;
+    return function_cache;
+}
+
+PyObject *
+pygi_function_cache_invoke (PyGIFunctionCache *function_cache,
+                            PyObject *py_args,
+                            PyObject *py_kwargs)
+{
+    PyGIInvokeState state = { 0, };
+
+    return function_cache->invoke (function_cache, &state,
+                                   py_args, py_kwargs);
+}
+
+/* PyGICCallbackCache */
+
+PyGIFunctionCache *
+pygi_ccallback_cache_new (GICallableInfo *info,
+                          GCallback function_ptr)
+{
+    PyGICCallbackCache *ccallback_cache;
+    PyGIFunctionCache *function_cache;
+
+    ccallback_cache = g_new0 (PyGICCallbackCache, 1);
+    function_cache = (PyGIFunctionCache *) ccallback_cache;
+
+    function_cache->invoker.native_address = function_ptr;
+
+    if (!_function_cache_init (function_cache, info)) {
+        g_free (ccallback_cache);
+        return NULL;
+     }
+
+    return function_cache;
+}
 
-    if (!_setup_invoker (callable_info, type, &cache->invoker, function_ptr)) {
-        goto err;
+PyObject *
+pygi_ccallback_cache_invoke (PyGICCallbackCache *ccallback_cache,
+                             PyObject *py_args,
+                             PyObject *py_kwargs,
+                             gpointer user_data)
+{
+    PyGIFunctionCache *function_cache = (PyGIFunctionCache *) ccallback_cache;
+    PyGIInvokeState state = { 0, };
+
+    state.user_data = user_data;
+
+    return function_cache->invoke (function_cache, &state,
+                                   py_args, py_kwargs);
+}
+
+/* PyGIConstructorCache */
+
+static PyObject *
+_constructor_cache_invoke_real (PyGIFunctionCache *function_cache,
+                                PyGIInvokeState *state,
+                                PyObject *py_args,
+                                PyObject *py_kwargs)
+{
+    PyGICallableCache *cache = (PyGICallableCache *) function_cache;
+    PyObject *constructor_class;
+    PyObject *ret;
+
+    constructor_class = PyTuple_GetItem (py_args, 0);
+    if (constructor_class == NULL) {
+        PyErr_Clear ();
+        PyErr_Format (PyExc_TypeError,
+                      "Constructors require the class to be passed in as an argument, "
+                      "No arguments passed to the %s constructor.",
+                      ((PyGICallableCache *) function_cache)->name);
+
+        return FALSE;
     }
 
-    return cache;
-err:
-    pygi_callable_cache_free (cache);
+    py_args = PyTuple_GetSlice (py_args, 1, PyTuple_Size (py_args));
+    ret = _function_cache_invoke_real (function_cache, state,
+                                       py_args, py_kwargs);
+    Py_DECREF (py_args);
+
+    if (cache->return_cache == NULL || cache->return_cache->is_skipped)
+        return ret;
+
+    if (ret != NULL && ret != Py_None) {
+        if (!PyTuple_Check (ret))
+            return ret;
+
+        if (PyTuple_GET_ITEM (ret, 0) != Py_None)
+            return ret;
+    }
+
+    PyErr_SetString (PyExc_TypeError, "constructor returned NULL");
+
+    Py_XDECREF (ret);
     return NULL;
 }
+
+PyGIFunctionCache *
+pygi_constructor_cache_new (GICallableInfo *info)
+{
+    PyGIConstructorCache *constructor_cache;
+    PyGIFunctionCache *function_cache;
+
+    constructor_cache = g_new0 (PyGIConstructorCache, 1);
+    function_cache = (PyGIFunctionCache *) constructor_cache;
+
+    function_cache->invoke = _constructor_cache_invoke_real;
+
+    if (!_function_cache_init (function_cache, info)) {
+        g_free (constructor_cache);
+        return NULL;
+    }
+
+    return function_cache;
+}
+
+/* PyGIFunctionWithInstanceCache */
+
+static gboolean
+_function_with_instance_cache_generate_args_cache_real (PyGICallableCache *callable_cache,
+                                                        GICallableInfo *callable_info)
+{
+    GIInterfaceInfo *interface_info;
+    PyGIArgCache *instance_cache;
+
+    interface_info = g_base_info_get_container ((GIBaseInfo *) callable_info);
+
+    instance_cache =
+        _arg_cache_new_for_interface (interface_info,
+                                      NULL,
+                                      NULL,
+                                      GI_TRANSFER_NOTHING,
+                                      PYGI_DIRECTION_FROM_PYTHON,
+                                      callable_cache);
+
+    g_base_info_unref ((GIBaseInfo *) interface_info);
+
+    if (instance_cache == NULL)
+        return FALSE;
+
+    /* Because we are not supplied a GITypeInfo for instance arguments,
+     * assume some defaults. */
+    instance_cache->is_pointer = TRUE;
+    instance_cache->py_arg_index = 0;
+    instance_cache->c_arg_index = 0;
+
+    _pygi_callable_cache_set_arg (callable_cache, 0, instance_cache);
+
+    callable_cache->n_py_args++;
+
+    return _callable_cache_generate_args_cache_real (callable_cache,
+                                                     callable_info);
+}
+
+static gboolean
+_function_with_instance_cache_init (PyGIFunctionWithInstanceCache *fwi_cache,
+                                    GICallableInfo *info)
+{
+    PyGICallableCache *callable_cache = (PyGICallableCache *) fwi_cache;
+
+    callable_cache->args_offset += 1;
+    callable_cache->generate_args_cache = _function_with_instance_cache_generate_args_cache_real;
+
+    return _function_cache_init ((PyGIFunctionCache *) fwi_cache, info);
+}
+
+/* PyGIMethodCache */
+
+PyGIFunctionCache *
+pygi_method_cache_new (GICallableInfo *info)
+{
+    PyGIMethodCache *method_cache;
+    PyGIFunctionWithInstanceCache *fwi_cache;
+
+    method_cache = g_new0 (PyGIMethodCache, 1);
+    fwi_cache = (PyGIFunctionWithInstanceCache *) method_cache;
+
+    if (!_function_with_instance_cache_init (fwi_cache, info)) {
+        g_free (method_cache);
+        return NULL;
+    }
+
+    return (PyGIFunctionCache *) method_cache;
+}
+
+/* PyGIVFuncCache */
+
+static PyObject *
+_vfunc_cache_invoke_real (PyGIFunctionCache *function_cache,
+                          PyGIInvokeState *state,
+                          PyObject *py_args,
+                          PyObject *py_kwargs)
+{
+    PyGIVFuncCache *vfunc_cache = (PyGIVFuncCache *) function_cache;
+    PyObject *py_gtype;
+    GType implementor_gtype;
+    GError *error = NULL;
+    PyObject *ret;
+
+    py_gtype = PyTuple_GetItem (py_args, 0);
+    if (py_gtype == NULL) {
+        PyErr_SetString (PyExc_TypeError,
+                         "need the GType of the implementor class");
+        return FALSE;
+    }
+
+    implementor_gtype = pyg_type_from_object (py_gtype);
+    if (implementor_gtype == G_TYPE_INVALID)
+        return FALSE;
+
+    /* vfunc addresses are pulled into the state at call time and cannot be
+     * cached because the call site can specify a different portion of the
+     * class hierarchy. e.g. Object.do_func vs. SubObject.do_func might
+     * retrieve a different vfunc address but GI gives us the same vfunc info.
+     */
+    state->function_ptr = g_vfunc_info_get_address ((GIVFuncInfo *) vfunc_cache->info,
+                                                    implementor_gtype,
+                                                    &error);
+    if (pygi_error_check (&error)) {
+        return FALSE;
+    }
+
+    py_args = PyTuple_GetSlice (py_args, 1, PyTuple_Size (py_args));
+    ret = _function_cache_invoke_real (function_cache, state,
+                                       py_args, py_kwargs);
+    Py_DECREF (py_args);
+
+    return ret;
+}
+
+static void
+_vfunc_cache_deinit_real (PyGICallableCache *callable_cache)
+{
+    g_base_info_unref (((PyGIVFuncCache *) callable_cache)->info);
+
+    _function_cache_deinit_real (callable_cache);
+}
+
+PyGIFunctionCache *
+pygi_vfunc_cache_new (GICallableInfo *info)
+{
+    PyGIVFuncCache *vfunc_cache;
+    PyGIFunctionCache *function_cache;
+    PyGIFunctionWithInstanceCache *fwi_cache;
+
+    vfunc_cache = g_new0 (PyGIVFuncCache, 1);
+    function_cache = (PyGIFunctionCache *) vfunc_cache;
+    fwi_cache = (PyGIFunctionWithInstanceCache *) vfunc_cache;
+
+    ((PyGICallableCache *) vfunc_cache)->deinit = _vfunc_cache_deinit_real;
+
+    /* This must be non-NULL for _function_cache_init() to create the
+     * invoker, the real address will be set in _vfunc_cache_invoke_real().
+     */
+    function_cache->invoker.native_address = (gpointer) 0xdeadbeef;
+
+    function_cache->invoke = _vfunc_cache_invoke_real;
+
+    if (!_function_with_instance_cache_init (fwi_cache, info)) {
+        g_free (vfunc_cache);
+        return NULL;
+    }
+
+    /* Required by _vfunc_cache_invoke_real() */
+    vfunc_cache->info = g_base_info_ref ((GIBaseInfo *) info);
+
+    return function_cache;
+}
diff --git a/gi/pygi-cache.h b/gi/pygi-cache.h
index 1a160c4..f8ad3e4 100644
--- a/gi/pygi-cache.h
+++ b/gi/pygi-cache.h
@@ -29,8 +29,15 @@
 
 G_BEGIN_DECLS
 
-typedef struct _PyGICallableCache PyGICallableCache;
 typedef struct _PyGIArgCache PyGIArgCache;
+typedef struct _PyGICallableCache PyGICallableCache;
+typedef struct _PyGIFunctionCache PyGIFunctionCache;
+typedef struct _PyGIVFuncCache PyGIVFuncCache;
+
+typedef PyGIFunctionCache PyGICCallbackCache;
+typedef PyGIFunctionCache PyGIConstructorCache;
+typedef PyGIFunctionCache PyGIFunctionWithInstanceCache;
+typedef PyGIFunctionCache PyGIMethodCache;
 
 typedef gboolean (*PyGIMarshalFromPyFunc) (PyGIInvokeState   *state,
                                            PyGICallableCache *callable_cache,
@@ -70,32 +77,24 @@ typedef enum {
 } PyGIMetaArgType;
 
 /*
- * GI determines function types via a combination of flags and info classes.
- * Since for branching purposes they are mutually exclusive, the 
- * PyGIFunctionType enum consolidates them into one enumeration for ease of 
- * branching and debugging.
+ * Argument direction types denotes how we marshal,
+ * e.g. to Python or from Python or both.
  */
 typedef enum {
-    PYGI_FUNCTION_TYPE_FUNCTION,
-    PYGI_FUNCTION_TYPE_METHOD,
-    PYGI_FUNCTION_TYPE_CONSTRUCTOR,
-    PYGI_FUNCTION_TYPE_VFUNC,
-    PYGI_FUNCTION_TYPE_CALLBACK,
-    PYGI_FUNCTION_TYPE_CCALLBACK,
- } PyGIFunctionType;
+    PYGI_DIRECTION_TO_PYTHON     = 1 << 0,
+    PYGI_DIRECTION_FROM_PYTHON   = 1 << 1,
+    PYGI_DIRECTION_BIDIRECTIONAL = PYGI_DIRECTION_TO_PYTHON | PYGI_DIRECTION_FROM_PYTHON
+ } PyGIDirection;
 
 /*
  * In PyGI IN and OUT arguments mean different things depending on the context
- * of the callable (e.g. is it a callback that is being called from C or a
- * function that is being called from python).  We don't as much care if the
- * parameter is an IN or OUT C parameter, than we do if the parameter is being
- * marshalled into Python or from Python.
+ * of the callable, e.g. is it a callback that is being called from C or a
+ * function that is being called from Python.
  */
 typedef enum {
-    PYGI_DIRECTION_TO_PYTHON     = 1 << 0,
-    PYGI_DIRECTION_FROM_PYTHON   = 1 << 1,
-    PYGI_DIRECTION_BIDIRECTIONAL = PYGI_DIRECTION_TO_PYTHON | PYGI_DIRECTION_FROM_PYTHON
- } PyGIDirection;
+    PYGI_CALLING_CONTEXT_IS_FROM_C,
+    PYGI_CALLING_CONTEXT_IS_FROM_PY
+} PyGICallingContext;
 
 
 struct _PyGIArgCache
@@ -159,7 +158,7 @@ struct _PyGICallableCache
 {
     const gchar *name;
 
-    PyGIFunctionType function_type;
+    PyGICallingContext calling_context;
 
     PyGIArgCache *return_cache;
     GPtrArray *args_cache;
@@ -191,9 +190,30 @@ struct _PyGICallableCache
      * This count does not include args with defaults. */
     gssize n_py_required_args;
 
+    void     (*deinit)              (PyGICallableCache *callable_cache);
+
+    gboolean (*generate_args_cache) (PyGICallableCache *callable_cache,
+                                     GICallableInfo *callable_info);
+};
+
+typedef struct _PyGIFunctionCache {
+    PyGICallableCache callable_cache;
+
     /* An invoker with ffi_cif already setup */
     GIFunctionInvoker invoker;
-};
+
+    PyObject *(*invoke) (PyGIFunctionCache *function_cache,
+                         PyGIInvokeState *state,
+                         PyObject *py_args,
+                         PyObject *py_kwargs);
+} PyGIFunctionCache;
+
+typedef struct _PyGIVFuncCache {
+    PyGIFunctionWithInstanceCache fwi_cache;
+
+    GIBaseInfo *info;
+} PyGIVFuncCache;
+
 
 gboolean
 pygi_arg_base_setup      (PyGIArgCache *arg_cache,
@@ -241,12 +261,34 @@ void
 pygi_arg_cache_free      (PyGIArgCache *cache);
 
 void
-pygi_callable_cache_free (PyGICallableCache *cache);
+pygi_callable_cache_free    (PyGICallableCache *cache);
+
+PyGIFunctionCache *
+pygi_function_cache_new     (GICallableInfo *info);
+
+PyObject *
+pygi_function_cache_invoke  (PyGIFunctionCache *function_cache,
+                             PyObject *py_args,
+                             PyObject *py_kwargs);
+
+PyGIFunctionCache *
+pygi_ccallback_cache_new    (GICallableInfo *info,
+                             GCallback function_ptr);
+
+PyObject *
+pygi_ccallback_cache_invoke (PyGIFunctionCache *function_cache,
+                             PyObject *py_args,
+                             PyObject *py_kwargs,
+                             gpointer user_data);
+
+PyGIFunctionCache *
+pygi_constructor_cache_new  (GICallableInfo *info);
+
+PyGIFunctionCache *
+pygi_method_cache_new       (GICallableInfo *info);
 
-PyGICallableCache *
-pygi_callable_cache_new  (GICallableInfo *callable_info,
-                          GCallback function_ptr,
-                          gboolean is_ccallback);
+PyGIFunctionCache *
+pygi_vfunc_cache_new        (GICallableInfo *info);
 
 #define _pygi_callable_cache_args_len(cache) ((cache)->args_cache)->len
 
diff --git a/gi/pygi-ccallback.c b/gi/pygi-ccallback.c
index 9f63ea0..f28487f 100644
--- a/gi/pygi-ccallback.c
+++ b/gi/pygi-ccallback.c
@@ -32,16 +32,16 @@ _ccallback_call(PyGICCallback *self, PyObject *args, PyObject *kwargs)
     PyObject *result;
 
     if (self->cache == NULL) {
-        self->cache = pygi_callable_cache_new (self->info, self->callback, TRUE);
+        self->cache = (PyGICCallbackCache *)pygi_ccallback_cache_new (self->info,
+                                                                      self->callback);
         if (self->cache == NULL)
             return NULL;
     }
 
-    result = pygi_callable_info_invoke( (GIBaseInfo *) self->info,
-                                         args,
-                                         kwargs,
-                                         self->cache,
-                                         self->user_data);
+    result = pygi_ccallback_cache_invoke (self->cache,
+                                          args,
+                                          kwargs,
+                                          self->user_data);
     return result;
 }
 
@@ -78,6 +78,10 @@ static void
 _ccallback_dealloc (PyGICCallback *self)
 {
     g_base_info_unref ( (GIBaseInfo *)self->info);
+
+    if (self->cache != NULL) {
+        pygi_callable_cache_free ( (PyGICallableCache *)self->cache);
+    }
 }
 
 void
diff --git a/gi/pygi-info.c b/gi/pygi-info.c
index 43ee711..fdf12b3 100644
--- a/gi/pygi-info.c
+++ b/gi/pygi-info.c
@@ -175,7 +175,8 @@ _base_info_dealloc (PyGIBaseInfo *self)
 
     g_base_info_unref (self->info);
 
-    pygi_callable_cache_free(self->cache);
+    if (self->cache != NULL)
+        pygi_callable_cache_free ( (PyGICallableCache *) self->cache);
 
     Py_TYPE( (PyObject *) self)->tp_free ( (PyObject *) self);
 }
diff --git a/gi/pygi-invoke-state-struct.h b/gi/pygi-invoke-state-struct.h
index 174f473..732f3b6 100644
--- a/gi/pygi-invoke-state-struct.h
+++ b/gi/pygi-invoke-state-struct.h
@@ -12,8 +12,6 @@ typedef struct _PyGIInvokeState
     PyObject *py_in_args;
     gssize n_py_in_args;
 
-    GType implementor_gtype;
-
     /* Number of arguments the ffi wrapped C function takes. Used as the exact
      * count for argument related arrays held in this struct.
      */
diff --git a/gi/pygi-invoke.c b/gi/pygi-invoke.c
index 6dc93e2..3f72576 100644
--- a/gi/pygi-invoke.c
+++ b/gi/pygi-invoke.c
@@ -25,45 +25,6 @@
 #include "pygi-marshal-cleanup.h"
 #include "pygi-error.h"
 
-static inline gboolean
-_invoke_callable (PyGIInvokeState *state,
-                  PyGICallableCache *cache,
-                  GICallableInfo *callable_info)
-{
-    GIFFIReturnValue ffi_return_value = {0};
-
-    Py_BEGIN_ALLOW_THREADS;
-
-        ffi_call (&cache->invoker.cif,
-                  state->function_ptr,
-                  (void *)&ffi_return_value,
-                  (void **)state->args);
-
-    Py_END_ALLOW_THREADS;
-
-    /* If the callable throws, the address of state->error will be bound into
-     * the state->args as the last value. When the callee sets an error using
-     * the state->args passed, it will have the side effect of setting
-     * state->error allowing for easy checking here.
-     */
-    if (state->error != NULL) {
-        if (pygi_error_check (&(state->error))) {
-            /* even though we errored out, the call itself was successful,
-               so we assume the call processed all of the parameters */
-            pygi_marshal_cleanup_args_from_py_marshal_success (state, cache);
-            return FALSE;
-        }
-    }
-
-    if (cache->return_cache) {
-        gi_type_info_extract_ffi_return_value (cache->return_cache->type_info,
-                                               &ffi_return_value,
-                                               &state->return_arg);
-    }
-
-    return TRUE;
-}
-
 static gboolean
 _check_for_unexpected_kwargs (const gchar *function_name,
                               GHashTable  *arg_name_hash,
@@ -247,15 +208,14 @@ _py_args_combine_and_check_length (PyGICallableCache *cache,
     return combined_py_args;
 }
 
-static inline gboolean
-_invoke_state_init_from_callable_cache (GIBaseInfo *info,
-                                        PyGIInvokeState *state,
-                                        PyGICallableCache *cache,
-                                        PyObject *py_args,
-                                        PyObject *kwargs)
+static gboolean
+_invoke_state_init_from_cache (PyGIInvokeState *state,
+                               PyGIFunctionCache *function_cache,
+                               PyObject *py_args,
+                               PyObject *kwargs)
 {
-    PyObject *combined_args = NULL;
-    state->implementor_gtype = 0;
+    PyGICallableCache *cache = (PyGICallableCache *) function_cache;
+
     state->n_args = _pygi_callable_cache_args_len (cache);
 
     if (cache->throws) {
@@ -263,74 +223,14 @@ _invoke_state_init_from_callable_cache (GIBaseInfo *info,
     }
 
     /* Copy the function pointer to the state for the normal case. For vfuncs,
-     * this will be filled out based on the implementor_gtype calculated below.
+     * this has already been filled out based on the implementor's GType.
      */
-    state->function_ptr = cache->invoker.native_address;
-
-    /* TODO: We don't use the class parameter sent in by  the structure
-     * so we remove it from the py_args tuple but we can keep it 
-     * around if we want to call actual gobject constructors
-     * in the future instead of calling g_object_new
-     */
-    if (cache->function_type == PYGI_FUNCTION_TYPE_CONSTRUCTOR) {
-        PyObject *constructor_class;
-        constructor_class = PyTuple_GetItem (py_args, 0);
-
-        if (constructor_class == NULL) {
-            PyErr_Clear ();
-            PyErr_Format (PyExc_TypeError,
-                          "Constructors require the class to be passed in as an argument, "
-                          "No arguments passed to the %s constructor.",
-                          cache->name);
-
-            return FALSE;
-        }
-    } else if (cache->function_type == PYGI_FUNCTION_TYPE_VFUNC) {
-        PyObject *py_gtype;
-        GError *error = NULL;
-
-        py_gtype = PyTuple_GetItem (py_args, 0);
-        if (py_gtype == NULL) {
-            PyErr_SetString (PyExc_TypeError,
-                             "need the GType of the implementor class");
-            return FALSE;
-        }
-
-        state->implementor_gtype = pyg_type_from_object (py_gtype);
-
-        if (state->implementor_gtype == 0)
-            return FALSE;
-
-        /* vfunc addresses are pulled into the state at call time and cannot be
-         * cached because the call site can specify a different portion of the
-         * class hierarchy. e.g. Object.do_func vs. SubObject.do_func might
-         * retrieve a different vfunc address but GI gives us the same vfunc info.
-         */
-        state->function_ptr = g_vfunc_info_get_address ((GIVFuncInfo *)info,
-                                                        state->implementor_gtype,
-                                                        &error);
-        if (pygi_error_check (&error)) {
-            return FALSE;
-        }
-    }
-
-    if  (cache->function_type == PYGI_FUNCTION_TYPE_CONSTRUCTOR ||
-            cache->function_type == PYGI_FUNCTION_TYPE_VFUNC) {
-
-        /* we could optimize this by using offsets instead of modifying the tuple but it makes the
-         * code more error prone and confusing so don't do that unless profiling shows
-         * significant gain
-         */
-        combined_args = PyTuple_GetSlice (py_args, 1, PyTuple_Size (py_args));
-    } else {
-        combined_args = py_args;
-        Py_INCREF (combined_args);
-    }
+    if (state->function_ptr == NULL)
+        state->function_ptr = function_cache->invoker.native_address;
 
     state->py_in_args = _py_args_combine_and_check_length (cache,
-                                                           combined_args,
+                                                           py_args,
                                                            kwargs);
-    Py_DECREF (combined_args);
 
     if (state->py_in_args == NULL) {
         return FALSE;
@@ -373,8 +273,8 @@ _invoke_state_init_from_callable_cache (GIBaseInfo *info,
     return TRUE;
 }
 
-static inline void
-_invoke_state_clear (PyGIInvokeState *state, PyGICallableCache *cache)
+static void
+_invoke_state_clear (PyGIInvokeState *state, PyGIFunctionCache *function_cache)
 {
     g_slice_free1 (state->n_args * sizeof(GIArgument *), state->args);
     g_slice_free1 (state->n_args * sizeof(gpointer), state->args_cleanup_data);
@@ -427,7 +327,7 @@ _caller_alloc (PyGIArgCache *arg_cache, GIArgument *arg)
     return TRUE;
 }
 
-/* _invoke_marshal_in_args:
+/* pygi_invoke_marshal_in_args:
  *
  * Fills out the state struct argument lists. arg_values will always hold
  * actual values marshaled either to or from Python and C. arg_pointers will
@@ -455,9 +355,10 @@ _caller_alloc (PyGIArgCache *arg_cache, GIArgument *arg)
  * ]]
  *
  */
-static inline gboolean
-_invoke_marshal_in_args (PyGIInvokeState *state, PyGICallableCache *cache)
+static gboolean
+_invoke_marshal_in_args (PyGIInvokeState *state, PyGIFunctionCache *function_cache)
 {
+    PyGICallableCache *cache = (PyGICallableCache *) function_cache;
     gssize i;
 
     if (state->n_py_in_args > cache->n_py_args) {
@@ -600,9 +501,10 @@ _invoke_marshal_in_args (PyGIInvokeState *state, PyGICallableCache *cache)
     return TRUE;
 }
 
-static inline PyObject *
-_invoke_marshal_out_args (PyGIInvokeState *state, PyGICallableCache *cache)
+static PyObject *
+_invoke_marshal_out_args (PyGIInvokeState *state, PyGIFunctionCache *function_cache)
 {
+    PyGICallableCache *cache = (PyGICallableCache *) function_cache;
     PyObject *py_out = NULL;
     PyObject *py_return = NULL;
     gssize total_out_args = cache->n_to_py_args;
@@ -610,15 +512,6 @@ _invoke_marshal_out_args (PyGIInvokeState *state, PyGICallableCache *cache)
 
     if (cache->return_cache) {
         if (!cache->return_cache->is_skipped) {
-            if (cache->function_type == PYGI_FUNCTION_TYPE_CONSTRUCTOR) {
-                if (state->return_arg.v_pointer == NULL) {
-                    PyErr_SetString (PyExc_TypeError, "constructor returned NULL");
-                    pygi_marshal_cleanup_args_return_fail (state,
-                                                       cache);
-                    return NULL;
-                }
-            }
-
             py_return = cache->return_cache->to_py_marshaller ( state,
                                                                 cache,
                                                                 cache->return_cache,
@@ -697,7 +590,7 @@ _invoke_marshal_out_args (PyGIInvokeState *state, PyGICallableCache *cache)
             if (py_obj == NULL) {
                 if (has_return)
                     py_arg_index--;
- 
+
                 pygi_marshal_cleanup_args_to_py_parameter_fail (state,
                                                                 cache,
                                                                 py_arg_index);
@@ -713,41 +606,100 @@ _invoke_marshal_out_args (PyGIInvokeState *state, PyGICallableCache *cache)
 }
 
 PyObject *
-pygi_callable_info_invoke (GIBaseInfo *info, PyObject *py_args,
-                           PyObject *kwargs, PyGICallableCache *cache,
-                           gpointer user_data)
+pygi_invoke_c_callable (PyGIFunctionCache *function_cache,
+                        PyGIInvokeState *state,
+                        PyObject *py_args,
+                        PyObject *py_kwargs)
 {
-    PyGIInvokeState state = { 0, };
+    PyGICallableCache *cache = (PyGICallableCache *) function_cache;
+    GIFFIReturnValue ffi_return_value = {0};
     PyObject *ret = NULL;
 
-    if (!_invoke_state_init_from_callable_cache (info, &state, cache, py_args, kwargs))
-        goto err;
+    if (!_invoke_state_init_from_cache (state, function_cache,
+                                        py_args, py_kwargs))
+         goto err;
+
+    if (!_invoke_marshal_in_args (state, function_cache))
+         goto err;
+
+    Py_BEGIN_ALLOW_THREADS;
 
-    if (cache->function_type == PYGI_FUNCTION_TYPE_CCALLBACK)
-        state.user_data = user_data;
+        ffi_call (&function_cache->invoker.cif,
+                  state->function_ptr,
+                  (void *) &ffi_return_value,
+                  (void **) state->args);
+
+    Py_END_ALLOW_THREADS;
+
+    /* If the callable throws, the address of state->error will be bound into
+     * the state->args as the last value. When the callee sets an error using
+     * the state->args passed, it will have the side effect of setting
+     * state->error allowing for easy checking here.
+     */
+    if (state->error != NULL) {
+        if (pygi_error_check (&state->error)) {
+            /* even though we errored out, the call itself was successful,
+               so we assume the call processed all of the parameters */
+            pygi_marshal_cleanup_args_from_py_marshal_success (state, cache);
+            goto err;
+        }
+    }
 
-    if (!_invoke_marshal_in_args (&state, cache))
-        goto err;
+    if (cache->return_cache) {
+        gi_type_info_extract_ffi_return_value (cache->return_cache->type_info,
+                                               &ffi_return_value,
+                                               &state->return_arg);
+    }
 
-    if (!_invoke_callable (&state, cache, info))
-        goto err;
+    ret = _invoke_marshal_out_args (state, function_cache);
+    pygi_marshal_cleanup_args_from_py_marshal_success (state, cache);
 
-    ret = _invoke_marshal_out_args (&state, cache);
-    pygi_marshal_cleanup_args_from_py_marshal_success (&state, cache);
+    if (ret != NULL)
+        pygi_marshal_cleanup_args_to_py_marshal_success (state, cache);
 
-    if (ret)
-        pygi_marshal_cleanup_args_to_py_marshal_success (&state, cache);
 err:
-    _invoke_state_clear (&state, cache);
+    _invoke_state_clear (state, function_cache);
     return ret;
 }
 
 PyObject *
+pygi_callable_info_invoke (GIBaseInfo *info, PyObject *py_args,
+                           PyObject *kwargs, PyGICallableCache *cache,
+                           gpointer user_data)
+{
+    return pygi_function_cache_invoke ((PyGIFunctionCache *) cache,
+                                       py_args, kwargs);
+}
+
+PyObject *
 _wrap_g_callable_info_invoke (PyGIBaseInfo *self, PyObject *py_args,
                               PyObject *kwargs)
 {
     if (self->cache == NULL) {
-        self->cache = pygi_callable_cache_new (self->info, NULL, FALSE);
+        PyGIFunctionCache *function_cache;
+        GIInfoType type = g_base_info_get_type (self->info);
+
+        if (type == GI_INFO_TYPE_FUNCTION) {
+            GIFunctionInfoFlags flags;
+
+            flags = g_function_info_get_flags ( (GIFunctionInfo *)self->info);
+
+            if (flags & GI_FUNCTION_IS_CONSTRUCTOR) {
+                function_cache = pygi_constructor_cache_new (self->info);
+            } else if (flags & GI_FUNCTION_IS_METHOD) {
+                function_cache = pygi_method_cache_new (self->info);
+            } else {
+                function_cache = pygi_function_cache_new (self->info);
+            }
+        } else if (type == GI_INFO_TYPE_VFUNC) {
+            function_cache = pygi_vfunc_cache_new (self->info);
+        } else if (type == GI_INFO_TYPE_CALLBACK) {
+            g_error ("Cannot invoke callback types");
+        } else {
+            function_cache = pygi_method_cache_new (self->info);
+        }
+
+        self->cache = (PyGICallableCache *)function_cache;
         if (self->cache == NULL)
             return NULL;
     }
diff --git a/gi/pygi-invoke.h b/gi/pygi-invoke.h
index b6bb4b9..b49ffa7 100644
--- a/gi/pygi-invoke.h
+++ b/gi/pygi-invoke.h
@@ -26,8 +26,12 @@
 
 #include "pygi-private.h"
 #include "pygi-invoke-state-struct.h"
+
 G_BEGIN_DECLS
 
+PyObject *pygi_invoke_c_callable    (PyGIFunctionCache *function_cache,
+                                     PyGIInvokeState *state,
+                                     PyObject *py_args, PyObject *py_kwargs);
 PyObject *pygi_callable_info_invoke (GIBaseInfo *info, PyObject *py_args,
                                      PyObject *kwargs, PyGICallableCache *cache,
                                      gpointer user_data);
diff --git a/gi/pygi.h b/gi/pygi.h
index 2a9ee14..3caf6f3 100644
--- a/gi/pygi.h
+++ b/gi/pygi.h
@@ -75,7 +75,7 @@ typedef struct {
     gpointer user_data;
     GIScopeType scope;
     GDestroyNotify destroy_notify_func;
-    PyGICallableCache *cache;
+    PyGICCallbackCache *cache;
 } PyGICCallback;
 
 



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