01-28-2011 10:22 AM
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:
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.
01-28-2011 12:26 PM
@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.
01-31-2011 07:49 AM
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).
01-31-2011 10:56 AM - edited 01-31-2011 10:57 AM
A few comments:
Basically, just reiterating all the great advice so far.
PS - +1 for the good question.
01-31-2011 01:10 PM - edited 01-31-2011 01:14 PM
@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.