Past NIWeek Sessions

cancel
Showing results for 
Search instead for 
Did you mean: 

LabVIEW Classes: The State of the Art

Join LabVIEW developers who know the LabVIEW basics and are interested in learning more. Discuss new development techniques and new LabVIEW features that extend the LabVIEW object-oriented programming model.

Included in the .zip: The full PowerPoint presentation, VIs used for presenting LV class property nodes, VIs used for presenting the Actor Framework, and a tutorial document on how to use the Actor Framework.

Comments
AristosQueue (NI)
NI Employee (retired)
on

The .zip file contains both the PowerPoint presentation and the VIs used during the presentation. This includes the Actor Framework that I spent so much time talking about. I really do want user feedback on this framework. I believe this framework can become a powerful tool for LV developers. I imagine a basic framework used for most multi-actor apps and many variations for specific apps, just as we have a basic state machine with many variations today. The framework as it stands at the moment works and -- from its limited exposure to real-world problems by CLA Allen Smith -- appears to work well. But my PowerPoint slides include a list of several extensions and improvements that we could make to the overall framework to significantly improve its power. If you have additional recommendations beyond those listed, please comment here.

The Actor Framework VIs are saved in LV 2009. The BioCensus VIs used for demonstrating the Property Node support are saved in LV 2010.

KuGr
Member
Member
on

AQ, Nice work.  My biggest struggle is with well thought out design.  Frameworks like this and the examples help quite a bit.  I have heavily relied on putting a DVR as the class's private member data.  While I have not had the deadlock conditions (because I am careful on using the IPE), I have had a few race conditions.  My biggest frustration is the management of the DVR itself - creation and destruction since this is not part of the dataflow paradigm.  Further, my colleagues (non-programmer programmers) have never thought about object oriented design and, in my opinion, rely on globals way too much - reminds me of early BASIC.  It is quite a quantum leap for me to get them into this century.  So, I need all the help I can get.

I am going to try this out on a small part of a system I have been working on.  I might need to incorporate a publish/subscribe framework around this.  There are multple listeners (like loggers, plot UIs, etc.) that need to either poll or hear of an update to a value. I'll keep this thread posted as I have updates or questions.

Kurt

Dmitry
Active Participant Active Participant
Active Participant
on

AQ, thanks for posting the Actor Framework code. I did enjoy your presentation at NI Week and was quite interested to look into the details. It seems that using objects for messaging between components is getting attention of the LabVIEW Community – check out a paper posted by Paul_at_Lowell on this and related matters on LAVA less than a month ago.

I’ve been working on component-based frameworks in LabVIEW and using them for mid size applications (1000 to 5000+ VIs) since 2005. However, I could not benefit from using LVOOP as I got stuck in LV 7.1.1 for a while and only recently migrated the latest framework to LV2009/LV2010. My frameworks are more along the lines of JKI State Machine – using Event Loops and passing clusters between Components – with a sequence of text Commands (in a String) as the payload.

Several notes/comments on the presented Actor Framework:

  1. The Actor Framework approach seems like an elegant way for executing arbitrary caller-defined and caller-controlled payloads on Actor’s [private] data.  It allows altering Actor behavior without touching Actor code. This could be an important feature for feasibility studies or breadboard support but I would be VERY careful using such a design to control a piece of machinery or a mission-critical subsystem. I think it is important for an Actor to have full control over the code it executes inside its event loop.
  2. In my experience Components have to support/expose functionality in several independent areas (like Loading/Storing their persistent data (part of Component lifecycle), acting on a piece of HW (their main purpose), etc.).  Actor Framework implicitly couples messages designed for different purpose (i.e. part of different APIs) by feeding a concrete Actor Data Cluster in/out of each message Do method. This defies encapsulation and increases risk of hard-to-detect [un]intended side-effects.
  3. Using OOP in general (and LVOOP in particular) typically leads to a more fragmented code (smaller size VIs and Typedefs). I’ve experienced that while developing class library hierarchies in Turbo Pascal and C++ before switching to LabVIEW in 1998. However, the number of classes and methods in the project resulting from a ‘one class per message’ approach is just too much for me. I find it harder to read/understand and maintain the code at such fragmentation levels. The largest component-based application I worked on had 20 component instances with a total of 30+ different APIs, ranging from 10 to 30+ methods per API, resulting in ~600 classes with a minimum of 1200 methods to define the supported set of Messages.  This can become an obstacle for a main street LabVIEW developer in using classes for messaging between actors/components.
  4. I think it is important to support executing a sequence of Messages as an atomic unit (and/or pausing/canceling its execution as a whole). Something along the lines of a Macro in the Command Pattern or a Script in case of text-based command. This will reduce overhead of queuing N separate messages as well as prevent other callers from injecting their payloads inside the original Message Sequence.

I think that well defined and well controlled APIs are an important part of a component-centric framework. A possible extension to Actor Framework may be done along the following lines:

  1. An API is a class derived from the Root [abstract] API class.
  2. Each API defines a set of supported Messages.
  3. Each API has a Do method – a case structure responsible for executing its API Messages
  4. Child APIs can override their parent implementations (Ex: a natural way of implementing [or repackaging an existing] Instrument  Driver).
  5. Instead of calling a Message.Do inside its Event Loop an Actor needs to branch based on a unique API ID for a each Message, calling the appropriate API.Do method.
  6. API class definition contains all data required to execute its Messages. Actor’s Private Data cluster keeps object data for each API instance and passes a specific API object (instead of the entire Actor Data Cluster)to a respective API.Do call.
  7. API class can either contain individual Message.Do methods or have their code in-lined in the API.Do method (reduces overall number of methods).
  8. Upon receiving an API Message absent from its Do case, API.Do calls a Parent API.Do method with the same Message. Root API.Do method logs an ‘Unsupported API Message’ error.
  9. Most API classes need to be packaged separately from Actor classes as they may be shared across multiple Actor classes. JKI VIPM seems like a natural choice for tracking API/API and API/Actor dependencies and support for version control.

This should resolve the issue with executing caller-defined/controlled message payloads as well as the implicit ‘data coupling’ across Actor’s messages. It also allows overriding API behavior (Message implementation) and/or expanding APIs with new Messages by using LVOOP inheritance.

After moving Message.Do methods to the owning  API implementation (or in-lining in the API.Do method), the only reason for keeping individual Message classes is passing custom Message data from caller to the API.Do method. Which makes me wonder whether ‘one class per message’ approach is really worth the code clutter …

I am currently working on an incarnation of a Component Framework heavily based on LVOOP. The above ideas are part of that project.

Dmitry


    AristosQueue (NI)
    NI Employee (retired)
    on

    Dmitry: Lots of interesting feedback. I look forward to hearing if you make any progress on these lines. One point you made caught my immediate attention... how to put multiple messages into the queue in such a way that you guarantee back-to-back handling. One option is simply that you never share the Send queue with anyone else. But if you absolutely need such sharing, that might not be possible.

    Here's the best idea I've come up with this morning. Thoughts?

    http://decibel.ni.com/content/docs/DOC-13123

    Dmitry
    Active Participant Active Participant
    Active Participant
    on

    AQ, sure, the ‘Multiple Message’ message takes proper care of queuing/executing the payload as an atomic unit. There is a caveat, though, that may or may not be important in your case (I don’t have a list of requirements you’re working off):

    • One can cancel executing messages accumulated in the Actor message queue by enquiring a Cancel Message to the head of Actor’s message queue ;
    • It will not be possible canceling ‘Multiple Message’ payload in the same way;  One may need to pass a Cancel Event handle of some sort and check for Cancel Event on every loop iteration. But I wouldn’t go this way since a caller has no idea whether the Actor is executing a ‘Multiple Message’ or not when issuing a Cancel to that Actor  ;

    This brings a quite important generic question – would Actor Framework in its final form provide support for an application developer to Pause/Resume and/or Cancel Actor activities? What about doing the same for a subsystem (Subsystem Actor aggregates & coordinates lower-level Actors)?

    Example: I am implementing an Actor for controlling a Thermocycler. A Thermocycler Run may take anywhere from several minutes to 3-4 and more hours. From time to time a user may need to Pause the Run, perform some actions on the sample plate and Resume the Run. Or Cancel it (without shutting down the entire application) in case there is something wrong with the Sample.

    How would you implement executing a Thermocycler Profile (a Profile is a sequence of repeating steps, each step being a ‘set and hold for X seconds’ or ‘ramp from T1 to T2 in Y seconds’)? Is there a way to execute a non-blocking Wait on an Actor (responds to monitoring commands but ignores control commands returning error to the caller)?

    Dmitry

    Daklu
    Active Participant
    Active Participant
    on

    This framework flips things around from what I expected.  Instead of dealing directly with actor objects the class user manages the object's send queue.  Instead of having a MessageQueue:Enqueue method that takes a message as an input, this framework has Message:Send methods that take a queue as an input.  Very interesting...

    Despite the coolness of the framework, Dmitry identified a fundamental issue that limits its usefulness to me.  The actor is (as near as I can tell) unable to run a continuous process in its thread.  Usually when I spawn parallel threads its because I have a process I want to execute independently of the main thread.  Currently this framework only executes when it receives a message, and when it finishes executing that message it stops and waits for the next message.

    [A little later...]

    I might be able to get this behavior if I create an Actor Core override that uses a two-loop producer consumer pattern.  Each message's Do method would simply dispatch a message to the consumer loop.  Of course, without any outputs from the Message:Do method the actor object would need a reference to the consumer loop's message queue.  I'll have to play around with this a bit.

    AristosQueue (NI)
    NI Employee (retired)
    on

    You can have a continuous process. Look at TemperatureSensor.lvclass:ActorCore.vi. It has a continuous process that is operating independent of the main message handling loop. Same with the UI classes, which are live and working independent of the message interface.

    Daklu
    Active Participant
    Active Participant
    on

    Sure 'nuff, I was wrong.  After my previous post I saw that you mentioned in the introduction that state machine loops are possible.  It looks like TempSensor:ActorCore provides the best example for how to proceed down that path.

    Here's another question for you.  All your objects are custom built to be actors.  Suppose I have an existing vanilla class with a fixed inheritance tree I can't change.  Are there any gotchas I need to worry about if I derive an actor object that wraps the vanilla class?  It looks like it should be pretty straightforward, but it never hurts to ask...

    AristosQueue (NI)
    NI Employee (retired)
    on

    > Are there any gotchas I need to worry about

    None known. That doesn't mean there aren't any. But we have done that kind of wrapping once, and it worked well. Post here if you find any, please.

    Daklu
    Active Participant
    Active Participant
    on

    - Why did you decide to use queue refnums as handles to the actors instead of an actor object?

    - How would you use this framework to implement an observable actor that can communicate with multiple callers?  Wrap it in a class that manages multiple send/receive queues?

    - Why use MyMessage:Send methods instead of just using the enqueue prim on the actor queue?  I ask because I have a messaging framework I use and I'm curious what will bite me if I change that part of your framework.

    BTW, in the introduction you mention the 'send' queue is used to send messages *to* the actor.  You reversed that in the Actor:ActorCore method.  The terminology in ActorCore makes sense from the perspective of the actor, but the switch confused me for a bit.

    I also changed the labels in Exchange Queues.vi to make their purposes more clear to me.  Instead of Send Queue and Receive Queue, I named them 'Queue to Send' and 'Queue Received.'

    EDIT - The StartRunning class has a vi named, "Read Actor.lvclass.vi."  That seemed to confuse Labview's source control feature... I couldn't add it to our repository.  Taking out the .lvclass fixed it.

    AristosQueue (NI)
    NI Employee (retired)
    on

    - Why did you decide to use queue refnums as handles to the actors instead of an actor object?

    We were not sure at the outset what kind of limits we wanted on the queue refnum, so we use the queue refnum directly. One of the slides talks about "Future work" and mentions wrapping the queue in a "Send Queue.lvclass" and a "Receive Queue.lvclass" -- the former would have a method for Send Message and the latter would have a method for Receive Message. Neither one would allow for Release Queue. That would let us share the queue refnums around without worrying about someone calling operations that shouldn't be used in that part of the framework.

    It would not be part of the Actor itself. The communications queues are not the same as the actor. And a major part of this framework is making sure that the caller no longer has any access to the actor data directly once Launch Actor is called.

    -  How would you use this framework to implement an observable actor that  can communicate with multiple callers?  Wrap it in a class that manages  multiple send/receive queues?

    Our safety net for spawning and shutdown requires that anyone who will initially message each other knows about each other. So the question becomes: who spawned those multiple callers? If you have a single supercaller that spawned all those callers, then each one can message the supercaller and ask to be put on some sort of notification list. If you do not have such a single caller and you are spinning these callers up as truly independent hierarchies, the current Actor Framework does not support that use case. That's part of the future development when we start talking about network queues and remote invocations.

    - Why use MyMessage:Send methods  instead of just using the enqueue prim on the actor queue?  I ask  because I have a messaging framework I use and I'm curious what will  bite me if I change that part of your framework.

    It makes it explicit that messages aren't generally initialized and passed around except through the message queues. It avoids cluttering the block diagrams with a bunch of "Set This" and "Set That" calls ending in an Enqueue operation. Since all of that work is generally done as a block, it is cleaner to package it all together. And it makes explicit all the parameters that have to be set in order to have a valid message.

    - BTW,  in the introduction you mention the 'send' queue is used to send  messages *to* the actor.  You reversed that in the Actor:ActorCore  method.  The terminology in ActorCore makes sense from the perspective  of the actor, but the switch confused me for a bit.

    Yes. Every queue is a send queue and a receive queue depending upon which end is being used.  Just as every caller is potentially an Actor for the next layer up. Isn't recursion fun? 🙂

    Daklu
    Active Participant
    Active Participant
    on

    Thanks for the responses Stephen.  I'm using the framework in a system I started a couple weeks ago and so far I'm liking it.

    In the Benefits section you said, "A properly implemented Actor can be used without launching the Actor Core."  I'm building an Actor for an I2C monitor.  Once I tell the Actor to start monitoring the bus it has to continuously read from the device and post the I2C data to a separate data queue supplied by the caller, similar to what you've done in TemperatureSensor:ActorCore.  Since the read loop has to be in the ActorCore override, it seems to make sense to put all the device's state machine code there too and use the Message:Do overrides to redispatch messages to this loop.  However, that means I have to launch ActorCore for the Actor to work and I'm faced with the difficulties of debugging a dynamically launched vi.

    Is this a valid exception to the rule above or have I improperly implemented this Actor?

    "Isn't recursion fun? :-)"

    Well yeah, but trying to figure out if *this* send queue input terminal is for sending messages to the actor or for the actor to send messages to the caller isn't so much fun.  🙂  You're using the labels to describe what the queue *does*, which changes depending on perspective.  Maybe more specifically describing what the queue *is* would help--something like "Actor Input Queue" and "Caller Input Queue?"  I know I've spent a lot of brain cycles tracing through the queues making sure I've got them wired up correctly.

    AristosQueue (NI)
    NI Employee (retired)
    on

    > Is this a valid exception to the rule above or have I improperly implemented this Actor?

    No. What you're trying to do requires the dynamic launch. When we (Allen and I) wrote the statement "A properly implemented Actor can be used without launching the Actor Core." we meant this: The actor object is a data object. I should be able to call its methods directly to assign it values, to tell it to do things. MyActor.lvclass may override the Actor Core.vi and in that VI it may bundle items (such as event refnums or other data) into the MyActor data. A well-written Actor will either use a public Init method to set these values in Actor Core.vi (so that non-dynamic users could call that Init to set up the object) or all of its methods will be written to do their work without returning an error if those refnums are Not A Refnum. Not all of the classes in the example follow this rule -- we were particularly sloppy with the UI actors.

    To say "properly implemented" is, in retrospect, too strong. We should probably say, "maximally useful" or something like that. If you only ever intend to use an actor through dynamic communication (which is, after all, the primary motivation for all of this), you don't have to follow this design guideline. But following the design guideline does make testing the actor's correctness simpler since you can call functions directly and monitor for proper responses.

    AristosQueue (NI)
    NI Employee (retired)
    on

    Daklu wrote:

    You're using the labels to describe what the queue *does*, which changes depending on perspective. 
                       

    But it doesn't change based on perspective on the diagram the label, and it doesn't change when calling into most subVIs. Even the "Swap Queues" subVI is always correctly labeled -- what goes in is the Receive Queue. What comes out is the Send Queue. This is true no matter who is calling the swap subVI.

    Is there a particular subVI in the demo that I've got them flopped?

    Daklu
    Active Participant
    Active Participant
    on

    AristosQueue wrote:


    Is there a particular subVI in the demo that I've got them flopped?


    To be honest I'm not sure anymore... I went through and changed all the labels to something that made more sense to me.  It could just be that I after reading the Introduction I was expecting the Send queue to be for messages to the actor and the Receive queue to be for messages to the caller.  The only place (I could find) where it's documented that the labelling is reversed inside the actor code is the comment in ActorCore.

    (Introduction page 1)

    "The input queue, called the Receive Queue, is used to pass messages from the actor to the caller.  The generated queue reference, called the Send Queue, is the one that will be used to pass messages from the caller to the actor."

    The labelling is minor stuff and I hope my comments don't detract from the framework as a whole, which I do like.  My remarks are intended as "hey, I found this part kind of confusing" feedback.  Nothing more.

    [Edit]

    Incidentally, the "Actor Libraries" section in the Introduction is a perfect example of why separating libraries from namespaces is helpful.  The Reply Message class should be part of the Actor Framework namespace, but for the reasons you explained isn't part of the library itself.

    Fire
    Member
    Member
    on

    Hello

    recently I published the HGF class library: http://wiki.gsi.de/cgi-bin/view/NIUser/HGFBaseClassLibrary.

    You find some talks and publications on this page as well as a snapshot fo the sources concerning the mentioned design patterns published under GPL. A prototype for a Mobile Agent System in available, too.

    The focus of this class library is: How to deal with passive LVOOP objects as entities and how to activate them following the LabVIEW dataflow paradigm strictly in scalable distributed systems.

    At http://wiki.gsi.de/cgi-bin/view/NIUser/LVMobileAgentSystem you find the diploma thesis (German language) concerning the Mobile Agent System and an explanation of the design pattern used.

    Regards Holger

    Contributors