Lua environment on callbacks

#1

I’m a bit fuzzy on the environement in which things run.

It seems to me that if I make a device, each instance runs in its own environment (please correct my terminology if wrong) and a local variables defined before any code in startup are available only to that device instance. Not that device, but that device instance.

Now what about a local function. It is only accessible from within that device instance environment but is it efficiently not duplicated for more device instances of that type?

Now I know I must make a callback global, but why? As far as I know, when I’m called back (e.g. a call delay from within a device), it is running in my device instance environment. Why, then, would it be a requirement to be global. I realize it is being called by the OS, but the OS is obviously setting up the environment first.

Although I have everything in my device working, that’s no reason to skip the understanding of how it works and why. Any insight would be extremely helpful.

Keith

0 Likes

#2

[quote=“GeekGoneOld, post:1, topic:204460”]I’m a bit fuzzy on the environement in which things run.

It seems to me that if I make a device, each instance runs in its own environment (please correct my terminology if wrong) and a local variables defined before any code in startup are available only to that device instance. Not that device, but that device instance.

Now what about a local function. It is only accessible from within that device instance environment but is it efficiently not duplicated for more device instances of that type?

Now I know I must make a callback global, but why? As far as I know, when I’m called back (e.g. a call delay from within a device), it is running in my device instance environment. Why, then, would it be a requirement to be global. I realize it is being called by the OS, but the OS is obviously setting up the environment first.

Although I have everything in my device working, that’s no reason to skip the understanding of how it works and why. Any insight would be extremely helpful.

Keith[/quote]

There are many questions here, but they all stem from “why must callbacks be global?” The answer is that the callback is specified by name in a string, rather than a function reference. The string, as a function name, lacks the context to tell the system what context the function can be found in. So requiring it to be global context quickly answers that question.

This is an unfortunate design decision on Vera’s part that has persisted for some time, and could be improved upon any time without disrupting any current plugins or user fragments, IMO. The better choice would have been to simply pass a function reference, essentially a pointer directly to the function. This is unique to each function, so it removes all ambiguity, and removes the necessity for the system to search for the function–it already has it. Here’s an example:

Old (current) way:

[code]function myGlobalCallback( data ) – old way, this has to be global
luup.log(“callback called! I got data=” + tostring(data))
end

luup.call_delay( “myGlobalCallback”, 10, data )[/code]

New way:

[code]local function privateCallback( data ) – declared local, does not need to be global now
luup.log(“callback called! I got data=” + tostring(data))
end

luup.call_delay( privateCallback, 10, data )
[/code]

This would also facilitate using an anonymous closure as the callback:

-- Turn off light after 10 seconds luup.call_delay( function() luup.call_action( "urn:upnp-org:serviceId:SwitchPower1", "SetTarget", { newTargetValue="0" }, lightDevNum ) end, 10 )

This may be a little challenging to read for new players, but it’s just passing an anonymous function (defined in line as part of the call) to call_delay(). Being able to do it this way (passing function reference) also makes it possible to use upvalues without any extra or unusual caution.

Vera could make this change today without disrupting current code. All they would need to do is look at the first argument, and if it’s a string data type, they do it the old way, and if it’s a function, do it the new way. At some point, all they are doing is calling the function pointer anyway, the old way just does the extra work of figuring out how to get it first, while the new way passes it directly, so again, no search needed, and thus doesn’t need to be global.

0 Likes

#3

I second that. Coming from C#, anonymous functions are better in terms of readability too.

0 Likes

#4

Very interesting. I certainly didn’t know that!

I like your proposal but as a programmer I can’t tell you how many times I’ve seen a change in one part of code affect something in a completely unrelated area. Also as a user I would expect full testing prior to release. All of that takes manpower and there are higher priority issues.

Do you have any comments about the other issues such as whether the functions of a device instance are efficiently not recompiled (i.e. only exist once for the device type). Specifically, the global functions are redefined (identically) for each device instance. In most programming languages that is a no-no, but lua is different. Then there are the local functions. Are they recompiled for each instance? Looking at squeezing as much efficiency as possible…

0 Likes

#5

I don’t think that there is anything you should be worrying about here. Lua byte code is incredibly compact. Much worse is Vera’s profligate use of memory. The latter arises from each plugin having its own Lua instance, and each instance is quite separate from every other. Within any one, the Lua interpreter is really rather efficient in sharing objects, particularly because of its lexical scoping and use of closures.

Strings, of course, are immutable and always shared, but you should be careful with concatenation operators, always preferring the use of table.concat() over multiple uses of ‘…’

0 Likes