Delegates in AngelScript

Started by
25 comments, last by WitchLord 10 years, 9 months ago
Currently thinking about how delegates should work, as I find myself becoming really, really hamstrung without their inclusion in AngelScript. While I think I have some of the implementation worked out, I'm curious what people would want in an implementation and what the syntax should be. From a technical perspective, I think it's best approached at a single-subscriber level, with a delegate storing a function to call and an additional 'this' pointer; multicast delegates/events could be either a library add-on extending the built-in array type or something left to the application interface to provide.

What I'm not sure of, however, is how this should interact with garbage collection (as I understand it, this bites people in the ass with startling frequency in C#, do we need to have a language-level 'weak reference' construct too?) and cross-module function imports. Community, fire away.
clb: At the end of 2012, the positions of jupiter, saturn, mercury, and deimos are aligned so as to cause a denormalized flush-to-zero bug when computing earth's gravitational force, slinging it to the sun.
Advertisement
Ah, you beat me to this subject :)

I feel that delegates is currently one of the most important lacking feature in AngelScript, and one that I was planning to work on for the upcoming releases.

I personally don't have any experience to draw from in designing how delegates should work in AngelScript, so this kind of community input will definitely be important to map out how delegates should work.

Personally I want to make the delegates as simple as possible (e.g. no built-in multicasting). I would also like to see the delegates replace the current function pointers, to reduce the amount of redundant features in the library/language.

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 think the C# way is the best approach as far as language features go. Being able to declare a funcdef / delegate type in-place would be a huge help, as would the ability to take a handle to a member function by storing a "this" pointer if necessary.

It could be a problem if the object that owns the method is destroyed before the delegate, though (I think this is what InvalidPointer is talking about with regards to garbage collection). It probably wouldn't be a good idea to have the delegate keep a strong reference to the object to keep it alive. Maybe there could be a way to invalidate the delegate when its object is destroyed, so calling it would throw an "Unbound function" exception just like calling an empty function handle.

I agree with the decision to keep multicasting out of the engine proper. With the addition of a function call operator it would be reasonably simple to create a custom class that acted as a multicast delegate.
I made some delegates system in C++ that I exposed to Angel script in my project with minor modifications of a v2.22.0 WIP of AS (in order to be able to specify a method adress).

This delegate system works with events and subscribers, only subscribers has been exposed to my angelscript (ie AS can catch some Engine event but not raise some event).
To expose briefly how it's used:
I register a funcdef in the ASEngine which will be my signature (for instance void MouseMoveEvent(CMouse@, const CVec2f&in))
Then I expose to the script an "event subscriber" (for instance: CMouseMoveEventHandler) that declares a method "SetCallback" which takes in our exemple (IEventHandler@, MouseMoveEvent@). IEventHandler is an empty interface that I declared so AS classes can register and catch events simply by inherit from it (there may be better solutions, and maybe there already is a hidden common parent class to all script classes)

After that all Game Engine that have events expose a RegisterToEventxxx and a UnregisterFromEventxxx (in our exemple RegisterToEventMouseMove(CMouseEventHandler@+) / UnregisterFromEventMouseMove(CMouseEventHandler@+)

Then the class script declares an EventHandler, implements the "callback" method and all is done
(this is pseudo code and may contain syntax errors)

class CScriptCatcher : IEventHandler
{
CScriptCatcher()
{
m_MouseMoved.SetCallback(this, @OnMouseMove);
GetGame().RegisterToEventMouseMove(m_MouseMoved);
}
~CScriptCatcher()
{
GetGame().UnregisterFromEventMouseMove(m_MouseMoved);
}
private void OnMouseMove(CMouse@ _pMouse, const CVec2f& _vDelta)
{
//Do some stuff here to hande mouse move
}
private CMouseMoveEventHandler m_MouseMoved;
}


There certainly a better way and a better interface to use the idea to be a little more lightweight for the user, but it might help you to have an idea on how things could be done.

For the delegate implementation in C++, it's an event class that holds a list of generics subscribers using templates (because the system is primarily used with C++). Subscribers are only a "this" pointer and method pointer OR a function pointer so delegates can be either method members with a this or a static method / a function.

Hope this might help ;)
Jake Albano:

I like the C# syntax too (without the multicasting). I think it is the most clean syntax for delegates.



Quittouff:

I'm not sure what customizations you made to the library, but it looks like you don't have a compile time verification of the method signature. Is that right?

There is no common parent class for all script classes, and I do not plan to add one. However, the CScriptHandle add-on would probably work for you as it serves as a generic handle that can hold a reference to any reference object, including script classes.

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

The only modifications I made is being able to point a method address: I added a "IsSignatureExceptNameAndObjectEqual" that is the same as "IsSignatureExceptNameEqual" and does not care about the object type, so a funcdef is not dependent on the object type but only on the method signature. It may not be ideal but it was the fastest way for me to do what I wanted.

And actually yes, this method provide a compile time verification of the callback signature, if you implement a callback with the wrong signature, it will not compile at the "SetCallback" as the second argument is a pointer to a funcdef. It would produce this kind of error: (example taken for my script sorry if names are not very relevant)

Compiling BPE
MainMenuForm.bhass (3, 5): INFO: Compiling CMainMenuForm::CMainMenuForm()
MainMenuForm.bhass (6, 27): ERROR: No matching signatures to 'CButtonEventHandler::SetCallback(CMainMenuForm&, OnChallengesClick@const)'
MainMenuForm.bhass (6, 27): INFO: Candidates are:
MainMenuForm.bhass (6, 27): INFO: void CButtonEventHandler::SetCallback(IEventHandler@, ButtonEvent@)

(just added an int parameter to the callback method)

So even if the error is not very clear with current implementation, it is triggered when the callback signature is incorrect at compile time.

I didn't think of the CScriptHandle that would have been cleaner indeed. But now the product has been released so it may be for the next one :P.
It's a good solution for what you wanted.

Delegates would need something similar to work, as they are supposed to be able to take either a global function or an object method. I think the opCall feature that cellulose is working on will be needed too.

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

If I may request a feature to be included in this: When taking the handle of an object method, it would be awesome to be able to get accessor functions by their short names. For example:


// This
myDelegate = @myObject.prop;
// Instead of this
myDelegate = @myObject.get_prop;


I have no experience with writing compilers, so I don't know if this is feasible to implement.
It's tricky, but can be done similarly to how the property accessors are evaluated already, i.e. the decision to use either set or get needs to be deferred until the very last moment when it can be determined which function signature the delegate is expecting.

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

Is Delegate a confirmed feature for AngelScript?

A function pointer and an ad-don just as well do the same job.


void GlobalFunction()
{
}

Object obj1;
Object2 obj2;

Signal signal;

signal << function(Object::DoStuff, @obj1);
signal << function(Object2::DoOtherStuff, @obj2);
signal << function(GlobalFunction);

signal.call(); // call all functions, also accepts parameters and refuses to call functions with wrong parameters.

I already do this but with strings and cached method pointers.
It just need to be faster with function pointers.

I don't see the point of adding delegates. Or any Observer pattern specific functionality to core language.

This topic is closed to new replies.

Advertisement