From 7f2e740dd49068473dd2386a08fe3c88137078e8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 9 Jun 2018 10:32:26 +0200 Subject: [PATCH] WIP: some refactoring of pya to reduce dependency on GSI for bridge applications. --- src/pya/pya/pya.h | 42 +--- src/pya/pya/pya.pro | 8 +- src/pya/pya/pyaConvert.cc | 8 + src/pya/pya/pyaConvert.h | 12 +- src/pya/pya/pyaHelpers.cc | 1 + src/pya/pya/pyaModule.cc | 2 + src/pya/pya/pyaModule.h | 16 +- src/pya/pya/pyaObject.cc | 277 +++++----------------- src/pya/pya/pyaObject.h | 177 ++------------ src/pya/pya/pyaSignalHandler.cc | 219 +++++++++++++++++ src/pya/pya/pyaSignalHandler.h | 108 +++++++++ src/pya/pya/pyaStatusChangedListener.cc | 49 ++++ src/pya/pya/pyaStatusChangedListener.h | 64 +++++ src/pya/pya/pyaUtils.h | 38 +++ src/pymod/bridge_sample/bridge_sample.pro | 6 +- src/pymod/pymodHelper.h | 3 + 16 files changed, 605 insertions(+), 425 deletions(-) create mode 100644 src/pya/pya/pyaSignalHandler.cc create mode 100644 src/pya/pya/pyaSignalHandler.h create mode 100644 src/pya/pya/pyaStatusChangedListener.cc create mode 100644 src/pya/pya/pyaStatusChangedListener.h diff --git a/src/pya/pya/pya.h b/src/pya/pya/pya.h index 7f04af740..52d3033f3 100644 --- a/src/pya/pya/pya.h +++ b/src/pya/pya/pya.h @@ -20,16 +20,18 @@ */ +/** + * @brief This header provides the definitions for embedding support + */ #ifndef _HDR_pya #define _HDR_pya #include "pyaRefs.h" +#include "pyaCommon.h" -#include "gsi.h" #include "gsiInterpreter.h" #include "tlScriptError.h" -#include "pyaCommon.h" #include #include @@ -68,42 +70,6 @@ namespace pya throw; \ } -/** - * Two helper macros that translate C++ exceptions into Python errors - */ - -#define PYA_TRY \ - { \ - try { - -#define PYA_CATCH(where) \ - } catch (tl::ExitException &ex) { \ - PyErr_SetObject (PyExc_SystemExit, PyLong_FromLong (ex.status ())); \ - } catch (std::exception &ex) { \ - std::string msg = std::string(ex.what ()) + tl::to_string (QObject::tr (" in ")) + (where); \ - PyErr_SetString (PyExc_RuntimeError, msg.c_str ()); \ - } catch (tl::Exception &ex) { \ - std::string msg; \ - msg = ex.msg () + tl::to_string (QObject::tr (" in ")) + (where); \ - PyErr_SetString (PyExc_RuntimeError, msg.c_str ()); \ - } catch (...) { \ - std::string msg = tl::to_string (QObject::tr ("Unspecific exception in ")) + (where); \ - PyErr_SetString (PyExc_RuntimeError, msg.c_str ()); \ - } \ - } - -#define PYA_CATCH_ANYWHERE \ - } catch (tl::ExitException &ex) { \ - PyErr_SetObject (PyExc_SystemExit, PyLong_FromLong (ex.status ())); \ - } catch (std::exception &ex) { \ - PyErr_SetString (PyExc_RuntimeError, ex.what ()); \ - } catch (tl::Exception &ex) { \ - PyErr_SetString (PyExc_RuntimeError, ex.msg ().c_str ()); \ - } catch (...) { \ - PyErr_SetString (PyExc_RuntimeError, tl::to_string (QObject::tr ("Unspecific exception in ")).c_str ()); \ - } \ - } - /** * @brief A class encapsulating a python exception */ diff --git a/src/pya/pya/pya.pro b/src/pya/pya/pya.pro index e6db43679..4ca438b7e 100644 --- a/src/pya/pya/pya.pro +++ b/src/pya/pya/pya.pro @@ -15,7 +15,9 @@ SOURCES = \ pyaObject.cc \ pyaRefs.cc \ pyaUtils.cc \ - pyaModule.cc + pyaModule.cc \ + pyaSignalHandler.cc \ + pyaStatusChangedListener.cc HEADERS += \ pya.h \ @@ -27,7 +29,9 @@ HEADERS += \ pyaObject.h \ pyaRefs.h \ pyaUtils.h \ - pyaModule.h + pyaModule.h \ + pyaSignalHandler.h \ + pyaStatusChangedListener.h INCLUDEPATH += $$PYTHONINCLUDE $$TL_INC $$GSI_INC DEPENDPATH += $$PYTHONINCLUDE $$TL_INC $$GSI_INC diff --git a/src/pya/pya/pyaConvert.cc b/src/pya/pya/pyaConvert.cc index b99208f67..e93a1d059 100644 --- a/src/pya/pya/pyaConvert.cc +++ b/src/pya/pya/pyaConvert.cc @@ -24,13 +24,21 @@ #include "pyaConvert.h" #include "pyaObject.h" #include "pyaModule.h" +#include "pyaStatusChangedListener.h" #include "pyaUtils.h" +#include "gsiClassBase.h" + #include namespace pya { +bool is_derived_from (const gsi::ClassBase *cls, const std::type_info &ti) +{ + return cls->is_derived_from (gsi::class_by_typeinfo_no_assert (ti)); +} + template <> long python2c_func::operator() (PyObject *rval) { diff --git a/src/pya/pya/pyaConvert.h b/src/pya/pya/pyaConvert.h index ea1b0217d..ac62e1bae 100644 --- a/src/pya/pya/pyaConvert.h +++ b/src/pya/pya/pyaConvert.h @@ -29,7 +29,6 @@ #include "pyaCommon.h" #include "pyaModule.h" #include "pyaObject.h" -#include "gsiClassBase.h" #include "tlVariant.h" #include "tlException.h" @@ -39,10 +38,14 @@ #include #include +#include + namespace gsi { class ClassBase; class ArgType; + + const ClassBase *class_by_typeinfo_no_assert (const std::type_info &ti); } namespace pya @@ -50,6 +53,9 @@ namespace pya class PYAObjectBase; +// Forward declarations to reduce the dependency on gsi +PYA_PUBLIC bool is_derived_from (const gsi::ClassBase *cls, const std::type_info &ti); + // ------------------------------------------------------------------- // Conversion of a generic object to a Python object @@ -196,7 +202,7 @@ struct test_type_func { // TODO: we currently don't check for non-constness const gsi::ClassBase *cls_decl = pya::PythonModule::cls_for_type (Py_TYPE (rval)); - return cls_decl && cls_decl->is_derived_from (gsi::class_by_typeinfo_no_assert (typeid (T))); + return cls_decl && is_derived_from (cls_decl, typeid (T)); } }; @@ -338,7 +344,7 @@ template struct python2c_func const gsi::ClassBase *cls_decl = PythonModule::cls_for_type (Py_TYPE (rval)); tl_assert (cls_decl != 0); - tl_assert (cls_decl->is_derived_from (gsi::class_by_typeinfo_no_assert (typeid (T)))); + tl_assert (is_derived_from (cls_decl, typeid (T))); PYAObjectBase *p = (PYAObjectBase *) (rval); return *((T *)p->obj ()); diff --git a/src/pya/pya/pyaHelpers.cc b/src/pya/pya/pyaHelpers.cc index 58ab8404f..ff9be7289 100644 --- a/src/pya/pya/pyaHelpers.cc +++ b/src/pya/pya/pyaHelpers.cc @@ -26,6 +26,7 @@ #include "pyaMarshal.h" #include "pyaObject.h" #include "pyaConvert.h" +#include "pyaSignalHandler.h" #include "pya.h" namespace pya diff --git a/src/pya/pya/pyaModule.cc b/src/pya/pya/pyaModule.cc index 2e3ae4db7..06c21d234 100644 --- a/src/pya/pya/pyaModule.cc +++ b/src/pya/pya/pyaModule.cc @@ -24,10 +24,12 @@ #include #include "pyaModule.h" +#include "pya.h" #include "pyaObject.h" #include "pyaConvert.h" #include "pyaHelpers.h" #include "pyaMarshal.h" +#include "pyaSignalHandler.h" #include "pyaUtils.h" #include diff --git a/src/pya/pya/pyaModule.h b/src/pya/pya/pyaModule.h index d68093d85..85515e7d6 100644 --- a/src/pya/pya/pyaModule.h +++ b/src/pya/pya/pyaModule.h @@ -24,7 +24,21 @@ #ifndef _HDR_pyaModule #define _HDR_pyaModule -#include "pya.h" +#include + +#include "pyaCommon.h" +#include "pyaRefs.h" + +#include +#include +#include +#include + +namespace gsi +{ + class ClassBase; + class MethodBase; +} namespace pya { diff --git a/src/pya/pya/pyaObject.cc b/src/pya/pya/pyaObject.cc index 938e40488..eb89da27f 100644 --- a/src/pya/pya/pyaObject.cc +++ b/src/pya/pya/pyaObject.cc @@ -25,91 +25,61 @@ #include "pyaMarshal.h" #include "pyaUtils.h" #include "pyaConvert.h" +#include "pyaSignalHandler.h" +#include "pyaStatusChangedListener.h" #include "pya.h" +#include "gsiDecl.h" +#include "gsiDeclBasic.h" +#include "gsiSignals.h" +#include "tlObject.h" + #include "tlLog.h" namespace pya { // -------------------------------------------------------------------------- -// Implementation of CallbackFunction +// Private classes -CallbackFunction::CallbackFunction (PythonRef pym, const gsi::MethodBase *m) - : mp_method (m) +/** + * @brief An adaptor class for the callback mechanism + */ +class Callee + : public gsi::Callee { - // We have a problem here with cyclic references. Bound instances methods can - // create reference cycles if their target objects somehow points back to us - // (or worse, to some parent of us, i.e. inside a QWidget hierarchy). - // A solution is to take a bound instance method apart and store a weak - // reference to self plus a real reference to the function. +public: + /** + * @brief Constructor for a Callee object pointing the to given Python object + */ + Callee (PYAObjectBase *obj); - if (pym && PyMethod_Check (pym.get ()) && PyMethod_Self (pym.get ()) != NULL) { + /** + * @brief Destructor + */ + ~Callee (); - m_weak_self = PythonRef (PyWeakref_NewRef (PyMethod_Self (pym.get ()), NULL)); - m_callable = PythonRef (PyMethod_Function (pym.get ()), false /* borrowed ref */); -#if PY_MAJOR_VERSION < 3 - m_class = PythonRef (PyMethod_Class (pym.get ()), false /* borrowed ref */); -#endif + /** + * @brief Adds a callback (given by the CallbackFunction) + * This method returns a callback ID which can be used to register the callback + * at an GSI object. + */ + int add_callback (const CallbackFunction &vf); - } else { - m_callable = pym; - } -} + /** + * @brief Clears all callbacks registered + */ + void clear_callbacks (); -const gsi::MethodBase *CallbackFunction::method () const -{ - return mp_method; -} + /** + * @brief Implementation of the Callee interface + */ + virtual void call (int id, gsi::SerialArgs &args, gsi::SerialArgs &ret) const; -PythonRef CallbackFunction::callable () const -{ - if (m_callable && m_weak_self) { - - PyObject *self = PyWeakref_GetObject (m_weak_self.get ()); - if (self == Py_None) { - // object expired - no callback possible - return PythonRef (); - } - -#if PY_MAJOR_VERSION < 3 - return PythonRef (PyMethod_New (m_callable.get (), self, m_class.get ())); -#else - return PythonRef (PyMethod_New (m_callable.get (), self)); -#endif - - } else { - return m_callable; - } -} - -bool CallbackFunction::is_instance_method () const -{ - return m_callable && m_weak_self; -} - -PyObject *CallbackFunction::self_ref () const -{ - return PyWeakref_GetObject (m_weak_self.get ()); -} - -PyObject *CallbackFunction::callable_ref () const -{ - return m_callable.get (); -} - -bool CallbackFunction::operator== (const CallbackFunction &other) const -{ - if (is_instance_method () != other.is_instance_method ()) { - return false; - } - if (m_weak_self) { - if (self_ref () != other.self_ref ()) { - return false; - } - } - return callable_ref () == other.callable_ref (); -} +private: + PYAObjectBase *mp_obj; + std::vector m_cbfuncs; +}; // -------------------------------------------------------------------------- // Implementation of Callee @@ -196,135 +166,12 @@ Callee::call (int id, gsi::SerialArgs &args, gsi::SerialArgs &ret) const } } -// -------------------------------------------------------------------------- -// Implementation of SignalHandler - -SignalHandler::SignalHandler () -{ - // .. nothing yet .. -} - -SignalHandler::~SignalHandler () -{ - clear (); -} - -void SignalHandler::call (const gsi::MethodBase *meth, gsi::SerialArgs &args, gsi::SerialArgs &ret) const -{ - PYTHON_BEGIN_EXEC - - tl::Heap heap; - - int args_avail = int (std::distance (meth->begin_arguments (), meth->end_arguments ())); - PythonRef argv (PyTuple_New (args_avail)); - for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) { - PyTuple_SetItem (argv.get (), int (a - meth->begin_arguments ()), pop_arg (*a, args, 0, heap).release ()); - } - - // NOTE: in case one event handler deletes the object, it's safer to first collect the handlers and - // then call them. - std::vector callables; - callables.reserve (m_cbfuncs.size ()); - for (std::vector::const_iterator c = m_cbfuncs.begin (); c != m_cbfuncs.end (); ++c) { - callables.push_back (c->callable ()); - } - - PythonRef result; - - for (std::vector::const_iterator c = callables.begin (); c != callables.end (); ++c) { - - // determine the number of arguments required - int arg_count = args_avail; - if (args_avail > 0) { - - PythonRef fc (PyObject_GetAttrString (c->get (), "__code__")); - if (fc) { - PythonRef ac (PyObject_GetAttrString (fc.get (), "co_argcount")); - if (ac) { - arg_count = python2c (ac.get ()); - if (PyObject_HasAttrString (c->get (), "__self__")) { - arg_count -= 1; - } - } - } - - } - - // use less arguments if applicable - if (arg_count == 0) { - result = PythonRef (PyObject_CallObject (c->get (), NULL)); - } else if (arg_count < args_avail) { - PythonRef argv_less (PyTuple_GetSlice (argv.get (), 0, arg_count)); - result = PythonRef (PyObject_CallObject (c->get (), argv_less.get ())); - } else { - result = PythonRef (PyObject_CallObject (c->get (), argv.get ())); - } - - if (! result) { - check_error (); - } - - } - - push_arg (meth->ret_type (), ret, result.get (), heap); - - // a Python callback must not leave temporary objects - tl_assert (heap.empty ()); - - PYTHON_END_EXEC -} - -void SignalHandler::add (PyObject *callable) -{ - remove (callable); - m_cbfuncs.push_back (CallbackFunction (PythonPtr (callable), 0)); -} - -void SignalHandler::remove (PyObject *callable) -{ - // To avoid cyclic references, the CallbackFunction holder is employed. However, the - // "true" callable no longer is the original one. Hence, we need to do a strict compare - // against the effective one. - CallbackFunction cbref (PythonPtr (callable), 0); - for (std::vector::iterator c = m_cbfuncs.begin (); c != m_cbfuncs.end (); ++c) { - if (*c == cbref) { - m_cbfuncs.erase (c); - break; - } - } -} - -void SignalHandler::clear () -{ - m_cbfuncs.clear (); -} - -void SignalHandler::assign (const SignalHandler *other) -{ - m_cbfuncs = other->m_cbfuncs; -} - -// -------------------------------------------------------------------------- -// Implementation of StatusChangedListener - -StatusChangedListener::StatusChangedListener (PYAObjectBase *pya_object) - : mp_pya_object (pya_object) -{ - // .. nothing yet .. -} - -void -StatusChangedListener::object_status_changed (gsi::ObjectBase::StatusEventType type) -{ - mp_pya_object->object_status_changed (type); -} - // -------------------------------------------------------------------------- // Implementation of PYAObjectBase PYAObjectBase::PYAObjectBase(const gsi::ClassBase *_cls_decl) - : m_listener (this), - m_callee (this), + : m_listener (new pya::StatusChangedListener (this)), + m_callee (new pya::Callee (this)), m_cls_decl (_cls_decl), m_obj (0), m_owned (false), @@ -361,32 +208,24 @@ PYAObjectBase::~PYAObjectBase () } void -PYAObjectBase::object_status_changed (gsi::ObjectBase::StatusEventType type) +PYAObjectBase::object_destroyed () { - if (type == gsi::ObjectBase::ObjectDestroyed) { + // This may happen outside the Python interpreter, so we safeguard ourselves against this. + // In this case, we may encounter a memory leak, but there is little we can do + // against this and it will happen in the application teardown anyway. + if (PythonInterpreter::instance ()) { - // This may happen outside the Python interpreter, so we safeguard ourselves against this. - // In this case, we may encounter a memory leak, but there is little we can do - // against this and it will happen in the application teardown anyway. - if (PythonInterpreter::instance ()) { + bool prev_owner = m_owned; - bool prev_owner = m_owned; + m_destroyed = true; // NOTE: must be set before detach! - m_destroyed = true; // NOTE: must be set before detach! - - detach (); - - // NOTE: this may delete "this"! - if (!prev_owner) { - Py_DECREF (this); - } + detach (); + // NOTE: this may delete "this"! + if (!prev_owner) { + Py_DECREF (this); } - } else if (type == gsi::ObjectBase::ObjectKeep) { - keep_internal (); - } else if (type == gsi::ObjectBase::ObjectRelease) { - release (); } } @@ -446,7 +285,7 @@ PYAObjectBase::detach () if (! m_destroyed && cls && cls->is_managed ()) { gsi::ObjectBase *gsi_object = cls->gsi_object (m_obj, false); if (gsi_object) { - gsi_object->status_changed_event ().remove (&m_listener, &StatusChangedListener::object_status_changed); + gsi_object->status_changed_event ().remove (m_listener.get (), &StatusChangedListener::object_status_changed); } } @@ -485,7 +324,7 @@ PYAObjectBase::set (void *obj, bool owned, bool const_ref, bool can_destroy) if (gsi_object->already_kept ()) { keep_internal (); } - gsi_object->status_changed_event ().add (&m_listener, &StatusChangedListener::object_status_changed); + gsi_object->status_changed_event ().add (m_listener.get (), &StatusChangedListener::object_status_changed); } if (!m_owned) { @@ -586,8 +425,8 @@ PYAObjectBase::initialize_callbacks () const char *nstr = (*m)->primary_name ().c_str (); py_attr = PyObject_GetAttrString ((PyObject *) Py_TYPE (this), nstr); - int id = m_callee.add_callback (CallbackFunction (py_attr, *m)); - (*m)->set_callback (m_obj, gsi::Callback (id, &m_callee, (*m)->argsize (), (*m)->retsize ())); + int id = m_callee->add_callback (CallbackFunction (py_attr, *m)); + (*m)->set_callback (m_obj, gsi::Callback (id, m_callee.get (), (*m)->argsize (), (*m)->retsize ())); } @@ -671,7 +510,7 @@ PYAObjectBase::detach_callbacks () } } - m_callee.clear_callbacks (); + m_callee->clear_callbacks (); } void diff --git a/src/pya/pya/pyaObject.h b/src/pya/pya/pyaObject.h index 78904abd8..fa0023f8b 100644 --- a/src/pya/pya/pyaObject.h +++ b/src/pya/pya/pyaObject.h @@ -24,152 +24,28 @@ #ifndef _HDR_pyaObject #define _HDR_pyaObject -#include "Python.h" - -#include "gsiDecl.h" -#include "gsiDeclBasic.h" -#include "gsiSignals.h" -#include "tlObject.h" +#include #include "pyaRefs.h" #include "pyaCommon.h" +#include +#include +#include + +namespace gsi +{ + class ClassBase; + class MethodBase; +} + namespace pya { class PYAObjectBase; - -/** - * @brief A storage object for a function to callback - */ -struct CallbackFunction -{ - CallbackFunction (PythonRef pym, const gsi::MethodBase *m); - - PythonRef callable () const; - const gsi::MethodBase *method () const; - bool operator== (const CallbackFunction &other) const; - -private: - PythonRef m_callable; - PythonRef m_weak_self; - PythonRef m_class; - const gsi::MethodBase *mp_method; - - PyObject *self_ref () const; - PyObject *callable_ref () const; - bool is_instance_method () const; -}; - -/** - * @brief An adaptor class for the callback mechanism - */ -class Callee - : public gsi::Callee -{ -public: - /** - * @brief Constructor for a Callee object pointing the to given Python object - */ - Callee (PYAObjectBase *obj); - - /** - * @brief Destructor - */ - ~Callee (); - - /** - * @brief Adds a callback (given by the CallbackFunction) - * This method returns a callback ID which can be used to register the callback - * at an GSI object. - */ - int add_callback (const CallbackFunction &vf); - - /** - * @brief Clears all callbacks registered - */ - void clear_callbacks (); - - /** - * @brief Implementation of the Callee interface - */ - virtual void call (int id, gsi::SerialArgs &args, gsi::SerialArgs &ret) const; - -private: - PYAObjectBase *mp_obj; - std::vector m_cbfuncs; -}; - -/** - * @brief The signal handler abstraction - * - * This class implements the signal handler that interfaces to GSI's signal system - */ -class SignalHandler - : public gsi::SignalHandler -{ -public: - /** - * @brief Constructor - */ - SignalHandler (); - - /** - * @brief Destructor - */ - ~SignalHandler (); - - /** - * @brief Implementation of the callback interface - */ - virtual void call (const gsi::MethodBase *method, gsi::SerialArgs &args, gsi::SerialArgs &ret) const; - - /** - * @brief Adds a callable to the list of targets - */ - void add (PyObject *callable); - - /** - * @brief Removes a callable from the list of targets - */ - void remove (PyObject *callable); - - /** - * @brief Clears the list of callables - */ - void clear (); - - /** - * @brief Assign another handler to this - */ - void assign (const SignalHandler *other); - -private: - std::vector m_cbfuncs; -}; - -/** - * @brief A helper object to forward status changed events to a Python object - * This object is used to connect the events to the Python object. Unfortunately, - * PYAObjectBase cannot be derived from tl::Object directly since in that case, - * tl::Object will be placed before PyObject in the memory layout. - */ -class StatusChangedListener - : public tl::Object -{ -public: - StatusChangedListener (PYAObjectBase *pya_object); - - void object_status_changed (gsi::ObjectBase::StatusEventType type); - - PYAObjectBase *pya_object () const - { - return mp_pya_object; - } - -private: - PYAObjectBase *mp_pya_object; -}; +class SignalHandler; +class Callee; +class StatusChangedListener; /** * @brief The Python object representing a GSI object @@ -290,22 +166,6 @@ public: return m_owned; } - /** - * @brief The callee interface - */ - Callee &callee () - { - return m_callee; - } - - /** - * @brief The callee interface (const pointer) - */ - const Callee &callee () const - { - return m_callee; - } - /** * @brief Returns the signal handler for the signal given by "meth" * If a signal handler was already present, the existing object is returned. @@ -326,9 +186,11 @@ private: void detach_callbacks (); void initialize_callbacks (); + void object_destroyed (); + void keep_internal (); - StatusChangedListener m_listener; - Callee m_callee; + std::auto_ptr m_listener; + std::auto_ptr m_callee; const gsi::ClassBase *m_cls_decl; void *m_obj; bool m_owned : 1; @@ -336,9 +198,6 @@ private: bool m_destroyed : 1; bool m_can_destroy : 1; std::map m_signal_table; - - void object_status_changed (gsi::ObjectBase::StatusEventType type); - void keep_internal (); }; } diff --git a/src/pya/pya/pyaSignalHandler.cc b/src/pya/pya/pyaSignalHandler.cc new file mode 100644 index 000000000..88b7d9651 --- /dev/null +++ b/src/pya/pya/pyaSignalHandler.cc @@ -0,0 +1,219 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "pyaSignalHandler.h" +#include "pya.h" +#include "pyaMarshal.h" +#include "pyaConvert.h" +#include "pyaUtils.h" + +namespace pya +{ + +// -------------------------------------------------------------------------- +// Implementation of CallbackFunction + +CallbackFunction::CallbackFunction (PythonRef pym, const gsi::MethodBase *m) + : mp_method (m) +{ + // We have a problem here with cyclic references. Bound instances methods can + // create reference cycles if their target objects somehow points back to us + // (or worse, to some parent of us, i.e. inside a QWidget hierarchy). + // A solution is to take a bound instance method apart and store a weak + // reference to self plus a real reference to the function. + + if (pym && PyMethod_Check (pym.get ()) && PyMethod_Self (pym.get ()) != NULL) { + + m_weak_self = PythonRef (PyWeakref_NewRef (PyMethod_Self (pym.get ()), NULL)); + m_callable = PythonRef (PyMethod_Function (pym.get ()), false /* borrowed ref */); +#if PY_MAJOR_VERSION < 3 + m_class = PythonRef (PyMethod_Class (pym.get ()), false /* borrowed ref */); +#endif + + } else { + m_callable = pym; + } +} + +const gsi::MethodBase *CallbackFunction::method () const +{ + return mp_method; +} + +PythonRef CallbackFunction::callable () const +{ + if (m_callable && m_weak_self) { + + PyObject *self = PyWeakref_GetObject (m_weak_self.get ()); + if (self == Py_None) { + // object expired - no callback possible + return PythonRef (); + } + +#if PY_MAJOR_VERSION < 3 + return PythonRef (PyMethod_New (m_callable.get (), self, m_class.get ())); +#else + return PythonRef (PyMethod_New (m_callable.get (), self)); +#endif + + } else { + return m_callable; + } +} + +bool CallbackFunction::is_instance_method () const +{ + return m_callable && m_weak_self; +} + +PyObject *CallbackFunction::self_ref () const +{ + return PyWeakref_GetObject (m_weak_self.get ()); +} + +PyObject *CallbackFunction::callable_ref () const +{ + return m_callable.get (); +} + +bool CallbackFunction::operator== (const CallbackFunction &other) const +{ + if (is_instance_method () != other.is_instance_method ()) { + return false; + } + if (m_weak_self) { + if (self_ref () != other.self_ref ()) { + return false; + } + } + return callable_ref () == other.callable_ref (); +} + +// -------------------------------------------------------------------------- +// Implementation of SignalHandler + +SignalHandler::SignalHandler () +{ + // .. nothing yet .. +} + +SignalHandler::~SignalHandler () +{ + clear (); +} + +void SignalHandler::call (const gsi::MethodBase *meth, gsi::SerialArgs &args, gsi::SerialArgs &ret) const +{ + PYTHON_BEGIN_EXEC + + tl::Heap heap; + + int args_avail = int (std::distance (meth->begin_arguments (), meth->end_arguments ())); + PythonRef argv (PyTuple_New (args_avail)); + for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) { + PyTuple_SetItem (argv.get (), int (a - meth->begin_arguments ()), pop_arg (*a, args, 0, heap).release ()); + } + + // NOTE: in case one event handler deletes the object, it's safer to first collect the handlers and + // then call them. + std::vector callables; + callables.reserve (m_cbfuncs.size ()); + for (std::vector::const_iterator c = m_cbfuncs.begin (); c != m_cbfuncs.end (); ++c) { + callables.push_back (c->callable ()); + } + + PythonRef result; + + for (std::vector::const_iterator c = callables.begin (); c != callables.end (); ++c) { + + // determine the number of arguments required + int arg_count = args_avail; + if (args_avail > 0) { + + PythonRef fc (PyObject_GetAttrString (c->get (), "__code__")); + if (fc) { + PythonRef ac (PyObject_GetAttrString (fc.get (), "co_argcount")); + if (ac) { + arg_count = python2c (ac.get ()); + if (PyObject_HasAttrString (c->get (), "__self__")) { + arg_count -= 1; + } + } + } + + } + + // use less arguments if applicable + if (arg_count == 0) { + result = PythonRef (PyObject_CallObject (c->get (), NULL)); + } else if (arg_count < args_avail) { + PythonRef argv_less (PyTuple_GetSlice (argv.get (), 0, arg_count)); + result = PythonRef (PyObject_CallObject (c->get (), argv_less.get ())); + } else { + result = PythonRef (PyObject_CallObject (c->get (), argv.get ())); + } + + if (! result) { + check_error (); + } + + } + + push_arg (meth->ret_type (), ret, result.get (), heap); + + // a Python callback must not leave temporary objects + tl_assert (heap.empty ()); + + PYTHON_END_EXEC +} + +void SignalHandler::add (PyObject *callable) +{ + remove (callable); + m_cbfuncs.push_back (CallbackFunction (PythonPtr (callable), 0)); +} + +void SignalHandler::remove (PyObject *callable) +{ + // To avoid cyclic references, the CallbackFunction holder is employed. However, the + // "true" callable no longer is the original one. Hence, we need to do a strict compare + // against the effective one. + CallbackFunction cbref (PythonPtr (callable), 0); + for (std::vector::iterator c = m_cbfuncs.begin (); c != m_cbfuncs.end (); ++c) { + if (*c == cbref) { + m_cbfuncs.erase (c); + break; + } + } +} + +void SignalHandler::clear () +{ + m_cbfuncs.clear (); +} + +void SignalHandler::assign (const SignalHandler *other) +{ + m_cbfuncs = other->m_cbfuncs; +} + +} diff --git a/src/pya/pya/pyaSignalHandler.h b/src/pya/pya/pyaSignalHandler.h new file mode 100644 index 000000000..751229ad0 --- /dev/null +++ b/src/pya/pya/pyaSignalHandler.h @@ -0,0 +1,108 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef _HDR_pyaSignalHandler +#define _HDR_pyaSignalHandler + +#include + +#include "pyaRefs.h" + +#include "gsiSignals.h" + +namespace pya +{ + +/** + * @brief A storage object for a function to callback + */ +struct CallbackFunction +{ + CallbackFunction (PythonRef pym, const gsi::MethodBase *m); + + PythonRef callable () const; + const gsi::MethodBase *method () const; + bool operator== (const CallbackFunction &other) const; + +private: + PythonRef m_callable; + PythonRef m_weak_self; + PythonRef m_class; + const gsi::MethodBase *mp_method; + + PyObject *self_ref () const; + PyObject *callable_ref () const; + bool is_instance_method () const; +}; + +/** + * @brief The signal handler abstraction + * + * This class implements the signal handler that interfaces to GSI's signal system + */ +class SignalHandler + : public gsi::SignalHandler +{ +public: + /** + * @brief Constructor + */ + SignalHandler (); + + /** + * @brief Destructor + */ + ~SignalHandler (); + + /** + * @brief Implementation of the callback interface + */ + virtual void call (const gsi::MethodBase *method, gsi::SerialArgs &args, gsi::SerialArgs &ret) const; + + /** + * @brief Adds a callable to the list of targets + */ + void add (PyObject *callable); + + /** + * @brief Removes a callable from the list of targets + */ + void remove (PyObject *callable); + + /** + * @brief Clears the list of callables + */ + void clear (); + + /** + * @brief Assign another handler to this + */ + void assign (const SignalHandler *other); + +private: + std::vector m_cbfuncs; +}; + +} + +#endif diff --git a/src/pya/pya/pyaStatusChangedListener.cc b/src/pya/pya/pyaStatusChangedListener.cc new file mode 100644 index 000000000..37dcaed0e --- /dev/null +++ b/src/pya/pya/pyaStatusChangedListener.cc @@ -0,0 +1,49 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "pyaStatusChangedListener.h" +#include "pyaObject.h" + +namespace pya +{ +// -------------------------------------------------------------------------- +// Implementation of StatusChangedListener + +StatusChangedListener::StatusChangedListener (PYAObjectBase *pya_object) + : mp_pya_object (pya_object) +{ + // .. nothing yet .. +} + +void +StatusChangedListener::object_status_changed (gsi::ObjectBase::StatusEventType type) +{ + if (type == gsi::ObjectBase::ObjectDestroyed) { + mp_pya_object->object_destroyed (); + } else if (type == gsi::ObjectBase::ObjectKeep) { + mp_pya_object->keep_internal (); + } else if (type == gsi::ObjectBase::ObjectRelease) { + mp_pya_object->release (); + } +} + +} diff --git a/src/pya/pya/pyaStatusChangedListener.h b/src/pya/pya/pyaStatusChangedListener.h new file mode 100644 index 000000000..6b781acdb --- /dev/null +++ b/src/pya/pya/pyaStatusChangedListener.h @@ -0,0 +1,64 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef _HDR_pyaStatusChangedListener +#define _HDR_pyaStatusChangedListener + +#include + +#include "pyaRefs.h" + +#include "gsiSignals.h" +#include "gsiObject.h" + +namespace pya +{ + +class PYAObjectBase; + +/** + * @brief A helper object to forward status changed events to a Python object + * This object is used to connect the events to the Python object. Unfortunately, + * PYAObjectBase cannot be derived from tl::Object directly since in that case, + * tl::Object will be placed before PyObject in the memory layout. + */ +class StatusChangedListener + : public tl::Object +{ +public: + StatusChangedListener (PYAObjectBase *pya_object); + + void object_status_changed (gsi::ObjectBase::StatusEventType type); + + PYAObjectBase *pya_object () const + { + return mp_pya_object; + } + +private: + PYAObjectBase *mp_pya_object; +}; + +} + +#endif diff --git a/src/pya/pya/pyaUtils.h b/src/pya/pya/pyaUtils.h index eef23c8ff..a1be141ce 100644 --- a/src/pya/pya/pyaUtils.h +++ b/src/pya/pya/pyaUtils.h @@ -24,9 +24,47 @@ #ifndef _HDR_pyaUtils #define _HDR_pyaUtils +#include "tlScriptError.h" + namespace pya { +/** + * Some helper macros that translate C++ exceptions into Python errors + */ + +#define PYA_TRY \ + { \ + try { + +#define PYA_CATCH(where) \ + } catch (tl::ExitException &ex) { \ + PyErr_SetObject (PyExc_SystemExit, PyLong_FromLong (ex.status ())); \ + } catch (std::exception &ex) { \ + std::string msg = std::string(ex.what ()) + tl::to_string (QObject::tr (" in ")) + (where); \ + PyErr_SetString (PyExc_RuntimeError, msg.c_str ()); \ + } catch (tl::Exception &ex) { \ + std::string msg; \ + msg = ex.msg () + tl::to_string (QObject::tr (" in ")) + (where); \ + PyErr_SetString (PyExc_RuntimeError, msg.c_str ()); \ + } catch (...) { \ + std::string msg = tl::to_string (QObject::tr ("Unspecific exception in ")) + (where); \ + PyErr_SetString (PyExc_RuntimeError, msg.c_str ()); \ + } \ + } + +#define PYA_CATCH_ANYWHERE \ + } catch (tl::ExitException &ex) { \ + PyErr_SetObject (PyExc_SystemExit, PyLong_FromLong (ex.status ())); \ + } catch (std::exception &ex) { \ + PyErr_SetString (PyExc_RuntimeError, ex.what ()); \ + } catch (tl::Exception &ex) { \ + PyErr_SetString (PyExc_RuntimeError, ex.msg ().c_str ()); \ + } catch (...) { \ + PyErr_SetString (PyExc_RuntimeError, tl::to_string (QObject::tr ("Unspecific exception in ")).c_str ()); \ + } \ + } + /** * @brief Turn Python errors into C++ exceptions */ diff --git a/src/pymod/bridge_sample/bridge_sample.pro b/src/pymod/bridge_sample/bridge_sample.pro index a5f8377e6..cebbf3217 100644 --- a/src/pymod/bridge_sample/bridge_sample.pro +++ b/src/pymod/bridge_sample/bridge_sample.pro @@ -49,9 +49,9 @@ QT = core # - GSI (generic scripting interface) # - TL (basic toolkit) # - PYA (Python binding for GSI) -INCLUDEPATH += $$PYTHONINCLUDE $$INC/tl/tl $$INC/gsi/gsi $$INC/pya/pya -DEPENDPATH += $$PYTHONINCLUDE $$INC/tl/tl $$INC/gsi/gsi $$INC/pya/pya -LIBS += $$PYTHONLIBFILE -L$$LIBDIR -lklayout_tl -lklayout_gsi -lklayout_pya +INCLUDEPATH += $$PYTHONINCLUDE $$INC/tl/tl $$INC/pya/pya +DEPENDPATH += $$PYTHONINCLUDE $$INC/tl/tl $$INC/pya/pya +LIBS += $$PYTHONLIBFILE -L$$LIBDIR -lklayout_tl -lklayout_pya # Also include DB as this is our sample INCLUDEPATH += $$INC/db/db diff --git a/src/pymod/pymodHelper.h b/src/pymod/pymodHelper.h index de87281d2..12f5f2fdc 100644 --- a/src/pymod/pymodHelper.h +++ b/src/pymod/pymodHelper.h @@ -31,7 +31,10 @@ */ #include + #include "pyaModule.h" +#include "pyaUtils.h" + #include "gsi.h" #include "gsiExpression.h"