Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

Registering for actor 'callbacks'

Does anyone have any ideas on how you'd register for actor "callbacks"?  In particular, I'm trying to set up a "subscriber/publisher" architecture where I am able to have one actor register to receive data from another actor--for example a file IO actor being registered to receive data from a DAQ actor.  I'd also like to do this without creating a separate parallel loop in my actor core.  What I'd like is for a file IO actor to have a method called "write data", and I'd like to register a "write data" message with the DAQ actor.  When that DAQ actor acquires the data, it places that data on to the "write data" message and sends it back to the file IO actor by placing a 'write data' message on to the file IO actor's message queue.  The only thing in common amongst all the messages that are being used as the "callback" message is that they have a common interface, so all messages have a "data" property that is a 2D array of doubles, for example.  This would allow me to register a FileIO loop using a write data" message that takes a 2D array of doubles, or to register an analyzis actor that has an "analyze this" message that also takes a 2D array of doubles.  The DAQ actor knew nothing of the FileIO or Analysis actors, and vice-versa--all these registrations took places via an application-specific "controller" actor.

Any ideas here?  How do people implement these kinds of 0-coupling callback solutions in the actor framework?

0 Kudos
Message 1 of 18
(12,655 Views)

nvm, found this:  https://decibel.ni.com/content/message/41428

But would love to hear any more thoughts since this chain of messages took place!

0 Kudos
Message 2 of 18
(4,494 Views)

I have a TDMS data logger actor that I create at the same time as my data generating actors.  I give any data generator an enqueuer reference in there class attributes for the logger.  Then when there is data to be logged I send a message on the enqueuer for the logger.  I also have a boolean for whether the logger is enabled with a default value of false.  Then I check to see if the logger is there when I attempt to send data.  If it isn't then I don't send.

I have created several messages for the logger.  Log Double, Log Array of Doubles, Log Error, etc.  The implementation is the responsibility of the logger actor.  The data generator only needs to know that it can send certain messages.

You can add messages to the caller of either, for instance, I have data, or I logged the data.  In the Zero Coupling model the message would go to the caller of the actor; in this case that would be the controller that launched the logger and the generator.  They wouldn't send messages to one another.  I would launch these actors in the actor core.vi of the controller.

Casey Lamers


Phoenix, LLC


casey.lamers@phoenixwi.com


CLA, LabVIEW Champion


Check Out the Software Engineering Processes, Architecture, and Design track at NIWeek. 2018 I guarantee you will learn things you can use daily! I will be presenting!

0 Kudos
Message 3 of 18
(4,494 Views)

Hi Synaesthete,

As I was reading what you want to achieve with Actor Framework, I got more and more confident that you need the Advanced Message Maker. I have created that tool to make creating zero coupled messages as easy as regular messages. (regular message later called as Receive Message)

You can create Send Messages for any actor. That is basically a zero coupled message. It has no implementation of the message's Do.vi, it only defines the data type.

You should have a separate actor (just copy the one I use in the example, and modify it) that has Transmit Messages. These transmit messages are childs of the Send Messages. This actor is the Controller. Upon launching this actor the Send Messages are informed (with message injection) that this actor is the controller for now, and whenever a message is sent by Send Message, this Controller's Transmit Messages should be called and executed. (The controller also has a mechanism to handle routing messages from multiple instances of actors to multiple instances of actors, but that's not necessary for understanding - you will see more on it in the tutorial videos)

The Transmit Message has the receiving actor's Receive Message in it's Do.vi. If you want to change the destination of the messages, you can:

- create more controllers that transmit to different receivers and just launch whichever you like

- create more transmit messages for the same Send Message and decide upon initialization which will be the actual transmitter

- build the logic to decide who is the receiver in the Transmit Message's Do.vi

With this Send - Transmit - Receive concept you can keep the Send Messages and Receive Messages totally decoupled. The only part which is not reusable is the Controller actor and the Transmit Messages.

One more point: please listen to all the video tutorials, as there in no user's manual just these videos. If you have any suggestion on what should be better explained or have any questions, feel free to write. You find the links to the videos in the middle of the Advanced Message Maker's description.

0 Kudos
Message 4 of 18
(4,494 Views)

Hey Synaesthete,

I do this all the time, and I have found that drdjpowell's messaging library is awesome at what you want to do, so take a look at it.

http://lavag.org/topic/14600-self-addressed-stamped-envelopes/?hl=messaging

and download the vip package at:

http://lavag.org/files/file/220-messenging/

Ben

Ben Yeske
0 Kudos
Message 5 of 18
(4,494 Views)

Just looking through these.  Lots of great ideas here!

What came to me when sleeping on this last night was to do something as follows (maybe this is similar to the above, and I might likely use one of the above methods instead of mine):

Create a generic "callback" message that has a variant as its data.  Any actor that has a registerable method has a message called, say, "Register for Data Callback".  The data on this message is a callback message and a callback message queue.  I could even see having a generic "Register for Callback" message object to speed up making the interface.

The callback message with the variant just uses a regular Do.vi that casts the variant to the appropriate data type then calls the corresponding actor method.

I'm sure the above solutions do something quite similar to this when you get in to the details.  This approach seems like the simplest possible way to do it that doesn't require any extra VIs inside the message other than the Do.vi.  It also doesn't require any special addons or scripting for making this go any faster, it's about as fast to implement as any other message might be.

Edit:  I was looking for the absolute most general means of doing this that allows for almost no preconceived notions about what type of thing is registering for the callback other than that it expects a certain data type.  By inheriting callback messages from a generic "Callback Message" and inheriting register-for-callback messages from a generic "Register-for-Callback" message, I provide a very general means for doing this throughout my application.  The variant allows me to include any data type whatsoever in my callback, and its assumed that things registering for the callback know what type of data it will receive and can cast it appropriately.

Edit 2:  I've attached a .ZIP containing my generic Callback Message and Register for Callback message VIs.  If it's safe to assume callbacks will always work with the variant data type, you can implement specific functionality in the Do.vi for how that callback is handled.  Additionally, you can implement actor-specific registration functionality which will often be slightly different on a per-actor basis--again, just implement it in the Do.vi for the Register for Callback message.

0 Kudos
Message 6 of 18
(4,494 Views)

After the culmination of that thread you linked in the first reply [EDIT: actually, it was this thread which everyone should read before using AF] I ended up making a library for exactly this purpose. I've been using it since then with the intention of publishing it, but in my opinion it needs a scripting tool similar to the AFMM to maximize its utility, as there's still a fair amount of boilerplate involved in doing anything with AF using it. I'm in a big development crunch this week and had planned on finally cleaning it up for public release next week, but since you started asking now and appear to be making your own copy, I'll throw in a few opinions on design strategy and hopefully influence the direction of your work...or maybe convince you to wait just one more week until I can publish my (already finished, tested, and deployed in the field) library.

First, we need to address the intent of your broadcast.

You can't just broadcast any kind of message anywhere; that breaks the purpose of the Actor Framework and actor-oriented design. When designing a message, you need to decide on its intent fist. There are three kinds of messaging intent:

  1. Command (or Request): A message sent to a sub-actor to tell it to do something. Because actor-oriented design allows the recipient of this message to ignore it, cache it for later processing, or act on it at will; some people prefer to call it a Request. In AF, Commands are implemented as simple concrete classes with both data and a method that executes an action on the recipient.
  2. Status (or Event): A message sent to a parent actor to notify it that something happened. Note the past-tense language! Actors aren't allowed to control their parents, so they can't send Commands up the chain; they can only notify their parents of the occurrence of something and let the parent decide how (and even whether) to handle it. In AF, you these messages should be designed as "Zero-Coupled" pairs, with an oubound class in the child actor that holds the message data and an inbound message in the parent actor that holds the message method. The parent actor's message (concrete) inherits from the child actor's message (abstract).
  3. Data: A message sent anywhere that only carries data; it does not dictate what should be done with the data, nor does it imply any state information along with the data. (In contrast, Commands dictate action and Events notify state changes.) This kind of message can be sent to any other actor, provided you have their unique identifier, and it can be sent with any multiplicity. In AF, that means the sender needs to have a copy of the Enqueuer for every recipient of the message. If the sender wants to broadcast the message to multiple listeners, he just needs them to register their Enqueuers with him. The implementation of a Data message in AF is highly dependent on minimizing coupling between otherwise unrelated actors, so it also uses a "Zero-Coupled" message pair like the Event message.

Second, "Zero-Coupled" messages are not zero-coupled.

Actually, that point was beaten to death in the other thread. I'm still firmly in the camp that asserts a unidirectional coupling exists in any message hierarchy so the nomenclature is inaccurate and misleading. We seem to be a dwindling minority, though, so I'll set that concern aside and instead focus on this one: Because every Data msg will inherit from the same base class, you're going to have a lot of unrelated broadcasters and listeners linked to each other through LVOOP hierarchy. "Who cares?, you'll respond. "Message.lvclass already establishes this linkage anyway." Yes, it does...but it's important to acknowledge anyway.

Okay, back to points that actually matter. Sorry for the rant.

Third, you have to use a combination of Commands, Events, and Data in order to create a properly decoupled broadcasting mechanism in AF.

If you're designing your application with adherence to good actor-oriented principles, you're going to quickly realize that even though the Data message is abstracted from its recipient, you still have to share the Broadcaster's enqueuer (NQR) with the Listener...how else is the Listener going to register his own NQR with the Broadcaster for the Data message? So you create a Command message called "Register Msg" on the Broadcaster and have the Listeners call it to share their NQRs. But now you have this tangled mess of messages that couple each Broadcaster with all of his Listeners!

I found a closed-form design that (as much as allowed by AF) completely decouples Broadcasters from their Listeners and their parent actors. It also decouples Listeners from the specific Broadcaster they receive a Data message from, as long as the Data message received is of the right datatype. I'm very eager to share it, but it needs a little polish and a working example before I can put it up on the web. I'm willing to forego the scripting tool that automates message creation for now in the interest of putting it online sometime next week. (I'll get the tool done by NI Week.)

...Oh, and I held a code review of it with AQ and niACS at the CLA Summit last week: it earned their approval!

Message 7 of 18
(4,494 Views)

Glancing at "Edit" and "Edit 2", it sounds like you've figured out all the stuff I was rambling about in my other reply. I have to laugh at your decision to use variants for the data, not because I think you're wrong in doing so, but because AQ is a big proponent of type safety and designed the AF to enforce it. My own broadcasting library requires the Broadcaster and Listener to create type-safe Data messages to share among themselves, but that's just because the AF generally wants things to be that way. A general-purpose variant Data message used by all Broadcasters and Listeners will be easier to implement and require less source code, but it eschews the type safety and exposes a design to potential runtime errors when casting the variant.

0 Kudos
Message 8 of 18
(4,494 Views)

David_Staab wrote:

Glancing at "Edit" and "Edit 2", it sounds like you've figured out all the stuff I was rambling about in my other reply. I have to laugh at your decision to use variants for the data, not because I think you're wrong in doing so, but because AQ is a big proponent of type safety and designed the AF to enforce it. My own broadcasting library requires the Broadcaster and Listener to create type-safe Data messages to share among themselves, but that's just because the AF generally wants things to be that way. A general-purpose variant Data message used by all Broadcasters and Listeners will be easier to implement and require less source code, but it eschews the type safety and exposes a design to potential runtime errors when casting the variant.

I have struggled with the push-and-pull between "strongly-typed" versus "stringly-typed" interprocess messages, yet I'm now biased toward stringly-typed (so, the variant choice would fall into this category). This helps dissolve coupling due to a shared datatype, and increases opportunity for interoperability with remote and non-LabVIEW systems; yet as David mentions, comes at the cost of run-time errors and lack of type safety. (Once you start crossing process/actor/agent/application/machine boundaries, stringly-typing starts to make more sense)

0 Kudos
Message 9 of 18
(4,494 Views)

Interesting response.  Definitely some food-for-thought.  I have a few questions about some things you mentioned:

1.)  Why does the LVOOP hierarchy matter?  What is the nature of those 'linkages'?  If every message inherits from a common data message, does that cause any issues from either a performance or architectural perspective?  I understand that all the inherited messages are linked to a generic base message, but I don't see how that links them 'together'.

2.)  Is there a reason to strictly define different message types and their intents?  Does this impact 'how' they are used architecturally, such that all "Event" messages inherit from a common "Event" message or are part of a common "Event handler", or is this designation arbitrary?  In other words, if there are Command, Status, and Data messages, are they called as such because they are different in their design, or only different in their usage?  Does making this distinction make it possible to capitalize on common routines or structures, or is it only a convention?

3.)  I think I'm in the camp of not understanding why something isn't truly zero-coupled.  It seems to me that if there's a lot going on in the generic classes that "breaks" that coupling, maybe there's something wrong with the architecture--you ought not to be messing around with generic classes once a framework and concept has matured.

4.)  Why is it necessary to have a recipient contain the enqueuer for the broadcaster and register for that broadcaster itself?  I'd imagine that you'd be adhering to more of a model-view-controller type architecture where a controller (typically the top-level parent) is responsible for making those connections happen, not something that occurs between broadcasters/listeners.

0 Kudos
Message 10 of 18
(4,494 Views)