Event handling: is there a way to know which function/callable accept() assigned to specific events?

Checked the manual entries for event handling and the API reference for DirectObject but there doesn’t seem to be a method to allow that.

It is not a critical problem, since one just has to keep track of which callable (and args) gets assigned to a particular event via accept().

But even so I thought it would be useful to ask, even if just to have it officially answered here for future reference.

Thanks in advance.

I believe that, in taking a request for an event to be “accepted”, DirectObject is simply passing the request on to Messenger, which is where the actual work of “accepting” the event is done.

I particularly note the “find” method in Messenger, which–if I understand correctly–looks like it does what you want.

(Although I noted that it’s indicated as being “primarily a debugging tool”–I don’t know whether or not that indicates some unstated cost or pitfall of its usage.)

1 Like

Oh, I saw the messenger object in DirectObject’s source, but didn’t think at the time to check it. It makes sense, after all most of what happens inside DirectObject is delegated to the messenger.

I’ll check it as well, although for now I’m thinking the solution I proposed in the first message on this thread is more appropriate. That is, messing with an object like the messenger that, IIRC, is not presented on the manual as an object for users to handle directly seems very brain surgery-y to me, something which I’d like to avoid when possible.

For now I’m doing something like this, for a class that works as a single state in a custom state machine:

    def __init__(self):

        # [...] other constructor code

        # dict mapping events to their handlers

        self.event_handler_map = {

            'home': self.go_to_origin,

            'tab': ( 

                partial(
                    setattr,
                    self,
                    'next_state_name',
                    'visualization_navigation',
                )

            ),

        }

    def enter(self):

        for event_name, handler in self.event_handler_map.items():
            base.accept(event_name, handler)

    def exit(self):

        for event_name in self.event_handler_map:
            base.ignore(event_name)

    # [...] rest of the class

I’m not using the FSM that comes with Panda3D because although I find it fine, in general I believe having the states as different classes allows better reuse of code and resources. For instance, a state that has a subset of the functionality provided in another existing state could simply be a subclass of that state or vice-versa.

Edit:

Coming back after having checked the Messenger’s source and yes, keeping track of the functions/callables used for each event manually seems to be better after all. First, the messenger interface is very convoluted (not blaming the maintainers, as I also maintain FOSS proejcts and know the pain), as it has to keep track of several other objects and resources. Second, as I said before, the manual doesn’t explicitly mentions the messenger as something to be handled by users (or at least it is not featured prominently). Finally, the messenger’s source is heavily name mangled, the class is mostly “hidden” from regular users and it manages so many resources.

All those factors indicate that relying on it directly is a recipe for disaster, or at least having to keep track of future changes, doing a lot maintenance. I think it is much simpler and more maintainable to just keep references to the callable objects used for the events and use accept() and ignore() to enable/disable them, which are also much more prominently featured in the manual and much simpler to use than the methods available within the messenger.

Also thank you for your answer, Thaumaturge. It pointed me in the direction I needed to understand event handling a bit more in Panda3D and pick what I think is the best solution for my project.

1 Like

From what you say, you are indeed likely right to just keep track of things yourself. I suppose that accessing Messenger might be called for if you (for some reason) had no initial access or means to store the mapping–but since you do, a simple dictionary is likely the way to go!

While I hear you, let me note the alternative solution of shared methods. It amounts to much the same thing–but allows one to use the built-in FSM-system.

That said, your system might be more elegant if you do need per-state events. Such a thing could, I daresay, be done via the built-in system–but this allows the handling to be inherent.

By “shared methods” I’m assuming you are referring to the possibility of isolating functionality I want to share between different states as their own methods that can be used by the different states when they enter/exit or in-between those calls when the state is active.

Hadn’t thought of that. Indeed, that’s a possibility.

Having to subclass direct.fsm.FSM.FSM in a central class that gathers all methods for each state, this could end up leaving us with a big class that served only as a gatherer of methods. Even so, it is not necessarily a bad thing.

The way I see it, one could still keep all the methods in separate modules and only put everything together in the central FSM subclass. That would provide a solution that would be as tidy and modular as the one I’m using indeed, and thus making it a valid alternative.

In fact, the only drawback I see is that - and I’m only assuming it based on my own experience - this practice of creating new methods for each state based on special names doesn’t seem to be very idiomatic as far as Python practice goes (except for dunder methods, though, which do indeed force us to use special names in order to provide certain functionalities).

Yet, the possibility of using the API provided by Panda3D for FSMs is very tempting (as one doesn’t have to create and manage its own thing). As a matter of fact, as I type this very reply, I’m still a bit torn between my own solution and this possibility of using Panda3D’s FSM that you mentioned.

For now, though, the reason I’m still considering keeping my custom solution is because it is actually very, very simple and seems to be working fine so far. That is, despite my preference for relying on Panda3D when I can, my solution isn’t very disruptive nor require much maintenance.

Perhaps the only upside of my solution is that, since each state is represented by a single class and is at the same time an instance of that class, I can use such instance to store other data related to the state. In comparison, having a single FSM subclass gathering all the state-representing methods would force me to use that subclass to store data for all the state.

And to make me even more torn, having this central FSM subclass sharing data among states is not necessarily a bad thing, after all different states still have to share resources anyway.

Thus the difference between my solution and Panda3D’s FSM is just in how specifically the data, shared or not, will be managed.

Again, I’ll have to keep thinking about it to make a final decision later. But thanks again for pointing out yet another possibility, Thaumaturge. In summary, I’ll see if can visualize myself using Panda3D’s FSM for all my intended purposes in a satisfying way (satisfying here means that it is a simple and maintainable solution that an external dev not familiar with my code would be able to understand and use/modify). If I can visualize that, then I rather rely on the infrastructure that comes with Panda3D already, rather than spin my own thing.

I’m glad if I’ve helped! :slight_smile:

And ultimately, if a given solution works for you, then it may well be the appropriate solution for you.

Here I’ll argue for a slight advantage to using Panda’s system, in that an external code may be familiar with (or at least know of) that system, where they may not be familiar with yours, and Panda’s system comes with extant documentation and examples.

You could perhaps have a common data-store object that’s passed between your state-classes, thus allowing them to share data.

(Just to further complicate your deliberations. ;P)

1 Like