pandora icon indicating copy to clipboard operation
pandora copied to clipboard

🔥 Hot reloading

Open bitbrain opened this issue 2 years ago • 2 comments

While debugging my game, I want to change things in Pandora and see them reflected in my game instantly. Currently, you have to restart the game in order for changes to take effect.

bitbrain avatar Sep 24 '23 09:09 bitbrain

Hi @bitbrain 👋

I'd like to help implement hot-reloading for Pandora and have put together a small prototype to start the conversation.

Proposed approach

  1. File watcher node (file_watcher.gd)

    @tool
    class_name PandoraFileWatcher
    extends Node
    
    signal file_changed(file_path: String)
    
    var _watched_file: String
    var _last_modified_time: int = 0
    var _check_timer := Timer.new()
    var _debounce_timer := Timer.new()
    
    func _ready() -> void:
        _check_timer.wait_time = 0.5
        _check_timer.autostart = true
        _check_timer.timeout.connect(_check_file_changes)
        add_child(_check_timer)
    
        _debounce_timer.wait_time = 0.2
        _debounce_timer.one_shot = true
        _debounce_timer.timeout.connect(_emit_file_changed)
        add_child(_debounce_timer)
    
    func watch_file(file_path: String) -> void:
        _watched_file = file_path
        if FileAccess.file_exists(_watched_file):
            _last_modified_time = FileAccess.get_modified_time(_watched_file)
    
    func _check_file_changes() -> void:
        if _watched_file.is_empty() or not FileAccess.file_exists(_watched_file):
            return
        var current_time = FileAccess.get_modified_time(_watched_file)
        if current_time > _last_modified_time:
            _last_modified_time = current_time
            _debounce_timer.start()
    
    func _emit_file_changed() -> void:
        file_changed.emit(_watched_file)
    
  2. Integrate in api.gd

    signal data_reloaded
    
    var _file_watcher := PandoraFileWatcher.new()
    add_child(_file_watcher)
    _file_watcher.file_changed.connect(_on_data_file_changed)
    _file_watcher.watch_file(data_path)
    
    func _on_data_file_changed(_fp: String) -> void:
        reload_data()
    
    func reload_data() -> void:
        if not _loaded:
            return
        _clear()
        load_data()
        data_reloaded.emit()
    

Questions & next steps

  • Scope – Is watching a single data file enough for the first iteration, or do we also want to monitor entire folders / other asset types?
  • Performance – A 0.5s polling interval feels responsive in practice, but I can expose it as an export var if you'd prefer configurability.
  • Debounce – I added a 0.2s debounce to avoid double-fires on rapid saves; let me know if that should be tuned differently.
  • API surface – Are the file_changed and data_reloaded signals the hooks you had in mind, or would you like a different design?

If this direction looks good I'm happy to polish it and open a PR.

Thanks, and let me know what you think!

JohnC0de avatar Jul 29 '25 15:07 JohnC0de

Feel free to create a PR and I can leave specific feedback on the code itself. This is a great start!

One issue I can see is object referencing: after reloading the data, objects in the game may still have their old objects initialized, so the new state is not reflected in-game. To make matters more complex, there are also "entity instances" that should automatically receive those changes.

bitbrain avatar Jul 31 '25 16:07 bitbrain