Advertisement

Delegates from application (for gui)

Started by January 31, 2014 07:23 PM
2 comments, last by WitchLord 10 years, 9 months ago

Hello,

I'm trying to implemented scripting for my gui-module, however I'm having some troubles. I use a qt-like system where normally each widget has multiple signals that the application can connect to:


class TestController :
     public BaseController
{
     TestController();
     void OnClickButton();
};

TestController::TestController()
{
    Widget widget;
    widget.SigReleased.Connect(this, &TestController::OnClickButton);
}

I'm fail to see whats the best way to port this to anglescript. What I've got so far is that I can load a generic BaseController in script, and access the widgets:


class Scene1 : IScene
{
	Scene1()
	{
		@controller = loadController("Menu/HUD.axm");
		Widget@ widget = controller.GetWidget("Overhead");
                // widget. ??? => should call OnClick when SigClicked from widget is executed
	}

        void OnClick()
        {
        }
	
	GuiController@ controller;
}

Now seeing that the controller is created in script, whats an easy way to have the OnClick-method being called from code? I've read some seperate sections about function handles, how to register them, but I failed to see how to connect all this information. Can someone give me an example or some pointers?

I think you'll want to use delegates for this.

Your script would look something like this:

class Scene1 : IScene
{
   Scene1()
   {
     @controller = loadController("Menu/HUD.axm");
     Widget@ widget = controller.GetWidget("Overhead");
 
     // Create a delegate object and connect it to the signal handler
     widget.SigClicked.Connect(OnClickCallback(this.OnClick));
   }

   void OnClick()
   {
   }

   GuiController@ controller;
}

The OnClickCallback is a funcdef that is registered from the application like this:

engine->RegisterFuncdef("void OnClickCallback()");

The SigClicked::Connect method is registered like this

engine->RegisterObjectMethod("SigClicked", "void Connect(OnClickCallback @)", asMETHODPR(SigClicked, (asIScriptFunction *), void), asCALL_THISCALL);

As you can see the Connect method will receive a pointer to the delegate as a normal script function, which you can then use to prepare and execute in a script context just as if it was a global script function.

Should you want to extract the object pointer and the class method from the delegate object and store those instead of the delegate itself, you can do so with the methods GetDelegateObject, GetDelegateObjectType, and GetDelegateFunction.

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

Advertisement

Thanks for your answer,

I do understand the process in more detail now, but there is still one point left that makes me uncertain. I quess its rather due to my design that a general AngelScript-issue, but I'll ask anyway, maybe you know an answer.

So In the registered SigClicked.Connect-method... thats where the problem lies really, SigClicked is just an instance of the variadic templates Signal-type (core::Signal<> SigClicked), I quess that would be ok for registration, but how am I supposed to store the asIScriptFunction for execution? I quess I could make it a free function instead:


void connectSigClicked(core::Signal<>& signal, asIScriptFunction* pFunction)
{
}

but the problem is, I have to prepare and execute as you said. Its not so much a problem how to get the script-engine (I'd store it as a namespace-nested "global" in the CPP-file where I register all those messages), but rather how to call those methods. signal would take a normal and a function pointer, that have to be called, thats not enough for executing the script function. Unfortunately it is not practical for me to modify any of eigther the signals, controller or widget classes - they should work independant of scripts as well, and I have about 30 widgets just now so e.g. giving a modified widget parent class is out of the question. The only thing I can think about now is to use my
method-wrapper-class:


Method* connectSigClicked(core::Signal<>& signal, asIScriptObject* pObject, asIScriptFunction* pFunction)
{
        Method* pMethod = new script::Method(*pContext, *pObject, *pFunction);
        signal.Connect(pMethod, &Method::Execute<>);
        return pMethod; 
}

Execute of the method-wrapper does just what you described, it prepares, sets the objects and parameters and then executes. Since the signal stores a pointer, I have to create it with dynamic allocation though, and therefore probably have to return it for the script to store and eigther manually release or using AngelScripts reference counting:


class Scene1 : IScene
{
   Scene1()
   {
     @controller = loadController("Menu/HUD.axm");
     Widget@ widget = controller.GetWidget("Overhead");
 
     // Create a delegate object and connect it to the signal handler
     @clickedMethod = widget.SigClicked.Connect(OnClickCallback(this.OnClick));
   }

   void OnClick()
   {
   }

   GuiController@ controller;
   Method@ clickedMethod;
}

Otherwise I'd have a nice big memory-leak, but having to create & store (and potentially manually destroy) such a useless pointer nevertheless for every signal is pretty inconvenient and prone to error. Do you see any easier solution?

Using the Method object to abstract away the fact that the signal callback is actually a script function rather than a normal c++ function sounds like a good idea.

You probably don't want your Method to hold the pointer to the asIScriptContext though. Instead it should request an available script context when it is time to execute the script function. The asIScriptContext object is quite heavy weight and you should design your application to reuse the object as much as possible. Only if you have more than one script executing in parallel will you need multiple script contexts.

I also don't think you should be returning the Method instance to the script. The script writer shouldn't have to bother with the internals of your engine.

How are you dealing with the memory management for signal callbacks to C++ objects? If your signal handler doesn't have the logic to free the Method instance once it is removed, then you need to implement that somewhere in between.

Perhaps you can modify your widget class to allow it to have an optional clean-up routine for the connected signal callbacks? That would still be generic and would likely be useful for some C++ classes 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

This topic is closed to new replies.

Advertisement