Templated map transforms

I had a potentially terrible idea that solves a missing feature (there's a forum post with the idea somewhere but I can't find it). The idea is to add the ability to create saved transform templates, for example save map transforms that you use all the time.

I had an idea that I could work around this by instead using a script transform with library functions that use a dictionary lookup to contain the mappings. The most obvious use-case for me is in device symbol status and mode displays to change the colour of the device based on the status tag. Function examples are below.

These would be used on every device on a page, between 5-40 devices maybe, and 70-130 clients.
I'm just wondering how terrible would this be on gateway performance and if it's a good idea.. I always try to minimise the number of script transforms I use as these have slight performance impacts. The gain here is development time, maintainability, and standardisation. But if it's at the cost of client performance, then I don't think it's viable

def deviceStatus(statusDesc):
	statusMap = {
		'Stopped': 'Devices/Status/Off',
		'Closed': 'Devices/Status/Off',
		
		'Running': 'Devices/Status/On',
		'Opened': 'Devices/Status/On',
		
		'Faulted': 'Devices/Status/Faulted-nonflashing',
		
		'Starting': 'Devices/Status/On-trans',
		'Opening': 'Devices/Status/On-trans',
		
		'Stopping': 'Devices/Status/Off-trans',
		'Closing': 'Devices/Status/Off-trans',
		
		'Paused': 'Devices/Status/Paused'
	}
	
	return statusMap.get(statusDesc, 'Devices/Status/Invalid')


def deviceMode(modeDesc):
	modeMap = {
		'Manual': 'Devices/Modes/Manual',
		'Auto': 'Devices/Modes/Auto',
		'Maintenance': 'Devices/Modes/Maintenance',
		'Off': 'Devices/Modes/Off'
	}
	
	return modeMap.get(modeDesc, 'Devices/Modes/Invalid')
1 Like

Write a module to make these expression transforms. Way more efficient. Configure using static datasets. (Or just use the lookup() expression function.)

BTW, if you do use scripts, a constant dictionary should be a script module top level variable, not set up inside the function. If the above is your typical code style, that would significantly impact performance. In other words, it should be like this:

_deviceStatusMap = {
	'Stopped': 'Devices/Status/Off',
	'Closed': 'Devices/Status/Off',
	
	'Running': 'Devices/Status/On',
	'Opened': 'Devices/Status/On',
	
	'Faulted': 'Devices/Status/Faulted-nonflashing',
	
	'Starting': 'Devices/Status/On-trans',
	'Opening': 'Devices/Status/On-trans',
	
	'Stopping': 'Devices/Status/Off-trans',
	'Closing': 'Devices/Status/Off-trans',
	
	'Paused': 'Devices/Status/Paused'
}
def deviceStatus(statusDesc):
	return _deviceStatusMap.get(statusDesc, 'Devices/Status/Invalid')


_deviceModeMap = {
	'Manual': 'Devices/Modes/Manual',
	'Auto': 'Devices/Modes/Auto',
	'Maintenance': 'Devices/Modes/Maintenance',
	'Off': 'Devices/Modes/Off'
}
def deviceMode(modeDesc):
	return _deviceModeMap.get(modeDesc, 'Devices/Modes/Invalid')
5 Likes

Ah yes, I do normally do this!

I guess this would be a good introduction to module writing! :grimacing: I've always wanted to break things write a module. But as always, I would want to then extend it. Like make an interface for the dev to add their own custom map definitions, and importing/exporting them, etc... which I really don't have time for but would obviously find time for :smile:

This wouldn't be the end of the world, but it's less simple than I'd like since it requires copying the path of the dataset tag. It might be the biggest bang for the buck though

I put my dictionaries at the top but I always did it because I think it's neater code wise.

Is there a serious performance issue with sticking the dictionaries inside the functions like that (assuming this function is run at a high frequency)? Does it have to "make" the dictionary every time the function is called where as it only makes it once when the module is loaded if its a top level variable? What's going on?

Yes, it will build the dictionary every time.
In basic cases like this one, variables are destroyed when their scope is left. Which, in this case, is when the function returns.

I'm kind of guessing that in the case of generators, functions keep their local variables alive.
I'm picturing it like a static variable in C

1 Like

Yes.

It is even more important for computed constants--things that need a complex operation or function to compute, but don't change after that. Such should be initialized at the top level from a function return value.

def _someComplexInitialization():
    # do something complicated
    return result

someConstant = _someComplexInitialization()
2 Likes

What exactly are you envisioning here? Something like style classes?

I can see where you're going with a map transform, but what about the other transforms. How would this apply for (Format, Script, and Expression transforms?)

Not sure, but I guess so. Just somewhere to configure and save your map transforms. Ideally you'd then be able to select them in the map transform area within a binding.

You're right, it only really makes sense for the map transform and none of the others

I agree that it would be nice if there was something like a style class for map transforms.

You can use an embedded view to lock in a static map transforms and pass in the binding that the map transform is applied to. That's probably one of the more elegant ways to do at this time.

I can definitely see a use case for this. I find myself regularly using the same map transforms throughout a project to apply enabled/disabled style classes to components.

1 Like

Every embedded view layer increases load time though, so this would be my least preferred option, not that I haven't done this in the past though. But these days I would avoid this option. I've gone with Phil's suggestion to use tag datasets and the lookup expression function which isnt too bad and offers excellent performance. Would still prefer templated transforms though!

Oh yeah. I really like the idea of transforms.