From 13d2391e57cccf7bff023ab9930440068f7b12dc Mon Sep 17 00:00:00 2001 From: Bram Dingelstad Date: Sun, 21 Nov 2021 12:40:05 +0100 Subject: [PATCH] worked on better integrating existing code w Godot --- addons/Wol/Wol.gd | 71 +++++--------- addons/Wol/core/StandardLibrary.gd | 96 +++++++++++++++++++ addons/Wol/core/dialogue.gd | 76 +-------------- addons/Wol/core/libraries/standard.gd | 69 -------------- addons/Wol/core/library.gd | 28 +++--- addons/Wol/core/virtual_machine.gd | 130 +++++++++++++------------- 6 files changed, 199 insertions(+), 271 deletions(-) create mode 100644 addons/Wol/core/StandardLibrary.gd delete mode 100644 addons/Wol/core/libraries/standard.gd diff --git a/addons/Wol/Wol.gd b/addons/Wol/Wol.gd index 72aa4e4..b3aa8e0 100644 --- a/addons/Wol/Wol.gd +++ b/addons/Wol/Wol.gd @@ -18,7 +18,9 @@ signal finished const Constants = preload('res://addons/Wol/core/constants.gd') const WolCompiler = preload('res://addons/Wol/core/compiler/compiler.gd') -const WolDialogue = preload('res://addons/Wol/core/dialogue.gd') +const WolLibrary = preload('res://addons/Wol/core/library.gd') +const VirtualMachine = preload('res://addons/Wol/core/virtual_machine.gd') +const StandardLibrary = preload('res://addons/Wol/core/StandardLibrary.gd') export(String, FILE, '*.wol,*.yarn') var path setget set_path export(String) var start_node = 'Start' @@ -28,13 +30,17 @@ export var auto_show_options = true onready var variable_storage = get_node(variable_storage_path) -var program - -var dialogue +var virtual_machine func _ready(): if Engine.editor_hint: return + + var libraries = WolLibrary.new() + libraries.import_library(StandardLibrary.new()) + virtual_machine = VirtualMachine.new(self, libraries) + + set_path(path) if not variable_storage: variable_storage = Node.new() @@ -45,43 +51,22 @@ func _ready(): if auto_start: start() -func init_dialogue(): - # FIXME: Move visited count to variable storage - var existing_state - if dialogue != null: - existing_state = dialogue._visitedNodeCount - - dialogue = WolDialogue.new(variable_storage) - - # FIXME: Remove these lines - if existing_state: - dialogue._visitedNodeCount = existing_state - - dialogue.get_vm().lineHandler = funcref(self, '_handle_line') - dialogue.get_vm().optionsHandler = funcref(self, '_handle_options') - dialogue.get_vm().commandHandler = funcref(self, '_handle_command') - dialogue.get_vm().nodeCompleteHandler = funcref(self, '_handle_node_complete') - dialogue.get_vm().dialogueCompleteHandler = funcref(self, '_handle_dialogue_complete') - dialogue.get_vm().nodeStartHandler = funcref(self, '_handle_node_start') - - dialogue.set_program(program) - func set_path(_path): path = _path - if not Engine.editor_hint: + if not Engine.editor_hint and virtual_machine: var compiler = WolCompiler.new(path) - program = compiler.compile() + virtual_machine.program = compiler.compile() -func _handle_line(line): +func _on_line(line): call_deferred('emit_signal', 'line', line) if auto_show_options \ - and dialogue.get_vm().get_next_instruction().operation == Constants.ByteCode.AddOption: + and virtual_machine.get_next_instruction().operation == Constants.ByteCode.AddOption: return Constants.HandlerState.ContinueExecution else: return Constants.HandlerState.PauseExecution -func _handle_command(command): +func _on_command(command): call_deferred('emit_signal', 'command', command) if get_signal_connection_list('command').size() > 0: @@ -89,39 +74,33 @@ func _handle_command(command): else: return Constants.HandlerState.ContinueExecution -func _handle_options(options): +func _on_options(options): call_deferred('emit_signal', 'options', options) return Constants.HandlerState.PauseExecution -func _handle_dialogue_complete(): +func _on_dialogue_finished(): emit_signal('finished') -func _handle_node_start(node): +func _on_node_start(node): emit_signal('node_started', node) - dialogue.resume() + resume() - if !dialogue._visitedNodeCount.has(node): - dialogue._visitedNodeCount[node] = 1 - else: - dialogue._visitedNodeCount[node] += 1 - -func _handle_node_complete(node): +func _on_node_finished(node): emit_signal('node_finished', node) return Constants.HandlerState.ContinueExecution func select_option(id): - dialogue.get_vm().set_selected_option(id) + virtual_machine.set_selected_option(id) resume() func pause(): - dialogue.call_deferred('pause') + virtual_machine.call_deferred('pause') func start(node = start_node): - init_dialogue() emit_signal('started') - dialogue.set_node(node) - dialogue.start() + virtual_machine.set_node(node) + virtual_machine.resume() func resume(): - dialogue.call_deferred('resume') + virtual_machine.call_deferred('resume') diff --git a/addons/Wol/core/StandardLibrary.gd b/addons/Wol/core/StandardLibrary.gd new file mode 100644 index 0000000..7f2c2a5 --- /dev/null +++ b/addons/Wol/core/StandardLibrary.gd @@ -0,0 +1,96 @@ +extends 'res://addons/Wol/core/library.gd' + +const Value = preload('res://addons/Wol/core/value.gd') + +func _init(): + register_function('Add', 2, funcref(self, 'add'), true) + register_function('Minus', 2, funcref(self, 'sub'), true) + register_function('UnaryMinus', 1, funcref(self, 'unary_minus'), true) + register_function('Divide', 2, funcref(self, 'div'), true) + register_function('Multiply', 2, funcref(self, 'mul'), true) + register_function('Modulo', 2, funcref(self, 'mod'), true) + register_function('EqualTo', 2, funcref(self, 'equal'), true) + register_function('NotEqualTo', 2, funcref(self, 'noteq'), true) + register_function('GreaterThan', 2, funcref(self, 'ge'), true) + register_function('GreaterThanOrEqualTo', 2, funcref(self, 'geq'), true) + register_function('LessThan', 2, funcref(self, 'le'), true) + register_function('LessThanOrEqualTo', 2, funcref(self, 'leq'), true) + register_function('And', 2, funcref(self, 'land'), true) + register_function('Or', 2, funcref(self, 'lor'), true) + register_function('Xor', 2, funcref(self, 'xor'), true) + register_function('Not', 1, funcref(self, 'lnot'), true) + + # `visited` and `visit_count` functions + register_function('visited', -1, funcref(self, 'is_node_visited'), true) + register_function('visit_count', -1, funcref(self, 'node_visit_count'), true) + + +func add(param1, param2): + return param1.add(param2) + +func sub(param1, param2): + return param1.sub(param2) + +func unary_minus(param1): + return param1.negative() + +func div(param1, param2): + return param1.div(param2) + +func mul(param1, param2): + return param1.mult(param2) +func mod(param1, param2): + return param1.mod(param2) + +func equal(param1, param2): + return param1.equals(param2) + +func noteq(param1, param2): + return !param1.equals(param2) + +func ge(param1, param2): + return param1.greater(param2) + +func geq(param1, param2): + return param1.geq(param2) + +func le(param1, param2): + return param1.less(param2) + +func leq(param1, param2): + return param1.leq(param2) + +func land(param1, param2): + return param1.as_bool() and param2.as_bool() + +func lor(param1, param2): + return param1.as_bool() or param2.as_bool() + +func xor(param1, param2): + return param1.as_bool() != param2.as_bool() + +func lnot(param1): + return not param1.as_bool() + +var visited_node_count = {} + +func is_node_visited(node = virtual_machine.current_node_name()): + return node_visit_count(node) > 0 + +func node_visit_count(node = virtual_machine.current_node_name()): + if node is Value: + node = virtual_machine.program.strings[node.value()].text + + var visit_count = 0 + if visited_node_count.has(node): + visit_count = visited_node_count[node] + + return visit_count + +func get_visited_nodes(): + return visited_node_count.keys() + +func set_visited_nodes(visitedList): + visited_node_count.clear() + for string in visitedList: + visited_node_count[string] = 1 diff --git a/addons/Wol/core/dialogue.gd b/addons/Wol/core/dialogue.gd index a3eb228..67c6b9c 100644 --- a/addons/Wol/core/dialogue.gd +++ b/addons/Wol/core/dialogue.gd @@ -1,11 +1,7 @@ extends Node -const DEFAULT_START = 'Start' - const Constants = preload('res://addons/Wol/core/constants.gd') -const StandardLibrary = preload('res://addons/Wol/core/libraries/standard.gd') const VirtualMachine = preload('res://addons/Wol/core/virtual_machine.gd') -const WolLibrary = preload('res://addons/Wol/core/library.gd') const Value = preload('res://addons/Wol/core/value.gd') var _variableStorage @@ -14,42 +10,21 @@ var _program var library var _vm - var _visitedNodeCount = {} -var executionComplete - func _init(variableStorage): _variableStorage = variableStorage - _vm = VirtualMachine.new(self) - library = WolLibrary.new() - executionComplete = false - - # import the standard library - # this contains math constants, operations and checks - library.import_library(StandardLibrary.new())#FIX - - #add a function to lib that checks if node is visited - library.register_function('visited', -1, funcref(self, 'is_node_visited'), true) - - #add function to lib that gets the node visit count - library.register_function('visit_count', -1, funcref(self, 'node_visit_count'), true) func is_active(): return get_exec_state() != Constants.ExecutionState.Stopped -#gets the current execution state of the virtual machine -func get_exec_state(): - return _vm.executionState - func set_selected_option(option): _vm.set_selected_option(option) -func set_node(name = DEFAULT_START): +func set_node(name = 'Start'): _vm.set_node(name) func start(): - print('got here') if _vm.executionState == Constants.ExecutionState.Stopped: _vm.resume() @@ -65,29 +40,7 @@ func pause(): func stop(): _vm.stop() -func get_all_nodes(): - return _program.nodes.keys() - -func current_node(): - return _vm.get_current() - -func get_node_id(name): - if _program.nodes.size() == 0: - return '' - if _program.nodes.has(name): - return 'id:'+name - else: - return '' - -func unloadAll(clear_visited:bool = true): - if clear_visited : - _visitedNodeCount.clear() - _program = null - -func dump()->String: - return _program.dump(library) - -func node_exists(name:String)->bool: +func node_exists(name): return _program.nodes.has(name) func set_program(program): @@ -95,32 +48,7 @@ func set_program(program): _vm.set_program(_program) _vm.reset() -func get_program(): - return _program - func get_vm(): return _vm -func is_node_visited(node = _vm.current_node_name()): - return node_visit_count(node) > 0 -func node_visit_count(node = _vm.current_node_name()): - if node is Value: - node = _program.strings[node.value()].text - - var visitCount : int = 0 - if _visitedNodeCount.has(node): - visitCount = _visitedNodeCount[node] - - - print('visit count for %s is %d' % [node, visitCount]) - - return visitCount - -func get_visited_nodes(): - return _visitedNodeCount.keys() - -func set_visited_nodes(visitedList): - _visitedNodeCount.clear() - for string in visitedList: - _visitedNodeCount[string] = 1 diff --git a/addons/Wol/core/libraries/standard.gd b/addons/Wol/core/libraries/standard.gd deleted file mode 100644 index fc89bc1..0000000 --- a/addons/Wol/core/libraries/standard.gd +++ /dev/null @@ -1,69 +0,0 @@ -extends 'res://addons/Wol/core/library.gd' - -const Value = preload('res://addons/Wol/core/value.gd') - -func _init(): - register_function('Add', 2, funcref(self, 'add'), true) - register_function('Minus', 2, funcref(self, 'sub'), true) - register_function('UnaryMinus', 1, funcref(self, 'unary_minus'), true) - register_function('Divide', 2, funcref(self, 'div'), true) - register_function('Multiply', 2, funcref(self, 'mul'), true) - register_function('Modulo', 2, funcref(self, 'mod'), true) - register_function('EqualTo', 2, funcref(self, 'equal'), true) - register_function('NotEqualTo', 2, funcref(self, 'noteq'), true) - register_function('GreaterThan', 2, funcref(self, 'ge'), true) - register_function('GreaterThanOrEqualTo', 2, funcref(self, 'geq'), true) - register_function('LessThan', 2, funcref(self, 'le'), true) - register_function('LessThanOrEqualTo', 2, funcref(self, 'leq'), true) - register_function('And', 2, funcref(self, 'land'), true) - register_function('Or', 2, funcref(self, 'lor'), true) - register_function('Xor', 2, funcref(self, 'xor'), true) - register_function('Not', 1, funcref(self, 'lnot'), true) - -func add(param1, param2): - return param1.add(param2) - -func sub(param1, param2): - return param1.sub(param2) - -func unary_minus(param1): - return param1.negative() - -func div(param1, param2): - return param1.div(param2) - -func mul(param1, param2): - return param1.mult(param2) - -func mod(param1, param2): - return param1.mod(param2) - -func equal(param1, param2): - return param1.equals(param2) - -func noteq(param1, param2): - return !param1.equals(param2) - -func ge(param1, param2): - return param1.greater(param2) - -func geq(param1, param2): - return param1.geq(param2) - -func le(param1, param2): - return param1.less(param2) - -func leq(param1, param2): - return param1.leq(param2) - -func land(param1, param2): - return param1.as_bool() and param2.as_bool() - -func lor(param1, param2): - return param1.as_bool() or param2.as_bool() - -func xor(param1, param2): - return param1.as_bool() != param2.as_bool() - -func lnot(param1): - return not param1.as_bool() diff --git a/addons/Wol/core/library.gd b/addons/Wol/core/library.gd index 14497cc..b1d91d7 100644 --- a/addons/Wol/core/library.gd +++ b/addons/Wol/core/library.gd @@ -1,28 +1,24 @@ extends Object -const FunctionInfo = preload("res://addons/Wol/core/function_info.gd") +const FunctionInfo = preload('res://addons/Wol/core/function_info.gd') const Constants = preload('res://addons/Wol/core/constants.gd') -var functions : Dictionary = {}# String , FunctionInfo +var functions = {} +var virtual_machine -func get_function(name:String)->FunctionInfo: +func get_function(name): if functions.has(name): return functions[name] else : - printerr("Invalid Function: %s"% name) - return null + printerr('Invalid Function: %s'% name) + return -func import_library(other)->void: - Constants.merge_dir(functions,other.functions) +func import_library(other): + Constants.merge_dir(functions, other.functions) -func register_function(name: String, paramCount: int, function: FuncRef, returnsValue: bool): - var functionInfo: FunctionInfo = FunctionInfo.new(name, paramCount, function, returnsValue) +func register_function(name, parameter_count, function, returns_value): + var functionInfo = FunctionInfo.new(name, parameter_count, function, returns_value) functions[name] = functionInfo -func deregister_function(name: String): - if !functions.erase(name): - pass - - - - +func deregister_function(name): + functions.erase(name) diff --git a/addons/Wol/core/virtual_machine.gd b/addons/Wol/core/virtual_machine.gd index a279fc8..291efe1 100644 --- a/addons/Wol/core/virtual_machine.gd +++ b/addons/Wol/core/virtual_machine.gd @@ -8,48 +8,62 @@ const EXECUTION_COMPLETE : String = 'execution_complete_command' var NULL_VALUE = Value.new(null) # Function references to handlers -var lineHandler -var optionsHandler -var commandHandler -var nodeStartHandler -var nodeCompleteHandler -var dialogueCompleteHandler +var line_handler +var options_handler +var command_handler +var node_start_handler +var node_finished_handler +var dialogue_finished_handler -var _dialogue -var _program +var dialogue +var libraries +var program var _state var _currentNode -var executionState = Constants.ExecutionState.Stopped +var execution_state = Constants.ExecutionState.Stopped var string_table = {} -func _init(dialogue): - self._dialogue = dialogue - _state = VmState.new() +func _init(_dialogue, _libraries): + dialogue = _dialogue + libraries = _libraries + libraries.virtual_machine = self -func set_program(program): - _program = program + line_handler = funcref(dialogue, '_on_line') + options_handler = funcref(dialogue, '_on_options') + command_handler = funcref(dialogue, '_on_command') + node_start_handler = funcref(dialogue, '_on_node_start') + node_finished_handler = funcref(dialogue, '_on_node_finished') + dialogue_finished_handler = funcref(dialogue, '_on_dialogue_finished') + + assert(line_handler.is_valid(), 'Cannot run without a line handler (_on_line)') + assert(options_handler.is_valid(), 'Cannot run without a options handler (_on_options)') + assert(command_handler.is_valid(), 'Cannot run without a command handler (_on_command)') + assert(node_start_handler.is_valid(), 'Cannot run without a node start handler (_on_node_start)') + assert(node_finished_handler.is_valid(), 'Cannot run without a node finished handler (_on_node_finished)') + + _state = VmState.new() #set the node to run #return true if successeful false if no node #of that name found -func set_node(name:String) -> bool: - if _program == null || _program.nodes.size() == 0: +func set_node(name): + if program == null || program.nodes.size() == 0: printerr('Could not load %s : no nodes loaded' % name) return false - if !_program.nodes.has(name): - executionState = Constants.ExecutionState.Stopped + if !program.nodes.has(name): + execution_state = Constants.ExecutionState.Stopped reset() printerr('No node named %s has been loaded' % name) return false - _currentNode = _program.nodes[name] + _currentNode = program.nodes[name] reset() _state.currentNodeName = name - nodeStartHandler.call_func(name) + node_start_handler.call_func(name) return true func current_node_name()->String: @@ -59,11 +73,11 @@ func current_node(): return _currentNode func pause(): - executionState = Constants.ExecutionState.Suspended + execution_state = Constants.ExecutionState.Suspended #stop exectuion func stop(): - executionState = Constants.ExecutionState.Stopped + execution_state = Constants.ExecutionState.Stopped reset() _currentNode = null @@ -71,7 +85,7 @@ func stop(): #resume execution if waiting for result #return false if error func set_selected_option(id): - if executionState != Constants.ExecutionState.WaitingForOption: + if execution_state != Constants.ExecutionState.WaitingForOption: printerr('Unable to select option when dialogue not waiting for option') return false @@ -84,7 +98,7 @@ func set_selected_option(id): _state.currentOptions.clear() #no longer waiting for option - executionState = Constants.ExecutionState.Suspended + execution_state = Constants.ExecutionState.Suspended return true @@ -99,42 +113,26 @@ func resume(): printerr('Cannot run dialogue with no node selected') return false - if executionState == Constants.ExecutionState.WaitingForOption: + if execution_state == Constants.ExecutionState.WaitingForOption: printerr('Cannot run while waiting for option') return false - if lineHandler == null: - printerr('Cannot run without a lineHandler') - return false - if optionsHandler == null: - printerr('Cannot run without an optionsHandler') - return false - if commandHandler == null: - printerr('Cannot run without an commandHandler') - return false - if nodeStartHandler == null: - printerr('Cannot run without a nodeStartHandler') - return false - if nodeCompleteHandler == null: - printerr('Cannot run without an nodeCompleteHandler') - return false - - executionState = Constants.ExecutionState.Running + execution_state = Constants.ExecutionState.Running #execute instruction until something cool happens - while executionState == Constants.ExecutionState.Running: + while execution_state == Constants.ExecutionState.Running: var currentInstruction = _currentNode.instructions[_state.programCounter] run_instruction(currentInstruction) _state.programCounter += 1 if _state.programCounter >= _currentNode.instructions.size(): - nodeCompleteHandler.call_func(_currentNode.nodeName) - executionState = Constants.ExecutionState.Stopped + node_finished_handler.call_func(_currentNode.nodeName) + execution_state = Constants.ExecutionState.Stopped reset() - dialogueCompleteHandler.call_func() + dialogue_finished_handler.call_func() return true @@ -158,17 +156,17 @@ func run_instruction(instruction)->bool: #pass it to client as line var key = instruction.operands[0].value - var line = _program.strings[key] + var line = program.strings[key] #the second operand is the expression count # of format function if instruction.operands.size() > 1: pass#add format function support - var pause : int = lineHandler.call_func(line) + var pause = line_handler.call_func(line) if pause == Constants.HandlerState.PauseExecution: - executionState = Constants.ExecutionState.Suspended + execution_state = Constants.ExecutionState.Suspended Constants.ByteCode.RunCommand: var commandText : String = instruction.operands[0].value @@ -178,9 +176,9 @@ func run_instruction(instruction)->bool: var command = Program.Command.new(commandText) - var pause = commandHandler.call_func(command) as int + var pause = command_handler.call_func(command) as int if pause == Constants.HandlerState.PauseExecution: - executionState = Constants.ExecutionState.Suspended + execution_state = Constants.ExecutionState.Suspended Constants.ByteCode.PushString: #push String var to stack @@ -217,7 +215,7 @@ func run_instruction(instruction)->bool: #push any return value to stack var functionName : String = instruction.operands[0].value - var function = _dialogue.library.get_function(functionName) + var function = libraries.get_function(functionName) var expected_parameter_count : int = function.paramCount var actual_parameter_count : int = _state.pop_value().as_number() @@ -250,20 +248,21 @@ func run_instruction(instruction)->bool: Constants.ByteCode.PushVariable: #get content of variable and push to stack var name : String = instruction.operands[0].value - var loaded = _dialogue._variableStorage.get_value(name) + # TODO: Reimplement variable storage + var loaded = dialogue.variable_storage.get_value(name) _state.push_value(loaded) Constants.ByteCode.StoreVariable: #store top stack value to variable var top = _state.peek_value() var destination : String = instruction.operands[0].value - _dialogue._variableStorage.set_value(destination,top) + dialogue.variable_storage.set_value(destination,top) Constants.ByteCode.Stop: #stop execution and repost it - nodeCompleteHandler.call_func(_currentNode.name) - dialogueCompleteHandler.call_func() - executionState = Constants.ExecutionState.Stopped + node_finished_handler.call_func(_currentNode.name) + dialogue_finished_handler.call_func() + execution_state = Constants.ExecutionState.Stopped reset() Constants.ByteCode.RunNode: @@ -276,17 +275,17 @@ func run_instruction(instruction)->bool: else : name = instruction.operands[0].value - var pause = nodeCompleteHandler.call_func(_currentNode.name) + var pause = node_finished_handler.call_func(_currentNode.name) set_node(name) _state.programCounter-=1 if pause == Constants.HandlerState.PauseExecution: - executionState = Constants.ExecutionState.Suspended + execution_state = Constants.ExecutionState.Suspended Constants.ByteCode.AddOption: # add an option to current state var key = instruction.operands[0].value - var line = _program.strings[key] + var line = program.strings[key] if instruction.operands.size() > 2: pass #formated text options @@ -297,9 +296,9 @@ func run_instruction(instruction)->bool: Constants.ByteCode.ShowOptions: #show options - stop if none if _state.currentOptions.size() == 0: - executionState = Constants.ExecutionState.Stopped + execution_state = Constants.ExecutionState.Stopped reset() - dialogueCompleteHandler.call_func() + dialogue_finished_handler.call_func() return false #present list of options @@ -309,16 +308,15 @@ func run_instruction(instruction)->bool: choices.append(Program.Option.new(option.key, optionIndex, option.value)) #we cant continue until option chosen - executionState = Constants.ExecutionState.WaitingForOption + execution_state = Constants.ExecutionState.WaitingForOption #pass the options to the client #delegate for them to call #when user makes selection - optionsHandler.call_func(choices) + options_handler.call_func(choices) _: - #bytecode messed up woopsise - executionState = Constants.ExecutionState.Stopped + execution_state = Constants.ExecutionState.Stopped reset() printerr('Unknown Bytecode %s' % instruction.operation) return false