Ownership problem of Funcdefs.

Started by
12 comments, last by Wracky 10 years, 6 months ago

Hi and thanks for reading.

I've been looking at how to implement callback functions that I can pass to the application from script, and subsequently call from within the application.

A while back, when funcdefs didn't exist yet, I implemented my own Delegate class to solve this problem, but the problem with those is, that I cannot call object methods/actual delegates with those, since I cannot find a way to pass the script-class object reference to the application.

The documentation shows how to create a script class from within the application, and then call a function on that, but I have no idea how to pass a script object that was created in script to the application for calling.

Now I've updated to version 2.27.1 of Angelscript, and I found out it supports function definitions and delegates. I think this is a great solution to the problem. My delegate class for instance, couldn't match arguments, so it wasn't really safe. But with funcdefs I have an ownership problem.

When I create a delegate from an object method, the delegate object refs the object. So the object cannot be cleaned up while this delegate exists. Now if I proceed to pass this delegate to the application, I get an asIScriptFunction object. If I want to store that asIScriptFunction for later use, I reckon I'll have to increase it's ref.

Now the problem is, that the scripter is now no longer in control over when his object is destroyed, or when the delegate is released. If for instance, he registers an object method in a delegate as an event handler for, let's say, a key press, I'd have to return a certain handle, that he can later use to explicitly deregister this even, and thus "release" the asIScriptFunction object, in turn releasing the delegate, in turn releasing his object. This feals really redunant, since the delegate already represents this bond.

However if I don't ref the asIScriptFunction pointer in the application, I have no way of knowing when the delegate is released in script. Some kind of Weak referencing or a callback could work.....

I'm not sure which direction I want to take, or what is a good approach.
Is there a way to pass a script class object to the application so I can implement my own delegate?

Or should I stick to returning a new handle object that the scripter needs to keep reffed for as long as the delegate should stay alive?

In the last case generic func defs would be nice. Andreas talked about these when funcdefs were introduced, but I don't think they exist yet?

Any tips are appreciated. Many thanks,

Wracky.

http://www.piko3d.net
Advertisement

Hi Wracky,

If you don't want the delegate to hold a strong reference to the original object, then you'll need to look into implementing a weak-reference container.

Luckily for you, with version 2.27.1 weak references is already a reality, so it should be quite easy for you to do cool.png

I recommend you keep using the built-in delegates for allowing the script to pass the callback to the application, but instead of storing the received delegate directly, you dissect it and store the object pointer with a weak reference and the method pointer normally.

void FuncReceivingDelegate(asIScriptFunction *delegate)
{
   // Get the object pointer from the delegate
   if( delegate->GetFuncType() == asFUNC_DELEGATE )
   {
     void *obj = delegate->GetDelegateObject();
     asIObjectType *objType = delegate->GetDelegateObjectType();
     asIScriptFunction *method = delegate->GetDelegateMethod();
  
     // Create a weak ref to keep the pointer to the object
     CScriptWeakRef *weakRefToDelegatedObject = new CScriptWeakRef(obj, objType);
     
     // Store the weak ref and the method instead of the original delegate
     ...
   }
 
   delegate->Release();
}

In case you wish to explore the generic funcdefs option, then you need to know that the generic handle add-on is capable of holding references to funcdefs too.

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

Thank you for the very quick response!

This looks very interesting. I think I can make something work with this.
Thanks again.

Wracky.

http://www.piko3d.net

I've been trying some things, but I noticed I still have one ownership problem:
That of the asIscriptFunction object.

If I only weak-ref the OBJECT, the "binding" of a script function bound for this object will still exist in the application even when the delegate goes out of scope in script.

With the delegate out of scope, the scripter might assume that this binding is now broken.

The same problem occurs when the user passes a simpel global function callback. In this case, I have no object to ref, and only the function to hold.
Again, reffing the function object would move control over the callback from scripter to application, adding the necessity for some other application interface to break the callback binding. Not reffing the object would cause me to call it when the function object is already gone, which I assume, is a bad thing.

This can ofcourse be prevented if I can weak-ref the asIScriptFunction as well (but this is currently not possible is it?, The CScriptWeakRef needs an object type?). As a matter of fact, if I could just weak-ref the asIScriptFunction, I don't need to destinguish between an object delegate or a normal callback, since if the delegate object is destroyed, the object ref will be freed as expected, and the func ref will be as well, and the object will not be destroyed as long as the delegate exists, since it refs it.

Couldn't the script delegate weak-ref the object and fallback to a no-op if the object is destroyed before the delegate is called?

In that case, the control of both the lifetime of the object, and the lifetime of the delegate remain in the hands of the scripter, that is, if the application can weakref the asIScriptFunction*

I've read different posts about the subject and understand this is a difficult problem smile.png However I can explain a scripter that the delegate will ref the object for as long as it exists, but it is kind of strange that a script callback or delegate stops existing when the object funcdef object goes out of scope, but not when a global function callback is passed to the application (only when it's a delegate).

Any thoughts on this?

Regards,

Wracky.

http://www.piko3d.net

The asIScriptFunction object is the actual script code. As long as you don't discard the script module the function object will be valid. You do not need to increase the reference of the asIScriptFunction if you have control over when the script modules are discarded. If you want you can use the user data of the asIScriptFunction to receive a callback when the actual script function is being removed from the engine. With the callback you will then be able to cleanup any code that is still referencing the asIScriptFunction.

See:

asIScriptEngine::SetFunctionUserDataCleanupCallback

asIScriptFunction::SetUserData

While I could theoretically change the built-in delegate object to use weak references it is currently not something I plan to do. Delegates can be used with application registered types as well, and not all application types are implemented to support weak references.

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

OK, I've tried some more things, and then realized that what I was asking is probably something weird smile.png
I thought about how other languages solve this problem, and I just realized that unregistering callbacks is always a bit awkward smile.png

I thought this could be handled with the lifespan of the function handle, but this is not usually the case in other languages. So I reckon this might not be the best way to go, and is perhaps not always what you want, as it requires the user to keep the thing alive/in scope some how.

You did help me a great deal in understanding what is going on, and have given me a couple of options to solve this. At least now I know how to let the App know when the delegate CANNOT be called (and the user forgot to unregister the callback). I particularly like the callback function option, as it eliminates inactive objects that stay there simply because the didn't get called. smile.png

The Object weakref is nice to make sure I don't take ownership of the object.

I think I will go with the C# way of deregistering callbacks, by passing the delegate once for register, and another time to unregister it.

So perhaps something like:


timer.register(timerCallback(myObject.myFunction));
timer.unregister(timerCallback(myObject.myFunction));

//Or perhaps I can even go full C# and make this work :)

timer.event += timerCallback(myObject.myFunction);
timer.event -= timerCallback(myObject.myFunction);

I noticed that when a function handle is passed it is the same, even when stored in different handle instances, and the delegates are not, but I can use GetDelegateFunction in that case. I'll use the function pointers to identify which callback to remove, and use the userdata callback to clean up callbacks that are no longer valid.

Thank you very much for the information, and taking the time of helping me with this.

Regards,

Wracky.

http://www.piko3d.net

Hi Andreas,

So I finally got around to implementing this, and I noticed a problem with the CScriptWeakRef.

The code you showed me for weakreffing the object in the engine throws an exception when I pass it a script object.


void FuncReceivingDelegate(asIScriptFunction *delegate)
{
   // Get the object pointer from the delegate
   if( delegate->GetFuncType() == asFUNC_DELEGATE )
   {
     void *obj = delegate->GetDelegateObject();
     asIObjectType *objType = delegate->GetDelegateObjectType();
     asIScriptFunction *method = delegate->GetDelegateFunction();
  
     // Create a weak ref to keep the pointer to the object
     CScriptWeakRef *weakRefToDelegatedObject = new CScriptWeakRef(obj, objType);
     
     // Store the weak ref and the method instead of the original delegate
     ...
   }
 
   delegate->Release();
}
The constructor for the CScriptWeakRef throws an exception on this line:

// Release the handle that was received, since the weakref isn't suppose to prevent the object from being destroyed
m_type->GetEngine()->ReleaseScriptObject(m_ref, m_type->GetSubType());

As far as I can see, GetSubType returns 0 if the object is not templated?
So it passes 0 as subtype here, and crashes in "ReleaseScriptObject" upon trying to access this null-pointer.

I've tried passing m_type instead of subtype when subtype was null, but that messes up the ref-count and destroys my object when it is still in scope.

adding another m_type->AddRef() didn't solve that.

Since I don't know exactly what I'm doing, I thought I'd ask ;-)

Here's my script:


Timer@ timer;

class Foo
{
  void bar()
  {
   	echo ("Called bar...\n");
  }
	
  ~Foo()
  {
  	echo ("Foo destroyed\n");
  }
}

Foo@ f;

void main(Control@ control)
{
  @f = Foo();
  @timer = control.createTimer(1000, true);
  timer.elapsed += Action(f.bar);
  timer.start();
}

Sincerely,

Wracky

http://www.piko3d.net

I'm sorry. I passed the wrong information to you before. The asIObjectType that should be passed to the weakref constructor is the object type of the weakref instance, not the type of the object being referenced.

Try this instead:

void FuncReceivingDelegate(asIScriptFunction *delegate)
{
  // Get the object pointer from the delegate
  if( delegate->GetFuncType() == asFUNC_DELEGATE )
  {
    void *obj = delegate->GetDelegateObject();
    asIObjectType *objType = delegate->GetDelegateObjectType();
    asIScriptFunction *method = delegate->GetDelegateFunction();
 
    // Determine the needed weakref template instance type
    string weakRefDecl = "weakref<";
    weakRefDecl += objType->GetName();
    weakRefDecl += ">";  
    asIObjectType *weakRefType = objType->GetEngine()->GetObjectTypeByDecl(weakRefDecl.c_str());
 
    // Create a weak ref to keep the pointer to the object
    CScriptWeakRef *weakRefToDelegatedObject = new CScriptWeakRef(obj, weakRefType);

    // Store the weak ref and the method instead of the original delegate
    ...
  }

  delegate->Release();
}

PS. I'll look into the exception when passing a null pointer, because that should be treated nicely by AngelScript.

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

Hmm. I think you don't have the latest version of the weakref add-on.

The CScriptWeakRef constructor shouldn't call ReleaseScriptObject. In fact, the weakref shouldn't call ReleaseScriptObject in any of the methods.

Please download the latest version of the add-on.

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

Hi Andreas,

Thank you for your tips. Sorry for my late response. I was on holiday for a bit :)

The example you gave me still doesn't work. The main problem being that GetObjectTypeByDecl doesn't exist :)

I tried to make it work with GetObjectTypeByName, but that function just returns NULL unless I ask for a Class name registered by the application.
So on both "weakref<Foo>" and "Foo" it just returns NULL. "Control" for instance, does work.

I did some hacking right before I went, and managed to get it to work in a (probably) ugly way, by just skipping the ReleaseScriptObject calls, and passing Type in cases where getSubType returned NULL. (when I still constructed the weakref with objectype instead of weakref<Foo> type.

This seemed to work but I had no idea if I broke anything else.

So far it's obvious to me that the weakref was written with script binding in mind ;-)
What I was wondering about though, is since there is no interface that communicates the refcount of objects of application registered types, weakrefs in script will probably be set to null while the application still holds refs, meaning the weakref will become null while the object is not destroyed. Is this assumption correct ?

It would really be wonderful in angelscript could support weakrefs better. callbacks could default to no-op when objects decay so they no longer ref the object they are holding. addRef/release functions could return the refcount so the scriptengine would be aware of the objects refcount, even when the object comes from the application.

Anyway, I could probably throw something together based on the weakref implementation that would work, but ofcourse it would be nice to have something that is also supported in future releases :)

What do you recommend?

Regards,

Wracky

http://www.piko3d.net

This topic is closed to new replies.

Advertisement