|
// An example of how to bind a C++ class in four scripting languages: lua, squirrel, mruby, and angelscript. |
|
// Written by James S Urquhart ( http://www.cuppadev.co.uk/ ). Feel free to experiment! |
|
|
|
#include <stdarg.h> |
|
#include <stdio.h> |
|
#include <assert.h> |
|
|
|
// Lua includes |
|
extern "C" |
|
{ |
|
#include <lua.h> |
|
#include <lauxlib.h> |
|
#include <lualib.h> |
|
} |
|
|
|
// Squirrel includes |
|
#include <squirrel.h> |
|
#include <sqstdio.h> |
|
#include <sqstdaux.h> |
|
|
|
// mruby includes |
|
#include <mruby.h> |
|
#include <mruby/class.h> |
|
#include <mruby/compile.h> |
|
#include <mruby/data.h> |
|
#include <mruby/proc.h> |
|
#include <mruby/string.h> |
|
#include <mruby/value.h> |
|
#include <mruby/variable.h> |
|
#include <mruby/compile.h> |
|
|
|
// Angelscript includes |
|
#include <angelscript.h> |
|
#include <scriptstdstring/scriptstdstring.h> |
|
|
|
// Notes for each binding: |
|
// - TestObject is binded as a class object for each scripting language |
|
// - TestObject has the following functions in script: getAnalysis, testException |
|
// - TestObject has the following properties in script: message, number |
|
// - "testException" tests each languages ability to handle errors, and ensures any C++ destructors |
|
// for local stack variables can be called when an error occurs. |
|
// - A sample script is loaded from memory |
|
// - Each sample script contains a function "doError" which throws an exception |
|
// - A "main" function is called with an instance of a binded TestObject created in C++ |
|
|
|
// ======================= |
|
// Test Object to bind |
|
// ======================= |
|
|
|
class TestObject |
|
{ |
|
public: |
|
|
|
TestObject() |
|
{ |
|
printf("TestObject created\n"); |
|
mNumber = 0; |
|
refCount = 1; |
|
} |
|
|
|
virtual ~TestObject() |
|
{ |
|
printf("TestObject has been destroyed\n"); |
|
} |
|
|
|
int refCount; |
|
|
|
// properties |
|
std::string mMessage; |
|
float mNumber; |
|
|
|
// functions |
|
std::string getMessage() |
|
{ |
|
return mMessage; |
|
} |
|
|
|
void setMessage(std::string value) |
|
{ |
|
mMessage = value; |
|
} |
|
|
|
std::string getAnalysis() |
|
{ |
|
char buf[64]; |
|
snprintf(buf, 64, "The number is: %f", mNumber); |
|
return buf; |
|
} |
|
|
|
void addRef() |
|
{ |
|
refCount++; |
|
} |
|
|
|
void decRef() |
|
{ |
|
refCount--; |
|
if (refCount <= 0) |
|
delete this; |
|
} |
|
}; |
|
|
|
// ======================= |
|
// Example object to test scope |
|
// ======================= |
|
|
|
class ScopedVariable |
|
{ |
|
public: |
|
ScopedVariable() |
|
{ |
|
printf("ScopedVariable created\n"); |
|
} |
|
|
|
virtual ~ScopedVariable() |
|
{ |
|
printf("ScopedVariable destroyed\n"); |
|
} |
|
}; |
|
|
|
// ======================= |
|
// Lua Code |
|
// ======================= |
|
|
|
// |
|
// Lua code notes: |
|
// This is a simple binding of TestObject. A metatable is assigned to userdata instances to expose an instance of an object. |
|
// The metatable overrides __index and __newindex, which means all property and function access goes through |
|
// lua_testobject_setproperty and lua_testobject_getproperty. |
|
// Binding functions is a bit tricky in this scenario as if you simply use lua_pushcclosure to push a c function to the stack, |
|
// no "self" variable will be present on the stack. To resolve this, the instance of the object is associated with the function, |
|
// and retrieved via lua_upvalueindex. |
|
// |
|
|
|
// Makes an instance of testobject in lua |
|
// obj = TestObject() |
|
int lua_make_testobject(lua_State *L) |
|
{ |
|
TestObject *obj = new TestObject(); |
|
|
|
TestObject** ptr = (TestObject**)lua_newuserdata(L, sizeof(TestObject*)); |
|
*ptr = obj; |
|
|
|
// simple binding, just make a userdata with a metatable |
|
int userdata = lua_gettop(L); |
|
luaL_getmetatable(L, "TestObject"); |
|
lua_setmetatable(L, userdata); |
|
|
|
return 1; |
|
} |
|
|
|
// __gc |
|
int lua_testobject_gc(lua_State *L) |
|
{ |
|
TestObject** obj = static_cast<TestObject**>(luaL_checkudata(L, -1, "TestObject")); |
|
if (*obj) |
|
(*obj)->decRef(); |
|
return 0; |
|
} |
|
|
|
// .message |
|
int lua_testobject_getMessage(lua_State *L) |
|
{ |
|
TestObject** obj = static_cast<TestObject**>(luaL_checkudata(L, -1, "TestObject")); |
|
if (*obj) |
|
{ |
|
lua_pushstring(L, (*obj)->getMessage().c_str()); |
|
} |
|
else |
|
{ |
|
lua_pushnil(L); |
|
} |
|
return 1; |
|
} |
|
|
|
// .message = <str> |
|
int lua_testobject_setMessage(lua_State *L) |
|
{ |
|
TestObject** obj = static_cast<TestObject**>(luaL_checkudata(L, -2, "TestObject")); |
|
if (*obj) |
|
{ |
|
printf("testObject:setMessage(%s)\n", lua_tostring(L, -1)); |
|
(*obj)->setMessage(lua_tostring(L, -1)); |
|
} |
|
return 0; |
|
} |
|
|
|
// .number |
|
int lua_testobject_getNumber(lua_State *L) |
|
{ |
|
TestObject** obj = static_cast<TestObject**>(luaL_checkudata(L, -1, "TestObject")); |
|
if (*obj) |
|
{ |
|
lua_pushnumber(L, (*obj)->mNumber); |
|
} |
|
else |
|
{ |
|
lua_pushnil(L); |
|
} |
|
return 1; |
|
} |
|
|
|
// .number = <number> |
|
int lua_testobject_setNumber(lua_State *L) |
|
{ |
|
TestObject** obj = static_cast<TestObject**>(luaL_checkudata(L, -2, "TestObject")); |
|
if (*obj) |
|
{ |
|
printf("testObject:setNumber(%f)\n", lua_tonumber(L, -1)); |
|
(*obj)->mNumber = lua_tonumber(L, -1); |
|
} |
|
return 0; |
|
} |
|
|
|
// print stack. useful if you get confused. |
|
void lua_debug_print_stack(lua_State *L) |
|
{ |
|
int newtop = lua_gettop(L); |
|
printf("--------\n"); |
|
for (int i=1; i<newtop+1; i++) |
|
{ |
|
printf("Stack value[%i]: %s\n", i, lua_tostring(L, i)); |
|
} |
|
printf("--------\n"); |
|
} |
|
|
|
// property setter |
|
int lua_testobject_setproperty(lua_State *L) |
|
{ |
|
int top = lua_gettop(L); |
|
TestObject** obj = static_cast<TestObject**>(luaL_checkudata(L, -3, "TestObject")); |
|
|
|
//printf("setProperty: key=%s\n", lua_tostring(L, top-1)); |
|
//printf("setProperty: value=%s\n", lua_tostring(L, top)); |
|
|
|
// Push values in the correct order (table, value) |
|
lua_pushvalue(L, -3); |
|
lua_pushvalue(L, top); |
|
|
|
const char *key = lua_tostring(L, top-1); |
|
int result = 0; |
|
if (strcmp(key, "number") == 0) |
|
{ |
|
result = lua_testobject_setNumber(L); |
|
} |
|
else if (strcmp(key, "message") == 0) |
|
{ |
|
result = lua_testobject_setMessage(L); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
// .getAnalysis() |
|
int lua_testobject_getAnalysis(lua_State *L) |
|
{ |
|
// NOTE: since we use pushcclosure with a value, we need to use lua_upvalueindex |
|
// to grab the appropriate stack insex to get our userdata object |
|
TestObject** obj = static_cast<TestObject**>(luaL_checkudata(L, -1, "TestObject")); |
|
if (*obj) |
|
{ |
|
lua_pushstring(L, (*obj)->getAnalysis().c_str()); |
|
} |
|
else |
|
{ |
|
lua_pushnil(L); |
|
} |
|
return 1; |
|
} |
|
|
|
int lua_testobject_testException(lua_State *L) |
|
{ |
|
// NOTE: this tests if lua errors will destroy the scope variable with lua_pcall. |
|
// If you replace lua_pcall with lua_call, scope will not be destroyed! |
|
ScopedVariable scope; |
|
TestObject** obj = static_cast<TestObject**>(luaL_checkudata(L, -1, "TestObject")); |
|
if (*obj) |
|
{ |
|
lua_getglobal(L, "doError"); |
|
if (lua_pcall(L, 0, 0, 0) != 0) |
|
{ |
|
printf("Error running doError: %s\n", lua_tostring(L, -1)); |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
// property getter |
|
int lua_testobject_getproperty(lua_State *L) |
|
{ |
|
int top = lua_gettop(L); |
|
TestObject** obj = static_cast<TestObject**>(luaL_checkudata(L, top-1, "TestObject")); |
|
|
|
//printf("getProperty: key=%s\n", lua_tostring(L, top)); |
|
|
|
// Push table to the top for our callbacks (table) |
|
lua_pushvalue(L, -2); |
|
|
|
const char *key = lua_tostring(L, top); |
|
int result = 1; |
|
if (strcmp(key, "number") == 0) |
|
{ |
|
result = lua_testobject_getNumber(L); |
|
} |
|
else if (strcmp(key, "message") == 0) |
|
{ |
|
result = lua_testobject_getMessage(L); |
|
} |
|
else if (strcmp(key, "getAnalysis") == 0) |
|
{ |
|
lua_pushcclosure(L, &lua_testobject_getAnalysis, 1); |
|
} |
|
else if (strcmp(key, "testException") == 0) |
|
{ |
|
lua_pushcclosure(L, &lua_testobject_testException, 1); |
|
} |
|
else |
|
{ |
|
lua_pushnil(L); |
|
} |
|
|
|
// NOTE: lua will automatically remove items in the stack below result |
|
|
|
return result; |
|
} |
|
|
|
void bind_lua(lua_State *L) |
|
{ |
|
luaopen_io(L); // provides io.* |
|
luaopen_base(L); |
|
luaopen_table(L); |
|
luaopen_string(L); |
|
luaopen_math(L); |
|
|
|
// Define TestObject constructor function |
|
lua_pushcfunction(L, &lua_make_testobject); |
|
lua_setglobal(L, "TestObject"); |
|
|
|
// Register TestObject in the lua registry |
|
luaL_newmetatable(L, "TestObject"); |
|
int metatable = lua_gettop(L); |
|
|
|
// NOTE: we don't define any properties or values here. Instead lookup of those |
|
// is delegated to __index & __newindex. |
|
|
|
// garbage collection |
|
lua_pushstring(L, "__gc"); |
|
lua_pushcfunction(L, &lua_testobject_gc); |
|
lua_settable(L, metatable); |
|
|
|
// property getter |
|
lua_pushstring(L, "__index"); |
|
lua_pushcfunction(L, lua_testobject_getproperty); |
|
lua_settable(L, metatable); |
|
|
|
// property setter |
|
lua_pushstring(L, "__newindex"); |
|
lua_pushcfunction(L, lua_testobject_setproperty); |
|
lua_settable(L, metatable); |
|
|
|
} |
|
|
|
void eval_lua(const char *str) |
|
{ |
|
lua_State *L = luaL_newstate(); |
|
bind_lua(L); |
|
|
|
// Run the script |
|
if (luaL_loadstring(L, str) != 0) |
|
{ |
|
printf("Error parsing script: %s\n", lua_tostring(L, -1)); |
|
} |
|
|
|
if (lua_pcall(L, 0, 0, 0) != 0) |
|
{ |
|
printf("Error running script: %s\n", lua_tostring(L, -1)); |
|
} |
|
|
|
// Grab main function |
|
lua_getglobal(L, "main"); |
|
// Make a test object on the stack |
|
lua_make_testobject(L); |
|
|
|
// Grab the pointer to our created TestObject |
|
TestObject** obj = static_cast<TestObject**>(luaL_checkudata(L, -1, "TestObject")); |
|
if (obj) |
|
{ |
|
(*obj)->setMessage("Created from C++"); |
|
} |
|
|
|
// Execute the "main" function |
|
if (lua_pcall(L, 1, 0, 0) != 0) |
|
{ |
|
printf("Error running main: %s\n", lua_tostring(L, -1)); |
|
} |
|
|
|
lua_close(L); |
|
} |
|
|
|
// ======================= |
|
// Squirrel Code |
|
// ======================= |
|
|
|
// |
|
// Squirrel code notes: |
|
// This is similar to the lua binding, except we bind to a class instead of a combined userdata object & table object. |
|
// Property access goes through a "_get" and "_set" handler, though unlike the lua binding functions are bound directly to |
|
// the class object so we only need to worry about handling "message" and "number" in these handlers. |
|
// |
|
|
|
// print(...) |
|
void squirrel_printfunc(HSQUIRRELVM v, const SQChar *s, ...) |
|
{ |
|
va_list arglist; |
|
va_start(arglist, s); |
|
vprintf(s, arglist); |
|
va_end(arglist); |
|
} |
|
|
|
// __gc |
|
SQInteger squirrel_gc_testobject(SQUserPointer ptr, SQInteger size) |
|
{ |
|
if (!ptr) |
|
return 0; |
|
|
|
TestObject *obj = (TestObject*)ptr; |
|
obj->decRef(); |
|
return 0; |
|
} |
|
|
|
// obj = TestObject() |
|
SQInteger squirrel_make_testobject(HSQUIRRELVM v) |
|
{ |
|
TestObject *obj = new TestObject(); |
|
if (SQ_FAILED(sq_setinstanceup(v, -1, (SQUserPointer)obj))) |
|
{ |
|
printf("WTF!\n"); |
|
} |
|
sq_setreleasehook(v, -1, &squirrel_gc_testobject); |
|
return 1; |
|
} |
|
|
|
// .getAnalysis() |
|
SQInteger squirrel_testobject_getAnalysis(HSQUIRRELVM v) |
|
{ |
|
TestObject* obj = NULL; |
|
if (SQ_FAILED(sq_getinstanceup(v, 1, (SQUserPointer*)&obj, (SQUserPointer)&squirrel_make_testobject))) { \ |
|
return 0; |
|
} |
|
|
|
sq_pushstring(v, obj->getAnalysis().c_str(), -1); |
|
return 1; |
|
} |
|
|
|
// .message |
|
SQInteger squirrel_testobject_getMessage(HSQUIRRELVM v) |
|
{ |
|
TestObject* obj = NULL; |
|
if (SQ_FAILED(sq_getinstanceup(v, 1, (SQUserPointer*)&obj, (SQUserPointer)&squirrel_make_testobject))) { |
|
return 0; |
|
} |
|
|
|
sq_pushstring(v, obj->getMessage().c_str(), -1); |
|
return 1; |
|
} |
|
|
|
// .message = value |
|
SQInteger squirrel_testobject_setMessage(HSQUIRRELVM v) |
|
{ |
|
TestObject* obj = NULL; |
|
if (SQ_FAILED(sq_getinstanceup(v, 1, (SQUserPointer*)&obj, (SQUserPointer)&squirrel_make_testobject))) { |
|
return 0; |
|
} |
|
|
|
const SQChar* value = NULL; |
|
sq_getstring(v, -1, &value); |
|
|
|
obj->setMessage(value); |
|
return 0; |
|
} |
|
|
|
// .number |
|
SQInteger squirrel_testobject_getNumber(HSQUIRRELVM v) |
|
{ |
|
TestObject* obj = NULL; |
|
if (SQ_FAILED(sq_getinstanceup(v, 1, (SQUserPointer*)&obj, (SQUserPointer)&squirrel_make_testobject))) { |
|
return 0; |
|
} |
|
|
|
sq_pushfloat(v, obj->mNumber); |
|
return 1; |
|
} |
|
|
|
// .number = value |
|
SQInteger squirrel_testobject_setNumber(HSQUIRRELVM v) |
|
{ |
|
TestObject* obj = NULL; |
|
if (SQ_FAILED(sq_getinstanceup(v, 1, (SQUserPointer*)&obj, (SQUserPointer)&squirrel_make_testobject))) { |
|
return 0; |
|
} |
|
|
|
sq_getfloat(v, -1, &obj->mNumber); |
|
return 0; |
|
} |
|
|
|
// .testException() |
|
SQInteger squirrel_testobject_testException(HSQUIRRELVM v) |
|
{ |
|
ScopedVariable scope; |
|
TestObject* obj = NULL; |
|
if (SQ_FAILED(sq_getinstanceup(v, 1, (SQUserPointer*)&obj, (SQUserPointer)&squirrel_make_testobject))) { |
|
return 0; |
|
} |
|
|
|
// Call doError |
|
SQInteger top = sq_gettop(v); |
|
sq_pushroottable(v); |
|
sq_pushstring(v, "doError", -1); |
|
sq_get(v, -2); |
|
SQInteger doErrorFunc = sq_gettop(v); |
|
|
|
if (SQ_FAILED(sq_call(v, 2, SQFalse, SQTrue))) |
|
{ |
|
printf("Error running doError\n"); |
|
return 0; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
// .tostring() |
|
SQInteger squirrel_testobject_tostring(HSQUIRRELVM v) |
|
{ |
|
TestObject* obj = NULL; |
|
if (SQ_FAILED(sq_getinstanceup(v, 1, (SQUserPointer*)&obj, (SQUserPointer)&squirrel_make_testobject))) { |
|
return 0; |
|
} |
|
|
|
char buf[128]; |
|
snprintf(buf, 128, "TestObject: %p", obj); |
|
sq_pushstring(v, buf, -1); |
|
return 1; |
|
} |
|
|
|
// .slot = value |
|
SQInteger squirrel_testobject_set(HSQUIRRELVM v) |
|
{ |
|
TestObject* obj = NULL; |
|
if (SQ_FAILED(sq_getinstanceup(v, -3, (SQUserPointer*)&obj, (SQUserPointer)&squirrel_make_testobject))) { |
|
return 0; |
|
} |
|
const SQChar *key; |
|
|
|
if (SQ_FAILED(sq_getstring(v, -2, &key))) |
|
{ |
|
return sq_throwerror(v, _SC("Invalid property name")); |
|
} |
|
|
|
SQInteger result = 0; |
|
if (strcmp(key, "number") == 0) |
|
{ |
|
result = squirrel_testobject_setNumber(v); |
|
} |
|
else if (strcmp(key, "message") == 0) |
|
{ |
|
result = squirrel_testobject_setMessage(v); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
// .slot |
|
SQInteger squirrel_testobject_get(HSQUIRRELVM v) |
|
{ |
|
TestObject* obj = NULL; |
|
if (SQ_FAILED(sq_getinstanceup(v, -2, (SQUserPointer*)&obj, (SQUserPointer)&squirrel_make_testobject))) { |
|
return 0; |
|
} |
|
const SQChar *key; |
|
|
|
if (SQ_FAILED(sq_getstring(v, -1, &key))) |
|
{ |
|
return sq_throwerror(v, _SC("Invalid property name")); |
|
} |
|
|
|
SQInteger result = 0; |
|
if (strcmp(key, "number") == 0) |
|
{ |
|
result = squirrel_testobject_getNumber(v); |
|
} |
|
else if (strcmp(key, "message") == 0) |
|
{ |
|
result = squirrel_testobject_getMessage(v); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
void bind_squirrel(HSQUIRRELVM v) |
|
{ |
|
sq_newclass(v, SQFalse); |
|
SQInteger klass = sq_gettop(v); |
|
sq_settypetag(v, -1, (SQUserPointer)&squirrel_make_testobject); |
|
|
|
sq_pushroottable(v); |
|
sq_pushstring(v, _SC("TestObject"), -1); |
|
sq_push(v, klass); |
|
sq_newslot(v, -3, SQFalse); |
|
sq_pop(v, 1); |
|
|
|
// Bind functions for TestObject |
|
|
|
sq_pushstring(v, _SC("_set"), -1); |
|
sq_newclosure(v, &squirrel_testobject_set, 0); |
|
sq_newslot(v, klass, false); |
|
|
|
sq_pushstring(v, _SC("_get"), -1); |
|
sq_newclosure(v, &squirrel_testobject_get, 0); |
|
sq_newslot(v, klass, false); |
|
|
|
sq_push(v, klass); |
|
sq_pushstring(v, "constructor", -1); |
|
sq_newclosure(v, &squirrel_make_testobject, 0); |
|
sq_setnativeclosurename(v, -1, "constructor"); |
|
sq_newslot(v, klass, SQFalse); |
|
|
|
sq_pushstring(v, "getAnalysis", -1); |
|
sq_newclosure(v, &squirrel_testobject_getAnalysis, 0); |
|
sq_setnativeclosurename(v, -1, "getAnalysis"); |
|
sq_newslot(v, klass, SQFalse); |
|
|
|
sq_pushstring(v, "testException", -1); |
|
sq_newclosure(v, &squirrel_testobject_testException, 0); |
|
sq_setnativeclosurename(v, -1, "testException"); |
|
sq_newslot(v, klass, SQFalse); |
|
|
|
sq_pushstring(v, "tostring", -1); |
|
sq_newclosure(v, &squirrel_testobject_tostring, 0); |
|
sq_setnativeclosurename(v, -1, "tostring"); |
|
sq_newslot(v, klass, SQFalse); |
|
|
|
sq_pop(v, 1); |
|
|
|
sq_pop(v, 1); |
|
} |
|
|
|
void eval_squirrel(const char *str) |
|
{ |
|
HSQUIRRELVM v; |
|
v = sq_open(1024); |
|
|
|
bind_squirrel(v); |
|
|
|
sqstd_seterrorhandlers(v); |
|
sq_setprintfunc(v, squirrel_printfunc, squirrel_printfunc); //sets the print function |
|
|
|
// Evaluate the script |
|
sq_pushroottable(v); |
|
if (SQ_FAILED(sq_compilebuffer(v, str, strlen(str), "test.nut", SQTrue))) { |
|
printf("Error parsing script\n"); |
|
return; |
|
} |
|
sq_pushroottable(v); |
|
if (SQ_FAILED(sq_call(v,1,0,SQTrue))) |
|
{ |
|
printf("Error running script\n"); |
|
return; |
|
} |
|
|
|
// find main() |
|
SQInteger top = sq_gettop(v); |
|
sq_pushroottable(v); |
|
sq_pushstring(v, "main", -1); |
|
if (SQ_FAILED(sq_get(v, -2))) |
|
{ |
|
printf("Failed to find main()\n"); |
|
return; |
|
} |
|
SQInteger mainFunc = sq_gettop(v); |
|
|
|
// construct TestObject |
|
sq_pushroottable(v); |
|
sq_pushstring(v, _SC("TestObject"), -1); |
|
sq_get(v, -2); |
|
sq_createinstance(v, -1); |
|
squirrel_make_testobject(v); // construct TestObject |
|
SQInteger testObject = sq_gettop(v); |
|
|
|
// Set message for our created TestObject |
|
TestObject* obj = NULL; |
|
sq_getinstanceup(v, -1, (SQUserPointer*)&obj, (SQUserPointer)&squirrel_make_testobject); |
|
obj->setMessage("Created from C++!"); |
|
|
|
// call main() |
|
sq_push(v, mainFunc); |
|
sq_pushroottable(v); |
|
sq_push(v, testObject); |
|
|
|
if (SQ_FAILED(sq_call(v, 2, SQFalse, SQTrue))) |
|
{ |
|
printf("Failed to execute main()\n"); |
|
} |
|
sq_settop(v, top); |
|
|
|
// cleanup |
|
sq_close(v); |
|
} |
|
|
|
|
|
// ======================= |
|
// Ruby Code |
|
// ======================= |
|
|
|
// |
|
// Ruby code notes: |
|
// TestObject is binded to a ruby class, pretty simple. |
|
// Unlike lua and squirrel this is a pretty straight forward API (provided you can figure out which functions you are meant to be using!). |
|
// Since properties in ruby are really just methods, we can just access them through regular binded function calls. |
|
// Unlike the other examples, a little manipulation of the internal mruby state is required for handling the doError call in the |
|
// testException method, otherwise it will setjmp right back to our eval_ruby function which is not good! |
|
|
|
// garbage collection callback |
|
void mrb_testobject_gc(mrb_state *mrb, void* ptr) |
|
{ |
|
TestObject *obj = (TestObject*)ptr; |
|
if (obj) |
|
{ |
|
obj->decRef(); |
|
} |
|
} |
|
|
|
// TestObject data type |
|
struct mrb_data_type sMrbTestObjectType = {"TestObject", mrb_testobject_gc}; |
|
|
|
// obj = TestObject.new |
|
mrb_value mrb_testobject_initialize(mrb_state *mrb, mrb_value self) |
|
{ |
|
TestObject *obj = new TestObject(); |
|
DATA_PTR(self) = obj; |
|
DATA_TYPE(self) = &sMrbTestObjectType; |
|
return self; |
|
} |
|
|
|
mrb_value ruby_testobject_getAnalysis(mrb_state *mrb, mrb_value self) |
|
{ |
|
TestObject *obj = (TestObject*)mrb_get_datatype(mrb, self, &sMrbTestObjectType); |
|
if (!obj) |
|
return mrb_nil_value(); |
|
|
|
return mrb_str_new_cstr(mrb, obj->getAnalysis().c_str()); |
|
} |
|
|
|
mrb_value ruby_testobject_getMessage(mrb_state *mrb, mrb_value self) |
|
{ |
|
TestObject *obj = (TestObject*)mrb_get_datatype(mrb, self, &sMrbTestObjectType); |
|
if (!obj) |
|
return mrb_nil_value(); |
|
|
|
return mrb_str_new_cstr(mrb, obj->getMessage().c_str()); |
|
} |
|
|
|
mrb_value ruby_testobject_setMessage(mrb_state *mrb, mrb_value self) |
|
{ |
|
TestObject *obj = (TestObject*)mrb_get_datatype(mrb, self, &sMrbTestObjectType); |
|
if (!obj) |
|
return mrb_nil_value(); |
|
|
|
// Get string parameter |
|
mrb_value str; |
|
mrb_get_args(mrb, "o", &str); |
|
str = mrb_obj_as_string(mrb, str); |
|
|
|
// Set |
|
const char *value = mrb_string_value_ptr(mrb, str); |
|
obj->setMessage(value); |
|
|
|
return mrb_nil_value(); |
|
} |
|
|
|
mrb_value ruby_testobject_getNumber(mrb_state *mrb, mrb_value self) |
|
{ |
|
TestObject *obj = (TestObject*)mrb_get_datatype(mrb, self, &sMrbTestObjectType); |
|
if (!obj) |
|
return mrb_nil_value(); |
|
|
|
return mrb_float_value(mrb, obj->mNumber); |
|
} |
|
|
|
mrb_value ruby_testobject_setNumber(mrb_state *mrb, mrb_value self) |
|
{ |
|
TestObject *obj = (TestObject*)mrb_get_datatype(mrb, self, &sMrbTestObjectType); |
|
if (!obj) |
|
return mrb_nil_value(); |
|
|
|
// Get float parameter |
|
mrb_float num; |
|
mrb_get_args(mrb, "f", &num); |
|
|
|
// Set |
|
obj->mNumber = num; |
|
|
|
return mrb_nil_value(); |
|
} |
|
|
|
mrb_value ruby_testobject_testException(mrb_state *mrb, mrb_value self) |
|
{ |
|
ScopedVariable scope; |
|
TestObject *obj = (TestObject*)mrb_get_datatype(mrb, self, &sMrbTestObjectType); |
|
if (!obj) |
|
return mrb_nil_value(); |
|
|
|
// NOTE: In order to allow scope to be deleted we need to clear the jump value in mrb |
|
void* old_buf = mrb->jmp; |
|
mrb->jmp = NULL; |
|
|
|
// NOTE: in ruby "Object" contains the global functions |
|
mrb_value ret = mrb_funcall(mrb, mrb_obj_value(mrb_class_get(mrb, "Object")), "doError", 0); |
|
if (mrb->exc) { |
|
printf("Error executing doError...\n"); |
|
mrb_p(mrb, ret); |
|
mrb_print_backtrace(mrb); |
|
} |
|
|
|
// Clear exception |
|
mrb->exc = NULL; |
|
|
|
// Restore jump value |
|
mrb->jmp = old_buf; |
|
|
|
return mrb_nil_value(); |
|
} |
|
|
|
void bind_ruby(mrb_state *mrb) |
|
{ |
|
struct RClass *klass; |
|
klass = mrb_define_class(mrb, "TestObject", mrb->object_class); |
|
MRB_SET_INSTANCE_TT(klass, MRB_TT_DATA); // TestObject is a data type |
|
|
|
// |
|
mrb_define_method(mrb, klass, "initialize", mrb_testobject_initialize, ARGS_NONE()); |
|
|
|
mrb_define_method(mrb, klass, "getAnalysis", ruby_testobject_getAnalysis, MRB_ARGS_NONE()); |
|
mrb_define_method(mrb, klass, "message", ruby_testobject_getMessage, MRB_ARGS_NONE()); |
|
mrb_define_method(mrb, klass, "message=", ruby_testobject_setMessage, MRB_ARGS_REQ(1)); |
|
mrb_define_method(mrb, klass, "number", ruby_testobject_getNumber, MRB_ARGS_NONE()); |
|
mrb_define_method(mrb, klass, "number=", ruby_testobject_setNumber, MRB_ARGS_REQ(1)); |
|
mrb_define_method(mrb, klass, "testException", ruby_testobject_testException, MRB_ARGS_NONE()); |
|
} |
|
|
|
void eval_ruby(const char *str) |
|
{ |
|
mrb_state *mrb = mrb_open(); |
|
|
|
// Bind our TestObject |
|
bind_ruby(mrb); |
|
|
|
// Make an instance of TestObject |
|
TestObject *obj = new TestObject(); |
|
obj->setMessage("Created from C++"); |
|
mrb_value obj_value = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_get(mrb, "TestObject"), obj, &sMrbTestObjectType)); |
|
|
|
// Make a new compilation context |
|
mrbc_context *c = mrbc_context_new(mrb); |
|
mrbc_filename(mrb, c, "test.rb"); |
|
|
|
// Evaluate script |
|
mrb_load_string_cxt(mrb, str, c); |
|
|
|
mrbc_context_free(mrb, c); |
|
|
|
// Call main |
|
mrb_value ret = mrb_funcall(mrb, mrb_top_self(mrb), "main", 1, obj_value); |
|
if (mrb->exc) { |
|
printf("Error executing script...\n"); |
|
mrb_p(mrb, ret); |
|
mrb_print_backtrace(mrb); |
|
} |
|
|
|
mrb_close(mrb); |
|
} |
|
|
|
// ======================= |
|
// Angelscript Code |
|
// ======================= |
|
|
|
// Angelscript code notes: |
|
// Out of the whole bunch, this is the simplest binding code as no bridge functions are required when binding the |
|
// TestObject methods. Even better, no functions are required for properties either! |
|
|
|
asIScriptEngine *sAngelscriptEngine; |
|
asIScriptContext *sAngelscriptContext; |
|
|
|
void MessageCallback(const asSMessageInfo *msg, void *param) |
|
{ |
|
const char *type = "ERR "; |
|
if( msg->type == asMSGTYPE_WARNING ) |
|
type = "WARN"; |
|
else if( msg->type == asMSGTYPE_INFORMATION ) |
|
type = "INFO"; |
|
printf("%s (%d, %d) : %s : %s\n", msg->section, msg->row, msg->col, type, msg->message); |
|
} |
|
|
|
void angelscript_print(std::string str) |
|
{ |
|
printf("%s\n", str.c_str()); |
|
} |
|
|
|
void angelscript_abort() |
|
{ |
|
sAngelscriptContext->SetException("Threw up an exception"); |
|
} |
|
|
|
void angelscript_testobject_testException(TestObject *obj) |
|
{ |
|
ScopedVariable scope; |
|
|
|
// Call the testException function |
|
asIScriptModule *mod = sAngelscriptEngine->GetModule("script", asGM_ONLY_IF_EXISTS); |
|
asIScriptFunction *func = mod->GetFunctionByDecl("void doError()"); |
|
|
|
sAngelscriptContext->PushState(); |
|
sAngelscriptContext->Prepare(func); |
|
int r = sAngelscriptContext->Execute(); |
|
if( r != asEXECUTION_FINISHED ) |
|
{ |
|
// The execution didn't complete as expected. Determine what happened. |
|
if( r == asEXECUTION_EXCEPTION ) |
|
{ |
|
// An exception occurred, let the script writer know what happened so it can be corrected. |
|
printf("An exception '%s' occurred.\n", sAngelscriptContext->GetExceptionString()); |
|
} |
|
} |
|
|
|
// cleanup |
|
sAngelscriptContext->PopState(); |
|
} |
|
|
|
std::string angelscript_testobject_describe(TestObject *obj) |
|
{ |
|
char buf[128]; |
|
snprintf(buf, 128, "TestObject:%x", obj); |
|
return buf; |
|
} |
|
|
|
TestObject* angelscript_testobject_create() |
|
{ |
|
return new TestObject(); |
|
} |
|
|
|
void bind_angelscript(asIScriptEngine *engine) |
|
{ |
|
int r = 0; |
|
|
|
// Set error handler |
|
engine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL); |
|
|
|
// Register the string type |
|
RegisterStdString(engine); |
|
|
|
// Register TestObject |
|
r = engine->RegisterObjectType("TestObject",0, asOBJ_REF); assert(r >= 0); |
|
r = engine->RegisterObjectBehaviour("TestObject", asBEHAVE_FACTORY, "TestObject@ f()", asFUNCTION(angelscript_testobject_create), asCALL_CDECL); |
|
r = engine->RegisterObjectMethod("TestObject", "string get_message()", asMETHOD(TestObject, getMessage), asCALL_THISCALL); assert(r >= 0); |
|
r = engine->RegisterObjectMethod("TestObject", "void set_message(string)", asMETHOD(TestObject, setMessage), asCALL_THISCALL); assert(r >= 0); |
|
r = engine->RegisterObjectMethod("TestObject", "string getAnalysis()", asMETHOD(TestObject, getAnalysis), asCALL_THISCALL); assert(r >= 0); |
|
r = engine->RegisterObjectMethod("TestObject", "void testException()", asFUNCTION(angelscript_testobject_testException), asCALL_CDECL_OBJFIRST); assert(r >= 0); |
|
r = engine->RegisterObjectMethod("TestObject", "string describe()", asFUNCTION(angelscript_testobject_describe), asCALL_CDECL_OBJFIRST); assert(r >= 0); |
|
r = engine->RegisterGlobalFunction("void doAbort()", asFUNCTION(angelscript_abort), asCALL_CDECL); assert(r >= 0); |
|
|
|
// NOTE: we can directly bind to field values in AngelScript |
|
r = engine->RegisterObjectProperty("TestObject", "float number", asOFFSET(TestObject,mNumber)); assert( r >= 0 ); |
|
|
|
// Reference counting for TestObject |
|
r = engine->RegisterObjectBehaviour("TestObject", asBEHAVE_ADDREF, "void f()", asMETHOD(TestObject,addRef), asCALL_THISCALL); assert( r >= 0 ); |
|
r = engine->RegisterObjectBehaviour("TestObject", asBEHAVE_RELEASE, "void f()", asMETHOD(TestObject,decRef), asCALL_THISCALL); assert( r >= 0 ); |
|
|
|
// Print function |
|
r = engine->RegisterGlobalFunction("void print(const string &in)", asFUNCTION(angelscript_print), asCALL_CDECL); assert( r >= 0 ); |
|
} |
|
|
|
void eval_angelscript(const char *str) |
|
{ |
|
int r = 0; |
|
asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION); |
|
sAngelscriptEngine = engine; |
|
|
|
// Bind our TestObject |
|
bind_angelscript(engine); |
|
|
|
// Compile our script |
|
asIScriptModule *mod = engine->GetModule("script", asGM_CREATE_IF_NOT_EXISTS); |
|
mod->AddScriptSection("test.as", str); |
|
r = mod->Build(); |
|
if (r < 0) |
|
{ |
|
return; |
|
} |
|
|
|
// Make a sample TestObject |
|
TestObject *obj = new TestObject(); |
|
obj->setMessage("Created from C++"); |
|
|
|
asIScriptFunction *func = mod->GetFunctionByDecl("void main(TestObject@ param)"); |
|
if( func == 0 ) |
|
{ |
|
// Main not found! |
|
printf("The script must have the function 'void main()'. Please add it and try again.\n"); |
|
return; |
|
} |
|
|
|
// Create our context, prepare it, and then execute |
|
asIScriptContext *ctx = engine->CreateContext(); |
|
sAngelscriptContext = ctx; |
|
func->SetUserData(obj); |
|
ctx->Prepare(func); |
|
ctx->SetArgObject(0, obj); // set parameter 0 |
|
r = ctx->Execute(); |
|
if( r != asEXECUTION_FINISHED ) |
|
{ |
|
// The execution didn't complete as expected. Determine what happened. |
|
if( r == asEXECUTION_EXCEPTION ) |
|
{ |
|
// An exception occurred, let the script writer know what happened so it can be corrected. |
|
printf("An exception '%s' occurred. Please correct the code and try again.\n", ctx->GetExceptionString()); |
|
} |
|
} |
|
|
|
// cleanup |
|
obj->decRef(); |
|
ctx->Release(); |
|
engine->Release(); |
|
} |
|
|
|
// ======================= |
|
|
|
// Helper to load scripts |
|
static char sFileData[4096]; |
|
const char *load_file(const char *name) |
|
{ |
|
FILE *fp = fopen(name, "rb"); |
|
if (fp) |
|
{ |
|
size_t size = fread(sFileData, 1, 4095, fp); |
|
sFileData[size] = '\0'; |
|
return sFileData; |
|
} |
|
else |
|
{ |
|
return NULL; |
|
} |
|
} |
|
|
|
// Lets test! |
|
int main(int argc, char **argv) |
|
{ |
|
const char *script = NULL; |
|
|
|
printf("Testing Lua...\n"); |
|
script = load_file("test.lua"); |
|
if (script) |
|
{ |
|
eval_lua(script); |
|
} |
|
else |
|
{ |
|
printf("Error: Couldn't load test.lua!\n"); |
|
} |
|
printf("==============\n"); |
|
|
|
printf("Testing Squirrel...\n"); |
|
script = load_file("test.nut"); |
|
if (script) |
|
{ |
|
eval_squirrel(script); |
|
} |
|
else |
|
{ |
|
printf("Error: Couldn't load test.nut!\n"); |
|
} |
|
printf("==============\n"); |
|
|
|
|
|
printf("Testing Ruby...\n"); |
|
script = load_file("test.rb"); |
|
if (script) |
|
{ |
|
eval_ruby(script); |
|
} |
|
else |
|
{ |
|
printf("Error: Couldn't load test.rb!\n"); |
|
} |
|
printf("==============\n"); |
|
|
|
printf("Testing Angelscript...\n"); |
|
script = load_file("test.as"); |
|
if (script) |
|
{ |
|
eval_angelscript(script); |
|
} |
|
else |
|
{ |
|
printf("Error: Couldn't load test.as!\n"); |
|
} |
|
printf("==============\n"); |
|
} |