cmajor-lang/cmajor

Web view content doesn't load with local builds of JUCE plugins with custom UIs

Closed this issue · 9 comments

Just for the record, I'm reporting here an issue we were chatting about on Discurse regarding an issue I'm experiencing with local builds of the HYCE plugin.

@cesaref has done some fixes recently to the JUCE plugin to solve a problem with the state of the webview on first load.
He passed me the VST3 and standalone binaries for the CutomGUI example patch, and they work fine.

However, builds done my be on different machines, and by a collegue of mine, with the same cmajor 1.0.2444 are not working.
In the case of CustomGUI I see an empty black panel (it's a webview since I can see the contextual menu).
With Pro54 instead I get the usual grey panel of JUCE, with the JUCE logo.

I've built the CutomGUI with the DevTools option enabled. By inspecting the console log I see the following error reported:

Uncaught (in promise) SyntaxError: Invalid or unexpected token (index.js:94).

Indeed, the createPatchView promise is never fullfilled (console logs placed inside it do not print).

Maybe the js file included in the compiled binary is somehow corrupted? I don't know...

The only reason I can think of for such a diffence could be the build tools. We're using Visual Studio 17 2022 with MSVC v143 and Windows 11 SDK 10.0.22621.0.

We're trying to reproduce this, but just as a sanity-check:
The error you're seeing at index.js:94 is at the end of the file, and an invalid token there sounds to me like you've got some junk appended to that file. Can you double check you've not inserted something there by mistake?

We're trying to reproduce this, but just as a sanity-check:
The error you're seeing at index.js:94 is at the end of the file, and an invalid token there sounds to me like you've got some junk appended to that file. Can you double check you've not inserted something there by mistake?

I didn't. The code is the original one coming from the repo.
After seeing the error I've put some console logs inside cmaj_PatchWebView.h, but they didn't alter the error.

I've also tried to check if there was something strange inside the .rdata section of the vst3 library, but I couldn't spot anything.

If it can be useful here it is an extract from the rdata sections of a working vst3 (@cesaref) and mine, around the end of the index.js text

working:
image

mine:
image

They look pretty similar, except the memory offsets

Another evidence of the problem loading the patch.

This is loading the patch with the Cmajor plugin:
image

This is my build (missing source code for demo_ui/index.js):
image

Notice that, apparently, the string view lenght for index.js inside cmajor_plugin.cpp is different from the one reported by @cesaref.

@cesaref

    static constexpr std::array files =
    {
        File { "cmaj_api/cmaj-patch-connection.js", std::string_view (cmaj_api_cmajpatchconnection_js, 12712) },
        File { "Freeverb_CustomGUI.cmajorpatch", std::string_view (Freeverb_CustomGUI_cmajorpatch, 436) },
        File { "cmaj_api/assets/cmajor-logo.svg", std::string_view (cmaj_api_assets_cmajorlogo_svg, 2913) },
        File { "cmaj_api/cmaj-audio-worklet-helper.js", std::string_view (cmaj_api_cmajaudioworklethelper_js, 27963) },
        File { "cmaj_api/cmaj-event-listener-list.js", std::string_view (cmaj_api_cmajeventlistenerlist_js, 3474) },
        File { "cmaj_api/cmaj-generic-patch-view.js", std::string_view (cmaj_api_cmajgenericpatchview_js, 6186) },
        File { "cmaj_api/cmaj-midi-helpers.js", std::string_view (cmaj_api_cmajmidihelpers_js, 13253) },
        File { "cmaj_api/cmaj-parameter-controls.js", std::string_view (cmaj_api_cmajparametercontrols_js, 29343) },
        File { "cmaj_api/cmaj-patch-view.js", std::string_view (cmaj_api_cmajpatchview_js, 4941) },
        File { "cmaj_api/cmaj-piano-keyboard.js", std::string_view (cmaj_api_cmajpianokeyboard_js, 15540) },
        File { "cmaj_api/cmaj-server-session.js", std::string_view (cmaj_api_cmajserversession_js, 18844) },
        File { "demo_ui/index.js", std::string_view (demo_ui_index_js, 3384) }
    };

mine:

    static constexpr std::array files =
    {
        File { "cmaj_api/cmaj-patch-connection.js", std::string_view (cmaj_api_cmajpatchconnection_js, 12712) },
        File { "Freeverb_CustomGUI.cmajorpatch", std::string_view (Freeverb_CustomGUI_cmajorpatch, 436) },
        File { "cmaj_api/assets/cmajor-logo.svg", std::string_view (cmaj_api_assets_cmajorlogo_svg, 2913) },
        File { "cmaj_api/cmaj-audio-worklet-helper.js", std::string_view (cmaj_api_cmajaudioworklethelper_js, 27963) },
        File { "cmaj_api/cmaj-event-listener-list.js", std::string_view (cmaj_api_cmajeventlistenerlist_js, 3474) },
        File { "cmaj_api/cmaj-generic-patch-view.js", std::string_view (cmaj_api_cmajgenericpatchview_js, 6186) },
        File { "cmaj_api/cmaj-midi-helpers.js", std::string_view (cmaj_api_cmajmidihelpers_js, 13253) },
        File { "cmaj_api/cmaj-parameter-controls.js", std::string_view (cmaj_api_cmajparametercontrols_js, 29343) },
        File { "cmaj_api/cmaj-patch-view.js", std::string_view (cmaj_api_cmajpatchview_js, 4941) },
        File { "cmaj_api/cmaj-piano-keyboard.js", std::string_view (cmaj_api_cmajpianokeyboard_js, 15540) },
        File { "cmaj_api/cmaj-server-session.js", std::string_view (cmaj_api_cmajserversession_js, 18844) },
        File { "demo_ui/index.js", std::string_view (demo_ui_index_js, 3477) }
    };

This is the string I have (NOTICE the split around half the string):

    static constexpr const char* demo_ui_index_js =
        R"(
/*
    This simple web component just manually creates a set of plain sliders for the
    known parameters, and uses some listeners to connect them to the patch.
*/
class DemoView extends HTMLElement
{
    constructor (patchConnection)
    {
        super();
        this.patchConnection = patchConnection;
        this.classList = "demo-patch-element";
        this.innerHTML = this.getHTML();
    }

    connectedCallback()
    {
        this.paramListener = (event) =>
        {
            // Each of our sliders has the same IDs as an endpoint, so we can find
            // the HTML element from the endpointID that has changed:
            const slider = this.querySelector ("#" + event.endpointID);

            if (slider)
                slider.value = event.value;
        };

        // Attach a parameter listener that will be triggered when any param is moved
        this.patchConnection.addAllParameterListener (this.paramListener);

        for (const param of this.querySelectorAll (".param"))
        {
            // When one of our sliders is moved, this will send the new value to the patch.
            param.oninput = () => this.patchConnection.sendEventOrValue (param.id, param.value);

            // for each slider, request an initial update, to make sure it shows the right value
            this.patchConnection.requestParameterValue (param.id);
        }
    }

    disconnectedCallback()
    {
        // when our element goes offscreen, we should remove any listeners
        // from the PatchConnection (which may be shared with other clients)
        this.patchConnection.removeAllParameterListener (this.paramListener);
    }

    getHTML()
    {
        return `
        <style>
            .demo-patch-element {
                background: #bcb;
                display: block;
                width: 100%;
                height: 100%;
                padding: 10px;
                overflow: auto;
            }

            .param {
)"
R"(
                display: inline-block;
                margin: 10px;
                width: 300px;
            }
        </style>

        <div id="controls">
          <input type="range" class="param" id="roomSize" min="0" max="100">Room Size</input>
          <input type="range" class="param" id="damping"  min="0" max="100">Damping</input>
          <input type="range" class="param" id="width"    min="0" max="100">Width</input>
          <input type="range" class="param" id="wetLevel" min="0" max="100">Wet Level</input>
          <input type="range" class="param" id="dryLevel" min="0" max="100">Dry Level</input>
        </div>`;
    }
}

window.customElements.define ("demo-patch-view", DemoView);

/* This is the function that a host (the command line patch player, or a Cmajor plugin
   loader, or our VScode extension, etc) will call in order to create a view for your patch.

   Ultimately, a DOM element must be returned to the caller for it to append to its document.
   However, this function can be `async` if you need to perform asyncronous tasks, such as
   fetching remote resources for use in the view, before completing.

   When using libraries such as React, this is where the call to `ReactDOM.createRoot` would
   go, rendering into a container component before returning.
*/
export default function createPatchView (patchConnection)
{
    return new DemoView (patchConnection);
}
)";

@julianstorer @cesaref I got it working by changin 3477 to 3384 in File { "demo_ui/index.js", std::string_view (demo_ui_index_js, 3477) }.

I guess there's something wrong with the calculation of that value (f.content.length()). And so, being the size smaller then real, it gets cut? Couls it be?

Maybe a problem with line endings, carraige returns?

Iive applied the pathc proposed by @julianstorer

diff --git a/modules/compiler/include/cmaj_CppGenerationUtils.h b/modules/compiler/include/cmaj_CppGenerationUtils.h
index c31ea69..94f8044 100644
--- a/modules/compiler/include/cmaj_CppGenerationUtils.h
+++ b/modules/compiler/include/cmaj_CppGenerationUtils.h
@@ -107,9 +107,7 @@ namespace cmaj::cpp_utils
             }
         }

-        std::ostringstream s;
-        s << "R\"" << guard << "(" << text << ")" << guard << "\"";
-        return s.str();
+        return "R\"" + guard + "(" + std::string(text) + ")" + guard + "\"";
     }

but apparently it doesn't solve the issue with the size. You can see me step debugging in the screenshot below:

image

After removing the CR characters from index.js the size was calculated correctly.

@cesaref apparently the problem seems gone with your latest fixes. I will close the issue for the moment and I will keep testing the next week.