Tuesday, February 5, 2013

Lexical Scope for the Kids

‹prev | My Chain | next›

I think I may have a cheat that will solve my JavaScript object woes. My woes are not of the how-to-do-it variety. This is JavaScript after all, I have at least three ways that I can solve my current problem. My woes are more along the lines of how much of this mess do I inflict on kids in 3D Game Programming for Kids?

The problem remains that I have a JavaScript prototype that describes a Three.js / Physijs ramp in a game:


Each ramp needs to support mouse drag events to allow the player to reposition the ramp. I have a fairly decent solution that relies on the prototype defining an onDrag() method:
  var ramp = {
    startAt: function(location) {
      this.mesh = new Physijs.ConvexMesh( /* ... */ );
      this.setRotation(location.rotation);
      this.setPosition(location.position[0], location.position[1]);
      addMouseHandlers(this);
    },
    onDrag: function(event) {
      this.mesh.__dirtyPosition = true;
      this.mesh.position.x = this.mesh.position.x + event.x_diff;
      this.mesh.position.y = this.mesh.position.y + event.y_diff;      
    },
    // ...
  };
As much as that works, I would prefer to add the mouse event listener in a manner that is more consistent with the rest of the book. Something like:
      Mouse.addEventListener('drag', this.mesh, this.drag, this)
But without the this craziness. This had me stymied last night, because this is JavaScript. Context is king in JavaScript so I could see no way to avoid binding this (and thus needing to explain it to kids).

What I realize today is that I can cheat, with a little help from JavaScript's lexical scoping (another thing that I hope to avoid telling kids about). Specifically, the onDrag method that I currently have in place acts on and uses only the ramp's mesh object. So I can create a lexically scoped reference to that mesh, which can then be used in the event listener:
  var ramp = {
    startAt: function(location) {
      this.mesh = new Physijs.ConvexMesh( /* ... */ );
      this.setRotation(location.rotation);
      this.setPosition(location.position[0], location.position[1]);

      var mesh = this.mesh;
      Mouse.addEventListener('drag', mesh, function(event) {
        mesh.__dirtyPosition = true;
        mesh.position.x = mesh.position.x + event.x_diff;
        mesh.position.y = mesh.position.y + event.y_diff;      
      });
    },
    // ...
  };
Obviously I would have to introduce this. I would have to make some mention that it does not always mean what you think it means, but I could leave the complete explanation as beyond the scope of the book (or possibly inflict it in an appendix).

I get that working with a small wrapper around the previous addMouseHandlers() code which requires the supplied object follow the conventions of defining a mesh property and any event properties requiring a callback:
  var Mouse = {
    callbacks: {'drag': {}},
    addEventListener: function(name, mesh, callback) {
      if (!this.callbacks.hasOwnProperty(name)) return;
      var handler = {};
      handler.mesh = mesh;
      handler[name] = callback;
      addMouseHandlers(handler);
    }  
  };
With that, I have mouse events working on Three.js object in way that looks very much like the document.addEventListener() that I inflict on kids in a previous chapter.

I could almost see it modifying the Physijs and Three.js mesh prototype so that something like the following would work:
      mesh.addEventListener('drag', function(event) {
        this.__dirtyPosition = true;
        this.position.x = this.position.x + event.x_diff;
        this.position.y = this.position.y + event.y_diff;      
      });
I worry that this syntax would open the this can of proverbial worms.

I will probably factor this code out into a library tomorrow. I may give the Three.js mesh prototype support for addEventListener() a try at the same time. Even if I avoid it in the book, it might be nice to have around.

(the current state of the code)


Day #652

No comments:

Post a Comment