// Copyright 2012 United States Government, as represented by the Secretary of Defense, Under // Secretary of Defense (Personnel & Readiness). // // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software distributed under the License // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express // or implied. See the License for the specific language governing permissions and limitations under // the License. /// vwf/model/blockly.js is the driver for the Google blockly visual programming language. /// /// @module vwf/model/blockly /// @requires vwf/model ... and others define( [ "module", "vwf/model", "vwf/utility", "vwf/model/blockly/JS-Interpreter/acorn", "vwf/model/blockly/blockly_compressed", "vwf/model/blockly/blocks_compressed", "vwf/model/blockly/javascript_compressed", "vwf/model/blockly/msg/js/en" ], function( module, model, utility, acorn, Blockly ) { var self; return model.load( module, { // == Module Definition ==================================================================== // -- initialize --------------------------------------------------------------------------- initialize: function( options ) { self = this; this.arguments = Array.prototype.slice.call( arguments ); if ( options === undefined ) { options = {}; } this.state = { "nodes": {}, "scenes": {}, "prototypes": {}, "blockly": { "node": undefined }, "executingBlocks": {}, "executionHalted": false, "createNode": function( nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childIndex, childName, callback ) { return { "parentID": nodeID, "ID": childID, "extendsID": childExtendsID, "implementsIDs": childImplementsIDs, "source": childSource, "type": childType, "name": childName, "blocks": "", "toolbox": undefined, "defaultXml": undefined, "code": undefined, "lastLineExeTime": undefined, "timeBetweenLines": 1, "interpreter": undefined, "interpreterStatus": "" }; } }; // turns on logger debugger console messages this.debug = { "creation": false, "initializing": false, "parenting": false, "deleting": false, "properties": false, "setting": false, "getting": false, "methods": false, "prototypes": false }; // interpreter documentation // https://neil.fraser.name/software/JS-Interpreter/docs.html }, creatingNode: function( nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childIndex, childName, callback ) { // If the parent nodeID is 0, this node is attached directly to the root and is therefore either // the scene or a prototype. In either of those cases, save the uri of the new node var childURI = ( nodeID === 0 ? childIndex : undefined ); var appID = this.kernel.application(); if ( this.debug.creation ) { this.logger.infox( "creatingNode", nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childName ); } // If the node being created is a prototype, construct it and add it to the array of prototypes, // and then return var prototypeID = utility.ifPrototypeGetId( appID, this.state.prototypes, nodeID, childID ); if ( prototypeID !== undefined ) { if ( this.debug.prototypes ) { this.logger.infox( "prototype: ", prototypeID ); } this.state.prototypes[ prototypeID ] = { parentID: nodeID, ID: childID, extendsID: childExtendsID, implementsID: childImplementsIDs, source: childSource, type: childType, uri: childURI, name: childName }; return; } var node = this.state.nodes[ childID ]; if ( node === undefined && isBlockly3Node( childID ) ) { this.state.nodes[ childID ] = node = this.state.createNode( nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childIndex, childName, callback ); } }, initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childIndex, childName ) { if ( this.debug.initializing ) { this.logger.infox( "initializingNode", nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childName ); } }, deletingNode: function( nodeID ) { if ( this.debug.deleting ) { this.logger.infox( "deletingNode", nodeID ); } if ( this.state.nodes[ nodeID ] !== undefined ) { delete this.state.nodes[ nodeID ]; } }, addingChild: function( nodeID, childID, childName ) { if ( this.debug.parenting ) { this.logger.infox( "addingChild", nodeID, childID, childName ); } }, movingChild: function( nodeID, childID, childName ) { if ( this.debug.parenting ) { this.logger.infox( "movingChild", nodeID, childID, childName ); } }, removingChild: function( nodeID, childID, childName ) { if ( this.debug.parenting ) { this.logger.infox( "removingChild", nodeID, childID, childName ); } }, // -- creatingProperty --------------------------------------------------------------------- creatingProperty: function( nodeID, propertyName, propertyValue ) { var value = undefined; if ( this.debug.properties ) { this.logger.infox( "C === creatingProperty ", nodeID, propertyName, propertyValue ); } var node = this.state.nodes[ nodeID ]; if ( node !== undefined ) { value = this.settingProperty( nodeID, propertyName, propertyValue ); } return value; }, // -- initializingProperty ----------------------------------------------------------------- initializingProperty: function( nodeID, propertyName, propertyValue ) { var value = undefined; if ( this.debug.properties ) { this.logger.infox( " I === initializingProperty ", nodeID, propertyName, propertyValue ); } var node = this.state.nodes[ nodeID ]; if ( node !== undefined ) { value = this.settingProperty( nodeID, propertyName, propertyValue ); } return value; }, // -- settingProperty ---------------------------------------------------------------------- settingProperty: function( nodeID, propertyName, propertyValue ) { if ( this.debug.properties || this.debug.setting ) { this.logger.infox( " S === settingProperty ", nodeID, propertyName, propertyValue ); } var node = this.state.nodes[ nodeID ]; // { name: childName, glgeObject: undefined } var value = undefined; if ( ( node !== undefined ) && ( utility.validObject( propertyValue ) ) ) { switch ( propertyName ) { case "blockly_code": value = node.code = propertyValue; break; case "blockly_xml": value = node.blocks = propertyValue; break; case "blockly_executing": var exe = Boolean( propertyValue ); if ( exe ) { if ( this.state.executingBlocks === undefined ) { this.state.executingBlocks = {}; } if ( this.state.executingBlocks[ nodeID ] === undefined ) { getJavaScript( node ); this.state.executingBlocks[ nodeID ] = node; } setToolboxBlockEnable( false ); } else { if ( this.state.executingBlocks && this.state.executingBlocks[ nodeID ] !== undefined ) { delete this.state.executingBlocks[ nodeID ]; var count = Object.keys( this.state.executingBlocks ).length; if ( count === 0 ) { this.state.executingBlocks = {}; setToolboxBlockEnable( true ); } } } break; case "blockly_toolbox": node.toolbox = propertyValue; break; case "blockly_defaultXml": node.defaultXml = propertyValue; break; default: break; } } return value; }, // -- gettingProperty ---------------------------------------------------------------------- gettingProperty: function( nodeID, propertyName ) { if ( this.debug.properties || this.debug.getting ) { this.logger.infox( " G === gettingProperty ", nodeID, propertyName ); } var node = this.state.nodes[ nodeID ]; var value = undefined; if ( node !== undefined ) { switch ( propertyName ) { case "blockly_executing": value = ( this.state.executingBlocks && this.state.executingBlocks[ nodeID ] !== undefined ); break; case "blockly_code": value = node.code; break; case "blockly_xml": value = node.blocks; break; } } return value; }, // TODO: deletingMethod // -- callingMethod -------------------------------------------------------------------------- callingMethod: function( nodeID, methodName /* [, parameter1, parameter2, ... ] */ ) { // TODO: parameters var node = this.state.nodes[ nodeID ]; if ( this.debug.methods ) { this.logger.infox( " M === callingMethod ", nodeID, methodName ); } if ( nodeID == this.kernel.application() ) { switch ( methodName ) { case "stopAllExecution": for ( var id in this.state.executingBlocks ) { this.state.executingBlocks[ id ].interpreterStatus = "completed"; this.kernel.setProperty( id, 'blockly_executing', false ); this.kernel.fireEvent( id, "blocklyStopped", [ true ] ); } break; case "startAllExecution": for ( var id in this.state.nodes ) { this.kernel.setProperty( id, 'blockly_executing', true ); this.kernel.fireEvent( id, "blocklyStarted", [ true ] ); } break; } } else if ( node !== undefined ) { switch ( methodName ) { case "blocklyClear": if ( Blockly.mainWorkspace ) { Blockly.mainWorkspace.clear(); this.kernel.setProperty( nodeID, "blockly_xml", '' ); } break; } } }, // TODO: creatingEvent, deltetingEvent, firingEvent // -- executing ------------------------------------------------------------------------------ //executing: function( nodeID, scriptText, scriptType ) { // return undefined; //}, // == ticking ============================================================================= ticking: function( vwfTime ) { if ( this.state.executingBlocks !== undefined ) { var blocklyNode = undefined; for ( var nodeID in this.state.executingBlocks ) { blocklyNode = this.state.executingBlocks[ nodeID ]; var executeNextLine = false; if ( blocklyNode.interpreter === undefined || blocklyNode.interpreterStatus === "completed" ) { blocklyNode.interpreter = createInterpreter( acorn, blocklyNode.code ); blocklyNode.interpreterStatus = "created"; blocklyNode.lastLineExeTime = vwfTime; executeNextLine = true; } else { var elaspedTime = vwfTime - blocklyNode.lastLineExeTime; if ( elaspedTime >= blocklyNode.timeBetweenLines ) { executeNextLine = true; blocklyNode.lastLineExeTime = vwfTime; } } if ( executeNextLine ) { self.state.executionHalted = false; nextStep( blocklyNode ); this.kernel.fireEvent( nodeID, "blocklyExecuted", [ blocklyNode.interpreter.value ] ); } } } } } ); function getPrototypes( extendsID ) { var prototypes = []; var id = extendsID; while ( id !== undefined ) { prototypes.push( id ); id = self.kernel.prototype( id ); } return prototypes; } function isBlockly3Node( nodeID ) { return self.kernel.test( nodeID, "self::element(*,'http://vwf.example.com/blockly/controller.vwf')", nodeID ); } function isBlocklyNode( implementsIDs ) { var found = false; if ( implementsIDs ) { for ( var i = 0; i < implementsIDs.length && !found; i++ ) { found = ( implementsIDs[i] == "http://vwf.example.com/blockly/controller.vwf" ); } } return found; } function getJavaScript( node ) { var xml = Blockly.Xml.workspaceToDom( Blockly.getMainWorkspace() ); Blockly.JavaScript.vwfID = node.ID; if ( xml ) { node.blocks = Blockly.Xml.domToText( xml ); } node.code = Blockly.JavaScript.workspaceToCode(); } function setToolboxBlockEnable( enable ) { if ( Blockly.Toolbox.flyout_ !== undefined && Blockly.Toolbox.flyout_.workspace_ !== undefined ) { var blocks = Blockly.Toolbox.flyout_.workspace_.getTopBlocks( false ); if ( blocks ) { for ( var i = 0; i < blocks.length; i++ ) { blocks[ i ].setDisabled( !enable ); } } } else if ( Blockly.mainWorkspace && Blockly.mainWorkspace.flyout_ && Blockly.mainWorkspace.flyout_.workspace_ ){ var blocks = Blockly.mainWorkspace.flyout_.workspace_.getTopBlocks( false ); if ( blocks ) { for ( var i = 0; i < blocks.length; i++ ) { blocks[ i ].setDisabled( !enable ); } } } } function nextStep( node ) { if ( node.interpreter !== undefined ) { var stepType = node.interpreter.step(); while ( stepType && !self.state.executionHalted ) { if ( stepType === "stepProgram" ) { if ( node.interpreterStatus === "created" ) { self.kernel.fireEvent( node.ID, "blocklyStarted", [ true ] ); node.interpreterStatus = "started"; } } stepType = node.interpreter.step(); } if ( stepType === false ) { if ( node.interpreterStatus === "started" ) { self.kernel.setProperty( node.ID, "blockly_executing", false ); self.kernel.fireEvent( node.ID, "blocklyStopped", [ true ] ); node.interpreterStatus = "completed"; } } } } function createInterpreter( acorn, code ) { var initFunc = function( interpreter, scope ) { var vwfKernelFunctions, i; var myVwf = interpreter.createObject( interpreter.OBJECT ); interpreter.setProperty( scope, 'vwf', myVwf ); vwfKernelFunctions = [ 'setProperty', 'getProperty' ]; for ( i = 0; i < vwfKernelFunctions.length; i++ ) { var wrapper = ( function( nativeFunc ) { return function() { var parms = []; for ( var j = 0; j < arguments.length; j++) { parms.push( arguments[ j ].toString() ); } self.state.executionHalted = true; return interpreter.createPrimitive( nativeFunc.apply( vwf, parms ) ); }; } )( vwf[ vwfKernelFunctions[ i ] ] ); interpreter.setProperty( myVwf, vwfKernelFunctions[ i ], interpreter.createNativeFunction( wrapper ) ); } vwfKernelFunctions = [ 'callMethod', 'fireEvent' ]; for ( i = 0; i < vwfKernelFunctions.length; i++ ) { var wrapper = ( function( nativeFunc ) { return function() { var parms = []; for ( var j = 0; j < arguments.length; j++) { if ( j >= 2 ) { if ( arguments[ j ].type === "object" ) { parms.push( setArgsFromObj( arguments[ j ] ) ); } } else { parms.push( arguments[ j ].toString() ); } } self.state.executionHalted = true; return interpreter.createPrimitive( nativeFunc.apply( vwf, parms ) ); }; } )( vwf[ vwfKernelFunctions[ i ] ] ); interpreter.setProperty( myVwf, vwfKernelFunctions[ i ], interpreter.createNativeFunction( wrapper ) ); } }; return new Interpreter( acorn, code, initFunc ); } function setArgsFromObj( object ) { var args = []; for ( var i in object.properties ) { if ( object.properties[ i ].type === "object" ) { args.push( setArgsFromObj( object.properties[ i ] ) ); } else { args.push( object.properties[ i ].data ); } } return args; } } );