mirror of https://github.com/KLayout/klayout.git
894 lines
21 KiB
C++
894 lines
21 KiB
C++
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2022 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
|
|
|
|
*/
|
|
|
|
|
|
#if defined(HAVE_RUBY)
|
|
|
|
#include "rba.h"
|
|
#include "rbaInternal.h"
|
|
#include "rbaUtils.h"
|
|
#include "rbaMarshal.h"
|
|
|
|
#include "tlLog.h"
|
|
|
|
namespace rba
|
|
{
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Implementation of the locked object vault
|
|
|
|
class LockedObjectVault
|
|
{
|
|
public:
|
|
static void init (VALUE module, const char *name);
|
|
|
|
LockedObjectVault ();
|
|
~LockedObjectVault ();
|
|
|
|
static LockedObjectVault *instance ()
|
|
{
|
|
return mp_instance;
|
|
}
|
|
|
|
void add (VALUE object);
|
|
void remove (VALUE object);
|
|
void mark_this ();
|
|
|
|
private:
|
|
std::map<VALUE, size_t> m_objects;
|
|
|
|
static VALUE m_klass;
|
|
static VALUE m_instance;
|
|
static LockedObjectVault *mp_instance;
|
|
|
|
static void free (void *p);
|
|
static void mark (void *p);
|
|
static VALUE alloc (VALUE klass);
|
|
};
|
|
|
|
VALUE LockedObjectVault::m_klass = Qnil;
|
|
VALUE LockedObjectVault::m_instance = Qnil;
|
|
LockedObjectVault *LockedObjectVault::mp_instance = 0;
|
|
|
|
LockedObjectVault::LockedObjectVault ()
|
|
{
|
|
mp_instance = this;
|
|
}
|
|
|
|
LockedObjectVault::~LockedObjectVault ()
|
|
{
|
|
if (mp_instance == this) {
|
|
mp_instance = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
LockedObjectVault::add (VALUE object)
|
|
{
|
|
auto i = m_objects.find (object);
|
|
if (i != m_objects.end ()) {
|
|
i->second += 1;
|
|
} else {
|
|
m_objects.insert (std::make_pair (object, size_t (1)));
|
|
}
|
|
}
|
|
|
|
void
|
|
LockedObjectVault::remove (VALUE object)
|
|
{
|
|
auto i = m_objects.find (object);
|
|
if (i != m_objects.end ()) {
|
|
i->second -= 1;
|
|
if (i->second == 0) {
|
|
m_objects.erase (i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LockedObjectVault::mark_this ()
|
|
{
|
|
for (auto o = m_objects.begin (); o != m_objects.end (); ++o) {
|
|
rb_gc_mark (o->first);
|
|
}
|
|
}
|
|
|
|
void
|
|
LockedObjectVault::mark (void *p)
|
|
{
|
|
((LockedObjectVault *) p)->mark_this ();
|
|
}
|
|
|
|
void
|
|
LockedObjectVault::free (void *p)
|
|
{
|
|
delete ((LockedObjectVault *) p);
|
|
}
|
|
|
|
VALUE
|
|
LockedObjectVault::alloc (VALUE klass)
|
|
{
|
|
tl_assert (TYPE (klass) == T_CLASS);
|
|
|
|
LockedObjectVault *vault = new LockedObjectVault ();
|
|
return Data_Wrap_Struct (klass, &LockedObjectVault::mark, &LockedObjectVault::free, vault);
|
|
}
|
|
|
|
void
|
|
LockedObjectVault::init (VALUE module, const char *name)
|
|
{
|
|
if (m_instance != Qnil) {
|
|
return;
|
|
}
|
|
|
|
m_klass = rb_define_class_under (module, name, rb_cObject);
|
|
rb_define_alloc_func (m_klass, LockedObjectVault::alloc);
|
|
|
|
m_instance = rba_class_new_instance_checked (0, 0, m_klass);
|
|
rb_gc_register_address (&m_instance);
|
|
}
|
|
|
|
void
|
|
make_locked_object_vault (VALUE module)
|
|
{
|
|
LockedObjectVault::init (module, "RBALockedObjectVault");
|
|
}
|
|
|
|
void
|
|
gc_lock_object (VALUE value)
|
|
{
|
|
if (LockedObjectVault::instance ()) {
|
|
LockedObjectVault::instance ()->add (value);
|
|
}
|
|
}
|
|
|
|
void
|
|
gc_unlock_object (VALUE value)
|
|
{
|
|
if (LockedObjectVault::instance ()) {
|
|
LockedObjectVault::instance ()->remove (value);
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Proxy implementation
|
|
|
|
Proxy::Proxy (const gsi::ClassBase *_cls_decl)
|
|
: m_cls_decl (_cls_decl),
|
|
m_obj (0),
|
|
m_owned (false),
|
|
m_const_ref (false),
|
|
m_destroyed (false),
|
|
m_can_destroy (false),
|
|
m_self (Qnil)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
Proxy::~Proxy ()
|
|
{
|
|
try {
|
|
reset ();
|
|
} catch (std::exception &ex) {
|
|
tl::warn << "Caught exception in object destructor: " << ex.what ();
|
|
} catch (tl::Exception &ex) {
|
|
tl::warn << "Caught exception in object destructor: " << ex.msg ();
|
|
} catch (...) {
|
|
tl::warn << "Caught unspecified exception in object destructor";
|
|
}
|
|
m_destroyed = true;
|
|
}
|
|
|
|
void
|
|
Proxy::mark ()
|
|
{
|
|
for (std::map <const gsi::MethodBase *, VALUE>::const_iterator pt = m_signal_handlers.begin (); pt != m_signal_handlers.end (); ++pt) {
|
|
rb_gc_mark (pt->second);
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
Proxy::signal_handler (const gsi::MethodBase *meth)
|
|
{
|
|
std::map <const gsi::MethodBase *, VALUE>::iterator pt = m_signal_handlers.find (meth);
|
|
if (pt != m_signal_handlers.end ()) {
|
|
return pt->second;
|
|
}
|
|
|
|
// end register the new one
|
|
VALUE args[] = { m_self };
|
|
VALUE sh = rba_class_new_instance_checked (1, args, SignalHandler::klass);
|
|
|
|
m_signal_handlers.insert (std::make_pair (meth, sh));
|
|
|
|
// install the handler
|
|
SignalHandler *sig_handler = 0;
|
|
Data_Get_Struct (sh, SignalHandler, sig_handler);
|
|
meth->add_handler (obj (), sig_handler);
|
|
|
|
return sh;
|
|
}
|
|
|
|
bool Proxy::can_call () const
|
|
{
|
|
return rba::RubyInterpreter::instance () != 0;
|
|
}
|
|
|
|
void
|
|
Proxy::call (int id, gsi::SerialArgs &args, gsi::SerialArgs &ret) const
|
|
{
|
|
tl_assert (id < int (m_cbfuncs.size ()) && id >= 0);
|
|
|
|
const gsi::MethodBase *meth = m_cbfuncs [id].method;
|
|
ID mid = m_cbfuncs [id].method_id;
|
|
|
|
try {
|
|
|
|
VALUE argv = rb_ary_new2 (long (std::distance (meth->begin_arguments (), meth->end_arguments ())));
|
|
RB_GC_GUARD (argv);
|
|
|
|
tl::Heap heap;
|
|
|
|
// TODO: callbacks with default arguments?
|
|
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) {
|
|
rb_ary_push (argv, pop_arg (*a, 0, args, heap));
|
|
}
|
|
|
|
VALUE rb_ret = rba_funcall2_checked (m_self, mid, RARRAY_LEN (argv), RARRAY_PTR (argv));
|
|
|
|
push_arg (meth->ret_type (), ret, rb_ret, heap);
|
|
|
|
if (meth->ret_type ().pass_obj ()) {
|
|
// In factory callbacks, make sure the returned object is not deleted by
|
|
// anyone except the caller.
|
|
Proxy *p = 0;
|
|
Data_Get_Struct (rb_ret, Proxy, p);
|
|
p->keep ();
|
|
}
|
|
|
|
// a Ruby callback must not leave temporary objects
|
|
tl_assert (heap.empty ());
|
|
|
|
} catch (rba::RubyError &err) {
|
|
rba::RubyError err_with_context (err);
|
|
err_with_context.set_context (m_cls_decl->name () + "::" + meth->names ());
|
|
throw err_with_context;
|
|
} catch (tl::ExitException & /*ex*/) {
|
|
throw;
|
|
} catch (tl::Exception &ex) {
|
|
throw tl::Exception (tl::to_string (tr ("Error calling method")) + " '" + m_cls_decl->name () + "::" + meth->names () + "': " + ex.msg ());
|
|
} catch (...) {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void
|
|
Proxy::destroy ()
|
|
{
|
|
if (! m_cls_decl) {
|
|
m_obj = 0;
|
|
return;
|
|
}
|
|
|
|
if (! (m_owned || m_can_destroy) && m_obj) {
|
|
throw tl::Exception (tl::to_string (tr ("Object cannot be destroyed explicitly")));
|
|
}
|
|
|
|
// first create the object if it was not created yet and check if it has not been
|
|
// destroyed already (the former is to ensure that the object is created at least)
|
|
if (! m_obj) {
|
|
if (m_destroyed) {
|
|
throw tl::Exception (tl::to_string (tr ("Object has been destroyed already")));
|
|
} else {
|
|
m_obj = m_cls_decl->create ();
|
|
m_owned = true;
|
|
}
|
|
}
|
|
|
|
void *o = 0;
|
|
if (m_owned || m_can_destroy) {
|
|
o = m_obj;
|
|
}
|
|
detach ();
|
|
if (o) {
|
|
m_cls_decl->destroy (o);
|
|
}
|
|
}
|
|
|
|
void
|
|
Proxy::detach ()
|
|
{
|
|
if (! m_destroyed && m_cls_decl && m_cls_decl->is_managed ()) {
|
|
gsi::ObjectBase *gsi_object = m_cls_decl->gsi_object (m_obj, false);
|
|
if (gsi_object) {
|
|
gsi_object->status_changed_event ().remove (this, &Proxy::object_status_changed);
|
|
}
|
|
if (!m_owned && m_self != Qnil) {
|
|
gc_unlock_object (m_self);
|
|
}
|
|
}
|
|
|
|
// NOTE: m_owned = false might mean the C++ object is already destroyed. We must not
|
|
// modify in this case and without is_managed() there is no way of knowing the state.
|
|
if (m_owned) {
|
|
clear_callbacks ();
|
|
}
|
|
|
|
m_self = Qnil;
|
|
m_obj = 0;
|
|
m_destroyed = true;
|
|
m_const_ref = false;
|
|
m_owned = false;
|
|
m_can_destroy = false;
|
|
}
|
|
|
|
Proxy::callbacks_cache Proxy::s_callbacks_cache;
|
|
|
|
void
|
|
Proxy::initialize_callbacks ()
|
|
{
|
|
#if 0
|
|
|
|
callbacks_cache::iterator cb = s_callbacks_cache.find (CLASS_OF (m_self));
|
|
if (cb == s_callbacks_cache.end ()) {
|
|
|
|
cb = s_callbacks_cache.insert (std::make_pair (CLASS_OF (m_self), callback_methods_type ())).first;
|
|
|
|
const gsi::ClassBase *cls = m_cls_decl;
|
|
|
|
// we got a new object - hence we have to attach event handlers.
|
|
// We don't need to install virtual function callbacks because in that case, no overload is possible
|
|
// (the object has been created on C++ side).
|
|
while (cls) {
|
|
|
|
for (gsi::ClassBase::method_iterator m = cls->begin_methods (); m != cls->end_methods (); ++m) {
|
|
|
|
if ((*m)->is_callback ()) {
|
|
|
|
// HINT: all callback may not have aliases nor overloads
|
|
const char *nstr = (*m)->primary_name ().c_str ();
|
|
|
|
// There is no place in the ruby API to determine whether a method is defined.
|
|
// Instead we explicitly call "method_defined?" to check, whether the given method
|
|
// is defined.
|
|
VALUE name = rb_str_new (nstr, strlen (nstr));
|
|
|
|
for (int prot = 0; prot < 2; ++prot) {
|
|
|
|
VALUE rb_ret;
|
|
if (prot) {
|
|
rb_ret = rb_funcall2_checked (rb_class_of (self), rb_intern ("protected_method_defined?"), 1, &name);
|
|
} else {
|
|
rb_ret = rb_funcall2_checked (rb_class_of (self), rb_intern ("method_defined?"), 1, &name);
|
|
}
|
|
if (RTEST (rb_ret)) {
|
|
// Only if the class defines that method we can link the virtual method call to the
|
|
// Ruby method
|
|
cb->second.push_back (*m);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// consider base classes as well.
|
|
cls = cls->base ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (callback_methods_type::const_iterator m = cb->second.begin (); m != cb->second.end (); ++m) {
|
|
|
|
bool prot = false;
|
|
const char *nstr = extract_protected ((*m)->name ().c_str (), prot);
|
|
|
|
ID method_id = rb_intern (nstr);
|
|
|
|
int id = add_callback (Proxy::CallbackFunction (method_id, m_self, *m));
|
|
(*m)->set_callback (m_obj, gsi::Callback (id, this, (*m)->argsize (), (*m)->retsize ()));
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
const gsi::ClassBase *cls = m_cls_decl;
|
|
|
|
// we got a new object - hence we have to attach callback handlers.
|
|
// We don't need to install virtual function callbacks because in that case, no overload is possible
|
|
// (the object has been created on C++ side).
|
|
while (cls) {
|
|
|
|
for (gsi::ClassBase::method_iterator m = cls->begin_callbacks (); m != cls->end_callbacks (); ++m) {
|
|
|
|
// HINT: all callback may not have aliases nor overloads
|
|
const char *nstr = (*m)->primary_name ().c_str ();
|
|
|
|
// There is no place in the ruby API to determine whether a method is defined.
|
|
// Instead we explicitly call "method_defined?" to check, whether the given method
|
|
// is defined.
|
|
VALUE name = rb_str_new (nstr, long (strlen (nstr)));
|
|
RB_GC_GUARD (name);
|
|
|
|
for (int prot = 0; prot < 2; ++prot) {
|
|
|
|
VALUE rb_ret;
|
|
if (prot) {
|
|
rb_ret = rba_funcall2_checked (rb_class_of (m_self), rb_intern ("protected_method_defined?"), 1, &name);
|
|
} else {
|
|
rb_ret = rba_funcall2_checked (rb_class_of (m_self), rb_intern ("method_defined?"), 1, &name);
|
|
}
|
|
if (RTEST (rb_ret)) {
|
|
|
|
// Only if the class defines that method we can link the virtual method call to the
|
|
// Ruby method
|
|
int id = add_callback (Proxy::CallbackFunction (rb_intern (nstr), *m));
|
|
(*m)->set_callback (m_obj, gsi::Callback (id, this, (*m)->argsize (), (*m)->retsize ()));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// consider base classes as well.
|
|
cls = cls->base ();
|
|
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
void
|
|
Proxy::clear_callbacks ()
|
|
{
|
|
m_cbfuncs.clear ();
|
|
|
|
if (! m_obj) {
|
|
return;
|
|
}
|
|
|
|
const gsi::ClassBase *cls = m_cls_decl;
|
|
while (cls) {
|
|
|
|
// reset all callbacks
|
|
for (gsi::ClassBase::method_iterator m = cls->begin_callbacks (); m != cls->end_callbacks (); ++m) {
|
|
(*m)->set_callback (m_obj, gsi::Callback ());
|
|
}
|
|
|
|
// consider base classes as well.
|
|
cls = cls->base ();
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Proxy::release ()
|
|
{
|
|
// If the object is managed we first reset the ownership of all other clients
|
|
// and then make us the owner
|
|
const gsi::ClassBase *cls = cls_decl ();
|
|
if (cls) {
|
|
|
|
if (cls->is_managed ()) {
|
|
void *o = obj ();
|
|
if (o) {
|
|
cls->gsi_object (o)->keep ();
|
|
}
|
|
}
|
|
|
|
if (!m_owned) {
|
|
if (cls->is_managed () && m_self != Qnil) {
|
|
gc_unlock_object (m_self);
|
|
}
|
|
m_owned = true;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Proxy::keep ()
|
|
{
|
|
const gsi::ClassBase *cls = cls_decl ();
|
|
if (cls) {
|
|
void *o = obj ();
|
|
if (o) {
|
|
if (cls->is_managed ()) {
|
|
cls->gsi_object (o)->keep ();
|
|
} else {
|
|
keep_internal ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Proxy::keep_internal ()
|
|
{
|
|
if (m_owned) {
|
|
// Fallback: the object is not gsi-enabled: we use the ownership flag in this
|
|
// case to keep it alive. This will not reset the ownership flag for all clients.
|
|
m_owned = false;
|
|
tl_assert (m_self != Qnil);
|
|
if (m_cls_decl->is_managed ()) {
|
|
gc_lock_object (m_self);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Proxy::set (void *obj, bool owned, bool const_ref, bool can_destroy, VALUE self)
|
|
{
|
|
const gsi::ClassBase *cls = cls_decl ();
|
|
tl_assert (cls);
|
|
|
|
if (obj != m_obj) {
|
|
|
|
// cleanup
|
|
if (m_obj) {
|
|
|
|
bool prev_owned = m_owned;
|
|
void *prev_obj = m_obj;
|
|
|
|
detach ();
|
|
|
|
// Destroy the object if we are owner. We don't destroy the object if it was locked
|
|
// (either because we are not owner or from C++ side using keep())
|
|
if (prev_owned) {
|
|
cls->destroy (prev_obj);
|
|
}
|
|
|
|
}
|
|
|
|
m_obj = obj;
|
|
m_self = self;
|
|
m_owned = owned;
|
|
m_can_destroy = can_destroy;
|
|
m_const_ref = const_ref;
|
|
|
|
if (m_obj) {
|
|
|
|
if (cls->is_managed ()) {
|
|
|
|
gsi::ObjectBase *gsi_object = cls->gsi_object (m_obj);
|
|
// Consider the case of "keep inside constructor"
|
|
if (m_owned && gsi_object->already_kept ()) {
|
|
m_owned = false;
|
|
}
|
|
gsi_object->status_changed_event ().add (this, &Proxy::object_status_changed);
|
|
|
|
if (! m_owned) {
|
|
gc_lock_object (m_self);
|
|
}
|
|
|
|
}
|
|
|
|
initialize_callbacks ();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// m_owned = owned; - thou shalt not change the ownership status
|
|
m_can_destroy = can_destroy;
|
|
m_const_ref = const_ref;
|
|
|
|
}
|
|
|
|
// now we have a valid object (or nil) - we can reset "destroyed" state. Note: this has to be done
|
|
// here because before detach might be called on *this which resets m_destroyed.
|
|
m_destroyed = false;
|
|
}
|
|
|
|
void *
|
|
Proxy::obj ()
|
|
{
|
|
if (! m_obj) {
|
|
if (m_destroyed) {
|
|
throw tl::Exception (tl::to_string (tr ("Object has been destroyed already")));
|
|
} else if (cls_decl ()->can_default_create()) {
|
|
// delayed creation of a detached C++ object ..
|
|
set (cls_decl ()->create (), true, false, true, m_self);
|
|
} else {
|
|
throw tl::Exception (tl::to_string (tr ("Object cannot be default-created (missing arguments to 'new'?)")));
|
|
}
|
|
}
|
|
|
|
return m_obj;
|
|
}
|
|
|
|
void
|
|
Proxy::object_status_changed (gsi::ObjectBase::StatusEventType type)
|
|
{
|
|
if (type == gsi::ObjectBase::ObjectDestroyed) {
|
|
m_destroyed = true; // NOTE: must be set before detach and indicates that the object was destroyed externally.
|
|
detach ();
|
|
} else if (type == gsi::ObjectBase::ObjectKeep) {
|
|
keep_internal ();
|
|
} else if (type == gsi::ObjectBase::ObjectRelease) {
|
|
release ();
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// SignalHandler implementation
|
|
|
|
VALUE SignalHandler::klass = Qnil;
|
|
|
|
SignalHandler::SignalHandler ()
|
|
: m_obj (Qnil)
|
|
{
|
|
}
|
|
|
|
SignalHandler::~SignalHandler ()
|
|
{
|
|
clear_procs ();
|
|
}
|
|
|
|
void
|
|
SignalHandler::initialize (VALUE obj)
|
|
{
|
|
m_obj = obj;
|
|
}
|
|
|
|
void
|
|
SignalHandler::assign (VALUE proc)
|
|
{
|
|
clear_procs ();
|
|
add (proc);
|
|
}
|
|
|
|
void
|
|
SignalHandler::clear ()
|
|
{
|
|
clear_procs ();
|
|
}
|
|
|
|
void
|
|
SignalHandler::add (VALUE proc)
|
|
{
|
|
remove (proc);
|
|
m_procs.push_back (proc);
|
|
}
|
|
|
|
void
|
|
SignalHandler::remove (VALUE proc)
|
|
{
|
|
for (std::list<VALUE>::iterator p = m_procs.begin (); p != m_procs.end (); ++p) {
|
|
if (*p == proc) {
|
|
m_procs.erase (p);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
SignalHandler::mark_this ()
|
|
{
|
|
if (m_obj != Qnil) {
|
|
rb_gc_mark (m_obj);
|
|
}
|
|
for (std::list<VALUE>::iterator p = m_procs.begin (); p != m_procs.end (); ++p) {
|
|
rb_gc_mark (*p);
|
|
}
|
|
}
|
|
|
|
void
|
|
SignalHandler::clear_procs ()
|
|
{
|
|
m_procs.clear ();
|
|
}
|
|
|
|
void
|
|
SignalHandler::free (void *p)
|
|
{
|
|
delete ((SignalHandler *) p);
|
|
}
|
|
|
|
void
|
|
SignalHandler::mark (void *p)
|
|
{
|
|
((SignalHandler *) p)->mark_this ();
|
|
}
|
|
|
|
VALUE
|
|
SignalHandler::alloc (VALUE klass)
|
|
{
|
|
tl_assert (TYPE (klass) == T_CLASS);
|
|
|
|
SignalHandler *signal = new SignalHandler ();
|
|
return Data_Wrap_Struct (klass, &SignalHandler::mark, &SignalHandler::free, signal);
|
|
}
|
|
|
|
VALUE
|
|
SignalHandler::static_initialize (VALUE self, VALUE obj)
|
|
{
|
|
SignalHandler *p = 0;
|
|
Data_Get_Struct (self, SignalHandler, p);
|
|
if (p) {
|
|
p->initialize (obj);
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
VALUE
|
|
SignalHandler::static_assign (VALUE self, VALUE proc)
|
|
{
|
|
// NOTE: self-assignment happens on "signal+=proc" which is resolved to
|
|
// "signal=(signal+proc)" and "signal+proc" returns the signal itself.
|
|
// and:
|
|
if (proc != self) {
|
|
|
|
if (TYPE (proc) != T_DATA || rb_obj_is_kind_of (proc, rb_cProc) != Qtrue) {
|
|
std::string msg = tl::to_string (tr ("Single argument to signal must be a Proc object"));
|
|
VALUE args [1];
|
|
args [0] = rb_str_new2 (msg.c_str ());
|
|
rb_exc_raise (rb_class_new_instance(1, args, rb_eRuntimeError));
|
|
}
|
|
|
|
SignalHandler *p = 0;
|
|
Data_Get_Struct (self, SignalHandler, p);
|
|
if (p) {
|
|
p->assign (proc);
|
|
}
|
|
|
|
}
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
VALUE
|
|
SignalHandler::static_add (VALUE self, VALUE proc)
|
|
{
|
|
if (TYPE (proc) != T_DATA || rb_obj_is_kind_of (proc, rb_cProc) != Qtrue) {
|
|
std::string msg = tl::to_string (tr ("Single argument to signal's add method must be a Proc object"));
|
|
VALUE args [1];
|
|
args [0] = rb_str_new2 (msg.c_str ());
|
|
rb_exc_raise (rb_class_new_instance(1, args, rb_eRuntimeError));
|
|
}
|
|
|
|
SignalHandler *p = 0;
|
|
Data_Get_Struct (self, SignalHandler, p);
|
|
if (p) {
|
|
p->add (proc);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
VALUE
|
|
SignalHandler::static_clear (VALUE self)
|
|
{
|
|
SignalHandler *p = 0;
|
|
Data_Get_Struct (self, SignalHandler, p);
|
|
if (p) {
|
|
p->clear ();
|
|
}
|
|
return self;
|
|
}
|
|
|
|
VALUE
|
|
SignalHandler::static_remove (VALUE self, VALUE proc)
|
|
{
|
|
SignalHandler *p = 0;
|
|
Data_Get_Struct (self, SignalHandler, p);
|
|
if (p) {
|
|
p->remove (proc);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
void
|
|
SignalHandler::define_class (VALUE module, const char *name)
|
|
{
|
|
klass = rb_define_class_under (module, name, rb_cObject);
|
|
rb_define_alloc_func (klass, SignalHandler::alloc);
|
|
rb_define_method (klass, "initialize", (ruby_func) &SignalHandler::static_initialize, 1);
|
|
rb_define_method (klass, "set", (ruby_func) &SignalHandler::static_assign, 1);
|
|
rb_define_method (klass, "clear", (ruby_func) &SignalHandler::static_clear, 0);
|
|
rb_define_method (klass, "+", (ruby_func) &SignalHandler::static_add, 1);
|
|
rb_define_method (klass, "-", (ruby_func) &SignalHandler::static_remove, 1);
|
|
}
|
|
|
|
void SignalHandler::call (const gsi::MethodBase *meth, gsi::SerialArgs &args, gsi::SerialArgs &ret) const
|
|
{
|
|
VALUE argv = rb_ary_new2 (long (std::distance (meth->begin_arguments (), meth->end_arguments ())));
|
|
RB_GC_GUARD (argv);
|
|
|
|
tl::Heap heap;
|
|
|
|
// TODO: signals with default arguments?
|
|
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) {
|
|
rb_ary_push (argv, pop_arg (*a, 0, args, heap));
|
|
}
|
|
|
|
// call the signal handlers ... the last one will deliver the return value
|
|
VALUE rb_ret = Qnil;
|
|
for (std::list<VALUE>::const_iterator p = m_procs.begin (); p != m_procs.end (); ++p) {
|
|
rb_ret = rba_funcall2_checked (*p, rb_intern ("call"), RARRAY_LEN (argv), RARRAY_PTR (argv));
|
|
}
|
|
|
|
push_arg (meth->ret_type (), ret, rb_ret, heap);
|
|
|
|
// a Ruby callback must not leave temporary objects
|
|
tl_assert (heap.empty ());
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Class map management
|
|
|
|
static std::map <VALUE, const gsi::ClassBase *> cls_map;
|
|
static std::map <std::pair<const gsi::ClassBase *, bool>, VALUE> rev_cls_map;
|
|
|
|
void register_class (VALUE ruby_cls, const gsi::ClassBase *gsi_cls, bool as_static)
|
|
{
|
|
cls_map.insert (std::make_pair (ruby_cls, gsi_cls));
|
|
rev_cls_map.insert (std::make_pair (std::make_pair (gsi_cls, as_static), ruby_cls));
|
|
}
|
|
|
|
VALUE ruby_cls (const gsi::ClassBase *cls, bool as_static)
|
|
{
|
|
auto c = rev_cls_map.find (std::make_pair (cls, as_static));
|
|
tl_assert (c != rev_cls_map.end ());
|
|
return c->second;
|
|
}
|
|
|
|
bool is_registered (const gsi::ClassBase *cls, bool as_static)
|
|
{
|
|
return rev_cls_map.find (std::make_pair (cls, as_static)) != rev_cls_map.end ();
|
|
}
|
|
|
|
const gsi::ClassBase *find_cclass (VALUE k)
|
|
{
|
|
const gsi::ClassBase *cls = find_cclass_maybe_null (k);
|
|
tl_assert (cls != 0);
|
|
return cls;
|
|
}
|
|
|
|
const gsi::ClassBase *find_cclass_maybe_null (VALUE k)
|
|
{
|
|
std::map <VALUE, const gsi::ClassBase *>::const_iterator cls;
|
|
|
|
// find the class that is bound to C++ (maybe a super class)
|
|
while (k != rb_cObject) {
|
|
cls = cls_map.find (k);
|
|
if (cls == cls_map.end ()) {
|
|
// if not found advance to super class
|
|
k = RCLASS_SUPER (k);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return cls != cls_map.end () ? cls->second : 0;
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|