🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Odd behavior on using Delegate method as parameter for callback

Started by
3 comments, last by robertomurta 4 years, 4 months ago

I'm exposing a C++ Class to AS (WebsocketClient), and it should to be destroyed as the class instantiating it is destroyed.

When the C++ Class is destroyed, it's destructor runs Disconnect() method.

So, for the callbacks functions I'm breaking up as the Delegates example on https://www.angelcode.com/angelscript/sdk/docs/manual/doc_callbacks.html.

The odd behavior:

After some fix suggested on other discussion, It almost works as expected, I could pass a delegate for callback, and when destroyed, WebsocketClient destructor and Disconnect() are called, unless the method used for callback is defined in the same class as the one that instantiate a WebsocketClient object.

Seemed to be some kind of circular references, but then I added another object in the circle and it worked.

Some code:

funcdef void CALLBACK();

void PrintConnectedB()
{
	print("AS >>> is connected! ");
}

class teste
{
	void PrintConnected()
	{
		print("AS >>> is connected! ");
	}
}

class GameScene : Scene
{
...
	private WebsocketClient@ m_room;
	teste a;

	void PrintConnected()
	{
		print("AS >>> is connected! ");
	}

	void onCreated()
	{
	...
		@m_room = WebsocketClient();
 // new instance


		// test one: passing global function
		m_room.SetOnConnect(@PrintConnectedB);
 // Works as expected, connection is made, and when destructing GameScene, WebsocketClient destructor is called

		if(!m_room.IsConnected())
 // connect
			m_room.Connect("localhost", "1201");
	...
	}
...
}
...
		// Passing a method from a class instantiated on this
		m_room.SetOnConnect(CALLBACK(a.PrintConnected)); // Works as expected, connection is made, and when destructing GameScene, WebsocketClient destructor is called
...
...		
		// Passing method from this object.
		m_room.SetOnConnect(CALLBACK(this.PrintConnected)); // Connection is made, but when destructing GameScene, WebsocketClient destructor is not called
...

I also tried instantiating teste the same way as WebsocketClient with:

private teste@ a;
// then
@a = teste();

That worked as well.

Advertisement

Update:

If I ensure @m_room = null; before GameScene destruction, (ie. GameScene destructor is called), WebsocketClient is properly destroyed just as GameScene destructor is called.

but if @m_room = null; is inside ~GameScene, GameScene is not destructed.

Let's analyze the references:

first case:

stack → GameScene → WebSocketClient (through m_room) → global PrintConnectedB (no circular reference)

second case:

stack → GameScene → WebSocketClient (through m_room) → teste (through delegate with a.PrintConnected) (no circular reference)

Observe, the teste instance ‘a’ is a member of GameScene, but as it is a reference object it is allocated separately in the memory so holding a reference to ‘a’ doesn't keep the GameScene instance alive.

third case:

stack → GameScene → WebSocketClient (through m_room) → GameScene (through delegate with this.PrintConnected) (circular reference)

Observe, when you manually set m_room to null before releasing your reference to GameScene you break up the circular reference, thus it works properly.

Without this you need to either make sure you store the reference from WebSocketClient to GameScene using a weak reference, or implement and register the behaviours for garbage collection on WebSocketClient so that angelscript can detect and destroy the circular reference.

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

Thank you very much, that enlightened me.
I choosed to register the behaviors for garbage collection. By design, the class constructor creates a new asIScriptContext for running the callbacks, and it was released at the destructor code, now it's automatically released when the references are released (hope I'm right with that, did some debugging), code seems cleaner.

For someone else with trouble like this, here's what was done:

added gc_flag member to the class.

class WebsocketClient : public std::enable_shared_from_this<WebsocketClient>
{
	int m_ref;
	bool m_gc_flag;
	...

almost copy/paste the example in the doc

void WebsocketClient::SetGCFlag()
{
	// Set the gc flag as the high bit in the reference counter
	m_gc_flag = true;
}

bool WebsocketClient::GetGCFlag()
{
	// Return the gc flag
	return m_gc_flag;
}

int WebsocketClient::GetRefCount()
{
	// Return the reference count, without the gc flag
	return m_ref;
}

void WebsocketClient::EnumReferences(asIScriptEngine* engine)
{
	// Call the engine::GCEnumCallback for all references to other objects held
	engine->GCEnumCallback(m_on_connect_callback);
	engine->GCEnumCallback(m_on_connect_callbackObject);
}

void WebsocketClient::ReleaseAllReferences(asIScriptEngine* engine)
{
	// When we receive this call, we are as good as dead, but
	// the garbage collector will still hold a references to us, so we
	// cannot just delete ourself yet. Just free all references to other
	// objects that we hold

	if (m_on_connect_callback)
		m_on_connect_callback->Release();
	if (m_on_connect_callbackObject)
		engine->ReleaseScriptObject(m_on_connect_callbackObject, m_on_connect_callbackObjectType);
	m_on_connect_callback = 0;
	m_on_connect_callbackObject = 0;
	m_on_connect_callbackObjectType = 0;
}

I added the “| asOBJ_GC” on RegisterObjectType

void RegisterWebSocketClient(asIScriptEngine* pASEngine)
{
	int r;

	// Class
	r = pASEngine->RegisterObjectType("WebsocketClient", 0, asOBJ_REF | asOBJ_GC); assert(r >= 0);

Used the macros for asCALL_GENERIC

asDECLARE_METHOD_WRAPPERPR(WebsocketClient__SetGCFlag, WebsocketClient, SetGCFlag, (void), void);
asDECLARE_METHOD_WRAPPERPR(WebsocketClient__GetGCFlag, WebsocketClient, GetGCFlag, (void), bool);
asDECLARE_METHOD_WRAPPERPR(WebsocketClient__GetRefCount, WebsocketClient, GetRefCount, (void), int);
asDECLARE_METHOD_WRAPPERPR(WebsocketClient__EnumReferences, WebsocketClient, EnumReferences, (int&), void);
asDECLARE_METHOD_WRAPPERPR(WebsocketClient__ReleaseAllReferences, WebsocketClient, ReleaseAllReferences, (int&), void);

And register the behaviours.

	// Register the GC support behaviours
	r = pASEngine->RegisterObjectBehaviour("WebsocketClient", asBEHAVE_SETGCFLAG, "void f()", asFUNCTION(WebsocketClient__SetGCFlag), asCALL_GENERIC); assert(r >= 0);
	r = pASEngine->RegisterObjectBehaviour("WebsocketClient", asBEHAVE_GETGCFLAG, "bool f()", asFUNCTION(WebsocketClient__GetGCFlag), asCALL_GENERIC); assert(r >= 0);
	r = pASEngine->RegisterObjectBehaviour("WebsocketClient", asBEHAVE_GETREFCOUNT, "int f()", asFUNCTION(WebsocketClient__GetRefCount), asCALL_GENERIC); assert(r >= 0);
	r = pASEngine->RegisterObjectBehaviour("WebsocketClient", asBEHAVE_ENUMREFS, "void f(int&in)", asFUNCTION(WebsocketClient__EnumReferences), asCALL_GENERIC); assert(r >= 0);
	r = pASEngine->RegisterObjectBehaviour("WebsocketClient", asBEHAVE_RELEASEREFS, "void f(int&in)", asFUNCTION(WebsocketClient__ReleaseAllReferences), asCALL_GENERIC); assert(r >= 0);

This topic is closed to new replies.

Advertisement