Skip to main content
GameDev.net gamedev.net
🔒 Locked

AngelScript 2.6.0 released

Started by WitchLord Apr 8, 2006 at 8:37 PM 29 replies 7.6k views
Original Post
WitchLord
WitchLord
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 - <a href="http://www.angelcode.com/tower" rel
SharkBait
SharkBait
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
WitchLord
WitchLord
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 - <a href="http://www.angelcode.com/tower" rel
SharkBait
SharkBait
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 ∈)", 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
SharkBait
SharkBait
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
SharkBait
SharkBait
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
WitchLord
WitchLord
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 - <a href="http://www.angelcode.com/tower" rel
WitchLord
WitchLord
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 ∈)", 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 - <a href="http://www.angelcode.com/tower" rel
SharkBait
SharkBait
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
WitchLord
WitchLord
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 - <a href="http://www.angelcode.com/tower" rel
SharkBait
SharkBait
I'm using the Pro edition but as you said, it shouldn't make a difference in terms of compiled code.

I'm using the statically linked version of AS so it should be using the application's heap. I'm also compiling with no optimisations.

I've yet to register the line callback. I'll try that out and see if I can pinpoint the problem with more accuracy.
tIDE Tile Map Editorhttp://tide.codeplex.com
SharkBait
SharkBait
I set up a line callback function but it didn't give me any more information - it simply triggered whenever I invoked a script function. I did try placing breakpoints on the wrapper functions and Point class methods however, and those seem to indicate to a temporary variable getting deleted - the references of the input arguments and the return argument all had their references > 0 at the time the rogue pointer deletion occured so it appears to be a copy of the arguments or the result that AS is trying to delete.
tIDE Tile Map Editorhttp://tide.codeplex.com
Rain Dog
Rain Dog
Does the express edition offer link time code generation? If not, that would be a large difference in code generation.
WitchLord
WitchLord
Yes, the express edition supports link time code generation.

SharkBait, you may actually want to verify that option. Perhaps turning it on or off affects the results of your application?
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
SharkBait
SharkBait
Link Time Code Generation was not enabled (Default). I tried the other options but I couldn't even compile as I am not using code profiling.

Anyway, AS 2.5.0 worked fine with VS 2005 with the compiler/linker configuration unchanged.

But nevermined, I just figured out a solution! You did in fact hint at it when you asked me if I was running AS in a DLL. Although I was and am still running AS statically within the same executable I actually found out that the alloc and free functions used by AS where somehow different from those used by the main exe.. or at least, the 'delete' operation had an implementation incompatible with that of 'free' used by AS.

So I used the engine method SetCommonObjectMemoryFunctions through my wrapper API and passed on pointers to 'malloc' and 'free' as seen from my exe's scope, and it worked! :)

Coincidentally, I recall asking for this functionality a while back.. although what I had in mind was the possiblity of allowing the user to replace the standard functions with more performant versions or with tracing built in. Ironically, this also turned out as a solution to my problem!

Thanks for your assistance ( and for implementing that function :) )


[Edited by - SharkBait on April 29, 2006 7:54:30 AM]
tIDE Tile Map Editorhttp://tide.codeplex.com
WitchLord
WitchLord
I'm happy that you were able to solve the problem. :D
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
Rain Dog
Rain Dog
ahh, i did not know that function was there. That is good news as I too have had those problems when using a DLL. Objects new'd w/ .exe version and deleted with .DLL function always crashed me.
WitchLord
WitchLord
Which is exactly why I added that possibility. :)

You can do it on a per-object level as well by registering the asBEHAVE_ALLOC and asBEHAVE_FREE behaviours. This can be useful for those objects that use a different memory manager, for example a memory pool.
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
WitchLord
WitchLord
Hmm, as far as I know AngelScript is working on Mac OS X with AS_MAX_PORTABILITY on. It could be that the latest changes has broken that (though I hope not).

What is the size of size_t on your platform? And what is the size of long? If the size of long and int is different then that is why you're getting segmentation error.

If you're compiling for a 64 bit platform you also need to define the AS_64BIT_PTR.

I'll create another typedef to use instead of size_t, because it seems that it is not always correct on the various compilers out there.

Unfortunately I haven't heard more from the developer that said he was working on the PPC port, so I can't say anything about when it will be available. I just received a MIPS port (PS2, PSP) though from Manu Evans, and he said that he might give PPC a try as well. I'm hopeful that it will be available soon, but since I'm not the one doing it I cannot give any guarantees.

Regards,
Andreas
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
WitchLord
WitchLord
The 64bit support is new and not heavily tested (since I do not have a 64bit system myself). I'll need your help in order to try to find whatever bug it might be.

First, I need to know what the script you're using looks like.

Would it be possible for you to write a small test app that reproduces the problem with a minimal of other stuff, so that I can try to figure out what's going on?
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.