Skip to content

Instantly share code, notes, and snippets.

@jamesu
Last active February 9, 2021 13:10
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesu/8552408 to your computer and use it in GitHub Desktop.
Save jamesu/8552408 to your computer and use it in GitHub Desktop.
// 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");
}
// 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);
}
-- 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
// 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);
}
# 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