Crash on manually executing a script class method

Started by
4 comments, last by Brian Damage 14 years, 7 months ago
Hey, first time poster here. Love your library, it's just what I needed for my game engine project and it's been mostly pretty smooth sailing so far (Except for that bug with the handle-types and template objects, but I came here to check and found you'd fixed that, so good on ya). Now, however, I've been attempting to implement a script class that my c++-coded screens (part of a GUI system, child class of a generic GUI object) can use to implement scripted behaviour. What I have script-wise is one base class:
class screen
{
	screen()
	{}

	~screen()
	{}

	void OnKey(string name, SL_KEY key)
	{}

	void OnClick(string name, uint8 button, uint x, uint y, bool down)
	{}

	void OnMouseOver(string name, uint x, uint y, bool over)
	{}

	void OnMouseMove(uint x, uint y)
	{}

	void OnTick(uint time)
	{}

	void OnOpen()
	{}

	void OnClose()
	{}
}
... with each screen object in c++-side loading the main part of the child class (the functions) from an xml file and prepending/appending automatically generated script code onto the ends to produce a working class named after the screen itself, for example:
#include "scripts\stdlib\screen.as"
class test_class : screen
{
test_class()
{
}

~test_class()
{
}
//Stuff from xml file here
		void OnClick(string name, uint8 button, uint x, uint y, bool down)
		{
			if(name == "button1")
			{
				print("YAY FOR THE FIRST BUTTON!\n");
			}
			if(name == "button2")
			{
				print("YAY FOR THE SECOND BUTTON!\n");
			}
			if(name == "button3")
			{
				print("YAY FOR THE THIRD BUTTON!\n");
			}
			if(name == "button4")
			{
				print("YAY FOR THE FOURTH BUTTON!\n");
			}
			if(name == "tbutton1")
			{
				print("YAY FOR THE TEXT BUTTON!\n");
				print("Closing the screen!!\n");
				close_screen("test");
				open_screen("test2");
			}
		}
		
		void OnMouseOver(string name, uint x, uint y, bool over)
		{}
		
		void OnMouseMove(uint x, uint y)
		{
			print("MOVING!");
		}
	
		void OnTick(uint time)
		{}
		
		void OnOpen()
		{}
		
		void OnClose()
		{}
//Stuff from XML ends
}
This is then added to the script builder (the most recent version with the ultra-handy include callback, which I use to disallow files with identical names) along with all other generated screen scripts, and it all seems to compile sucessfully. I then get the type and so on of the object I want instanciate, with a method based on the tutorial on the angelcode website:
asIObjectType* SLAScriptEngine::SLGetObjTypeByID   (const char* decl, asIScriptModule* module)
{
    SL_LOG("Declaration: %s", decl);
    static int ID = 0;
    ID = module->GetTypeIdByDecl(decl);

    if( ID <= 0)
	{
	    SL_WARN(false, "Out of %i functions, ", module->GetFunctionCount());
		SLAScriptEngine::ctx->Release();
		SLAScriptEngine::engine->Release();
		SL_ASSERT(ID != asINVALID_TYPE , "The type '%s' is not valid", decl);
		return 0;
	}

	SL_LOG("Type ID: %i", ID);

    asIObjectType* obj = NULL;
    obj =  SLAScriptEngine::engine->GetObjectTypeById(ID);

    SL_ASSERT(obj != NULL,"Obj undefined!");

	return obj;
}

int SLAScriptEngine::SLGetDecFactID   (asIObjectType* t, const char* decl, asIScriptModule* module)
{
    int ID = t->GetFactoryIdByDecl(decl);
	if( ID < 0)
	{
	    SL_WARN(false, "Out of %i functions, ", module->GetFunctionCount());
		SL_ASSERT(ID != asERROR, "The module '%s' did not compile sucessfully", module->GetName());
		SL_ASSERT(ID != asINVALID_DECLARATION, "The constructor declaration '%s' is not valid.", decl);
		SL_ASSERT(ID != asNO_FUNCTION , "No constructor with the declaration '%s' was found.", decl);
		SLAScriptEngine::ctx->Release();
		SLAScriptEngine::engine->Release();
		return 0;
	}
	return ID;
}

void SLAScriptEngine::SLExecFunc    ()
{
    int r = SLAScriptEngine::ctx->Execute();
    SL_LOG("Executed");
    SL_ASSERT(r != asEXECUTION_ERROR, "Execution failed!");
    SL_ASSERT(r != asEXECUTION_ABORTED , "Execution aborted!");
    SL_ASSERT(r != asEXECUTION_SUSPENDED , "Execution suspended!");
    SL_ASSERT(r != asEXECUTION_EXCEPTION , "Execution excepted!");
    SL_ASSERT(r == asEXECUTION_FINISHED, "Unknown Error!");
}

void* SLAScriptEngine::SLExecFuncAndReturnAdress    ()
{
    void* ret = NULL;
    SLAScriptEngine::SLExecFunc();
    ret = SLAScriptEngine::ctx->GetReturnAddress();
    SL_ASSERT(ret != NULL, "GetReturnAdress failed!");
    return ret;
}

void* SLAScriptEngine::SLExecFuncAndReturnObject    ()
{
    void* ret = NULL;
    SLAScriptEngine::SLExecFunc();
    ret = SLAScriptEngine::ctx->GetReturnObject();
    SL_ASSERT(ret != NULL, "GetReturnObject failed!");
    return ret;
}

void SLAScriptEngine::SLPrepFunc    (int ScriptID)
{
    int r = SLAScriptEngine::ctx->Prepare(ScriptID);
	if( r < 0 )
	{
		SL_ASSERT(r != asCONTEXT_ACTIVE, "Failed to prepare the context: Context still active.");
		SL_ASSERT(r != asNO_FUNCTION , "Failed to prepare the context: Function ID invalid.");
		SLAScriptEngine::ctx->Release();
		SLAScriptEngine::engine->Release();
	}
	return;
}

int SLAScriptEngine::SLGetDecMethID   (asIObjectType* t, const char* decl, asIScriptModule* module)
{
    int ID = t->GetMethodIdByDecl(decl);
	if( ID < 0  and ID != asNO_FUNCTION)
	{
	    SL_WARN(false, "Out of %i functions, ", module->GetFunctionCount());
		SL_ASSERT(ID != asERROR, "The module '%s' did not compile sucessfully", module->GetName());
		SL_ASSERT(ID != asINVALID_DECLARATION, "The method declaration '%s' is not valid.", decl);
		SL_ASSERT(ID != asMULTIPLE_FUNCTIONS, "Multiple method with the declaration '%s' were found.", decl);
		SLAScriptEngine::ctx->Release();
		SLAScriptEngine::engine->Release();
		return 0;
	}
    SL_WARN(ID != asNO_FUNCTION, "The method '%s' was not found.", decl);
	return ID;
}

void SLScreen::initialise()
{
    string t_name(name+"_class");
    asIObjectType* type = SLAScriptEngine::SLGetObjTypeByID(t_name.c_str());

    SL_LOG("AS Object Type: %s", type->GetName());

    asIObjectType* b_type = type->GetBaseType();

    SL_LOG("AS Object Base Type: %s", b_type->GetName());

    string screen_dec(name+"_class @"+name+"_class()");

    SL_LOG("Screen declaration: %s", screen_dec.c_str());

    int factory_id = SLAScriptEngine::SLGetDecFactID(type, screen_dec.c_str());

    SL_LOG("Factory function ID is: %i", factory_id);

    SLAScriptEngine::SLPrepFunc(factory_id);

    script_class = SLAScriptEngine::SLExecFuncAndReturnObject();

    SL_LOG("Setting up click func ID for Screen %s", name.c_str());
    string funcDef("void OnClick(string name, uint8 button, uint x, uint y, bool down)");
    ClickFuncID = SLAScriptEngine::SLGetDecMethID(type, funcDef.c_str());
    SL_LOG("Func ID is: %i", ClickFuncID);

    SL_LOG("Setting up mouse-over ID for Screen %s", name.c_str());
    funcDef = "void OnMouseOver(string name, uint x, uint y, bool over)";
    MOFuncID = SLAScriptEngine::SLGetDecMethID(type, funcDef.c_str());
    SL_LOG("Func ID is: %i", MOFuncID);

    SL_LOG("Setting up mouse-move ID for Screen %s ", name.c_str());
    funcDef = "void OnMouseMove(uint x, uint y)";
    MMFuncID = SLAScriptEngine::SLGetDecMethID(type, funcDef.c_str());
    SL_LOG("Func ID is: %i", MMFuncID);

    SL_LOG("Setting up key-press ID for Screen %s ", name.c_str());
    funcDef = "void OnKey(string name, SL_KEY key)";
    KeyFuncID = SLAScriptEngine::SLGetDecMethID(type, funcDef.c_str());
    SL_LOG("Func ID is: %i", KeyFuncID);
}
Which, when run, logs:
LOG: Declaration: test_class
LOG: Type ID: 134217744
LOG: AS Object Type: test_class
LOG: AS Object Base Type: screen
LOG: Screen declaration: test_class @test_class()
LOG: Factory function ID is: 122
LOG: Executed
LOG: Setting up click func ID for Screen test
LOG: Func ID is: 157
LOG: Setting up mouse-over ID for Screen test
LOG: Func ID is: 158
LOG: Setting up mouse-move ID for Screen test 
LOG: Func ID is: 159
LOG: Setting up key-press ID for Screen test 
LOG: Func ID is: 156
So that seems to be fine. However, when I get into the actual function calling (which pretty much happens as soon as I move the mouse and send a mouse-move check through the input system), which uses this code:
SLGUIObject* SLScreen::mouseMove  (int x, int y)
{
    static SLGUIObject* ret;
    ret = NULL;
    foreach(SLGUIObject* child, children)
    {
        ret = child->mouseMove(x, y);
        if(ret != NULL)
        {break;}
    }

    if(ret != NULL and last_mouse_mov_ptr != NULL and ret != last_mouse_mov_ptr and MOFuncID > 0)
    {
        //call mouseOver with an out for last_mouse_mov_ptr
        SL_LOG("Executing MouseOver function for an out %i", MOFuncID);
        SLAScriptEngine::SLPrepFunc(MOFuncID);
        SLAScriptEngine::SLSetObject(script_class);
        SLAScriptEngine::SLSetArgObject(0, ret->getName());
        SLAScriptEngine::SLSetArgDWord(1, x);
        SLAScriptEngine::SLSetArgDWord(2, y);
        SLAScriptEngine::SLSetArgByte(3, true);
        SLAScriptEngine::SLExecFunc();
    }

    //call mouse-in script function for ret
    if(ret != NULL and MOFuncID > 0)
    {
        //call mouseOver with an in for ret
        SL_LOG("Executing MouseOver function for an in %i", MOFuncID);
        SLAScriptEngine::SLPrepFunc(MOFuncID);
        SLAScriptEngine::SLSetObject(script_class);
        SLAScriptEngine::SLSetArgObject(0, ret->getName());
        SLAScriptEngine::SLSetArgDWord(1, x);
        SLAScriptEngine::SLSetArgDWord(2, y);
        SLAScriptEngine::SLSetArgByte(3, false);
        SLAScriptEngine::SLExecFunc();
    }

    if(MMFuncID > 0)
    {
        //call mouseMove with an in for ret
        SL_LOG("Executing MouseMove function for an in %i", MMFuncID);
        SLAScriptEngine::SLPrepFunc(MMFuncID);
        SL_LOG("Prepared");
        SLAScriptEngine::SLSetObject(script_class);
        SL_LOG("Obj Set");
        SLAScriptEngine::SLSetArgDWord(0, x);
        SL_LOG("Arg 1");
        SLAScriptEngine::SLSetArgDWord(1, y);
        SL_LOG("Arg 2");
        SLAScriptEngine::SLExecFunc();
        SL_LOG("Executed");
    }

    last_mouse_mov_ptr = ret;
    return ret;
}
.. it crashes, without any sort of message apart from a generic Windows "this program crashed unexpectedly" message. The log file:
LOG: Executing MouseMove function for an in 159
LOG: Prepared
LOG: Obj Set
LOG: Arg 1
LOG: Arg 2
... shows it gets to calling the execfunc function, shown far above, but it seems to crash upon calling the function, without even returning anything to assert against, as it never gets to logging "Executed". I note that executing normal global functions from within the engine works fine - there's an "OnStart" function and an "OnTick" function that execute without any trouble. For the record, I'm running Angelscript from the SVN with a revison number of 477, compiling using Code::blocks and the latest MinGW. Am I doing something wrong, or is this an Angelscript bug? Should I be defining a single instance of the child script class in the script instead of instanciating one in the engine, perhaps, and attempting to get a reference to that instance instead? Have I majorly failed to understand something?
Advertisement
Hi Brian,

I'm happy you like the library. Let me know if you have a link to your project page that uses AS so I can include it on the users list. :)

I reviewed your code, and I believe the problem is that you're not increasing the ref counter of the script_class after you receive it from the factory in SLScreen::initialise(). The asIScriptContext::GetReturnObject() method just returns the pointer without increasing the reference, so unless you increase the reference when you store the pointer, the context will free the instance when the context is reused or freed. This would explain the unexpected crash, as asIScriptContext::Execute attempts to call a method on an invalid script object.

Other than that you seem to be doing everything correctly.

Let me know if adding a call to asIScriptObject::AddRef() resolves the problem or if I need to investigate potential bugs in the library.

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

I'll certainly let you know as soon as I actually have a project page.

As for the fix: That would make sense. Thanks. I'll give that a go and report back to you.
Yes, that worked. Thanks muchly.

For future reference, which part of the manual refers to ScriptObject and the cicumstances under which the context cleans them up? I was reading "Using script classes", but I can't see anything in there.
Glad to hear it worked. :)

In your situation it is explained on the page Calling a script function, which is referenced from the Using Script Classes page. But I really need to document it in the description for the GetReturnedObject as well.

I try to make the way the functions behave as consistent as possible, and in general any functions with the prefix Get will return the pointer without increasing the reference (i.e. the engine is still the owner), and any function with prefix Create will return the pointer and increase the reference (the application becomes the owner). There may be some places where this is not consistent, and if you find one I'd appreciate it if you alert me.



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

Ah, I see. I read this bit:

"The library will still hold a reference to the object, which will only be freed as the context is released."

... and thought I was okay because I don't release the context unless something screws up and the program is going to exit, but I didn't read this bit:

"It is important to make a copy of the returned object, or if it is managed by reference counting add a reference to it. If this is not done the pointer obtained with GetReturnObject() will be invalidated as the context is released, or reused for another script function call."

Comprehension failure on my part.

This topic is closed to new replies.

Advertisement