New operator support in Javascript engine?
-
I wanted to code my scripting logic in a object oriented fashion, but according this compiler error:
Interface:! Line 61, column 15: new is not supported anymore
It seems the new keyword is not supported anymore? Is object oriented Javascript not supported?
Testcase:
function FooBar(){ this.shout = function(){ Console.print("Bazinga"); }; } var foo = new FooBar(); foo.shout();
-
@bastiaan HISE Script is not javascript :) I tried going the object oriented route once upon a time too. The best solution in HISE Script is to use namespaces
https://forum.hise.audio/topic/79/scripting-best-practices
https://forum.hise.audio/topic/92/namespaces-in-javascript/13@Christoph-Hart You should add links to all of these very informative blog posts to the documentation menu on the main website.
-
Thanks for clearing that up, those links are very helpful indead and answered a lot of my questions.
-
So i have been reading the "best practices" guide. I like the namespacing concept, but those are just static objects for as far as i understand and mainly have the purpose of reducing code clutter. At some point, in the code i'm planning to write, I need some sort of object instantiation to prevent duplicate code and that things getting really messy (me = chairman at the federation of pizza lovers against duplicate code).
I need to simulate an LCD display on a scripting panel, and therefore I would like to write a few "classes" that mimic certain "on-screen" display controls like ADSR graphs and other visual elements that you'll normally find in settings pages of traditional 90's rack synthesizer units. I would like to be able to re-use these virtual controls code wise. No fast refresh rates are needed and i'm planning to buffer certain areas so that the painting routines are doing minimal as possible.
So in my first attempt to work Object Oriented in HISE "Javascript" without using the new operator I came up with the following structure to mimic the behaviour of Classes:
function MyClass(options){ /* Store constructor argument in private property */ this.options = options; return { otherFunction: function(){ Console.print("Console print from other function"); }, foo: function(){ /* Calling other function in class, just for illustration */ this.otherFunction(); /* Example on how to access properties of the options object */ Console.print(options.rect.top); Console.print(options.enabled ? "true" : "false"); }, printRectTop: function(){ Console.print(options.rect.top); } } } /* Construct first object */ var object1 = MyClass({ enabled: false, rect: { top: 500 }, }); /* Call method on first object, which works as expected */ object1.printRectTop(); /* outputs 500 */ /* Construct second object, and set alternative top value */ var object2 = MyClass({ enabled: false, rect: { top: 1000 }, }); /* Call method on second object, which also works as expected */ object2.printRectTop(); /* outputs 1000 */ /* Call method on first object again, which will now outputs 1000 which is not as expected */ object1.printRectTop(); /* outputs 1000 */
As you can see this clearly does not work, the first object was mutated. So I came up with another solution, which does work:
function ObjectManager(){ this.objects = []; return { construct: function(options){ var instance = {}; instance.options = options; instance.otherMethod = function(){ Console.print("Other method on class, just for illustration.."); }; objects.push(instance); return instance; } } } /* Construct first object and call method, works like expected */ var object1 = ObjectManager().construct({rect: {top: 500}}); Console.print(object1.options.rect.top); /* 500 */ /* Construct second object and call method, works like expected */ var object2 = ObjectManager().construct({rect: {top: 1000}}); Console.print(object2.options.rect.top); /* 1000 */ /* Call method again on first object, works like expected because the value of its property is unchanged */ Console.print(object1.options.rect.top); /* 500 */
It compiles and works for as far as i can see. I don't know how this will impact performance, still needs to measure it and compare it to some other writing styles. I'm just posting it here, so it maybe can help other users, or even to illustrate how to not write your code. Just experimenting how far I can go with the HISE "Javascript" engine, if performance gets in the way I'll probably need to switch to the C++ API and hope that allows me to implement custom painting routines for the UI.
-
I got pretty far with the ScriptPanel for these kinds of tasks. Bare in mind that every ScriptPanel has a object attached to it which can act as encapsulated data holder:
inline function create(name, specialProperty) { local p = Content.getComponent(name); p.data.specialProperty = specialProperty; p.setPaintRoutine(function(g) { g.drawAlignedText(this.data.specialProperty, [0, 0, 100, 50], "centred") }); } create("First", "FirstProperty"); create("Second", "SecondProperty");
The example you posted looks pretty complicated and the reason why I don't encourage "actual" object oriented programming in the scripting engine is its lack of a garbage collection (which is deliberate for real time performance reasons), so if you go too crazy with relations and properties between objects you end up with leaks pretty easily.
-
@Christoph-Hart: I see, your concerns about the GC are valid. Memory leaks in these type of application are horrible. Thanks for pointing out this behaviour of the Javascript Engine garbage collector. I will definitely take these into account.
I have been refining and simplifying the method above:
/* The helper manager that helps us instantiate new objects */ function ObjectManager(){ return { construct: function(prototype, parameters){ var instance = PrototypeFactory(prototype); if(typeof(instance.construct) === "function"){ instance.construct(parameters); } return instance; } } } /* Note: you need to add all your class definitions to this factory */ function PrototypeFactory(name){ switch(name){ case "Cat": return Cat(); } } /* Now we can just define classes in a modern Javascript fashion, like this Cat class with some properties and methods */ function Cat(){ return { name: "Garfield", construct: function(parameters){ this.age = parameters.age; }, printInformation: function(){ Console.print("Age: " + this.age); Console.print("Name: " + this.name); } } } /* Now we can instantiate objects of that class */ var cat = ObjectManager().construct("Cat", {age: 2}); cat.printInformation(); /* Outputs: Age: 2 Name: Garfield */ var olderCat = ObjectManager().construct("Cat", {age: 4}); olderCat.printInformation(); /* Outputs: Age: 4 Name: Garfield */
Again, i'm still experimenting and planning my code design strategy. Is there a way that I can monitor the Javascript engine's object count thru the API? Something like (pseudocode):
const var objectCountTimer = Engine.createTimerObject(); objectCountTimer.callback = function() { Console.print("Object count: " + Engine.getObjectCount()); }; objectCountTimer.startTimer(10000);
Besides that fact that this would allow me to test my design strategy for memory leaks, I think it would be a really nice addition to the framework because it allows monitoring of object instance regression (at least during debugging) and analyzing the behaviour of the Javascript Engine GC.
It's not that i'm planning to do very advanced OO Javascript stuff, so no inheritance etc. Just some basic prototyping and instantiation.. should that work (without memory leaks) than that would be my prefered strategy in contrast to using the C++ API. Just because i'm more comfortable with Javascript then C++ now-a-days.
So the GUI design I have in mind for this particular plugin i'm building is a very dynamic in nature, i'm trying to dynamically modify the module backend (Effects & Modulators) based on the users input. So for this to work, the Javascript Engine must be able to free memory of (my custom Javascript UI) objects that are unreferenced and go out of scope, otherwise it will leak like hell (like you mentioned) and render my method (mentioned above) totally unusable. Today I'll try to attach some custom C++ code using the C++ API, try and see how that works out.
Ps. It's good to know that a script panel can hold a custom data object, that would allow me to go for a more C stylish approach inside Javascript instead of the my custom OOP approach above. Like putting all the logic in a namespace and let the script panels cary their own data. Will give this approach a shot too, but I slowly getting the feeling that I need to go down the C++ road for my idea to work properly (and keep my code clean/maintainable).
Update: I found out that the object oriented Javascript method failed when trying to access class properties from within a callback function:
function DisplayComponent(){ return { backgroundColor: Colours.green, construct: function(parameters){ this.setPaintRoutine(); }, setPaintRoutine: function(){ var classthis = this; Display.setPaintRoutine(function(g) { g.fillAll(classthis.backgroundColor); }); } } }
It just can't access that backgroundColor property from within that scope. I could not find any documentation or examples on how to implement custom C++ code so i'm going to see if i can do something with the data object holder inside Script panel and build my design around that.