From e3b05ad2c164a416ad2606ae9e1ae673aa033cb9 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 26 Feb 2016 14:13:18 -0800 Subject: [PATCH 1/7] Move connection_db to a new file and start adding tests --- core/connection.js | 233 +++++------------------------ core/connection_db.js | 211 ++++++++++++++++++++++++++ tests/jsunit/connection_db_test.js | 56 +++++++ tests/jsunit/db_test.js | 76 ++++++++++ 4 files changed, 384 insertions(+), 192 deletions(-) create mode 100644 core/connection_db.js create mode 100644 tests/jsunit/db_test.js diff --git a/core/connection.js b/core/connection.js index 2fd2cb60e..fa4205620 100644 --- a/core/connection.js +++ b/core/connection.js @@ -25,7 +25,6 @@ 'use strict'; goog.provide('Blockly.Connection'); -goog.provide('Blockly.ConnectionDB'); goog.require('goog.dom'); @@ -332,7 +331,7 @@ Blockly.Connection.connectReciprocally = function(first, second) { } first.targetConnection = second; second.targetConnection = first; -} +}; /** * Does the given block have one and only one connection point that will accept @@ -516,38 +515,6 @@ Blockly.Connection.prototype.moveBy = function(dx, dy) { this.moveTo(this.x_ + dx, this.y_ + dy); }; -/** - * Add highlighting around this connection. - */ -Blockly.Connection.prototype.highlight = function() { - var steps; - if (this.type == Blockly.INPUT_VALUE || this.type == Blockly.OUTPUT_VALUE) { - var tabWidth = this.sourceBlock_.RTL ? -Blockly.BlockSvg.TAB_WIDTH : - Blockly.BlockSvg.TAB_WIDTH; - steps = 'm 0,0 ' + Blockly.BlockSvg.TAB_PATH_DOWN + ' v 5'; - - } else { - steps = 'm -20,0 h 5 ' + Blockly.BlockSvg.NOTCH_PATH_LEFT + ' h 5'; - } - var xy = this.sourceBlock_.getRelativeToSurfaceXY(); - var x = this.x_ - xy.x; - var y = this.y_ - xy.y; - Blockly.Connection.highlightedPath_ = Blockly.createSvgElement('path', - {'class': 'blocklyHighlightedConnectionPath', - 'd': steps, - transform: 'translate(' + x + ',' + y + ')' + - (this.sourceBlock_.RTL ? ' scale(-1 1)' : '')}, - this.sourceBlock_.getSvgRoot()); -}; - -/** - * Remove the highlighting around this connection. - */ -Blockly.Connection.prototype.unhighlight = function() { - goog.dom.removeNode(Blockly.Connection.highlightedPath_); - delete Blockly.Connection.highlightedPath_; -}; - /** * Move the blocks on either side of this connection right next to each other. * @private @@ -590,22 +557,12 @@ Blockly.Connection.prototype.closest = function(maxLimit, dx, dy) { var currentX = this.x_ + dx; var currentY = this.y_ + dy; - // Binary search to find the closest y location. - var pointerMin = 0; - var pointerMax = db.length - 2; - var pointerMid = pointerMax; - while (pointerMin < pointerMid) { - if (db[pointerMid].y_ < currentY) { - pointerMin = pointerMid; - } else { - pointerMax = pointerMid; - } - pointerMid = Math.floor((pointerMin + pointerMax) / 2); - } + // Find the closest y location. + var candidatePosition = db.findPositionForConnection_(this); // Walk forward and back on the y axis looking for the closest x,y point. - pointerMin = pointerMid; - pointerMax = pointerMid; + var pointerMin = candidatePosition; + var pointerMax = candidatePosition; var closestConnection = null; var sourceBlock = this.sourceBlock_; var thisConnection = this; @@ -763,57 +720,25 @@ Blockly.Connection.prototype.getShadowDom = function() { * @private */ Blockly.Connection.prototype.neighbours_ = function(maxLimit) { - // Determine the opposite type of connection. - var db = this.dbOpposite_; + return this.dbOpposite_.getNeighbours(this, maxLimit); +}; - var currentX = this.x_; - var currentY = this.y_; +// Appearance or lack thereof. - // Binary search to find the closest y location. - var pointerMin = 0; - var pointerMax = db.length - 2; - var pointerMid = pointerMax; - while (pointerMin < pointerMid) { - if (db[pointerMid].y_ < currentY) { - pointerMin = pointerMid; - } else { - pointerMax = pointerMid; +/** + * Returns a shape enum for this connection. + * @return {number} Enum representing shape. + */ +Blockly.Connection.prototype.getOutputShape = function() { + if (!this.check_) return Blockly.Connection.NUMBER; + if (this.check_.indexOf('Boolean') !== -1) { + return Blockly.Connection.BOOLEAN; } - pointerMid = Math.floor((pointerMin + pointerMax) / 2); - } - - // Walk forward and back on the y axis looking for the closest x,y point. - pointerMin = pointerMid; - pointerMax = pointerMid; - var neighbours = []; - var sourceBlock = this.sourceBlock_; - if (db.length) { - while (pointerMin >= 0 && checkConnection_(pointerMin)) { - pointerMin--; + if (this.check_.indexOf('String') !== -1) { + return Blockly.Connection.STRING; } - do { - pointerMax++; - } while (pointerMax < db.length && checkConnection_(pointerMax)); - } - /** - * Computes if the current connection is within the allowed radius of another - * connection. - * This function is a closure and has access to outside variables. - * @param {number} yIndex The other connection's index in the database. - * @return {boolean} True if the current connection's vertical distance from - * the other connection is less than the allowed radius. - */ - function checkConnection_(yIndex) { - var dx = currentX - db[yIndex].x_; - var dy = currentY - db[yIndex].y_; - var r = Math.sqrt(dx * dx + dy * dy); - if (r <= maxLimit) { - neighbours.push(db[yIndex]); - } - return dy < maxLimit; - } - return neighbours; + return Blockly.Connection.NUMBER; }; /** @@ -895,110 +820,34 @@ Blockly.Connection.prototype.unhideAll = function() { return renderList; }; - /** - * Database of connections. - * Connections are stored in order of their vertical component. This way - * connections in an area may be looked up quickly using a binary search. - * @constructor + * Add highlighting around this connection. */ -Blockly.ConnectionDB = function() { -}; +Blockly.Connection.prototype.highlight = function() { + var steps; + if (this.type == Blockly.INPUT_VALUE || this.type == Blockly.OUTPUT_VALUE) { + var tabWidth = this.sourceBlock_.RTL ? -Blockly.BlockSvg.TAB_WIDTH : + Blockly.BlockSvg.TAB_WIDTH; + steps = 'm 0,0 v 4 ' + Blockly.BlockSvg.NOTCH_PATH_DOWN + ' v 4'; -Blockly.ConnectionDB.prototype = new Array(); -/** - * Don't inherit the constructor from Array. - * @type {!Function} - */ -Blockly.ConnectionDB.constructor = Blockly.ConnectionDB; - -/** - * Add a connection to the database. Must not already exist in DB. - * @param {!Blockly.Connection} connection The connection to be added. - * @private - */ -Blockly.ConnectionDB.prototype.addConnection_ = function(connection) { - if (connection.inDB_) { - throw 'Connection already in database.'; + } else { + steps = 'm 0,0 v -4 ' + Blockly.BlockSvg.NOTCH_PATH_UP + ' v -4'; } - if (connection.sourceBlock_.isInFlyout) { - // Don't bother maintaining a database of connections in a flyout. - return; - } - // Insert connection using binary search. - var pointerMin = 0; - var pointerMax = this.length; - while (pointerMin < pointerMax) { - var pointerMid = Math.floor((pointerMin + pointerMax) / 2); - if (this[pointerMid].y_ < connection.y_) { - pointerMin = pointerMid + 1; - } else if (this[pointerMid].y_ > connection.y_) { - pointerMax = pointerMid; - } else { - pointerMin = pointerMid; - break; - } - } - this.splice(pointerMin, 0, connection); - connection.inDB_ = true; + var xy = this.sourceBlock_.getRelativeToSurfaceXY(); + var x = this.x_ - xy.x; + var y = this.y_ - xy.y; + Blockly.Connection.highlightedPath_ = Blockly.createSvgElement('path', + {'class': 'blocklyHighlightedConnectionPath', + 'd': steps, + transform: 'translate(' + x + ',' + y + ')' + + (this.sourceBlock_.RTL ? ' scale(-1 1)' : '')}, + this.sourceBlock_.getSvgRoot()); }; /** - * Remove a connection from the database. Must already exist in DB. - * @param {!Blockly.Connection} connection The connection to be removed. - * @private + * Remove the highlighting around this connection. */ -Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) { - if (!connection.inDB_) { - throw 'Connection not in database.'; - } - connection.inDB_ = false; - // Find the connection using a binary search. - // About 10% faster than a linear search using indexOf. - var pointerMin = 0; - var pointerMax = this.length - 2; - var pointerMid = pointerMax; - while (pointerMin < pointerMid) { - if (this[pointerMid].y_ < connection.y_) { - pointerMin = pointerMid; - } else { - pointerMax = pointerMid; - } - pointerMid = Math.floor((pointerMin + pointerMax) / 2); - } - - // Walk forward and back on the y axis looking for the connection. - // When found, splice it out of the array. - pointerMin = pointerMid; - pointerMax = pointerMid; - while (pointerMin >= 0 && this[pointerMin].y_ == connection.y_) { - if (this[pointerMin] == connection) { - this.splice(pointerMin, 1); - return; - } - pointerMin--; - } - do { - if (this[pointerMax] == connection) { - this.splice(pointerMax, 1); - return; - } - pointerMax++; - } while (pointerMax < this.length && - this[pointerMax].y_ == connection.y_); - throw 'Unable to find connection in connectionDB.'; -}; - -/** - * Initialize a set of connection DBs for a specified workspace. - * @param {!Blockly.Workspace} workspace The workspace this DB is for. - */ -Blockly.ConnectionDB.init = function(workspace) { - // Create four databases, one for each connection type. - var dbList = []; - dbList[Blockly.INPUT_VALUE] = new Blockly.ConnectionDB(); - dbList[Blockly.OUTPUT_VALUE] = new Blockly.ConnectionDB(); - dbList[Blockly.NEXT_STATEMENT] = new Blockly.ConnectionDB(); - dbList[Blockly.PREVIOUS_STATEMENT] = new Blockly.ConnectionDB(); - workspace.connectionDBList = dbList; +Blockly.Connection.prototype.unhighlight = function() { + goog.dom.removeNode(Blockly.Connection.highlightedPath_); + delete Blockly.Connection.highlightedPath_; }; diff --git a/core/connection_db.js b/core/connection_db.js new file mode 100644 index 000000000..606657429 --- /dev/null +++ b/core/connection_db.js @@ -0,0 +1,211 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * 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. + */ + +/** + * @fileoverview Components for managing connections between blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.ConnectionDB'); + +goog.require('Blockly.Connection'); + + +/** + * Database of connections. + * Connections are stored in order of their vertical component. This way + * connections in an area may be looked up quickly using a binary search. + * @constructor + */ +Blockly.ConnectionDB = function() { +}; + +Blockly.ConnectionDB.prototype = new Array(); +/** + * Don't inherit the constructor from Array. + * @type {!Function} + */ +Blockly.ConnectionDB.constructor = Blockly.ConnectionDB; + +/** + * Add a connection to the database. Must not already exist in DB. + * @param {!Blockly.Connection} connection The connection to be added. + * @private + */ +Blockly.ConnectionDB.prototype.addConnection_ = function(connection) { + if (connection.inDB_) { + throw 'Connection already in database.'; + } + if (connection.sourceBlock_.isInFlyout) { + // Don't bother maintaining a database of connections in a flyout. + return; + } + var position = this.findPositionForConnection_(connection); + this.splice(position, 0, connection); + connection.inDB_ = true; +}; + +/** + * Finds a candidate position for inserting this connection into the list. + * This will be in the correct y order but makes no guarantees about ordering in the x axis. + * @param {Blockly.Connection} connection The connection to insert. + * @return {number} The candidate index. + * @private + */ +Blockly.ConnectionDB.prototype.findPositionForConnection_ = function(connection) { + if (this.length == 0) { + return 0; + } + var pointerMin = 0; + var pointerMax = this.length; + while (pointerMin < pointerMax) { + var pointerMid = Math.floor((pointerMin + pointerMax) / 2); + if (this[pointerMid].y_ < connection.y_) { + pointerMin = pointerMid + 1; + } else if (this[pointerMid].y_ > connection.y_) { + pointerMax = pointerMid; + } else { + pointerMin = pointerMid; + break; + } + } + return pointerMin; +}; + +/** + * Remove a connection from the database. Must already exist in DB. + * @param {!Blockly.Connection} connection The connection to be removed. + * @private + */ +Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) { + if (!connection.inDB_) { + throw 'Connection not in database.'; + } + connection.inDB_ = false; + // Find the connection using a binary search. + // About 10% faster than a linear search using indexOf. + var pointerMin = 0; + var pointerMax = this.length - 2; + var pointerMid = pointerMax; + while (pointerMin < pointerMid) { + if (this[pointerMid].y_ < connection.y_) { + pointerMin = pointerMid; + } else { + pointerMax = pointerMid; + } + pointerMid = Math.floor((pointerMin + pointerMax) / 2); + } + + // Walk forward and back on the y axis looking for the connection. + // When found, splice it out of the array. + pointerMin = pointerMid; + pointerMax = pointerMid; + while (pointerMin >= 0 && this[pointerMin].y_ == connection.y_) { + if (this[pointerMin] == connection) { + this.splice(pointerMin, 1); + return; + } + pointerMin--; + } + do { + if (this[pointerMax] == connection) { + this.splice(pointerMax, 1); + return; + } + pointerMax++; + } while (pointerMax < this.length && + this[pointerMax].y_ == connection.y_); + throw 'Unable to find connection in connectionDB.'; +}; + +/** + * Find all nearby connections to the given connection. + * Type checking does not apply, since this function is used for bumping. + * @param {Blockly.Connection} connection The connection whose neighbours should be returned. + * @param {number} maxRadius The maximum radius to another connection. + * @return {!Array.} List of connections. + * @private + */ +Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) { + var db = this; + var currentX = connection.x_; + var currentY = connection.y_; + + // Binary search to find the closest y location. + var pointerMin = 0; + var pointerMax = db.length - 2; + var pointerMid = pointerMax; + while (pointerMin < pointerMid) { + if (db[pointerMid].y_ < currentY) { + pointerMin = pointerMid; + } else { + pointerMax = pointerMid; + } + pointerMid = Math.floor((pointerMin + pointerMax) / 2); + } + + // Walk forward and back on the y axis looking for the closest x,y point. + pointerMin = pointerMid; + pointerMax = pointerMid; + var neighbours = []; + var sourceBlock = connection.sourceBlock_; + if (db.length) { + while (pointerMin >= 0 && checkConnection_(pointerMin)) { + pointerMin--; + } + do { + pointerMax++; + } while (pointerMax < db.length && checkConnection_(pointerMax)); + } + + /** + * Computes if the current connection is within the allowed radius of another + * connection. + * This function is a closure and has access to outside variables. + * @param {number} yIndex The other connection's index in the database. + * @return {boolean} True if the current connection's vertical distance from + * the other connection is less than the allowed radius. + */ + function checkConnection_(yIndex) { + var dx = currentX - db[yIndex].x_; + var dy = currentY - db[yIndex].y_; + var r = Math.sqrt(dx * dx + dy * dy); + if (r <= maxRadius) { + neighbours.push(db[yIndex]); + } + return dy < maxRadius; + } + return neighbours; +}; + +/** + * Initialize a set of connection DBs for a specified workspace. + * @param {!Blockly.Workspace} workspace The workspace this DB is for. + */ +Blockly.ConnectionDB.init = function(workspace) { + // Create four databases, one for each connection type. + var dbList = []; + dbList[Blockly.INPUT_VALUE] = new Blockly.ConnectionDB(); + dbList[Blockly.OUTPUT_VALUE] = new Blockly.ConnectionDB(); + dbList[Blockly.NEXT_STATEMENT] = new Blockly.ConnectionDB(); + dbList[Blockly.PREVIOUS_STATEMENT] = new Blockly.ConnectionDB(); + workspace.connectionDBList = dbList; +}; diff --git a/tests/jsunit/connection_db_test.js b/tests/jsunit/connection_db_test.js index c7f897168..2e53b1aa5 100644 --- a/tests/jsunit/connection_db_test.js +++ b/tests/jsunit/connection_db_test.js @@ -93,3 +93,59 @@ function test_DB_removeConnection() { db.removeConnection_(o3b); verify_DB_('Removing connection #3b', [], db); } + +function test_DB_getNeighbours() { + var db = new Blockly.ConnectionDB(); + + // Search an empty list. + assertEquals(helper_getNeighbours(db, 10 /* x */, 10 /* y */, 100 /* radius */).length, 0); + + // Set up some connections. + for (var i = 0; i < 10; i++) { + db.addConnection_(helper_createConnection(0, i, Blockly.PREVIOUS_STATEMENT)); + } + + // Test block belongs at beginning + var result = helper_getNeighbours(db, 0, 0, 4); + assertEquals(5, result.length); + for (i = 0; i < result.length; i++) { + assertNotEquals(result.indexOf(db[i]), -1); // contains + } + + // Test block belongs at middle + result = helper_getNeighbours(db, 0, 4, 2); + assertEquals(5, result.length); + for (i = 0; i < result.length; i++) { + assertNotEquals(result.indexOf(db[i + 2]), -1); // contains + } + + // Test block belongs at end + result = helper_getNeighbours(db, 0, 9, 4); + assertEquals(5, result.length); + for (i = 0; i < result.length; i++) { + assertNotEquals(result.indexOf(db[i + 5]), -1); // contains + } + + // Test block has no neighbours due to being out of range in the x direction + result = helper_getNeighbours(db, 10, 9, 4); + assertEquals(result.length, 0); + + // Test block has no neighbours due to being out of range in the y direction + result = helper_getNeighbours(db, 0, 19, 4); + assertEquals(result.length, 0); + + // Test block has no neighbours due to being out of range diagonally + result = helper_getNeighbours(db, -2, -2, 2); + assertEquals(result.length, 0); +} + +function helper_getNeighbours(db, x, y, radius) { + return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT), radius); +} + +function helper_createConnection(x, y, type) { + var conn = new Blockly.Connection({workspace: {}}, type); + conn.x_ = x; + conn.y_ = y; + return conn; +} \ No newline at end of file diff --git a/tests/jsunit/db_test.js b/tests/jsunit/db_test.js new file mode 100644 index 000000000..45fc62a4b --- /dev/null +++ b/tests/jsunit/db_test.js @@ -0,0 +1,76 @@ +/** + * @license + * Blockly Tests + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * 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. + */ +'use strict'; + +function test_DB_getNeighbours() { + var db = new Blockly.ConnectionDB(); + + // Search an empty list. + assertEquals(helper_getNeighbours(db, 10 /* x */, 10 /* y */, 100 /* radius */).length, 0); + + // Set up some connections. + for (var i = 0; i < 10; i++) { + db.addConnection_(helper_createConnection(0, i, Blockly.PREVIOUS_STATEMENT)); + } + + // Test block belongs at beginning + var result = helper_getNeighbours(db, 0, 0, 4); + assertEquals(5, result.length); + for (i = 0; i < result.length; i++) { + assertNotEquals(result.indexOf(db[i]), -1); // contains + } + + // Test block belongs at middle + result = helper_getNeighbours(db, 0, 4, 2); + assertEquals(5, result.length); + for (i = 0; i < result.length; i++) { + assertNotEquals(result.indexOf(db[i + 2]), -1); // contains + } + + // Test block belongs at end + result = helper_getNeighbours(db, 0, 9, 4); + assertEquals(5, result.length); + for (i = 0; i < result.length; i++) { + assertNotEquals(result.indexOf(db[i + 5]), -1); // contains + } + + // Test block has no neighbours due to being out of range in the x direction + result = helper_getNeighbours(db, 10, 9, 4); + assertEquals(result.length, 0); + + // Test block has no neighbours due to being out of range in the y direction + result = helper_getNeighbours(db, 0, 19, 4); + assertEquals(result.length, 0); + + // Test block has no neighbours due to being out of range diagonally + result = helper_getNeighbours(db, -2, -2, 2); + assertEquals(result.length, 0); +} + +function helper_getNeighbours(db, x, y, radius) { + return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT), radius); +} + +function helper_createConnection(x, y, type) { + var conn = new Blockly.Connection({workspace: {}}, type); + conn.x_ = x; + conn.y_ = y; + return conn; +} \ No newline at end of file From 0a363803f08a171a3a7b1f3f7fdc723d0c20f6d9 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 26 Feb 2016 14:41:49 -0800 Subject: [PATCH 2/7] Clean up removeConnection; port in more tests. --- core/block_render_svg.js | 1 + core/connection_db.js | 76 +++++++++++++++++------------- tests/jsunit/connection_db_test.js | 61 ++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 34 deletions(-) diff --git a/core/block_render_svg.js b/core/block_render_svg.js index 9edb3de8b..c4d503370 100644 --- a/core/block_render_svg.js +++ b/core/block_render_svg.js @@ -29,6 +29,7 @@ goog.provide('Blockly.BlockSvg.render'); goog.require('Blockly.BlockSvg'); + // UI constants for rendering blocks. /** * Horizontal space between elements. diff --git a/core/connection_db.js b/core/connection_db.js index 606657429..157a46f51 100644 --- a/core/connection_db.js +++ b/core/connection_db.js @@ -63,6 +63,44 @@ Blockly.ConnectionDB.prototype.addConnection_ = function(connection) { connection.inDB_ = true; }; +/** + * Find the given connection. + * Starts by doing a binary search to find the approximate location, then linearly searches + * nearby for the exact connection. + * @param {Blockly.Connection} conn The connection to find. + * @return The index of the connection, or -1 if the connection was not found. + */ +Blockly.ConnectionDB.prototype.findConnection = function(conn) { + if (this.length == 0) { + return -1; + } + + var bestGuess = this.findPositionForConnection_(conn); + if (bestGuess >= this.length) { + // Not in list + return -1; + } + + var yPos = conn.y_; + // Walk forward and back on the y axis looking for the connection. + var pointerMin = bestGuess; + var pointerMax = bestGuess; + while(pointerMin >= 0 && this[pointerMin].y_ == yPos) { + if (this[pointerMin] == conn) { + return pointerMin; + } + pointerMin--; + } + + while (pointerMax < this.length && this[pointerMax].y_ == yPos) { + if (this[pointerMax] == conn) { + return pointerMax; + } + pointerMax++; + } + return -1; +}; + /** * Finds a candidate position for inserting this connection into the list. * This will be in the correct y order but makes no guarantees about ordering in the x axis. @@ -99,41 +137,11 @@ Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) { if (!connection.inDB_) { throw 'Connection not in database.'; } - connection.inDB_ = false; - // Find the connection using a binary search. - // About 10% faster than a linear search using indexOf. - var pointerMin = 0; - var pointerMax = this.length - 2; - var pointerMid = pointerMax; - while (pointerMin < pointerMid) { - if (this[pointerMid].y_ < connection.y_) { - pointerMin = pointerMid; - } else { - pointerMax = pointerMid; - } - pointerMid = Math.floor((pointerMin + pointerMax) / 2); + var removalIndex = this.findConnection(connection); + if (removalIndex == -1) { + throw 'Unable to find connection in connectionDB.'; } - - // Walk forward and back on the y axis looking for the connection. - // When found, splice it out of the array. - pointerMin = pointerMid; - pointerMax = pointerMid; - while (pointerMin >= 0 && this[pointerMin].y_ == connection.y_) { - if (this[pointerMin] == connection) { - this.splice(pointerMin, 1); - return; - } - pointerMin--; - } - do { - if (this[pointerMax] == connection) { - this.splice(pointerMax, 1); - return; - } - pointerMax++; - } while (pointerMax < this.length && - this[pointerMax].y_ == connection.y_); - throw 'Unable to find connection in connectionDB.'; + this.splice(removalIndex, 1); }; /** diff --git a/tests/jsunit/connection_db_test.js b/tests/jsunit/connection_db_test.js index 2e53b1aa5..cd60bf06d 100644 --- a/tests/jsunit/connection_db_test.js +++ b/tests/jsunit/connection_db_test.js @@ -139,6 +139,67 @@ function test_DB_getNeighbours() { assertEquals(result.length, 0); } +function test_DB_findPositionForConnection() { + var db = new Blockly.ConnectionDB(); + db.addConnection_(helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT)); + db.addConnection_(helper_createConnection(0, 1, Blockly.PREVIOUS_STATEMENT)); + db.addConnection_(helper_createConnection(0, 2, Blockly.PREVIOUS_STATEMENT)); + db.addConnection_(helper_createConnection(0, 4, Blockly.PREVIOUS_STATEMENT)); + db.addConnection_(helper_createConnection(0, 5, Blockly.PREVIOUS_STATEMENT)); + + assertEquals(5, db.length); + var conn = helper_createConnection(0, 3, Blockly.PREVIOUS_STATEMENT); + assertEquals(3, db.findPositionForConnection_(conn)); +} + +function test_DB_findConnection() { + var db = new Blockly.ConnectionDB(); + for (var i = 0; i < 10; i++) { + db.addConnection_(helper_createConnection(i, 0, Blockly.PREVIOUS_STATEMENT)); + db.addConnection_(helper_createConnection(0, i, Blockly.PREVIOUS_STATEMENT)); + } + + var conn = helper_createConnection(3, 3, Blockly.PREVIOUS_STATEMENT); + db.addConnection_(conn); + assertEquals(conn, db[db.findConnection(conn)]); + + conn = helper_createConnection(3, 3, Blockly.PREVIOUS_STATEMENT); + assertEquals(-1, db.findConnection(conn)); +} + +function test_DB_ordering() { + var db = new Blockly.ConnectionDB(); + for (var i = 0; i < 10; i++) { + db.addConnection_(helper_createConnection(0, 9 - i, Blockly.PREVIOUS_STATEMENT)); + } + + for (i = 0; i < 10; i++) { + assertEquals(i, db[i].y_); + } + + // quasi-random + var xCoords = [-29, -47, -77, 2, 43, 34, -59, -52, -90, -36, -91, 38, 87, -20, 60, 4, -57, + 65, -37, -81, 57, 58, -96, 1, 67, -79, 34, 93, -90, -99, -62, 4, 11, -36, -51, -72, + 3, -50, -24, -45, -92, -38, 37, 24, -47, -73, 79, -20, 99, 43, -10, -87, 19, 35, + -62, -36, 49, 86, -24, -47, -89, 33, -44, 25, -73, -91, 85, 6, 0, 89, -94, 36, -35, + 84, -9, 96, -21, 52, 10, -95, 7, -67, -70, 62, 9, -40, -95, -9, -94, 55, 57, -96, + 55, 8, -48, -57, -87, 81, 23, 65]; + var yCoords = [-81, 82, 5, 47, 30, 57, -12, 28, 38, 92, -25, -20, 23, -51, 73, -90, 8, 28, + -51, -15, 81, -60, -6, -16, 77, -62, -42, -24, 35, 95, -46, -7, 61, -16, 14, 91, 57, + -38, 27, -39, 92, 47, -98, 11, -33, -72, 64, 38, -64, -88, -35, -59, -76, -94, 45, + -25, -100, -95, 63, -97, 45, 98, 99, 34, 27, 52, -18, -45, 66, -32, -38, 70, -73, + -23, 5, -2, -13, -9, 48, 74, -97, -11, 35, -79, -16, -77, 83, -57, -53, 35, -44, + 100, -27, -15, 5, 39, 33, -19, -20, -95]; + for (i = 0; i < xCoords.length; i++) { + db.addConnection_(helper_createConnection(xCoords[i], yCoords[i], + Blockly.PREVIOUS_STATEMENT)); + } + + for (i = 1; i < xCoords.length; i++) { + assertTrue(db[i].y_ >= db[i - 1].y_); + } +} + function helper_getNeighbours(db, x, y, radius) { return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT), radius); } From 1b1d777bc598826203363cd642474b8421da2da8 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 26 Feb 2016 16:17:17 -0800 Subject: [PATCH 3/7] Add isConnectionAllowed and tests for it --- core/connection.js | 174 ++++++++++++++++------------- core/connection_db.js | 120 ++++++++++++++++++++ tests/jsunit/connection_db_test.js | 58 ++++++++++ 3 files changed, 273 insertions(+), 79 deletions(-) diff --git a/core/connection.js b/core/connection.js index fa4205620..595a84b86 100644 --- a/core/connection.js +++ b/core/connection.js @@ -152,6 +152,17 @@ Blockly.Connection.prototype.isSuperior = function() { this.type == Blockly.NEXT_STATEMENT; }; +/** + * Returns the distance between this connection and another connection. + * @param {Blockly.Connection} otherConnection The other connection to measure the distance to. + * @return {number} The distance between connections. + */ +Blockly.Connection.prototype.distanceFrom = function(otherConnection) { + var xDiff = this.x_ - otherConnection.x_; + var yDiff = this.y_ - otherConnection.y_; + return Math.sqrt(xDiff * xDiff + yDiff * yDiff); +}; + /** * Checks whether the current connection can connect with the target * connection. @@ -546,94 +557,99 @@ Blockly.Connection.prototype.tighten_ = function() { * another connection or null, and 'radius' which is the distance. */ Blockly.Connection.prototype.closest = function(maxLimit, dx, dy) { - if (this.targetConnection) { - // Don't offer to connect to a connection that's already connected. - return {connection: null, radius: maxLimit}; + var closestConnection = this.dbOpposite_.searchForClosest(this, maxLimit, dx, dy); + if (closestConnection) { + return {connection: closestConnection, radius: this.distanceFrom(closestConnection)}; } - // Determine the opposite type of connection. - var db = this.dbOpposite_; + return {connection: null, radius: maxLimit}; + // if (this.targetConnection) { + // // Don't offer to connect to a connection that's already connected. + // return {connection: null, radius: maxLimit}; + // } + // // Determine the opposite type of connection. + // var db = this.dbOpposite_; - // Since this connection is probably being dragged, add the delta. - var currentX = this.x_ + dx; - var currentY = this.y_ + dy; + // // Since this connection is probably being dragged, add the delta. + // var currentX = this.x_ + dx; + // var currentY = this.y_ + dy; - // Find the closest y location. - var candidatePosition = db.findPositionForConnection_(this); + // // Find the closest y location. + // var candidatePosition = db.findPositionForConnection_(this); - // Walk forward and back on the y axis looking for the closest x,y point. - var pointerMin = candidatePosition; - var pointerMax = candidatePosition; - var closestConnection = null; - var sourceBlock = this.sourceBlock_; - var thisConnection = this; - if (db.length) { - while (pointerMin >= 0 && checkConnection_(pointerMin)) { - pointerMin--; - } - do { - pointerMax++; - } while (pointerMax < db.length && checkConnection_(pointerMax)); - } + // // Walk forward and back on the y axis looking for the closest x,y point. + // var pointerMin = candidatePosition; + // var pointerMax = candidatePosition; + // var closestConnection = null; + // var sourceBlock = this.sourceBlock_; + // var thisConnection = this; + // if (db.length) { + // while (pointerMin >= 0 && checkConnection_(pointerMin)) { + // pointerMin--; + // } + // do { + // pointerMax++; + // } while (pointerMax < db.length && checkConnection_(pointerMax)); + // } - /** - * Computes if the current connection is within the allowed radius of another - * connection. - * This function is a closure and has access to outside variables. - * @param {number} yIndex The other connection's index in the database. - * @return {boolean} True if the search needs to continue: either the current - * connection's vertical distance from the other connection is less than - * the allowed radius, or if the connection is not compatible. - * @private - */ - function checkConnection_(yIndex) { - var connection = db[yIndex]; - if (connection.type == Blockly.OUTPUT_VALUE || - connection.type == Blockly.PREVIOUS_STATEMENT) { - // Don't offer to connect an already connected left (male) value plug to - // an available right (female) value plug. Don't offer to connect the - // bottom of a statement block to one that's already connected. - if (connection.targetConnection) { - return true; - } - } - // Offering to connect the top of a statement block to an already connected - // connection is ok, we'll just insert it into the stack. + // /** + // * Computes if the current connection is within the allowed radius of another + // * connection. + // * This function is a closure and has access to outside variables. + // * @param {number} yIndex The other connection's index in the database. + // * @return {boolean} True if the search needs to continue: either the current + // * connection's vertical distance from the other connection is less than + // * the allowed radius, or if the connection is not compatible. + // * @private + // */ + // function checkConnection_(yIndex) { + // var connection = db[yIndex]; + // if (connection.type == Blockly.OUTPUT_VALUE || + // connection.type == Blockly.PREVIOUS_STATEMENT) { + // // Don't offer to connect an already connected left (male) value plug to + // // an available right (female) value plug. Don't offer to connect the + // // bottom of a statement block to one that's already connected. + // if (connection.targetConnection) { + // return true; + // } + // } + // // Offering to connect the top of a statement block to an already connected + // // connection is ok, we'll just insert it into the stack. - // Offering to connect the left (male) of a value block to an already - // connected value pair is ok, we'll splice it in. - // However, don't offer to splice into an unmovable block. - if (connection.type == Blockly.INPUT_VALUE && - connection.targetConnection && - !connection.targetBlock().isMovable() && - !connection.targetBlock().isShadow()) { - return true; - } + // // Offering to connect the left (male) of a value block to an already + // // connected value pair is ok, we'll splice it in. + // // However, don't offer to splice into an unmovable block. + // if (connection.type == Blockly.INPUT_VALUE && + // connection.targetConnection && + // !connection.targetBlock().isMovable() && + // !connection.targetBlock().isShadow()) { + // return true; + // } - // Do type checking. - if (!thisConnection.checkType_(connection)) { - return true; - } + // // Do type checking. + // if (!thisConnection.checkType_(connection)) { + // return true; + // } - // Don't let blocks try to connect to themselves or ones they nest. - var targetSourceBlock = connection.sourceBlock_; - do { - if (sourceBlock == targetSourceBlock) { - return true; - } - targetSourceBlock = targetSourceBlock.getParent(); - } while (targetSourceBlock); + // // Don't let blocks try to connect to themselves or ones they nest. + // var targetSourceBlock = connection.sourceBlock_; + // do { + // if (sourceBlock == targetSourceBlock) { + // return true; + // } + // targetSourceBlock = targetSourceBlock.getParent(); + // } while (targetSourceBlock); - // Only connections within the maxLimit radius. - var dx = currentX - connection.x_; - var dy = currentY - connection.y_; - var r = Math.sqrt(dx * dx + dy * dy); - if (r <= maxLimit) { - closestConnection = connection; - maxLimit = r; - } - return Math.abs(dy) < maxLimit; - } - return {connection: closestConnection, radius: maxLimit}; + // // Only connections within the maxLimit radius. + // var dx = currentX - connection.x_; + // var dy = currentY - connection.y_; + // var r = Math.sqrt(dx * dx + dy * dy); + // if (r <= maxLimit) { + // closestConnection = connection; + // maxLimit = r; + // } + // return Math.abs(dy) < maxLimit; + // } + // return {connection: closestConnection, radius: maxLimit}; }; /** diff --git a/core/connection_db.js b/core/connection_db.js index 157a46f51..bdbc012b3 100644 --- a/core/connection_db.js +++ b/core/connection_db.js @@ -204,6 +204,126 @@ Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) { return neighbours; }; + +Blockly.ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) { + return (Math.abs(this[index].y_ - baseY) <= maxRadius); +} + +/** + * Find the closest compatible connection to this connection. + * @param {Blockly.Connection} conn The connection searching for a compatible mate. + * @param {number} maxRadius The maximum radius to another connection. + * @param {number} dx Horizontal offset between this connection's location + * in the database and the current location (as a result of dragging). + * @param {number} dy Vertical offset between this connection's location + * in the database and the current location (as a result of dragging). + * @return {!{connection: ?Blockly.Connection, radius: number}} Contains two properties: 'connection' which is either + * another connection or null, and 'radius' which is the distance. + */ +Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx, dy) { + // Don't bother. + if (this.length == 0) { + return null; + } + + // Stash the values of x and y from before the drag. + var baseY = conn.y_; + var baseX = conn.x_; + + conn.x_ = baseX + dx; + conn.y_ = baseY + dy; + + // findPositionForConnection finds an index for insertion, which is always after any + // block with the same y index. We want to search both forward and back, so search + // on both sides of the index. + var closestIndex = this.findPositionForConnection_(conn); + + var bestConnection = null; + var bestRadius = maxRadius; + var temp; + + // Walk forward and back on the y axis looking for the closest x,y point. + var pointerMin = closestIndex - 1; + while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y_, maxRadius)) { + temp = this[pointerMin]; + if (isConnectionAllowed(conn, temp, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); + } + pointerMin--; + } + + var pointerMax = closestIndex; + while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_, maxRadius)) { + temp = this[pointerMax]; + if (isConnectionAllowed(conn, temp, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); + } + pointerMax++; + } + + // Reset the values of x and y. + conn.x_ = baseX; + conn.y_ = baseY; + return bestConnection; +}; + +// TODO: fenichel: consider moving this to connection.js +/** + * Check if the two connections can be dragged to connect to each other. + * @param {Blockly.Connection} moving The connection being dragged. + * @param {Blockly.Connection} candidate A nearby connection to check. + * @param {number} maxRadius The maximum radius allowed for connections. + * @return {boolean} True if the connection is allowed, false otherwise. + */ +Blockly.ConnectionDB.prototype.isConnectionAllowed = function(moving, candidate, maxRadius) { + if (moving.distanceFrom(candidate) > maxRadius) { + return false; + } + + // Type checking + var canConnect = moving.canConnectWithReason_(candidate); + if (canConnect != Blockly.Connection.CAN_CONNECT + && canConnect != Blockly.Connection.REASON_MUST_DISCONNECT) { + return false; + } + + // Don't offer to connect an already connected left (male) value plug to + // an available right (female) value plug. Don't offer to connect the + // bottom of a statement block to one that's already connected. + if (candidate.type == Blockly.OUTPUT_VALUE + || candidate.type == Blockly.PREVIOUS_STATEMENT) { + if (candidate.targetConnection) { + return false; + } + } + + // Offering to connect the left (male) of a value block to an already + // connected value pair is ok, we'll splice it in. + // However, don't offer to splice into an unmovable block. + if (candidate.type == Blockly.INPUT_VALUE && + candidate.targetConnection && + !candidate.targetBlock().isMovable() && + !candidate.targetBlock().isShadow()) { + return true; + } + + // Don't let blocks try to connect to themselves or ones they nest. + var targetSourceBlock = candidate.sourceBlock_; + var sourceBlock = moving.sourceBlock_; + if (targetSourceBlock && sourceBlock) { + do { + if (sourceBlock == targetSourceBlock) { + return true; + } + targetSourceBlock = targetSourceBlock.getParent(); + } while (targetSourceBlock); + } + + return true; +}; + /** * Initialize a set of connection DBs for a specified workspace. * @param {!Blockly.Workspace} workspace The workspace this DB is for. diff --git a/tests/jsunit/connection_db_test.js b/tests/jsunit/connection_db_test.js index cd60bf06d..c0be95b70 100644 --- a/tests/jsunit/connection_db_test.js +++ b/tests/jsunit/connection_db_test.js @@ -200,10 +200,68 @@ function test_DB_ordering() { } } +function test_DB_isConnectionAllowed() { + var db = new Blockly.ConnectionDB(); + var sharedWorkspace = {}; + // Two connections of opposite types near each other + var one = helper_createConnection(5 /* x */, 10 /* y */, Blockly.INPUT_VALUE); + one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + + var two = helper_createConnection(10 /* x */, 15 /* y */, Blockly.OUTPUT_VALUE); + two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + + assertTrue(db.isConnectionAllowed(one, two, 20.0)); + // Move connections farther apart + two.x_ = 100; + two.y_ = 100; + assertFalse(db.isConnectionAllowed(one, two, 20.0)); + + // Don't offer to connect an already connected left (male) value plug to + // an available right (female) value plug. + var three = helper_createConnection(0, 0, Blockly.OUTPUT_VALUE); + three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + + assertTrue(db.isConnectionAllowed(one, three, 20.0)); + var four = helper_createConnection(0, 0, Blockly.INPUT_VALUE); + four.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + + Blockly.Connection.connectReciprocally(three, four); + assertFalse(db.isConnectionAllowed(one, three, 20.0)); + + // Don't connect two connections on the same block + two.sourceBlock_ = one.sourceBlock_; + assertFalse(db.isConnectionAllowed(one, two, 1000.0)); +} + +// function test_DB_isConnectionAllowedNext() { +// var db = new Blockly.ConnectionDB(); +// var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT); +// one.setInput(new Input.InputValue("test input", "" /* align */, null /* checks */)); + +// var two = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT); +// two.setInput(new Input.InputValue("test input", "" /* align */, null /* checks */)); + +// // Don't offer to connect the bottom of a statement block to one that's already connected. +// varv three = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT); +// assertTrue(db.isConnectionAllowed(one, three, 20.0)); +// three.connectReciprocally_(two); +// assertFalse(db.isConnectionAllowed(one, three, 20.0)); +// } + function helper_getNeighbours(db, x, y, radius) { return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT), radius); } +function helper_makeSourceBlock(sharedWorkspace) { + return {workspace: sharedWorkspace, + parentBlock_: null, + getParent: function() { return null; }, + movable_: true, + isMovable: function() { return true; }, + isShadow: function() { return false; } + }; +} + function helper_createConnection(x, y, type) { var conn = new Blockly.Connection({workspace: {}}, type); conn.x_ = x; From c177fa527684797feb8593337d07a22f6c66c428 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 26 Feb 2016 16:35:08 -0800 Subject: [PATCH 4/7] add searchForClosest --- core/connection.js | 88 ------------------------------ core/connection_db.js | 5 +- tests/jsunit/connection_db_test.js | 15 ----- 3 files changed, 3 insertions(+), 105 deletions(-) diff --git a/core/connection.js b/core/connection.js index 595a84b86..a4cc160c5 100644 --- a/core/connection.js +++ b/core/connection.js @@ -562,94 +562,6 @@ Blockly.Connection.prototype.closest = function(maxLimit, dx, dy) { return {connection: closestConnection, radius: this.distanceFrom(closestConnection)}; } return {connection: null, radius: maxLimit}; - // if (this.targetConnection) { - // // Don't offer to connect to a connection that's already connected. - // return {connection: null, radius: maxLimit}; - // } - // // Determine the opposite type of connection. - // var db = this.dbOpposite_; - - // // Since this connection is probably being dragged, add the delta. - // var currentX = this.x_ + dx; - // var currentY = this.y_ + dy; - - // // Find the closest y location. - // var candidatePosition = db.findPositionForConnection_(this); - - // // Walk forward and back on the y axis looking for the closest x,y point. - // var pointerMin = candidatePosition; - // var pointerMax = candidatePosition; - // var closestConnection = null; - // var sourceBlock = this.sourceBlock_; - // var thisConnection = this; - // if (db.length) { - // while (pointerMin >= 0 && checkConnection_(pointerMin)) { - // pointerMin--; - // } - // do { - // pointerMax++; - // } while (pointerMax < db.length && checkConnection_(pointerMax)); - // } - - // /** - // * Computes if the current connection is within the allowed radius of another - // * connection. - // * This function is a closure and has access to outside variables. - // * @param {number} yIndex The other connection's index in the database. - // * @return {boolean} True if the search needs to continue: either the current - // * connection's vertical distance from the other connection is less than - // * the allowed radius, or if the connection is not compatible. - // * @private - // */ - // function checkConnection_(yIndex) { - // var connection = db[yIndex]; - // if (connection.type == Blockly.OUTPUT_VALUE || - // connection.type == Blockly.PREVIOUS_STATEMENT) { - // // Don't offer to connect an already connected left (male) value plug to - // // an available right (female) value plug. Don't offer to connect the - // // bottom of a statement block to one that's already connected. - // if (connection.targetConnection) { - // return true; - // } - // } - // // Offering to connect the top of a statement block to an already connected - // // connection is ok, we'll just insert it into the stack. - - // // Offering to connect the left (male) of a value block to an already - // // connected value pair is ok, we'll splice it in. - // // However, don't offer to splice into an unmovable block. - // if (connection.type == Blockly.INPUT_VALUE && - // connection.targetConnection && - // !connection.targetBlock().isMovable() && - // !connection.targetBlock().isShadow()) { - // return true; - // } - - // // Do type checking. - // if (!thisConnection.checkType_(connection)) { - // return true; - // } - - // // Don't let blocks try to connect to themselves or ones they nest. - // var targetSourceBlock = connection.sourceBlock_; - // do { - // if (sourceBlock == targetSourceBlock) { - // return true; - // } - // targetSourceBlock = targetSourceBlock.getParent(); - // } while (targetSourceBlock); - - // // Only connections within the maxLimit radius. - // var dx = currentX - connection.x_; - // var dy = currentY - connection.y_; - // var r = Math.sqrt(dx * dx + dy * dy); - // if (r <= maxLimit) { - // closestConnection = connection; - // maxLimit = r; - // } - // return Math.abs(dy) < maxLimit; - // } - // return {connection: closestConnection, radius: maxLimit}; }; /** diff --git a/core/connection_db.js b/core/connection_db.js index bdbc012b3..eb5378c3b 100644 --- a/core/connection_db.js +++ b/core/connection_db.js @@ -141,6 +141,7 @@ Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) { if (removalIndex == -1) { throw 'Unable to find connection in connectionDB.'; } + connection.inDB_ = false; this.splice(removalIndex, 1); }; @@ -246,7 +247,7 @@ Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx, var pointerMin = closestIndex - 1; while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y_, maxRadius)) { temp = this[pointerMin]; - if (isConnectionAllowed(conn, temp, bestRadius)) { + if (this.isConnectionAllowed(conn, temp, bestRadius)) { bestConnection = temp; bestRadius = temp.distanceFrom(conn); } @@ -256,7 +257,7 @@ Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx, var pointerMax = closestIndex; while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_, maxRadius)) { temp = this[pointerMax]; - if (isConnectionAllowed(conn, temp, bestRadius)) { + if (this.isConnectionAllowed(conn, temp, bestRadius)) { bestConnection = temp; bestRadius = temp.distanceFrom(conn); } diff --git a/tests/jsunit/connection_db_test.js b/tests/jsunit/connection_db_test.js index c0be95b70..99ac3e478 100644 --- a/tests/jsunit/connection_db_test.js +++ b/tests/jsunit/connection_db_test.js @@ -233,21 +233,6 @@ function test_DB_isConnectionAllowed() { assertFalse(db.isConnectionAllowed(one, two, 1000.0)); } -// function test_DB_isConnectionAllowedNext() { -// var db = new Blockly.ConnectionDB(); -// var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT); -// one.setInput(new Input.InputValue("test input", "" /* align */, null /* checks */)); - -// var two = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT); -// two.setInput(new Input.InputValue("test input", "" /* align */, null /* checks */)); - -// // Don't offer to connect the bottom of a statement block to one that's already connected. -// varv three = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT); -// assertTrue(db.isConnectionAllowed(one, three, 20.0)); -// three.connectReciprocally_(two); -// assertFalse(db.isConnectionAllowed(one, three, 20.0)); -// } - function helper_getNeighbours(db, x, y, radius) { return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT), radius); } From 76719867348c2689689339d6b265f45c670ef9f8 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Mon, 29 Feb 2016 15:04:07 -0800 Subject: [PATCH 5/7] Move isConnectionAllowed to connection; clean up --- core/connection.js | 92 ++++++++---- core/connection_db.js | 111 ++++----------- tests/jsunit/connection_db_test.js | 219 ++++++++++++++++------------- tests/jsunit/connection_test.js | 108 ++++++++++---- 4 files changed, 303 insertions(+), 227 deletions(-) diff --git a/core/connection.js b/core/connection.js index a4cc160c5..3e5200455 100644 --- a/core/connection.js +++ b/core/connection.js @@ -154,7 +154,8 @@ Blockly.Connection.prototype.isSuperior = function() { /** * Returns the distance between this connection and another connection. - * @param {Blockly.Connection} otherConnection The other connection to measure the distance to. + * @param {!Blockly.Connection} otherConnection The other connection to measure + * the distance to. * @return {number} The distance between connections. */ Blockly.Connection.prototype.distanceFrom = function(otherConnection) { @@ -217,6 +218,60 @@ Blockly.Connection.prototype.checkConnection_ = function(target) { } }; +/** + * Check if the two connections can be dragged to connect to each other. + * @param {Blockly.Connection} candidate A nearby connection to check. + * @param {number} maxRadius The maximum radius allowed for connections. + * @return {boolean} True if the connection is allowed, false otherwise. + */ +Blockly.Connection.prototype.isConnectionAllowed = function(candidate, + maxRadius) { + if (this.distanceFrom(candidate) > maxRadius) { + return false; + } + + // Type checking + var canConnect = this.canConnectWithReason_(candidate); + if (canConnect != Blockly.Connection.CAN_CONNECT + && canConnect != Blockly.Connection.REASON_MUST_DISCONNECT) { + return false; + } + + // Don't offer to connect an already connected left (male) value plug to + // an available right (female) value plug. Don't offer to connect the + // bottom of a statement block to one that's already connected. + if (candidate.type == Blockly.OUTPUT_VALUE + || candidate.type == Blockly.PREVIOUS_STATEMENT) { + if (candidate.targetConnection || this.targetConnection) { + return false; + } + } + + // Offering to connect the left (male) of a value block to an already + // connected value pair is ok, we'll splice it in. + // However, don't offer to splice into an unmovable block. + if (candidate.type == Blockly.INPUT_VALUE && + candidate.targetConnection && + !candidate.targetBlock().isMovable() && + !candidate.targetBlock().isShadow()) { + return false; + } + + // Don't let blocks try to connect to themselves or ones they nest. + var targetSourceBlock = candidate.sourceBlock_; + var sourceBlock = this.sourceBlock_; + if (targetSourceBlock && sourceBlock) { + do { + if (sourceBlock == targetSourceBlock) { + return false; + } + targetSourceBlock = targetSourceBlock.getParent(); + } while (targetSourceBlock); + } + + return true; +}; + /** * Connect this connection to another connection. * @param {!Blockly.Connection} otherConnection Connection to connect to. @@ -513,7 +568,7 @@ Blockly.Connection.prototype.moveTo = function(x, y) { this.y_ = y; // Insert it into its new location in the database. if (!this.hidden_) { - this.db_.addConnection_(this); + this.db_.addConnection(this); } }; @@ -553,13 +608,16 @@ Blockly.Connection.prototype.tighten_ = function() { * in the database and the current location (as a result of dragging). * @param {number} dy Vertical offset between this connection's location * in the database and the current location (as a result of dragging). - * @return {!{connection: ?Blockly.Connection, radius: number}} Contains two properties: 'connection' which is either - * another connection or null, and 'radius' which is the distance. + * @return {!{connection: ?Blockly.Connection, radius: number}} Contains two + * properties:' connection' which is either another connection or null, + * and 'radius' which is the distance. */ Blockly.Connection.prototype.closest = function(maxLimit, dx, dy) { - var closestConnection = this.dbOpposite_.searchForClosest(this, maxLimit, dx, dy); + var closestConnection = this.dbOpposite_.searchForClosest(this, maxLimit, dx, + dy); if (closestConnection) { - return {connection: closestConnection, radius: this.distanceFrom(closestConnection)}; + return {connection: closestConnection, + radius: this.distanceFrom(closestConnection)}; } return {connection: null, radius: maxLimit}; }; @@ -653,22 +711,6 @@ Blockly.Connection.prototype.neighbours_ = function(maxLimit) { // Appearance or lack thereof. -/** - * Returns a shape enum for this connection. - * @return {number} Enum representing shape. - */ -Blockly.Connection.prototype.getOutputShape = function() { - if (!this.check_) return Blockly.Connection.NUMBER; - if (this.check_.indexOf('Boolean') !== -1) { - return Blockly.Connection.BOOLEAN; - } - if (this.check_.indexOf('String') !== -1) { - return Blockly.Connection.STRING; - } - - return Blockly.Connection.NUMBER; -}; - /** * Set whether this connections is hidden (not tracked in a database) or not. * @param {boolean} hidden True if connection is hidden. @@ -678,7 +720,7 @@ Blockly.Connection.prototype.setHidden = function(hidden) { if (hidden && this.inDB_) { this.db_.removeConnection_(this); } else if (!hidden && !this.inDB_) { - this.db_.addConnection_(this); + this.db_.addConnection(this); } }; @@ -756,10 +798,10 @@ Blockly.Connection.prototype.highlight = function() { if (this.type == Blockly.INPUT_VALUE || this.type == Blockly.OUTPUT_VALUE) { var tabWidth = this.sourceBlock_.RTL ? -Blockly.BlockSvg.TAB_WIDTH : Blockly.BlockSvg.TAB_WIDTH; - steps = 'm 0,0 v 4 ' + Blockly.BlockSvg.NOTCH_PATH_DOWN + ' v 4'; + steps = 'm 0,0 ' + Blockly.BlockSvg.TAB_PATH_DOWN + ' v 5'; } else { - steps = 'm 0,0 v -4 ' + Blockly.BlockSvg.NOTCH_PATH_UP + ' v -4'; + steps = 'm -20,0 h 5 ' + Blockly.BlockSvg.NOTCH_PATH_LEFT + ' h 5'; } var xy = this.sourceBlock_.getRelativeToSurfaceXY(); var x = this.x_ - xy.x; diff --git a/core/connection_db.js b/core/connection_db.js index eb5378c3b..b039b5189 100644 --- a/core/connection_db.js +++ b/core/connection_db.js @@ -50,7 +50,7 @@ Blockly.ConnectionDB.constructor = Blockly.ConnectionDB; * @param {!Blockly.Connection} connection The connection to be added. * @private */ -Blockly.ConnectionDB.prototype.addConnection_ = function(connection) { +Blockly.ConnectionDB.prototype.addConnection = function(connection) { if (connection.inDB_) { throw 'Connection already in database.'; } @@ -65,8 +65,8 @@ Blockly.ConnectionDB.prototype.addConnection_ = function(connection) { /** * Find the given connection. - * Starts by doing a binary search to find the approximate location, then linearly searches - * nearby for the exact connection. + * Starts by doing a binary search to find the approximate location, then + * linearly searches nearby for the exact connection. * @param {Blockly.Connection} conn The connection to find. * @return The index of the connection, or -1 if the connection was not found. */ @@ -103,7 +103,8 @@ Blockly.ConnectionDB.prototype.findConnection = function(conn) { /** * Finds a candidate position for inserting this connection into the list. - * This will be in the correct y order but makes no guarantees about ordering in the x axis. + * This will be in the correct y order but makes no guarantees about ordering in + * the x axis. * @param {Blockly.Connection} connection The connection to insert. * @return {number} The candidate index. * @private @@ -148,7 +149,8 @@ Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) { /** * Find all nearby connections to the given connection. * Type checking does not apply, since this function is used for bumping. - * @param {Blockly.Connection} connection The connection whose neighbours should be returned. + * @param {Blockly.Connection} connection The connection whose neighbours should + * be returned. * @param {number} maxRadius The maximum radius to another connection. * @return {!Array.} List of connections. * @private @@ -212,16 +214,18 @@ Blockly.ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) { /** * Find the closest compatible connection to this connection. - * @param {Blockly.Connection} conn The connection searching for a compatible mate. + * @param {Blockly.Connection} conn The connection searching for a compatible + * mate. * @param {number} maxRadius The maximum radius to another connection. * @param {number} dx Horizontal offset between this connection's location * in the database and the current location (as a result of dragging). * @param {number} dy Vertical offset between this connection's location * in the database and the current location (as a result of dragging). - * @return {!{connection: ?Blockly.Connection, radius: number}} Contains two properties: 'connection' which is either + * @return ?Blockly.Connection the closest valid connection. * another connection or null, and 'radius' which is the distance. */ -Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx, dy) { +Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx, + dy) { // Don't bother. if (this.length == 0) { return null; @@ -234,9 +238,9 @@ Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx, conn.x_ = baseX + dx; conn.y_ = baseY + dy; - // findPositionForConnection finds an index for insertion, which is always after any - // block with the same y index. We want to search both forward and back, so search - // on both sides of the index. + // findPositionForConnection finds an index for insertion, which is always + // after any block with the same y index. We want to search both forward + // and back, so search on both sides of the index. var closestIndex = this.findPositionForConnection_(conn); var bestConnection = null; @@ -245,23 +249,25 @@ Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx, // Walk forward and back on the y axis looking for the closest x,y point. var pointerMin = closestIndex - 1; - while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y_, maxRadius)) { - temp = this[pointerMin]; - if (this.isConnectionAllowed(conn, temp, bestRadius)) { - bestConnection = temp; - bestRadius = temp.distanceFrom(conn); - } - pointerMin--; + while (pointerMin >= 0 && + this.isInYRange_(pointerMin, conn.y_, maxRadius)) { + temp = this[pointerMin]; + if (conn.isConnectionAllowed(temp, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); + } + pointerMin--; } var pointerMax = closestIndex; - while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_, maxRadius)) { - temp = this[pointerMax]; - if (this.isConnectionAllowed(conn, temp, bestRadius)) { - bestConnection = temp; - bestRadius = temp.distanceFrom(conn); - } - pointerMax++; + while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_, + maxRadius)) { + temp = this[pointerMax]; + if (conn.isConnectionAllowed(temp, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); + } + pointerMax++; } // Reset the values of x and y. @@ -270,61 +276,6 @@ Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx, return bestConnection; }; -// TODO: fenichel: consider moving this to connection.js -/** - * Check if the two connections can be dragged to connect to each other. - * @param {Blockly.Connection} moving The connection being dragged. - * @param {Blockly.Connection} candidate A nearby connection to check. - * @param {number} maxRadius The maximum radius allowed for connections. - * @return {boolean} True if the connection is allowed, false otherwise. - */ -Blockly.ConnectionDB.prototype.isConnectionAllowed = function(moving, candidate, maxRadius) { - if (moving.distanceFrom(candidate) > maxRadius) { - return false; - } - - // Type checking - var canConnect = moving.canConnectWithReason_(candidate); - if (canConnect != Blockly.Connection.CAN_CONNECT - && canConnect != Blockly.Connection.REASON_MUST_DISCONNECT) { - return false; - } - - // Don't offer to connect an already connected left (male) value plug to - // an available right (female) value plug. Don't offer to connect the - // bottom of a statement block to one that's already connected. - if (candidate.type == Blockly.OUTPUT_VALUE - || candidate.type == Blockly.PREVIOUS_STATEMENT) { - if (candidate.targetConnection) { - return false; - } - } - - // Offering to connect the left (male) of a value block to an already - // connected value pair is ok, we'll splice it in. - // However, don't offer to splice into an unmovable block. - if (candidate.type == Blockly.INPUT_VALUE && - candidate.targetConnection && - !candidate.targetBlock().isMovable() && - !candidate.targetBlock().isShadow()) { - return true; - } - - // Don't let blocks try to connect to themselves or ones they nest. - var targetSourceBlock = candidate.sourceBlock_; - var sourceBlock = moving.sourceBlock_; - if (targetSourceBlock && sourceBlock) { - do { - if (sourceBlock == targetSourceBlock) { - return true; - } - targetSourceBlock = targetSourceBlock.getParent(); - } while (targetSourceBlock); - } - - return true; -}; - /** * Initialize a set of connection DBs for a specified workspace. * @param {!Blockly.Workspace} workspace The workspace this DB is for. diff --git a/tests/jsunit/connection_db_test.js b/tests/jsunit/connection_db_test.js index 99ac3e478..6c76594c8 100644 --- a/tests/jsunit/connection_db_test.js +++ b/tests/jsunit/connection_db_test.js @@ -39,23 +39,23 @@ function verify_DB_(msg, expected, db) { function test_DB_addConnection() { var db = new Blockly.ConnectionDB(); var o2 = {y_: 2, sourceBlock_: {}}; - db.addConnection_(o2); + db.addConnection(o2); verify_DB_('Adding connection #2', [o2], db); var o4 = {y_: 4, sourceBlock_: {}}; - db.addConnection_(o4); + db.addConnection(o4); verify_DB_('Adding connection #4', [o2, o4], db); var o1 = {y_: 1, sourceBlock_: {}}; - db.addConnection_(o1); + db.addConnection(o1); verify_DB_('Adding connection #1', [o1, o2, o4], db); var o3a = {y_: 3, sourceBlock_: {}}; - db.addConnection_(o3a); + db.addConnection(o3a); verify_DB_('Adding connection #3a', [o1, o2, o3a, o4], db); var o3b = {y_: 3, sourceBlock_: {}}; - db.addConnection_(o3b); + db.addConnection(o3b); verify_DB_('Adding connection #3b', [o1, o2, o3b, o3a, o4], db); } @@ -67,12 +67,12 @@ function test_DB_removeConnection() { var o3b = {y_: 3, sourceBlock_: {}}; var o3c = {y_: 3, sourceBlock_: {}}; var o4 = {y_: 4, sourceBlock_: {}}; - db.addConnection_(o1); - db.addConnection_(o2); - db.addConnection_(o3c); - db.addConnection_(o3b); - db.addConnection_(o3a); - db.addConnection_(o4); + db.addConnection(o1); + db.addConnection(o2); + db.addConnection(o3c); + db.addConnection(o3b); + db.addConnection(o3a); + db.addConnection(o4); verify_DB_('Adding connections 1-4', [o1, o2, o3a, o3b, o3c, o4], db); db.removeConnection_(o2); @@ -98,143 +98,169 @@ function test_DB_getNeighbours() { var db = new Blockly.ConnectionDB(); // Search an empty list. - assertEquals(helper_getNeighbours(db, 10 /* x */, 10 /* y */, 100 /* radius */).length, 0); + assertEquals(helper_getNeighbours(db, + 10 /* x */, 10 /* y */, 100 /* radius */).length, 0); // Set up some connections. for (var i = 0; i < 10; i++) { - db.addConnection_(helper_createConnection(0, i, Blockly.PREVIOUS_STATEMENT)); + db.addConnection(helper_createConnection(0, i, + Blockly.PREVIOUS_STATEMENT)); } - // Test block belongs at beginning + // Test block belongs at beginning. var result = helper_getNeighbours(db, 0, 0, 4); assertEquals(5, result.length); for (i = 0; i < result.length; i++) { - assertNotEquals(result.indexOf(db[i]), -1); // contains + assertNotEquals(result.indexOf(db[i]), -1); // contains } - // Test block belongs at middle + // Test block belongs at middle. result = helper_getNeighbours(db, 0, 4, 2); assertEquals(5, result.length); for (i = 0; i < result.length; i++) { - assertNotEquals(result.indexOf(db[i + 2]), -1); // contains + assertNotEquals(result.indexOf(db[i + 2]), -1); // contains } - // Test block belongs at end + // Test block belongs at end. result = helper_getNeighbours(db, 0, 9, 4); assertEquals(5, result.length); for (i = 0; i < result.length; i++) { - assertNotEquals(result.indexOf(db[i + 5]), -1); // contains + assertNotEquals(result.indexOf(db[i + 5]), -1); // contains } - // Test block has no neighbours due to being out of range in the x direction + // Test block has no neighbours due to being out of range in the x direction. result = helper_getNeighbours(db, 10, 9, 4); assertEquals(result.length, 0); - // Test block has no neighbours due to being out of range in the y direction + // Test block has no neighbours due to being out of range in the y direction. result = helper_getNeighbours(db, 0, 19, 4); assertEquals(result.length, 0); - // Test block has no neighbours due to being out of range diagonally + // Test block has no neighbours due to being out of range diagonally. result = helper_getNeighbours(db, -2, -2, 2); assertEquals(result.length, 0); } function test_DB_findPositionForConnection() { - var db = new Blockly.ConnectionDB(); - db.addConnection_(helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT)); - db.addConnection_(helper_createConnection(0, 1, Blockly.PREVIOUS_STATEMENT)); - db.addConnection_(helper_createConnection(0, 2, Blockly.PREVIOUS_STATEMENT)); - db.addConnection_(helper_createConnection(0, 4, Blockly.PREVIOUS_STATEMENT)); - db.addConnection_(helper_createConnection(0, 5, Blockly.PREVIOUS_STATEMENT)); + var db = new Blockly.ConnectionDB(); + db.addConnection(helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT)); + db.addConnection(helper_createConnection(0, 1, Blockly.PREVIOUS_STATEMENT)); + db.addConnection(helper_createConnection(0, 2, Blockly.PREVIOUS_STATEMENT)); + db.addConnection(helper_createConnection(0, 4, Blockly.PREVIOUS_STATEMENT)); + db.addConnection(helper_createConnection(0, 5, Blockly.PREVIOUS_STATEMENT)); - assertEquals(5, db.length); - var conn = helper_createConnection(0, 3, Blockly.PREVIOUS_STATEMENT); - assertEquals(3, db.findPositionForConnection_(conn)); + assertEquals(5, db.length); + var conn = helper_createConnection(0, 3, Blockly.PREVIOUS_STATEMENT); + assertEquals(3, db.findPositionForConnection_(conn)); } function test_DB_findConnection() { - var db = new Blockly.ConnectionDB(); - for (var i = 0; i < 10; i++) { - db.addConnection_(helper_createConnection(i, 0, Blockly.PREVIOUS_STATEMENT)); - db.addConnection_(helper_createConnection(0, i, Blockly.PREVIOUS_STATEMENT)); - } + var db = new Blockly.ConnectionDB(); + for (var i = 0; i < 10; i++) { + db.addConnection(helper_createConnection(i, 0, + Blockly.PREVIOUS_STATEMENT)); + db.addConnection(helper_createConnection(0, i, + Blockly.PREVIOUS_STATEMENT)); + } - var conn = helper_createConnection(3, 3, Blockly.PREVIOUS_STATEMENT); - db.addConnection_(conn); - assertEquals(conn, db[db.findConnection(conn)]); + var conn = helper_createConnection(3, 3, Blockly.PREVIOUS_STATEMENT); + db.addConnection(conn); + assertEquals(conn, db[db.findConnection(conn)]); - conn = helper_createConnection(3, 3, Blockly.PREVIOUS_STATEMENT); - assertEquals(-1, db.findConnection(conn)); + conn = helper_createConnection(3, 3, Blockly.PREVIOUS_STATEMENT); + assertEquals(-1, db.findConnection(conn)); } function test_DB_ordering() { - var db = new Blockly.ConnectionDB(); - for (var i = 0; i < 10; i++) { - db.addConnection_(helper_createConnection(0, 9 - i, Blockly.PREVIOUS_STATEMENT)); - } + var db = new Blockly.ConnectionDB(); + for (var i = 0; i < 10; i++) { + db.addConnection(helper_createConnection(0, 9 - i, + Blockly.PREVIOUS_STATEMENT)); + } - for (i = 0; i < 10; i++) { - assertEquals(i, db[i].y_); - } + for (i = 0; i < 10; i++) { + assertEquals(i, db[i].y_); + } - // quasi-random - var xCoords = [-29, -47, -77, 2, 43, 34, -59, -52, -90, -36, -91, 38, 87, -20, 60, 4, -57, - 65, -37, -81, 57, 58, -96, 1, 67, -79, 34, 93, -90, -99, -62, 4, 11, -36, -51, -72, - 3, -50, -24, -45, -92, -38, 37, 24, -47, -73, 79, -20, 99, 43, -10, -87, 19, 35, - -62, -36, 49, 86, -24, -47, -89, 33, -44, 25, -73, -91, 85, 6, 0, 89, -94, 36, -35, - 84, -9, 96, -21, 52, 10, -95, 7, -67, -70, 62, 9, -40, -95, -9, -94, 55, 57, -96, - 55, 8, -48, -57, -87, 81, 23, 65]; - var yCoords = [-81, 82, 5, 47, 30, 57, -12, 28, 38, 92, -25, -20, 23, -51, 73, -90, 8, 28, - -51, -15, 81, -60, -6, -16, 77, -62, -42, -24, 35, 95, -46, -7, 61, -16, 14, 91, 57, - -38, 27, -39, 92, 47, -98, 11, -33, -72, 64, 38, -64, -88, -35, -59, -76, -94, 45, - -25, -100, -95, 63, -97, 45, 98, 99, 34, 27, 52, -18, -45, 66, -32, -38, 70, -73, - -23, 5, -2, -13, -9, 48, 74, -97, -11, 35, -79, -16, -77, 83, -57, -53, 35, -44, - 100, -27, -15, 5, 39, 33, -19, -20, -95]; - for (i = 0; i < xCoords.length; i++) { - db.addConnection_(helper_createConnection(xCoords[i], yCoords[i], - Blockly.PREVIOUS_STATEMENT)); - } + // quasi-random + var xCoords = [-29, -47, -77, 2, 43, 34, -59, -52, -90, -36, -91, 38, 87, -20, + 60, 4, -57, 65, -37, -81, 57, 58, -96, 1, 67, -79, 34, 93, -90, -99, -62, + 4, 11, -36, -51, -72, 3, -50, -24, -45, -92, -38, 37, 24, -47, -73, 79, + -20, 99, 43, -10, -87, 19, 35, -62, -36, 49, 86, -24, -47, -89, 33, -44, + 25, -73, -91, 85, 6, 0, 89, -94, 36, -35, 84, -9, 96, -21, 52, 10, -95, 7, + -67, -70, 62, 9, -40, -95, -9, -94, 55, 57, -96, 55, 8, -48, -57, -87, 81, + 23, 65]; + var yCoords = [-81, 82, 5, 47, 30, 57, -12, 28, 38, 92, -25, -20, 23, -51, 73, + -90, 8, 28, -51, -15, 81, -60, -6, -16, 77, -62, -42, -24, 35, 95, -46, + -7, 61, -16, 14, 91, 57, -38, 27, -39, 92, 47, -98, 11, -33, -72, 64, 38, + -64, -88, -35, -59, -76, -94, 45, -25, -100, -95, 63, -97, 45, 98, 99, 34, + 27, 52, -18, -45, 66, -32, -38, 70, -73, -23, 5, -2, -13, -9, 48, 74, -97, + -11, 35, -79, -16, -77, 83, -57, -53, 35, -44, 100, -27, -15, 5, 39, 33, + -19, -20, -95]; + for (i = 0; i < xCoords.length; i++) { + db.addConnection(helper_createConnection(xCoords[i], yCoords[i], + Blockly.PREVIOUS_STATEMENT)); + } - for (i = 1; i < xCoords.length; i++) { - assertTrue(db[i].y_ >= db[i - 1].y_); - } + for (i = 1; i < xCoords.length; i++) { + assertTrue(db[i].y_ >= db[i - 1].y_); + } } -function test_DB_isConnectionAllowed() { - var db = new Blockly.ConnectionDB(); - var sharedWorkspace = {}; - // Two connections of opposite types near each other - var one = helper_createConnection(5 /* x */, 10 /* y */, Blockly.INPUT_VALUE); - one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); +function test_SearchForClosest() { + var db = new Blockly.ConnectionDB(); + var sharedWorkspace = {id: "Shared workspace"}; - var two = helper_createConnection(10 /* x */, 15 /* y */, Blockly.OUTPUT_VALUE); - two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + // search an empty list + assertEquals(null, helper_searchDB(db, 10 /* x */, 10 /* y */, + 100 /* radius */)); - assertTrue(db.isConnectionAllowed(one, two, 20.0)); - // Move connections farther apart - two.x_ = 100; - two.y_ = 100; - assertFalse(db.isConnectionAllowed(one, two, 20.0)); + db.addConnection(helper_createConnection(100, 0, Blockly.PREVIOUS_STATEMENT, + sharedWorkspace)); + assertEquals(null, helper_searchDB(db, 0, 0, 5, sharedWorkspace)); - // Don't offer to connect an already connected left (male) value plug to - // an available right (female) value plug. - var three = helper_createConnection(0, 0, Blockly.OUTPUT_VALUE); - three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + db = new Blockly.ConnectionDB(); + for (var i = 0; i < 10; i++) { + var tempConn = helper_createConnection(0, i, Blockly.PREVIOUS_STATEMENT, + sharedWorkspace); + tempConn.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + db.addConnection(tempConn); + } - assertTrue(db.isConnectionAllowed(one, three, 20.0)); - var four = helper_createConnection(0, 0, Blockly.INPUT_VALUE); - four.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + // should be at 0, 9 + var last = db[db.length - 1]; + // correct connection is last in db; many connections in radius + assertEquals(last, helper_searchDB(db, 0, 10, 15, sharedWorkspace)); + // Nothing nearby. + assertEquals(null, helper_searchDB(db, 100, 100, 3, sharedWorkspace)); + // first in db, exact match + assertEquals(db[0], helper_searchDB(db, 0, 0, 0, sharedWorkspace)); - Blockly.Connection.connectReciprocally(three, four); - assertFalse(db.isConnectionAllowed(one, three, 20.0)); + tempConn = helper_createConnection(6, 6, Blockly.PREVIOUS_STATEMENT, + sharedWorkspace); + tempConn.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + db.addConnection(tempConn); + tempConn = helper_createConnection(5, 5, Blockly.PREVIOUS_STATEMENT, + sharedWorkspace); + tempConn.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + db.addConnection(tempConn); - // Don't connect two connections on the same block - two.sourceBlock_ = one.sourceBlock_; - assertFalse(db.isConnectionAllowed(one, two, 1000.0)); + var result = helper_searchDB(db, 4, 6, 3, sharedWorkspace); + assertEquals(5, result.x_); + assertEquals(5, result.y_); } + function helper_getNeighbours(db, x, y, radius) { - return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT), radius); + return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT), + radius); +} + +function helper_searchDB(db, x, y, radius, shared_workspace) { + var tempConn = helper_createConnection(x, y, + Blockly.NEXT_STATEMENT, shared_workspace); + tempConn.sourceBlock_ = helper_makeSourceBlock(shared_workspace); + return db.searchForClosest(tempConn, radius, 0, 0); } function helper_makeSourceBlock(sharedWorkspace) { @@ -247,8 +273,9 @@ function helper_makeSourceBlock(sharedWorkspace) { }; } -function helper_createConnection(x, y, type) { - var conn = new Blockly.Connection({workspace: {}}, type); +function helper_createConnection(x, y, type, opt_shared_workspace) { + var workspace = opt_shared_workspace ? opt_shared_workspace : {}; + var conn = new Blockly.Connection({workspace: workspace}, type); conn.x_ = x; conn.y_ = y; return conn; diff --git a/tests/jsunit/connection_test.js b/tests/jsunit/connection_test.js index b9bcc2b23..b4448b083 100644 --- a/tests/jsunit/connection_test.js +++ b/tests/jsunit/connection_test.js @@ -33,10 +33,14 @@ var dummyWorkspace; function connectionTest_setUp() { dummyWorkspace = {}; - input = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.INPUT_VALUE); - output = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.OUTPUT_VALUE); - previous = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.PREVIOUS_STATEMENT); - next = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.NEXT_STATEMENT); + input = new Blockly.Connection({workspace: dummyWorkspace}, + Blockly.INPUT_VALUE); + output = new Blockly.Connection({workspace: dummyWorkspace}, + Blockly.OUTPUT_VALUE); + previous = new Blockly.Connection({workspace: dummyWorkspace}, + Blockly.PREVIOUS_STATEMENT); + next = new Blockly.Connection({workspace: dummyWorkspace}, + Blockly.NEXT_STATEMENT); } function connectionTest_tearDown() { @@ -48,13 +52,14 @@ function connectionTest_tearDown() { } /** - * These tests check that the reasons for failures to connect are consistent (internal view of - * error states). + * These tests check that the reasons for failures to connect are consistent + * (internal view of error states). */ function testCanConnectWithReason_TargetNull() { connectionTest_setUp(); - assertEquals(Blockly.Connection.REASON_TARGET_NULL, input.canConnectWithReason_(null)); + assertEquals(Blockly.Connection.REASON_TARGET_NULL, + input.canConnectWithReason_(null)); connectionTest_tearDown(); } @@ -62,9 +67,11 @@ function testCanConnectWithReason_TargetNull() { function testCanConnectWithReason_Disconnect() { connectionTest_setUp(); - var tempConnection = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.OUTPUT_VALUE); + var tempConnection = new Blockly.Connection({workspace: dummyWorkspace}, + Blockly.OUTPUT_VALUE); Blockly.Connection.connectReciprocally(input, tempConnection); - assertEquals(Blockly.Connection.REASON_MUST_DISCONNECT, input.canConnectWithReason_(output)); + assertEquals(Blockly.Connection.REASON_MUST_DISCONNECT, + input.canConnectWithReason_(output)); connectionTest_tearDown(); } @@ -73,9 +80,11 @@ function testCanConnectWithReason_DifferentWorkspaces() { connectionTest_setUp(); input = new Blockly.Connection({workspace: {}}, Blockly.INPUT_VALUE); - output = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.OUTPUT_VALUE); + output = new Blockly.Connection({workspace: dummyWorkspace}, + Blockly.OUTPUT_VALUE); - assertEquals(Blockly.Connection.REASON_DIFFERENT_WORKSPACES, input.canConnectWithReason_(output)); + assertEquals(Blockly.Connection.REASON_DIFFERENT_WORKSPACES, + input.canConnectWithReason_(output)); connectionTest_tearDown(); } @@ -86,7 +95,8 @@ function testCanConnectWithReason_Self() { var block = {type_: "test block"}; input.sourceBlock_ = block; - assertEquals(Blockly.Connection.REASON_SELF_CONNECTION, input.canConnectWithReason_(input)); + assertEquals(Blockly.Connection.REASON_SELF_CONNECTION, + input.canConnectWithReason_(input)); connectionTest_tearDown(); } @@ -94,17 +104,25 @@ function testCanConnectWithReason_Self() { function testCanConnectWithReason_Type() { connectionTest_setUp(); - assertEquals(Blockly.Connection.REASON_WRONG_TYPE, input.canConnectWithReason_(previous)); - assertEquals(Blockly.Connection.REASON_WRONG_TYPE, input.canConnectWithReason_(next)); + assertEquals(Blockly.Connection.REASON_WRONG_TYPE, + input.canConnectWithReason_(previous)); + assertEquals(Blockly.Connection.REASON_WRONG_TYPE, + input.canConnectWithReason_(next)); - assertEquals(Blockly.Connection.REASON_WRONG_TYPE, output.canConnectWithReason_(previous)); - assertEquals(Blockly.Connection.REASON_WRONG_TYPE, output.canConnectWithReason_(next)); + assertEquals(Blockly.Connection.REASON_WRONG_TYPE, + output.canConnectWithReason_(previous)); + assertEquals(Blockly.Connection.REASON_WRONG_TYPE, + output.canConnectWithReason_(next)); - assertEquals(Blockly.Connection.REASON_WRONG_TYPE, previous.canConnectWithReason_(input)); - assertEquals(Blockly.Connection.REASON_WRONG_TYPE, previous.canConnectWithReason_(output)); + assertEquals(Blockly.Connection.REASON_WRONG_TYPE, + previous.canConnectWithReason_(input)); + assertEquals(Blockly.Connection.REASON_WRONG_TYPE, + previous.canConnectWithReason_(output)); - assertEquals(Blockly.Connection.REASON_WRONG_TYPE, next.canConnectWithReason_(input)); - assertEquals(Blockly.Connection.REASON_WRONG_TYPE, next.canConnectWithReason_(output)); + assertEquals(Blockly.Connection.REASON_WRONG_TYPE, + next.canConnectWithReason_(input)); + assertEquals(Blockly.Connection.REASON_WRONG_TYPE, + next.canConnectWithReason_(output)); connectionTest_tearDown(); } @@ -112,17 +130,21 @@ function testCanConnectWithReason_Type() { function testCanConnectWithReason_CanConnect() { connectionTest_setUp(); - assertEquals(Blockly.Connection.CAN_CONNECT, previous.canConnectWithReason_(next)); - assertEquals(Blockly.Connection.CAN_CONNECT, next.canConnectWithReason_(previous)); - assertEquals(Blockly.Connection.CAN_CONNECT, input.canConnectWithReason_(output)); - assertEquals(Blockly.Connection.CAN_CONNECT, output.canConnectWithReason_(input)); + assertEquals(Blockly.Connection.CAN_CONNECT, + previous.canConnectWithReason_(next)); + assertEquals(Blockly.Connection.CAN_CONNECT, + next.canConnectWithReason_(previous)); + assertEquals(Blockly.Connection.CAN_CONNECT, + input.canConnectWithReason_(output)); + assertEquals(Blockly.Connection.CAN_CONNECT, + output.canConnectWithReason_(input)); connectionTest_tearDown(); } /** - * The next set of tests checks that exceptions are being thrown at the correct times (external - * view of errors). + * The next set of tests checks that exceptions are being thrown at the correct + * times (external view of errors). */ function testCheckConnection_Self() { connectionTest_setUp(); @@ -222,6 +244,40 @@ function testCheckConnection_TypeNextOutput() { connectionTest_tearDown(); } +function test_isConnectionAllowed() { + var sharedWorkspace = {}; + // Two connections of opposite types near each other. + var one = helper_createConnection(5 /* x */, 10 /* y */, + Blockly.INPUT_VALUE); + one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + + var two = helper_createConnection(10 /* x */, 15 /* y */, + Blockly.OUTPUT_VALUE); + two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + + assertTrue(one.isConnectionAllowed(two, 20.0)); + // Move connections farther apart. + two.x_ = 100; + two.y_ = 100; + assertFalse(one.isConnectionAllowed(two, 20.0)); + + // Don't offer to connect an already connected left (male) value plug to + // an available right (female) value plug. + var three = helper_createConnection(0, 0, Blockly.OUTPUT_VALUE); + three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + + assertTrue(one.isConnectionAllowed(three, 20.0)); + var four = helper_createConnection(0, 0, Blockly.INPUT_VALUE); + four.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace); + + Blockly.Connection.connectReciprocally(three, four); + assertFalse(one.isConnectionAllowed(three, 20.0)); + + // Don't connect two connections on the same block. + two.sourceBlock_ = one.sourceBlock_; + assertFalse(one.isConnectionAllowed(two, 1000.0)); +} + function testCheckConnection_Okay() { connectionTest_setUp(); previous.checkConnection_(next); From 42065aec3dd5977a30c9a9d059eb6b64ec61a2fd Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Mon, 29 Feb 2016 15:08:58 -0800 Subject: [PATCH 6/7] Cleaned up comments --- tests/jsunit/connection_db_test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/jsunit/connection_db_test.js b/tests/jsunit/connection_db_test.js index 6c76594c8..0e7b33401 100644 --- a/tests/jsunit/connection_db_test.js +++ b/tests/jsunit/connection_db_test.js @@ -211,7 +211,7 @@ function test_SearchForClosest() { var db = new Blockly.ConnectionDB(); var sharedWorkspace = {id: "Shared workspace"}; - // search an empty list + // Search an empty list. assertEquals(null, helper_searchDB(db, 10 /* x */, 10 /* y */, 100 /* radius */)); @@ -227,13 +227,13 @@ function test_SearchForClosest() { db.addConnection(tempConn); } - // should be at 0, 9 + // Should be at 0, 9. var last = db[db.length - 1]; - // correct connection is last in db; many connections in radius + // Correct connection is last in db; many connections in radius. assertEquals(last, helper_searchDB(db, 0, 10, 15, sharedWorkspace)); // Nothing nearby. assertEquals(null, helper_searchDB(db, 100, 100, 3, sharedWorkspace)); - // first in db, exact match + // First in db, exact match. assertEquals(db[0], helper_searchDB(db, 0, 0, 0, sharedWorkspace)); tempConn = helper_createConnection(6, 6, Blockly.PREVIOUS_STATEMENT, From d29ffdc283f27633e9896e699d9358ce63bd8fa6 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Mon, 29 Feb 2016 15:50:12 -0800 Subject: [PATCH 7/7] Lint --- core/connection.js | 14 +++---- core/connection_db.js | 92 +++++++++++++++++++++---------------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/core/connection.js b/core/connection.js index 3e5200455..44a213191 100644 --- a/core/connection.js +++ b/core/connection.js @@ -225,23 +225,23 @@ Blockly.Connection.prototype.checkConnection_ = function(target) { * @return {boolean} True if the connection is allowed, false otherwise. */ Blockly.Connection.prototype.isConnectionAllowed = function(candidate, - maxRadius) { + maxRadius) { if (this.distanceFrom(candidate) > maxRadius) { - return false; + return false; } // Type checking var canConnect = this.canConnectWithReason_(candidate); - if (canConnect != Blockly.Connection.CAN_CONNECT - && canConnect != Blockly.Connection.REASON_MUST_DISCONNECT) { - return false; + if (canConnect != Blockly.Connection.CAN_CONNECT && + canConnect != Blockly.Connection.REASON_MUST_DISCONNECT) { + return false; } // Don't offer to connect an already connected left (male) value plug to // an available right (female) value plug. Don't offer to connect the // bottom of a statement block to one that's already connected. - if (candidate.type == Blockly.OUTPUT_VALUE - || candidate.type == Blockly.PREVIOUS_STATEMENT) { + if (candidate.type == Blockly.OUTPUT_VALUE || + candidate.type == Blockly.PREVIOUS_STATEMENT) { if (candidate.targetConnection || this.targetConnection) { return false; } diff --git a/core/connection_db.js b/core/connection_db.js index b039b5189..b64c5e23a 100644 --- a/core/connection_db.js +++ b/core/connection_db.js @@ -149,14 +149,14 @@ Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) { /** * Find all nearby connections to the given connection. * Type checking does not apply, since this function is used for bumping. - * @param {Blockly.Connection} connection The connection whose neighbours should + * @param {!Blockly.Connection} connection The connection whose neighbours should * be returned. * @param {number} maxRadius The maximum radius to another connection. * @return {!Array.} List of connections. * @private */ Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) { - var db = this; + var db = this; var currentX = connection.x_; var currentY = connection.y_; @@ -226,54 +226,54 @@ Blockly.ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) { */ Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx, dy) { - // Don't bother. - if (this.length == 0) { - return null; + // Don't bother. + if (!this.length) { + return null; + } + + // Stash the values of x and y from before the drag. + var baseY = conn.y_; + var baseX = conn.x_; + + conn.x_ = baseX + dx; + conn.y_ = baseY + dy; + + // findPositionForConnection finds an index for insertion, which is always + // after any block with the same y index. We want to search both forward + // and back, so search on both sides of the index. + var closestIndex = this.findPositionForConnection_(conn); + + var bestConnection = null; + var bestRadius = maxRadius; + var temp; + + // Walk forward and back on the y axis looking for the closest x,y point. + var pointerMin = closestIndex - 1; + while (pointerMin >= 0 && + this.isInYRange_(pointerMin, conn.y_, maxRadius)) { + temp = this[pointerMin]; + if (conn.isConnectionAllowed(temp, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); } + pointerMin--; + } - // Stash the values of x and y from before the drag. - var baseY = conn.y_; - var baseX = conn.x_; - - conn.x_ = baseX + dx; - conn.y_ = baseY + dy; - - // findPositionForConnection finds an index for insertion, which is always - // after any block with the same y index. We want to search both forward - // and back, so search on both sides of the index. - var closestIndex = this.findPositionForConnection_(conn); - - var bestConnection = null; - var bestRadius = maxRadius; - var temp; - - // Walk forward and back on the y axis looking for the closest x,y point. - var pointerMin = closestIndex - 1; - while (pointerMin >= 0 && - this.isInYRange_(pointerMin, conn.y_, maxRadius)) { - temp = this[pointerMin]; - if (conn.isConnectionAllowed(temp, bestRadius)) { - bestConnection = temp; - bestRadius = temp.distanceFrom(conn); - } - pointerMin--; + var pointerMax = closestIndex; + while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_, + maxRadius)) { + temp = this[pointerMax]; + if (conn.isConnectionAllowed(temp, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); } + pointerMax++; + } - var pointerMax = closestIndex; - while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_, - maxRadius)) { - temp = this[pointerMax]; - if (conn.isConnectionAllowed(temp, bestRadius)) { - bestConnection = temp; - bestRadius = temp.distanceFrom(conn); - } - pointerMax++; - } - - // Reset the values of x and y. - conn.x_ = baseX; - conn.y_ = baseY; - return bestConnection; + // Reset the values of x and y. + conn.x_ = baseX; + conn.y_ = baseY; + return bestConnection; }; /**