Mutator with same kind of elements

298 views
Skip to first unread message

Licorne Magique

unread,
Mar 6, 2021, 4:43:17 AM3/6/21
to Blockly

Hello,
I'm struggling with a mutator, which is nearly finished:
Image 1.png
But id does not work... When I add an element and try to move the obtained block, it explodes :-D
Image 2.png
It's like it does not save child or element. If someone has an example of something similar to my problem, or if you see an evident problem on my code, that would be great!


Blockly.defineBlocksWithJsonArray(
    [{
            "type": "board_serial_formatCSV",
            "message0": "serialize CSV %1",
            "args0": [{
                    "type": "input_dummy"
                }
            ],
            "message1": "data %1",
            "args1": [{
                    "type": "input_value",
                    "name": "DATA0",
                    "check": ['int', 'float', 'Number', 'Array']
                }
            ],
            "message2": "separator %1",
            "args2": [{
                    "type": "input_value",
                    "name": "SEP0",
                    "check": "String"
                }
            ],
            "previousStatement": null,
            "nextStatement": null,
            "style": "board_blocks",
            "helpUrl": "lien vers le wiki",
            "mutator": "serial_formatCSV_mutator",
            "tooltip": "serial_formatCSV_tooltip"
        }, {
            "type": "mutator_stack",
            "message0": "stack",
            "nextStatement": null,
            "enableContextMenu": null,
            "style": "board_blocks",
            "tooltip": "%{BKY_mutator_this.stack_TOOLTIP}"
        }, {
            "type": "mutator_data",
            "message0": "data to send",
            "previousStatement": null,
            "nextStatement": null,
            "style": "board_blocks",
            "tooltip": "%{BKY_mutator_data_TOOLTIP}"
        }, {
            "type": "mutator_sep",
            "message0": "separator",
            "previousStatement": null,
            "nextStatement": null,
            "style": "board_blocks",
            "tooltip": "%{BKY_mutator_sep_TOOLTIP}"
        }
    ]);

/**
 * Mutator methods added to controls_if blocks.
 * @mixin
 * @augments Blockly.Block
 * @package
 * @readonly
 */
Blockly.Block.FORMATCSV_MUTATOR_MIXIN = {
    dataCount_: 1,
    sepCount_: 1,
    totalCount_: 2,
    stack_: ['d', 's'],

    /**
     * Don't automatically add STATEMENT_PREFIX and STATEMENT_SUFFIX to generated
     * code. These will be handled manually in this block's generators.
     */
    suppressPrefixSuffix: true,

    /**
     * Create XML to represent the number of data and separator inputs.
     * @return {Element} XML storage element.
     * @this {Blockly.Block}
     */
    mutationToDom: function () {
        if (!this.dataCount_ && !this.sepCount_) {
            return null;
        }
        var container = Blockly.utils.xml.createElement('mutation');
        if (this.dataCount_) {
            container.setAttribute('attribData', this.dataCount_);
        }
        if (this.sepCount_) {
            container.setAttribute('attribSep', this.sepCount_);
        }
        return container;
    },
    /**
     * Parse XML to restore the data and separator inputs.
     * @param {!Element} xmlElement XML storage element.
     * @this {Blockly.Block}
     */
    domToMutation: function (xmlElement) {
        this.dataCount_ = parseInt(xmlElement.getAttribute('attribData'), 10) || 0;
        this.sepCount_ = parseInt(xmlElement.getAttribute('attribSep'), 10) || 0;
        this.totalCount_ = this.dataCount_ + this.sepCount_;
        console.log(this.dataCount_);
        console.log(this.sepCount_);
        console.log(this.totalCount_);
        this.rebuildShape_();
    },
    /**
     * Populate the mutator's dialog with this block's components.
     * @param {!Blockly.Workspace} workspace Mutator's workspace.
     * @return {!Blockly.Block} Root block in mutator.
     * @this {Blockly.Block}
     */
    decompose: function (workspace) {
        var containerBlock = workspace.newBlock('mutator_stack');
        containerBlock.initSvg();
        var connection = containerBlock.nextConnection;
        for (var i = 0; i < this.totalCount_; i++) {
            if (this.stack_[i] == 's') {
                var sepBlock = workspace.newBlock('mutator_sep');
                sepBlock.initSvg();
                connection.connect(sepBlock.previousConnection);
                connection = sepBlock.nextConnection;
            } else if (this.stack_[i] == 'd') {
                var dataBlock = workspace.newBlock('mutator_data');
                dataBlock.initSvg();
                connection.connect(dataBlock.previousConnection);
                connection = dataBlock.nextConnection;
            }
        }
        console.log('stack ' + this.stack_);
        return containerBlock;
    },
    /**
     * Reconfigure this block based on the mutator dialog's components.
     * @param {!Blockly.Block} containerBlock Root block in mutator.
     * @this {Blockly.Block}
     */
    compose: function (containerBlock) {
        var clauseBlock = containerBlock.nextConnection.targetBlock();
        // Count number of inputs.
        this.dataCount_ = 0;
        this.sepCount_ = 0;
        this.totalCount_ = 0;
        var dataConnections = [null];
        var separatorConnections = [null];
        while (clauseBlock && !clauseBlock.isInsertionMarker()) {
            switch (clauseBlock.type) {
                case 'mutator_data':
                    this.stack_[this.totalCount_] = 'd';
                    this.dataCount_++;
                    this.totalCount_++;
                    dataConnections.push(clauseBlock.valueConnection_);
            clauseBlock = clauseBlock.nextConnection && clauseBlock.nextConnection.targetBlock();
                    break;
                case 'mutator_sep':
                    this.stack_[this.totalCount_] = 's';
                    this.sepCount_++;
                    this.totalCount_++;
                    separatorConnections.push(clauseBlock.valueConnection_);
            clauseBlock = clauseBlock.nextConnection && clauseBlock.nextConnection.targetBlock();
                    break;
                default:
                    throw TypeError('Unknown block type: ' + clauseBlock.type);
            }
        }
        console.log('step 4');
        this.updateShape_();
        // Reconnect any child blocks.
        this.reconnectChildBlocks_(dataConnections, separatorConnections);
    },
    /**
     * Store pointers to any connected child blocks.
     * @param {!Blockly.Block} containerBlock Root block in mutator.
     * @this {Blockly.Block}
     */
    saveConnections: function (containerBlock) {
        var clauseBlock = containerBlock.nextConnection.targetBlock();
        var i = 0;
        var j = 0;
        while (clauseBlock) {
            switch (clauseBlock.type) {
            case 'mutator_data':
                var input = this.getInput('DATA' + i);
                i++;
                break;
            case 'mutator_sep':
                var input = this.getInput('SEP' + j);
                j++;
                break;
            default:
                throw TypeError('Unknown block type: ' + clauseBlock.type);
            }
            clauseBlock.valueConnection_ = input && input.connection.targetConnection;
            console.log(clauseBlock.valueConnection_);
            clauseBlock = clauseBlock.nextConnection &&
                clauseBlock.nextConnection.targetBlock();
        }
        console.log('i' + i);
        console.log('j' + j);
    },
    /**
     * Reconstructs the block with all child blocks attached.
     * @this {Blockly.Block}
     */
    rebuildShape_: function () {
        var valueConnections = [null];
        var i = 0;
        var j = 0;
        var k = 0;
        while (i < this.totalCount_) {
            switch (this.stack_[i]) {
            case 'd':
                var inputData = this.getInput('DATA' + j);
                valueConnections.push(inputData.connection.targetConnection);
                j++;
                break;
            case 's':
                var inputSep = this.getInput('SEP' + k);
                valueConnections.push(inputSep.connection.targetConnection);
                k++;
                break;
            default:
                throw TypeError('Unknown block type: ' + clauseBlock.type);
            }
            i++;
        }
        // console.log(valueConnections);
        this.updateShape_();
        this.reconnectChildBlocks_(valueConnections);
    },
    /**
     * Modify this block to have the correct number of inputs.
     * @this {Blockly.Block}
     * @private
     */
    updateShape_: function () {
        // Delete everything.
        var i = 0;
        while (this.getInput('DATA' + i)) {
            this.removeInput('DATA' + i);
            i++;
        }
        var i = 0;
        while (this.getInput('SEP' + i)) {
            this.removeInput('SEP' + i);
            i++;
        }
        console
        // Rebuild block.
        var j = 0;
        var k = 0;
        for (i = 0; i < this.totalCount_; i++) {
            switch (this.stack_[i]) {
            case 'd':
                this.appendValueInput('DATA' + j)
                    .setCheck('Number')
                    .appendField("new data");
                j++;
                break;
            case 's':
                this.appendValueInput('SEP' + k)
                    .setCheck('String')
                    .appendField("data separator");
                k++;
                break;
            }
        }
    },
    /**
     * Reconnects child blocks.
     * @param {!Array.<?Blockly.RenderedConnection>} dataConnections List of
     * value connections for 'if' input.
     * @param {!Array.<?Blockly.RenderedConnection>} separatorConnections List of
     * statement connections for 'sep' input.
     * @param {?Blockly.RenderedConnection} elseSeparatorConnection Statement
     * connection for else input.
     * @this {Blockly.Block}
     */
    reconnectChildBlocks_: function (valueConnections) {
        var j = 0;
        var k = 0;
        for (var i = 0; i < this.totalCount_; i++) {
            switch (this.stack_[i]) {
            case 'd':
                Blockly.Mutator.reconnect(valueConnections[i], this, 'DATA' + j);
                j++;
                break;
            case 's':
                Blockly.Mutator.reconnect(valueConnections[i], this, 'SEP' + k);
                k++
                break;
            }
        }
    }
};

Blockly.Extensions.registerMutator('serial_formatCSV_mutator',
    Blockly.Block.FORMATCSV_MUTATOR_MIXIN, null, ['mutator_data', 'mutator_sep']);

Many thanks!

Beka Westberg

unread,
Mar 6, 2021, 7:20:53 PM3/6/21
to blo...@googlegroups.com
Hello,

It looks like in your domToMutation_ function you're updating your dataCount_, sepCount_, and totalCount_, but your stack_ isn't getting updated, which your rebuildShape function uses. Do you think this could be causing a problem? It might be helpful to log which input name is causing getInput() to return null. If you knew what combination of stack index (i) and data index (j) was causing the problem, I think that would really help figure this out!

Sorry I can't be more help :/
--Beka

--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/64f063f4-50a1-42e2-ad17-753a38a8118dn%40googlegroups.com.

Licorne Magique

unread,
Mar 6, 2021, 7:34:03 PM3/6/21
to Blockly
Thanks for your help.
The problem occurs when I add more than 2 element to my stack. Here's a screen capture with 3 elements, and console.log:
Image 9.jpg
The '3' is a console.log(this.totalCount_); in rebuildShape_
Then in my "while (i < this.totalCount_)" I ask for:
console.log(i,j,k);
console.log(this.stack_);
and in switch case I ask for a console.log(inputData); or console.log(inputSep);
So even if "DATA1" exists, during rebuildShape it seems to be the problem. But I can't find why.

Licorne Magique

unread,
Mar 6, 2021, 7:38:14 PM3/6/21
to Blockly
I also have same problem with only 2 data, so the DATA1 inout or SEP1 input never can be found.
The stack_ doesn't seem to need update.
Many thanks for your help.

Beka Westberg

unread,
Mar 8, 2021, 6:20:06 PM3/8/21
to blo...@googlegroups.com
Ok I think the issue here is twofold.

The first issue is that your stack_ is being shared between all instances of your block. This is because all of the properties defined in your mutator object gets mixed in to your block using the mixin function. This function only does a *shallow* copy of the property. So each of your blocks gets a property stack_ that essentially "points" to the same array.

Instead of creating the stack_ property using mixin you'll want to assign it using a helper function. This will make sure that each block gets a fresh stack_ equal to `['d', 's'] when it is created.

Making this change also means that you need to serialize and deserialize your stack_ in mutationToDom and domToMutation. Some options for this are (A) creating separate XML nodes for each option in your stack (the XML spec requires order to be maintained so you'll be good there). This is how the procedure blocks work. (B) You could concatenate all of your values in the stack_ (since they're all one character) and assign that string to a property of your mutator.

If you don't make this change it will cause you problems when you go to mutate multiple blocks and serialize/deserialize them.

I think the second issue is with your rebuildShape_ function and what it assumes/where it is used. Currently it is being called from domToMutation, and it's trying to save the values of connections connected to its inputs. But when domToMutation is called, the only inputs that should exist are ones in your block definition. And there also shouldn't be any blocks connected to inputs, because domToMutation is called before child blocks due to the fact that XML nodes are processed sequentially, and the mutator node always comes before child-block-nodes.

So I think that from domToMutation you don't need to call rebuildShape_, just updateShape_. Does that sound right to you? 

I hope this helps! This is a really crazy mutator haha

Best wishes,
Beka
 

Licorne Magique

unread,
Mar 10, 2021, 12:10:11 PM3/10/21
to Blockly
MANY THANKS! That was just this damn rebuildShape_ , I just changed to updateShape_  and everything works...but not perfectly.
It seems I do not have pb with the stack_, in my code, as I think, it seems to be the instantiated one, I use a this.stack_ created with the mutator.
But when I do a refresh I lost all my connections and child blocks. Go back to investigate...
Reply all
Reply to author
Forward
0 new messages