文章目录

  • 参考
  • 入手
  • Block
    • ShadowBlock
    • InsertionMarker
  • Comment
  • Input
    • 类型
  • Connection
    • 作用
    • 连接事务
    • ShadowBlock
  • Variable
  • Field
    • Field Variable
  • Toolbox
  • Flyout
  • Workspace
  • Events
    • 点击
    • 拖拽
      • 开始
      • 结束
    • 触发监听器
    • 撤销、重做
      • Blockly.Events.Abstract.group
      • Blockly.Events.recordUndo
    • 复制
    • 粘贴
  • To XML
    • workspaceToDom
      • blockToDom
        • allFieldsToDom_
  • From XML
    • domToWorkspace
    • domToBlock
  • Generator

参考

https://blockly.games/
https://www.scratch-cn.cn/
https://developers.google.com/blockly
https://developers.google.com/closure
创建块
https://github.com/google/blockly


入手

Core下面的代码最重要
看代码用tests/playground.html,浏览器打开后,F12,搜索js代码进行调试,入口是inject,搜函数方法:xxx = function


从Export To XML这部分开始看比较好,因为几乎涉及到所有的主要知识。即搭几个block,然后点击Export To XML,一边调试一边学习。

可以导入这个XML:

<xml xmlns="https://developers.google.com/blockly/xml"><variables><variable id="e]VSmXQo9)T`Inl.!G~+">jk</variable></variables><block type="math_change" id="k{-4Vg3N}YNs)GakL8C7" x="63" y="263"><field name="VAR" id="e]VSmXQo9)T`Inl.!G~+">jk</field><comment pinned="true" h="80" w="160">just do it</comment><value name="DELTA"><shadow type="math_number" id="4/a5LJ@rH/6k)XzdZEy_"><field name="NUM">10</field></shadow><block type="variables_get" id="ACH`~Qn~;z`-xK)C3!un"><field name="VAR" id="e]VSmXQo9)T`Inl.!G~+">jk</field></block></value><next><block type="controls_if" id="1yMp!C~8SRc`uT^SIiL^"><mutation else="1"></mutation><value name="IF0"><block type="variables_get" id=",5IYpIV(g`Op;!*v,ChI"><field name="VAR" id="e]VSmXQo9)T`Inl.!G~+">jk</field></block></value><statement name="DO0"><block type="controls_repeat_ext" id="B,${f]buS#@h$O_kw2zX"><value name="TIMES"><shadow type="math_number" id="vAH_Zqo8G^WF4,O~6Y/k"><field name="NUM">10</field></shadow><block type="variables_get" id="9wgVr5@H86PA]/34;a-W"><field name="VAR" id="e]VSmXQo9)T`Inl.!G~+">jk</field></block></value><statement name="DO"><block type="text_print" id="dst[6cXx+ari!6bW~0Wu"><value name="TEXT"><shadow type="text" id="4E;(Ulrwjoe:H5W3ZsS)"><field name="TEXT">jkchen</field></shadow></value></block></statement><next><block type="text_print" id="45LjNr+=dwdM:?2)/K+E"><value name="TEXT"><shadow type="text" id="|)dj`Gz()w8ULh:v=Foz"><field name="TEXT">chen</field></shadow></value></block></next></block></statement><statement name="ELSE"><block type="text_print" id="L?3{(3{=XHwYv84ae-?["><value name="TEXT"><shadow type="text" id="40[!#J*a9f81~*9)Z;;S"><field name="TEXT">chen</field></shadow></value></block></statement></block></next></block>
</xml>

调试的时候可以通过svgGroup_查看变量所指的块是哪个,这个会包含子block


Block

所有的块都是一个block,有一个TopBlock的概念,是指所有拼接的块中的起始块

ShadowBlock

见Connection

InsertionMarker

在拖动块的过程中,用于显示插入位置的块

  • Blockly.Gesture.prototype.startDraggingBlock_也就是开始拖拽时,会产生一个Blockly.BlockDragger对象,然后是属性初始化:

      /*** The insertion marker corresponding to the last block in the stack, if* that's not the same as the first block in the stack.* Set in initAvailableConnections, if at all* @type {Blockly.BlockSvg}* @private*/this.lastMarker_ = null;/*** The insertion marker that shows up between blocks to show where a block* would go if dropped immediately.* @type {Blockly.BlockSvg}* @private*/this.firstMarker_ = this.createMarkerBlock_(this.topBlock_);
    
  • createMarkerBlock_内部,通过sourceBlock.type、sourceBlock.mutationToDom()、sourceBlock.inputList、resultField.setValue(sourceField.getValue())创建一个当前拖拽的block的拷贝(去除其它连接块下的形状)

  • Blockly.BlockDragger.prototype.drag内部使用this.draggedConnectionManager_.update(delta, this.dragTarget_)进行显示更新,通过getCandidate_函数获取最近的Connection

  • 显示内容分别是连接的轮廓、InsertionMarker


Comment

块的注释,记录信息有:pinned="true" h="80" w="160",序列化时不会保留位置信息

或者是独立注释,记录信息有:x="62" y="58" h="100" w="100"


Input

组成block的单元,一个对接输入的部分,下面这个就是有一个input的块,

每个input可以有一个connection用于接收输入,上图中数字10这个块可以通过input.connection.targetBlock()获取

下图中一共有5个input

类型

input有3种类型Blockly.inputTypes

下图中,change块有一个value input(3个field,中间的jk是variable field),连接了右边的jk块。

而右边的jk块的Input属于dummy input,也就是没有提供连接。

而statement则是连接函数块


Connection

作用

块之间的连接,大致分为四种上下左右。

以右为例,Connection包含于input上。即block的inputList存多个Input,每个Input都有一个Connection。

两个部分的连接也比较有意思,参考:

Blockly.Connection.prototype.targetBlock = function() {if (this.isConnected()) {return this.targetConnection.getSourceBlock();}return null;
};

两个块的连接其实是两个接口部分的Connection之间的连接。

除了Inputs里面的Connection以外,Block本身有三个Connection

/** @type {Blockly.Connection} */
this.outputConnection = null;
/** @type {Blockly.Connection} */
this.nextConnection = null;
/** @type {Blockly.Connection} */
this.previousConnection = null;
/** @type {!Array<!Blockly.Input>} */
this.inputList = [];

上下左右分别连接上一个块、下一个块、接收本块输出内容的块、提供输入内容的块

连接事务

具体的连接部分代码如下:

Blockly.Connection.prototype.connect = function(otherConnection) {if (this.targetConnection == otherConnection) {// Already connected together.  NOP.return;}var checker = this.getConnectionChecker();if (checker.canConnect(this, otherConnection, false)) {var eventGroup = Blockly.Events.getGroup();if (!eventGroup) {Blockly.Events.setGroup(true);}// Determine which block is superior (higher in the source stack).if (this.isSuperior()) {// Superior block.this.connect_(otherConnection);} else {// Inferior block.otherConnection.connect_(this);}if (!eventGroup) {Blockly.Events.setGroup(false);}}
};Blockly.Connection.prototype.connect_ = function(childConnection) {var INPUT = Blockly.connectionTypes.INPUT_VALUE;var parentConnection = this;var parentBlock = parentConnection.getSourceBlock();var childBlock = childConnection.getSourceBlock();// Make sure the childConnection is available.if (childConnection.isConnected()) {childConnection.disconnect();}// Make sure the parentConnection is available.var orphan;if (parentConnection.isConnected()) {var shadowDom = parentConnection.getShadowDom(true);parentConnection.shadowDom_ = null;  // Set to null so it doesn't respawn.var target = parentConnection.targetBlock();if (target.isShadow()) {target.dispose(false);} else {parentConnection.disconnect();orphan = target;}parentConnection.shadowDom_ = shadowDom;}// Connect the new connection to the parent.var event;if (Blockly.Events.isEnabled()) {event = new (Blockly.Events.get(Blockly.Events.BLOCK_MOVE))(childBlock);}Blockly.Connection.connectReciprocally_(parentConnection, childConnection);childBlock.setParent(parentBlock);if (event) {event.recordNew();Blockly.Events.fire(event);}// Deal with the orphan if it exists.if (orphan) {var orphanConnection = parentConnection.type === INPUT ?orphan.outputConnection : orphan.previousConnection;var connection = Blockly.Connection.getConnectionForOrphanedConnection(childBlock, /** @type {!Blockly.Connection} */ (orphanConnection));if (connection) {orphanConnection.connect(connection);} else {orphanConnection.onFailedConnect(parentConnection);}}
};

ShadowBlock

ShadowBlock是Connection的默认参数项,例如下图中的10就是ShadowBlock,是change这个block的input的Connection的参数

ShadowBlock是拆不下来的,但是可以使用其它Block替代。
关键的是,这个ShadowBlock是Connection内部的参数:Blockly.Connection.prototype.shadowDom_,即便被其它Block代替,代码内部也会保留。
所以将其它块卸下后,会自动连接回ShadowBlock,并且保留之前的状态

通过isShadow_判断是否是ShadowBlock


Variable

VariableMap对象的variableMap_是一个{String(type):Array[VariableModel]}对象,参考创建变量函数:Blockly.VariableMap.prototype.createVariable

在Block内,Variable的载体为FieldVariable,内部存着Id和Name

和Block的交互,参考Field Variable


Field

组成input的单元,下面这个block有3个field


field有很多类型:

Field Variable

数据存储:

具体和存储变量模块的交互:

Blockly.FieldVariable.dropdownCreate = function() {if (!this.variable_) {throw Error('Tried to call dropdownCreate on a variable field with no' +' variable selected.');}var name = this.getText();var variableModelList = [];if (this.sourceBlock_ && this.sourceBlock_.workspace) {var variableTypes = this.getVariableTypes_();// Get a copy of the list, so that adding rename and new variable options// doesn't modify the workspace's list.for (var i = 0; i < variableTypes.length; i++) {var variableType = variableTypes[i];var variables =this.sourceBlock_.workspace.getVariablesOfType(variableType);variableModelList = variableModelList.concat(variables);}}variableModelList.sort(Blockly.VariableModel.compareByName);var options = [];for (var i = 0; i < variableModelList.length; i++) {// Set the UUID as the internal representation of the variable.options[i] = [variableModelList[i].name, variableModelList[i].getId()];}options.push([Blockly.Msg['RENAME_VARIABLE'], Blockly.RENAME_VARIABLE_ID]);if (Blockly.Msg['DELETE_VARIABLE']) {options.push([Blockly.Msg['DELETE_VARIABLE'].replace('%1', name),Blockly.DELETE_VARIABLE_ID]);}return options;
};

Toolbox

在html内部,有一个

<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-categories" style="display: none"><category name="Logic" categorystyle="logic_category"><block type="controls_if"></block><block type="logic_compare"></block><block type="logic_operation"></block><block type="logic_negate"></block><block type="logic_boolean"></block><block type="logic_null" disabled="true"></block><block type="logic_ternary"></block></category><category name="Loops" categorystyle="loop_category">...</category>...

在入口Blockly.inject里面,opt_options的参数toolbox就是对应的xml element

在Blockly.Options的构造函数内再对这个xml element进行解析

var toolboxJsonDef = Blockly.utils.toolbox.convertToolboxDefToJson(options['toolbox']);


最后存到languageTree

/** @type {?Blockly.utils.toolbox.ToolboxInfo} */
this.languageTree = toolboxJsonDef;

Flyout

Flyout是一个工作空间,点击Toolbox后弹出子菜单积木。积木显示在哪,怎么显示,由Flyout控制。Flyout分为行布局和列布局,如下图为列布局。

点击Toolbox时,通过当前选中的ToolboxItem(传入block),刷新Flyout。刷新其实是创建了一堆isInFlyout的block,render出来。堆栈为:


Workspace

整个工作台

Blockly.inject = function(container, opt_options)

初始化Toolbox,使用toolbox.init()将工具栏渲染出来

if (options.languageTree) {var toolbox = mainWorkspace.getToolbox();var flyout = mainWorkspace.getFlyout(true);if (toolbox) {toolbox.init();} else if (flyout) {// Build a fixed flyout with the root blocks.flyout.init(mainWorkspace);flyout.show(options.languageTree);if (typeof flyout.scrollToStart == 'function') {flyout.scrollToStart();}}
}

核心代码是:

this.contents_.push(toolboxItem);
this.contentMap_[toolboxItem.getId()] = toolboxItem;

将各个categories存起来


Events

把点击、拖动、创建、删除等封装成一个事件,使blockly具有触发、撤销、重做等功能

点击

Blockly.BlockSvg.prototype.select = function() {...var event = new (Blockly.Events.get(Blockly.Events.SELECTED))(oldId, this.id,this.workspace.id);Blockly.Events.fire(event);
Blockly.Events.get = function(eventType) {return Blockly.registry.getClass(Blockly.registry.Type.EVENT, eventType);
};

这里调用了构造函数

Blockly.Events.Selected = function(opt_oldElementId, opt_newElementId,opt_workspaceId) {Blockly.Events.Selected.superClass_.constructor.call(this, opt_workspaceId);...

拖拽

开始

Blockly.BlockDragger.prototype.fireDragStartEvent_ = function() {var event = new (Blockly.Events.get(Blockly.Events.BLOCK_DRAG))(this.draggingBlock_, true, this.draggingBlock_.getDescendants(false));Blockly.Events.fire(event);
};
Blockly.Events.BlockDrag = function(opt_block, opt_isStart, opt_blocks) {var workspaceId = opt_block ? opt_block.workspace.id : undefined;Blockly.Events.BlockDrag.superClass_.constructor.call(this, workspaceId);this.blockId = opt_block ? opt_block.id : null;

结束

Blockly.BlockDragger.prototype.fireDragEndEvent_ = function() {var event = new (Blockly.Events.get(Blockly.Events.BLOCK_DRAG))(this.draggingBlock_, false, this.draggingBlock_.getDescendants(false));Blockly.Events.fire(event);
};

触发监听器

流程:

  1. Blockly.Events.fire

    • 将event加入FIRE_QUEUE_
    • 使用了setTimeout(Blockly.Events.fireNow_, 0),让同时fire多个event的情况下,只触发一次fireNow_
  2. Blockly.Events.fireNow_
    • 使用Blockly.Events.filter进行操作合并(例如相同块的移动操作只保留最后一次)
    • 清空FIRE_QUEUE_
    • 对每个event触发fireChangeListener
  3. Blockly.Workspace.prototype.fireChangeListener
    • 维护.撤销、重做 队列 ,对绑定的监听器this.listeners_[i],进行一次触发
  4. Blockly.Workspace.prototype.addChangeListener
    • 例如在 Blockly.Block.prototype.setOnChange = function(onchangeFn)内,就是通过addChangeListener将指定的函数绑定到当前block的变化上

撤销、重做

首先通过Blockly.ShortcutItems.registerRedo和Blockly.ShortcutItems.registerUndo绑定快捷键,具体触发的函数为:Blockly.Workspace.prototype.undo = function(redo):redo为false是执行撤销操作,反之是重做操作

workspace内部存储了undoStack_和redoStack_两个数组:Array<!Blockly.Events.Abstract>,撤销和重做其实就是把event从两个数组之间来回移动而已。

我们参考一个事件的执行函数run

/*** Run an event.* @param {boolean} _forward True if run forward, false if run backward (undo).*/
Blockly.Events.Abstract.prototype.run = function(_forward) {// Defined by subclasses.
};

在撤销的时候run(false),在重做的时候run(true),事件具体的撤销什么重做什么其实是内部写好的。

Blockly.Events.Abstract.group

事件有个group,用来标记多个事件是否是同一个操作组。在撤销的时候,栈顶的多个event如果是相同group,会被一起撤销。

调用Blockly.Events.setGroup(true),标志着一组事件的开始:

var existingGroup = Blockly.Events.getGroup();
if (!existingGroup) {Blockly.Events.setGroup(true);
}
Blockly.Events.setGroup = function(state) {if (typeof state == 'boolean') {Blockly.Events.group_ = state ? Blockly.utils.genUid() : '';} else {Blockly.Events.group_ = state;}
};

结束为Blockly.Events.setGroup(false)

Blockly.Events.recordUndo

用于标记下一次触发(fire)的event是否需要被记录到undoStack_内,例如撤销操作前undoStack_需要改为false

复制

使用Blockly.BlockSvg.prototype.toCopyData,获取数据:
data.xml:删除Next的XML,用于还原blocks和位置xy
data.source:workspace,用于设置workspace
data.typeCounts:Map{block type : number},用于判断复制之后blocks个数是否到达个数上限

Blockly.copy = function(toCopy) {var data = toCopy.toCopyData();if (data) {Blockly.clipboardXml_ = data.xml;Blockly.clipboardSource_ = data.source;Blockly.clipboardTypeCounts_ = data.typeCounts;}
};
Blockly.Workspace.prototype.isCapacityAvailable = function(typeCountsMap) {if (!this.hasBlockLimits()) {return true;}var copyableBlocksCount = 0;for (var type in typeCountsMap) {if (typeCountsMap[type] > this.remainingCapacityOfType(type)) {return false;}copyableBlocksCount += typeCountsMap[type];}if (copyableBlocksCount > this.remainingCapacity()) {return false;}return true;
};

粘贴

  • 设置workspace:workspace.paste
  • 判断个数限制:isCapacityAvailable
  • 从xml还原:Blockly.Xml.domToBlock
  • 判断位置:和所有已经存在的块比较,避免重合Math.abs(blockX - otherXY.x) <= 1 && Math.abs(blockY - otherXY.y) <= 1)
  • 触发event:Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(block));
Blockly.paste = function() {if (!Blockly.clipboardXml_) {return false;}// Pasting always pastes to the main workspace, even if the copy// started in a flyout workspace.var workspace = Blockly.clipboardSource_;if (workspace.isFlyout) {workspace = workspace.targetWorkspace;}if (Blockly.clipboardTypeCounts_ &&workspace.isCapacityAvailable(Blockly.clipboardTypeCounts_)) {Blockly.Events.setGroup(true);workspace.paste(Blockly.clipboardXml_);Blockly.Events.setGroup(false);return true;}return false;
};


To XML

通过下面的函数生成XML

var xml = Blockly.Xml.workspaceToDom(workspace);
var text = Blockly.Xml.domToPrettyText(xml);

workspaceToDom

  • mutationToDom:加入特定block的信息
    某些模块可能在过程中变化,例如if-ifelse-ifelseif,所以需要加入这些信息,以controls_if为例,当多出else的时候,会加入:

    <mutation else="1"></mutation>
    

    代码如下:

    mutationToDom: function() {if (!this.elseifCount_ && !this.elseCount_) {return null;
    }
    var container = Blockly.utils.xml.createElement('mutation');
    if (this.elseifCount_) {container.setAttribute('elseif', this.elseifCount_);
    }
    if (this.elseCount_) {container.setAttribute('else', 1);
    }return container;
    }
    
    • 例1

      需要记录数量信息,所以:

      <mutation items="4"></mutation>
      
    • 例2

      可以选择从前往后第N、从后往前第N、最后,这里最后这个项是不需要数字的,也就是说会产生结构上的变化,所以:

      <mutation at1="true" at2="false"></mutation>
      
  • allUsedVarModels:获取创建的所有变量

    Blockly.Variables.allUsedVarModels = function(ws) {var blocks = ws.getAllBlocks(false);var variableHash = Object.create(null);// Iterate through every block and add each variable to the hash.for (var i = 0; i < blocks.length; i++) {var blockVariables = blocks[i].getVarModels();if (blockVariables) {for (var j = 0; j < blockVariables.length; j++) {var variable = blockVariables[j];var id = variable.getId();if (id) {variableHash[id] = variable;}}}}// Flatten the hash into a list.var variableList = [];for (var id in variableHash) {variableList.push(variableHash[id]);}return variableList;
    };
    
  • Blockly.Xml.variablesToDom:将变量转化为XML,返回这个Variables的XML标签

    <variables><variable id="e]VSmXQo9)T`Inl.!G~+">jk</variable></variables>
    
  • 然后是所有的comments和blocks

    var comments = workspace.getTopComments(true);
    for (var i = 0, comment; (comment = comments[i]); i++) {xml.appendChild(comment.toXmlWithXY(opt_noId));
    }
    
    var blocks = workspace.getTopBlocks(true);
    for (var i = 0, block; (block = blocks[i]); i++) {xml.appendChild(Blockly.Xml.blockToDomWithXY(block, opt_noId));
    }
    

blockToDom

blockToDomWithXY内部由blockToDom,加上xy信息组成。blockToDom处理当前block的代码如下:

// 把所有Input内需要序列化的Field加入XML
Blockly.Xml.allFieldsToDom_(block, element);
// 记录comment的高h和宽w,以及是否打开pinned
var commentText = block.getCommentText();
if (commentText) {var size = block.commentModel.size;var pinned = block.commentModel.pinned;var commentElement = Blockly.utils.xml.createElement('comment');commentElement.appendChild(Blockly.utils.xml.createTextNode(commentText));commentElement.setAttribute('pinned', pinned);commentElement.setAttribute('h', size.height);commentElement.setAttribute('w', size.width);element.appendChild(commentElement);
}if (block.data) {var dataElement = Blockly.utils.xml.createElement('data');dataElement.appendChild(Blockly.utils.xml.createTextNode(block.data));element.appendChild(dataElement);
}
// 序列化各个Input的连接块
for (var i = 0, input; (input = block.inputList[i]); i++) {var container;var empty = true;// input如果是dummy,就不需要处理(在处理field时已经加入了)if (input.type == Blockly.inputTypes.DUMMY) {continue;} else {var childBlock = input.connection.targetBlock();if (input.type == Blockly.inputTypes.VALUE) {container = Blockly.utils.xml.createElement('value');} else if (input.type == Blockly.inputTypes.STATEMENT) {container = Blockly.utils.xml.createElement('statement');}var shadow = input.connection.getShadowDom();if (shadow && (!childBlock || !childBlock.isShadow())) {container.appendChild(Blockly.Xml.cloneShadow_(shadow, opt_noId));}if (childBlock) {var elem = Blockly.Xml.blockToDom(childBlock, opt_noId);if (elem.nodeType == Blockly.utils.dom.NodeType.ELEMENT_NODE) {container.appendChild(elem);empty = false;}}}container.setAttribute('name', input.name);if (!empty) {element.appendChild(container);}
}
if (block.inputsInline != undefined &&block.inputsInline != block.inputsInlineDefault) {element.setAttribute('inline', block.inputsInline);
}
if (block.isCollapsed()) {element.setAttribute('collapsed', true);
}
if (!block.isEnabled()) {element.setAttribute('disabled', true);
}
if (!block.isDeletable() && !block.isShadow()) {element.setAttribute('deletable', false);
}
if (!block.isMovable() && !block.isShadow()) {element.setAttribute('movable', false);
}
if (!block.isEditable()) {element.setAttribute('editable', false);
}

处理玩当前块后再递归处理下一个块var nextBlock = block.getNextBlock();,通过如下代码组成一个<next><block></block></next>结构

var elem = Blockly.Xml.blockToDom(nextBlock, opt_noId);
if (elem.nodeType == Blockly.utils.dom.NodeType.ELEMENT_NODE) {var container = Blockly.utils.xml.createElement('next');container.appendChild(elem);element.appendChild(container);
}

allFieldsToDom_

将需要各个Input内需要序列化的Field加入XML

Blockly.Xml.allFieldsToDom_ = function(block, element) {for (var i = 0, input; (input = block.inputList[i]); i++) {for (var j = 0, field; (field = input.fieldRow[j]); j++) {var fieldDom = Blockly.Xml.fieldToDom_(field);if (fieldDom) {element.appendChild(fieldDom);}}}
};

例如:

<field name="VAR" id="e]VSmXQo9)T`Inl.!G~+">jk</field>

Field是否加入XML通过isSerializable()判断

Blockly.Field.prototype.isSerializable = function() {var isSerializable = false;if (this.name) {if (this.SERIALIZABLE) {isSerializable = true;} else if (this.EDITABLE) {console.warn('Detected an editable field that was not serializable.' +' Please define SERIALIZABLE property as true on all editable custom' +' fields. Proceeding with serialization.');isSerializable = true;}}return isSerializable;
};

这个图中有3个input,但是每个input都只有一个Label的Field,所以不会序列化


From XML

清空重建

Blockly.Xml.clearWorkspaceAndLoadFromXml = function(xml, workspace)

创建blocks

Blockly.Xml.domToWorkspace = function(xml, workspace)

domToWorkspace

  • 遍历XML的各个childNode
  • 如果是variables,配合variablesFirst确认只有一个variables标签,然后逐个创建变量
    var type = xmlChild.getAttribute('type');
    var id = xmlChild.getAttribute('id');
    var name = xmlChild.textContent;workspace.createVariable(name, type, id);
    
  • 如果是comment……
  • 如果是block,使用Blockly.Xml.domToBlock(xmlChildElement, workspace)创建块(当前块是topBlock)

domToBlock

  • 使用Blockly.Xml.domToBlockHeadless_创建当前整个块组
  • 使用Blockly.Xml.applyNextTagNodes_,通过<next><block>标签,创建这个block的子block(递归Blockly.Xml.domToBlockHeadless_
  • 使用Blockly.Xml.applyInputTagNodes_,根据input的connection,也就是<value><block>标签,创建相应的block(递归Blockly.Xml.domToBlockHeadless_

先确定xml内的inputlist,再找block对应的input

Generator

生成其它脚本语言

Blockly的"JavaScript"属性(其它脚本语言同),是一个Blockly.Generator,通过workspaceToCode函数,生成脚本

Blockly["JavaScript"].workspaceToCode(workspace);

在这个函数内,通过var blocks = workspace.getTopBlocks(true);获取各个分离的block,再使用var line = this.blockToCode(block);将每个block转为脚本

blockToCode内,通过block的类型,获取对应的转换函数,在转换为脚本。这个函数通过遍历各个condition和branch(ifelse、do)组装成一个完整的代码

Blockly.JavaScript['controls_if'] = function(block) {// If/elseif/else condition.var n = 0;var code = '', branchCode, conditionCode;if (Blockly.JavaScript.STATEMENT_PREFIX) {// Automatic prefix insertion is switched off for this block.  Add manually.code += Blockly.JavaScript.injectId(Blockly.JavaScript.STATEMENT_PREFIX,block);}do {conditionCode = Blockly.JavaScript.valueToCode(block, 'IF' + n,Blockly.JavaScript.ORDER_NONE) || 'false';branchCode = Blockly.JavaScript.statementToCode(block, 'DO' + n);if (Blockly.JavaScript.STATEMENT_SUFFIX) {branchCode = Blockly.JavaScript.prefixLines(Blockly.JavaScript.injectId(Blockly.JavaScript.STATEMENT_SUFFIX,block), Blockly.JavaScript.INDENT) + branchCode;}code += (n > 0 ? ' else ' : '') +'if (' + conditionCode + ') {\n' + branchCode + '}';++n;} while (block.getInput('IF' + n));if (block.getInput('ELSE') || Blockly.JavaScript.STATEMENT_SUFFIX) {branchCode = Blockly.JavaScript.statementToCode(block, 'ELSE');if (Blockly.JavaScript.STATEMENT_SUFFIX) {branchCode = Blockly.JavaScript.prefixLines(Blockly.JavaScript.injectId(Blockly.JavaScript.STATEMENT_SUFFIX,block), Blockly.JavaScript.INDENT) + branchCode;}code += ' else {\n' + branchCode + '}';}return code + '\n';
};

在生成代码之后,再用scrub_函数加入注释。

这是一个重载函数,下面是这个函数在Generator和JavaScript内的定义:

Blockly.Generator.prototype.scrub_ = function(_block, code, _opt_thisOnly) {// Optionally overridereturn code;
};
Blockly.JavaScript.scrub_ = function(block, code, opt_thisOnly) {var commentCode = '';// Only collect comments for blocks that aren't inline.if (!block.outputConnection || !block.outputConnection.targetConnection) {// Collect comment for this block.var comment = block.getCommentText();if (comment) {comment = Blockly.utils.string.wrap(comment, this.COMMENT_WRAP - 3);commentCode += this.prefixLines(comment + '\n', '// ');}// Collect comments for all value arguments.// Don't collect comments for nested statements.for (var i = 0; i < block.inputList.length; i++) {if (block.inputList[i].type == Blockly.inputTypes.VALUE) {var childBlock = block.inputList[i].connection.targetBlock();if (childBlock) {comment = this.allNestedComments(childBlock);if (comment) {commentCode += this.prefixLines(comment, '// ');}}}}}var nextBlock = block.nextConnection && block.nextConnection.targetBlock();var nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock);return commentCode + code + nextCode;
};

神奇的js,这里貌似把JavaScript看成Generator的子类,我们再看下声明部分

Blockly.JavaScript = new Blockly.Generator('JavaScript');
Blockly.Generator = function(name) {this.name_ = name;this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ =new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g');
};

Blockly源码解析相关推荐

  1. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

  2. 谷歌BERT预训练源码解析(三):训练过程

    目录 前言 源码解析 主函数 自定义模型 遮蔽词预测 下一句预测 规范化数据集 前言 本部分介绍BERT训练过程,BERT模型训练过程是在自己的TPU上进行的,这部分我没做过研究所以不做深入探讨.BE ...

  3. 谷歌BERT预训练源码解析(一):训练数据生成

    目录 预训练源码结构简介 输入输出 源码解析 参数 主函数 创建训练实例 下一句预测&实例生成 随机遮蔽 输出 结果一览 预训练源码结构简介 关于BERT,简单来说,它是一个基于Transfo ...

  4. Gin源码解析和例子——中间件(middleware)

    在<Gin源码解析和例子--路由>一文中,我们已经初识中间件.本文将继续探讨这个技术.(转载请指明出于breaksoftware的csdn博客) Gin的中间件,本质是一个匿名回调函数.这 ...

  5. Colly源码解析——结合例子分析底层实现

    通过<Colly源码解析--框架>分析,我们可以知道Colly执行的主要流程.本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现.(转载请指明出于break ...

  6. libev源码解析——定时器监视器和组织形式

    我们先看下定时器监视器的数据结构.(转载请指明出于breaksoftware的csdn博客) /* invoked after a specific time, repeatable (based o ...

  7. libev源码解析——定时器原理

    本文将回答<libev源码解析--I/O模型>中抛出的两个问题.(转载请指明出于breaksoftware的csdn博客) 对于问题1:为什么backend_poll函数需要指定超时?我们 ...

  8. libev源码解析——I/O模型

    在<libev源码解析--总览>一文中,我们介绍过,libev是一个基于事件的循环库.本文将介绍其和事件及循环之间的关系.(转载请指明出于breaksoftware的csdn博客) 目前i ...

  9. libev源码解析——调度策略

    在<libev源码解析--监视器(watcher)结构和组织形式>中介绍过,监视器分为[2,-2]区间5个等级的优先级.等级为2的监视器最高优,然后依次递减.不区分监视器类型和关联的文件描 ...

最新文章

  1. 机器学习数学 — 初等函数求导
  2. linux下A免密码登录B
  3. 处理后台返回文本带空格和换行页面不显示断句的问题
  4. java 内存情况_java查看jvm内存使用情况
  5. dedeCMS版权信息、备案号的调用代码 - 代码大全
  6. 二分法的计算机应用,二分法(数学领域术语)_百度百科
  7. Python学习笔记之字典(二)
  8. 火车票售票系统mysql_今日开售!2020元旦火车票可以买了,春运首日车票也快了→...
  9. 这么多人,AI怎么知道你说的是哪个? | 李飞飞团队CVPR论文+代码
  10. java8的新特性详解-----------Lamda表达式
  11. pyspark入门---通过kmeans分析出租车数据并调用百度API进行可视化
  12. VNC viewer双击鼠标出发ctrl+c
  13. 移动网优大神VoLTE学习笔记(四):主叫信令流程
  14. php 中 normdist,excel中的normdist
  15. 06-jQuery属性操作
  16. 懂车帝与蛋蛋订车两大平台对比
  17. 街霸 隆(Ryu)升龙拳(Syoryuken)动画(四)制作过程中几个版本动画比较一下
  18. Jetson-Xavier-NX刷机+pytorch环境配置+yolov5运行
  19. MySQL自定义函数创建与使用总结
  20. 用计算机弹大白菜鸡毛菜,抖音大白菜、鸡毛菜是什么歌

热门文章

  1. 计算机一级office应用考试题库,2017年计算机一级office题库试题
  2. DBSliderView一个高仿屏幕解锁的控件
  3. 北邮通信原理公开课 杨鸿文老师 课程学习笔记【第二课 复信号】
  4. Android中歌词显示的实现
  5. pdf怎么去除背景水印?实用工具说明
  6. android 涨潮动画加载_My Tide Times(潮汐数据查看器)
  7. Idea配置maven,指定settings.xml文件不生效
  8. 后疫情时代,开元酒店集团挖需求、练内功、担责任
  9. PAT 1018 锤子剪刀布 (20 分)
  10. 4个led数码管从左至右显示123451单片机c语言,51单片机:4位数码管动态显示,从1234开始,每次按S1键加1...