mirror of
https://github.com/rubyjs/therubyracer
synced 2023-03-27 23:21:42 -04:00
Merge pull request #374 from cowboyd/4.5/referential-integrity
add back in the memory map + documentation
This commit is contained in:
commit
39a6cd6ce3
6 changed files with 143 additions and 67 deletions
|
@ -6,7 +6,6 @@ require 'v8/context'
|
|||
# require 'v8/error'
|
||||
# require 'v8/stack'
|
||||
require 'v8/conversion/fundamental'
|
||||
# require 'v8/conversion/indentity'
|
||||
# require 'v8/conversion/reference'
|
||||
# require 'v8/conversion/primitive'
|
||||
# require 'v8/conversion/code'
|
||||
|
|
|
@ -1,23 +1,85 @@
|
|||
##
|
||||
# An extensible conversion mechanism for converting objects to and
|
||||
# from their V8 representations.
|
||||
#
|
||||
# The Ruby Racer has two levels of representation for JavaScript
|
||||
# objects: the low-level C++ objects which are just thin wrappers
|
||||
# native counterparts. These are the objects in the `V8::C::*`
|
||||
# namespace. It also has high-level Ruby objects which can
|
||||
# either be instances of `V8::Object`, `V8::Date`, or just plain Ruby
|
||||
# objects that have representations in the JavaScript runtime.
|
||||
#
|
||||
# The conversion object held by the context captures this transition
|
||||
# from one object type to the other. It is implemented as a "middleware"
|
||||
# stack where at the base is the `Fundamental` conversion which does
|
||||
# basic conversion and identity mapping.
|
||||
#
|
||||
# In order to "extend" or override the conversion mechanism, you can
|
||||
# extend this object to add behaviors. For example, the following
|
||||
# extension will add a `__fromRuby__` property to every ruby object
|
||||
# that is embedded into this context.
|
||||
#
|
||||
# module TagRubyObjects
|
||||
# def to_v8(context, ruby_object)
|
||||
# super.tap do |v8_object|
|
||||
# v8_object.Set(V8::C::String::NewFromUtf8("__fromRuby__"), V8::C::Boolean::New(true))
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# context.conversion.extend TagRubyObjects
|
||||
#
|
||||
# @see V8::Conversion::Fundamental for the basic conversion operation.
|
||||
class V8::Conversion
|
||||
include Fundamental
|
||||
# include Identity
|
||||
|
||||
|
||||
##
|
||||
# Convert a low-level instance of `V8::C::Value` or one of its
|
||||
# subclasses into a Ruby object.
|
||||
#
|
||||
# The `Fundamental` conversion will call `v8_object.to_ruby`, but
|
||||
# any number of middlewares can be inserted between then.
|
||||
#
|
||||
# @param [V8::C::Value] the object to convert
|
||||
# @return [Object] the corresponding Ruby value
|
||||
def to_ruby(v8_object)
|
||||
super v8_object
|
||||
end
|
||||
|
||||
##
|
||||
# Convert a Ruby Object into a low-level `V8::C::Value` or one of
|
||||
# its subclasses. Note here that things like `V8::Object` are
|
||||
# considered Ruby objects and *not* V8 objects. So, for example, the
|
||||
# fundamental conversion for `V8::Object` will return a
|
||||
# `V8::C::Object`
|
||||
#
|
||||
# The `Fundamental` conversion will call
|
||||
# `ruby_object.to_v8(context)` optionally storing the result in an
|
||||
# identity map in the case where the result is a `V8::C::Object`
|
||||
#
|
||||
# @param [V8::Context] the Ruby context in the conversion happens
|
||||
# @param [Object] Ruby object to convert
|
||||
# @return [V8::C::Value] the v8 representation
|
||||
def to_v8(context, ruby_object)
|
||||
super context, ruby_object
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
##
|
||||
# The folowing represent the default conversions from instances of
|
||||
# `V8::C::Value` into their Ruby counterparts.
|
||||
module V8::C
|
||||
class String
|
||||
alias_method :to_ruby, :Utf8Value
|
||||
def to_ruby
|
||||
self.Utf8Value()
|
||||
end
|
||||
end
|
||||
|
||||
class Number
|
||||
alias_method :to_ruby, :Value
|
||||
def to_ruby
|
||||
self.Value()
|
||||
end
|
||||
end
|
||||
|
||||
class Undefined
|
||||
|
@ -51,6 +113,9 @@ module V8::C
|
|||
end
|
||||
end
|
||||
|
||||
##
|
||||
# The following are the default conversions from Ruby objects into
|
||||
# low-level C++ objects.
|
||||
class String
|
||||
def to_v8(context)
|
||||
V8::C::String::NewFromUtf8(context.isolate.native, self)
|
||||
|
@ -69,25 +134,3 @@ class Symbol
|
|||
V8::C::Symbol::For(context.isolate.native, V8::C::String::NewFromUtf8(isolate, to_s))
|
||||
end
|
||||
end
|
||||
|
||||
# for type in [TrueClass, FalseClass, NilClass, Float] do
|
||||
# type.class_eval do
|
||||
# include V8::Conversion::Primitive
|
||||
# end
|
||||
# end
|
||||
|
||||
# for type in [Class, Object, Array, Hash, String, Symbol, Time, Proc, Method, Fixnum] do
|
||||
# type.class_eval do
|
||||
# include V8::Conversion.const_get(type.name)
|
||||
# end
|
||||
# end
|
||||
|
||||
# class UnboundMethod
|
||||
# include V8::Conversion::Method
|
||||
# end
|
||||
|
||||
# for type in [:Object, :String, :Date] do
|
||||
# V8::C::const_get(type).class_eval do
|
||||
# include V8::Conversion::const_get("Native#{type}")
|
||||
# end
|
||||
# end
|
||||
|
|
|
@ -1,11 +1,77 @@
|
|||
require 'v8/weak'
|
||||
|
||||
class V8::Conversion
|
||||
##
|
||||
# This is the fundamental conversion for from Ruby objects into
|
||||
# `V8::C::Value`s and vice-versa. For instances of `V8::C::Object`,
|
||||
# the result is stored in an identity map, so that subsequent
|
||||
# conversions return the same object both to and from ruby.
|
||||
#
|
||||
# It sits at the top of the conversion "middleware stack"
|
||||
module Fundamental
|
||||
##
|
||||
# Convert a low-level `V8::C::Value` into a Ruby object,
|
||||
# optionally storing the result in an identity map so that it can
|
||||
# be re-used for subsequent conversions.
|
||||
#
|
||||
# @param [V8::C::Value] low-level C++ object.
|
||||
# @return [Object] Ruby object counterpart
|
||||
def to_ruby(v8_object)
|
||||
# Only objects can be reliably identified. If this is an
|
||||
# object, then we want to see if there is already a ruby object
|
||||
# associated with it.
|
||||
if v8_object.kind_of? V8::C::Object
|
||||
if rb_object = v8_idmap[v8_object.GetIdentityHash()]
|
||||
# it was in the id map, so return the existing instance.
|
||||
rb_object
|
||||
else
|
||||
# it was not in the id map, so we run the default conversion
|
||||
# and store it in the id map
|
||||
v8_object.to_ruby.tap do |object|
|
||||
equate object, v8_object
|
||||
end
|
||||
end
|
||||
else
|
||||
# not an object, just do the default conversion
|
||||
v8_object.to_ruby
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Convert a Ruby object into a low-level C++ `V8::C::Value`.
|
||||
#
|
||||
# First it checks to see if there is an entry in the id map for
|
||||
# this object. Otherwise, it will run the default conversion.
|
||||
def to_v8(context, ruby_object)
|
||||
ruby_object.to_v8 context
|
||||
rb_idmap[ruby_object.object_id] || ruby_object.to_v8(context)
|
||||
end
|
||||
|
||||
##
|
||||
# Mark a ruby object and a low-level V8 C++ object as being
|
||||
# equivalent in this context.
|
||||
#
|
||||
# After being equated the two objects are like mirrors of each
|
||||
# other, where one exists in the Ruby world, and the other exists
|
||||
# in the V8 world. It's all very through the looking glass.
|
||||
#
|
||||
# Whenever `ruby_object` is reflected into the V8 runtime, then
|
||||
# `v8_object` will be used in its stead, and whenever `v8_object`
|
||||
# is reflected into the Ruby runtime, then `ruby_object` will be
|
||||
# used in *its* stead.
|
||||
#
|
||||
# @param [Object] the ruby object
|
||||
# @param [V8::C::Value] the v8 object
|
||||
def equate(ruby_object, v8_object)
|
||||
v8_idmap[v8_object.GetIdentityHash()] = ruby_object
|
||||
rb_idmap[ruby_object.object_id] = v8_object
|
||||
end
|
||||
|
||||
def v8_idmap
|
||||
@v8_idmap ||= V8::Weak::WeakValueMap.new
|
||||
end
|
||||
|
||||
def rb_idmap
|
||||
@ruby_idmap ||= V8::Weak::WeakValueMap.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
require 'ref'
|
||||
|
||||
class V8::Conversion
|
||||
module Identity
|
||||
def to_ruby(v8_object)
|
||||
if v8_object.class <= V8::C::Object
|
||||
v8_idmap[v8_object.GetIdentityHash()] || super(v8_object)
|
||||
else
|
||||
super(v8_object)
|
||||
end
|
||||
end
|
||||
|
||||
def to_v8(ruby_object)
|
||||
return super(ruby_object) if ruby_object.is_a?(String) || ruby_object.is_a?(Primitive)
|
||||
rb_idmap[ruby_object.object_id] || super(ruby_object)
|
||||
end
|
||||
|
||||
def equate(ruby_object, v8_object)
|
||||
v8_idmap[v8_object.GetIdentityHash()] = ruby_object
|
||||
rb_idmap[ruby_object.object_id] = v8_object
|
||||
end
|
||||
|
||||
def v8_idmap
|
||||
@v8_idmap ||= V8::Weak::WeakValueMap.new
|
||||
end
|
||||
|
||||
def rb_idmap
|
||||
@ruby_idmap ||= V8::Weak::WeakValueMap.new
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,8 +4,7 @@ class V8::Object
|
|||
|
||||
def initialize(native = nil)
|
||||
@context = V8::Context.current or fail "tried to initialize a #{self.class} without being in an entered V8::Context"
|
||||
@native = block_given? ? yield : native || V8::C::Object::New()
|
||||
#@context.link self, @native
|
||||
@native = native || V8::C::Object::New(@context.isolate.native)
|
||||
end
|
||||
|
||||
def [](key)
|
||||
|
|
|
@ -100,12 +100,12 @@ describe "V8::Context" do
|
|||
# end
|
||||
# end
|
||||
|
||||
# xit "always returns the same ruby object for a single javascript object" do
|
||||
# obj = @cxt.eval('obj = {}')
|
||||
# obj.should be(@cxt['obj'])
|
||||
# @cxt.eval('obj').should be(@cxt['obj'])
|
||||
# @cxt['obj'].should be(@cxt['obj'])
|
||||
# end
|
||||
it "always returns the same ruby object for a single javascript object" do
|
||||
obj = @cxt.eval('obj = {}')
|
||||
obj.should be(@cxt['obj'])
|
||||
@cxt.eval('obj').should be(@cxt['obj'])
|
||||
@cxt['obj'].should be(@cxt['obj'])
|
||||
end
|
||||
|
||||
# xit "converts arrays to javascript" do
|
||||
# @cxt['a'] = [1,2,4]
|
||||
|
|
Loading…
Add table
Reference in a new issue