LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Questions about where to Unbudle and Bundle

PROVISO: My background is test systems.  My focus is test systems.  My objective is ease of maintenance and integration not software design.  I do not claim this is the "software engineering" approach but do pay attention to software engineering principals.  That said:

 

I keep my configuration data in *.ini format. and use the Sections to assign "scope" to my keys.  How do I divide? I ask myself several questions:

  1. Can I associate this data with a specific resource? (DAQmx Task, VISA/IVI session...)
  2. Can I logically group this data? (report options, cal info, Test control options, Station parameters, User permissions...)
  3. When can this data change? (Each App Launch, each execution, each step, each day)
  4. Can I get it from somewhere else? (time, MAX, Device query) If so I won't store it in the .ini.

Then I build my modules to encapsulate the operations that will be done with or on the datum. FGVs for Static data (datum that won't change between initializations of the module) or AEs ( for data the can change between initializations of the data). and of course initialize each module from the .ini independantly and at the correct point (App Launch, Execution start, begining of step...)

 

This gives me a great base of independant modules specific to the system I'm building that I can run to test hardware, and the modules themselves, OUTSIDE the Test Mainline, for debug and characterization while I build the "States" that conduct "test steps."- Also great for debugging!   YOU bet I build a "Debug.exe" with these "modules" as start-up.vis for finding out just what broke when the system has failures. 

 

Others may do it differently and for good reasons!  This is how I scope data and it serves test system development well for my projects.

 

 

 


"Should be" isn't "Is" -Jay
Message 11 of 15
(621 Views)

@broken Arrow wrote:

Speaking of “God clusters” (nice one Jeff), most of my programs have a large amount of configuration data that end up in one of those big honkin’ clusters. I’m speaking of the parameters the user sets upon entering the program but are never modified (i.e. static data).


You're talking about immutable data.  Here's what I do:

 

1. Create a class (Config.lvclass) that contains all the data.

2. Create a 'Create Config' method.  This takes in whatever information it needs to obtain the data.  In your case the easiest thing might be to pass in a path and have the Create Config method open the file and read the information from disk.

3. Create appropriate 'Get' methods to retrieve information.  Do not create 'Set' accessors.  (Or at least keep them private.)

 

This way I can freely pass the data around anywhere that needs it and *know* that it isn't being changed.  Getters make it easy to retrieve the data, and since each getter is an individual vi it's super easy to see who is using what data via the VI hierarchy window.

 

Notes and personal preferences:

a. Except in specific situations, my Create methods don't have a class input terminal.  The idea is that object isn't instantiated until the creator is called.  With a class input terminal the object is instantiated prior to the create method, which I find less intuitive.  Essentially the create method replaces the class constant on block diagrams that use the class.  (Except for type information, such as the Cast To More Specific Class prim, where I will use the class constant.)

b. Sometimes a class benefits from multiple creators.  In those cases I'll append something to the filename to differentiate them.  (i.e. 'Create Config(Path).vi' and 'Create Config(KeyValuePair).vi')

c. If performance is a concern, make your getters static dispatch.  Dynamic dispatching incurs a slight performance hit.  Static dispatch getters will give you the same perfomance as unbundling from a cluster.

Message 12 of 15
(604 Views)

In answer to your original question, it depends on what you do with the data after that particular code fragment.  Given the original question, the second solution, unbundling inside the case structure, will usually give you better performance.  If your cluster is passing through a subVI and you are branching to extract a piece of data, the cluster wire should go through all the case structure frames (maintaining in-placeness) and you should unbundle what you need in the particular frames you need it.  Using the In Place Element structure is optional if you are only unbundling.  If you are unbundling and bundling, the In Place Element can help ensure LabVIEW does not make an extra copy of your data (but will not prevent you from doing it inside the In Place Element itself).

 

That being said, the concept of moving a massive cluster around is just bad programming practice.  Your data should, at minimum, be clustered with data of like nature (e.g. the vertical and horizontal parameters of an oscilloscope application should be in separate clusters).  LabVIEW classes help force you into this behavior.

 

You also need to get a clear distinction between local and global data.  You can usually make more data local than you think.  I tend to make far more data global than I should, and this tends to clutter my designs with more complexity than it should, trying to ensure no race conditions.  Thinking in a data flow paradigm (LabVIEW) with objects is very different from thinking in a procedural language (C++) with objects.  If you can embrace the difference, your programming life will be easier (and much faster).

Message 13 of 15
(580 Views)

DataScope.png

 

A few comments:

  1. 1A is better than 1B because it conveys intention to only modify one piece of data within the context of the case structure. Similarly, 2A is better than 2B because it conveys intention of only modifying relevant data within the SubVI.
  2. 1B and 2B make sense sometimes, IFF (if and only if) every element of the cluster is "fair game" for being modified within the context of of the case structure or SubVI, AND the data structure contains highly cohesive elements.
  3. I'm on a current kick reconsidering typedef'd clusters in lieu of classes.
  4. 1B and 2B are in my mind an anti-pattern - it seems like you're saving time or nodes, but it violates what little encapsulation or scope you are able to achieve without using LVOOP.
  5. For my state machines or sequencers, I do not like to typedef state data (the cluster that resides on shift registers). This data is completely local to the while loop, and it has no business elsewhere (including venturing into a SubVI inside the loop). Further, this data typically has pretty low cohesion. That said, it would be chaos to achieve 2B, which is already established as a bad idea.
  6. One thing that sticks in my mind is Dak's explanation of data structures crossing code boundaries as a pitfall of interface design. (can't find quote, perhaps those weren't his words, but the concept applies. maybe he'll jump in here and explain it better) For 1B and 2B the two code boundaries are the Case Structure edge and the SubVI connector pane. I've had better luck with interfaces if I/O is "fanned out" rather than "clustered up".

Basically, just reiterating all the great advice so far.

 

PS - +1 for the good question.

Message 14 of 15
(562 Views)

@JackDunaway wrote:

One thing that sticks in my mind is Dak's explanation of data structures crossing code boundaries as a pitfall of interface design. (can't find quote, perhaps those weren't his words, but the concept applies. maybe he'll jump in here and explain it better) For 1B and 2B the two code boundaries are the Case Structure edge and the SubVI connector pane. I've had better luck with interfaces if I/O is "fanned out" rather than "clustered up".


To be more precise, my objection is using typedefs (specifically typedeffed clusters--I'm on the fence with enums) as part of the public interface of a code component due to the difficulty in ensuring changes to the typedef are propogated correctly.  A component is a set of vis that cooperate to provide some sort of functionality.  It can be one vi or many vis.  In my apps components are defined by lvlibs.  All my vis are members of a lvlib so it is clear when a component boundary is being crossed.  (Most of my vis are members of classes within the library, so each class can also be considered a component depending on the context.)  For my purposes, the case structure in 1B (or any other block diagram artifact) isn't a component boundary, so using a typedeffed cluster there is okay.  In 2B, whether or not that call crosses a component boundary depends on whether the sub vi is a member of the same library (or class) as the calling vi.

 

This doesn't invalidate anything you've said.  It's still a good guideline to only pass required data across code boundaries, regardless of whether crossing a component boundary or a case structure.  It's just that my specific reasons for not exposing typedefs may or may not apply to the examples shown.

Message 15 of 15
(544 Views)