Last active
February 9, 2021 13:10
-
-
Save jamesu/8552408 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Angelscript test script | |
void doError() | |
{ | |
doAbort(); | |
} | |
void do_something_with_testobj(TestObject@ obj) | |
{ | |
print("Object test..."); | |
obj.number = 1234; | |
print("Number is: " + obj.number); | |
print("Message is: " + obj.message); | |
print("Object analysis is: " + obj.getAnalysis()); | |
print("Testing object method with exception..."); | |
obj.testException(); | |
print("Done"); | |
} | |
void main(TestObject@ param) | |
{ | |
print("We were called with: " + param.describe()); | |
do_something_with_testobj(param); | |
print("Creating new object..."); | |
TestObject@ obj = TestObject(); | |
obj.message = "Created from Angelscript"; | |
do_something_with_testobj(obj); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Lua test script | |
function doError() | |
error("Error occured") | |
end | |
function do_something_with_testobj(obj) | |
print("Object test...") | |
obj.number = 1234 | |
print("Number is: " .. obj.number) | |
print("Message is: " .. obj.message) | |
print("Object analysis is: " .. obj:getAnalysis()) | |
print("Testing object method with exception...") | |
obj:testException() | |
print("Done") | |
end | |
function main(param) | |
print("We were called with: " .. tostring(param)) | |
do_something_with_testobj(param) | |
print("Creating new object...") | |
obj = TestObject() | |
obj.message = "Created from lua" | |
do_something_with_testobj(obj) | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Squirrel test scriot | |
function doError() | |
{ | |
//throw "Error occured"; | |
} | |
function do_something_with_testobj(obj) | |
{ | |
print("Object test...\n"); | |
obj.number = 1234; | |
print("Number is: " + obj.number+ "\n"); | |
print("Message is: " + obj.message + "\n"); | |
print("Object analysis is: " + obj.getAnalysis() + "\n"); | |
print("Testing object method with exception...\n"); | |
obj.testException(); | |
print("Done\n"); | |
} | |
function main(param) | |
{ | |
print("We were called with: " + param.tostring() + "\n"); | |
do_something_with_testobj(param); | |
print("Creating new object...\n"); | |
local obj = TestObject(); | |
obj.message = "Created from Squirrel"; | |
do_something_with_testobj(obj); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Ruby test script | |
def doError() | |
raise Exception, "Error occured" | |
end | |
def do_something_with_testobj(obj) | |
puts("Object test...") | |
obj.number = 1234 | |
puts("Number is: " + obj.number.to_s) | |
puts("Message is: " + obj.message) | |
puts("Object analysis is: " + obj.getAnalysis()) | |
puts("Testing object method with exception...") | |
obj.testException() | |
puts("Done") | |
end | |
def main(param) | |
puts("We were called with: " .. param.inspect) | |
do_something_with_testobj(param) | |
puts("Creating new object...") | |
obj = TestObject.new() | |
obj.message = "Created from ruby" | |
do_something_with_testobj(obj) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment