Wednesday, November 6, 2013

Pixi.js and GoogleBlockly


Pixi.js is a fast 2D rendering engine that is optimized for WebGL, and is able to use HTML5 Canvas as a fallback. Using WebGL Pixi is able to render high resolution graphics very quickly. The Pixi API is well designed, and easily wrapped in PythonJS, see my bindings here.

Google Blockly Integration

The officially documented way to integrate Google Blockly with an external API is using Blockly.addChangeListener( my_callback ), your callback will be called each time the UI is updated. Your callback can then call Blockly.JavaScript.workspaceToCode() and then use JavaScript's eval to execute the string that it returns. This only allows for weak integration, here are the following problems:

  1. Your callback will be called for every single UI event, like moving blocks, or typing in an input field. This is slow and makes it very hard to catch just the events you are interested in.
  2. You have no way to send signals back to the blocks that generated the code. This makes it impossible to update fields on Blockly's blocks, like number fields. For example, your custom block creates a Sprite, and then in the game the sprite is moved, now your code will need to update the position field on the block in Blockly's workspace, but the Sprite has no reference to the block to do so.
  3. Each time your callback is triggered, you might have to tear down and reinitialize alot of state.
  4. This is generally a bad fit for an Actor model, where entities carry out their own behaviors, and interact with their environment using dynamic rules.

The above problems show us that the official way to integrate Blockly is pretty much useless with a dynamic system that requires bi-directional updates. The workaround to these problems requires us to modify Blockly's prototypes and functions so that blocks can be created from an external API, and UI events can be caught directly for each field. My solution is implemented in the binding layer between Blockly and PythonJS. Blockly's source code can remain intact - because prototypes and functions can be changed at runtime in JavaScript using special syntax in PythonJS. You can see my binding here.

Wrapper Decorators

One of my goals when starting the integration between Google Blockly and Pixi was to write as little wrapper code as possible, but not so little that it was unclear how things are tied together. To achive this I implemented two special class decorators: @pythonjs.init_callbacks and @pythonjs.property_callbacks. These are different from normal Python class decorators because they inject some code and attributes into the class and it's methods at compile time. The "Block Generator" in the Blockly binding can then use the extra attributes to hook in it's own callbacks for when instances of a class are created, and when values are set on property setters. In my first attempts to integrate Blockly with Three.js I had written alot of code to generate a custom block for each Three.js class. The new Block Generator can wrap a class and generate a custom block with a single call: block.bind_class( my_class ).

To run this demo you need the checkout the latest source code for: Pixi.js, GoogleBlockly, and Tween.js. Then pull the latest source for PythonJS. The source code for the demo is here.

No comments:

Post a Comment