Skip to content
This repository was archived by the owner on Dec 4, 2023. It is now read-only.

Commit 39a6cd6

Browse files
committed
Merge pull request #374 from cowboyd/4.5/referential-integrity
add back in the memory map + documentation
2 parents 3727570 + 288ae8c commit 39a6cd6

6 files changed

Lines changed: 143 additions & 67 deletions

File tree

lib/v8.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# require 'v8/error'
77
# require 'v8/stack'
88
require 'v8/conversion/fundamental'
9-
# require 'v8/conversion/indentity'
109
# require 'v8/conversion/reference'
1110
# require 'v8/conversion/primitive'
1211
# require 'v8/conversion/code'

lib/v8/conversion.rb

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,85 @@
1+
##
2+
# An extensible conversion mechanism for converting objects to and
3+
# from their V8 representations.
4+
#
5+
# The Ruby Racer has two levels of representation for JavaScript
6+
# objects: the low-level C++ objects which are just thin wrappers
7+
# native counterparts. These are the objects in the `V8::C::*`
8+
# namespace. It also has high-level Ruby objects which can
9+
# either be instances of `V8::Object`, `V8::Date`, or just plain Ruby
10+
# objects that have representations in the JavaScript runtime.
11+
#
12+
# The conversion object held by the context captures this transition
13+
# from one object type to the other. It is implemented as a "middleware"
14+
# stack where at the base is the `Fundamental` conversion which does
15+
# basic conversion and identity mapping.
16+
#
17+
# In order to "extend" or override the conversion mechanism, you can
18+
# extend this object to add behaviors. For example, the following
19+
# extension will add a `__fromRuby__` property to every ruby object
20+
# that is embedded into this context.
21+
#
22+
# module TagRubyObjects
23+
# def to_v8(context, ruby_object)
24+
# super.tap do |v8_object|
25+
# v8_object.Set(V8::C::String::NewFromUtf8("__fromRuby__"), V8::C::Boolean::New(true))
26+
# end
27+
# end
28+
# end
29+
# context.conversion.extend TagRubyObjects
30+
#
31+
# @see V8::Conversion::Fundamental for the basic conversion operation.
132
class V8::Conversion
233
include Fundamental
3-
# include Identity
434

35+
36+
##
37+
# Convert a low-level instance of `V8::C::Value` or one of its
38+
# subclasses into a Ruby object.
39+
#
40+
# The `Fundamental` conversion will call `v8_object.to_ruby`, but
41+
# any number of middlewares can be inserted between then.
42+
#
43+
# @param [V8::C::Value] the object to convert
44+
# @return [Object] the corresponding Ruby value
545
def to_ruby(v8_object)
646
super v8_object
747
end
848

49+
##
50+
# Convert a Ruby Object into a low-level `V8::C::Value` or one of
51+
# its subclasses. Note here that things like `V8::Object` are
52+
# considered Ruby objects and *not* V8 objects. So, for example, the
53+
# fundamental conversion for `V8::Object` will return a
54+
# `V8::C::Object`
55+
#
56+
# The `Fundamental` conversion will call
57+
# `ruby_object.to_v8(context)` optionally storing the result in an
58+
# identity map in the case where the result is a `V8::C::Object`
59+
#
60+
# @param [V8::Context] the Ruby context in the conversion happens
61+
# @param [Object] Ruby object to convert
62+
# @return [V8::C::Value] the v8 representation
963
def to_v8(context, ruby_object)
1064
super context, ruby_object
1165
end
1266
end
1367

68+
69+
##
70+
# The folowing represent the default conversions from instances of
71+
# `V8::C::Value` into their Ruby counterparts.
1472
module V8::C
1573
class String
16-
alias_method :to_ruby, :Utf8Value
74+
def to_ruby
75+
self.Utf8Value()
76+
end
1777
end
1878

1979
class Number
20-
alias_method :to_ruby, :Value
80+
def to_ruby
81+
self.Value()
82+
end
2183
end
2284

2385
class Undefined
@@ -51,6 +113,9 @@ def to_ruby
51113
end
52114
end
53115

116+
##
117+
# The following are the default conversions from Ruby objects into
118+
# low-level C++ objects.
54119
class String
55120
def to_v8(context)
56121
V8::C::String::NewFromUtf8(context.isolate.native, self)
@@ -69,25 +134,3 @@ def to_v8(context)
69134
V8::C::Symbol::For(context.isolate.native, V8::C::String::NewFromUtf8(isolate, to_s))
70135
end
71136
end
72-
73-
# for type in [TrueClass, FalseClass, NilClass, Float] do
74-
# type.class_eval do
75-
# include V8::Conversion::Primitive
76-
# end
77-
# end
78-
79-
# for type in [Class, Object, Array, Hash, String, Symbol, Time, Proc, Method, Fixnum] do
80-
# type.class_eval do
81-
# include V8::Conversion.const_get(type.name)
82-
# end
83-
# end
84-
85-
# class UnboundMethod
86-
# include V8::Conversion::Method
87-
# end
88-
89-
# for type in [:Object, :String, :Date] do
90-
# V8::C::const_get(type).class_eval do
91-
# include V8::Conversion::const_get("Native#{type}")
92-
# end
93-
# end

lib/v8/conversion/fundamental.rb

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,77 @@
1+
require 'v8/weak'
2+
13
class V8::Conversion
4+
##
5+
# This is the fundamental conversion for from Ruby objects into
6+
# `V8::C::Value`s and vice-versa. For instances of `V8::C::Object`,
7+
# the result is stored in an identity map, so that subsequent
8+
# conversions return the same object both to and from ruby.
9+
#
10+
# It sits at the top of the conversion "middleware stack"
211
module Fundamental
12+
##
13+
# Convert a low-level `V8::C::Value` into a Ruby object,
14+
# optionally storing the result in an identity map so that it can
15+
# be re-used for subsequent conversions.
16+
#
17+
# @param [V8::C::Value] low-level C++ object.
18+
# @return [Object] Ruby object counterpart
319
def to_ruby(v8_object)
4-
v8_object.to_ruby
20+
# Only objects can be reliably identified. If this is an
21+
# object, then we want to see if there is already a ruby object
22+
# associated with it.
23+
if v8_object.kind_of? V8::C::Object
24+
if rb_object = v8_idmap[v8_object.GetIdentityHash()]
25+
# it was in the id map, so return the existing instance.
26+
rb_object
27+
else
28+
# it was not in the id map, so we run the default conversion
29+
# and store it in the id map
30+
v8_object.to_ruby.tap do |object|
31+
equate object, v8_object
32+
end
33+
end
34+
else
35+
# not an object, just do the default conversion
36+
v8_object.to_ruby
37+
end
538
end
639

40+
##
41+
# Convert a Ruby object into a low-level C++ `V8::C::Value`.
42+
#
43+
# First it checks to see if there is an entry in the id map for
44+
# this object. Otherwise, it will run the default conversion.
745
def to_v8(context, ruby_object)
8-
ruby_object.to_v8 context
46+
rb_idmap[ruby_object.object_id] || ruby_object.to_v8(context)
47+
end
48+
49+
##
50+
# Mark a ruby object and a low-level V8 C++ object as being
51+
# equivalent in this context.
52+
#
53+
# After being equated the two objects are like mirrors of each
54+
# other, where one exists in the Ruby world, and the other exists
55+
# in the V8 world. It's all very through the looking glass.
56+
#
57+
# Whenever `ruby_object` is reflected into the V8 runtime, then
58+
# `v8_object` will be used in its stead, and whenever `v8_object`
59+
# is reflected into the Ruby runtime, then `ruby_object` will be
60+
# used in *its* stead.
61+
#
62+
# @param [Object] the ruby object
63+
# @param [V8::C::Value] the v8 object
64+
def equate(ruby_object, v8_object)
65+
v8_idmap[v8_object.GetIdentityHash()] = ruby_object
66+
rb_idmap[ruby_object.object_id] = v8_object
67+
end
68+
69+
def v8_idmap
70+
@v8_idmap ||= V8::Weak::WeakValueMap.new
71+
end
72+
73+
def rb_idmap
74+
@ruby_idmap ||= V8::Weak::WeakValueMap.new
975
end
1076
end
1177
end

lib/v8/conversion/indentity.rb

Lines changed: 0 additions & 31 deletions
This file was deleted.

lib/v8/object.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ class V8::Object
44

55
def initialize(native = nil)
66
@context = V8::Context.current or fail "tried to initialize a #{self.class} without being in an entered V8::Context"
7-
@native = block_given? ? yield : native || V8::C::Object::New()
8-
#@context.link self, @native
7+
@native = native || V8::C::Object::New(@context.isolate.native)
98
end
109

1110
def [](key)

spec/v8/context_spec.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,12 @@
100100
# end
101101
# end
102102

103-
# xit "always returns the same ruby object for a single javascript object" do
104-
# obj = @cxt.eval('obj = {}')
105-
# obj.should be(@cxt['obj'])
106-
# @cxt.eval('obj').should be(@cxt['obj'])
107-
# @cxt['obj'].should be(@cxt['obj'])
108-
# end
103+
it "always returns the same ruby object for a single javascript object" do
104+
obj = @cxt.eval('obj = {}')
105+
obj.should be(@cxt['obj'])
106+
@cxt.eval('obj').should be(@cxt['obj'])
107+
@cxt['obj'].should be(@cxt['obj'])
108+
end
109109

110110
# xit "converts arrays to javascript" do
111111
# @cxt['a'] = [1,2,4]

0 commit comments

Comments
 (0)