New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added Max Instances Property to Workspace Options #2130
Conversation
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed (or fixed any issues), please reply here (e.g. What to do if you already signed the CLAIndividual signers
Corporate signers
|
I signed it! |
CLAs look good, thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some high level comments on structure.
…k type to max instances). isDuplicate() changed to correctly handle siblings/branches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Structure looks good. Couple more notes on APIs and runtime. Expect one more review to go over nits and style.
Thanks again for making this change!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also create a test block in the 'Basic' category of the playground with the words "limit 3 instances":
tests/playground.html?toolbox=test-blocks
That way this feature may be easily tested.
core/block.js
Outdated
Blockly.Block.prototype.isDuplicatable = function() { | ||
var copyableBlocks = this.getDescendants(true); | ||
// Remove all "next statement" blocks because they will not be copied. | ||
if (this.getNextBlock()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cache the result of getNextBlock rather than calling it twice.
core/block.js
Outdated
|
||
var checkedTypes = []; | ||
for (var i = 0, checkBlock; checkBlock = copyableBlocks[i]; i++) { | ||
if (checkedTypes.includes(checkBlock.type)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Array.prototype.includes isn't ES5; not supported by IE. Use indexOf != -1 instead.
However, checkedTypes really should be a map for many reasons (including the one Erik states below), so this problem goes away. Note that indexOf and includes are both O(n), whereas a map lookup is essentially O(1).
core/workspace.js
Outdated
* @param {!Blockly.Block} block Block to remove. | ||
*/ | ||
Blockly.Workspace.prototype.removeTypedBlock = function(block) { | ||
this.typedBlocksDB_[block.type].splice(this.typedBlocksDB_[block.type].indexOf(block), 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line length.
core/workspace.js
Outdated
*/ | ||
Blockly.Workspace.prototype.removeTypedBlock = function(block) { | ||
this.typedBlocksDB_[block.type].splice(this.typedBlocksDB_[block.type].indexOf(block), 1); | ||
if (this.typedBlocksDB_[block.type].length === 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'!' is shorter than '=== 0'.
core/workspace.js
Outdated
if (this.RTL) { | ||
offset *= -1; | ||
} | ||
blocks.sort(function(a, b) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you pull this function out and make it a static helper function, then it won't need to be redeclared every time getBlocklyByType is called. Better performance.
Suggest:
Blockly.Workspace.prototype.getBlocksByType.sortFunc_ = ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me! 2 things:
- The same sort function is used by Blockly.Workspace.prototype.getTopBlocks, so I figure just declare it a private function of the workspace?
- I'm not sure how to pass the offset var into the sort method if I separate it out. My thought was to set a .offset property of the sort function, and then have it use this.offset, but declaring properties of functions seems like a bad idea (not even sure if it would work).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Good idea.
- I think a property on the function is also a good idea. It's a seldom used but useful ability in JS.
core/workspace.js
Outdated
* @return {!Array.<!Blockly.Block>} The blocks of the given type. | ||
*/ | ||
Blockly.Workspace.prototype.getBlocksByType = function(type, ordered) { | ||
if (this.typedBlocksDB_[type]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using slice(0) is faster than [].concat:
https://jsperf.com/cloning-arrays/22
Also, try reversing this to drop the 'else'.
if (!this.typedBlocksDB_[type]) {
return [];
}
var blocks = this.typedBlocksDB_[type].slice(0);
core/block.js
Outdated
* decendents will put this block over the workspace\'s capacity this block is | ||
* not duplicatable. If duplicating this block and decendents will put any | ||
* type over their maxInstances this block is not duplicatable. | ||
* @return {boolean} True if duplicatable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We prefer comments to end with punctuation. (Silliest nit, ever.)
core/block.js
Outdated
@@ -668,6 +670,43 @@ Blockly.Block.prototype.setMovable = function(movable) { | |||
this.movable_ = movable; | |||
}; | |||
|
|||
/** | |||
* Get whether is block is duplicatable or not. If duplicating this block and | |||
* decendents will put this block over the workspace\'s capacity this block is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the backslash before the apostrophe?
core/block.js
Outdated
/** | ||
* Get whether is block is duplicatable or not. If duplicating this block and | ||
* decendents will put this block over the workspace\'s capacity this block is | ||
* not duplicatable. If duplicating this block and decendents will put any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean: descendants?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still one more 'decendents' on this line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a second instance of decendents -> descendants.
core/blockly.js
Outdated
@@ -98,6 +98,13 @@ Blockly.clipboardXml_ = null; | |||
*/ | |||
Blockly.clipboardSource_ = null; | |||
|
|||
/** | |||
* Copied object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a bit more context here. Something like:
Object copied onto the clipboard.
Don't let the avalanche of comments discourage you. This is an awesome change. |
core/block.js
Outdated
/** | ||
* Get whether is block is duplicatable or not. If duplicating this block and | ||
* decendents will put this block over the workspace\'s capacity this block is | ||
* not duplicatable. If duplicating this block and decendents will put any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a second instance of decendents -> descendants.
core/blockly.js
Outdated
@@ -273,6 +281,7 @@ Blockly.copy_ = function(toCopy) { | |||
} | |||
Blockly.clipboardXml_ = xml; | |||
Blockly.clipboardSource_ = toCopy.workspace; | |||
Blockly.copiedObject_ = toCopy; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missed this before: What happens if the block on the clipboard gets deleted? I think you may have to optimistically create the copy and then undo it if it causes a count to go over the limit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thought about it some more. Instead of putting the block stack to copy here can you put the map of typecounts? That way it'll be quick to check when pasting and you won't need to undo anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a good solution, but could you give me some tips on implimentation?
Currently the map of typecounts is stored as a local variable inside the isDuplicatable function. It could be moved to a property of the block object, but to get an updated version of it you'd need to call some getCopyableBlocksTypeCounts() function (since isDuplicatable stops once it runs into a type that goes over the remainingCapacityOfType).
So if the above doesn't have any gaps in logic, we have two options:
- getCopyableBlocksTypeCounts() & isDuplicatable() share duplicate code.
- isDuplicatable() calls getCopyableBlocksTypeCounts() and then iterates over the types (like blockly core would do on paste).
Idk if I should minimize iterations, duplicate code, or if there is a different solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's go with 2. It'll be easier to maintain and visiting every block once shouldn't be too bad.
The method probably belongs in Blockly.utils.getBlockTypeCounts(block).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM for JS style. Wait for Erik to also approve.
core/block.js
Outdated
/** | ||
* Get whether is block is duplicatable or not. If duplicating this block and | ||
* decendents will put this block over the workspace\'s capacity this block is | ||
* not duplicatable. If duplicating this block and decendents will put any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still one more 'decendents' on this line.
core/block.js
Outdated
return false; | ||
} | ||
|
||
var copyableBlocksTypeCounts = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Object.create(null) would be better than {}, since if there's ever a block type named "constructor", or "toString", the results would be unexpected.
core/workspace.js
Outdated
@@ -189,6 +194,54 @@ Blockly.Workspace.prototype.getTopBlocks = function(ordered) { | |||
return blocks; | |||
}; | |||
|
|||
/** Add a block to the list of blocks keyed by type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First line of a JSDoc should be on the next line (/** should be on its own). Here and method below.
core/workspace.js
Outdated
if (this.RTL) { | ||
offset *= -1; | ||
} | ||
blocks.sort(function(a, b) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Good idea.
- I think a property on the function is also a good idea. It's a seldom used but useful ability in JS.
.eslintrc
Outdated
@@ -1,68 +0,0 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Undelete this file (from memory I think you should run git checkout develop -- .eslintrc
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I just noticed, my dad was helping me get set up with a better dev environment last night and must have commited some stuff.
Should I remove the changes to the .gitignore as well? (it added anything beneath idea/ to the ignore list)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, go ahead and revert that as well.
…. Fixed nits. Undeleted .eslintrc
…orkspace. Changed clipboard to save typeCountsMap rather than object.
Everything looks good to me. Thanks again for this PR! |
The basics
The details
Resolves
Issue 1435
Also resolves a bug I could not find an issue for (I assume because no one noticed) where you could not duplicate a block, even if there was enough remaining capacity in the workspace to do so, because the context menu was checking against block.getDecendants() which includes blocks that do not get duplicated.
Proposed Changes
Reason for Changes
Adding this feature gives additional flexibility to event-driven and multi-threaded type Blockly applications. It also suggests creating Blockly games that force you to work more "efficiently" (with a limit number, and types, of blocks).
Test Coverage
I tested the changes by following this proceedure:
This proceedure tests all of the features of this PR:
Tested on:
Additional Information