Jump to content

Action Engines... are we still using these?


Recommended Posts

Action Engines, aka LV2 style functional globals with a bit of embedded logic. These are great and I have definitely leveraged them pretty hard over the last decade or so of my programming.

Today I am going through some old code, porting it to a newer framework I have developed. The old code had lots of Action Engines. Most of the inter-loop (process) data sharing was done using these, and there is nothing inherently wrong with it. The old application works well.

It has been a while since I heavily relied on Action Engines, but I seem to recall them being a bit difficult to debug due to their global scope and data in other Action Engines going stale when the application was stopped (or aborted).

However, as I drink more of the cool-aid I am moving more and more to a ByVal type approach to code, so that is having the data on a wire when I need it, rather than tucked away somewhere else in an action engine.

Concrete example: a system has multiple temperature measurements. These are used to determine approximate deformation of a structure which can then be used to correct for something else. However not all the temperatures are always used, and in fact we only care about the mean temperature. The operator can select which temperature measurements are to be used. I had previously implemented this as an Action Engine and it works perfectly, but am thinking of re-implementing it as a class that lives on a wire somewhere in my main controller (or something like that).

So my question is this, for those developers that use (or used to use) Action Engines, do they still retain that special place in your heart or have they now been tucked away in a drawer?

Link to comment

If I need something like the an Action engine (reading/writing some common data in different parallel VIs), I’ll always go with classes.
And then of course it has to be by reference classes.


Converting an Action Engine to a Singleton class is a solution I often use. I follow the Single Responsibility Principle, for a class like this.

ForceCal.png
Link to comment

Yes I suspect if I had discovered ByRef classes before I become familiar with Action Engines I would be the same. But..I do not really make much distinction between a singleton ByRef class and an Action Engine (except perhaps that the class is easier to manage using one of the ByRef OOP toolkits).

I am a bit torn on this one... I want to use ByVal as I think it makes debugging and scope easier to understand, but that means the class has to live somewhere, ok fine I have a "main controller" actor/process, so it can go there. However any time I want to interact with it I then have to create messages that the main controller can process. And then there is the issue of getting data out of it and usable by the rest of the application.

I suppose for your example I would have the ForceCalibration object as a ByVal class in the process that is actually going to use it.

 

Edited by Neil Pate
Link to comment

Action Engines are singletons and class clusters are an instance scoped variable, not global. So. If you want an exact equivalent then you have to jump through hoops like using a queue or a FGV (just like an AE-DOH!). The migration means you get new VI methods for each ENUM entry (I've talked before about code bloat) and forcing the user to drop a load of different VIs from a palette or quickdrop. You don't get a lot else apart from increased complexity and the ability to wave the "I'm using OOP" banner.

Personally. I would prefer a polymorphic VI over a class because you still have the code bloat but don't burden the user..It looks like you have just swapped the enum for a drop-down menu and you get the adapt to type for the different inputs.

If you want it as a class because.......reasons. Just wrap the action engine in  some class methods (one for each enum). That's pretty much what you will end up doing if you don't use a queue and you can still wave the banner ;)

However. if you promise and "cross your heart and hope to die" that you will only have one instance then a normal class (with a DVR?) will suffice ;).

Edited by ShaunR
  • Like 1
Link to comment
28 minutes ago, Neil Pate said:

I have done pretty much all of these kinds of things in the past, including a rather ropey check-in/check-out procedure of a class before DVRs came along.

I just am trying to get a feeling from other devs what they currently consider to be their "normal".

Well. As I said. For me it's.polymorphic VIs because they have the menu in lieu of an enum (same thing but different) and de-clutter the compane. However. I don't think I have required global storage like that for a long time. Using a DB makes AEs (and a lot of other things) almost redundant since you just craft a query string.So for your average N sensors example it would be a one liner like

SELECT avg(value) from Sensors WHERE status='active';

Edited by ShaunR
  • Like 1
Link to comment

To answer the primary question of the thread, yes I do still use them but kinda rarely.  For each medium to large sized project I'd say I probably create 2, and there are probably another 3 or 4 buried in reuse library function, used in the project.  The reuse functions I can make a decent argument to make them classes, for the project specific ones, I could say the simplicity, and ease of use for other developers makes me want to leave them alone.  

In most of my cases I don't see action engines working well because there maybe a time when I intend on doing some kind of periodic function, like checking status and reporting it every 100ms, and an action engine is a blocking, synchronous call.  So I usually end up making a separate asynchronous module (actor) so that in the future if I need to do some task periodically it can be done there.  The calls to this will still look like the action engine, but with a message to the actor, and then a message that is in the reply coming back so the call will still be synchronous, but asynchronous tasks can take place too.  It's rare that I can convince myself that an interaction with some private data, will never need this feature.

Link to comment

Action Engines are still a tool that get dusted off and used. I have several communication plugins (packed libraries) where I needed to put in debug screens which wound up being a simple AE. I could have used a different method of showing the debug information, but it made sense to use an AE.

  • Like 1
Link to comment
23 hours ago, Neil Pate said:

I am a bit torn on this one... I want to use ByVal as I think it makes debugging and scope easier to understand, but that means the class has to live somewhere, ok fine I have a "main controller" actor/process, so it can go there. However any time I want to interact with it I then have to create messages that the main controller can process. And then there is the issue of getting data out of it and usable by the rest of the application.

I suppose for your example I would have the ForceCalibration object as a ByVal class in the process that is actually going to use it.

I’m surprised there’s not more love for Action Engines.  I’ve never really used them, but instead have focused on making “actors/processes” easy to make and communicate with, and on having by-value classes live wherever it’s natural for them to live.   If they naturally need to live in an independent process, then it is easier for me to make it an actor than an Action Engine.  I’m not really negative on Action Engines, but they don’t give me capability that I can’t get with an actor, and as hooovahh said, I can do active, periodic stuff with an actor.

I only very rarely use actual by-ref objects (usually using a DVR).  “ForceCalibration” would most likely by a by-value object for me, too.

As a general programming principle, I believe one should standardize on a set of complimentary techniques (and get very good at those techniques), rather than mix multiple alternate ways of doing similar things.  Action Engines, “actors”, and by-ref objects are different ways of doing the same thing, so one should pick the one you think is best and run with it.

Link to comment

James, for me Action Engines came quite early in my LabVIEW education. Certainly more than 5 years before I came across actors, classes, DVRs etc. So now I am trying to standardise my habits, but even that is tough as new ideas come along all the time and I want to try and stay current with my best-practices/education.

My gut feeling tells me that any new code I develop should probably not have any Action Engines in it as I now have other ways of dealing with the same problem.

I suppose one of my big problems with Action Engines (and really any ByRef type architecture) is that it is so much harder to debug and generally grok what is going on that a purely ByVal system. Wires, (essentiallly pure ByVal operation) are to me one of LabVIEW's greatest strengths.

Edited by Neil Pate
Link to comment
1 hour ago, drjdpowell said:

I’m surprised there’s not more love for Action Engines.  I’ve never really used them, but instead have focused on making “actors/processes” easy to make and communicate with, and on having by-value classes live wherever it’s natural for them to live.   If they naturally need to live in an independent process, then it is easier for me to make it an actor than an Action Engine.  I’m not really negative on Action Engines, but they don’t give me capability that I can’t get with an actor, and as hooovahh said, I can do active, periodic stuff with an actor.

I only very rarely use actual by-ref objects (usually using a DVR).  “ForceCalibration” would most likely by a by-value object for me, too.

As a general programming principle, I believe one should standardize on a set of complimentary techniques (and get very good at those techniques), rather than mix multiple alternate ways of doing similar things.  Action Engines, “actors”, and by-ref objects are different ways of doing the same thing, so one should pick the one you think is best and run with it.

Actors and AEs aren't equivalent. It's like saying a program is the same as a DLL. AEs are just wrappers around a shared resource to try and introduce mutexes. So you could have a "List" and methods "Add", "Remove", "Read" etc called by multiple VIs.. Nothing is "running", They were primordial classes - wrapping a global memory when we didn't have the language construct of classes.

Link to comment
1 hour ago, ShaunR said:

AEs are just wrappers around a shared resource to try and introduce mutexes.

Actors, AEs and by-ref classes can all be used as wrappers around shared resources that serialize parallel access to them and protect critical sections of code.  They aren’t equivalent, but they have considerable overlap.   

Edited by drjdpowell
Link to comment

In my applications I tend to use state machines that run in parallel and acces them by queues to get/set/perform some operations based on data that this SM is using.  I use ActionEngine if I don;t need to do any background operations and they are working good. I usually have 1 global variable in every app that is set at beginning and read in many places.

Now I am working on big OOP project, they never use Action Engines. All is based on DVR classes but idea is almost the same. Enums are replaced by dynamic dispatch, it is easier to create another instance of an object (state machine)  and it is easier to add another references/implementations by inheritance instead of case structures. I am not seeing benefits of private/public acces scopes introduced by classes, projects in LV are simply to small.  Biggest disadventage is code bloat... project has more than 10k vis and IDE needs to load more than 5 minutes, compilation times are also very long and for some reason when I close project most of classes need to be saved even if no change was made :D And another thing are PPLs, I have no idea why this feature was introduced..

Link to comment

DVRs of an object are really handy, but I am hesitant to rely on them for core features.

As I see it there are two options, design the class purely as ByVal and turn it into a DVR, then everywhere on the block diagram you have to use an IPE (which does not feel nice and is messy).

The alternative is the class private data is just a DVR. This is the convention adopted by G# and the other toolkit (GDS).

Of the two I prefer the second option, but it still means all data is ByRef, and this is the very thing I am trying to avoid.

Certainly though if I knew about GDS or G# when I was initially using Action Engines I would probably have used those toolkits instead as the method creation wizards can certainly save a lot of time. As others have said, an Action Engine is very similar to a ByRef singleton class.

Edited by Neil Pate
Link to comment
1 hour ago, drjdpowell said:

Actors, AEs and by-ref classes can all be used as wrappers around shared resources that serialize parallel access to them and protect critical sections of code.  They aren’t equivalent, but they have considerable overlap.   

Yes. And I can use a screwdriver as a chisel.:shifty: (By-ref classes is an irrelevance.)  

Any class is a number of [atomic] methods acting on a protected storage container and this is what AEs attempt to do without the language support. Any other interpretation or extrapolation is just linguistic gymnastics.

Edited by ShaunR
Link to comment
2 minutes ago, ShaunR said:

Any class is a number of [atomic] methods acting on a protected storage container and this is what AEs attempt to do without the language support. 

Ah, you mean the encapsulated data.   Your right, but you talked about a “shared resource” that might need “mutexes”, which is about accessing things from processes running in parallel.  By-value objects are not sharable and have no use for mutexes.  

Link to comment
2 hours ago, drjdpowell said:

Ah, you mean the encapsulated data.   Your right, but you talked about a “shared resource” that might need “mutexes”, which is about accessing things from processes running in parallel.  By-value objects are not sharable and have no use for mutexes.  

Not quite.

I said they are wrappers around shared resources to introduce mutexes. The mutex is the VI boundary and makes access atomic.

It is indeed about accessing things from processes running in parallel. I think you have forgotten that they are not reentrant so they have blocking behaviour around the resource.

I'm not sure what you mean by " By-value objects are not sharable and have no use for mutexes. ". You can branch a class wire and what good would a global variable be if you couldn't put it on multiple diagrams?

Link to comment
4 hours ago, ShaunR said:

I'm not sure what you mean by " By-value objects are not sharable and have no use for mutexes. ". You can branch a class wire and what good would a global variable be if you couldn't put it on multiple diagrams?

Branching makes copies; but the individual copies are not shared.  Globals are by-ref.

Link to comment

I try not to use them for new projects but for maintenance sometimes its easier and less risky to add one into the code than to manipulate all the data structures to add what I need. For new projects I'd only use DVRs or messaging. Usually messages but DVRs are certainly useful.

Quote

 ok fine I have a "main controller" actor/process, so it can go there. However any time I want to interact with it I then have to create messages that the main controller can process

Depending on how high performance your application is, you can usually just cheat/be awesome by separating out invariant data (for example you might read a file to find what DAQ channels to use but they never change after that, so you can pass the constant data around to all the loops) and by sending variable metadata along with the data (in your first example, maybe the loop calculating the deformation could also send "calculation performed using channels 0, 4, 7" and because the channels are known by all loops, you get to send whopping 24 bits of information and never have to make an annoying request-response message). Computers are very fast, and I think its better to waste a little bit of their performance rather than try to document a ton of extra messages. Plus sending metadata+data avoids races if something changes.

In that first example I'm honestly not sure what you'd use an fgv for, it seems like a much better fit for messages. HW loop broadcasts temp. Calculation loop subscribes to temp and broadcasts deformation. UI subscribes to both and broadcasts control parameters. I know your description is high level but based on your description I'd immediately jump to that implementation. Could use classes too but I'd keep that inside each process (variable calculation methods, or hardware abstraction).

Link to comment
11 hours ago, drjdpowell said:

Branching makes copies; but the individual copies are not shared.  Globals are by-ref.

Making copies is how LabVIEW resolves shared resource contention. Even with DVRs on a wire this is true except the resource is just a pointer to the data and so it is that which gets copied.However. Even then. With a DVR, you are forced to use an IPE to access it because it can be accessed from anywhere. This is your mutex around the data.

Globals are not by ref. They also are copies (see above).The second biggest argument for why you shouldn't use them is that every instance is a copy of the data so liberally spreading them throughout your code is a quick way to resource starvation when that 250MB 2d array gets in there..

Link to comment
5 hours ago, smithd said:

In that first example I'm honestly not sure what you'd use an fgv for, it seems like a much better fit for messages. HW loop broadcasts temp. Calculation loop subscribes to temp and broadcasts deformation. UI subscribes to both and broadcasts control parameters. I know your description is high level but based on your description I'd immediately jump to that implementation. Could use classes too but I'd keep that inside each process (variable calculation methods, or hardware abstraction).

The FGV (Action Engine actually) was used as the global storage of the temperatures and a mechanism for calculating the mean. The temperatures were needed all over the application (mainly for display purposes).

Please try and remember this was six years ago, and was done way before I had started to seriously use an sensible messaging framework. I am not suggesting this is how I would do it now-a-days.

Link to comment
5 minutes ago, Neil Pate said:

The FGV (Action Engine actually) was used as the global storage of the temperatures and a mechanism for calculating the mean. The temperatures were needed all over the application (mainly for display purposes).

Please try and remember this was six years ago, and was done way before I had started to seriously use an sensible messaging framework. I am not suggesting this is how I would do it now-a-days.

I think the main point Smithd was making is that the current thinking about replacing AEs is to use DVRs but with messaging architectures, global storage is handled differently requiring neither. You have, however, restricted your options by insisting that you  don't want to use by-ref solutions (although I think you and a few others , maybe misunderstand what that exactly is since AEs are not by-ref) 

  • Like 1
Link to comment

Let’s try a different tack.

There is an important difference between situations where one loop can affect another loop operating in parallel (a side effect), and where it cannot.  Globals, Locals, DVRs, Queues, Notifiers, FGVs, Action Engines, or the new "Channels” are all means where one can “reach in” and affect a loop while it is running.  Data on a wire is not. 

An interesting talk related to this by AQ: Why Dataflow Works: The Potato and Candy Explanation

Link to comment
21 minutes ago, drjdpowell said:

Let’s try a different tack.

There is an important difference between situations where one loop can affect another loop operating in parallel (a side effect), and where it cannot.  Globals, Locals, DVRs, Queues, Notifiers, FGVs, Action Engines, or the new "Channels” are all means where one can “reach in” and affect a loop while it is running.  Data on a wire is not. 

An interesting talk related to this by AQ: Why Dataflow Works: The Potato and Candy Explanation

I really don't know where you are trying to get to now. This is starting to look like an "Anything is an airplane given enough thrust." kind of argument.

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.