enkimute / ganja.js

:triangular_ruler: Javascript Geometric Algebra Generator for Javascript, c++, c#, rust, python. (with operator overloading and algebraic literals) -
MIT License
1.52k stars 108 forks source link

Having troubles including classes within Algebra() scope #155

Open skydog23 opened 1 year ago

skydog23 commented 1 year ago

Hi fellow ganja-ers,

I'm trying to test out a simple scene graph class for, among other things, handling transformations within ganja in a structured way.
But I'm having problems getting my classes to work inside the Algebra() scope.

Sample ganja code isolating the problem: https://enki.ws/ganja.js/examples/coffeeshop.html#71aWIewU1

Sample ganja code without reference to Algebra class, showing that the classes work properly: https://enki.ws/ganja.js/examples/coffeeshop.html#HbhknTqEE

Longterm I should create a module for this scene graph package and import it into my ganja apps. That seems likely to work. But for this initial debugging it's convenient to have everything in one file. And I would like to understand what the limitations are on using classes with ganja.

Thanks for your help.

skydog23 commented 1 year ago

I seemed to have fixed the problem with the classes within the Algebra() scope by removing the private class fields prefixed by '#'. I assume that caused problems with the ganja pre-processor.

kungfooman commented 1 year ago

I fixed some issues with the inline function in the past, but since the PR's are generally uncommented/unmerged, I will not bother with the # private issue.

If you want to use classes outside Algebra: it's an artifact of the operator overloading system, which breaks the "closure chain". You can circumvent that by simply assigning the classes into window:

class ClassOutside {
  name = "fooOut";
};
class SGNode {
  static count = 0;
  static base = "000000";
  static formatCount = ()=>{
      let num = SGNode.count.toString().length;
      return SGNode.base.substring(1,6-num)+SGNode.count.toString();
  }

  // using private field with '#' prefix was the source of the problem
  //#name = "?";
  name = "?";
  constructor() {
    this.name = `${this.constructor.name}-${SGNode.formatCount()}`;
    SGNode.count++;
    console.log("constructing ",SGNode.count," ",this.name);
  }
  getName() {return this.name}
  render(rdr) {console.log("rendering ",this.name, "currTf = ",rdr.tfstack)}
  reset() {}
}
window.SGNode = SGNode;
Algebra(3,0,1,()=>{

  class ClassInside {
    name = "fooIn";
  }

  let fooIn = new ClassInside();
  console.log(fooIn);

  //The following generates: "Uncaught ReferenceError: ClassOutside is not defined"
  // let fooOut = new ClassOutside();
  // console.log(fooOut);

  // and the following more realistic class causes problems only when private fields
  // with the '#' prefix are used.  I've removed that now and everytyhing runs fine. -24.2.2023 cg

  let sgn = new SGNode();
  console.log(sgn);

 return this.graph(()=>{

   return ["Test", 0xff0000, fooIn.name, sgn.name
  ]},{
  });

});
skydog23 commented 1 year ago

Thanks for this!

kungfooman commented 1 year ago

No problem, JavaScript standard otherwise is to write this:

class SGNode {
  /**
   * @private
   */
  _name = "?";
}

Most tools should respect that and the underscore makes it even more explicit - "do not use this variable outside SGNode":

image

And since you now have JSDoc documentation comments, you can also start documenting your source code.

And if you want to expose the name, use a getter instead:

class SGNode {
  /**
   * @private
   */
  _name = "?";
  /**
   * Name can only be read.
   * 
   * @type {string}
   */
  get name() {
    return this._name;
  }
}