diff --git a/ext/v8/backref.cc b/ext/v8/backref.cc new file mode 100644 index 0000000..436bea1 --- /dev/null +++ b/ext/v8/backref.cc @@ -0,0 +1,56 @@ +#include "rr.h" + +namespace rr { + + VALUE Backref::WeakRef; + ID Backref::_new; + ID Backref::__getobj__; + ID Backref::__setobj__; + ID Backref::__weakref_alive; + + void Backref::Init() { + WeakRef = rb_eval_string("require 'weakref'; WeakRef"); + rb_gc_register_address(&WeakRef); + _new = rb_intern("new"); + __getobj__ = rb_intern("__getobj__"); + __setobj__ = rb_intern("__setobj__"); + __weakref_alive = rb_intern("weakref_alive?"); + } + + Backref::Backref(VALUE initial) { + this->ref = rb_funcall(WeakRef, _new, 1, initial); + rb_gc_register_address(&ref); + } + + Backref::~Backref() { + rb_gc_unregister_address(&ref); + } + + VALUE Backref::get() { + if (alive_p()) { + return rb_funcall(ref, __getobj__, 0); + } else { + return Qnil; + } + } + + void Backref::set(VALUE value) { + rb_funcall(ref, __setobj__, 1, value); + } + + bool Backref::alive_p() { + return RTEST(rb_funcall(ref, __weakref_alive, 0)); + } + + v8::Handle Backref::to_external() { + v8::Local wrapper = v8::External::Wrap(this); + v8::Persistent::New(wrapper).MakeWeak(this, &release); + return wrapper; + } + + void Backref::release(v8::Persistent handle, void* data) { + handle.Dispose(); + Backref* backref = (Backref*)data; + delete backref; + } +} \ No newline at end of file diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 96efcbc..d64aa45 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -33,5 +33,6 @@ extern "C" { Locker::Init(); ResourceConstraints::Init(); HeapStatistics::Init(); + Backref::Init(); } } \ No newline at end of file diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 00e8c11..1c7fabc 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -132,6 +132,27 @@ VALUE Object::SetAccessor(int argc, VALUE* argv, VALUE self) { } Object::operator VALUE() { + VALUE value; + Backref* backref; + v8::Local key(v8::String::NewSymbol("rr::Backref")); + v8::Local holder = handle->GetHiddenValue(key); + if (holder.IsEmpty()) { + value = downcast(); + backref = new Backref(value); + handle->SetHiddenValue(key, backref->to_external()); + } else { + backref = (Backref*)v8::External::Unwrap(holder); + if (backref->alive_p()) { + value = backref->get(); + } else { + value = downcast(); + backref->set(value); + } + } + return value; +} + +VALUE Object::downcast() { if (handle->IsFunction()) { return Function((v8::Handle) v8::Function::Cast(*handle)); } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 901e60c..c39c42f 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -155,6 +155,24 @@ public: }; template VALUE Ref::Class; +class Backref { +public: + static void Init(); + Backref(VALUE initial); + virtual ~Backref(); + VALUE get(); + void set(VALUE); + bool alive_p(); + v8::Handle to_external(); + static void release(v8::Persistent handle, void* data); +private: + VALUE ref; + static VALUE WeakRef; + static ID _new; + static ID __getobj__; + static ID __setobj__; + static ID __weakref_alive; +}; class Handles { public: static void Init(); @@ -492,6 +510,9 @@ public: inline Object(VALUE value) : Ref(value) {} inline Object(v8::Handle object) : Ref(object) {} virtual operator VALUE(); + +protected: + VALUE downcast(); }; class Array : public Ref { diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index aab245e..bb647be 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -37,4 +37,10 @@ describe V8::C::Object do o.Set(property, V8::C::String::New("Bro! ")) o.Get(property).Utf8Value().should eql "Bro! I am Legend" end + it "always returns the same ruby object for the same V8 object" do + one = V8::C::Object::New() + two = V8::C::Object::New() + one.Set("two", two) + one.Get("two").should be two + end end \ No newline at end of file