Performance on continous control from external device

Hi Everyone

Here is my first post on this forum. I started some UXP tests in order to control Photoshop from an external device, like a midi controller.

Basically the goal is to control a continous settings as brush Size
, or transparency of a layer using an external potentiometer.

I actually succeeded in having quickly some nice result, but I am facing a performance issue.

Photoshop cannot handle enough “batchPlay / sec” in order to have a smooth control of the app in realtime : UI delays, lag, freeze . I which i could improve this performance issue :face_with_peeking_eye:

Is changing UXP to CEP, or C++ could solve the problem ?
Or do have any idea to do it in a cleaner way that batchPlay ?

Thanks a lot

1 Like

This sounds super interesting, but a bit out of my wheelhouse to offer much guidance though!

As I understand it batchPlay is the closest to “bare metal” you can get and as such is the fastest, at least in terms of UXP - I couldn’t speak as to C++.
CEP is a dead end as it will be deprecated in the future.

But it strikes me that commercial products already exist that do what you are attempting and so it’s evidently possible.

may I ask how did you manage to narrow down the cause?
how are you reading the value of the pot?
and does logging the output work as expected?

in terms of speed I doubt there’s anything close to C++ but it’s a time investment so I’d investigate more before switching.

edit : also does your code go into modal state at all?


@Maher I am not sure about " narrow down the cause".
Actually I am reading potentiometer value through an external app that send websocket to my plugin.
Then my plugin execute batchPlay, and, for exemple, change the brush size.
Logging the output works well, but if I increase the rate of data, i got a warning " too much promises"

Let’s say that the process works, but if the frequency of changing the size brush is over 15 times / sec , then delays, freeze appears.

I am not sure about the “modal state”, but my plugin has an UI, and I want the settings changed from the plugin affects the photoshop UI in realtime ( I want to see the sizeBrush slider on the UI move in realtime )

You probably do want to try C++ for the best real-time results. (If you get “Access Denied” on that link try this.)

There are some known issues with UXP surrounding this kind of real-time feedback…

Thank you Erin
I haven’t try C++ plugin even if C++ is familiar to me.
I was convinced that javascript UXP was an easiest way to start plugin, but if C++ is the only solution … :raised_hands:

@Timothy_Bennett according to your sentence “But it strikes me that commercial products already exist that do what you are attempting and so it’s evidently possible”

I decided to have a look at “monogram” plugin. And because they have a new “uxp photoshop” plugin, I examinated the code precisely.

My conclusion is quite suprising : they are using APIV1 of uxp, where probably modal was not alreay prepared. And the result is much more better :face_holding_back_tears:

Here are 2 screenshots recording, where I call 30 time / sec a function that change the brush size, with API_v1 and API_v2

Have a look, the result is quite surprising !

The code below changes brush size using API-2 and it appears to execute reasonably fast and did not throw any errors for 1000 iterations. I do not see the brush size changing onscreen like in your video, though. So, the delay seems to maybe be in calling the function as opposed to executing it. In other words, "“batchPlay / sec” might not be the issue, but rather the time it takes to call a function that contains batchPlay.

document.getElementById("test").addEventListener("click", testButton);

async function testButton() {
    for (i=0; i < 250; i++) {
    try {await exeModal(targetFunction, {"commandName": "Progress...", "interactive": true});}
    catch(e) {
      if (e.number == 9) {showAlert("Some other plugin is using Photoshop!");}
      else {showAlert(e)}

    async function targetFunction(executionControl) {

        let hostControl = executionControl.hostControl;
        let documentID = await app.activeDocument._id;
        let suspensionID = await hostControl.suspendHistory({ "historyStateInfo": { "name": "Test Button", "target": [{ _ref: "document", _id: documentID }] } });

        ///////////////////// Start modal execution /////////////////////
        await batchPlay([       
            //Select Paintbrush tool
            { "_obj": "select","_target": { "_ref": "paintbrushTool" }, "_options": { "dialogOptions": "dontDisplay" } },
        ], {});

        // Store current tool options
        let toolOpt = batchPlay([
            { _obj: 'get', _target: [ { _property: "currentToolOptions" }, { _ref: 'application', _enum: 'ordinal', _value: 'targetEnum' } ], },
        ], { synchronousExecution: true })[0].currentToolOptions;
        let diameter = toolOpt.brush.diameter._value
        // Increase diameter by "1"
        toolOpt.brush.diameter._value = diameter + 1;

        await batchPlay([
            //Write back the Paintbrush Tool options using the toolOpt variable that includes "normal" blend mode
            {_obj: 'set', _target: { _ref: [{ _ref: 'paintbrushTool'}], }, to: toolOpt },        
        ], {});        
        ///////////////////// Stop modal execution /////////////////////

        await hostControl.resumeHistory(suspensionID);
    } // end targetFunction()  
    }  //end for