Jump to content

  • Log In with Google      Sign In   
  • Create Account

- - - - -

Ownership problem of Funcdefs.


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
13 replies to this topic

#1 Wracky   Members   -  Reputation: 140

Like
0Likes
Like

Posted 14 September 2013 - 02:39 PM

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.


Edited by Wracky, 14 September 2013 - 02:52 PM.


Sponsor:

#2 Andreas Jonsson   Moderators   -  Reputation: 3352

Like
1Likes
Like

Posted 14 September 2013 - 04:07 PM

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


Edited by Andreas Jonsson, 14 September 2013 - 04:09 PM.

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

#3 Wracky   Members   -  Reputation: 140

Like
0Likes
Like

Posted 15 September 2013 - 07:50 AM

Thank you for the very quick response!

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

Wracky.



#4 Wracky   Members   -  Reputation: 140

Like
0Likes
Like

Posted 15 September 2013 - 08:32 AM

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.

 


Edited by Wracky, 15 September 2013 - 08:43 AM.


#5 Andreas Jonsson   Moderators   -  Reputation: 3352

Like
1Likes
Like

Posted 15 September 2013 - 10:16 AM

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

#6 Wracky   Members   -  Reputation: 140

Like
0Likes
Like

Posted 15 September 2013 - 04:03 PM

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.

 

 

 


Edited by Wracky, 15 September 2013 - 04:16 PM.


#7 Wracky   Members   -  Reputation: 140

Like
0Likes
Like

Posted 03 October 2013 - 02:33 PM

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

 


Edited by Wracky, 03 October 2013 - 02:41 PM.


#8 Andreas Jonsson   Moderators   -  Reputation: 3352

Like
0Likes
Like

Posted 03 October 2013 - 04:39 PM

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

#9 Andreas Jonsson   Moderators   -  Reputation: 3352

Like
0Likes
Like

Posted 03 October 2013 - 04:45 PM

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

#10 Wracky   Members   -  Reputation: 140

Like
0Likes
Like

Posted 10 October 2013 - 01:11 PM

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

 



#11 Andreas Jonsson   Moderators   -  Reputation: 3352

Like
0Likes
Like

Posted 10 October 2013 - 02:34 PM

I'm so sorry. I wasn't in front of my own computer when responding and didn't validate the suggestion.

 

You're right, GetObjectTypeByDecl doesn't exist (I'll need to implement this). Instead a combination of GetObjectTypeById() and GetTypeIdByDecl() can be used to get the object type for a template instance.

 

asIObjectType *weakRefType = engine->GetObjectTypeById(engine->GetTypeIdByDecl(weakRefDecl.c_str());

 

The weakRef object will check the asILockableSharedBool instance that it retrieves from the object to determine if the real object instance is still alive or not. While the object is still alive the asILockableSharedBool::Get() will return false. Just before the object dies, it should set the value in the asILockableSharedBool to true to signal the weakRef object that the pointer is no longer valid.

 

Application registered objects that should support weak references must implement and register the asBEHAVE_GET_WEAKREF_FLAG behaviour with AngelScript.

 

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

#12 Wracky   Members   -  Reputation: 140

Like
0Likes
Like

Posted 10 October 2013 - 02:51 PM

I tried the GetTypeIdByDecl() approach before I commented, but that just returns asINVALID_TYPE for anything other than Application classes. The function is invoked by script, so it is compiled and other types should be available at the point of the call.

 

Thanks for telling me about the asBEHAVE_GET_WEAKREF_FLAG. Can't believe I've missed that :-)

Regards,

Wracky



#13 Andreas Jonsson   Moderators   -  Reputation: 3352

Like
0Likes
Like

Posted 10 October 2013 - 05:47 PM

Ah, yes. You'll need to use GetTypeIdByDecl() on the asIScriptModule for script types, otherwise it will not know which script type you're referring to. You can get the module from the object type.  

 

 

I'll need to think of an easier way to instanciate the weakref object from the application side. It shouldn't be necessary for the application to jump through so many hoops to get the object type of the weakref template instance.


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

#14 Wracky   Members   -  Reputation: 140

Like
0Likes
Like

Posted 11 October 2013 - 04:54 PM

Thanks for the quick responses.

I got it to work now :) with the type ID from the scriptModule.
The CScriptWeakRef also assumes it gets an handle that was reffed when it was passed, so it releases it as to not keep the strong.

So to make sure the object didn't get destroyed because of that I had to pass the object reffed (so add another ref)

 

Anywayz, glad to finally get it to work.

Oh btw, I looked at the documentation regarding asBEHAVE_GET_WEAKREF_FLAG, and it looked like a whole lot to take in.
Our application though, is fully aware of refcounting on application-registered objects, whether they were reffed/created by script or by the application.
We have our own set of smart pointers, much like the ones in boost, that count strong as well as weak references. Ref-objects expose addRef and Release functions that share the same use/ref count, which we use for the behaviours in script.

 

So basically I could write my own template specialization for weakref<T> instead of adding the WEAKREF_FLAG behavior I guess?
If I use template specialization for all Application types, and use CScriptWeakRef for everything else, I don't have to deal with the shared boolean, and can use our existing weakref methods.  I'll try that later :)

Well thanks again!

Regards,

Wracky.
 






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS