Skip to content

Commit

Permalink
Support for assync (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
codeboost committed Oct 12, 2012
1 parent 7a8b5f0 commit b46a8e1
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 26 deletions.
6 changes: 6 additions & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ the native call. It can be used to place guards for argument values/ranges in he

The way the native method is invoked can also be customized, using the @call directive.


Modifiers:
@noexpose -> do not expose class to javascript
@manual -> will declare function, but not generate implementation
@async -> will generate asynchronous function call

@type
=====
Expand Down
3 changes: 3 additions & 0 deletions src/beautils.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ class Argument
@type = new Type parsed.type, @ns
@value = parsed.value
if cast then @type.cast = expandCast cast, @type
#returns full argument declaration, without default argument value (eg. int k = 0)
orgNoDefault: ->
return @org.replace /\s*\=\s*.+$/, ''

parseDeclaration = (str, namespace) ->

Expand Down
152 changes: 127 additions & 25 deletions src/classconvert.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,25 @@ class ClassConverter
@exposed = false
return false

str = node.text

if /^@manual\s+/.test node.text
isManual = true
str = node.text.replace /^@manual\s+/, ''
else
str = node.text

if /^@async\s+/.test node.text
isAsync = true
str = node.text.replace /^@async\s+/,''

if /^@asyncWait\s+/.test node.text
isAsync = true
isAsyncWait = true
str = node.text.replace /^@asyncWait\s+/,''

if /@default\s+/.test str
generateDefaultImpl = true
str = str.replace /@default\s+/,''


#remove end comments
str = str.replace /\s*\/\/.*$/g, ''
Expand Down Expand Up @@ -232,13 +245,21 @@ class ClassConverter

fn.org = str
fn.manual = isManual
fn.isAsync = isAsync
fn.isAsyncWait = isAsyncWait
fn.generateDefaultImpl = generateDefaultImpl
fn.requiredArgs = @requiredArgs fn.args
fn.sublines = node.children
fn.node = node
fn.parentClass = @nativeClassName

if fn.virtual
@virtualCount++

if fn.isAsync and not fn.virtual
@warn "Async works with virtual functions only! Ignoring @async declaration.", node
fn.isAsync = false


#check sublines for @call directive
callNode = _.detect fn.sublines, (subline) -> /^\@call/.test subline.text
Expand Down Expand Up @@ -391,16 +412,19 @@ class ClassConverter
#native-called virtual functions go into the .cpp file
#this is because we may return specializations of Convert<> from these functions
#and this must not happen before the explicit specializations of Convert<> generated by TypeManager.

#implBlock -> implementation block which goes into the cpp file
implBlock = new CodeBlock.CodeBlock

#publicv -> functions which are called from Javascript
publicv = publicBlock.add new CodeBlock.CodeBlock "", false
publicv.add "//JS: These virtual functions will only be called from Javascript"
#publicd -> functions which are called from native code
publicd = publicBlock.add new CodeBlock.CodeBlock "", false
publicd.add "//Native: These virtual functions will only be called from native code"

#go through each virtual function in the class
_.each vfuncs, (vfunc) =>
dargs = _.map vfunc.args, (arg) -> arg.org
dargs = _.map vfunc.args, (arg) -> arg.orgNoDefault()
cargs = _.map vfunc.args, (arg) -> arg.name

vfunc.callAs = "_d_" + vfunc.name
Expand All @@ -419,38 +443,117 @@ class ClassConverter
vfuncdecla = "#{vfunc.type.org} #{vfunc.name}(#{dargs.join ', '})"

publicd.add vfuncdecla + ';'

if vfunc.isAsync
###
A function which is called by native code from a different thread.
Since javascript is single-threaded we must execute the call from the javascript thread. This is accomplished
by using uv_queue_work function (from libev). uv_queue_work takes a callback as parameter and calls our callback from the main
thread. Then we can actually make the call into javascript.
How it is implemented:
Suppose we have the following function in the C++ interface:
class TransportI{
virtual void SendPacket(unsigned char* packet, int numberOfBytes, int channel, int speechValue, int packetLoss) = 0;
};
and the following bea declaration:
@async virtual void SendPacket(unsigned char* packet, int numberOfBytes, int channel, int speechValue, int packetLoss) = 0;
The implementation goes like this:
struct SendPacketAsync : public bea::Async{
unsigned char* packet;
int numberOfBytes;
int channel;
int speechValue;
int packetLoss;
void saveArguments(unsigned char* packet, int numberOfBytes, int channel, int speechValue, int packetLoss);//no implementation
void callJS(); //no implementation
~SendPacketAsync(); //no implementation
}
in the cpp file:
void _d_TransportI::SendPacket(...){
SendAsyncPacket* as = new SendAsyncPacket(...);
uv_queue_work(uv_default_loop(), as, bea::Async::empty, bea::Async::complete);
}
Implementation of the struct constructor, callJs() and destructor is left to the user.
This is because it is impossible (hard) to guess how the data should be copied automatically.
####

asyncStructName = vfunc.name + 'Async'
asyncStruct = new CodeBlock.ClassBlock "struct #{asyncStructName} : public bea::Async"
#constructor

saveArgsFnDeclaration = 'saveArguments(' + _.map(vfunc.args, (arg) -> arg.type.org + ' ' + arg.name).join(', ') + ')'
asyncStruct.add vfunc.type.org + ' ' + saveArgsFnDeclaration + '; //must be implemented manually'
#destructor
asyncStruct.add '~' + asyncStructName + '(); //must be implemented manually'
asyncStruct.add 'void callJS(); //must be implemented manually'


#_d_TransportI* _this;
asyncStruct.add @nativeClassName + '* _this;'
asyncStruct.add asyncStructName + "(#{@nativeClassName}* that): _this(that){}"
#add all function arguments as struct members
_.each vfunc.args, (arg) => asyncStruct.add arg.type.org + ' m_' + arg.name + ';'

classBlock.add asyncStruct



fn = implBlock.add new CodeBlock.FunctionBlock "#{vfunc.type.org} #{@nativeClassName}::#{vfunc.name}(#{dargs.join ', '})"
fn.add "v8::Locker v8locker;"
#fn.add "v8::Context::Scope v8ctxScope(bea::Global::context);"
fn.add "v8::HandleScope v8scope; v8::Handle<v8::Value> v8retVal;"
cif = fn.add new CodeBlock.CodeBlock "if (bea_derived_hasOverride(\"#{vfunc.name}\"))"

arglist = _.map vfunc.args, (arg) =>
snippets.ToJS(@nativeType(arg.type), @castArgument(arg), '')
if not vfunc.isAsync
#fn.add "v8::Locker v8locker;"
#fn.add "v8::Context::Scope v8ctxScope(bea::Global::context);"
fn.add "v8::HandleScope v8scope; v8::Handle<v8::Value> v8retVal;"
cif = fn.add new CodeBlock.CodeBlock "if (bea_derived_hasOverride(\"#{vfunc.name}\"))"
cif.add @v8ArgsFromArgs(vfunc)
cif.add "v8retVal = bea_derived_callJS(\"#{vfunc.name}\", #{vfunc.args.length}, v8args);"

if vfunc.args.length > 0
cif.add "v8::Handle<v8::Value> v8args[#{vfunc.args.length}] = {#{arglist.join(', ')}};"
fn.add "if (v8retVal.IsEmpty()) #{ret} _d_#{vfunc.name}(#{cargs.join ', '});"

if vfunc.type.rawType != 'void'
nativeType = @nativeType(vfunc.type)
#shit, this is messy, but no time now
if nativeType.indexOf('*') != -1 && !vfunc.type.isPointer then castBack = '*' else castBack = ''
fn.add "return #{castBack}" + snippets.FromJS(nativeType, "v8retVal", 0)
else
cif.add "v8::Handle<v8::Value> v8args[1];"

cif.add "v8retVal = bea_derived_callJS(\"#{vfunc.name}\", #{vfunc.args.length}, v8args);"

fn.add "if (v8retVal.IsEmpty()) #{ret} _d_#{vfunc.name}(#{cargs.join ', '});"

if vfunc.type.rawType != 'void'
nativeType = @nativeType(vfunc.type)
#shit, this is messy, but no time now
if nativeType.indexOf('*') != -1 && !vfunc.type.isPointer then castBack = '*' else castBack = ''
fn.add "return #{castBack}" + snippets.FromJS(nativeType, "v8retVal", 0)
fn.add asyncStructName + '* as = new ' + asyncStructName + '(this);'
fn.add 'as->saveArguments(' + _.map(vfunc.args, (arg) -> arg.name).join(', ') + ');'
fn.add 'uv_queue_work(uv_default_loop(), as, bea::Async::empty, bea::Async::complete);'

if vfunc.generateDefaultImpl
saveArgsFn = new CodeBlock.FunctionBlock "void #{@nativeClassName}::#{asyncStructName}::#{saveArgsFnDeclaration}"
_.each vfunc.args, (arg) -> saveArgsFn.add 'm_' + arg.name + ' = ' + arg.name + ';'

destructorFn = new CodeBlock.FunctionBlock "#{@nativeClassName}::#{asyncStructName}::~#{asyncStructName}()"
implBlock.add saveArgsFn
implBlock.add destructorFn
callJSFn = new CodeBlock.FunctionBlock "void #{@nativeClassName}::#{asyncStructName}::callJS()"
callJSFn.add @v8ArgsFromArgs(vfunc, 'm_')
ifBlock = new CodeBlock.CodeBlock 'if (!_this->bea_derived_tryCall("' + vfunc.name + '", ' + vfunc.args.length + ', v8args))'
ifBlock.add "_this->#{vfunc.name}(" + _.map(vfunc.args, (arg) -> 'm_' + arg.name).join(', ') + ');'
callJSFn.add ifBlock
implBlock.add callJSFn


return {
decla: classBlock
impl: implBlock
}


v8ArgsFromArgs: (vfunc, argPrefix = '') ->
arglist = _.map vfunc.args, (arg) =>
snippets.ToJS(@nativeType(arg.type), argPrefix + @castArgument(arg), '')

if vfunc.args.length > 0
return "v8::Handle<v8::Value> v8args[#{vfunc.args.length}] = {#{arglist.join(', ')}};"
else
return "v8::Handle<v8::Value> v8args[1];"


#Create the InitJSObject function, to be added to the CPP file.
#This function will be called by the exposing function
Expand Down Expand Up @@ -528,7 +631,6 @@ class ClassConverter
nativeType = type.fullType()

if @typeManager.isWrapped(type) then return nativeType + '*'

nativeType

#returns the proper cast for an argument name
Expand Down
9 changes: 9 additions & 0 deletions src/mgr.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,19 @@ class TypeManager
#Check if a type is native or is in the list of declared types
knownType: (type) ->
type.isNative() || _.any @types, (wt) -> wt.rawType == type.rawType && wt.namespace == type.namespace

#Check if a type has been declared, ignoring namespaces
declaredType: (type) ->
type.isNative() || _.any @types, (wt) -> wt.rawType == type.rawType


#Attempts to see if the value looks like a known (user-defined) type constructor
#returns false if it's not a type constructor
#returns true type if not
typeFromValue: (value) ->
thatType = false
#eg void fn(Mat& arg = Mat()) --> Mat() is a type constructor

mret = value.match(/(\w+)\s*\(.*\)/)
if mret?.length > 1
probableType = mret[1]
Expand Down
2 changes: 1 addition & 1 deletion src/typetest.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ _ = require 'underscore'
fs = require 'fs'
beautils = require('./beautils').u
utest = require './utest'
debugIt = require('./debugIt').debugIt


inspectType = (typeStr, ns) ->
console.log 'TypeStr: ' + typeStr + '; ns = ' + ns
Expand Down

0 comments on commit b46a8e1

Please sign in to comment.