Home AMX User Forum NetLinx Studio

How Netlinx Works Internally

Now for the ulterior motive in the post - I think a thread on How Netlinx Works Internally as proposed by Fred/Marc is badly needed.
OK, let's start it then :-)
NI-700: average Mainline passes with single/simple counter - 16850 per second
Good
Netlinx is a multi-threaded application but the Master is capable of executing only a single thread (event, mainline, ...) at any given time?
The Netlinx OS is multi-threaded. It is difficult/impossible to manage so many fast I/O channels without interrupts of sorts (or co-processors).
The Netlinx PROGRAM is single-threaded or single-task, but supports cooperative multi-tasking. Once a code block exectutes, there is no stopping it. If an event contains an infinite loop, the program is locked (but not the master, you can still get to its web service, f.e.). "Events" are the cooperative multi-tasking tool. If you have some long processing to do (sorting a large list, say), you can send events to yourself and do a tiny bit at a time. That leaves time for other events to execute...
Mainline runs when no events are queued for handling?
True, I think. Mainline is run when "idle".
Once Mainline starts, it runs to completion (not pre-empted by events)?
True. This applies to events as well. Would be easy to test with an infinite loop in mainline.
While events are queued/pending, Mainline does not run (event queue has priority)?
True. We could try a simple string handler that sends the same string with a counter in mainline. I guess the counter would never go up...
Events are queued based on their arrival - there is no priority given to event types?
I don't think there is a priority in events, but there may be one (even implicit) in the mechanism that posts the events. Interrupts themselves generally have priority. It is linked to the hardware. So if the RS-232 hardware buffers are smaller than the Ethernet buffers, the RS232 has "priority". Even if no interrupts are used, the polling rate of the various devices is probably adapted to the hardware capabilities.
Can a busy system generating significant events potentially affect the accuracy of a TIMELINE event (long event queue, long event processing time)?
Certainly, and that was the start of this debate. Again, the infinite (or very long) loop test can be used to test that. Even if timelines do post an event, events ARE delayed by the ones before them in the queue. Maybe timeline events have priority over other ones, but if the currently running event (or mainline) does not relinquish control (see cooperative multitasking), the timeline event won't run on time.
WAITs, when encountered, are placed on a list for examination/execution at a later time?
Don't know. That was certainly true before Netlinx. I am not sure how much of the documentation on WAITs has been updated to reflect the internal mechansism. Given the API was the same than before, the explanation was good enough for understanding. I mean that it would make sense to have an internal mechanism which is the same than for timelines, and maybe there is, and maybe it's just the documentation that was left the same.
The WAIT list is processed as part of Mainline handling and only after Mainline has completed?
Don't know, but that could be easy to test as well.
If I stuff all of my feedback statements into Mainline and as a result it runs only once per second, what is the advantage to using a TIMELINE that triggers once per second for the same feedback statements instead of Mainline? :)
If there are a lot of events happening, the timeline will make sure the feedback happens. Using mainline feedback happens only if idle.
In any case, I would tend to prefer handling feedback in response to events and not as a "let's do them all" approach, which wastes resources, since you're testing/assigning stuff unnecessarily.
Futher, timelines have advantages over WAITs with respect to the accuracy of the timing of the event
If you're doing real time stuff, Netlinx is not the right tool (because of cooperative multitasking). If this is user interface/control stuff, then what it is so critical you need millisecond accuracy? I see WAIT delays on my system of 3 10th of a second or less... I am wondering in which kind of applications this makes any difference?
and the fact that WAITs can not operate on temporary (stack) data.
Huh? How does timelines fix that problem? WAITs cannot operate on the local stack vars of the block that contains them, but if some block starts a timeline, the timeline event cannot operate on the local stack vars of the block that started them EITHER. It's just more complex to manage a timeline, and it being another event, so obvious you can't use the local vars, that you naturally store whatever context info you need in some globally accessible variable. But the problem is the same: when the wait or timeline event occurs... the local variables that you'd like to use are gone (because the code block ended).

Comments welcomed!

Fred

Comments

  • DHawthorneDHawthorne Posts: 4,584
    I'm not convinced mainline "only" runs on idle. I think it's like the Axcent - it is always looping, and mainline is what checks the event queue; when there is an event in the queue (device channel event, button push, whatever), it then launches that even in a seperate thread that executes on it's own while mainline continues. This is where the difference occurs from Axcent: in Axcent, the event handling is necessarily part of the mainline loop itself. In NetLinx, your event queue is processed (checked for pending events and the appropriate threads launched), then whatever code you put in DEFINE_PROGRAM, then WAIT's are checked. It would be analagous to Main() in a Java application, or Mainline() in a C++ program (matter of fact, I suspect it is exactly that after interpretation by the compiler).
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally

    Fred,

    Thanks for getting the thread started.

    Regarding your comment on WAITs and stack variables, I should have been more clear. Any data required in the execution of a WAIT block must be global data. I did not mean to imply that TIMELINEs avoided this issue - they too do not have access to any local/stack data that may have been associated with the creation of the TIMELINE in the first place. Unique TIMELINE IDs that are relative to each other (sequence of IDs) can be used to convey an index into a global data structure where information for the timeline execution is maintained (or an index into a device array, ...). Named WAITs can be used in some cases to uniquely handle situations where shared global data is involved or a specific device needs to be identified. The purpose of the comment was to elicit thoughts from developers on how to circumvent some of the data restrictions that exist with tools like TIMELINEs and WAITs and not to impy that TIMELINEs solve all of the problems that exist with WAITs.

    On the Mainline and Event Handling thread question, check out AMX Tech Note 264. It is rather dated (July 2002) but was authored to address a question from the field regarding access to the underlying thread architecture in Netlinx. Some of the information is interesting although some of it may have changed since that time. I would have attached the Tech Note but it is not available in PDF format on the web site. However, see the attached GIF image which is a pictorial representation of the internal Netlinx flow which does imply as Fred stated that the Mainline thread runs only when the Event Handling thread has determined there are no events to process and when run, Mainline does run until completion.

    Another question:

    With the introduction of Duet and the JVM on the Master, what does this imply about the thread handling in Netlinx for firmware 3.0 and beyond? How is messaging or communication handled with the JVM (SNAPI and the SNAPI router)? Where does this fit into the architecture?
  • frthomasfrthomas Posts: 176
    DHawthorne wrote:
    I'm not convinced mainline "only" runs on idle.

    Try that:

    DATA_EVENT[somedev]{
    STRING: SEND_STRING somedev, 'whatever';
    }

    DEFINE_PROGRAM

    if (myvar ==0) SEND_STRING somedev, 'whatever';
    myvar++;


    and tell us if myvar ever goes above 1. If it does, then my understanding is only partial, agreed. But I guess it won't.

    Fred
  • frthomasfrthomas Posts: 176
    Regarding your comment on WAITs and stack variables, I should have been more clear. [..]. The purpose of the comment was to elicit thoughts from developers on how to circumvent some of the data restrictions that exist with tools like TIMELINEs and WAITs and not to impy that TIMELINEs solve all of the problems that exist with WAITs.
    If the intention of your comment was to elicit thoughts, then you'd concur the only advantage of WAITs wrt TIMELINES is precision (potentially). So again, what are you controlling that requires millisecond precision?

    It's not that I don't like TIMELINES, it's just that it seems everybody jumps around saying they are so much better than WAITs. I don't really understand why, and I think WAITs have some READABILITY advantages, so I play the pain-in-the-#$@ or more politely, the devil's advocate.
    On the Mainline and Event Handling thread question, check out AMX Tech Note 264.
    The Netlinx programming language manual contains a text version of the diagram, as well as the source of my explanation about 'idle'.
    With the introduction of Duet and the JVM on the Master, what does this imply about the thread handling in Netlinx for firmware 3.0 and beyond? How is messaging or communication handled with the JVM (SNAPI and the SNAPI router)? Where does this fit into the architecture?
    I am not familiar with SNAPI yet, so I don't know. But I guess AMX will keep a "looping" I/O thread (get inputs, deliver to program, accept program inputs, write outputs, loop) that feeds Java. If you let multiple threads access the I/O directly, now you need to deploy synchronization tools like semaphore and the like. And that's a tad complicated, even for trained grown up programmers.

    Fred
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally

    Fred,

    Ok, I can tell you are partial to WAITs :).

    Don't get me wrong, I use WAITs and TIMELINEs and both have their places. I am not in the camp that uses TIMELINEs to the exclusion of WAITs. I agree that while accuracy might be an advantage to TIMELINEs, if my actions take place within +- .5 to 1 second for a UI then everything is good. As a result, accuracy is not really my determining factor in whether or not to use a TIMELINE versus a WAIT. Perhaps an example helps me illustrate my thinking in choosing.

    Let's say I have an ONLINE event for an array of devices that need to be initialized but I need to wait for some time interval following the ONLINE event before sending commands to the device(s). I can't code a WAIT easily to handle this since DATA.DEVICE is not valid once the event has completed. Since the event handler deals with multiple devices, I can not use a single WAIT since the code block needs to handle a specific device and further multiple blocks may need to be active at the same time. I could use a named WAIT but then the name needs to be unique for each device I wish to initialize. This is cumbersome at best for large device arrays.

    Here is where I use a TIMELINE. I create an array of TIMELINE IDs (one for each device in the array). Using GET_LAST() in the ONLINE event, I select the TIMELINE ID and create a timeline to fire later. Once the timeline fires, I can determine the specific device to initialize by doing a table lookup or more simply by determining the relative TIMELINE ID (they are sequential). This easily identifies the device to be initialized and the SEND_COMMANDs are constructred accordingly. This is an example where (in my opinion) the code is simpler, more readable, and easier to maintain. Again, the choice had nothing to do with accuracy but with coding simplicity. The WAIT (in my opinion) is simply harder to use in this case. Of course, it could be coded using WAITs but that was not the point.

    Now, having said that, there are many places where WAIT can be used very effectively and I do so. I do not go out of my way to avoid WAITs replacing them with TIMELINEs. My criteria is simply based on which one leads to the most effective solution.

    I think WAITs versus TIMELINEs is a preference issue without a right or wrong answer. I like both mechanisms and I wish that a TIMELINE_CREATE would allow programmer specified data to be passed to the call and then populated into TIMELINE structure fields when the event fired. This would make TIMELINEs even more useful in some of my applications - similar to a X Windows toolkit callback. One thing I dislike about TIMELINEs is that a TIMELINE can not be created in one module and handled in another module (or in multiple modules) since Netlinx morphs TIMELINE IDs encountered in modules to be module specific. This is by design and while it has some positives, it makes TIMELINE events specific to the module in which the ID is defined and therefore a little different from other Netlinx event types.
  • frthomasfrthomas Posts: 176
    Let's say I have an ONLINE event for an array of devices that need to be initialized but I need to wait for some time interval following the ONLINE event before sending commands to the device(s). I can't code a WAIT easily to handle this since DATA.DEVICE is not valid once the event has completed. Since the event handler deals with multiple devices, I can not use a single WAIT since the code block needs to handle a specific device and further multiple blocks may need to be active at the same time. I could use a named WAIT but then the name needs to be unique for each device I wish to initialize. This is cumbersome at best for large device arrays.

    Here's how I do that with WAIT:
    DEFINE_TYPE
    
    STRUCT _DEVICEDATA {
        CHAR sType;
    	// + All the data you need to store wrt to your device
    
        LONG nNextOnline;  // delayed online... 
    }
    
    DEFINE_VARIABLE
    
    VOLATILE DEV gDev[DEV_COUNT]; // Holds our NL devices
    VOLATILE _DEVICEDATA gDevData[DEV_COUNT]; // Our device "database"
    VOLATILE LONG nextWakeUpOnline; // For the WAIT...
    
    DEFINE_FUNCTION DEV_NextOnlineWakeUp() {
        STACK_VAR CHAR i;
     
        nextWakeUpOnline = 0;
        FOR (i=1; i<=LENGTH_ARRAY(gDevData); i++) {
            IF (gDevData[i].nNextOnline){
                IF (gDevData[i].nNextOnline <= GET_TIMER) {
                    DoWhateverWithMeNow(i);
                    gDevData[i].nNextOnline=0;
                }
                ELSE {
                    IF (nextWakeUpOnline) {
                        nextWakeUpOnline = MIN_VALUE(nextWakeUpOnline, gDevData[i].nNextOnline);
                    }
                    ELSE {
                        nextWakeUpOnline = gDevData[i].nNextOnline;
                    }
                }
            }
        }
        CANCEL_WAIT 'DEV_NEXT_ONLINE';
        IF (nextWakeUpOnline) {
            nextWakeUpOnline = nextWakeUpOnline - GET_TIMER;
    
    	    SEND_STRING 0, "'Will be back in (', ITOA(nextWakeUpOnline),')'");
    
            WAIT nextWakeUpOnline 'DEV_NEXT_ONLINE' {
                DEV_NextOnlineWakeUp();
            }
        }
    }
    
    
    DEFINE_EVENT
    
    DATA_EVENT[gTPs_Dev] {
        ONLINE: {
    	STACK_VAR CHAR nDevIdx;
    	
    	nDevIdx = GET_LAST(gDev);
    
    	gDevData[nDevIdx].nNextOnline = GET_TIMER + 5;
    	
    	DEV_NextOnlineWakeUp();
    	}
    }
    

    Basically, you have a field in your data structure that tells you when you want that thing to "wake up". You then have a routine that finds the one closer to "wake up" and waits until then. When it wakes up, it does whatever needs to be done for all current or late devices, then calculates min and sleeps again if needed. In DoWhateverWithMeNow(), you have the entire struct at disposal so you could add fields to say what you want to do after the delay. This example is not coded for it, but DoWhateverWithMeNow could reassign nNextOnline for the next "hop".

    I have several similar constructions, and I have a debug output if (gDevData.nNextOnline < GET_TIMER), that is, if we're running late. And that's where I sometimes see delays of a couple of ticks.

    It works fine, there is ONE wait for as many devices you want, one single global for the WAIT, and it never wakes up for nothing. Of course the key is to always call the function whenever you change the gDevData.nNextOnline field...

    Hope you like it.

    Fred

    NB: The cancel wait is needed if for some reason you cancel a wake up (i.e., set nNextOnline to 0 and call the function) and there are none left to do => we must cancel the one we did last time we called the function.
  • Spire_JeffSpire_Jeff Posts: 1,917
    frthomas wrote:
    If the intention of your comment was to elicit thoughts, then you'd concur the only advantage of WAITs wrt TIMELINES is precision (potentially). So again, what are you controlling that requires millisecond precision?

    While at training in Tampa, I vaguely recall being told that a local amusement park had switched to the Netlinx controllers from some other controllers to control some of their rides specifically for the time precision. As memory serves, this was to control animatronics and such throughout the ride.

    One thing I just thought of regarding waits vs timelines is the ability to easily change the times when a timeline fires. I'm not sure that it would be more difficult to do this with WAITs, but I always find it more intuitive to do it with timelines. Also, timelines seem easier to setup a sequence that does not use regular intervals.

    As has been said before tho, most of this is purely a personal preference. While I may find timelines more intuitive or easier to read in code than WAITs, others will find the WAITs more appealing.

    Jeff
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally

    Fred,

    Thanks for the detailed code post on the use of WAITs - I am sure folks will find the code samples useful.

    Regarding WAITs and TIMELINEs, as I said earlier, I use both of them depending on the problem I am trying to solve. When people think of WAITs, they often only think about using them when a time condition is involved such as WAIT 10 and then perform some action. WAIT has several other variations including the ability to WAIT until a condition is satisfied such as a flag variable being set to a specific condition:
    WAIT_UNTIL (nFlag == 1) 'Name of Wait'
    {
       do something
    }
    
    Conditional WAITs, like timed WAITs, can be named or unnamed as needed. There are also other variations of WAIT that allow expressions to be combined with time conditions. All of these make WAIT more broadly useful than only the most common and simple use of WAIT which is a named or unnamed timed WAIT. WAITs can also be paused, restarted, and canceled provided they are named. In general, WAITs can be powerful tools.

    TIMELINEs too have some nice features like being able to reset them, restart them, reload them with new values, kill them, and check them to see if they are currently active.

    Reese
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally

    Marc,

    In your earlier post regarding Mainline execution (posted in the Timeline versus WAIT thread), you noted:
    3) mainline is NOT finished if a EVENT occurs! Mainline will be breaked and executed from the beginning(!!!) if no more EVENTs are in queue.

    Do we know this for sure? This could have some significant implications for code placed in Mainline on systems with frequent events particularly if Mainline execution is interrupted and is then restarted from the beginning and not continued from the interrupted point. In the AMX training class where we had a high level discussion regarding Netlinx control flow, we were led to believe (and the documentation implied this as well) that once Mainline execution started, it ran until completion (even if it had been interrupted).

    As Fred stated in the Netlinx Internals thread, perhaps this would be fairly easy to test. Creating an infinite loop in Mainline and then triggering events for processing would provide a data point. Note the following comment on WHILE statements from the Netlinx documentation:


    There is no timeout period as was the case with Axcess. The original intent of the timeout period was to prevent WHILE loops from locking out updates to/from the AXlink bus. The NetLinx Central Controller handles bus updates through a separate execution thread, thereby eliminating this potential problem.


    This would tend to imply that the Mainline thread is pended under certain conditions (handling interrupts) and perhaps to prevent Mainline from looping without relinquishing control (as Marc stated). The question in this case would be whether or not Mainline execution is resumed or restarted. It would appear some creative testing is in order to test the theories.

    I am going to cross-post to the Netlinx Internals thread - feel free to respond there or I can re-post your answer. Thanks,

    Reese
  • frthomasfrthomas Posts: 176
    Guys,

    The entire "user" program (what we write) can be (and is) interrupted and continued at any time by the Netlinx OS: to manage I/O, serve a web page, whatever.

    But if WITHIN the "user code", I don't think there is interruption. If an event can run in the middle of "mainline", that means given:
    BUTTON_EVENT[dev]{
      PUSH: my_global_var = 3;
    }
    DEFINE_PROGRAM
    IF(my_global_var == 2){
      do_something();
    }
    

    you cannot be sure that "my_global_var" is 2 when you "do_something()", because the event may have executed between the if and the "do_something". That is a major issue and would be highlighted in the documentation/training.
    The possible solution as Mark suggested is that if mainline is stopped, it restarts from the top.

    Nobody volonteers for some tests?
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally

    Fred,

    I agree with you - understanding what happens to Mainline with respect to events is important and could have design implications. That is why I raised the question back to Marc since perhaps he could provide some more details.

    To some extent, we are trying to guess or surmise the thread structure of Netlinx as well as how it handles priorities among those threads. I would be happy to do some testing but I am out of town this week without access to a Master. Perhaps next weekend I can check out some of the theories and we can move the discussion forward (unless someone from AMX Engineering wants to share some details with us before then or someone beats me to it).

    Reese
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally

    I finally got an opportunity to do some testing with regards to the various theories on the interaction between Mainline, Events and WAITs. Here are the basics of the test program:

    - Mainline test is triggered under a button event (so it can be repeated)
    - Mainline creates a TIMELINE to fire in 1/10th of a second
    - Mainline generates an immediate BUTTON_EVENT using DO_PUSH
    - Mainline generates a DATA_EVENT using SEND_STRING to virtual device
    - Mainline creates a WAIT code block to execute in 1/10th of a second
    - Mainline then does some feedback processing which results in a 15-16 second delay on an NXC-ME260 Master (mileage will vary depending on the Master type)
    - Mainline then completes and provides a report on what has transpired

    The WAIT code block and all of the Event handlers record a timestamp at the time they execute. Mainline records a separate timestamp when the WAIT is queued and when an event is generated. In addition to the above BUTTON and DATA events, I also used an ICSNet device to generate an ONLINE event so I could interject a real bus event into the test. Once Mainline was running and was in the delay section, I would connect the ICSNet device to the bus to generate the ONLINE event to see how it would be handled in this scenario. By examining the timestamps associated with event and WAIT block queueing versus their execution, certain conclusions can be drawn. In particular, it was easy to determine using this technique whether or not events had triggered or the WAIT code block had executed once Mainline had completed a single pass.

    Here are the conclusions from the testing:

    - Mainline runs to completion and is not interrupted for event processing (in other words, once Mainline execution has begun, it runs until it has completed). This does not imply that VxWorks/Netlinx does not pend Mainline internally for the queueing of events but only that any events that are queued are not handled until Mainline execution completes.

    - WAIT code blocks are executed following Mainline regardless of whether or not any events are queued for handling. In this case, the WAIT was delayed in execution by almost 15 seconds beyond the time it should have executed but note that it executed prior to the TIMELINE which was queued earlier because WAITs are handled first following Mainline regardless of pending events (even TIMELINE events).

    - Events are handled once Mainline has completed and all WAIT blocks have been executed following the Mainline pass. All queued events are processed before Mainline will be executed again.

    - Events are generally processed in the order they are received/queued but there are exceptions.

    - TIMELINE events are arranged in the event queue so that they will trigger as close as possible to the next timeline time value. Note however that a TIMELINE does not have priority over Mainline or WAIT code blocks and as the test demonstrates, a TIMELINE can be delayed significantly by a lengthy Mainline and/or WAIT code block. The TIMELINE created in the test should have fired in 1/10th of a second but instead fired almost 20 seconds later following 16 seconds of Mainline and 4 seconds of the WAIT code block.

    - The BUTTON_EVENT (DO_PUSH generated) and DATA_EVENT (SEND_STRING generated) fired in the order they were generated although both were delayed considerably by the length of Mainline and the WAIT code block.

    - The DATA_EVENT (device ONLINE generated) was given priority over all events except the TIMELINE_EVENT which of course was overdue for execution based on the timeline time trigger. Even though the ONLINE event was generated considerably later than the do push BUTTON or send string DATA events, the ONLINE event triggered prior to them. This seems to indicate some sort of preference or priority for ONLINE events - not sure if this extends to OFFLINE events as well.

    Note: the tests were performed using an NXC-ME260 (32Mb) Master running firmware version 2.31.137. An additional data point, on this Master, an average of 8300 iterations of mainline are performed every second when using the empty mainline test. Note this is almost 50% of the number of iterations on the average for the NI-700 (16850).

    I have some additional testing to perform using WHILE loops and LONG_WHILE loops so some additional conclusions can be drawn. I will post more information regarding the WHILE/LONG_WHILE tests and the conclusions later.

    Reese
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally (Testing Part II)

    I completed the additional testing using WHILE and LONG_WHILE loops (MEDIUM_WHILE is now treated as a WHILE by Netlinx so it was not tested). Here are the test basics:

    - First Test - Created a WHILE loop test that would not quit (conditional never satisfied). The WHILE loop from Mainline was trigged under a BUTTON_EVENT so it could be started anytime. A BUTTON_EVENT was also defined for the test that would cause the conditional to be satisfied thereby terminating the WHILE loop provided the BUTTON_EVENT was serviced by Netlinx once the WHILE loop was in execution (theoretically and based on observation from Testing Part I, the event would not be serviced and hence the loop would never be terminated).

    Test Result: The WHILE loop, once started, never completed and could not be terminated by the BUTTON_EVENT. An OFFLINE and ONLINE data event was also generated but the events were never serviced. The conclusion - once Mainline started the execution of the WHILE loop, it continued to execute it without servicing EVENTs or WAITs. In this case, Mainline began execution and since it never terminated, all other code was locked out. Moral of the story - do not use a loop in Mainline that may never terminate :). FOR loops would behave identically to the WHILE loop in terms of WAIT and Event handling.

    - Second Test - same as above except that LONG_WHILE was used instead of WHILE.

    Test Result - Different results - both WAITs and EVENTs were serviced at the end of an iteration of the LONG_WHILE loop. While the loop was designed to execute continuously, Netlinx did service WAITs and events following each iteration of the loop. One of the events queued was a BUTTON_EVENT which satisfied the loop conditional causing the LONG_WHILE loop to exit and for Mainline to essentially become an idle code path. Moral of the story - if you need a WHILE loop in Mainline and you want WAITs and EVENTs to continue to be processed in the event your loop may take a long time, use LONG_WHILE :).

    I am going to do some additional experimenting but I will post the test code later if someone is interested.

    Reese
  • frthomasfrthomas Posts: 176
    Reese,

    Excellent! I think it confirms what we thought all along.

    Maybe a suggestion: you considered mainline for a lot of testing. Should some of the tests be done in an event to confirm they basically have the same behaviour? (mainly, the Netlinx PL supports cooperative multitasking: use LONG_WHILE to be a good citizen).

    Re. WAITS, it would also be interresting to understand when WAITs inside events are handled (up to now we've discussed WAITs inside mainline, handled when mainline finishes). Question is are WAIT inside an event linked somehow to mainline execution? THAT would be a major drawback versus timelines...

    Fred
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally (Testing Part III)

    Fred,

    You read my mind regarding event testing. I had completed the part III tests focusing on events but was not able to post the results before your comment. Here are the tests and results:

    - Event Test 1 - I implemented a BUTTON_EVENT handler with a WHILE loop in it that could only be terminated via another EVENT or via Mainline. I really wanted to see if an event that essentially never completed processing would prevent other EVENTs from being serviced as well as preventing Mainline from executing.

    Result - As suspected, the BUTTON_EVENT executed continuously and could not be terminated via a second BUTTON_EVENT. Netlinx did not service the button event that would have cleared the WHILE loop flag and as a result, the 1st button event never stopped execution. Further, Mainline did not execute. The runaway button event essentially locked out all other processing including events and Mainline.

    - Event Test 2 - I implemented a 2nd BUTTON_EVENT this time using a LONG_WHILE instead of WHILE to see what effect that would have on both event and Mainline processing.

    Result - This is the more interesting of the test results so far. As expected, Netlinx serviced other events at the end of an iteration of the LONG_WHILE loop. As a result, a 2nd button event that cleared the loop flag was able to terminate the LONG_WHILE loop event and once this was done, processing returned to normal. The interesting point is that while other EVENTs could be serviced following an iteration of the LONG_WHILE loop, Mainline was not executed until all of the events were completed. In other words, Mainline did not execute even after LONG_WHILE iterations until the 2nd button event was used to terminate the 1st one at which point Mainline executed again (since no event processing remained). While this is intuitive given what we now know, the point was worth emphasizing.

    Regarding WAITs and Mainline - WAITs are processed as part of Mainline (following Mainline execution). In the above scenarios testing events, like Mainline, pending WAITs were not executed. Again, this is intuitive since WAIT execution is a function of Mainline and Mainline did not execute while the WHILE or LONG_WHILE loops were active within an event.

    I have a few more test scenarios to run and I will post results later. Let me know if anyone can think of any scenarios/questions that are unanswered at this point.

    Reese
  • frthomasfrthomas Posts: 176
    Regarding WAITs and Mainline - WAITs are processed as part of Mainline (following Mainline execution). In the above scenarios testing events, like Mainline, pending WAITs were not executed. Again, this is intuitive since WAIT execution is a function of Mainline and Mainline did not execute while the WHILE or LONG_WHILE loops were active within an event.

    Sorry to insist, did you try with a WAIT defined in an event? Sounds strange these would also be linked to mainline.

    Otherwise thanks a lot, very informative to see all of this confirmed. I guess a couple of readers have learned something, including me!

    Fred
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally (WAITs in Events and Mainline)

    Ok, here is some clarification regarding WAIT handling for the two basic scenarios - within an EVENT and within Mainline.

    - WAIT in an EVENT - If a WAIT is used within an EVENT, the WAIT will of course execute unless it is blocked by an infinite loop within the event. In other words, if you have an EVENT that defines a WAIT and that event also loops without termination (FOR or WHILE), the WAIT will not execute. If the event uses a LONG_WHILE, other events and WAITs are serviced at the end of loop iterations. Mainline does not however execute.

    - WAIT in Mainline - Here is a slight correction from previous posts. If Mainline loops as the result of a FOR/WHILE loop, then WAITs, EVENTs, and of course the rest of Mainline do not execute. If Mainline loops as the result of a LONG_WHILE, both EVENTs and WAITs are processed.

    In both cases, where an EVENT and Mainline were looping using LONG_WHILE constructs, I was able to effectively terminate the loops using WAITs or EVENTs. If FOR or WHILE constucts were used, then effectively all processing was halted whether it be EVENT or Mainline caused. Be careful with looping conditions in Mainline or in EVENTs - use LONG_WHILE if loops are needed and particularly where EVENT and WAIT processing are important.

    Based on the additional testing, it seems that WAITs enjoy some special treatment. It appears, but of course I do not know for a fact, that WAITs have their own queue as do EVENTs. Netlinx will execute Mainline, check for and process any WAITs pending at the conclusion of Mainline, check for and process any and all EVENTs pending at the conclusion of WAIT processing, and then repeat Mainline. However, as testing indicated, WAITs queued in an EVENT are handled (LONG_WHILE case) even when Mainline does not run. Could the conclusion be as simple as Mainline does not run when an EVENT is queued or an EVENT is being serviced but WAITs are handled provided the EVENT relinquishes control using LONG_WHILE?

    I will go back over the questions and summarize the answers later once everyone has a chance to digest the information and to challenge any of the conclusions.

    Reese
  • DHawthorneDHawthorne Posts: 4,584
    Looks like my conclusions were correct: mainline is really just a segment of the main() loop that processes the event and message queues. I think the real bottom line here is to keep as much code out of mainline as you possibly can.

    Which leads me to some observations about feedback. It seems to me there is an internal mechanism that keeps track of basic feedback items, like channel states and levels. If you do a simple equivalence, like this: [dvPanel, 1] = (<condition>), the channel output is not sent to the device unless <condition> changes. However, an explicit channel command like ON[dvPanel, 1] gets sent every time. You can get away with putting the first type of feedback in mainline; it's self-regulating. But putting the second generates an output event each pass, even though there is no change. That's a lot of events.

    SEND_COMMANDs and SEND_STRINGs are inherently explicit, and must get sent each pass, whether they represent a state change or not, so you also want to avoid them in mainline, unless there is a conditional regulating them. You do, however, have to consider the overhead of processing the conditional.

    SEND_LEVEL, it seems, is different. My observation is that output level events are like channels, they only fire if there is a change. So you can put a SEND_LEVEL wherever you like, and it only actually goes out if the level has changed.

    I haven't tested these observations very thoroughly, but in my never-ending quest to optimize message queue processing, this is how I have come to see it.
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally
    SEND_LEVEL, it seems, is different. My observation is that output level events are like channels, they only fire if there is a change. So you can put a SEND_LEVEL wherever you like, and it only actually goes out if the level has changed.

    Dave,

    This observation is definitely correct as I have seen it in practice and I have seen it documented. Netlinx tracks the levels of devices internally and will only send a level to a device in response to a SEND_LEVEL if the level to be sent is different from the current level of the device. This is an optimization since the SEND_LEVEL is not needed in that case and further it prevents needless LEVEL_EVENTs from being generated when the level did not actually change.
  • dchristodchristo Posts: 177
    WAIT in Mainline - Here is a slight correction from previous posts. If Mainline loops as the result of a FOR/WHILE loop, then WAITs, EVENTs, and of course the rest of Mainline do not execute. If Mainline loops as the result of a LONG_WHILE, both EVENTs and WAITs are processed.

    Ok, here's another scenario for you... Let's say you have a LONG_WHILE loop in mainline, and, as your testing shows, Events are processed at the end of each iteration of the LONG_WHILE. Now, what happens if a BUTTON_EVENT fires that does not have an associated Event Handler? Netlinx is supposed to execute Mainline to try to process that event as a PUSH (to be backwards compatible with Axcess code). Is this event now "lost" to the ether, since Mainline is already executing in a loop, or is this event somehow re-queued and processed the next iteration of Mainline?

    --D
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally
    Ok, here's another scenario for you... Let's say you have a LONG_WHILE loop in mainline, and, as your testing shows, Events are processed at the end of each iteration of the LONG_WHILE. Now, what happens if a BUTTON_EVENT fires that does not have an associated Event Handler? Netlinx is supposed to execute Mainline to try to process that event as a PUSH (to be backwards compatible with Axcess code). Is this event now "lost" to the ether, since Mainline is already executing in a loop, or is this event somehow re-queued and processed the next iteration of Mainline?

    Dave,

    Good question - I could speculate but why do that when it is easy to extend the test module to cover this case. I will try to extend the test this evening and post the result later.

    Reese
  • Reese JacobsReese Jacobs Posts: 347
    How Netlinx Works Internally (Part IV Testing)

    Dave,

    The answer to your question is that the PUSH event is not serviced if Mainline is looping on a LONG_WHILE when the event occurs and further the event is not queued for subsequent handling once Mainline returns to normal operation. In other words, the event is discarded.

    To test, I added a trigger event that would start Mainline on a LONG_WHILE loop. I added the PUSH handler to Mainline and also added a BUTTON_EVENT handler that would allow me to terminate the loop. Prior to activating the LONG_WHILE, the PUSH event was processed normally as one would expect. Once the LONG_WHILE loop started, the PUSH event in Mainline was no longer serviced. At that point, I used the BUTTON_EVENT to terminate the loop restoring Mainline to normal operation. All of the pending PUSH events which had been generated while the LONG_WHILE was executing were not serviced. Any new PUSH events generated once the LONG_WHILE had been terminated were processed normally.

    Reese
  • frthomasfrthomas Posts: 176
    Oops...
    Is this event now "lost" to the ether, since Mainline is already executing in a loop, or is this event somehow re-queued and processed the next iteration of Mainline?

    That test seems to indicate that the LONG WHILE code block is considered a Mainline iteration.

    And more generally, that for new projects at least, Mainline code should be examined attentively for its purpose, and anything not "idling" related should be moved to some other event handler.

    Fred
  • PUSH statements and events
    Dave,

    ... I added the PUSH handler to Mainline and also added a BUTTON_EVENT handler that would allow me to terminate the loop... Reese

    A tech note came out about event handlers and PUSH statements in the same code. I don't know the number but do remember it works like this:

    Before mainline is run, all the input hardware is scanned and any PUSH received since last time is recorded in memory - the fact that PUSH_DEVICE and PUSH_CHANNEL only hold one value implies only one PUSH can be handled per mainline, but that is conjecture by me. Multiple PUSH statements can exist in mainline for the same PUSH and all will work, so the PUSH is obviously stored for one pass of mainline and cleared at the end.
    However, the AMX tech note said that if an EVENT is defined that traps the PUSH, the PUSH statements will not work as the event handler clears the memory of that PUSH, which would suggest that events are handled after the hardware is scanned but before mainline runs.

    I prefer PUSH statements as they take up less room and can occur multiple times. I have a separate CALL for the PUSH's on each touch panel page, track which pages are in use and only run the CALL's for those pages that are open. Feedback to the buttons is in the same call, so there is no time wasted sending feedback to buttons that aren't visible.
  • A tech note came out about event handlers and PUSH statements in the same code. I don't know the number but do remember it works like this:

    Before mainline is run, all the input hardware is scanned and any PUSH received since last time is recorded in memory - the fact that PUSH_DEVICE and PUSH_CHANNEL only hold one value implies only one PUSH can be handled per mainline, but that is conjecture by me. Multiple PUSH statements can exist in mainline for the same PUSH and all will work, so the PUSH is obviously stored for one pass of mainline and cleared at the end.
    However, the AMX tech note said that if an EVENT is defined that traps the PUSH, the PUSH statements will not work as the event handler clears the memory of that PUSH, which would suggest that events are handled after the hardware is scanned but before mainline runs.

    I prefer PUSH statements as they take up less room and can occur multiple times. I have a separate CALL for the PUSH's on each touch panel page, track which pages are in use and only run the CALL's for those pages that are open. Feedback to the buttons is in the same call, so there is no time wasted sending feedback to buttons that aren't visible.

    It's correct - if there are a mainline PUSH and a BUTTON_EVENT to the same Device-Channel set, the PUSH is never executed (because BUTTON_EVENT has the higher priority and the PUSH message is removed from message queue after the EVENT was executed).

    It's possible to write multiple BUTTON_EVENTs to the same DEV/CHAN set. At compile time, all EVENTs with the same DEV/CHAN sets are put together to one Event, and the functions programmed inside the EVENTs are put together in the direction they are found in the code.

    BUTTON_EVENT[PANEL,1]
    {
    PUSH: { <Block C> }
    }
    BUTTON_EVENT[PANEL,1]
    {
    PUSH: { <Block A> }
    }
    BUTTON_EVENT[PANEL,1]
    {
    PUSH: { <Block B> }
    }


    Is internally put together to:

    BUTTON_EVENT[PANEL,1]
    {
    PUSH:
    {
    <Block C>
    <Block A>
    <Block B>
    }
    }


    The same is done internally with multiple PUSHs (in NetLinx and AXcess)

    Marc
Sign In or Register to comment.