AngelScript 2.6.0 released

Started by
29 comments, last by mandrav 17 years, 11 months ago
I've finally released version 2.6.0 after almost three months with only occasional bug fixes coming out. Two major steps have been taken with this release. 1) Script classes can now be declared with methods and constructors for use in the scripts and also from the application. There is still no inheritance or polymorphism available, but that is coming. 2) I've been able to add support for 64 bit processors, though still without native calling conventions. It's not been all that well tested yet since I do not have a 64 bit system myself, but I've been told that it works well. The next step for me now is to add the script interfaces, which will allow scripts to use polymorphism, similar to how Java does it. The application will also be able to define interfaces that the script classes must implement, which should make it easier to interact with the scripts. Regards, Andreas

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

Advertisement
I'm trying to update my game library to use AS 2.6.0 but I'm hitting problems when running my "test rig". Since I don't really understand the inner workings of AS, I set breaks on my constructor, copy constructor, destructorm assignment operator and some methods of interest in an registered object called Point, which is reference counted. I have a simple test method defined as follows:

Point AddPoints(Point p1, Point p2)
{
Point p3 = p1;
p3.x += p2.x;
p3.y += p2.y;
return p3;
}

The crash occurs somewhere within the execution of the function, specifically, when the last reference of the parameters passed to the function are released with a count of 1, resulting in an attempted deletion. However I am passing the objects by value so I am confused why the script engine is attempting to release references. It is acting as if I passed the object by reference and not by value.

If it is of any help, I have not changed my argument setting methods and still use those from 2.5.0 backwards i.e. SetArgObjects for native types, objects and references. Could this be related to the problem? Actually I'm not exaclty sure what is the difference between SetArgObject and the new SetArgAddress.
tIDE Tile Map Editorhttp://tide.codeplex.com
AddPoints() is a script function, isn't it?

Well, since it was declared to take the arguments by value you should use the method SetArgObject() to pass in the values. This method will then create a copy of your object and put their addresses on the script stack. When the script function ends, it will release these created objects.

SetArgAddress() should be used for parameters declared to take a reference, in which case the method will simply put the pointer on the script stack.

Would it be possible for you to send me your "test rig" so that I can test it? Or if it contains too much 'secret' stuff, perhaps you could send me a stripped down version?

Regards,
Andreas

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

You are right, I meant a script function called AddPoints() and not a test method.. it was quite late when I wrote that post :)

No secrets really, but all the AS calls are wrapped in my own API so it would take me a while to get a standalone working version. I will try to explain how everything is set up however, including the calls to the AS API:

The following is the Point class in C++:

class Point
{
public:
int iRefCount;
int x;
int y;
Point(void) : iRefCount(1), x(0), y(0) {};
~Point(void) {};
inline void Add(Point p) { this->x += p.x; this->y += p.y; };
inline Point &operator=(const Point &pointOther)
{
// leave ref count untouched
this->x = pointOther.x;
this->y = pointOther.y;
return *this;
}
inline void AddRef(void) { ++iRefCount; } ;
inline void Release(void)
{
if( --iRefCount == 0 )
delete this;
};
};


Due to the way I coded my API, I need to use wrapper functions with the OBJFIRST convention for all the class methods, constructors, destructors and operators:

void Point_Construct(Point *pPoint)
{
new(pPoint)Point();
}

void Point_Destruct(Point &point)
{
point.~Point();
}

Point &Point_Assign(Point &point, Point &pointOther)
{
point = pointOther;
return point;
}

void Point_Add(Point &point, Point p)
{
point.Add(p);
}

int *Point_Index(Point &point, int iIndex)
{
if ((iIndex < 0) || (iIndex > 1))
GetNGEngine()->GetScriptManager()->RaiseScriptException("Point index out of range.");
if (iIndex == 0)
return &point.x
else
return &point.y
}

void Point_AddRef(Point &point)
{
point.AddRef();
}

void Point_Release(Point &point)
{
point.Release();
}

And here are the registration calls for Point as they appear in my API:

ScriptManager *pScriptManager = pNGEngine->GetScriptManager();
pScriptManager->RegisterHostType("Point", sizeof(Point));
pScriptManager->RegisterHostTypeProperty("Point", "int x", offsetof(Point, x));
pScriptManager->RegisterHostTypeProperty("Point", "int y", offsetof(Point, y));
pScriptManager->RegisterHostTypeMethod("Point", "void Add(Point)", Point_Add);
pScriptManager->RegisterHostTypeBehaviour("Point", STB_Construct, "void f()", Point_Construct);
pScriptManager->RegisterHostTypeBehaviour("Point", STB_Destruct, "void f()", Point_Destruct);
pScriptManager->RegisterHostTypeBehaviour("Point", STB_Assign, "Point &f(Point &in)", Point_Assign);
pScriptManager->RegisterHostTypeBehaviour("Point", STB_Index, "int &f(int)", Point_Index);
pScriptManager->RegisterHostTypeBehaviour("Point", STB_AddRef, "void f()", Point_AddRef);
pScriptManager->RegisterHostTypeBehaviour("Point", STB_Release, "void f()", Point_Release);

Finally here is the implementation of my own API functions:

void ScriptManagerImpl::RegisterHostVariable(string strSignature, void* pVariable)
{
if (m_pasIScriptEngine->RegisterGlobalProperty(
strSignature.c_str(), pVariable) < 0)
throw Exception(
"Failed registering host function '" + strSignature + "'.");
}

void ScriptManagerImpl::RegisterHostFunction(string strSignature, void* pFunction)
{
if (m_pasIScriptEngine->RegisterGlobalFunction(
strSignature.c_str(), asFUNCTION(pFunction), asCALL_CDECL) < 0)
throw Exception(
"Failed registering host function '" + strSignature + "'.");
}

void ScriptManagerImpl::RegisterHostOperator(
ScriptOperator scriptOperator,
string strBehaviourSignature, void *pBehaviourFunction)
{
asDWORD dwBehaviour = this->ConvertToASBehaviour(scriptOperator);
if (m_pasIScriptEngine->RegisterGlobalBehaviour(
dwBehaviour, strBehaviourSignature.c_str(),
asFUNCTION(pBehaviourFunction), asCALL_CDECL) < 0)
throw Exception(
"Failed registering host operator behaviour '"
+ strBehaviourSignature + "'.", __FILE__, __LINE__);
}

void ScriptManagerImpl::RegisterHostType(string strType, int iTypeSize)
{
if (m_pasIScriptEngine->RegisterObjectType(
strType.c_str(), iTypeSize, asOBJ_CLASS_CDA) < 0)
throw Exception("Failed registering host type '" + strType + "'.",
__FILE__, __LINE__);
}

void ScriptManagerImpl::RegisterHostTypeProperty(
string strType, string strPropertySignature, size_t iPropertyOffset)
{
if (m_pasIScriptEngine->RegisterObjectProperty(
strType.c_str(), strPropertySignature.c_str(), (int) iPropertyOffset) < 0)
throw Exception(
"Failed registering host type property '" + strPropertySignature
+ "' for host type '" + strType + "'.",
__FILE__, __LINE__);
}

void ScriptManagerImpl::RegisterHostTypeMethod(
string strType, string strMethodSignature, void *pMethodFunction)
{
// for debug watch purposes
const char *pStr = strMethodSignature.c_str();

if (m_pasIScriptEngine->RegisterObjectMethod(
strType.c_str(), strMethodSignature.c_str(),
asFUNCTION(pMethodFunction), asCALL_CDECL_OBJFIRST) < 0)
throw Exception(
"Failed registering host type method '" + strMethodSignature
+ "' for host type '" + strType + "'.",
__FILE__, __LINE__);
}

void ScriptManagerImpl::RegisterHostTypeBehaviour(
string strType, ScriptTypeBehaviour scriptTypeBehaviour,
string strBehaviourSignature, void *pBehaviourFunction)
{
asDWORD dwBehaviour = this->ConvertToASBehaviour(scriptTypeBehaviour);
if (m_pasIScriptEngine->RegisterObjectBehaviour(
strType.c_str(), dwBehaviour, strBehaviourSignature.c_str(),
asFUNCTION(pBehaviourFunction), asCALL_CDECL_OBJFIRST) < 0)
throw Exception(
"Failed registering host type behaviour '"
+ strBehaviourSignature + "' for type '" + strType + "'.",
__FILE__, __LINE__);
}

tIDE Tile Map Editorhttp://tide.codeplex.com
Apologies for bumping the thread.. but the previous post was long enough :)

I checked the AS behaviour code conversion in case I was doing something stupid.. but it seems ok. Anyway.. here it is:

asDWORD ScriptManagerImpl::ConvertToASBehaviour(
ScriptTypeBehaviour scriptTypeBehaviour)
{
switch(scriptTypeBehaviour)
{
case STB_Construct: return asBEHAVE_CONSTRUCT;
case STB_Destruct: return asBEHAVE_DESTRUCT;
case STB_Assign: return asBEHAVE_ASSIGNMENT;
case STB_AddAssign: return asBEHAVE_ADD_ASSIGN;
case STB_SubAssign: return asBEHAVE_SUB_ASSIGN;
case STB_MulAssign: return asBEHAVE_MUL_ASSIGN;
case STB_DivAssign: return asBEHAVE_DIV_ASSIGN;
case STB_ModAssign: return asBEHAVE_MOD_ASSIGN;
case STB_OrAssign: return asBEHAVE_OR_ASSIGN;
case STB_AndAssign: return asBEHAVE_AND_ASSIGN;
case STB_XorAssign: return asBEHAVE_XOR_ASSIGN;
case STB_SllAssign: return asBEHAVE_SLL_ASSIGN;
case STB_SrlAssign: return asBEHAVE_SRL_ASSIGN;
case STB_SraAssign: return asBEHAVE_SRA_ASSIGN;
case STB_Index: return asBEHAVE_INDEX;
case STB_Negate: return asBEHAVE_NEGATE;
case STB_AddRef: return asBEHAVE_ADDREF;
case STB_Release: return asBEHAVE_RELEASE;
}
throw Exception("Unsupported type behaviour.", __FILE__, __LINE__);
}

[Edited by - SharkBait on April 17, 2006 12:32:22 PM]
tIDE Tile Map Editorhttp://tide.codeplex.com
Hi Witchlord,

Sorry for bumping again... but since the thread has gone quiet...

I was wondering if you had a chance to look into the problem above. So far it appears to be a problem in version 2.6.0 of AS as everything has worked fine for several versions until now. Perhaps it is due to the introduction of SetArgAddress? Anyway, I am still using SetArgObject and that appears to be the correct method to use according to my script function definition.

Thanks,


tIDE Tile Map Editorhttp://tide.codeplex.com
Unfortunately, I haven't had a chance to look into this yet. As soon as I do I'll let you know my findings.

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

I made a test just now trying to reproduce your problem, but everything worked fine. Please verify if I'm doing something differently from you:

#include "utils.h"namespace TestShark{#define TESTNAME "TestShark"class Point{public:	int iRefCount;	int x;	int y;	Point(void) : iRefCount(1), x(0), y(0) {};	~Point(void) {};	inline void Add(Point p) { this->x += p.x; this->y += p.y; };	inline Point &operator=(const Point &pointOther)	{		// leave ref count untouched		this->x = pointOther.x;		this->y = pointOther.y;		return *this;	}	inline void AddRef(void) { ++iRefCount; } ;	inline void Release(void)	{		if( --iRefCount == 0 )		delete this;	};};void Point_Construct(Point *pPoint){	new(pPoint)Point();}void Point_Destruct(Point &point){	point.~Point();}Point &Point_Assign(Point &point, Point &pointOther){	point = pointOther;	return point;}void Point_Add(Point &point, Point p){	point.Add(p);}int *Point_Index(Point &point, int iIndex){	if ((iIndex < 0) || (iIndex > 1))		asGetActiveContext()->SetException("Point index out of range.");	if (iIndex == 0)		return &point.x;	else		return &point.y;}void Point_AddRef(Point &point){	point.AddRef();}void Point_Release(Point &point){	point.Release();}static char *script ="Point AddPoints(Point p1, Point p2) \n""{                                   \n""Point p3 = p1;                      \n""p3.x += p2.x;                       \n""p3.y += p2.y;                       \n""return p3;                          \n""}                                   \n";bool Test(){	bool fail = false;	int r;	asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);	r = engine->RegisterObjectType("Point", sizeof(Point), asOBJ_CLASS_CDA); assert( r >= 0 );	r = engine->RegisterObjectProperty("Point", "int x", offsetof(Point, x)); assert( r >= 0 );	r = engine->RegisterObjectProperty("Point", "int y", offsetof(Point, y)); assert( r >= 0 );	r = engine->RegisterObjectMethod("Point", "void Add(Point)", asFUNCTION(Point_Add), asCALL_CDECL_OBJFIRST); assert( r >= 0 );	r = engine->RegisterObjectBehaviour("Point", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(Point_Construct), asCALL_CDECL_OBJFIRST); assert( r >= 0 );	r = engine->RegisterObjectBehaviour("Point", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(Point_Destruct), asCALL_CDECL_OBJFIRST); assert( r >= 0 );	r = engine->RegisterObjectBehaviour("Point", asBEHAVE_ASSIGNMENT, "Point &f(Point &in)", asFUNCTION(Point_Assign), asCALL_CDECL_OBJFIRST); assert( r >= 0 );	r = engine->RegisterObjectBehaviour("Point", asBEHAVE_INDEX, "int &f(int)", asFUNCTION(Point_Index), asCALL_CDECL_OBJFIRST); assert( r >= 0 );	r = engine->RegisterObjectBehaviour("Point", asBEHAVE_ADDREF, "void f()", asFUNCTION(Point_AddRef), asCALL_CDECL_OBJFIRST); assert( r >= 0 );	r = engine->RegisterObjectBehaviour("Point", asBEHAVE_RELEASE, "void f()", asFUNCTION(Point_Release), asCALL_CDECL_OBJFIRST); assert( r >= 0 );	COutStream out;	engine->AddScriptSection(0, TESTNAME, script, strlen(script));	engine->SetCommonMessageStream(&out);	r = engine->Build(0);	if( r < 0 )	{		printf("%s: Failed to build\n", TESTNAME);		fail = true;	}	else	{		// Internal return		int funcId = engine->GetFunctionIDByName(0, "AddPoints");		asIScriptContext *ctx = engine->CreateContext();		ctx->Prepare(funcId);		Point a, b, c;		a.x = 1; a.y = 1;		b.x = 2; b.y = 2;		ctx->SetArgObject(0, &a);		ctx->SetArgObject(1, &b);		r = ctx->Execute();		if( r != asEXECUTION_FINISHED )			fail = true;		Point *ret = (Point*)ctx->GetReturnObject();		c = *ret;		ctx->Release();	}	engine->Release();	return fail;}} // End namespace

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

I checked your test code and it appears to match exactly with how AS is being called internally in my wrapper code.

The error apparently occurs when some intermediate variable of type Point runs out of references and gets deleted. The deletion triggers a breakpoint when the pointer fails to pass a heap integrity test (_CrtIsValidHeapPointer). Perhaps I'm doing something wrong with references? Should the assignment operator, for example, do anything to the references?

Thinking of what else could be different from your test rig.. I'm compiling on VS 2005, which, altough it has worked with AS 2.5.0, could be the root of the problem, at least as far as AS 2.6.0 is concerned.

I am also beginning to wonder whether the 'inline' specifiers of some of Point's methods could have an effect.. but I suppose they simply act as a hints that replace hard-coded calls with the functions' content. In the case of AS, function calls are dynamic through the use of registered function pointer so that should not be an issue.. or could it? Just thinking out loud...
tIDE Tile Map Editorhttp://tide.codeplex.com
I'm also using VC++ 2005. I'm using the express version though, but that should hardly have anything to do with it.

From your Point class you seem to be doing everything correctly in regards to the reference counting.

Are you using AngelScript from a dll? If you do then you probably need to register the memory behaviours as well, otherwise AngelScript will allocate memory on the dll heap and the application will free it from the application heap.

Another possibility for the problem may be the optimization mode you're compiling with. I've found that when compiling AngelScript with maximum optimizations turned on, some of the tests fails. Though not the way your test fails, so this is probably not the cause.

Perhaps you could add a LineCallback to AngelScript and log every line visited when executing the script function? You can look at the test_debug.cpp test for an idea on how that can be done. This may shed some light on the problem, especially if you also log each of the calls to the AddRef and Release calls so that you can see how they are manipulated.

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

This topic is closed to new replies.

Advertisement