Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

Integrating AF & LVOOP with hardware

Solved!
Go to solution

@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.


------------------------------------------------------------------------------------

Please join the conversation to keep LabVIEW relevant for future engineers. Price hikes plus SaaS model has many current engineers seriously concerned...

Read the Conversation Here, LabVIEW-subscription-model-for-2022
0 Kudos
Message 61 of 88
(3,516 Views)

 


@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).

0 Kudos
Message 62 of 88
(3,509 Views)

@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:

 

BertMcMahan_0-1634919002753.png

 

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.

 

0 Kudos
Message 63 of 88
(3,506 Views)

 


@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 :

 

  1. It creates & configures all application actors/objects and needs a place to keep data structures used for application tear-down - which becomes Assembler class private data
  2. There are only 5 public methods - executed one after another (see diagram below). Init and BringUp methods typically create lots of RefNums - which are guaranteed to stay in memory for Assembler object lifespan. The latter is the same as the application lifespan - so no more refnums getting invalid because a top level VI that created them stopped executing
  3. I have a reuse code library with an Assembler class implementing common functionality that may be reused by many applications. So creating a Child Assembler Class for a specific application is a natural way to go

Dmitry_0-1635017415743.png

 

 

Message 64 of 88
(3,484 Views)

 


@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):

Dmitry_0-1635019620024.png

 

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:

 

  1. Application configuration does not need to be viewed/updated while application is running (your 'user edit a JSON file' case). Not a common one and the easiest to implement: Assembler object loads configuration, and configures/injects all objects. Nothing to save on application shutdown - so your main actor can choose to call destructors (Done methods) for all its subsystems since Done is, normally, a dynamic dispatch method.
  2. Application configuration needs to be viewed/updated while application is running, but it is OK to save all updates on application shutdown. This covers ~80% of applications I've developed so far. Requires more work as :
    1. I need to program a GUI method for each concrete class supporting configuration update
    2. Main actor needs to return its subsystem objects back to Assembler TearDown method. The latter gets a subsystem configuration cluster via accessor method, destroys subsystem object and saves all updates to persistent configuration storage. If you choose to do that in the main actor - it gets statically coupled to subsystem classes via subsystem configuration typedefs, compromising its reusability
  3. Application configuration updates need to be saved/reloaded while the application is running. This is a hard requirement for applications running 24/7. It is also the most expensive to implement without statically coupling your main actor to all its subsystems. I do this by creating a separate Configuration Handling Class. Might be an actor or a 'normal' object. Configuration Handler code is application-specific, but one can 'recycle' Configuration Handlers from another project to save on development effort. I think that Configuration Handler details are beyond the scope of this thread, but you can read about the design pattern and get example sample code (LV2015) here.

 

0 Kudos
Message 65 of 88
(3,480 Views)

Dmitry, why do you not use something like Variants instead of static-typed clusters to make reusable config-storage components?  

0 Kudos
Message 66 of 88
(3,470 Views)

 


@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 ...

 

 

0 Kudos
Message 67 of 88
(3,462 Views)

@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.

0 Kudos
Message 68 of 88
(3,458 Views)

@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.

0 Kudos
Message 69 of 88
(3,455 Views)

@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.)


------------------------------------------------------------------------------------

Please join the conversation to keep LabVIEW relevant for future engineers. Price hikes plus SaaS model has many current engineers seriously concerned...

Read the Conversation Here, LabVIEW-subscription-model-for-2022
0 Kudos
Message 70 of 88
(3,444 Views)