10-25-2024 01:13 PM
Hello all,
I'm a few months into practicing LVOOP and AF, and I now need to save some actor configurations to a human readable file. My goal is to load the correct child class from file, along with its config data, then update the file if the config is changed by the user. Several objects will contain arrays of clusters which has become difficult to manage in *.ini files. I've looked into JSON serializers (specifically JSONtext and AQCL) but I'm a little lost on how to implement them.
Does anyone have a serializer example, or thoughts on an alternative method?
10-25-2024 01:29 PM
I have the same question as well. I have been using .ini files for my configuration for a several months now, but I'd like to use JSON. I've been doing it with this toolkit, but it doesn't scale well since it uses variants (or I'm not using it correctly):
https://www.vipm.io/package/oglib_variantconfig/
There are example files with the toolkit above.
This is a recent presentation given by Stefan Lemmens with example code throughout for object JSON serialization:
https://www.youtube.com/watch?v=ktlqPpuJI6g
I've wanted to check out the resources, but I haven't made the dive.
If this is already figured out, how to load an object from JSON, it would fit nicely to the Factory Pattern:
https://knowledge.ni.com/KnowledgeArticleDetails?id=kA03q000000YK8ICAW&l=en-US
10-25-2024 01:38 PM
This isn't a challenge unique to actors, but the main sticking point I've found in the past is that configs aren't (usually) identical across actors. Because of this, it can be challenging to disseminate actor-specific information in a generic way. If you wrap the configuration information into a "config class" and all the config classes inherit from a common ancestor, then you can have a common method to distribute information, but you still have the issue of populating the classes with INI/JSON info generically.
If you want an out of the box way to tackle that issue, the one of the best solutions I've found is to use the JSONtext VIP along with the JSONtext Object Serialization add-on.
I don't have an example of that readily available, but hopefully that helps give some ideas.
10-25-2024 02:04 PM
I've tackled this a couple different ways, and finally found a method that I actually like somewhat. First things first, JSONText is the way to go. The built in tools won't work for this.
The method I use is to have my parent class implement a "Load settings from JSON" file. It takes a JSON file as an input. The parent class implements any load functions the parent class needs. All of it is explicitly defined- there's nothing automatic. If you need to load a Double value, you load a Double value by name. No automatic serialization.
This function is marked as "Overrides must call parent". Child classes can override this function, and they will explicitly load all of their child-specific data. Since each subclass loads its own data, you can use bundle and unbundle nodes, as each class ONLY loads its own private config data.
This method has been the most flexible and expandable. The downside is that it doesn't load everything automatically, and you have to update functions each time you add a parameter, but in practice it's not bad. Doing it the first time takes a while, but it's not THAT bad.
I've tried the "automatic serialization" trick before, and it doesn't really ever work well for me. I usually don't want to load ALL of the private data from a file, since there's state info or temporary data or whatever in there. I don't usually want to load that from my config file.
I've also tried "config objects" before and didn't care for it. It's nice from a "purist" OOP standpoint, where each object only does one thing, but it results in a LOT of boilerplate code and a LOT of redundant classes. For example, if I had actors Parent, Child A, and Child B, I'd need ParentSettings, ChildASettings, and ChildBSettings. The Settings hierarchy would need to mirror the parent hierarchy, and every Settings object needs getters and setters for every parameter to load, e.g., ChildASettings into ChildA. That's a LOT of functions to keep track of, and in practice I found it wasn't actually helpful. Just having a "Load myself, and only myself, from JSON" seems to work the best for me.
The cool thing about the JSONText library is that you can basically read nested key-value pairs from unstructured JSON files. You don't have to load the whole JSON file into an object to manipulate, which means each sub-actor can load the JSON file and read its own section without worrying about whether or not there are other sections.
If someone has a good example implementation of the "separate config object" method I'd love to see it. I could think of some potential benefits (like being able to load config from a database OR a JSON file OR a .tdms file OR... etc) but that's not a function I've needed, and the extra abstraction layers made it far less useful and more trouble to maintain.
10-25-2024 02:43 PM
10-28-2024 05:32 AM
May be the project CS++ (here description in german) is of interrest for you.
It is an actor-based framework used for physics experiments, mainly for ion traps and PHELIX lasers. It provides at least a standard way to store actor configurations in ini files. But since it is based on fabric pattern, you can add our preferred format for actor initialisation.
The start point is CSPP_Core repo. An example of ini file is CSPP_Core.ini. If you have interesst contact me or the autors HB@BNT and Dennis Neidher @ GSI.
10-28-2024 10:33 AM
Here is a presentation I did on serializing objects for the 2021 GLA Summit. In this case, it was message classes, but the principle is the same.
Distributed_Computing_with_Actor_Framework_and_Non-LabVIEW_Applications
I also strongly encourage using JSONText for this work.
Assuming a configuration file (or file section) for each actor, you can stand up as many actors as needed.
In one of my long-running projects, I'm using configuration objects within the plug-ins. It does make for a lot of glue code, but it works.
10-28-2024 10:43 AM
@NathanDavis wrote:
This is a recent presentation given by Stefan Lemmens with example code throughout for object JSON serialization:
https://www.youtube.com/watch?v=ktlqPpuJI6g
I saw this presentation, and it was excellent. I've applied it to a different project, where I *am* serializing actors directly. (They are pretty lightweight actors, being the individual step classes in a sequencer, where the sequencer is based on State Pattern Actors, but that's a topic for another day.)
The twist I've applied there is to put all of my serializable content in a "Config" cluster (or possibly a class). Then, all of my serializations look like this:
Top-level serialization looks like this:
Note that I'm cheating a bit, and assuming that all of my classes will be in memory. For a plug-in architecture, you'll need a way to look up where the classes are on disk. You can use class paths and load from disk, or preload all of your classes into memory at startup.
Note also that I *always* add a revision number, for *each* config cluster. You'll want this when (not if) you revise your configuration files going forward, so you can mutate your legacy files.
11-05-2024 12:21 PM
This thread is such a treasure-trove of information for me. Parsing JSON strings with SQLite JSON functions was the eye-opener!
I've been using a very simple Configurable Interface to load configuration from file. (Saving configuration to file is a custom method in the abstract or concrete LabVIEW class as required.)
Configurable.lvlib
└── Configurable.lvclass
├── private
│ └── loadClass.vi
├── protected
│ ├── configFilePath.vi (must override, should return Path)
│ ├── loadConfiguration.vi (must override)
│ └── updateIdAndConfigFilePath.vi (must override, should set ID and Path)
└── public
├── id.vi (must override, should return ID)
└── new.vi (static, accepts ID and Path inputs)
Every LabVIEW class that inherits this interface must be instantiated via the `new` method. I'm not using plugins, so the application must have knowledge of the concrete classes in memory.
My coworker and I use INI format. `ID` essentially represents the section name; and `Path` represents the full path of the configuration file. Every such section contains a `class` keyword, whose value is used by `loadClass.vi` to select the LabVIEW Class in memory. The remaining key-value pairs are meant to be read by the overriding `loadConfiguration.vi`.
I have had reasonable success with this approach. My biggest gripe is code duplication for getting/setting ID and Path.