(Cross-posted in LAVA: http://lavag.org/topic/15554-conversion-from-qmh-lv2-globals-to-af/)
I have a device with a serial interface. The device is always listening for commands. Some commands cause an immediate response, some commands cause a continuous response, and some responses show up without having given a command.
There are several types of responses (streaming and immediate). In my current code, I use several LV2 globals to hold the most recent 5 minutes of each type of data. In a particular test, I can retrieve the most recent value, all values, the next new value, etc.
WIth AF, I can't figure out a good way to replace the LV2s. In the attached zip file, the main code is in "main.vi" and "SerialDevice.lvlib Actors Sim.lvclass Actor Core.vi".
Any advice or analysis is appreciated. Note that some inheritance is there since the protocol can (and does) change often, and I need to maintain backwards compatibility.
For a circular buffer like this, you can convert the functional global into an actor. The actor's private data would be the data stored by your functional global (anything in a shift register), and it would have one public method for each operation the global supports (write data, read last, etc). You will provide a message for each of these methods, of course.
To use this buffer, launch the actor and pass its queue to whoever needs to access it.
If all you do is convert your LV2 global into an actor, you'll likely have to create ReplyMessages so that you can activley ask the global for its value. Actors are meant to be active, and they work best when they do the telling about their value changing, so you'll probably find a cleaner system (and one less likely to accidentally introduce a deadlock) if anyone who cares about the value in the global is told about it proactively by your new actor, instead of them having to ask for it.
It is true for a circular buffer that several of the messages will generate replies. But, as you have pointed out in the past, those messages don't have to be Reply Messages. A requestor can send a query and then assume the response will show up on its receive queue. That should be OK for the kinds of arbitrary retrieval scenarios where circular buffers are used.
You'll want to implement zero coupling, of course, but that's not a big deal.
Ok, something is slowly becoming more clear for me. I've been trying to write vis that look like the "traditional" left-to-right, open/do stuff/close - and have one by-ref wire running across the diagram into and out of an actor's API. (That way users don't have to know anything about the AF. It makes writing tests easier for me, too.) So far, I've been sending across the Send Queue and creating top-level send messages for each "write this" command, and reply messages for each "read this" command. It sounds instead like I should be sending a queue pair across. Or bundle the Message Queue Pair with the Caller-To-Actor Send Queue, or just bundle the Caller-To-Actor Queue and Actor-To-Caller Queue. That way the actor's API can be all in one virtual folder of one library. Anything I'm missing, here?
Your existing approach -- top-level send messages for each "write this" command, and reply messages for each "read this" command -- is sound. The problem with this approach is if you are using reply messages in other parts of your code, you can end up with a hang as everyone is waiting for a reply from someone else. But as long as reply messages are only used on one side of the communication channel, they are safe from deadlocks.
When you are calling into actors from code that is not organized entirely around actors, this is a sound approach, but it does not scale as an application becomes more complex. Inevitably, you start introducing more and more parts that want to "do their own thing", and that's when you start converting other parts of your program to actors. At that point, you'll find that you're far better off using your send VIs for the "write this" messages, but not having messsages for "read this" and instead having a queue where the LV2-global sends you messages that says, "By the way, this got written", and having one of those queues for every independent module. Essentially, the LV2-style-global stops being a central repository of data and turns into a message post office.
Ok, I see. I'm not locked in to any particular ideas, by the way - except for understanding best practices for scalability, maintainability, etc. So I'll try spinning up some data actors inside the instrument driver actor and bundle them into a user API, somehow.
... you'll find that you're far better off using your send VIs for the "write this" messages, but not having messsages for "read this" and instead having a queue where the LV2-global sends you messages that says, "By the way, this got written", and having one of those queues for every independent module ...
Which queue is that? The Receive Queue? And is the proper way to receive them - oh! by having the LV2-global call "<Send Message>". Event driven. Okay, time to read up on zero-couplling, again. And if I add a data-logger or a display, the LV2g can have their send queues, too.
Please keep in mind... I am not encouraging you to complicate your application if it is not needed. There's a balance to be struck here between apps that need to go to the next level of complexity and the business need to actually get apps written.
My job is merely to highlight options and to suggest improvements. You have to evaluate whether those are appropriate for your app. I have a bias against LV2-globals because I know where their failure points are and can design without them from the outset at this point, but if they get the job done well enough, use them. ("Well enough" is a term for you to define, with as short or long term a scope as you see fit.)
I hear you - no worries. I have a working app with working dynamic-dispatch drivers. It was my first attempt at LVOOP after having been out of the LabVIEW saddle for a few years. Now that I (kind of) understand inheritance and dynamic dispatch, I'm leisurely using some time to obsessively develop a "better" version of a specific instrument driver alongside the working project. The rest of the app is modular enough that swapping out this instrument driver will be simple.
So, the job is already being done "well enough" - but it could be better, and I can afford the time to search for "perfection" on the side. Well, I can't really afford the time, but I can't stop myself from working on this! And it's not holding up the rest of my show.
The conceptual issue I'm having at the moment is how to turn a set of specific linear, task-based tests into actor messages. Until I get there (if ever), I'm trying to create this driver-actor in such a way that it will function in a fully actor-aware project. If I do that well, I will also be able to create an instrument API for use in "linear" tests where "Open.vi" kicks off an instrument actor and some data buffers, "Read Serial Number.vi" calls a "Send Get Serial Number Command" message then returns data from one of the buffers, "Close.vi" sends the Stop message, etc.
By the way, I'm not trying to wrap a synchronous instrument with an actor (yet). The nature of this particular instrument and the possible GUIs into its guts make it seem to me that a parallel process is the right thing to use.
To (hopefully) speed up the learning curve, I'm trying an all-actor approach. (If I go any slower, I should probably just be trying this project in the land of pretty AF icons.)
I know picture and words are best, but the project is attached. I stripped it down a bit and added an attempt at zero-coupling.
The idea is that there is a Serial Device actor that can send and receive Bytes. The Bytes can come via a com port, a dll, or a software simulation. I made Transport actors for those three instances to see how the messaging can be the same.
The Serial Device actor's core continually sends a "Read Available Bytes" message to the Transport actor, which puts any Bytes into a zero-coupled message. Upon receiving that message, Serial Device puts them into a queue of Bytes. At the moment, those Bytes are only displayed on the actor core FP, which I've set to open.
I imagine there are lots of better ways to do many of the things in this code. Ithink I need to throttle the simulated Byte injection.
My zero-coupled message was way off-base. Here's an update. It still doesn't work, though. In the callee Transport actor, when "Read Available Bytes.vi" sees there are Bytes available, it calls its own Send method (Send Report Bytes From Device.vi) which I had hoped would dispatch the caller's Do.vi. That do (in Serial Device.lvlib/Report Messages From Other Actors/Report Bytes From Device Msg.lvclass/Do.vi) never gets called.
Here's a version that works (so far). It does not have the LV2 globals/data storage in place, but I needed a starting point. I'll clean up the actor spin-ups, and look for a good way to destroy the queue pair - then start adding data storage. I'll quit clogging up the attachments folder, though.
These zero-coupled messages now work. I started adding simulated device functionality, too. (Run Test.vi and click New, if you want to play.)
It will be interesting to see if instrument drivers developed in AF can easily be wrapped in an API actor so non-LVOOP internal customers can use them.