Using (evile black magic) preprocessor programming (sorcery) and the autowrapper addon to AngelScript, I believe I have successfully made AngelScript work on an Emscripten build. I’m not certain it fully works, as my project has other problems that cause the Emscripten build to crash (that I’m pretty sure are unrelated), but I was at least able to Print a message from AngelScript.
Code and such (i.e. modified CMake module), and possibly a pull request, will be forth coming, but for those who are impatient, these are the important parts:
- there were 2 or 3 macros that needed to be changed in the Urho Library dealing with message handling and exception handling – they can use the original AngelScript macros, which have been duplicated prefixed with an underscore in my file (i.e. _asFUNCTION(fn))
- Requires c++11 support (one of the things that had to be changed in the CMake
- Uses BOOST Preprocessor for some macro stuff, but in theory it should be completely replaceable
//angelscript.h
//at top
#ifdef EMSCRIPTEN
#define AS_MAX_PORTABILITY
#endif
...
//at bottom of file, to overwrite the macros defined above
END_AS_NAMESPACE
#ifndef URHO_PROJECT_BUILD // you need to define this for building the Angelscript library within the Urho build, but not when building Urho itself
#include "mine.h"
#endif
//mine.hpp -- where the evil sorcery happens. Not all of these macros work, I'm pretty sure (especially some of the behavior ones), but there are no compilation errors when building Urho, and any fixes should be trivial
#ifdef EMSCRIPTEN
#include "wrap16.h"
//can help solve potential ... problems if we need it at the start: http://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros
#define asFUNCTION(f) F, f
#define _asFUNCTION(f) asFunctionPtr(f)
//#define asMETHOD(cls, f) cls, M, f
#define asMETHOD(cls, f) M, (cls,f) //package class and function for UNWRAPping later
#define UNWRAP(x,y) x,y// try -fmacro-backtrace-limit=0 on compile
#define _asMETHOD(c,m) asSMethodPtr<sizeof(void (c::*)())>::Convert((void (c::*)())(&c::m))
#define RegisterGlobalFunction(...) RegGlobalIndirect(__VA_ARGS__)
#define RegGlobalIndirect(decl, F, func, kind) RegisterGlobalFunction##F##kind (decl,func) // ... = decl or it = decl, cls for asCall_ThisCall
#define RegisterGlobalFunctionFasCALL_CDECL(decl,fun) RegisterGlobalFunction(decl, WRAP_FN(fun), asCALL_GENERIC); //assert(r >= 0);
#define RegisterGlobalFunctionFasCALL_STDCALL(decl,fun) RegisterGlobalFunction(decl, WRAP_FN(fun), asCALL_GENERIC); //assert(r >= 0);
#define RegisterGlobalFunctionMasCALL_THISCALL_ASGLOBAL(decl,fun) RegisterGlobalFunctionMasCALL_THISCALL_ASGLOBAL_2(decl,UNWRAP fun)
#define RegisterGlobalFunctionMasCALL_THISCALL_ASGLOBAL_2(decl, ...) RegisterGlobalFunction(decl, WRAP_MFN(__VA_ARGS__), asCALL_GENERIC); //assert(r >= 0);
#define RegisterGlobalFunctionFasCALL_GENERIC(decl,fun) RegisterGlobalFunction(decl, asFunctionPtr(fun), asCALL_GENERIC); //assert(r >= 0);
//object indirects
#define RegisterObjectMethod(...) RegObjectMethodIndirect(__VA_ARGS__)
//#define RegObjectIndirect(clsdcl,decl, F, func, kind) RegObjectIndirect_2(clsdcl,decl, F, UNWRAP func, kind)
#define RegObjectMethodIndirect(clsdcl,decl, F, clsfunc, kind) RegisterObjectMethod##F##kind (clsdcl,decl,clsfunc) // ... = decl or it = decl, cls for asCall_ThisCall
#define RegisterObjectMethodMasCALL_THISCALL(clsdcl,decl,clsfunc) RegisterObjectMethodMasCALL_THISCALL_2(clsdcl,decl,UNWRAP clsfunc)
#define RegisterObjectMethodMasCALL_THISCALL_2(clsdcl,decl,...) RegisterObjectMethodMasCALL_THISCALL_3(clsdcl,decl, WRAP_MFN(__VA_ARGS__));
#define RegisterObjectMethodMasCALL_THISCALL_3(clsdcl,decl,fn) RegisterObjectMethod(clsdcl,decl, fn, asCALL_GENERIC); //assert(r >= 0);
#define RegisterObjectMethodFasCALL_CDECL_OBJLAST(clsdcl,decl,fun) RegisterObjectMethod(clsdcl,decl, WRAP_OBJ_LAST(fun), asCALL_GENERIC); //assert(r >= 0);
#define RegisterObjectMethodFasCALL_CDECL_OBJFIRST(clsdcl,decl,fun) RegisterObjectMethod(clsdcl,decl, WRAP_OBJ_FIRST(fun), asCALL_GENERIC); //assert(r >= 0);
//#define RegisterObjectMethodMasCALL_CDECL_OBJECTFIRST(decl,cls,fun) RegisterObjectMethodMasCALL_THISCALL_ASGLOBAL_2(decl,UNWRAP fun)
//#define RegisterObjectMethodMasCALL_THISCALL_ASGLOBAL_2(decl, ...) RegisterObjectMethod(decl, WRAP_MFN(__VA_ARGS__), asCALL_GENERIC); //assert(r >= 0);
#define RegisterObjectMethodFasCALL_GENERIC(clsdcl,decl,fun) RegisterObjectMethod(clsdcl,decl, asMethodPtr(fun), asCALL_GENERIC); //assert(r >= 0);
////note:unfortunately, the WrapCon methods are for creating the constructor wrapper, which typically seems to be done anyways, so we should just handle it like Register Object Method with 1 extra parameter
//#define RegisterObjectBehaviour(...) RegObjectBehaviorIndirect(__VA__ARGS__)
//#define RegObjectBehaviorIndirect(clsdcl,behavior,decl,fn,kind) RegisterObjectBehavior##behavior(clsdcl,behavior,decl,fn,kind)
//#define RegisterObjectBehaviorasBEHAVE_CONSTRUCT(clsdcl,behavior,decl,fn,kind) RegisterObjectConstructor##F##kind (clsdcl,behavior,decl,fn)
//#define RegisterObjectConstructorFasCALL_CDECL_OBJECTLAST(clsdcl,behavior,decl,fn) RegisterObjectBehavior(clsdcl,behavior,decl,WRAP_OBJ_LAST(fn),asCALL_GENERIC)
////RegisterObjectConstructorF##kind (clsdcl,decl,clsfunc)
//NOTE: Angelscript uses the British English spelling __iour!
#define RegisterObjectBehaviour(...) RegObjectBehaviorIndirect(__VA_ARGS__)
//#define RegObjectIndirect(clsdcl,behavior,decl, F, func, kind) RegObjectIndirect_2(clsdcl,behavior,decl, F, UNWRAP func, kind)
#define RegObjectBehaviorIndirect(clsdcl,behavior,decl, F, clsfunc, kind) RegisterObjectBehavior##F##kind (clsdcl,behavior,decl,clsfunc) // ... = decl or it = decl, cls for asCall_ThisCall
#define RegisterObjectBehaviorMasCALL_THISCALL(clsdcl,behavior,decl,clsfunc) RegisterObjectBehaviorMasCALL_THISCALL_2(clsdcl,behavior,decl,UNWRAP clsfunc)
#define RegisterObjectBehaviorMasCALL_THISCALL_2(clsdcl,behavior,decl,...) RegisterObjectBehaviour(clsdcl,behavior,decl, WRAP_MFN(__VA_ARGS__), asCALL_GENERIC); //assert(r >= 0);
#define RegisterObjectBehaviorFasCALL_CDECL_OBJLAST(clsdcl,behavior,decl,...) RegisterObjectBehaviour(clsdcl,behavior,decl, WRAP_OBJ_LAST(__VA_ARGS__), asCALL_GENERIC); //assert(r >= 0);
#define RegisterObjectBehaviorFasCALL_CDECL_OBJFIRST(clsdcl,behavior,decl,...) RegisterObjectBehaviour(clsdcl,behavior,decl, WRAP_OBJ_FIRST(__VA_ARGS__), asCALL_GENERIC); //assert(r >= 0);
//#define RegisterObjectBehaviorMasCALL_CDECL_OBJECTFIRST(decl,cls,fun) RegisterObjectBehaviorMasCALL_THISCALL_ASGLOBAL_2(decl,UNWRAP fun)
//#define RegisterObjectBehaviorMasCALL_THISCALL_ASGLOBAL_2(decl, ...) RegisterObjectBehavior(decl, WRAP_MFN(__VA_ARGS__), asCALL_GENERIC); //assert(r >= 0);
#define RegisterObjectBehaviorFasCALL_CDECL(clsdcl,behavior,decl,...) RegisterObjectBehaviour(clsdcl,behavior,decl, WRAP_FN(__VA_ARGS__),asCALL_GENERIC);
#define RegisterObjectBehaviorFasCALL_GENERIC(clsdcl,behavior,decl,fun) RegisterObjectBehaviour(clsdcl,behavior,decl, asFunctionPtr(fun), asCALL_GENERIC); //assert(r >= 0);
//Parameter-Return versions:
#define asFUNCTIONPR(f,p,r) FPR, (f,p,r)
#define UNWRAP3(x,y,z) x,y,z
#define UNWRAP4(x,y,z,w) x,y,z,w
#define asMETHODPR(cls,f,p,r) MPR, (cls,f,p,r)
#if (defined(_MSC_VER) && _MSC_VER <= 1200) || (defined(__BORLANDC__) && __BORLANDC__ < 0x590)
// MSVC 6 has a bug that prevents it from properly compiling using the correct asFUNCTIONPR with operator >
// so we need to use ordinary C style cast instead of static_cast. The drawback is that the compiler can't
// check that the cast is really valid.
// BCC v5.8 (C++Builder 2006) and earlier have a similar bug which forces us to fall back to a C-style cast.
#define _asFUNCTIONPR(f,p,r) asFunctionPtr((void (*)())((r (*)p)(f)))
#elif (defined(_MSC_VER) && _MSC_VER >= 1900)
// Urho3D: VS2015 does not compile the C-style cast of the function pointer
#define _asFUNCTIONPR(f,p,r) asFunctionPtr(reinterpret_cast<void (*)()>(static_cast<r (*)p>(f)))
#else
#define _asFUNCTIONPR(f,p,r) asFunctionPtr((void (*)())(static_cast<r (*)p>(f)))
#endif
#define _asMETHODPR(c,m,p,r) asSMethodPtr<sizeof(void (c::*)())>::Convert(AS_METHOD_AMBIGUITY_CAST(r (c::*)p)(&c::m))
#define RegisterGlobalFunctionFPRasCALL_CDECL(decl,fun) RegisterGlobalFunctionFPRasCALL_CDECL_2(decl, UNWRAP3 fun);
#define RegisterGlobalFunctionFPRasCALL_CDECL_2(decl,...) RegisterGlobalFunction(decl, WRAP_FN_PR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterGlobalFunctionFPRasCALL_STDCALL(decl,fun) RegisterGlobalFunctionFPRasCALL_STDCALL_2(decl, UNWRAP3 fun);
#define RegisterGlobalFunctionFPRasCALL_STDCALL_2(decl,...) RegisterGlobalFunction(decl, WRAP_FN_PR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterGlobalFunctionMPRasCALL_THISCALL_ASGLOBAL(decl,fun) RegisterGlobalFunctionMasCALL_THISCALL_ASGLOBAL_2(decl,UNWRAP4 fun)
#define RegisterGlobalFunctionMPRasCALL_THISCALL_ASGLOBAL_2(decl, ...) RegisterGlobalFunction(decl, WRAP_MFN_PR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterGlobalFunctionFPRasCALL_GENERIC(decl,fun) RegisterGlobalFunctionFPRasCALL_GENERIC_2(decl, UNWRAP3 fun);
#define RegisterGlobalFunctionFPRasCALL_GENERIC_2(decl,...) RegisterGlobalFunction(decl, _asFUNCTIONPR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterObjectMethodMPRasCALL_THISCALL(clsdcl,decl,clsfunc) RegisterObjectMethodMPRasCALL_THISCALL_2(clsdcl,decl,UNWRAP4 clsfunc)
#define RegisterObjectMethodMPRasCALL_THISCALL_2(clsdcl,decl,...) RegisterObjectMethod(clsdcl,decl, WRAP_MFN_PR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterObjectMethodFPRasCALL_CDECL_OBJLAST(clsdcl,decl,fun) RegisterObjectMethodFPRasCALL_CDECL_OBJLAST_2(clsdcl,decl, UNWRAP3 fun);
#define RegisterObjectMethodFPRasCALL_CDECL_OBJLAST_2(clsdcl,decl,...) RegisterObjectMethod(clsdcl,decl, WRAP_OBJ_LAST_PR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterObjectMethodFPRasCALL_CDECL_OBJFIRST(clsdcl,decl,fun) RegisterObjectMethodFPRasCALL_CDECL_OBJFIRST_2(clsdcl,decl, UNWRAP3 fun);
#define RegisterObjectMethodFPRasCALL_CDECL_OBJFIRST_2(clsdcl,decl,...) RegisterObjectMethod(clsdcl,decl, WRAP_OBJ_FIRST_PR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterObjectMethodFPRasCALL_GENERIC(clsdcl,decl,fun) RegisterObjectMethodFPRasCALL_GENERIC_2(clsdcl,decl, UNWRAP3 fun);
#define RegisterObjectMethodFPRasCALL_GENERIC_2(clsdcl,decl,...) RegisterObjectMethod(clsdcl,decl, _asFUNCTIONPR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterObjectBehaviorMPRasCALL_THISCALL(clsdcl,behavior,decl,clsfunc) RegisterObjectBehaviorMPRasCALL_THISCALL_2(clsdcl,behavior,decl,UNWRAP4 clsfunc)
#define RegisterObjectBehaviorMPRasCALL_THISCALL_2(clsdcl,behavior,decl,...) RegisterObjectBehaviour(clsdcl,behavior,decl, WRAP_MFN_PR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterObjectBehaviorFPRasCALL_CDECL_OBJLAST(clsdcl,behavior,decl,clsfunc) RegisterObjectBehaviour(clsdcl,behavior,decl, WRAP_OBJ_LAST_PR clsfunc, asCALL_GENERIC);
#define RegisterObjectBehaviorFPRasCALL_CDECL_OBJFIRST(clsdcl,behavior,decl,...) RegisterObjectBehaviour(clsdcl,behavior,decl, WRAP_OBJ_FIRST_PR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterObjectBehaviorFPRasCALL_GENERIC(clsdcl,behavior,decl,...) RegisterObjectBehaviour(clsdcl,behavior,decl, _asFUNCTIONPR(__VA_ARGS__), asCALL_GENERIC);
#define RegisterObjectBehaviorFPRasCALL_CDECL(clsdcl,behavior,decl,clsfunc) RegisterObjectBehaviour(clsdcl,behavior,decl, WRAP_FN_PR clsfunc, asCALL_GENERIC);
#define RegisterStringFactory(...) EV(RegisterStringFactory_2(__VA_ARGS__))
#define RegisterStringFactory_2(str,F,params,kind) RegisterStringFactory(str, WRAP_FN (params), asCALL_GENERIC);
/*#define SetExceptionCallback(...) SetExceptionCallback_2(__VA_ARGS__)
#define SetExceptionCallback_2(M,params,obj,kind) SetExceptionCallback(WRAP_MFN params, obj, asCALL_GENERIC);
#define SetMessageCallback(...) SetMessageCallback_2(__VA_ARGS__)
#define SetMessageCallback_2(M,params,obj,kind) SetMessageCallback(WRAP_MFN params, obj, asCALL_GENERIC);
*/
#endif
//wrap16.h -- the file generated by the angelscript autowrapper addon that supports 16 parameter arguments (because of Matrix4x4)
//I believe the only changes to the generate file were adding the "wrap.h" include and commenting out the WRAP_MFN_PR macro
...
} // end namespace gw
#include "wrap.h"
#endif
/*
* wrap.h -- this file contains my replacement of the WRAP_MFN_PR macro, which failed to compile due to complaints about template argument deduction or some such
* It requires c++11 for the lambdas, and it requires no-argument functions to be given as () and not (void) (I believe there was a single case of this in the Urho library)
*
* Created on: Apr 6, 2016
* Author: SirNate0
*/
#pragma once
#include <type_traits>
//#include "AngelScript/angelscript.h"
#include "angelscript.h"
#include <boost/preprocessor.hpp>
//Return void enable if: http://stackoverflow.com/questions/12002447/template-method-enable-if-specialization
//# do with // http://stackoverflow.com/questions/5588855/standard-alternative-to-gccs-va-args-trick
#define EV(...) __VA_ARGS__
//inline asSFuncPtr asFunctionPtr<asGENFUNC_t>(asGENFUNC_t func)
//{
// // Mark this as a generic function
// asSFuncPtr p(1);
// p.ptr.f.func = reinterpret_cast<asFUNCTION_t>(func);
// return p;
//}
typedef void (*asGENFUNC_t)(asIScriptGeneric *);
//#define DO_2(_,a,b) GET(a,0), GET(b,1)//Proxy<a>::cast(gen->arguments[0])
//#define DO_4(_,a,b,c,d) GET(a,0), GET(b,1),GET(c,2), GET(d,3)//(a*)(gen->arguments[0]),(b*)(gen->arguments[1]),(a*)(gen->arguments[2]),(b*)(gen->arguments[3])
//#define DO_6(_,a,b,c,d,e,f) GET(a,0), GET(b,1),GET(c,2), GET(d,3),GET(e,4), GET(f,5)//(a*)(gen->arguments[0]),(b*)(gen->arguments[1]),(a*)(gen->arguments[2]),(b*)(gen->arguments[3]),(a*)(gen->arguments[4]),(b*)(gen->arguments[5])
//
////#define DO_1(_,a) static_cast<Proxy <a> *>(gen->arguments[0])->value
//#define DO_1(_,a) Proxy<a>::cast(gen->arguments[0])
//#define DO_3(_,a,b,c) GET(a,0), GET(b,1),GET(c,2)
//#define DO_5(_,a,b,c,d,e) GET(a,0), GET(b,1),GET(c,2), GET(d,3),GET(e,4)
//
//#define DO_0(_)
//
//#define GET7TH(_,arg1, arg2, arg3, arg4, arg5, arg6, arg7,...) arg7
//#define DO_CHOOSER(...) GET7TH(__VA_ARGS__,DO_6,DO_5,DO_4,DO_3,DO_2,DO_1,DO_0)
//
//#define DO(...) DO_CHOOSER(_,##__VA_ARGS__)(_,##__VA_ARGS__)
//
//#define CHOOSE(...)
#define UNWRAP(...) __VA_ARGS__
#define EXTRACT_CONST(...) (__VA_ARGS__),
// from http://stackoverflow.com/questions/18851889/boost-preprocessor-skip-if-variadic-is-empty
// based on the: http://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments
#define __ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define __HAS_COMMA(...) __ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
#define __TRIGGER_PARENTHESIS_(...) ,
#define __PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
#define __IS_EMPTY_CASE_0001 ,
#define __IS_EMPTY(_0, _1, _2, _3) __HAS_COMMA(__PASTE5(__IS_EMPTY_CASE_, _0, _1, _2, _3))
#define TUPLE_IS_EMPTY(...) \
__IS_EMPTY( \
/* test if there is just one argument, eventually an empty one */ \
__HAS_COMMA(__VA_ARGS__), \
/* test if _TRIGGER_PARENTHESIS_ together with the argument adds a comma */ \
__HAS_COMMA(__TRIGGER_PARENTHESIS_ __VA_ARGS__), \
/* test if the argument together with a parenthesis adds a comma */ \
__HAS_COMMA(__VA_ARGS__ (/*empty*/)), \
/* test if placing it between _TRIGGER_PARENTHESIS_ and the parenthesis adds a comma */ \
__HAS_COMMA(__TRIGGER_PARENTHESIS_ __VA_ARGS__ (/*empty*/)) \
)
#define __GEN_EMPTY_ARGS(...)
#define __GEN_NONEMPTY_ARGS_CB(unused, data, idx, elem) \
, static_cast<gw::Proxy <elem> *>(gen->GetAddressOfArg(idx))->value
// , Proxy<elem>::cast(gen->arguments[idx])
#define __GEN_NONEMPTY_ARGS(seq) \
BOOST_PP_SEQ_FOR_EACH_I( \
__GEN_NONEMPTY_ARGS_CB \
,~ \
,seq \
)
#define CreateGenericFromMethod_2(...) EV(CreateGenericFromMethod_3(__VA_ARGS__))
#define CreateGenericFromMethod_3(class_t,method,parameters,const,return_t) \
asFunctionPtr<asGENFUNC_t>([] (asIScriptGeneric* gen) {\
BOOST_PP_CAT(wrap::call,const) <return_t,class_t BOOST_PP_COMMA_IF(BOOST_PP_IF(TUPLE_IS_EMPTY parameters ,0,1)) EV parameters>\
(gen,/*&(Proxy<class_t>::cast(gen->GetObject()))*/((class_t*)gen->GetObject()), static_cast<return_t (class_t::*) parameters const>(&class_t::method)\
BOOST_PP_IF( \
TUPLE_IS_EMPTY parameters \
,__GEN_EMPTY_ARGS \
,__GEN_NONEMPTY_ARGS \
)(BOOST_PP_TUPLE_TO_SEQ( parameters )) \
);\
})
#define WRAP_MFN_PR(class_t,method,parameters,return_t) EV(EV(EV(CreateGenericFromMethod_2(class_t,method,EXTRACT_CONST parameters,return_t))))
//typedef asIScriptGeneric Gen;
namespace wrap
{
template<class R, class C, typename ...P>
void call_helper(::std::false_type, asIScriptGeneric*g, C*obj, R (C::*fn)(P...), P... args)
{
// cout << "do non-void";
// g->return_value = new R ((obj->*fn)(args...));
new (g->GetAddressOfReturnLocation()) gw::Proxy<R> ((obj->*fn)(args...));
}
template<class R, class C, typename ...P>
void call_helper(::std::true_type,asIScriptGeneric*g, C*obj, R (C::*fn)(P...), P... args)
{
// cout << "do void";
(obj->*fn)(args...);
// g->return_value = nullptr;
}
template<class R, class C, typename ...P>
void call_helper(::std::false_type, asIScriptGeneric*g, C*obj, R (C::*fn)(P...)const, P... args)
{
// cout << "do non-void const";
// g->return_value = new R ((obj->*fn)(args...));
new (g->GetAddressOfReturnLocation()) gw::Proxy<R> ((obj->*fn)(args...));
}
template<class R, class C, typename ...P>
void call_helper(::std::true_type,asIScriptGeneric*g, C*obj, R (C::*fn)(P...)const, P... args)
{
// cout << "do void const";
(obj->*fn)(args...);
// g->return_value = nullptr;
}
template<class R, class C, typename ...P>
void call(asIScriptGeneric*g, C*obj, R (C::*fn)(P...), P... args)
{
call_helper<R,C,P...>(::std::is_void<R>(),
g,
obj,
fn,
args...);
}
template<class R, class C, typename ...P>
void callconst(asIScriptGeneric*g, C*obj, R (C::*fn)(P...)const, P... args)
{
call_helper<R,C,P...>(std::is_void<R>(),
g,
obj,
fn,
args...);
}
}