From ba16fdf0a0e9052163a5dd00b5927b2eccc0683f Mon Sep 17 00:00:00 2001
From: Josh Morman <mormjb@gmail.com>
Date: Thu, 23 Apr 2020 07:51:39 -0400
Subject: runtime: add pybind11 bindings

---
 gnuradio-runtime/python/gnuradio/gr/gateway.py | 349 +++++++++++++------------
 1 file changed, 183 insertions(+), 166 deletions(-)

(limited to 'gnuradio-runtime/python/gnuradio/gr/gateway.py')

diff --git a/gnuradio-runtime/python/gnuradio/gr/gateway.py b/gnuradio-runtime/python/gnuradio/gr/gateway.py
index 71d15781d2..615027b833 100644
--- a/gnuradio-runtime/python/gnuradio/gr/gateway.py
+++ b/gnuradio-runtime/python/gnuradio/gr/gateway.py
@@ -1,5 +1,5 @@
 #
-# Copyright 2011-2012, 2018 Free Software Foundation, Inc.
+# Copyright 2011-2012, 2018, 2020 Free Software Foundation, Inc.
 #
 # This file is part of GNU Radio
 #
@@ -12,11 +12,12 @@ from __future__ import unicode_literals
 
 
 import numpy
+import ctypes
+
+from . import gr_python as gr
+from .gr_python import io_signature  # , io_signaturev
+from .gr_python import block_gateway
 
-from . import runtime_swig as gr
-from .runtime_swig import io_signature, io_signaturev
-from .runtime_swig import block_gw_message_type
-from .runtime_swig import block_gateway
 
 ########################################################################
 # Magic to turn pointers into numpy arrays
@@ -25,54 +26,20 @@ from .runtime_swig import block_gateway
 def pointer_to_ndarray(addr, dtype, nitems):
     class array_like(object):
         __array_interface__ = {
-            'data' : (int(addr), False),
-            'typestr' : dtype.base.str,
-            'descr' : dtype.base.descr,
-            'shape' : (nitems,) + dtype.shape,
-            'strides' : None,
-            'version' : 3
+            'data': (int(addr), False),
+            'typestr': dtype.base.str,
+            'descr': dtype.base.descr,
+            'shape': (nitems,) + dtype.shape,
+            'strides': None,
+            'version': 3
         }
     return numpy.asarray(array_like()).view(dtype.base)
 
 ########################################################################
-# Handler that does callbacks from C++
-########################################################################
-class gateway_handler(gr.feval_ll):
-
-    #don't put a constructor, it won't work
-
-    def init(self, callback):
-        self._callback = callback
-
-    def eval(self, arg):
-        try: self._callback()
-        except Exception as ex:
-            print("handler caught exception: %s"%ex)
-            import traceback; traceback.print_exc()
-            raise ex
-        return 0
-
-########################################################################
-# Handler that does callbacks from C++
+# io_signature for Python
 ########################################################################
-class msg_handler(gr.feval_p):
-
-    #don't put a constructor, it won't work
-
-    def init(self, callback):
-        self._callback = callback
 
-    def eval(self, arg):
-        try: self._callback(arg)
-        except Exception as ex:
-            print("handler caught exception: %s"%ex)
-            import traceback; traceback.print_exc()
-            raise ex
-        return 0
 
-########################################################################
-# io_signature for Python
-########################################################################
 class py_io_signature(object):
     """
     Describes the type/number of ports for block input or output.
@@ -94,15 +61,15 @@ class py_io_signature(object):
         """
         self.__min_ports = min_ports
         self.__max_ports = max_ports
-        self.__types = tuple( numpy.dtype(t) for t in type_list )
+        self.__types = tuple(numpy.dtype(t) for t in type_list)
 
     def gr_io_signature(self):
         """
         Make/return a gr.io_signature. A non-empty list of sizes is
         required, even if there are no ports.
         """
-        return io_signaturev(self.__min_ports, self.__max_ports,
-                             [t.itemsize for t in self.__types] or [0])
+        return io_signature.makev(self.__min_ports, self.__max_ports,
+                                  [t.itemsize for t in self.__types] or [0])
 
     def port_types(self, nports):
         """
@@ -129,14 +96,19 @@ class py_io_signature(object):
 ########################################################################
 # The guts that make this into a gr block
 ########################################################################
+
+
 class gateway_block(object):
 
-    def __init__(self, name, in_sig, out_sig, work_type, factor):
+    def __init__(self, name, in_sig, out_sig, block_type):
+        self._decim = 1
+        self._interp = 1
+        self._block_type = block_type
 
         # Normalize the many Python ways of saying 'nothing' to '()'
         in_sig = in_sig or ()
         out_sig = out_sig or ()
-
+        
         # Backward compatibility: array of type strings -> py_io_signature
         if type(in_sig) is py_io_signature:
             self.__in_sig = in_sig
@@ -145,98 +117,93 @@ class gateway_block(object):
         if type(out_sig) is py_io_signature:
             self.__out_sig = out_sig
         else:
-            self.__out_sig = py_io_signature(len(out_sig), len(out_sig), out_sig)
-
-        #create internal gateway block
-        self.__handler = gateway_handler()
-        self.__handler.init(self.__gr_block_handle)
-        self.__gateway = block_gateway(
-            self.__handler, name,
-            self.__in_sig.gr_io_signature(), self.__out_sig.gr_io_signature(),
-            work_type, factor)
-        self.__message = self.__gateway.block_message()
-
-        #dict to keep references to all message handlers
-        self.__msg_handlers = {}
-
-        #register block functions
-        prefix = 'block__'
-        for attr in [x for x in dir(self.__gateway) if x.startswith(prefix)]:
-            setattr(self, attr.replace(prefix, ''), getattr(self.__gateway, attr))
-        self.pop_msg_queue = lambda: gr.block_gw_pop_msg_queue_safe(self.__gateway)
+            self.__out_sig = py_io_signature(
+                len(out_sig), len(out_sig), out_sig)
+
+        self.gateway = block_gateway(
+            self, name, self.__in_sig.gr_io_signature(), self.__out_sig.gr_io_signature())
+
+        self.msg_handlers = {}
+
+    def __getattr__(self, name):
+        """
+        Pass-through member requests to the C++ object.
+        """
+        if not hasattr(self, "gateway"):
+            raise RuntimeError(
+                "{0}: invalid state -- did you forget to call {0}.__init__ in "
+                "a derived class?".format(self.__class__.__name__))
+        return getattr(self.gateway, name)
 
     def to_basic_block(self):
         """
         Makes this block connectable by hier/top block python
         """
-        return self.__gateway.to_basic_block()
+        return self.gateway.to_basic_block()
+    
+    def fixed_rate_noutput_to_ninput(self, noutput_items):
+        return int((noutput_items * self._decim / self._interp) + self.gateway.history() - 1)
 
-    def __gr_block_handle(self):
+    def handle_forecast(self, noutput_items, ninputs):
         """
-        Dispatch tasks according to the action type specified in the message.
+        This is the handler function for forecast calls from 
+        block_gateway in c++ across pybind11 wrappers
         """
+        return self.forecast(noutput_items, ninputs)
+        
+        # return ninput_items_required
 
-        if self.__message.action == gr.block_gw_message_type.ACTION_GENERAL_WORK:
-            # Actual number of inputs/output from scheduler
-            ninputs = len(self.__message.general_work_args_input_items)
-            noutputs = len(self.__message.general_work_args_output_items)
-            in_types = self.__in_sig.port_types(ninputs)
-            out_types = self.__out_sig.port_types(noutputs)
-            self.__message.general_work_args_return_value = self.general_work(
-
-                input_items=[pointer_to_ndarray(
-                    self.__message.general_work_args_input_items[i],
-                    in_types[i],
-                    self.__message.general_work_args_ninput_items[i]
-                ) for i in range(ninputs)],
-
-                output_items=[pointer_to_ndarray(
-                    self.__message.general_work_args_output_items[i],
-                    out_types[i],
-                    self.__message.general_work_args_noutput_items
-                ) for i in range(noutputs)],
-            )
-
-        elif self.__message.action == gr.block_gw_message_type.ACTION_WORK:
-            # Actual number of inputs/output from scheduler
-            ninputs = len(self.__message.work_args_input_items)
-            noutputs = len(self.__message.work_args_output_items)
-            in_types = self.__in_sig.port_types(ninputs)
-            out_types = self.__out_sig.port_types(noutputs)
-            self.__message.work_args_return_value = self.work(
-
-                input_items=[pointer_to_ndarray(
-                    self.__message.work_args_input_items[i],
-                    in_types[i],
-                    self.__message.work_args_ninput_items
-                ) for i in range(ninputs)],
-
-                output_items=[pointer_to_ndarray(
-                    self.__message.work_args_output_items[i],
-                    out_types[i],
-                    self.__message.work_args_noutput_items
-                ) for i in range(noutputs)],
-            )
-
-        elif self.__message.action == gr.block_gw_message_type.ACTION_FORECAST:
-            self.forecast(
-                noutput_items=self.__message.forecast_args_noutput_items,
-                ninput_items_required=self.__message.forecast_args_ninput_items_required,
-            )
-
-        elif self.__message.action == gr.block_gw_message_type.ACTION_START:
-            self.__message.start_args_return_value = self.start()
-
-        elif self.__message.action == gr.block_gw_message_type.ACTION_STOP:
-            self.__message.stop_args_return_value = self.stop()
-
-    def forecast(self, noutput_items, ninput_items_required):
+    def forecast(self, noutput_items, ninputs):
         """
         forecast is only called from a general block
         this is the default implementation
         """
-        for i in range(len(ninput_items_required)):
-            ninput_items_required[i] = noutput_items + self.history() - 1
+        ninput_items_required = [0]*ninputs
+        for i in range(ninputs):
+            ninput_items_required[i] = noutput_items + self.gateway.history() - 1
+
+        return ninput_items_required
+
+    def handle_general_work(self, noutput_items, 
+                     ninput_items,
+                     input_items,
+                     output_items):
+
+        ninputs = len(input_items)
+        noutputs = len(output_items)
+        in_types = self.in_sig().port_types(ninputs)
+        out_types = self.out_sig().port_types(noutputs)
+
+        
+        ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
+        ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p]
+
+        if self._block_type != gr.GW_BLOCK_GENERAL:
+            ii=[pointer_to_ndarray(
+                ctypes.pythonapi.PyCapsule_GetPointer(input_items[i],None),
+                in_types[i],
+                self.fixed_rate_noutput_to_ninput(noutput_items)
+            ) for i in range(ninputs)]
+        else:
+            ii=[pointer_to_ndarray(
+                ctypes.pythonapi.PyCapsule_GetPointer(input_items[i],None),
+                in_types[i],
+                ninput_items[i]
+            ) for i in range(ninputs)]
+
+        oo=[pointer_to_ndarray(
+            ctypes.pythonapi.PyCapsule_GetPointer(output_items[i],None),
+            out_types[i],
+            noutput_items
+        ) for i in range(noutputs)]   
+
+        if self._block_type != gr.GW_BLOCK_GENERAL:
+            r = self.work(ii,oo)
+            self.consume_items(r)
+        else:
+            r = self.general_work(ii,oo)
+
+        return r
 
     def general_work(self, *args, **kwargs):
         """general work to be overloaded in a derived class"""
@@ -252,25 +219,43 @@ class gateway_block(object):
     def stop(self):
         return True
 
-    def set_msg_handler(self, which_port, handler_func):
-        handler = msg_handler()
-        handler.init(handler_func)
-        self.__gateway.set_msg_handler_feval(which_port, handler)
-        # Save handler object in class so it's not garbage collected
-        self.__msg_handlers[which_port] = handler
-
     def in_sig(self):
         return self.__in_sig
 
     def out_sig(self):
         return self.__out_sig
 
+    def set_msg_handler(self, which_port, handler_function):
+        self.gateway.set_msg_handler_pybind(which_port, handler_function.__name__)
+        # Save handler object in class so it's not garbage collected
+        self.msg_handlers[which_port] = handler_function
+
+    # TODO: Make gateway_block have an is-a instead of has-a relationship
+    #  That way we don't have to wrap all these functions
+
+    # def message_port_register_in(self, port_id):
+    #     return self.gateway.message_port_register_in(port_id)
+
+    # def message_port_register_out(self, port_id):
+    #     return self.gateway.message_port_register_out(port_id)
+
+    # def consume_each(self, how_many_items):
+    #     return self.gateway.consume_each( how_many_items)
+
+    # def consume(self, which_input, how_many_items):
+    #     return self.gateway.consume(which_input, how_many_items)
+
+    # def produce(self, which_output, how_many_items):
+    #     return self.gateway.produce(which_output, how_many_items)
+
+
 
 ########################################################################
 # Wrappers for the user to inherit from
 ########################################################################
-class basic_block(gateway_block):
-    """Args:
+class basic_block(gateway_block):   
+    """
+    Args:
     name (str): block name
 
     in_sig (gr.py_io_signature): input port signature
@@ -279,18 +264,19 @@ class basic_block(gateway_block):
 
     For backward compatibility, a sequence of numpy type names is also
     accepted as an io signature.
-
     """
     def __init__(self, name, in_sig, out_sig):
         gateway_block.__init__(self,
-            name=name,
-            in_sig=in_sig,
-            out_sig=out_sig,
-            work_type=gr.GR_BLOCK_GW_WORK_GENERAL,
-            factor=1, #not relevant factor
-        )
-
-class sync_block(gateway_block):
+                               name=name,
+                               in_sig=in_sig,
+                               out_sig=out_sig,
+                               block_type=gr.GW_BLOCK_GENERAL
+                               )
+
+    def consume_items(self, nitems ):
+        pass
+
+class sync_block(gateway_block):   
     """
     Args:
     name (str): block name
@@ -304,12 +290,18 @@ class sync_block(gateway_block):
     """
     def __init__(self, name, in_sig, out_sig):
         gateway_block.__init__(self,
-            name=name,
-            in_sig=in_sig,
-            out_sig=out_sig,
-            work_type=gr.GR_BLOCK_GW_WORK_SYNC,
-            factor=1,
-        )
+                               name=name,
+                               in_sig=in_sig,
+                               out_sig=out_sig,
+                               block_type=gr.GW_BLOCK_SYNC
+                               )
+        self._decim = 1
+        self._interp = 1
+
+    def consume_items(self, nitems ):
+        if (nitems > 0):
+            self.gateway.consume_each(nitems)
+
 
 class decim_block(gateway_block):
     """
@@ -325,12 +317,24 @@ class decim_block(gateway_block):
     """
     def __init__(self, name, in_sig, out_sig, decim):
         gateway_block.__init__(self,
-            name=name,
-            in_sig=in_sig,
-            out_sig=out_sig,
-            work_type=gr.GR_BLOCK_GW_WORK_DECIM,
-            factor=decim,
-        )
+                               name=name,
+                               in_sig=in_sig,
+                               out_sig=out_sig,
+                               block_type=gr.GW_BLOCK_DECIM
+                               )
+        self._decim = decim
+        self._interp = 1
+        self.gateway.set_relative_rate(self._interp, self._decim)
+        self.gateway.set_output_multiple(self._interp)
+
+    def consume_items(self, nitems ):
+        if (nitems > 0):
+            self.gateway.consume_each(int(nitems * self._decim))
+
+    def forecast(self, noutput_items, ninputs):
+        return [self.fixed_rate_noutput_to_ninput(noutput_items) for ii in range(ninputs)]
+
+
 
 class interp_block(gateway_block):
     """
@@ -346,9 +350,22 @@ class interp_block(gateway_block):
     """
     def __init__(self, name, in_sig, out_sig, interp):
         gateway_block.__init__(self,
-            name=name,
-            in_sig=in_sig,
-            out_sig=out_sig,
-            work_type=gr.GR_BLOCK_GW_WORK_INTERP,
-            factor=interp,
-        )
+                               name=name,
+                               in_sig=in_sig,
+                               out_sig=out_sig,
+                               block_type=gr.GW_BLOCK_DECIM
+                               )
+        self._decim = 1
+        self._interp = interp
+        self.gateway.set_relative_rate(self._interp, self._decim)
+        self.gateway.set_output_multiple(self._interp)
+
+
+    def consume_items(self, nitems ):
+        if (nitems > 0):
+            self.gateway.consume_each(int(nitems / self._interp))
+
+    def forecast(self, noutput_items, ninputs):
+        # print("{},{},{}".format(noutput_items, ninputs, [self.fixed_rate_noutput_to_ninput(noutput_items)  for ii in range(ninputs)]))
+
+        return [self.fixed_rate_noutput_to_ninput(noutput_items) for ii in range(ninputs)]
-- 
cgit v1.2.3