Function callbacks from loaded sheets
Hi, I'm planning on loading sheets from files (I believe that functionality exists/is intended?). The way my engine works right now, I have a 'GUI Boss' which keeps track of what sheets have been loaded into the GUI, and changes the GUI sheet when you move to a different section of the engine (e.g. from main menu to world editor). Because the function callbacks from GUI buttons et cetera can be to member functions of objects which get created and later deleted (e.g. my world editor gets created for use, then deleted when you leave it), the GUI will need to be able to figure out where to send callbacks from buttons whose details are stored in the gui sheet file, on the fly.
My suggestion for a way to achieve this would be for each of my classes that have their own GUI sheets to keep a std::map of strings to function pointers within themselves. Then, when they ask the GUI boss to load a particular GUI sheet for them, they can pass along a pointer to this map. Then, if the loadSheet() or whatever function in QuickGUI could take a map of strings to function pointers, it could connect the dots between function names stored as strings in the serialised sheet file, and actual function pointers in the class that's having GUIBoss load the sheet -- and set the function callbacks correctly.
Would you be up for approaching this issue this way? Or is there already a working solution for this issue?
Reading and writing to files are already supported, I test it regularly.
Now that I think about it, your suggestion might be doable! Basically what I would do is create another Singleton class, ie "CallbackManager". Users would have to register callbacks with this class, associated by some name, something like this:
CallbackManager* cbm = CallbackManager::getSingletonPtr();
cbm->registerCallback("My Callback", &myClass::myFunction, myClassInstance);
Then adding event handlers would be as simple as this:
I would have to add serialization support for Widget's Event Handlers, but the work flow would essentially become this:
1. Create QuickGUI
2. Register all Callbacks
3. Load/Unload Sheets as you want, and callbacks will be set appropriately.
[Edit] I just thought of a problem. As seen above, I need the instance in order to do the callback, ie myButton->myHandlerFunction(args). If myButton doesn't exist, I can't really pass in anything, can I?
Have to think about this some more. [/Edit]
[Edit2] Actually, I don't think this will be a problem. Right now as I code the library, I give Widgets event handler functions, so I have to supply the instance. But all other users can't modify QuickGUI code, and will instead be using event handlers that are not a part of Widget code. With this in mind I can assume that users will always be able to provide an instance. I can make the current "addEventHandler" functions protected, force use of the Callback manager, and add serialization support of event handlers of widgets.[/Edit2]
As long as you can overwrite old registered callback pointers when you need to update the pointers, I think that should work for my purposes. I'm going to be creating and deleting and recreating objects that will want callbacks to them, so every time they get constructed I'm going to want to update the callbacks with the new function pointers, and then load the sheet.
Only issue I can see from my perspective: I load the sheets the first time they're being displayed, and after that I just activate them when the relevant area is gone to.
So if I:
- create instance of class WorldEditor
- register all callbacks
- load sheet WeditorGUI
- delete instance of WorldEditor
- create new instance of WorldEditor
- activate sheet WeditorGUI
because I haven't reloaded the sheet, but only activated it, my sheet still has the old callbacks, and even if I re-register my callbacks with QuickGUI in my constructor, I'm guessing the existing widget callbacks wouldn't be updated. So:
My suggestion is that the widget callbacks actually have a pointer to an element in a map. The map is of String to (struct with a Function Pointer and Instance pointer inside). This map gets updated whenever a callback string is registered to a function.
So, if my button has a callback, the button.callBack or whatever can just be an int, say, 5. Now I go to [get fifth map element from std::map<CallBackStrings,Pointers_And_Instances>]. That element is just the function pointer that calls the right function.
So by putting a _pointer to a map element that has another pointer_ in the actual callback info, it means that whenever we update the registered location of a callback string with a new function pointer, we don't need to check all widgets that already point to that information: they continue to point to the right element in the map, and it's the map which we update, so that the new function gets called.
This would make my method feasible, and should be transparent to the end-user.
I can make the current "addEventHandler" functions protected
Note: as I understand it in the new system, these would not actually be protected: you'd just need to change their functionality so that instead of sending a function pointer and instance details, you add an event handler using just a string, as in your example in your post above:
And then like you said, the relationship between the string and the function pointer and instance pointer would be set by the callback manager before registering the event (or at least, before loading from file the sheet which contains the registered event string).
Regarding your comments about stored pointers to the map, you are correct in a sense. When a widget fires an event, it will get a pointer to the callback manager and execute the callback. Its up to the user to update the Callback functions stored in the Callback Manager, as it is assumed those will always be up to date. I will also allow overwriting callbacks, as you've raised a good scenario that would require this.
OK, so the callback info stored on each widget will actually be the string, then? And it'll check this against the CBM each time to get a pointer to the correct function?
The only reason I was suggesting an int offset to the correct part of the CBM map/list was to avoid the overhead of string lookups each time, rather than just checking the string when the string-formatted events are initially loaded from the sheet file. But, with std::map, maybe that's not too costly to look up each time a callback happens, anyway.
I don't think it will be too costly, the lookups only occur when some event is fired, and not every frame. I always use maps, so the lookup should be more efficient than iterating through a list and doing string compare, at least.
And thanks again for the idea, I think it will be a great addition.