Copy link to clipboard
Copied
hi there
Still on CF 9.0.1.x
I have in the same directory 2 CFCs, each implementing an object that communicates with each other. Each CFC has the variables scope used. They are part of an emulator for a physical device. There are more cfc involved but I reduced it to the minimum test case.
The parent is MCU which instantiates any number of TARGETS. A target communicates back to MCU with a callback function provided by the MCU. The callback function uses MCU's clock to log the incoming message.
---
component MCU {
variables.mcu.clock = 0;
more of variables.mcu.*
Init () {
// Create all targets, call each target’s registerMCUListener to let them know whom to call back.
variables.mcu.targets = CreateObject ("component", "target").Init ();
variables.mcu.targets.registerMCUListener (cbFromTarget);
}
private numeric function cbFromTarget (struct msg) {
writedump (var=#variables#); // <<<< This shows the variables scope of TARGET, expected MCU
variables.mcu.events.list[variables.mcu.events.ptr] = {
clock = variables.mcu.clock, // CF chokes on this one because MCU is unknown
msg = arguments.msg
};
return 0;
}
}
---
---
component TARGET {
variables.tg.clock = 0;
more of variables.tg.*
public void function registerMCUListener (any mculistener) {
/*
* register the mcu listener function to notify of any events
*/
variables.tg.mcu.listener = arguments.mculistener;
}
private void function ToMCU (struct msg) {
/*
* use the registered mcu listener function to notify of any events
*/
if (isCustomFunction (variables.tg.mcu.listener)) {
variables.tg.mcu.listener (msg);
}
}
}
---
Idea: The cbFunc registers the message in the MCU’s variables scope
But it does not. The marked variables.mcu.clock is throwing an error because mcu.clock does not exist in the variables scope.
Observation: CF is using the variables scope of TARGET instead of MCU.
I confirmed that by dumping the variables scope just before that line.
Now: Is this a bug? Looks as if this is an compiler issue of binding *too late* …
Because it's urgent (as usual) .. any workarounds? Be aware that even those two simple CFCs have to communicate in both directions MCU <-> TARGET .. and of course there are more CFCs involved ...
PS: In CF 10 I could use the keyword function as a data type declaration. I did that but to no avail. Same error.
Thanks
Idea: The cbFunc registers the message in the MCU’s variables scope
But it does not. The marked variables.mcu.clock is throwing an error because mcu.clock does not exist in the variables scope.
Observation: CF is using the variables scope of TARGET instead of MCU.
I confirmed that by dumping the variables scope just before that line.
Now: Is this a bug?
No, it's not a bug I'm afraid: it's expected behaviour.
If I have waded through your code correctly, you are wanting cbFunc to be implemented as a
...Copy link to clipboard
Copied
I don't think it's a bug. You passed the cbFromTarget function to a function in the target cfc. That means it is operating in the target cfc and reading the target cfc's variables scope.
I notice that the cbFromTarget function can take a structure as an argument but I don't see you passing it one. Maybe that's the path to the workaround you need.
Copy link to clipboard
Copied
Yes, you don't see how I use the regsitered function in TARGET .. it goes like this:
---
toMCU ( { msgnum = msgTGHitCorrect,
target = variables.tg.id,
hand = variables.tg.events.stack[evtptr].hand } );
---
My undestandig of the problem is: Since CF does many things quite dynamically, it evaluates the expression variables.xxx at runtime and not at compile time. If I suppose at runtime, it's clear that varaible.xx is the noe of TARGET .. but I was not aware that it is REALLY at runtime.
I considered compile time "dynamic" evaluation like this: CF tries to find unscoped variables first in this scoap, than in that scope finally in scop xyz .. but still at compile time. Hence the reason why I use fully scoped vairables.
Martin
Copy link to clipboard
Copied
I would define component instance variables explicitly. For example, like this
component MCU {
variables.mcu = structNew();
...
etc., etc.
}
component TARGET{
variables.tg= structNew();
...
etc., etc.
}
I am assuming you have also initialized the targets array, using something like
Init () {
variables.mcu.targets = arrayNew(1);
}
It is unclear from your code whether or not cbFromTarget has an updated value of mcu. You might have to do this using this.init() or some other means.
private numeric function cbFromTarget (struct msg) {
this.init();
...
etc., etc.
}
Finally, you get your function, cbFromTarget, to do 2 things at once. Namely to return 0(redundant) and to dump the variables struct. Improve the design by giving the function a returntype of struct, and changing the last line to return variables;.
Copy link to clipboard
Copied
MCUthe only thing I don't quite understand is your proposal in the callback to do a this.init();
I show you my TARGET.init()
---
public any function Init (numeric id, struct colors, struct timings) {
/*
* initialize the target with settings which are consistent for this bout.
*
*/
variables.tg = {};
variables.tg.id = arguments.id;
variables.tg.colors = arguments.colors;
variables.tg.timings = arguments.timings;
variables.tg.clock = 0;
variables.tg.stacks = {
tail = 1,
head = 0,
ptr = 0,
stack = []
}; // stacks represent the expected hits to be dealt with .... can be any arbitrary number
variables.tg.events = {
ptr = 0,
stack = []
}; // this array regsiters all hits to this target
variables.tg.mcu.listener = {}; // this callback function is called on any need to send notification to the MCU
return this;
}
---
MCU.init()
---
public any function Init (numeric cyclecount,
numeric boutcount,
numeric targetcount,
struct timings,
string targetsequence,
struct targetcolors
) {
/* Jab is an array of cycles */
/*
*/
try {
// initializue myself
variables.mcu = {};
variables.mcu.hitdefinition = arguments.targetsequence;
variables.mcu.hitcount = ListLen (arguments.targetsequence);
variables.mcu.colors = arguments.targetcolors;
variables.mcu.clock = 0;
// normalize all timings to time grid
for (var i in arguments.timings) {
arguments.timings = (arguments.timings / this.msecsPerClock);
}
arguments.timings.active = arguments.timings.flash + arguments.timings.waiting;
variables.mcu.timings = arguments.timings;
// prepare the event list
variables.mcu.events = {
ptr = 0,
list = []
};
// prepare the array of all target instances
variables.mcu.targetcount = arguments.targetcount; // this property is redundant because it's ArrayLen () ... maybe we can drop it later
variables.mcu.targets = [];
// Create all targets
for (var i = 1; i lte variables.mcu.targetcount; i++) {
try {
variables.mcu.targets = CreateObject ("component", "target").Init (id = i,
colors = variables.mcu.colors,
timings = variables.mcu.timings);
variables.mcu.targets.registerMCUListener (cbFromTarget);
} catch (any e) {
writedump (var=#e#);
}
}
// now we populate the event queue with the starttime of all targets
setTargetStartEvents();
} catch (any e) {
dump();
}
return this;
}
---
The MCU then is called from outsiede to emulate a clock tick. It will propagate each click down to all of its targets. everything works as expected unless that scope in the callback. I used callbacks for years, of course not so may times with CF.
To your proposal this.init(), maybe you can show a conceptual misunderstanging of mine ... however, it's clear to you what I want: The only thing the MCU must do is register the message of the target in its own event queue together with its own clock stamp.
Thank you for your help
Martin
Copy link to clipboard
Copied
tinu8805 wrote:
To your proposal this.init(), maybe you can show a conceptual misunderstanging of mine ...
I was simply suggesting a possible cause of the following issues you reported:
This shows the variables scope of TARGET, expected MCU... CF chokes on this one because MCU is unknown
If the function cbFromTarget() does not know variables.MCU it can only mean that the component does not have MCU as an instance variable. One way to define this variable is to call this.init(). In the context of the code in your original post, the this-keyword stands for the MCU component. This might be equivalent to MCU.init(cyclecount, boutcount, targetcount, timings, targetsequence, targetcolors) in the context of the code you last posted.
Copy link to clipboard
Copied
Idea: The cbFunc registers the message in the MCU’s variables scope
But it does not. The marked variables.mcu.clock is throwing an error because mcu.clock does not exist in the variables scope.
Observation: CF is using the variables scope of TARGET instead of MCU.
I confirmed that by dumping the variables scope just before that line.
Now: Is this a bug?
No, it's not a bug I'm afraid: it's expected behaviour.
If I have waded through your code correctly, you are wanting cbFunc to be implemented as a closure, ie: the external variables it references are bound at declaration-time: the function is declared in MCU, so you expect the references to the variables scope in cbFunc to refer to the variables scope of MCU. This is not how CF works.
Variable-binding in CF is done at runtime, and at runtime cbFunc is being called from within TARGET, so the variables-scope references in cbFunc are bound to TARGET's variables scope.
CF10 can use functions which are implemented as closures, but not via the syntax you're using here (I'll not repeat the docs: http://help.adobe.com/en_US/ColdFusion/10.0/Developing/WSe61e35da8d31851842acbba1353e848b35-8000.htm...).
--
Adam
Copy link to clipboard
Copied
hi adam
Thanks, that's what i figured out too.
just to be sure: using this. instead of variables. would not be a remedy i assume?
So, I could create another cfc as a queue manager so both MCU and TARGET use it to put/poll event messages ... this should no longer create problems, do you agree?
Thanks
Martin
Copy link to clipboard
Copied
Nah, it would not matter which variables scope you use: they're all bound at runtime, and givent he function is "within" the target CFC, it'll be that CFC's scope that is bound.
I think - if I get where you're suggesting - the idea of using an common intermediary CFC instance should work, yes.
The other thing you could possibly do is to pass a reference to MCU's variables scope along with the callback, and then pass that reference into the callback when you actually call it. It depends on what yo're doing as to what's gonna be a better approach here.
I'm in a slight rush to get out of the office, and I have to say I'm scanned your code a few times now (hence the delay in responding) and I don't quite get what you're doing... not your fault, I'm not poring over the code very thoroughly, so am kinda just assuming what you're doing.
But based on my passing understanding, I'd probably use your queue suggestion. I think.
--
Adam
Copy link to clipboard
Copied
Adam,
Using a closure worked well. It was easy to transfer to that.
variables.mcu.targets.registerMCUListener (cbFromTarget());
and its definition is now
private function function cbFromTarget () {
return function (struct msg) {
newEvent ();
writedump (var=#variables#);
variables.mcu.events.list[variables.mcu.events.ptr] = {
clock = variables.mcu.clock,
msg = arguments.msg
};
};
}
The rest didn’t need any reprogramming.
The only thing I observed: a closure does not seem to be a function, because isCustomFuntion (..) returns false. Maybe this should be changed, I dunno.
FYI, Martin Baur
Copy link to clipboard
Copied
tinu8805 wrote:
The only thing I observed: a closure does not seem to be a function, because isCustomFuntion (..) returns false. Maybe this should be changed, I dunno.
ColdFusion treats a closure more like a variable, rather than a user-defined function. That is why isCustomFunction(some_closure) returns false.
Copy link to clipboard
Copied
I justed learned that there is a new CF10 function: isClosure()