Anyone for Javascript?


#1

http://www.juce.com/forum/topic/anyone-javascript

 

Very good news :)

In our GUI system we use JS ( + XML for layout and styling) for all GUI logic.

We decided to use Google's V8. It is big, but it works like a charm ! It is easy to use, easy to communicate with C++ and it is performant.

A very good point for V8 is its debugger. In Chrome dev tools there is a plugin for eclipse that you can connect to your app's VM.


#2

Hi Jules,

seems great! I played around with a few scripting engines myself and decided on Lua, but I think two additional features would make your JS script engine more versatile. However, these feature requests are all about real time usage, so if your expected field of usage is GUI management (which is mostly executed at initialization) these requests are not relevant :)

1.) divide the parsing and the execution into two functions. According to your API documentation there is only the possibility to run a javascript code that is parsed just before execution. If some functions are executed several times (eg. callbacks for note-on messages) the overhead of reparsing the script string is unnecessary. This of course needs a new object type for bytecode:

String code = "..." // JS code

JSByteCode b = compile(code);

Result r = run(b,  100); // this function be executed repeatedly without the parsing overhead

2. I don't know if it is already possible but can javascripts make a non-busy wait (something like sleep())? I don't think there is a necessary for OS-level multithreading, but if a js function can be yielded and resumed at a later time, it should be possible to implement it.


#3

Ouch.. not sure about real-time use! Even if no parsing gets done, there are still loads of strings and objects being constructed and passed around during execution, with plenty of allocator use, so running it in e.g. an audio thread would be at your own risk! (I guess the same is true of lua and other scripts too though)

Re: parsing: the way I'd expect it to be used would be by calling execute() to parse your script file once, then call evaluate() to invoke functions/values. Parsing something evaluate ("myfunction (1, 2)") will take negligible time, though I will also add a method to directly invoke a function by name, mainly to make it easier to pass it some var objects as args rather than a string.

Re: sleep(), it's easy enough to give it some native code to provide functions like that if you need them. I'm not planning on providing a massive set of JS library calls - it's up to you to provide what you need for your own purposes!


#4

This is very impressive Jules. I'm probably never going to use it, but I must congratulate you!


#5

Thanks! Yes, I'm pretty proud of it as a piece of coding!


#6

It is amazing the Javascript engine, i will try to make it run the x86 emulator just for the fun of it:

http://copy.sh/v24/

Jules, is there anything planned to bring some sort of functionality with the Javascript engine + Browser Component? To use it for callbacks and reading vars to/from it?


#7

Thanks! No, no plans to integrate with the browser comp, I don't think the embedded browsers expose that kind of functionality.


#8

yeah. juce works quite well with v8. I had messed around with it a year or two ago. I had planned to release a thing that would allow creating full gui applications using js, but sorta dropped the ball with it :/


#9

Hello,

 

I have a problem using the JavaScriptEngine class in combination with a DynamicObject descendant. For example, this is my class:

 

class TestObject : public DynamicObject {

public:

    bool hasMethod(const Identifier &methodName) const override final

    {

        if (methodName.toString().toStdString() == "do")

            return true;

        else

            return DynamicObject::hasMethod(methodName);

    }

    

    juce::var invokeMethod (Identifier methodName, const var::NativeFunctionArgs &args) override final

    {

        if (methodName.toString().toStdString() == "do")

            return var(10);

        else

            return DynamicObject::invokeMethod(methodName, args);

    }

private:

};

 

and then I run this code:

 

    JavascriptEngine javaScriptEngine;

    TestObject::Ptr object(new TestObject);

    TestObject::Ptr nestedObject(new TestObject);

    

    object->setProperty("nested", var(nestedObject));

    nestedObject->setProperty("x", var(10));

    

    javaScriptEngine.registerNativeObject("object", object);

 

    cout << javaScriptEngine.evaluate("object.nested.x").toString().toStdString() << endl;  // this works: outputs 10

    cout << javaScriptEngine.evaluate("object.do()").toString().toStdString() << endl; // this doesnt work: outputs undefined

    cout << javaScriptEngine.evaluate("object.nested.do()").toString().toStdString() << endl; // doesnt work either

 

TestObject::hasMethod and TestObject::invokeMethod are never called during the evaluate() calls. Is this a bug or am I doing something wrong?

Thanks in advance!


#10

(There's really no need to call "toStdString", this code would probably all be fine without it)

It sounds like it should probably work - did you debug it to see if your DynamicObject methods were being called?


#11

Hi Jules,

The JS parser fails with some internal assertion if you try to evaluate the string “{”. Doesn’t bother me if it doesn’t bother you.

Thanks!


#12

OK, ta - probably just an unnecessary assertion, but will have a look.


#13

This is a little off topic, but just curious: is there a way to add anything other than a DynamicObject to the root namespace in the JS engine? A method such as

registerVariable (const Identifier &objectName, var someVariable)

would be nice to have in some situations.


#14

There is the getRootProperties (or something like that) method which returns a NamedValueSet that you can alter. You’ll have to const cast it though…


#15

Well, I thought there might be a good reason the return value was const. That, and lack of the method I asked for suggests to me that Jules meant for the root namespace to be left alone.


#16

I think the reason it is const is just because no one asked for it (I was requesting this method for implementing some debugging features and didn’t need to alter it).

I can’t think of any scenario that might cause harms if you change the NamedValueSet. Take for instance the assignment of a UnqualifiedName:

        void assign (const Scope& s, const var& newValue) const override
        {
            if (var* v = getPropertyPointer (s.scope, name))
                *v = newValue;
            else
                s.root->setProperty (name, newValue);
        }

So even if you do the most evil thing possible and delete a property (which I would not recommend), it just sets it again - we’ll ignore the race condition that might occur if you delete it on another thread just after the pointer was created in the if-condition since the whole engine is not threadsafe at all.

It would be more problematic if this assignment would store a pointer to a var in the NamedValueSet which changes as soon as you call a non const method on the NamedValueSet.


#17

Tried this in the demo Javascript parser page and it seems completely fine - gives a sensible error message. How did you trigger it?


#18
JavascriptEngine engine;
engine.evaluate ("{");

Here is the traceback:

Identifier::Identifier (const String& nm)
    : name (StringPool::getGlobalPool().getPooledString (nm))
{
    // An Identifier cannot be created from an empty string!
    jassert (nm.isNotEmpty()); // <- fails this assertion
}

#19

OK, thanks, I’ll sort that out.


#20

Hi Jules,

Here are other things that break the JS parser:

JavascriptEngine engine;
engine.evaluate ("function(");
engine.evaluate ("function(var)");

It’s always failing that same assertion, trying to create an identifier from an empty string.

Sounds petty, but shouldn’t users be able to type gibberish into the JS parser without it crashing the application?