169 lines
5.6 KiB
GDScript
169 lines
5.6 KiB
GDScript
class_name PankuModuleVariableTracker extends PankuModule
|
|
## Module to register and update some common environments.
|
|
##
|
|
## On module startup current scene root node registered as 'current' environment var,
|
|
## for more convenient access from interactive shell. Module constantly monitoring node
|
|
## tree afterwards and auto rebind 'current' environment in case of current scene changed.
|
|
## Also all user autoload singletons registered with its root node names.
|
|
|
|
const PROJECT_AUTOLOAD_PREFIX := "autoload/"
|
|
const CURRENT_SCENE_ENV := "current"
|
|
const SHELL_MODULE_NAME := "interactive_shell"
|
|
const DEFAULT_TRACKING_DELAY := 0.5
|
|
|
|
const CURRENT_REGISTERED_TIP := "[tip] Node '%s' registered as current scene, you can access it by [b]%s[/b]."
|
|
const CURRENT_REMOVED_TIP := "[tip] No current scene found, [b]%s[/b] keyword is no longer available."
|
|
const USER_AUTOLOADS_TIP := "[tip] Accessible user singleton modules: [b]%s[/b]"
|
|
|
|
var _raw_exceptions_string: String = ""
|
|
var _nodes_exception_regexp: RegEx
|
|
|
|
var _reverse_root_nodes_order: bool
|
|
var _current_scene_root:Node
|
|
var _user_singleton_files := []
|
|
var _tween_loop:Tween
|
|
var _loop_call_back:CallbackTweener
|
|
|
|
|
|
func init_module():
|
|
get_module_opt().tracking_delay = load_module_data("tracking_delay", DEFAULT_TRACKING_DELAY)
|
|
_reverse_root_nodes_order = load_module_data("use_last_as_current", true)
|
|
_raw_exceptions_string = load_module_data("root_node_exceptions", _raw_exceptions_string)
|
|
|
|
await core.get_tree().process_frame # not sure if it is necessary
|
|
|
|
update_exceptions_regexp()
|
|
_update_project_singleton_files()
|
|
_setup_scene_root_tracker()
|
|
_check_autoloads()
|
|
|
|
|
|
# Build root node exceptions regular expression
|
|
func update_exceptions_regexp() -> void:
|
|
if _raw_exceptions_string.is_empty():
|
|
_nodes_exception_regexp = RegEx.new() # not valid expression
|
|
return
|
|
|
|
_nodes_exception_regexp = RegEx.create_from_string(_raw_exceptions_string)
|
|
|
|
if not _nodes_exception_regexp.is_valid():
|
|
push_error("Can't parse '%s' expression for variable tracker" % _raw_exceptions_string)
|
|
|
|
|
|
# Parse project setting and collect and autoload files.
|
|
func _update_project_singleton_files() -> void:
|
|
_user_singleton_files.clear()
|
|
for property in ProjectSettings.get_property_list():
|
|
if property.name.begins_with(PROJECT_AUTOLOAD_PREFIX):
|
|
_user_singleton_files.append(ProjectSettings.get_setting(property.name).trim_prefix("*"))
|
|
|
|
|
|
# Check if given node is autoload singleton.
|
|
func _is_singleton(node: Node) -> bool:
|
|
# Comparing scene file and script file with list of autoload files
|
|
# from project settings. I'm not sure that approach hundred percent perfect,
|
|
# but it works so far.
|
|
if node.scene_file_path in _user_singleton_files:
|
|
return true
|
|
|
|
var script = node.get_script()
|
|
if script and (script.get_path() in _user_singleton_files):
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
# Setup monitoring loop for current scene root node.
|
|
func _setup_scene_root_tracker() -> void:
|
|
_check_current_scene()
|
|
# The whole idea looping something in the background
|
|
# while dev console is not even opened does not feel so right.
|
|
# Have no idea how to make it more elegant way,
|
|
# so lets make loop interval user controllable at least.
|
|
var tracking_delay = get_module_opt().tracking_delay
|
|
|
|
_tween_loop = core.create_tween()
|
|
_loop_call_back = _tween_loop.set_loops().tween_callback(_check_current_scene).set_delay(tracking_delay)
|
|
|
|
|
|
## Set current scene root node monitoring interval.
|
|
func change_tracking_delay(delay: float) -> void:
|
|
if _loop_call_back:
|
|
_loop_call_back.set_delay(delay)
|
|
|
|
|
|
# Update current scene root node environment.
|
|
func _check_current_scene() -> void:
|
|
var scene_root_found: Node = get_scene_root()
|
|
|
|
if scene_root_found:
|
|
if scene_root_found != _current_scene_root:
|
|
core.gd_exprenv.register_env(CURRENT_SCENE_ENV, scene_root_found)
|
|
_print_to_interactive_shell(CURRENT_REGISTERED_TIP % [scene_root_found.name, CURRENT_SCENE_ENV])
|
|
|
|
else:
|
|
if _current_scene_root:
|
|
core.gd_exprenv.remove_env(CURRENT_SCENE_ENV)
|
|
_print_to_interactive_shell(CURRENT_REMOVED_TIP % CURRENT_SCENE_ENV)
|
|
|
|
_current_scene_root = scene_root_found
|
|
|
|
|
|
## Find the root node of current active scene.
|
|
func get_scene_root() -> Node:
|
|
# Assuming current scene is the first node in tree that is not autoload singleton.
|
|
for node in _get_valid_root_nodes():
|
|
if not _is_singleton(node):
|
|
return node
|
|
|
|
return null
|
|
|
|
|
|
# Get list of tree root nodes filtered and sorted according module settings
|
|
func _get_valid_root_nodes() -> Array:
|
|
var nodes: Array = core.get_tree().root.get_children().filter(_root_nodes_filter)
|
|
|
|
if _reverse_root_nodes_order:
|
|
nodes.reverse()
|
|
|
|
return nodes
|
|
|
|
|
|
# Filter function for tree root nodes
|
|
func _root_nodes_filter(node: Node) -> bool:
|
|
# skip panku plugin itself
|
|
if node.name == core.SingletonName:
|
|
return false
|
|
|
|
# skip user defined exceptions
|
|
if _nodes_exception_regexp.is_valid() and _nodes_exception_regexp.search(node.name):
|
|
return false
|
|
|
|
return true
|
|
|
|
|
|
# Find all autoload singletons and bind its to environment vars.
|
|
func _check_autoloads() -> void:
|
|
var _user_singleton_names := []
|
|
|
|
for node in _get_valid_root_nodes():
|
|
if _is_singleton(node):
|
|
# register user singleton
|
|
_user_singleton_names.append(node.name)
|
|
core.gd_exprenv.register_env(node.name, node)
|
|
|
|
if not _user_singleton_names.is_empty():
|
|
_print_to_interactive_shell(USER_AUTOLOADS_TIP % ",".join(_user_singleton_names))
|
|
|
|
|
|
# Print a tip to interactive shell module, modules load order does matter.
|
|
func _print_to_interactive_shell(message: String) -> void:
|
|
if core.module_manager.has_module(SHELL_MODULE_NAME):
|
|
var ishell = core.module_manager.get_module(SHELL_MODULE_NAME)
|
|
ishell.interactive_shell.output(message)
|
|
|
|
|
|
func quit_module():
|
|
_tween_loop.kill()
|
|
super.quit_module()
|