Wrapper Library

Started by
2 comments, last by Deyja 17 years, 11 months ago
In the course of working on two projects that made heavy use of Angelscript, I have developed a nice sized library wrapped around it. I'm making it available here; http://www.omnisu.com/files/sil.zip Included in that zip is a simple example to loads a script, spawn two threads, and executes them until they are both finished. There is, unfortunatly, no documentation. I'd like this thread to serve as the initial documentation. I'd also like to know ways it can be improved. I've had to jump through some ugly hoops to get stuff working the way I want, and I'd really like to know about any glaring bugs too. :) What I consider to be the most usefull feature is the AutoBinder. Binding stuff to angelscript requires lots of calls to registration functions, and no matter how you break it up, it's dependancy bottleneck. The initialization function must no about everything you want to bind to angelscript. AutoBind removes that problem. Instead, all you have to do is invoke some macros in a cpp file, then call a single function to initialize and bind everything you want. Heres an example, included in the above zip, that binds std::string. It requires AS_UNSAFE_REFERENCES to be defined; the copy of angelscript 2.6.0 included in the zip already has this compile flag set.

#include <string>
#include "angelscript/autobind.h"

namespace
{
	std::string StringFactory(unsigned int length, const char *s)
	{
		return std::string(s,length);
	}

	char& IndexString(unsigned int i, std::string* thisp)
	{
		return (*thisp);
	}
};

REGISTER_COMPLETE_BASIC_TYPE("string",std::string);
REGISTER_TYPE_BEHAVIOR("string",asBEHAVE_ADD_ASSIGN,"string& op_addassign(const string&)",asMETHODPR(std::string,operator+=,(const std::string&),std::string&),asCALL_THISCALL);
REGISTER_BEHAVIOR(asBEHAVE_EQUAL,"bool op_equal(const string&,const string&)",asFUNCTIONPR(std::operator==,(const std::string&,const std::string&),bool),asCALL_CDECL);
REGISTER_BEHAVIOR(asBEHAVE_NOTEQUAL,"bool op_notequal(const string&, const string&)",asFUNCTIONPR(std::operator!=,(const std::string&, const std::string&), bool), asCALL_CDECL);
REGISTER_BEHAVIOR(asBEHAVE_LEQUAL,"bool op_lessequal(const string&, const string&)",asFUNCTIONPR(std::operator <=,(const std::string&, const std::string&), bool), asCALL_CDECL);
REGISTER_BEHAVIOR(asBEHAVE_GEQUAL,"bool op_greaterequal(const string&, const string&)",asFUNCTIONPR(std::operator >=,(const std::string&, const std::string&), bool), asCALL_CDECL);
REGISTER_BEHAVIOR(asBEHAVE_LESSTHAN,"bool op_less(const string&, const string&)",asFUNCTIONPR(std::operator <,(const std::string&, const std::string&), bool), asCALL_CDECL);
REGISTER_BEHAVIOR(asBEHAVE_GREATERTHAN,"bool op_greater(const string&, const string&)",asFUNCTIONPR(std::operator >,(const std::string&, const std::string&), bool), asCALL_CDECL);
REGISTER_BEHAVIOR(asBEHAVE_ADD,"string op_add(const string&, const string&)",asFUNCTIONPR(std::operator +,(const std::string&, const std::string&), std::string), asCALL_CDECL);

//The preprocessor can't grok the [] in 'operator []', so it needs a wrapper.
REGISTER_TYPE_BEHAVIOR("string",asBEHAVE_INDEX,"uint8& op_index(uint)",asFUNCTION(IndexString),asCALL_CDECL_OBJLAST);

REGISTER_METHOD("string", "uint length()", asMETHOD(std::string,size), asCALL_THISCALL);
REGISTER_STRING_FACTORY("string",asFUNCTION(StringFactory),asCALL_CDECL);

Advertisement
A chunk of the example that loads a script, spawns a couple of threads, and executes them.

ScriptInterface::Script script("script_demo.c");script.set_global("global_one",std::string("The answer to life, the universe, and everything."));script.set_global("global_two",42);ScriptInterface::AnyList parameters_one = ScriptInterface::AnyListBuilder() + std::string("Hello World!") + 5 + 8;ScriptInterface::Thread thread_one = script.spawn_process("test",parameters_one);ScriptInterface::AnyList parameters_two = ScriptInterface::AnyListBuilder() + 3 + 7;ScriptInterface::Thread thread_two = script.spawn_process("test",parameters_two);ScriptInterface::ThreadManager thread_manager;thread_manager.add_thread(thread_one);thread_manager.add_thread(thread_two);//run for 1/100th of a second...while (!thread_manager.empty()) thread_manager.execute(0.01);
Looking good :)

I think I'll add this thread to the wiki.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

That's great.

The abstractions in my wrapper are designed for an entity and event type model. Every entity has an instance of ScriptInterface::Script, and there is a global ScriptInterface::ThreadManager. Whenever there is an event, the entity spawns a thread from it's script and adds it to the manager.

Theres a lot of trickery going on with the Any type and the AutoBinder to resolve C++ types into the names of the type in angelscript. It works perfectly; unless you happen to bind the same C++ type to two different names in angelscript. Because of this problem, it also doesn't support the 'bits' types. Theres no way for the C++ code to tell bits apart from unsigned int, since both are the exact same type in C++. This leads me to another problem that I need your help to solve. Rather; I should say to make the code less ugly. Primitive types have to be set using SetArgDWord (or float or double) whereas class types can use SetArgObject. The problem this causes is best illustrated with code.

bool set_argument(int a_id, void* ptr){	if (pimple->state != SIT_READY) return false;	int r_code = pimple->ctx->SetArgObject(a_id, ptr);	if (r_code < 0) { pimple->state = SIT_ERROR; return false; }	return true;}template <typename T>bool set_argument_object(int a_id, T& value){	return set_argument(a_id,static_cast<void*>(&value));}bool set_argument_any(int a_id, ScriptInterface::Any value){	if (pimple->state != SIT_READY) return false;	int r_code;	//Why does angelscript not let me set primitive types using SetArgObject??	if (value.is_of_type<char>())		r_code = pimple->ctx->SetArgDWord(a_id,value.reference<char>());	else if (value.is_of_type<short>())		r_code = pimple->ctx->SetArgDWord(a_id,value.reference<short>());	else if (value.is_of_type<int>())		r_code = pimple->ctx->SetArgDWord(a_id,value.reference<int>());	else if (value.is_of_type<unsigned char>())		r_code = pimple->ctx->SetArgDWord(a_id,value.reference<unsigned char>());	else if (value.is_of_type<unsigned short>())		r_code = pimple->ctx->SetArgDWord(a_id,value.reference<unsigned short>());	else if (value.is_of_type<unsigned int>())		r_code = pimple->ctx->SetArgDWord(a_id,value.reference<unsigned int>());	else if (value.is_of_type<bool>())		r_code = pimple->ctx->SetArgDWord(a_id,value.reference<bool>());	else if (value.is_of_type<float>())		r_code = pimple->ctx->SetArgFloat(a_id,value.reference<float>());	else if (value.is_of_type<double>())		r_code = pimple->ctx->SetArgDouble(a_id,value.reference<double>());	else		return set_argument(a_id,value.void_pointer());	if (r_code < 0) { pimple->state = SIT_ERROR; return false; }	return true;}		


Idealy, I would implement set_argument_any as just the final else clause. Additionally, I'm going to have to specialize set_argument_object for primitive types (or supply seperate functions for them). This isn't as big an issue as I currently only set arguments using the set_argument_any function. The rest are only there to allow a user to bypass ScriptInterface::Script's spawn_process function.

Setting globals is somewhat easier, since angelscript can supply me a void* to them. If angelscript could supply a void* to the argument, I could set them the same way. Since Any 'stores' the type of the object, it can properly cast a void* and assign to it. It's just not type safe.

For reference, setting a global from an Any:

bool set_global_any(const std::string& name, Any value){	if (!good_module) return false;        //any::script_typename makes a call to AutoBind::type_name<T>().	std::string ang_type = value.script_typename(); 	int g_id = ScriptInterface::Engine()->GetGlobalVarIDByDecl(module_name.c_str(),(ang_type + " " + name).c_str());	if (g_id < 0) return false;	void * g_ptr = ScriptInterface::Engine()->GetGlobalVarPointer(g_id);	if (!g_ptr) return false;	value.assign_to_void(g_ptr);	return true;}

This topic is closed to new replies.

Advertisement