Home AMX User Forum NetLinx Studio

Release Time

jjamesjjames Posts: 2,908
I'm working on a project that has 18 TPs, and am working on the volume control. I've played around with GET_LASTs and in this instance, if one panel is raising the volume, then another panel comes in an lowers its volume, the one being raised stop. So I'm now handling my volume control through TIMELINEs.

I'm running into on problem though. IF (and this is a HUGE IF) two (or more) panels release their buttons at the EXACT same time, it will only kill one of the two (or more) currently now active TIMELINES. My question is, is there a way to distinguish which panels were RELEASED if both were released at the same exact time?

I know this would probably be an extremely rare instance, however if this does happen, one of the timelines will not be killed and will continue to raise or lower the volume even after the user told it to stop.
«1

Comments

  • DHawthorneDHawthorne Posts: 4,584
    Maybe I'm missing something in your description (because I am only devoting half my brain to this while I upload a compile :)), but it's not possible for PUSH and RELEASE events to happen "exactly" at the same time. Events are queued, and the event handler runs through the queue on it's own thread, processing the next event in the queue on each pass. So, even though your events are milliseconds apart, GET_LAST operates on each in turn.
  • jjamesjjames Posts: 2,908
    That's what I thought BUT when running diagnostics and trying to release them at the same time - here's what I get:

    Line 11 :: Input Status:Released [10003:1:1] - Channel 24 - 14:14:01.359
    Line 12 :: Input Status:Released [10002:1:1] - Channel 24 - 14:14:01.359

    And one of the TIMELINEs is still running. . . which makes me believe that it can happen at the exact same time. Or I'm messing myself up with my own code.

    Here's my code:
    BUTTON_EVENT[dv_TP,VOLUME_BTNS]
    {
    	PUSH:
    	{
    		STACK_VAR INTEGER nPANEL
    		STACK_VAR INTEGER nIND
    		nIND = GET_LAST(VOLUME_BTNS)
    		nPANEL = GET_LAST(dv_TP)
    		
    		nVOL_RPT[nPANEL] = nIND
    		
    		
    		
    		SELECT
    		{
    			ACTIVE(PANEL[nPANEL].AUDIO_TYPE = ADA):
    				CALL 'ADJUST ADA VOLUME'(nIND,PANEL[nPANEL].ADA_OUTPUT)
    			
    			ACTIVE(PANEL[nPANEL].AUDIO_TYPE = DTR):
    				SEND_STRING PANEL[nPANEL].RECIEVER, "DTR_VOLUME[nIND],CR"
    		}
    	}
    	HOLD[2]:
    	{
    		
    		LOCAL_VAR LONG lTIME[1]	// TIME
    		lTIME[1] = 170
    		
    		FOR(nLOOP1=1;nLOOP1<=MAX_PANELS;nLOOP1++)
    			IF(nVOL_RPT[nLOOP1] AND (!TIMELINE_ACTIVE(nLOOP1)))
    				TIMELINE_CREATE(nLOOP1,lTIME,1,TIMELINE_RELATIVE,TIMELINE_REPEAT)	// CREATE TIMELINE
    	}
    	RELEASE:
    	{
    		LOCAL_VAR INTEGER nIND[10]
    		
    		FOR(nLOOP1=1;nLOOP1<=10;nLOOP1++)		// QUEUE GET_LAST RELEASES
    		{
    			IF(!nIND[nLOOP1])
    			{
    				nIND[nLOOP1] = GET_LAST(dv_TP)
    				BREAK
    			}
    		}
    		OFF[nVOL_RPT[nIND[1]]]
    		IF(TIMELINE_ACTIVE(nIND[1]))			// STOP TIMELINE
    			TIMELINE_KILL(nIND[1])
    		
    		FOR(nLOOP1=1;nLOOP1<=9;nLOOP1++)		// SHIFT QUEUE
    			nIND[nLOOP1] = nIND[nLOOP1+1]
    	}
    }
    
    TIMELINE_EVENT[01]	// VOLUME TIMELINES (1-18 - ONE PER PANEL)
    ...
    TIMELINE_EVENT[18]
    {
    	IF(nVOL_RPT[TIMELINE.ID]<>3)
    	{
    		SELECT
    		{
    			ACTIVE(PANEL[TIMELINE.ID].AUDIO_TYPE = ADA):
    				CALL 'ADJUST ADA VOLUME'(nVOL_RPT[TIMELINE.ID],PANEL[TIMELINE.ID].ADA_OUTPUT)
    			
    			ACTIVE(PANEL[TIMELINE.ID].AUDIO_TYPE = DTR):
    				SEND_STRING PANEL[TIMELINE.ID].RECIEVER, "DTR_VOLUME[nVOL_RPT[TIMELINE.ID]],CR"
    		}
    	}
    }
    
    
    
    
  • jjames wrote:
    I'm working on a project that has 18 TPs, and am working on the volume control. I've played around with GET_LASTs and in this instance, if one panel is raising the volume, then another panel comes in an lowers its volume, the one being raised stop. So I'm now handling my volume control through TIMELINEs.
    Using GET_LAST() in the HOLD?
  • DHawthorneDHawthorne Posts: 4,584
    Well, the timestamp suggests my first statement was wrong. Still thinking in Axcent terms, I'm afraid ... scratch that concept, it's clearly obsolete.

    But why are you using timelines at all? Make your HOLD[2] a HOLD[2, REPEAT] (again, unless I'm missing something because I am going to fast while occupied with other things) - that should do the same thing. I would bet the overhead of creating, testing, and killing the timelines is throwing things off as well.

    The other factor is that you are using a local variable as an index to the timeline, and if two threads are manipulating that variable at the same time, you are going to get unexpected results. It's the same code block for both events, so it's going to be the same variable on both releases.
  • jjamesjjames Posts: 2,908
    DHawthorne wrote:
    But why are you using timelines at all? Make your HOLD[2] a HOLD[2, REPEAT] (again, unless I'm missing something because I am going to fast while occupied with other things) - that should do the same thing. I would bet the overhead of creating, testing, and killing the timelines is throwing things off as well.
    If I did it this way, and used GET_LAST instead, the problem comes along that TP1 is controlling the volume. Now if TP2 comes in and controls its volume, TP1's HOLD & REPEAT is gone because of the GET_LAST. I could take the route of having this 18 times:
    BUTTON_EVENT[TP1,24] // VOLUME UP
    BUTTON_EVENT[TP1,25]// VOLUME DOWN
    BUTTON_EVENT[TP1,26] // VOLUME MUTE
    {
      PUSH:
        // CODE HERE
      HOLD[2,REPEAT]:
        // CODE HERE
    }
    
    .. but that's not really efficient, and if it can get the same result of 500 lines down to 50 . . I'll do it. With the TIMELINE approach, I'm able to QUEUE which GET_LAST happened and run / kill seperate volume timelines concurrently EXCEPT when it happens at the exact same time.

    The only unexpected results I've ever had is the KILL of the timeline. The LOCAL_VAR has no effect on it if it were a global variable. This whole thing works exactly how I want, except for the rare and essentially never-happening-odds of the volumes being released at EXACTLY the same time. . . which is why I need to know which panels were released together. I am thinking about taking the approach of device channels, and have it correspond that way.
  • pauldpauld Posts: 106
    I always make my volume control code a seperate button event block for each room/zone/tp. It may not be efficent, but it works and will not cause any problems down the road.

    Paul
  • jjamesjjames Posts: 2,908
    Sorry Paul,

    But I'm able to individualy control my volumes with only "one block" of code. I have 18 defined timelines, so essentially there are 18 code blocks. We all know we can run two or more timelines at once, but can't run two or more button events at once, hence the way I took.

    Perhaps I'm thinking too much outside the box and nobody wants to try to understand this approach, but it works - except for relases happening at the same exact time, which is what this thread is about, not how to do volume control.
  • pauldpauld Posts: 106
    No worries, I just thought that i would point that out. Using timelines for volume controls is nothing that i ever thought of, and i think that it is a clever way to get around a the button limitation.


    Paul
  • pauld wrote:
    I have run into this problem before. After trying many different ways of coding and talking to techsupport I was told, that if you have 1 block of code controling all of your volume controls, only 1 volume control/tp will work at any given time. No matter how you set it up, you can on do 1 thing at a time with the a block of code. So if you have 18 touchpanels, no matter how you define them( array,combine,seperate) that code block will only change/control 1 thing at a time.

    So in light of this I always make my volume control code a seperate button event block for each room/zone/tp. It may not be efficent, but it works and will not cause any problems down the road.

    Paul


    This works - one block of code, unlimited touchpanels, no timelines, no problems
    // User raising the volume?
    button_event[dvTP, AUD_TP_VOL_UP_BUTTON]
    button_event[dvTP, AUD_TP_VOL_DN_BUTTON]
    {
        push:
        {
            stack_var integer i
            stack_var integer zone
    	stack_var sinteger idir
    
            // Which touch panel is this?
            i = get_last(dvTP)
    
            // Which zone is this touch panel in?
            zone = audTpCurZone[i]
    
    	TO[BUTTON.INPUT]
            // Mute is now off
    	IF(audMute[zone])
    	    AUDsetMute(zone, 0)
            [dvTP[i], AUD_TP_MUSIC_MUTE_BUTTON] = 0
            if (button.input.channel == AUD_TP_VOL_UP_BUTTON)
    	    iDir = 1
            else
    	    iDir = -1
            AUDvolAdjust(zone,idir, 1)
        }
    
        hold [5, REPEAT]:
        {
            stack_var integer i
            stack_var integer zone
    	stack_var sinteger idir
            
            i = GetDev(BUTTON.INPUT.DEVICE,dvTP)
             
            // Which zone is this touch panel in?
            zone = audTpCurZone[i]
    
            if (button.input.channel == AUD_TP_VOL_UP_BUTTON)
    	    iDir = 1
            else
    	    iDir = -1 
    	AUDvolAdjust(zone,idir, 1)
    
        }
    }
    
  • jjamesjjames Posts: 2,908
    icraigie wrote:
    This works - one block of code, unlimited touchpanels, no timelines, no problems

    So an unlimited number of panels can change the volume at the same time? TP1 is pressing and holding, then TP2 comes along, presses and holds and they both keep on track? I would bet they don't.

    Your GET_LAST value will change to the last panel pressed, and only that one will repeat. I observed this problem right in front of me, which is why I went to the TIMELINE solution - but there's a problem with the release time being the same.

    It seems since the RELEASE event isn't being registered for both panels at the same time, I'll need to querry the button "state" somehow.
  • jjames wrote:
    So an unlimited number of panels can change the volume at the same time? TP1 is pressing and holding, then TP2 comes along, presses and holds and they both keep on track? I would bet they don't.

    Your GET_LAST value will change to the last panel pressed, and only that one will repeat. I observed this problem right in front of me, which is why I went to the TIMELINE solution - but there's a problem with the release time being the same.

    It seems since the RELEASE event isn't being registered for both panels at the same time, I'll need to querry the button "state" somehow.

    Take a closer look - code is implimented on close to 20 systems all with multiple touchpanels and it works.
  • DHawthorneDHawthorne Posts: 4,584
    I think that your problem is the GET_LAST function inside the FOR loop. Events code blocks are supposed to fire in their own threads, but presumably that thread isn't going to complete in zero time. If you have multiple events happening within the time that code block is running, GET_LAST is going to evaluate to the last one pressed that is in the event table, even if in mid-stream. Which means in some cases, it's not going to be valid depending on the timing. In other words, your variables and code might launch in independant threads, but the GET_LAST function itself is looking outside the thread.

    I construct my event like Ian does - a set of stack variables right at the top of the block, and I do all my GET_LAST operations right away to evaluate them. I don't stack the events themselves like that though - I always use a panel array, and a button array, then populate an index variable for each, then calculate everything else I need from that. Then I run my code on it. I've never had a conflict problem, and I always use REPEAT for volume control when the controlled device doesn't support ramping on/off commands. To be perfectly frank, I've never actually tested for said conflicts either :) - I just haven't run across them.
  • jjamesjjames Posts: 2,908
    icraigie wrote:
    Take a closer look - code is implimented on close to 20 systems all with multiple touchpanels and it works.

    Unless you're doing something special in your AUDvolAdjust or GetDev functions, I'm getting the same results . . . one touch panels is controlling ITS volume, and another comes along using the same stack variables . . . the LAST PRESSED TP now controlls its volume and cancelling out the other's actions. Exactly as I expected.

    Like I said, unless your GetDev is doing something special, it doesn't work. Using a GET_LAST alone in a REPEAT with multiple TPs doesn't seem to be the route I need when handling 18 touchpanels in one system.

    Ian, have you tested what I'm trying to say? You can easily emulate this if you have a processor and one panel. Just write it up, use the emulator in Studio, and use the touch panel. Try to have both panels control their own audio zones at the same time. One controlling up, the other controlling down, as the same time. And I'm not saying releasing or pressing at the same time, just being held at the same time. Let me know what you find, and if you see two RS-232 transmit lights flash at the same time, then you must have some special code in those functions.
  • jjames wrote:
    Unless you're doing something special in your AUDvolAdjust or GetDev functions, I'm getting the same results . . . one touch panels is controlling ITS volume, and another comes along using the same stack variables . . . the LAST PRESSED TP now controlls its volume and cancelling out the other's actions. Exactly as I expected.

    Like I said, unless your GetDev is doing something special, it doesn't work. Using a GET_LAST alone in a REPEAT with multiple TPs doesn't seem to be the route I need when handling 18 touchpanels in one system.

    Ian, have you tested what I'm trying to say? You can easily emulate this if you have a processor and one panel. Just write it up, use the emulator in Studio, and use the touch panel. Try to have both panels control their own audio zones at the same time. One controlling up, the other controlling down, as the same time. And I'm not saying releasing or pressing at the same time, just being held at the same time. Let me know what you find, and if you see two RS-232 transmit lights flash at the same time, then you must have some special code in those functions.


    Son of a biyatch - that's not the way it supposed to work. Once again lead astray by AMX propoganda. BTW, that code block is a slightly modified version fo what Design Express spits out, they used GET_LAST in the HOLD. Well I'm not going to worry about until someone complains - although that also means there is all sorts of PRESS/HOLD code out there that isn't working properly.

    Thanks


    Guess the solution isn't that bad - the PUSH can still be stacked, just have to add a separate HOLD block for each device.
    // User raising the volume?
    button_event[dvTP, AUD_TP_VOL_UP_BUTTON]
    button_event[dvTP, AUD_TP_VOL_DN_BUTTON]
    {
        push:
        {
            stack_var integer i
            stack_var integer zone
    	stack_var sinteger idir
    
            // Which touch panel is this?
            i = get_last(dvTP)
    
            // Which zone is this touch panel in?
            zone = audTpCurZone[i]
    
    	TO[BUTTON.INPUT]
            // Mute is now off
    	IF(audMute[zone])
    	    AUDsetMute(zone, 0)
            [dvTP[i], AUD_TP_MUSIC_MUTE_BUTTON] = 0
            if (button.input.channel == AUD_TP_VOL_UP_BUTTON)
    	    iDir = 1
            else
    	    iDir = -1
            AUDvolAdjust(zone,idir, 1)
        }
    }
    
    button_event[dvTP[1], AUD_TP_VOL_UP_BUTTON]
    button_event[dvTP[1], AUD_TP_VOL_DN_BUTTON]
    {
        hold [5, REPEAT]:
        {
            stack_var integer i
            stack_var integer zone
    	stack_var sinteger idir
            
            i = GetDev(BUTTON.INPUT.DEVICE,dvTP)
             
            // Which zone is this touch panel in?
            zone = audTpCurZone[i]
    
            if (button.input.channel == AUD_TP_VOL_UP_BUTTON)
    	    iDir = 1
            else
    	    iDir = -1 
    	AUDvolAdjust(zone,idir, 1)
    
        }
    }
    
    button_event[dvTP[2], AUD_TP_VOL_UP_BUTTON]
    button_event[dvTP[2], AUD_TP_VOL_DN_BUTTON]
    {
        hold [5, REPEAT]:
        {
            stack_var integer i
            stack_var integer zone
    	stack_var sinteger idir
            
            i = GetDev(BUTTON.INPUT.DEVICE,dvTP)
             
            // Which zone is this touch panel in?
            zone = audTpCurZone[i]
    
            if (button.input.channel == AUD_TP_VOL_UP_BUTTON)
    	    iDir = 1
            else
    	    iDir = -1 
    	AUDvolAdjust(zone,idir, 1)
    
        }
    }
    
  • jjamesjjames Posts: 2,908
    LMAO - I love the "Reason For Editing" - that's too good!

    Your solution was one that I've looked at before and shown to by a Master Programmer. But to copy that 18 times . . not very time efficient nor pretty, so the TIMELINE route was also shown to me by the Master Programmer and that's the way I took. However, releasing at the same time (or NEARLY thereof). . . well - I'm sounding like a broken record now - LOL.
  • jjamesjjames Posts: 2,908
    Got It!!!

    Jackpot! It's working now. I was searching through some threads that have GET_LAST mentioned in them, and someone mentioned they use BUTTON.INPUT.DEVICE, which returns the D:P:S. Well, taking it a step further, I'm now using BUTTON.INPUT.DEVICE.NUMBER and them subtracting 10000. Since I have my DEV array set up according to the device number, Panel 10001 is panel 1, panel 10010 is panel 10, and so on.

    Since I ALWAYS get the device's D:P:S in the release I'm using that. Even if they're released at the same EXACT time like below:
    Line 187 :: Input Status:Released [10001:1:1] - Channel 24 - 11:45:19.041
    Line 188 :: Input Status:Released [10002:1:1] - Channel 25 - 11:45:19.041

    I can still get the D:P:S. So here's the revised code that impliments TIMELINES for multiple touch panels to control their volume at the same time.
    BUTTON_EVENT[dv_TP,VOLUME_BTNS]
    {
    	PUSH:
    	{
    		STACK_VAR INTEGER nPANEL
    		STACK_VAR INTEGER nIND
    		nIND = GET_LAST(VOLUME_BTNS)
    		nPANEL = GET_LAST(dv_TP)
    		
    		nVOL_RPT[nPANEL] = nIND
    		
    		SELECT
    		{
    			ACTIVE(PANEL[nPANEL].AUDIO_TYPE = ADA):
    				CALL 'ADJUST ADA VOLUME'(nIND,PANEL[nPANEL].ADA_OUTPUT)
    			
    			ACTIVE(PANEL[nPANEL].AUDIO_TYPE = DTR):
    				SEND_STRING PANEL[nPANEL].RECIEVER, "DTR_VOLUME[nIND],CR"
    		}
    	}
    	HOLD[2]:
    	{
    		
    		LOCAL_VAR LONG lTIME[1]	// TIME
    		lTIME[1] = 170
    		
    		FOR(nLOOP1=1;nLOOP1<=MAX_PANELS;nLOOP1++)
    			IF(nVOL_RPT[nLOOP1] AND (!TIMELINE_ACTIVE(nLOOP1)))	// IF VOL VAR ACTIVE BUT NO TIMELINE . . .
    				TIMELINE_CREATE(nLOOP1,lTIME,1,TIMELINE_RELATIVE,TIMELINE_REPEAT)	// CREATE TIMELINE
    	}
    	RELEASE:
    	{
    		TIMELINE_KILL(ATOI(ITOA(BUTTON.INPUT.DEVICE.NUMBER))-10000)
    		OFF[nVOL_RPT[ATOI(ITOA(BUTTON.INPUT.DEVICE.NUMBER))-10000]]
    	}
    }
    
    
    TIMELINE_EVENT[01]	// VOLUME TIMELINES (1-18 - ONE PER PANEL)
    TIMELINE_EVENT[02]
    TIMELINE_EVENT[03]
    TIMELINE_EVENT[04]
    TIMELINE_EVENT[05]
    TIMELINE_EVENT[06]
    TIMELINE_EVENT[07]
    TIMELINE_EVENT[08]
    TIMELINE_EVENT[09]
    TIMELINE_EVENT[10]
    TIMELINE_EVENT[11]
    TIMELINE_EVENT[12]
    TIMELINE_EVENT[13]
    TIMELINE_EVENT[14]
    TIMELINE_EVENT[15]
    TIMELINE_EVENT[16]
    TIMELINE_EVENT[17]
    TIMELINE_EVENT[18]
    {
    	IF(nVOL_RPT[TIMELINE.ID]<>3)
    	{
    		SELECT
    		{
    			ACTIVE(PANEL[TIMELINE.ID].AUDIO_TYPE = ADA):
    				CALL 'ADJUST ADA VOLUME'(nVOL_RPT[TIMELINE.ID],PANEL[TIMELINE.ID].ADA_OUTPUT)
    			
    			ACTIVE(PANEL[TIMELINE.ID].AUDIO_TYPE = DTR):
    				SEND_STRING PANEL[TIMELINE.ID].RECIEVER, "DTR_VOLUME[nVOL_RPT[TIMELINE.ID]],CR"
    		}
    	}
    }
    
  • jjames wrote:
    TIMELINE_EVENT[01]	// VOLUME TIMELINES (1-18 - ONE PER PANEL)
    TIMELINE_EVENT[02]
    TIMELINE_EVENT[03]
    TIMELINE_EVENT[04]
    TIMELINE_EVENT[05]
    TIMELINE_EVENT[06]
    TIMELINE_EVENT[07]
    TIMELINE_EVENT[08]
    TIMELINE_EVENT[09]
    TIMELINE_EVENT[10]
    TIMELINE_EVENT[11]
    TIMELINE_EVENT[12]
    TIMELINE_EVENT[13]
    TIMELINE_EVENT[14]
    TIMELINE_EVENT[15]
    TIMELINE_EVENT[16]
    TIMELINE_EVENT[17]
    TIMELINE_EVENT[18]
    {
    	IF(nVOL_RPT[TIMELINE.ID]<>3)
    	{
    		SELECT
    		{
    			ACTIVE(PANEL[TIMELINE.ID].AUDIO_TYPE = ADA):
    				CALL 'ADJUST ADA VOLUME'(nVOL_RPT[TIMELINE.ID],PANEL[TIMELINE.ID].ADA_OUTPUT)
    			
    			ACTIVE(PANEL[TIMELINE.ID].AUDIO_TYPE = DTR):
    				SEND_STRING PANEL[TIMELINE.ID].RECIEVER, "DTR_VOLUME[nVOL_RPT[TIMELINE.ID]],CR"
    		}
    	}
    }
    



    So now all we need is for GET_LAST to work on an array of timelines!
  • jjamesjjames Posts: 2,908
    icraigie wrote:
    So now all we need is for GET_LAST to work on an array of timelines!
    Well, I don't think it has to do with the TIMELINEs. In my testing I had a string sent to the master telling me which TIMELINE it was killing. With the GET_LAST it was grabbing two instances of the event, however it would only grab ONE of the two (or more) panels. So I would see:

    "TIMELINE 2 KILLED @" at a specific time, then
    "TIMELINE 2 KILLED @" at a slightly different time.
    And of course an error saying timeline 2 was already dead.

    So I think the flake my lie in the GET_LAST. It appears that the master will always recieve the DPS of any event, and I can use information to my advantage with the way the panels have been set up.

    Moral of the story: if using timelines to control your volume - which will allow you to control multiple "audio zones" at the same time - use BUTTON.INPUT.DEVICE.NUMBER and set your panels up accordingly. GET_LAST is flaky.
  • frthomasfrthomas Posts: 176
    DHawthorne wrote:
    It's not possible for PUSH and RELEASE events to happen "exactly" at the same time. Events are queued, and the event handler runs through the queue on it's own thread, processing the next event in the queue on each pass. So, even though your events are milliseconds apart, GET_LAST operates on each in turn.

    Yes and no, it depends which "events" you're talking about.
    (a) it is physically impossible for a single physical button to be pushed and released at the same time
    (b) it is physically possible for two buttons on two different panels to be pushed at the same time...
    (c) but netlinx will process one PUSH event and then the other PUSH event sequentially (i.e. the Netlinx event processing does not occur simultaneously, regardless of the fact the physical events that triggered them could have).

    Note that the likeliness of (b) somewhat depends on the precision of the clock used for the measurement, milliseconds it seems in practice.

    Fred
  • frthomasfrthomas Posts: 176
    DHawthorne wrote:
    The other factor is that you are using a local variable as an index to the timeline, and if two threads are manipulating that variable at the same time, you are going to get unexpected results. It's the same code block for both events, so it's going to be the same variable on both releases.

    There is no *concurrent* "threads" in Netlinx. The local variable cannot be manipulated by two threads at the same time. The local variable will be manipulated to handle the first event, then manipulated for the second.

    Fred
  • frthomasfrthomas Posts: 176
    jjames wrote:
    the LAST PRESSED TP now controlls its volume and cancelling out the other's actions.

    Well, the doc says so... "GET_LAST returns the index of the array element that MOST RECENTLY caused an event handler to be triggered".

    Which seems to mean that events occur, are queued and processed in their arrival order. So OLD event occurs and before the handler can fire, NEW event occurs and is in the queue. When the handler fires and GET_LAST is called... it returns NEW event. Accessing OLD event requires using the full event details as jjames explains...

    Logically, this should have two consequences:

    - It does not matter if events occur AT THE SAME TIME, it is enough for two (or more) of them having the same handler to arrive before the event handler for the first one can be executed. Once it is, GET_LAST will return the last event, and this for each event handler execution. (like if the same button was pressed multiple times)

    - It should also mean that if two user START (we've only been talking about simulatenous releases so far) to control the volume "at the same time" (given the above definition), only one controls its volume, even with the current jjames code (why limit the use of full D:P:S info to RELEASE and not to HOLD and PUSH as well?)

    Interresting!

    Fred
  • GET_LAST() Function in Event Handling

    Ok, I am going to jump out on the limb here so start chopping....

    If GET_LAST() suffers from the limitation that this thread is discussing, then there is probably a lot of broken code out there. I did some testing and I can not verify based on those tests that the conclusions drawn here are correct.

    First, I don't interpret the statement "GET_LAST returns the index of the array element that MOST RECENTLY caused an event handler to be triggered" the same as Fred did. I interpret this to mean that GET_LAST() returns the index of the element in the array that resulted in the current event handler invocation and not the last event queued by Netlinx (in case there are events queued that have not been processed). To me, triggered has a different connotation than queued.

    Second, this is fairly easy to test. I created a small test program. In it, I defined an array of devices and buttons for use in a BUTTON_EVENT. In the DEFINE_START section, I queued multiple BUTTON_EVENTs using DO_PUSH and then I did the same in DEFINE_PROGRAM. I used debug output messages to timestamp the generation of the events versus the handling of the events in BUTTON_EVENT. In other words, taking advantage of the fact that neither DEFINE_START nor DEFINE_PROGRAM are preempted to run event handlers (see previous threads on this topic), multiple events were queued. Within the BUTTON_EVENT, I used GET_LAST() on the device array and the button array and it worked every time based on the event being processed. The function was not influenced by the last event in the queue that had not yet been processed.

    Third and last, I think GET_LAST() is a pretty simple runtime function that does just what Jeremiah is doing in code without GET_LAST(). When GET_LAST() is called with a DEV array argument, it searches the array for a match on BUTTON.INPUT.DEVICE, CHANNEL.DEVICE, etc. based on the event handling data structure appropriate to that event type. If GET_LAST() is called with an INTEGER argument, it searches the device data structure to match BUTTON.INPUT.CHANNEL, etc. for a match and returns the index. This is why I do not believe that GET_LAST() is affected by queued events. I think GET_LAST() executes in the event handler when called and is basically a search and match routine using the BUTTON, CHANNEL, DATA, and LEVEL data structures to identify the array entry.

    We know there are issues with GET_LAST() on LEVEL_EVENTs but only on the DEV array and not on the level array. Other than that, I have never known GET_LAST() to fail in the way Jeremiah claims or that the thread speculates is occurring. My reason for prolonging the discussion is I really do want to know if it is broken since I use GET_LAST() religously and in complex systems where multiple events being queued at the same time is the norm and not the exception. As I said, I have never witnessed this problem except with LEVEL events (which is a known problem) and my test programs do not support the conclusions drawn thusfar. If it is indeed a problem, I need to work on some code changes pretty soon!

    Anyone else want to do some testing or if you have suggestions for test scenarios, I would be happy to run them.

    Hoping for more insight ....

    Reese
  • frthomasfrthomas Posts: 176
    First, I don't interpret the statement "GET_LAST returns the index of the array element that MOST RECENTLY caused an event handler to be triggered" the same as Fred did. I interpret this to mean that GET_LAST() returns the index of the element in the array that resulted in the current event handler invocation and not the last event queued by Netlinx (in case there are events queued that have not been processed). To me, triggered has a different connotation than queued.

    Reese, your interpretation is what I thought and then I just ran the thread ideas against GET_LAST definition. I am not saying my theory is correct. This is just more potential meat for the "How Netlinx really works" thread :-)

    Re interpretation, the name of the function contains LAST and the definition contains MOST RECENTLY. Yet you ignore all of these references to time and conclude it means "returns the index of THE element that triggered the event".

    My point is that all these references to time probably mean something. And given on the thread, I was conjecturing what...
    I used debug output messages to timestamp the generation of the events versus the handling of the events in BUTTON_EVENT.

    Given my post, it's only natural I try to find faults in the testing method :-)

    I am not sure you timestamped the event creation. You timestamped the execution of the function you believe generated an event (DO_PUSH). In diagnostics, did you get the equivalent of the logs above?

    DO_PUSH is a special kind of beast that could well be queued for itself and/or special cased.

    I'd try it with a real device and an event handler that takes ages to finish its execution. So you have plenty of physical time to push the button multiple times and see how each iteration reacts. I would also, as mentioned above, observe the diagnostics to look out for "queued events".

    Last point is ALL of DO_PUSH events have the same source (the Netlinx master). The "problem" may be only observable with multiple event sources (pure conjecture) (i.e. GET_LAST only gets confused when multiple sources are involved and/or when sources other than the master itself are involved).
    Third and last, I think GET_LAST() is a pretty simple runtime function that does just what Jeremiah is doing in code without GET_LAST()

    Yes, but again if that is the case, why did they name it get_LAST? Therein lies my doubts...

    Fred
  • dchristodchristo Posts: 177
    I'll have to agree with Reese on this one. In the statement "...that MOST RECENTLY caused an event handler to be triggered", I think that the word "triggered" is the key word. An event message that has been queued has not triggered an event yet. I think it only triggers an event when the interpreter pops it off the queue and processes it.

    --D
  • GET_LAST() - the meaning of LAST

    Fred,

    I am not denying the logic in your conclusion regarding the naming of the GET_LAST() function but a function that returns the index of the last DEV or button/channel/level for an event other than the one currently being handled in the event handler is of questionable value in my mind. Given that you have no idea what was the last occurring event much less its relationship to the event being handled, it is not clear that GET_LAST() would serve any purpose (I should say a useful purpose).

    Let me offer some counter-examples in AMX documentation to substantiate why I think GET_LAST() works correctly (for everything except DEV and DEVLEV arrays in LEVEL EVENTS).

    Consider the quotes from TechNote 383:
    GET_LAST() can be used to determine which index of an array (any type) caused the event to fire. The GET_LAST() function is very useful when running an event using an array as a parameter. GET_LAST() returns an index value; starting at 1, for the element that triggered the event.
    Further, consider the direct quote from TechNote 486 (the one that deals with the GET_LAST() problems on LEVEL_EVENTs):
    When used with a DEVLEV or DEV array, GET_LAST does not work correctly. It should return the number of the array element that triggers the event.
    Regarding the test methodology comments, you have a valid point. My test was relatively simple and self-contained. I will expand the test case tomorrow to include real events mixed with DO_PUSH() generated events. It is simple enough to introduce external touchpanel button events and even channel events generated from the local master. Since it is quite easy to cause mainline to run for an extended period of time (using what we learned from How Netlinx Works Internally), it is simple enough to generate events and ensure they are queued before the first handler is triggered. I have a test routine that takes the GET_LAST() value for the DEV array and the BUTTON or CHANNEL array and compares it to the expected value as determined by scanning the DEV or INTEGER arrays for a match based on the EVENT data structure (much like what Jeremiah is using now in place of GET_LAST). It will be very easy to validate that GET_LAST() is doing what I expect (or hope) it is doing.

    I will post up some results on Friday when I have a chance to test more.

    Reese
  • GET_LAST() - It Works

    Fred,

    I modified my tests to add the conditions you noted. In particular, I stalled Mainline long enough with floating point operations and string manipulations to give me a 20 second window in which to queue externally generated button events. From the time mainline began execution until it completed and events could be serviced, I generated between 15 and 20 BUTTON_EVENTs from a local touchpanel and from a touchpanel connected to a remote master using Master to Master. In addition, I left the DO_PUSH() event generation statements in the code block and also added CHANNEL_EVENTs to the same code block so I could test GET_LAST() in CHANNEL_EVENTs and also see if another event type would have an adverse effect on GET_LAST() in the BUTTON_EVENT. I verified using Diagnostics timestamps that events were being queued prior to the event handler firing so that by the time the 1st BUTTON_EVENT was serviced, multiple BUTTON and CHANNEL events had been queued (each event used a unique button or channel and in some cases a unique device as well).

    In all cases, GET_LAST() performed flawlessly working exactly as I expected (or hoped) it would work. I remain open to being convinced that GET_LAST() does not work properly but at this point, I firmly believe that GET_LAST() returns the index based on the currently triggered event being processed in the event handler and that GET_LAST() is not influenced in any way by any queued events that may have occurred after the event you are currently processing in the event handler.

    Again, DEV and DEVLEV handling in LEVEL_EVENTs is broken -- that is the exception to the GET_LAST() statements above. Let me know if you think there is some other test that needs to be attempted. At this point, it might be worthwhile to take a closer look at Jeremiah's original code block to see if there is another explanation for what was happening.

    Reese
  • From extensive experience...

    ...Get_Last will always work as advertised for the Push and Release sections for a Button_Event. NEVER use Get_Last in a Hold or Level_Event.

    Watch for another (better?) volume control solution later today.

    Brian
  • DHawthorneDHawthorne Posts: 4,584
    B_Clements wrote:
    ...Get_Last will always work as advertised for the Push and Release sections for a Button_Event. NEVER use Get_Last in a Hold or Level_Event.

    Watch for another (better?) volume control solution later today.

    Brian
    That suggests it does not, in fact, use the device event that triggered the handler, but reads it from the stack. Since PUSH and RELEASE are for all practical intents instantaneous, it's not an issue for them. But a HOLD handler may persist through several other events. As noted, if this is the case, it could potentially break a lot of code, and we just haven't noticed it because the circumstances can be pretty rare. Yet, I have never seen a problem using HOLD, and I have run some complex multi-panel systems. However, I always creat a holding variable, and dump the results of GET_LAST to the variable immediately. I have never trusted it would produce the same result if used multiple times in the same handler. Perhaps that is the workaround.
  • I have always treated a Hold in a Button_Event as I would a Wait. That is only one instance (per panel) per button.

    You can also safely use the Get_Last function in a Channel_Event.

    I will post my volume control solution in either Tips and Tricks or ACE. I just have not had the time yet today.
  • Here is the example.

    I posted my code example at http://www.amxforums.com/showthread.php?p=8676#post8676.

    I am using Timelines but, no Hold is required.
Sign In or Register to comment.