VisualScriptAction Debugger, Improvement Proposal

My first “hack” into the AGM APIs.

Visual Script debugging seems like it’s already going to be messy, and adding PrintMessageToConsole actions takes fiddly time to do https://guild.rpgmakerofficial.com/t/topic/197

What would be better is option right on the VisualScriptAction to enable that debugging.

I am not asking for it be added for 1.0.6 or 1.0.7. This still need refinement as an idea. Looking into what other debugging features can be done of Visual Scripting.

This was just going to be a picture showing where the options should appear, but the tool-coder took over with a demonstration script.

GitHub Gist: Action Game Marker, Visual Script debugging, GDScript tools .

Original Script

# debug_vsa.gd
extends VisualScriptAction

@export var print_message_to_console : bool = true :
	set(v):
		if v:
			_add_print_action.call_deferred()
		else:
			_remove_print_action.call_deferred()
		print_message_to_console = v

var _print_action : Actions = PrintMessageToConsole.new()
var _print_action_at_index : int

func _init() -> void:
	_print_action.print_message = visual_script_node_title + " entered"

func _add_print_action() -> void:
	other_actions.append(_print_action)
	_print_action_at_index = other_actions.size() - 1
	
func _remove_print_action() -> void:
	if  other_actions.size() > 0 and other_actions[_print_action_at_index] == _print_action:
		other_actions.remove_at(_print_action_at_index)

(.gd scripts can’t be uploaded as file type currently)

The runtime _remove_print_action() of this is untested.

For advanced users familiar with Godot scripting, to attach this script to an existing State in a VisualScript (VS)

  1. create the debug_vsa.gd the FileSystem
  2. this should extend VisualScriptAction , if you don’t…
  3. copy the whole script above
  4. create or use an existing VS
  5. select the Initial State
  6. in the Inspector scroll all the way to bottom 'RefCounted` header
  7. assign by load, quick load, or drag debug_vsa.gd

You should now have a checked Boolean for “Print Message to Console”. In a test run, you should get an Output message with the State’s name and “entered.”

What would be really helpful in debugging would be something like the SignalVisualizer plugin for Godot. To visually track which Links and States are triggering.

Beyond debugging, keep in mind that extending VisualScriptAction or VisualScriptActionLink doesn’t provide entry point to the actual Visual Script interpreter. These are just Resources that AGM will use to do pre-coded condition checks, and execute pre-scripted code.

They could allow custom GDScript access to easily modify select Links and States, their Conditions and Actions. I don’t know the current process safety, so consider using call_deferred and set_deferred from the _physics_process to allow AGM to reference and modify all the VS resources before you alter them.

3 Likes

Added a GitHub Gist link that will receive updates as I test options.

The use of PrintMessagetoConsole isn’t elegant. It doesn’t have enough information for a serious trace.

Debugging output should be able to tell not only if something is wrong, but where exactly it’s going wrong (GameObject instance & Link/State). Or close enough to find the trouble spot nearby.

Probably a better solution is to have Links register themselves and their Conditions with a central Debugger. This would let a Debugger be aware of what needs to be monitored. At a minimum a GUI could be made to investigate which Links should have triggered. Based purely off observable parameters elsewhere.

1 Like

Valid 1.0.2 – 1.0.4

It is possible to modify VisualScript (.vs) with GDscript. VisualScript can be accessed by directly loading them load(res://path/to/visual_script.vs) or using Object get_script on an existing GameObject.

Links (VisualScriptActionLink) and States (VisualScriptAction) references can be obtained by using VisualScript.get_node(id:int)

Class Inheritance

  • Resource // Godot class
    • VisualScriptNode // AGM class
      • VisualScriptAction // State
      • VisualScriptActionLink // Link

VisualScript
Method Descriptions

  • VisualScriptNode get_node(id: int)

Returns a reference to the VisualScriptNode contained in the VisualScript’s data. This can be either a VisualScriptAction (State) or VisualScriptActionLink (Link). In GDScript no casting is required. Not the same as the Godot method Node.get_node().

  • bool has_node(id: int)

Returns true if VisualScript has a valid VisualScriptNode at this index. Otherwise returns false.

The array or other collection is not directly accessible. Values may not be stored contiguously. Some Index values may be null. To obtain a full list of all VisualScriptNodes in the VisualScript, the get_node method must be looped.

#GDScript

var vscript #game_object.get_script() or load("res://path/to/visual_script.vs")

# Typed Dictionaries are not available until an engine update to Godot 4.4+ 
var states: Dictionary #[int,VisualScriptAction]
var links : Dictionary #[int,VisualScriptActionLink]

# The max limit of stored VisualScriptNode is unknown, 
# 1000 links and states in a single script seems excessive.
# I am assuming it's an array, no `id` lower than 0
for i in range(0, 1000):
		if !vscript.has_node(i):
			continue # skip to next index
		var n = vscript.get_node(i)
		if is_instance_valid(n):
			if n is VisualScriptAction:
				states[n.id] = n # KEY id : VALUE VisualScriptAction reference
			if n is VisualScriptActionLink:
				links[n.id] = n # KEY id : VALUE VisualScriptActionLink reference

States and Links have properties that can be accessed. These are viewable in the Inspector using Filter Properties :hammer_and_wrench: button (right of).

For VisualScriptAction the other_actions array contains Actions resources.

For debugging uses I am currently looking into PrintMessageToConsole, EmitSignal, InitializeObject , and ChangeAction actions.

Currently there is some form of compiling that locks in the logic and execution of a VisualScript. This is done at some point when the GameObject initializes (still investigating exactly when). Any changes made to the attached VisualScript after initialization will not apply to already instantiated GameObjects, but will apply to any new instances.

This makes activating a Debugging system during runtime complex.

The action InitializeObject will update an existing GameObject’s logic, adding new Actions. InitializeObject is currently unstable (runtime crash) when used on some GameObjects (currently investigating).

PrintMessageToConsole will begin working after a GameObject has been InitializeObject. For debugging, the print_message should be set to include Time and information about the NodeAction. It would be helpful if there were a PrintRichToConsole using @GlobalScope.print_rich().

The action EmitSignal currently does not work properly. When EmitSignal is added by GDScript to other_actions, the GameObject will have an new Signal added to. This can be confirmed with Object.has_signal("signal_name"). It can be connect() and emit() by GDScript. Sadly the EmitSignal action does not trigger the emit(), even after InitializeObject. This is currently non-functional for debugging.

ChangeAction and ChangeObjectProperty are possible alternatives to the non-functional EmitSignal. By forcefully changing the State of a “Debugger” GameObject to one with a pre-made EmitSignal that sends a Project Database record that contains the needed debug information. It should be connected to a more robust GDScript based debugger. This is currently untested.

1 Like

The best idea I have at the moment is to use a Godot Autoload to pre-load the Visual Scripts that need debugging. Modify them before CoreScene.tscn loads. And hope additions of PrintMessageToConsole and ChangeAction+ChangeObjectProperty stick.

What would be really useful is some kind of debugger at the GameManager. I don’t know the design of Visual Script execution so I can only give a rough suggestion of a double bool check. Goes along with adding a debug property checkbox to VisualScriptNode

if GameManager.debugger_on && current_node.debugger_on:
    GameManager.debugger_signaled.emit(current_game_object, current_node)

There’s defiantly better ways of setting this up in C++ so the debugger being inactive isn’t checked on every step of the Visual Script.

A debugger signal with a passed GameObject and the current VisualScriptNode, should be enough information for any 3rd party debuggers to build a GUI around.

I feel that allowing users to enable some options in the Inspector to print more detailed information to the console would be a good idea. Perhaps we can clarify this matter more clearly and have the official support this feature directly.

Are there any suggestions on which field options should be provided and what content should be printed?
If there are relatively clear and feasible suggestions, I think this is a good opportunity for them to implement these features. Because these features don’t seem to introduce overly complex interactions, they are very convenient during development.

In non-visual code you can set breakpoints. Where code execution will pause. Letting the person debugging examine where execution is at, and check on monitored variables.

  • Monitor Links, States, and Actions for execution
  • Set Breakpoints on Links, States, and Actions
  • Monitor specific GameObject properties and Database records
    • Baz put an example in the Discord English Showcase
  • Monitor GameObjects for their current State

The designer friendly tool is to make a GUI that presents this information in a centralized place.

  • Debug Links can supply their Conditions
  • Conditions supply properties and user Inputs to monitor
  • Debug States should report when they execute, and what actions they’re supposed do
  • Actions report their argument settings, in a hopefully easier to read and share format

These would go a long away in proving answers to some of the most common questions like:

Why isn’t this working?

This is asked publicly when a designer doesn’t have the knowledge or tools to begin asking more direct questions.

  1. Why isn’t a state or section of code being entered?
  2. Why isn’t this State or Action doing what I want? It is doing something different.

The first can be answered by monitoring the needed conditions, and if they’re (ever) meet.

The second tends to be wrong Parameter settings, a misunderstanding of how to use a particular Action, or (hopeful more rare) actual AGM bugged Action.

print , print_debug(script & line numbers), and print_rich(color & vfx highlight) are just crude ways to set up monitoring. In a text log. Useful for short term code design flaw debugging. Less useful in complex and highly conditional designs.

1 Like