Tag binding or expression to read dynamic list of tags

I have a perspective view with a custom prop that holds a list of status tags. This list is generated dynamically so it might say [tagpath1,tagpath2, tagpath3] or [tagpath4, tagpath5] or [tagpath6] so the size of the list changes. I want to read all the tags and do an AND to get the overall status, what is the best way to achieve this?

A tag binding only takes a single tagpath from what I've seen, so what is the best way to do this?

  1. An expression binding using tag()? This works if the number of tags in the custom prop are the same but I could get anywhere from 2-5 tags depending on the area. I can't find a way to loop within an expression.
  2. A runScript expression binding that calls system.tag.readBlocking to read the tags then combines the values. My understanding is that a tag binding or expression will update immediately but the runScript will only update at the pollRate so this option results in slower updates.
  3. Configure a tag binding that can read all the tags??

This isn't possible in a pure expression without @pturmel's simulation Aids module.

You can do it in script, so runScript expression or a propertyChange event that calls a library function will work.

def getStatus(paths):
    #assume that all paths point to boolean values
    return all(qv.value for qv in system.tag.readBlocking(paths))

Not even with my SimAids module. The tag() expression function is not compatible with my iterator functions, as best I can tell.

1 Like

Thanks for the quick help!

I started with the runScript method then wanted to see if there was a better way as I know runScript is to be avoided if possible. Guess i'll stick with it.

Can you use an expression structure binding preconfigured to read all the tags, and then extract by key/index the ones you 'care' about?

I would think that the penalty of using runScript compared to a script transform would be the same (interested to know though), but the benefit of a script transform is legibility, so I would use that

I'd expect a script transform to do slightly better than runScript, actually, but would also be interested in benchmarking. One of these days...

4 Likes

Speaking of benchmarking, it would be great to have a complete* list of benchmarking results comparing different ways of doing things to reference. I think that'd be super beneficial from a design and end user perspective to maximise performance

Because?

(The difference does happen to be non-benchmarkable with my new tools...)

Script transforms have a more direct optimization story; there's (modern) effort to ensure that they don't have to perform the Jython interpretation/compilation step, where runScript relies on a string equality check every time execute() is called.
If the de-duplication logic ends up being identical, they should indeed end up basically the same. The difference would only be if one or the other ends up "compiling" the code too often.

Just had an idea--hold the line for a few minutes....

2 Likes

I don't know about the line, but I'll hold your beer

3 Likes

See my fresh post over here:

As for the benchmarking, I just played with my new nanoTime() in some simplistic scenarios aimed at measuring overhead, returning delta nanoseconds as the output value.

The simplest is an expression like so:

runScript('transforms.deltaNanoTime', 2000, nanoTime())

where the transforms script module includes this:

def deltaNanoTime(ts):
	return System.nanoTime() - ts

The above reports consistently (after first execution) in 40 - 50 microseconds. I'm not sure if my nanoTime() function is being executed in the call chain before runScript sets up (part of) the script environment.

The next simplest is this, with a squirrely use of my asList() function to make it re-execute every couple seconds:

asList(
	now(2000),
	objectScript('transforms.System.nanoTime() - args[0]', nanoTime())
)[1]

This runs consistently in 180 - 200 microseconds. The call to execute subexpressions is before any script environment setup.

The next three scenarios aim to measure transform overhead. Each of the following has an initial expression that looks like this:

asList(
	now(2000),
	nanoTime()
)[1]

The output of this first stage is the nanoTime() value, and retriggers every two seconds. The time between execution of nanoTime() and delivery to the next stage is tiny.

The above is fed to this script transform:

def transform(self, value, quality, timestamp):
	return transforms.System.nanoTime() - value

This is pretty noisy, with values anywhere from 300 to 900 microseconds, but mostly around 550 - 600.

The first stage is then fed (separate binding) to this expression transform:

runScript('transforms.deltaNanoTime', 0, {value})

This is also noisy, with values from 250 - 900 microseconds, but mostly around 500. Just a bit faster than a very similar script transform.

Finally, that first stage is fed to this expression transform:

objectScript('transforms.System.nanoTime() - args[0]', {value})

Noisy, again, but a bit less. From 300 - 450. Just a bit faster than runScript(), by eye.

Probably ought to collect some stats with recorder().

5 Likes

Summary: transforms are terrible.

4 Likes

Interesting. It suggests there's a significant bottleneck in a common layer, which also suggests there might be room for improvement. I'll have to make a note to skim that code at some point.

4 Likes

Collected some overhead stats. Added a sixth binding for comparison, and made them all trigger from a common reference. The new expression transform looks like this:

nanoTime() - {value}

It is notably faster than the other transforms.
To make the recorder() run smoothly, I made a trigger timestamp with now({this.custom.pace}) and set pace to 200. Then I modified the transforms' first stages to look like this:

asList(
	{this.custom.trigger},
	nanoTime()
)[1]

The non-transform tests of runScript() and objectScript() now use that structure, too.

Results ('RS' is runScript, 'OS' is objectScript):

The bare runScript() result is so low that I'm not sure I believe it, as the difference between runScript() and objectScript() is much less in the transform setup.

Anyways, avoid transforms, and if you can, avoid scripts entirely.

Do notice how much faster everything runs without the designer, and how it skewed my observations last night.

3 Likes

I'm definitely interested in the deep dive into transform overhead, but to circle back to OP's question, our project is plagued with dynamic lists of tag paths in every view, so we've tried all sorts of things.

In cases when the max number of tags is not too high we will sometimes use an array of normal indirect tag bindings sized to our max possible number, and just use a path to an unchanging dummy/default memory tag in all the slots we don't need. It's annoying to setup all the tag bindings the first time, but works fine after that.

Honestly, our project would be so much simpler if there was a method to have a property take in an array of tag paths and spit out an array of tag values, and have the individual tag values in the array update in a dynamic (non-polling) manner.

Maybe abuse a hidden table with a column view defined that has the indirect binding you need, and message tag values from that nested view to page scope where the table could listen for them... Include a UUID perhaps, to make sure the messages are handled by the correct target.

1 Like

So, a new binding type that accepts an input property but knows to "spread" it as a list and read all the inner paths?
That'd be tricky to explain to users but easy to implement...

Yeah. The explanation feels simple to me: it behaves identically to an indirect tag binding, except if the input parameter is an array, then the output is an array.

I imagine it would look like this in use, with "tag_values" using the whole of "tag_paths" as the only parameter to it's binding:

property_sample