11 Replies Latest reply on Jun 29, 2017 10:43 AM by Bruce Bullis

    Event Chaining

    e.d. Level 3

      Hi community,

       

      I need to understand what I'm conceptually doing wrong or what I'm confusing here.

      This experiment is to test the chaining of events, so basically Event1 shall trigger Event2, which is split, so Event2 shall trigger Event3 and then finish. Sounds a bit weird when describing it that way, so here's the basic code:

       

      index.html:

      <html>
        <head>
          <meta charset="utf-8">
          <script src="./lib/CSInterface.js"></script>
          <script src="./lib/jquery.js"></script>
          <script>
          $(document).ready(function() { 
              $("#EventChain").on("click", function(e){
                  e.preventDefault(); 
                  var cs = new CSInterface();
                  var message = "Event Listeners created.\nLet's go!\n\n";
                  cs.addEventListener("Test.Event1", function(evt) {
                      message += evt.data + "\n\nEvent 2 to occur...\n\n";
                      $("#textarea").text(message);
                      // console.log(evt.data);
                      cs.evalScript('$._ext_ed.e_chain2("This is Event 2.")');
                  });
                  cs.addEventListener("Test.Event2", function(evt) {
                      message += evt.data + "\n\nEvent 3 to occur...\n\n";
                      $("#textarea").text(message);
                      // console.log(evt.data);
                      cs.evalScript('$._ext_ed.e_chain3("This is Event 3.")');
                  });
                  cs.addEventListener("Test.Event3", function(evt) {
                      message += evt.data + "\n\n";
                      $("#textarea").text(message);
                      // console.log(evt.data);
                      cs.removeEventListener("Test.Event1");
                      cs.removeEventListener("Test.Event2");
                      cs.removeEventListener("Test.Event3");
                  });
                  $("#textarea").text(message);
                  cs.evalScript('$._ext_ed.e_chain1("This is Event 1.")');
              });
          });
          </script>
      </head>
      <body>
      <header></header>
      <section>
        <button id="EventChain">EventChain</button><br/>
        <textarea id="textarea" placeholder="Click the EventChain button!"></textarea>
      </section>
      <footer></footer>
      </body>
      </html>
      

       

      JSX:

      try {
          var xLib = new ExternalObject("lib:\PlugPlugExternalObject");
      } catch (e) {
          alert(e);
      }
      
      $._ext_ed={ 
          dispatchEventCEP : function (_type, _payload) {
              if (xLib) {
                  var eventObj = new CSXSEvent();
                  eventObj.type = _type;
                  eventObj.data = _payload;
                  eventObj.dispatch();
              } else {
                  alert ("PlugPlugExternalObject not loaded.", true);            
              }
          },
          
          e_chain1 : function (msg) {
              alert ("Message \""+msg+"\" received.", false)
              var eventType = "Test.Event1";
              $._ext_ed.dispatchEventCEP(eventType, msg)
          },
          
          e_chain2 : function (msg) {
              var eventType = "Test.Event2";
              $._ext_ed.dispatchEventCEP(eventType, msg + "part one");
              $.sleep(5000);
              $._ext_ed.dispatchEventCEP(eventType, msg + "part two");
          },
          
          e_chain3 : function (msg) {
              var eventType = "Test.Event3";
              $._ext_ed.dispatchEventCEP(eventType, msg)
          }
      }
      

       

       

      expected test result:

      Event Listeners created.

      Let's go!

       

      This is Event 1.

       

      Event 2 to occur...

       

      This is Event 2.part one

       

      Event 3 to occur...

       

      This is Event 3.

       

      This is Event 2.part two

       

      Event 3 to occur...

       

      This is Event 3.

       

       

      actual test result:

      Event Listeners created.

      Let's go!

       

      This is Event 1.

       

      Event 2 to occur...

       

      This is Event 2.part one

       

      Event 3 to occur...

       

      This is Event 2.part two

       

      Event 3 to occur...

       

      This is Event 3.

       

      This is Event 3.

       

       

      What's going on? Why is Event 3 not completely dispatched when Event 2 part one is obviously finished?

       

      It gets even worse when I click the button once more:

      Event Listeners created.

      Let's go!

       

      This is Event 1.

       

      Event 2 to occur...

       

      This is Event 2.part one

       

      Event 3 to occur...

       

      This is Event 2.part two

       

      Event 3 to occur...

       

      This is Event 2.part one

       

      Event 3 to occur...

       

      This is Event 2.part two

       

      Event 3 to occur...

       

      This is Event 3.

       

      This is Event 3.

       

      This is Event 3.

       

      This is Event 3.

       

      This is Event 3.

       

      This is Event 3.

       

      This is Event 3.

       

      This is Event 3.

       

      I'll try to find some useful reading on this myself but I'd appreciate useful comments!

       

      Cheers,

      e.d.

        • 1. Re: Event Chaining
          sberic Level 2

          e.d.  wrote

           

          What's going on? Why is Event 3 not completely dispatched when Event 2 part one is obviously finished?

          I took a look through the code and believe I've isolated the issue. The location at which things seem to break for you is the moment you call $.sleep(5000);. I think I understand your intuition here: sleep should emulate what the browser setTimeout function does. Unfortunately, sleep appears to completely pause processing of the ExtendScript thread. From the ESTK's Object Model Viewer:

          $.sleep (msecs: number )

          Core JavaScript Classes

          Suspends the calling thread for a number of milliseconds.

          During a sleep period, checks at 100 millisecond intervals to see whether the sleep should be terminated. This can happen if there is a break request, or if the script timeout has expired.

          msecs: Data Type: number

          Number of milliseconds to sleep.

          The key part is highlighted in bold red. Whereas setTimeout in browsers schedules the function to be called after a given delay, the ExtendScript sleep function will pause it entirely. This means that the events you throw to the ExtendScript context will not have a chance to execute before the second call to dispatchEventCEP in your e_chain2 function. By the time the ExtendScript processing thread has a chance to process the queued callbacks, both are already there waiting (one for over 5 seconds, the other likely on the order of milliseconds).

           

          I should point out that all classes in the Premiere Pro ExtendScript API have an instance function called setTimeout with the following description:

          App.setTimeout (eventName: string , function:any, milliseconds: number )

          Adobe Premiere Pro CC 2017 (11.0) Object Model

          eventName: Data Type: string

          function: Data Type: any

          milliseconds: Data Type: number

          Perhaps there's a way that you could use one of these functions to "schedule" your calls in the delayed fashion that it seems you intended?

           

          e.d.  wrote

           

          It gets even worse when I click the button once more:

          When you call cs.removeEventListener("EventName"), you are not actually removing the callback - it's most likely silently failing. You have to specify the function (and optionally the object) to remove the listener. To properly support this you should create named functions which you can specify in both add/removeEventListener calls, rather than anonymous inline functions.

           

          The documentation for add/removeEventListener specifies that you could specify an "object containing the method handler" as a third argument, but I'm not sure if the handler function must still be specified or not (I'm guessing yes)...

           

          I hope this is helpful!

          1 person found this helpful
          • 2. Re: Event Chaining
            e.d. Level 3

            Hi sberic,

             

            sberic  schrieb

             

            e.d.   wrote

             

            What's going on? Why is Event 3 not completely dispatched when Event 2 part one is obviously finished?

            I took a look through the code and believe I've isolated the issue. The location at which things seem to break for you is the moment you call $.sleep(5000);. I think I understand your intuition here: sleep should emulate what the browser setTimeout function does. Unfortunately, sleep appears to completely pause processing of the ExtendScript thread. From the ESTK's Object Model Viewer:

            $.sleep (msecs: number )

            Core JavaScript Classes

            Suspends the calling thread for a number of milliseconds.

            During a sleep period, checks at 100 millisecond intervals to see whether the sleep should be terminated. This can happen if there is a break request, or if the script timeout has expired.

            msecs: Data Type: number

            Number of milliseconds to sleep.

            The key part is highlighted in bold red. Whereas setTimeout in browsers schedules the function to be called after a given delay, the ExtendScript sleep function will pause it entirely. This means that the events you throw to the ExtendScript context will not have a chance to execute before the second call to dispatchEventCEP in your e_chain2 function. By the time the ExtendScript processing thread has a chance to process the queued callbacks, both are already there waiting (one for over 5 seconds, the other likely on the order of milliseconds).

             

            thank you, that one I hadn't looked up as potential source of trouble... which reminds us: Never assume!

             

            What's interesting though is that every blocking method (for instance also an alert dialog) suspends the entire event chain, which in my view should not happen! When I dispatch an event before the blocking happens, it should be triggering the handler, shouldn't it? What are your thoughts on this?

             

            When you call cs.removeEventListener("EventName"), you are not actually removing the callback - it's most likely silently failing. You have to specify the function (and optionally the object) to remove the listener. To properly support this you should create named functions which you can specify in both add/removeEventListener calls, rather than anonymous inline functions.

             

            The documentation for add/removeEventListener specifies that you could specify an "object containing the method handler" as a third argument, but I'm not sure if the handler function must still be specified or not (I'm guessing yes)...

            You're mostly right, one needs to specify the handler on removal as well, which is why it's not a good idea to use an anonymous function. Again, this seems to be mandatory, the object seems to be optional, considering the CSInterface.js implementation.

             

            Best,

            e.d.

            • 3. Re: Event Chaining
              sberic Level 2

              e.d.  wrote

               

              What's interesting though is that every blocking method (for instance also an alert dialog) suspends the entire event chain, which in my view should not happen! When I dispatch an event before the blocking happens, it should be triggering the handler, shouldn't it? What are your thoughts on this?

              I am unclear as to what you specifically mean by "should be triggering the handler", but I will take a guess and say that you mean "calling a registered CEP listener when an ExtendScript CSXSEvent event is dispatched." To be honest: I have no idea.

               

              Whether the event is triggered immediately simply queued up for later delivery when you call CSXSEvent.dispatch() is an implementation detail. Based on your explanation, I would guess that the function queues up the event for delivery during a separate processing phase on the thread. This would be similar to the way that calling setTimeout(func, 0) in a browser does not immediately call the function, even though it should have a 0ms delay - it is put in a queue for processing during the next frame. In the case of the CSXSEvent class, this would depend on the underlying implementation to which we do not have any insight. Perhaps Bruce Bullis could offer a more definitive answer as to what's going on with CSXSEvent.dispatch under the hood.

               

              That said, my thoughts are that this is not-at-all surprising. You are likely adding an event to a queue but blocking the thread before the thread has a chance to process that queue. That would be my best guess. Basically: avoid blocking calls if you can!

               

              e.d.  wrote

               

              You're mostly right, one needs to specify the handler on removal as well, which is why it's not a good idea to use an anonymous function. Again, this seems to be mandatory, the object seems to be optional, considering the CSInterface.js implementation.

              Well, the first paragraph I wrote says basically the exact same thing, simply worded differently. The second paragraph was pointing out a potential option to try as do not like to assume I understand something in the CEP/Premiere SDK, even when there's documentation.

               

              The reason I wrote the second paragraph is because I could see a situation wherein Adobe implemented a way to "remove all event listeners for a given object and a given 'type' by calling cs.removeEventListener("someEvent", null, myObj);". I have actually written event systems that have just such a mechanism - even ones that support removing all registered listeners for a specified object, across all event "types".

               

              That said, I will admit now that this makes far less sense given that the setup is mirrored in the cs.addEventListener function parameters and I can't see a way that it makes sense to call cs.addEventListener("someEvent", null, myObj); in a useful way. I've still not tested anything along these lines but I'd be willing to bet that calling the add/removeEventListener functions without specifying a function and specifying an object will simply fail silently. Some error handling either way (in this case or the one you encountered, with an undefined function handler) would probably be a big help in the future...

               

              I'm glad my answer was helpful. I similarly hope that this one is.

              • 4. Re: Event Chaining
                sberic Level 2

                Actually, now I'm doubly confused. Based on your writeup, it looks like the CSXSEvent.dispatch() did succeed... the "Event 3 to occur..." message comes out before the "This is Event 2.part two" message, right? Without seeing the timing on how these appear, I'd guess that the five second wait occurs between those two messages, with the rest appearing almost instantly. Is this not the case? If so, then the blocking call to sleep() would be blocking the CEP side handler call - in your words: when [you] dispatch an event before the blocking happens, it [does trigger] the handler (before the block-induced pause).

                 

                Unless my initial guess as to what you were describing was of base...

                • 5. Re: Event Chaining
                  e.d. Level 3

                  Hi sberic,

                   

                  no, the 5sec break happens after "Event 2 to occur...", so obviously the queue (if there is one) is affected by the blocking method as well, which indicates the JSX engine is single-threaded, right?

                   

                   

                  Bruce Bullis: I noticed in the sample Panel the PlugPlugObject call is somewhat different, for Windows you're using

                  var eoName = new ExternalObject('lib:PlugPlugExternalObject.dll')
                  

                  but it seems to make no difference if I use

                  var xLib = new ExternalObject('lib:\PlugPlugExternalObject')
                  

                  -> built-in fault tolerance / error handling? Or legacy compatibility?

                  • 6. Re: Event Chaining
                    sberic Level 2

                    e.d.  wrote

                     

                    no, the 5sec break happens after "Event 2 to occur...", so obviously the queue (if there is one) is affected by the blocking method as well

                    Ahh, that was unclear in the original post! Given this bit of information, I'd say that "Yes, it does appear that the queue is affected."

                     

                    e.d.  wrote

                     

                    the JSX engine is single-threaded, right?

                    Yes. The JSX (ExtendScript) engine is effectively ECMAScript 262 3rd Edition + API Extensions. Under the hood, it actually "uses an older version of SpiderMonkey" (source). According to the SpiderMonkey documentation (emphasis mine):

                    All JS code and most JSAPI calls run within a JSContext. The JSContext can be thought of as a machine that knows how to run JavaScript code, or as an abstraction of the notion of a thread. Exception handling, for example, is per-JSContext. Each JSContext must be used by only one thread at a time.

                    Precisely how Adobe apps (e.g. Premiere Pro) process the queued events does remain unclear, but I'd be willing to bet that all messages are processed once the current script event is processed (recall that you're actually in an event handler when you call $.sleep()). What's more, the JavaScript concurrency model seems to suggest that only a single event can be processed at a time, which likely includes the processing (delivery?) of posted events.

                     

                    At least this is what your test suggests... I think at this point you'd simply need an Adobe engineer who's familiar with the ExtendScript integration to tell you more definitively...

                    • 7. Re: Event Chaining
                      Bruce Bullis Adobe Employee

                      > built-in fault tolerance / error handling? Or legacy compatibility?

                       

                      That's what the code I was stealing did.

                      • 8. Re: Event Chaining
                        e.d. Level 3

                        Hi Bruce,

                         

                        That's what the code I was stealing did.

                        what does that mean exactly? Can you please expand on this? Currently it's

                        ambiguous to me.

                         

                        Generally speaking, my original question has not been answered. I would

                        appreciate concise information along lines like

                        "The engine will always queue events to be executed after the current

                        function's context has ended, which implies blocking calls will also block

                        event propagation. [...further consequences or implications or references

                        to standards documents...] ". Can you or someone else from your team please

                        provide this in a concise way?

                        • 9. Re: Event Chaining
                          sberic Level 2

                          I see that you unmarked the post as "Answered". That's a bit surprising given that the information provided should have helped unlock you given the problem as stated:

                          What's going on? Why is Event 3 not completely dispatched when Event 2 part one is obviously finished?

                          The answer, of course, being that $.sleep() is a blocking call that stops the thread from sending the message. Theoretically, this should have allowed you to adjust your script to work around the issue.

                           

                          (Not to mention your second question, which was similarly solved by that first post.)

                           

                          That said, I DO fully understand your desire to get a definitive understanding of the underlying event scheduling and processing system - I just think it's a different question. I too am very interested to better understand how the event model is actually working under the hood (when processing of the 'scheduled/dispatched' events occurs). To that end I'll invoke the good name of Bruce Bullis to hope that he can come in and answer this follow-up question (or bring someone in who can).

                          • 10. Re: Event Chaining
                            e.d. Level 3

                            Hey sberic,

                             

                            I didn't do this in order to strip you of your merit (I appreciate your input), but to give a signal to Adobe there's action required here.

                            My suspicion is that questions which appear to be answered are not being looked into any more.

                            I'll revert this as soon as there is an answer from the Adobe team...

                            • 11. Re: Event Chaining
                              Bruce Bullis Adobe Employee

                              [I've asked for expert input...]