As promised, this time we are exploring using the Run method with subpanels. Attached is a simple application which has a plug–in architecture implemented with subpanels.
Start by getting the application running on your computer. Unzip the application to the directory of your choice, maintaining the directory structure. Correct the directory information in the two INI files in INIFiles subdirectory. Alternately, regenerate them using the VI in Tools subdirectory. Now run Main.vi in UsingSubPanels.lvproj. It is a simple app which switches between subtracting and adding two numbers. Stop it by clicking the Stop button.
Open the block diagram of Main.vi. The program architecture is a synchronous, event–driven task handler. This architecture was explained at the beginning of the Xylophone Project, so we will not repeat it here. Let's look at how the Run method is used with plug–ins and subpanels.
First, a series of user events are defined. These are used for communication from the plug–in VIs and the main VI. Notice that the DBLs are handled in the event structure of Main.vi. Evnt_Exit is used to stop the plug–ins, so is handled there. The local shift register in Main.vi is used to cache the plug–in object references, the user event references, and the current values of the inputs (kept up–to–date using the user events).
The Init case is the first case run. It first finds what plug–ins are available by looking in its directory for files with a certain filename pattern. This is one of a plethora of ways to find plug–ins. Some other options are registry entries (Windows only), a single file, binary file(s), or requiring the plug–ins to be in a certain directory. I like the multiple file approach since it lends itself to other people making plug–ins, and it does not limit them to putting those plug–ins in a single location. It is also cross platform. The front panel ring selector is populated with the results of the search.
Next, references to all the plug–in objects are created from the values read from the INI files. These values are cached in the shift register and the first one selected to initialize the subpanel. First, the VI reference is opened based on the plug–location. Then, the VI is run using the Run method. This prepares it for future use. The object keeps track of whether the VI is open or not and simply returns the value if it is already open.
The VI is then loaded with correct values. Notice that instead of using Set [xxx] methods on the controls, they are set using Value (signaling) properties. This is because the VI is already running. If a plug–in is loaded, it is kept running in memory until used again to speed load time.
Finally, the VI is inserted into the subpanel. The user sees a seamless interface that only changes its operand when the operand is changed. The function is changed, the reference to the new plug–in is created/fetched, the new plug–in is initialized, the old plug–in removed from the subpanel, and the new one inserted.
On exit, the running plug–in VIs are signaled to quit using the Evnt_Exit user event. All event references are then cleaned up by the main code.
This example uses user events exclusively for communication to and from the plug–in VIs. Other interprocess communication methods, such as queues, notifiers, and occurrences are also commonly used. The use will depend on the functionality and complexity of your code.
Subpanels are useful when you want to change the functionality of your code while keeping the GUI small. They lend themselves to the sort of plug–in architecture that is shown in this example. Try to create another function by copying one of the current ones. Be sure to make the object a child of the PlugInParent class. Function.vi is not in the class because it cannot be called and run dynamically if it is dynamically dispatched, but it cannot be overridden if it is not. Thus, the class and Function.vi were encapsulated into a library.
Have fun. Let me know if you have any questions.
I like the multiple file approach since it lends itself to other people making plug–ins, and it does not limit them to putting those plug–ins in a single location.
It does, however, pose the issues of forcing you to manually add the plugins to the list and of not being easily portable (the project you posted, for instance, doesn't work on other people's PCs because the paths in the INI file point to a specific folder which only exists on your PC).
A middle way is using an INI section in a single file to point to all the folders where you will have plugins and then scanning each those hierarchies. This way, you can still have a relative or absolute path to scan in, but people can simply add plugins by adding the files.
Subpanels in general are great (the options dialog in LabVIEW is a great example of where SPs are very useful in a plugin architecture), and I haven't checked recently, but at least back in the LabVIEW 7.0 days they had all sorts of annoying issues (like not being able to set the FP.Origin property on a VI which is inside a SP. Why? Because). This does add some overhead to working with them.
I have modified the ini file to match my paths, start main. The Initial setup is correct with the Minus configuration and to confirm this entered a couple of numbers and the result is correct. Switch to plus and the symbol changes to + with the result now matching addition. But when I switch back to minus, the panel doesn't change. I haven't delved into the code too deeply, but LaunchFunctionVI is getting a error 1000.
Must have missed something in the setup!!
You didn't miss something, I did. I thought I had tested that use case, but I obviously had not. In the Init and ChangeFunction cases, the changes to the objects are not propagated to the shift register cache. So, the objects in the shift register are never initialized and keep trying to run VIs which are already running. Please accept my apologies (and the fixed code, below - Main.vi is all that changed).
Before I get started, first let me say thanks for taking the time to provide these development examples to the community, we really do appreciate it!
Now, I hope you have your flame suit on 🙂
This article had only 2 responses, and was from the regulars. I think I know why.
Your architecture is just too weird!
I consider myself reasonably smart and pretty proficient in LabVIEW (got just under 90% for my CLD; not trying to boast, just stating where I fit in). I spent quite a while going through your previous series of posts on the xylophone project, and in the end although I got the concepts eventually, the architecture was just so different from one I normally use that I was not able to extract anything meaningful. This one is similar.
In nearly 6 years of LV development, I have only just scratched the surface of User Events, as they are also a little off the wall for me. These, combined with inherited classes, nested event handlers, and a strange main loop architecture will probably confuse around 95% (if not more) of the people on the forum.
I am sure your design decisions all have very sensible reasons behind them, and its probably an incredibly effecient architecture, but its just too much for us mere mortals without PhDs!
My mantra when coding is to design my code so that there are "obviously no bugs", as opposed to "no obvious bugs". If I have to trade off 5% of maximum performance so that my code is easily understood by a human, so be it.
You are trying to introduce us to a development concept, ie the sub-panel. I think it would have been much better worked as a more familiar producer consumer state machine, with no classes or User Events. Two separate loops, one for the event handler, one for the main state machine. This is what 50% of the developers are used to, and very comfortable with. Of the remaining 50%, 45% probably never even use this "normal" architecture, and the remaining 5% are the gods of LV, who the tutorial is probably not aimed at anyway.
I hope you take this comment in the spirit that it was meant. I would love to see more of your tutorials, introducing advanced concepts (do one on User Events, please!), but in slightly more bite sized chunks, slowly building up to the final thing.
Maybe I am totally wrong here, and I am grossly overestimating my LV skill. I would be really interested to hear from other devs out there to see if its just me.
<MadScientist>It is working PERFECTLY!</MadScientist>
Frivolity aside, you have some valid points. I fully agree with your base assumption - an architecture should be as simple as possible. But I also require a few more things. The architecture must be:
All of these take precedence over performance, provided performance is good enough. A corollary to the base assumption is - an architecture should be as simple as possible, but no simpler. I have spent a lot of time fixing oversimplified code which tried to save time by eliminating one or more of the conditions above (the middle two are particularly problematic). Simplifying works short term. It does not produce a maintainable product which can be easily feature enhanced for years. Meeting all these requirements mandates a certain level of complexity.
The purpose of my series of posts was to appeal to the advanced user and generate discussion on advanced architectures and methods. As such, I have tried to avoid simpler things which are available elsewhere. For example, the simple producer/consumer architecture has numerous examples available (including a template which ships with LabVIEW). However, the three loop UI/Acquire/Analyze architecture, used by the Xylophone Project, has no examples I am aware of. The building blocks are familiar (task handlers, event handlers, state machines, producer/consumer, queue-based communication, event-based communication, LabVIEW objects), but stitching them all together without race conditions and other bugs takes some work and some of the problems are not obvious. It results in an architecture which meets all of my needs above.
My examples thus far have mostly been GUI-based, which means they use the Event Structure. The Event Structure is almost an alien being on a LabVIEW diagram because it does not fit well with the dataflow paradigm. Interfacing between dataflow and events is a major issue. Getting it right requires complexity, whether it be state machines or polled loops or task handlers. If you have not dealt with Event Structures much, I would expect some confusion (I will do an Event Structure series).
In summary, I will attempt to simplify future examples, but not to the point that they become readily available elsewhere. I would like to add at least some value, so will try to place my examples into a real-world context. If this means a simple example followed by a real-world example, I wil try to do that. But you will have to keep me honest ;).
Thanks for your feedback!
If you have not dealt with Event Structures much, I would expect some confusion (I will do an Event Structure series)
Thanks for your feedback!
This is sarcasm right? 😉
There is a whole world of difference between the vanilla Event Structure that most people use, and implementing (registering etc) User Events.
My point was, in trying to introduce a new concept, most people will get lost for all the architectural detail surrounding it. An analogy to this is difficult task Mr I. Newton had trying to prove his Calculus to everyone else without using the concepts he had just developed.
I am sure someone of your considerable intellect can trim the architecture sufficiently so that the focus is on the new feature you are trying get across. Remember, this is not production grade code you are trying to present here.
ps: if the architecture you presented is as simple as it needs to be then WOW, I have a long way of my journey to LV mastery still to go!
Thanks for your reply 🙂
ok I am really curious. Time for an experiment 🙂
Please can some other devs with a reasonable amount of wiring under their belt take a look at the code posted at the top of this thread.
If you think its weird please kudo up this post.
Otherwise kudo up the one after this one.
kudo up this post if you think the code posted is not weird at all, and its just me being an idiot.
EDIT: Damien, this is in no way meant as an attack on you! I am just curious...