Skip to content

Commit

Permalink
Fix InstructionTree and Block circular dependency
Browse files Browse the repository at this point in the history
Since 4338632 there's been a circular dependency between InstructionTree
and Block that manifests as a script error due to a non-existent
resource in a seemingly unrelated object. Several components use
InstructionTree.TreeNode including Block, and prior to 4338632 there
were no references to any non-native classes. Split out the block tree
functions to a new BlockTreeUtil class so that InstructionTree no longer
contains references to Block.
  • Loading branch information
dbnicholson committed Aug 1, 2024
1 parent 7386139 commit 047ec47
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 86 deletions.
3 changes: 2 additions & 1 deletion addons/block_code/drag_manager/drag.gd
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extends Control

const Background = preload("res://addons/block_code/ui/blocks/utilities/background/background.gd")
const BlockCanvas = preload("res://addons/block_code/ui/block_canvas/block_canvas.gd")
const BlockTreeUtil = preload("res://addons/block_code/ui/block_tree_util.gd")
const Constants = preload("res://addons/block_code/ui/constants.gd")
const InstructionTree = preload("res://addons/block_code/instruction_tree/instruction_tree.gd")
const Types = preload("res://addons/block_code/types/types.gd")
Expand Down Expand Up @@ -143,7 +144,7 @@ func _snaps_to(node: Node) -> bool:
if _block_scope != top_block.get_entry_statement():
return false
elif top_block:
var tree_scope := InstructionTree.get_tree_scope(top_block)
var tree_scope := BlockTreeUtil.get_tree_scope(top_block)
if tree_scope != "" and _block_scope != tree_scope:
return false

Expand Down
3 changes: 2 additions & 1 deletion addons/block_code/drag_manager/drag_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ signal block_dropped
signal block_modified

const BlockCanvas = preload("res://addons/block_code/ui/block_canvas/block_canvas.gd")
const BlockTreeUtil = preload("res://addons/block_code/ui/block_tree_util.gd")
const Drag = preload("res://addons/block_code/drag_manager/drag.gd")
const InstructionTree = preload("res://addons/block_code/instruction_tree/instruction_tree.gd")
const Picker = preload("res://addons/block_code/ui/picker/picker.gd")
Expand Down Expand Up @@ -50,7 +51,7 @@ func drag_block(block: Block, copied_from: Block = null):

block.disconnect_signals()

var block_scope := InstructionTree.get_tree_scope(block)
var block_scope := BlockTreeUtil.get_tree_scope(block)
if block_scope != "":
_block_canvas.set_scope(block_scope)

Expand Down
77 changes: 0 additions & 77 deletions addons/block_code/instruction_tree/instruction_tree.gd
Original file line number Diff line number Diff line change
Expand Up @@ -56,80 +56,3 @@ static func generate_text_recursive(node: TreeNode, depth: int, out: PackedStrin

if node.next:
generate_text_recursive(node.next, depth, out)


static func generate_script_from_nodes(nodes: Array[Node], bsd: BlockScriptData) -> String:
var entry_blocks_by_entry_statement: Dictionary = {}

for block in nodes:
if !(block is Block):
continue

if block is EntryBlock:
var entry_statement = block.get_entry_statement()
if not entry_blocks_by_entry_statement.has(entry_statement):
entry_blocks_by_entry_statement[entry_statement] = []
entry_blocks_by_entry_statement[entry_statement].append(block)

var script: String = ""

script += "extends %s\n\n" % bsd.script_inherits

for variable in bsd.variables:
script += "var %s: %s\n\n" % [variable.var_name, type_string(variable.var_type)]

script += "\n"

var init_func = TreeNode.new("func _init():")

for entry_statement in entry_blocks_by_entry_statement:
var entry_blocks: Array[EntryBlock]
entry_blocks.assign(entry_blocks_by_entry_statement[entry_statement])
script += _generate_script_from_entry_blocks(entry_statement, entry_blocks, init_func)

if init_func.children:
script += generate_text(init_func)

return script


static func _generate_script_from_entry_blocks(entry_statement: String, entry_blocks: Array[EntryBlock], init_func: TreeNode) -> String:
var script = entry_statement + "\n"
var signal_node: TreeNode
var is_empty = true

IDHandler.reset()

for entry_block in entry_blocks:
var next_block := entry_block.bottom_snap.get_snapped_block()

if next_block != null:
var instruction_node: TreeNode = next_block.get_instruction_node()
var to_append := generate_text(instruction_node, 1)
script += to_append
script += "\n"
is_empty = false

if signal_node == null and entry_block.signal_name:
signal_node = TreeNode.new("{0}.connect(_on_{0})".format([entry_block.signal_name]))

if signal_node:
init_func.add_child(signal_node)

if is_empty:
script += "\tpass\n\n"

return script


## Returns the scope of the first non-empty scope child block
static func get_tree_scope(node: Node) -> String:
if node is Block:
if node.scope != "":
return node.scope

for c in node.get_children():
var scope := get_tree_scope(c)
if scope != "":
return scope
return ""
5 changes: 3 additions & 2 deletions addons/block_code/ui/block_canvas/block_canvas.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
extends MarginContainer

const BlockCodePlugin = preload("res://addons/block_code/block_code_plugin.gd")
const BlockTreeUtil = preload("res://addons/block_code/ui/block_tree_util.gd")
const DragManager = preload("res://addons/block_code/drag_manager/drag_manager.gd")
const InstructionTree = preload("res://addons/block_code/instruction_tree/instruction_tree.gd")
const Util = preload("res://addons/block_code/ui/util.gd")
Expand Down Expand Up @@ -219,7 +220,7 @@ func set_scope(scope: String):
if scope == block.get_entry_statement():
valid = true
else:
var tree_scope := InstructionTree.get_tree_scope(block)
var tree_scope := BlockTreeUtil.get_tree_scope(block)
if tree_scope == "" or scope == tree_scope:
valid = true

Expand Down Expand Up @@ -321,4 +322,4 @@ func set_mouse_override(override: bool):

func generate_script_from_current_window(bsd: BlockScriptData) -> String:
# TODO: implement multiple windows
return InstructionTree.generate_script_from_nodes(_window.get_children(), bsd)
return BlockTreeUtil.generate_script_from_nodes(_window.get_children(), bsd)
80 changes: 80 additions & 0 deletions addons/block_code/ui/block_tree_util.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
extends Object

const InstructionTree = preload("res://addons/block_code/instruction_tree/instruction_tree.gd")


static func generate_script_from_nodes(nodes: Array[Node], bsd: BlockScriptData) -> String:
var entry_blocks_by_entry_statement: Dictionary = {}

for block in nodes:
if !(block is Block):
continue

if block is EntryBlock:
var entry_statement = block.get_entry_statement()
if not entry_blocks_by_entry_statement.has(entry_statement):
entry_blocks_by_entry_statement[entry_statement] = []
entry_blocks_by_entry_statement[entry_statement].append(block)

var script: String = ""

script += "extends %s\n\n" % bsd.script_inherits

for variable in bsd.variables:
script += "var %s: %s\n\n" % [variable.var_name, type_string(variable.var_type)]

script += "\n"

var init_func = InstructionTree.TreeNode.new("func _init():")

for entry_statement in entry_blocks_by_entry_statement:
var entry_blocks: Array[EntryBlock]
entry_blocks.assign(entry_blocks_by_entry_statement[entry_statement])
script += _generate_script_from_entry_blocks(entry_statement, entry_blocks, init_func)

if init_func.children:
script += InstructionTree.generate_text(init_func)

return script


static func _generate_script_from_entry_blocks(entry_statement: String, entry_blocks: Array[EntryBlock], init_func: InstructionTree.TreeNode) -> String:
var script = entry_statement + "\n"
var signal_node: InstructionTree.TreeNode
var is_empty = true

InstructionTree.IDHandler.reset()

for entry_block in entry_blocks:
var next_block := entry_block.bottom_snap.get_snapped_block()

if next_block != null:
var instruction_node: InstructionTree.TreeNode = next_block.get_instruction_node()
var to_append := InstructionTree.generate_text(instruction_node, 1)
script += to_append
script += "\n"
is_empty = false

if signal_node == null and entry_block.signal_name:
signal_node = InstructionTree.TreeNode.new("{0}.connect(_on_{0})".format([entry_block.signal_name]))

if signal_node:
init_func.add_child(signal_node)

if is_empty:
script += "\tpass\n\n"

return script


## Returns the scope of the first non-empty scope child block
static func get_tree_scope(node: Node) -> String:
if node is Block:
if node.scope != "":
return node.scope

for c in node.get_children():
var scope := get_tree_scope(c)
if scope != "":
return scope
return ""
11 changes: 6 additions & 5 deletions tests/test_instruction_tree.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
extends GutTest
## Tests for InstructionTree

const BlockTreeUtil = preload("res://addons/block_code/ui/block_tree_util.gd")
const CategoryFactory = preload("res://addons/block_code/ui/picker/categories/category_factory.gd")
const InstructionTree = preload("res://addons/block_code/instruction_tree/instruction_tree.gd")

Expand Down Expand Up @@ -88,7 +89,7 @@ func test_tree_node_text():

func test_script_no_nodes():
var bsd := BlockScriptData.new("Foo")
var script := InstructionTree.generate_script_from_nodes([], bsd)
var script := BlockTreeUtil.generate_script_from_nodes([], bsd)
assert_eq(
script,
(
Expand All @@ -105,7 +106,7 @@ func test_script_no_nodes():
func test_script_no_entry_blocks():
var bsd := BlockScriptData.new("Foo")
var nodes: Array[Node] = [Node.new(), Node2D.new(), Control.new()]
var script := InstructionTree.generate_script_from_nodes(nodes, bsd)
var script := BlockTreeUtil.generate_script_from_nodes(nodes, bsd)
assert_eq(
script,
(
Expand Down Expand Up @@ -137,7 +138,7 @@ func test_basic_script():
assert_eq(ready_block.bottom_snap.get_snapped_block(), print_block)

var bsd := BlockScriptData.new("Node2D")
var script := InstructionTree.generate_script_from_nodes([ready_block], bsd)
var script := BlockTreeUtil.generate_script_from_nodes([ready_block], bsd)
assert_eq(
script,
(
Expand Down Expand Up @@ -165,7 +166,7 @@ func test_multiple_entry_script():
var ready_block_2: Block = dup_node(ready_block)

var bsd := BlockScriptData.new("Node2D")
var script := InstructionTree.generate_script_from_nodes([ready_block, ready_block_2], bsd)
var script := BlockTreeUtil.generate_script_from_nodes([ready_block, ready_block_2], bsd)
assert_eq(
script,
(
Expand Down Expand Up @@ -196,7 +197,7 @@ func test_signal_script():
entered_block.bottom_snap.snapped_block = print_block

var bsd := BlockScriptData.new("Area2D")
var script = InstructionTree.generate_script_from_nodes([entered_block], bsd)
var script = BlockTreeUtil.generate_script_from_nodes([entered_block], bsd)
assert_eq(
script,
(
Expand Down

0 comments on commit 047ec47

Please sign in to comment.