Adobe-CEP / CEP-Resources

Tools and documentation for building Creative Cloud app extensions with CEP
https://www.adobe.io/apis/creativecloud/cep.html
1.62k stars 824 forks source link

[BUG] The CSInterface.evalScript API blocks on macOS #163

Open ericdrobinson opened 6 years ago

ericdrobinson commented 6 years ago

The CSInterface.evalScript API is the method by which users are enabled to issue commands in the ExtendScript context from the panel (JavaScript) context. On Windows, this function runs asynchronously, as per expectation. On macOS, the function runs synchronously, counter to expectation.

While the callbacks are triggered in the same manner, on Windows, the calling JavaScript is allowed to finish processing prior to triggering the ExtendScript context. On macOS, the ExtendScript context is started and runs to completion before the JavaScript context that spawned it can run to completion.

Please see this forum post for more information.

dkstevekwak commented 6 years ago

@ericdrobinson I used your script and was able to replicate the issue on Chrome debugger. However, if you insert the same script into the panel code itself, you will get the correct sequence of console.log statements like below. Have you tried running the same in the panel rather than in the debugger?

Panel Start: 52:51.180
Panel End: 52:51.187
JSX Start: 52:51.187
JSX End: 52:54.213
ericdrobinson commented 6 years ago

@dkstevekwak What application are you testing in (app and version)? What operating system?

I have not yet tried running the code in the panel directly yet, no. It appears that others have, however, and their experiences appear to be inline with the bug report.

dkstevekwak commented 6 years ago

@ericdrobinson I've tested it in Photoshop 2018 in Mac OS. Since I saw your reply, I've also tested it in InDesign and Illustrator. Interestingly, while the ExtendScript code does not block in PS, it does block the flow in ID and IA.

bbb999 commented 6 years ago

[Pretty sure it depends on how each host app, implements ExtendScript...]

ericdrobinson commented 6 years ago

@dkstevekwak Interesting! Good to know that PS on macOS may be an exception!

ericdrobinson commented 6 years ago

[Pretty sure it depends on how each host app, implements ExtendScript...]

@bbb999 It sounds as though there is a way to implement ExtendScript such that the calls work as expected in macOS (see: Photoshop). Perhaps implementers in the other apps should take a look at what the PhotoShop implementers did...?

bbb999 commented 6 years ago

What wonderful integrations are you prevented from creating, by PPro's selfish unwillingness to support asynchronous scripting calls?

Also, the Photoshop team's eyes start twitching, whenever they read PhotoShop with CamelCaps. ;)

ericdrobinson commented 6 years ago

What wonderful integrations are you prevented from creating, by PPro's selfish unwillingness to support asynchronous scripting calls?

None that come to mind, at the moment. This is more for performance and consistency. I have no idea how complex the weeds are within the various ExtendScript integrations so its easy to make suggestions. For all I know, it could simply be a configuration setting (though very likely that is not the case)...

Also, the Photoshop team's eyes start twitching, whenever they read PhotoShop with CamelCaps. ;)

Hah, fair enough! I've addressed that in my previous comment ;) I spend very little time in Ps [😜] - currently, I'm mostly poking around Premierepro [🤣]

tjx666 commented 2 years ago

This issue still exits in ps2021, I can'd find a way to implements async evalScript...

ErinFinnegan commented 2 years ago

Based on the previous conversation, I suspect this is working as designed. Maybe @amandahuarng could weigh in on this.

ericdrobinson commented 2 years ago

@tjx666 What do you mean by "async evalScript"? Are you looking for a way to await EvalScript? Or are you looking for a way to offload a call to evalScript and have it work asynchronously in the event of "heavy processing on the ExtendScript side"?

tjx666 commented 2 years ago

I have 2 questions:

  1. Is there any api like After Effects $.scheduleTask(evalString, delay, isLoop)? like setTimeout in V8 to run async code.

  2. Seems Photoshop2021 mac doesn't support parallel run extendscript and render CEP browser. But I know Affect Effects can do that. When I run extendscript, the CEP panel will stop render. For this, I can't implements render a progress bar when execute extendscript task in Photoshop.

@ericdrobinson

ericdrobinson commented 2 years ago

@tjx666 I assume that the answer to my questions is that you're looking to "implement a progress bar when executing ExtendScript asks in Photoshop". Got it. I will now answer your questions to the best of my ability:

Is there any api like After Effects $.scheduleTask(evalString, delay, isLoop)? like setTimeout in V8 to run async code.

I have no idea. I am not familiar with the Photoshop ExtendScript API. I did a quick search of the API as listed here but didn't see anything promising.

Seems Photoshop2021 mac doesn't support parallel run extendscript and render CEP browser. But I know Affect Effects can do that. When I run extendscript, the CEP panel will stop render. For this, I can't implements render a progress bar when execute extendscript task in Photoshop.

This sounds likely. Different host applications run their ExtendScript engines in different ways. Long running ExtendScript functions do not block in AE or PPro as far as I'm aware. By deault, InDesign will appear to lock up while ExtendScript runs (there is apparently a flag that you can set somewhere - possibly via the C++ SDK? - that allows the engine to run separate from the main thread... I am not familiar with the details). Each host application implements ExtendScript differently and this may simply be "how things work" with Photoshop.

If you know of a specific extension that appears to have a working solution, you might try reaching out to the extension's developer to see if they'd be kind enough to share their solution.

tjx666 commented 2 years ago

@ericdrobinson Maybe only the developers of photoshop extendscript engin know about the questions clearly. I searched a lot but can't find the solution. My much extendscript knowledge is from opensource project bodymovin. There seems no such mature project in photoshop field.

ErinFinnegan commented 2 years ago

You may also trying this in either of the following two forums:

Newer UXP APIs: https://forums.creativeclouddeveloper.com/c/photoshop/63

CEP/Scripting/SDK: https://community.adobe.com/t5/photoshop-ecosystem/ct-p/ct-photoshop?page=1&sort=latest_replies&lang=all&tabid=all&topics=label-sdk%2Clabel-actionsandscripting

Or in adobedevs.slack.com ... I'll send an invite.

zwettemaan commented 2 years ago

I'm not immersed into that at all, but a technique I have used in the past in various difficult environments to use a separate dev tool (e.g. Xojo) to make a little standalone app that shows a global frontmost dialog with just a progress bar.

Launch the external progress app at the start of the script, and have the script write out little progress markers to some temp file, and the progress bar pick those up and progress. Crude.

I like Xojo, but know it works in other environments too.

I like Xojo for that because I am used to it, it is cross-platform (Mac/Win/Lin...), the resulting apps don't look weird and out of place in the target OS, and Xojo has that out of the box as a window type, so all it needs is a few lines of code

These dialogs can be frontmost (i.e. remain in front of any other windows). I normally add in additional code to show/hide the dialog when the associated app is not frontmost, but that's more like 'making it more polished'.

Inventsable commented 2 years ago

@tjx666 I've run into this issue often in multiple apps even on Windows:

The script here for the second button being:

function testSingle() {
  for (var i = 1; i < 10; i++) {
    $.sleep(500);
    updateProgressBar(i * 10);
  }
  return "JSX execution even blocks PlugPlug events, this is the return from script";
}
function updateProgressBar(value) {
  function JSXEvent(payload, eventType) {
    try {
      var xLib = new ExternalObject("lib:PlugPlugExternalObject");
    } catch (e) {}
    if (xLib) {
      var eventObj = new CSXSEvent();
      eventObj.type = eventType;
      eventObj.data = payload;
      eventObj.dispatch();
    }
    return;
  }
  JSXEvent(value, "progressbar");
}

My solution is to do as little in JSX as I can possibly get away with, even to the extent that I query document properties then immediately return them to CEP and do all calculation there until I have a chunked array of JSON data to process via JSX. Doing anything with batches or that requires a progress bar I split into chunks and send individual async evalScript calls instead of all through a single one. The third button in question is doing something similar to what chunks would be, even though the second and third buttons are sleeping the exact same amount of time overall and have virtually identical code:

// Looping code is in CEP, not here in JSX:
function testChunk(i) {
  $.sleep(500);
  return +i * 10;
}

It's far faster and smoother to send 10 evalScript calls to handle 50 files (or even 50 calls, whenever large operations are sent they can be chunked into smaller operations and processed individually) than it is for 1 evalScript call to handle the entire process though there are some ScriptUI workarounds with app.redraw() hacking that can allow you to simulate a realtime scripting progress bar. If you try sending CSEvents or external events, it's still not asynchronous and as shown in the above gif will send after the function returns altogether.

ericdrobinson commented 2 years ago

@tjx666 A separate conversation on the Slack community that @ErinFinnegan mentioned brought up some interesting options that you may look into. Specifically:

  1. app.doProgress()
  2. app.doForcedProgress()
  3. app.doProgressSegmentTask()
  4. app.doProgressSubTask()
  5. app.doProgressTask()
  6. app.updateProgress()

These APIs apparently allow you to specify ExtendScript work to do (as a string) and then show a progress bar along with it.

This forum post contains an example of how you might use app.doProgress() with app.updateProgress() to accomplish the goal.

tjx666 commented 2 years ago

You solution is try to to all the thing using browser script, and split the big task to smaller task. For better fluency, you may need to wrap the task in setTimeout or run in the web workers.

To implememt that, I need to refactor my code much to split task, and encapsulated many operation to extendscript call like get layer property.

@Inventsable

tjx666 commented 2 years ago

In the after effect, we can run extendscript and render in parallel:

https://user-images.githubusercontent.com/41773861/158729056-fe2d18cc-2d7a-4b62-a491-43e4950313f2.mov

But in ps, it's so bad, even can't render GIF when execute extendscript:

https://user-images.githubusercontent.com/41773861/158730692-b80e8ad5-aae9-44bd-b80f-79eb621f5f58.mov

Inventsable commented 2 years ago

Yes but it's just a personal recommendation. We had a discussion on the Slack channel where @jardicc recommended the app.doProgress method and I'd try this before doing any refactoring, but there are several reasons why leveraging CEP for all intensive code and sending JSX chunks gives advantages:

I'd definitely try to salvage what code you're currently trying via app.doProgress as-is, but if it's still not performant then I'd try to split this into subtasks or in the case of batches processing chunks instead of entire collections at least in the context of CEP and JSX instead of UXP where this is likely not going to be an issue.

ericdrobinson commented 2 years ago

But in ps, it's so bad, even can't render GIF when execute extendscript:

@tjx666 Yes. Unfortunately that is due to how the Photoshop team integrated ExtendScript. The After Effects team did it in a completely different way that did not block the main application render thread.

You might try the options outlined here to see if the experience is better. At the very least, they should be able to pop up a progress bar to show that something is happening, instead of freezing the app with no UI updates at all.

Alternatively, you can break up your work into smaller bits and process them in "chunks" as suggested by @Inventsable, effectively implementing "timeslicing".

tjx666 commented 2 years ago

I'm making a commercial project. I's impossible for us to use UXP because stability and version requirements. Maybe I will choose to using the doProgress API or just keep it block when running extendscript. Thanks for your reply @ericdrobinson @Inventsable

ErinFinnegan commented 2 years ago

This conversation was amazing, thanks everyone! (In the end there was no need to go to the Slack or forums, the world's top experts chimed in here...)

@tjx666, I understand the version limitations of UXP, but I am sure my team is interested in hearing about the stability issues that make it "impossible" for you to use.