10-22-2021 08:17 AM
@Dmitry wrote:
@WavePacket wrote:
@Dhakkan wrote:
@BertMcMahan, Pleased to make your acquaintance virtually.
@BertMcMahan wrote:
I get that the caller actor shouldn't know about the multimeter, but I find this "breaks down" a bit when configuration comes into play. For example, if I was to configure the main program, at some point I have to say "OK you are using a multimeter on COM4" or whatever. I usually have my config editor in my main actor, so it winds up knowing about all kinds of parameters.
...
...
@Dmitry wrote:...
A Dependency Injection Container (AKA Assembler class) is, simply put, an object that knows how to instantiate and configure all other application objects and all their dependencies. ...
...
I'll have to admit that I'm also confused about this exchange. I'm not initially seeing the difference between the main actor knowing the config details (which @BertMcMahan seemed concerned about) and the assembler class (which @Dmitry seems to be saying solves @BertMcMahan's concern.)
Is the assembler class somehow reusable? It knows all the application details, so it seems not reusable...
Assembler class is application-specific (by definition)...
Option 2: Assembler class is tasked with loading all configuration data...
Such long quotes now, but this conversation has been a marathon (in a good way).
Just curious -- in your example, why is Assembler class a "LabVIEW class?" Is there a reason that it couldn't be a state of a state machine, or an override of pre launch init of a root actor (just to name two examples for no particular reason)? This "Assembler code" isn't meant to be reusable, so I'd flop forward mostly from a position of ignorance that it doesn't necessarily need to be a class.
10-22-2021 11:00 AM
@WavePacket wrote:
Such long quotes now, but this conversation has been a marathon (in a good way).
Just curious -- in your example, why is Assembler class a "LabVIEW class?" Is there a reason that it couldn't be a state of a state machine, or an override of pre launch init of a root actor (just to name two examples for no particular reason)? This "Assembler code" isn't meant to be reusable, so I'd flop forward mostly from a position of ignorance that it doesn't necessarily need to be a class.
It doesn't have to be a class per se, but it should be separate from the rest of the stuff. Even though the Assembler code isn't reusable, the stuff it assembles IS reusable. Thus your "Main actor" would become reusable (if that's a benefit to you).
10-22-2021 11:09 AM
@cbutcher wrote:
I have been in the past (but have yet to really follow through to completion, so take with a mountain of salt) tempted to do the following (I think that LabVIEW's Options panel is perhaps similar either in principle or both principle and execution):
- Create a "Configuration Panel", which contains a ListBox and a Subpanel (or similar)
- Have each configurable thing send a message (possibly AF Message if the Config Panel is an Actor) with its name, and a VI reference
- Have the Panel (in the Do.vi called subVI, or equivalent) add the name to the listbox, and add the reference to a Map
- If you click on the name in the Listbox, look up the name in the Map and get the VI Reference, then insert in the subpanel (optionally, make this a class or something rather than a VI reference, and provide "Insert into Subpanel" and "Remove from Subpanel", perhaps also "Resize" or similar to allow more complicated behaviour defined by the configurable thing
- Use the contents of the VI (provided by the configurable thing) to control the settings of said thing. This is a bit more tricky, since you need a way to update the running "thing" according to new settings. Having the VI know an Actor Enqueuer (or similar message system, Queue, Channel, etc) is one way, send the new settings to the Enqueuer and have the thing (now an Actor) update itself.
That's basically what I've been thinking, with the exception of needing additional "selector" intermediate classes. For the classic "DAQ card or multimeter for measuring voltage" example- I'd imagine you'd want "Voltage input" as your column entry, then once you click that you can pick "DAQ card" or "Multimeter", then once you pick THAT you get the child-provided settings menu.
That's why I was picturing the whole thing as being somewhat recursive; after you click an option to the left, a sub-selector could come up. I don't think I'd want a whole dedicated "DAQ or Multimeter Selector" class to populate a submenu, as "select between these few options" is such a common need that I'd want to make that code reusable.
The CEF has some such functionality:
where you can right click on individual headings and add one or more sub-items. I bet you could make a reusable "Group" that just has a Ring control that gets populated at run-time with the available choices, but again building all of that up sounds like a lot of work, and I haven't ever had the time nor budget to make it happen.
10-23-2021 02:43 PM
@WavePacket wrote:
@Dmitry wrote:
Option 2: Assembler class is tasked with loading all configuration data...
Just curious -- in your example, why is Assembler class a "LabVIEW class?" Is there a reason that it couldn't be a state of a state machine, or an override of pre launch init of a root actor (just to name two examples for no particular reason)?
No, it does not have to be a 'class', but I find that packaging its functionality as a LabVIEW Class makes a lot of sense :
10-23-2021 05:14 PM
@BertMcMahan wrote:
Ah, so you're saying you have one Assembler class that creates the Main Actor, populating its sub-classes with XY Stage and Scanner. Thus, the Assembler class doesn't do a whole lot of things, but it decouples all of the other things. Basically it just runs once, right?
Correct. It does not do much and runs only once. But when it stops - all objects are properly destroyed, resources released and updates to configuration saved to persistent data storage (INI files/Database /you name it):
Above is part of the Application Main VI - which also serves as a splash screen for the app. Main Actor is started inside the Assembler.Run method. After Main Actor stops - application proceeds to the TearDown step and then closes/destroys all application-wide subsystems in Done (message broker, global log, etc.)
@BertMcMahan wrote:
For lots of my programs, "have the user edit a JSON file" won't work- they'd need a GUI, which itself would be called within the Main Actor. The GUI could then update all of the config settings and pass a Config object to the Main actor, who can implement methods to adjust config on the fly rather than having to be fully restarted. This is a method I've used in the past, but I don't love it since the GUI tends to be unweildy, having to know ALL settings for ALL of my different options.
I've used the Assembler class for implementing three different configuration-handling scenarios:
10-24-2021 07:14 AM
Dmitry, why do you not use something like Variants instead of static-typed clusters to make reusable config-storage components?
10-24-2021 10:50 PM
@drjdpowell wrote:
Dmitry, why do you not use something like Variants instead of static-typed clusters to make reusable config-storage components?
Good question 🙂 As a SW Developer, I grew up using programming languages with strong static (i.e .compile-time) type checking. It takes less effort/time to deal with compile-time errors compared to runtime errors. Improperly handling Variant data results in runtime errors. But its even worst - Variants do not force programmers to properly perform run-time error handling.
I think that using language features allowing to circumvent strong type checking leads to un-clean code.
I also know (from personal experience) that cutting corners results in programs that are harder to extend, scale and maintain.
Having said that - I do use Variants on occasion - but only when they are completely encapsulated by a class or a library - without exposing class/library users to Variant data. Having a Variant on a public VI connector pane is a strong code/design smell for me ...
10-25-2021 02:54 AM - edited 10-25-2021 02:57 AM
@Dmitry wrote:
@drjdpowell wrote:
Dmitry, why do you not use something like Variants instead of static-typed clusters to make reusable config-storage components?
Good question 🙂 As a SW Developer, I grew up using programming languages with strong static (i.e .compile-time) type checking. It takes less effort/time to deal with compile-time errors compared to runtime errors. Improperly handling Variant data results in runtime errors. But its even worst - Variants do not force programmers to properly perform run-time error handling.
Those arguments may have merit generally, but it's hard to see their application to configuration storage, where the job of the storage component is to save and recover whatever data structure was provided to it, without using or applying any checks to it. What compile-time error could actually happen? And your component has to convert your strong-typed LabVIEW types to some weak-typed storage format at some point in order to save it, so you are exposed to run time errors there.
Personally, I have all components return config info already converted to JSON, so the configuration storage code doesn't even have to do any conversion operation on the data. All conversion is done inside the component itself. Here one uses Strong typing, and this is the best place to actually deal with run-time errors.
10-25-2021 03:26 AM
@Dmitry wrote:
I also know (from personal experience) that cutting corners results in programs that are harder to extend, scale and maintain.
To me, your description of the three cases of configuration handling sound hard to extend at least. You mention effort in multiple places, including the effort to prevent coupling. If this were an architecture for more junior programmers than yourself, I would worry that this would lead to corners being cut, including having some lesser bits of program info never being put in configuration in the first place, and in a failure to actually prevent that coupling. Configuration should be (and can be) extremely easy.
10-25-2021 08:04 AM
@drjdpowell wrote:
@Dmitry wrote:
@drjdpowell wrote:
Dmitry, why do you not use something like Variants instead of static-typed clusters to make reusable config-storage components?
Good question 🙂 As a SW Developer, I grew up using programming languages with strong static (i.e .compile-time) type checking. It takes less effort/time to deal with compile-time errors compared to runtime errors. Improperly handling Variant data results in runtime errors. But its even worst - Variants do not force programmers to properly perform run-time error handling.
Those arguments may have merit generally, but it's hard to see their application to configuration storage, where the job of the storage component is to save and recover whatever data structure was provided to it, without using or applying any checks to it. What compile-time error could actually happen? And your component has to convert your strong-typed LabVIEW types to some weak-typed storage format at some point in order to save it, so you are exposed to run time errors there.
Personally, I have all components return config info already converted to JSON, so the configuration storage code doesn't even have to do any conversion operation on the data. All conversion is done inside the component itself. Here one uses Strong typing, and this is the best place to actually deal with run-time errors.
I know that this is getting a little off topic, but despite that if you guys would like to continue discussing I'm sure that I'll learn something from watching you two continue this discussion.
(One of the items that I've developed in the past is how to load a SQL/SQLite database recordset into LabVIEW. At the moment, my idea was LabVIEW variant attributes. To make the code separate from the structure of the database, so far I've been using database variants -- with their associated runtime errors.)