francogarcia / godot-gdscript.el

Major mode for editing Godot Engine GDScript files with Emacs.
GNU General Public License v3.0
51 stars 9 forks source link

godot-gdscript-run-script fails #2

Closed tavurth closed 6 years ago

tavurth commented 6 years ago

On my machine all of the following fail:

godot-gdscript-shell-run-script
godot-gdscript-shell-send-file (C-c C-l)
godot-gdscript-shell-send-defun (C-M-x)
godot-gdscript-shell-send-string (C-c C-s)
godot-gdscript-shell-send-buffer (C-c C-c)
godot-gdscript-shell-send-region (C-c C-r)
godot-gdscript-shell-switch-to-shell (C-c C-z)

Citing the error: Wrong type argument: arrayp, nil

This comes from the line 1954 @ shell-quote-argument

Any ideas on how to fix this?

francogarcia commented 6 years ago

Hello, @tavurth , How are you?

My apologies, these functions were never properly finished, as I left them as stubs from Python mode. As Godot does not have a built-in REPL, the send function cannot be implemented properly at this time. However, we can add functions to run project files.

In commit c0c9ef54def3, I have added functions and keybindings to run the project and currently edit files into Godot, as well as to open the script and scene in the editor. Currently, the implementation follows the assumption that there will be a corresponding .tscn file to the .gd file (with the same name). The implementation also assumes that we are using Godot 3 instead of Godot 2 (although it would be enough to change project.godot to the former engine.cfg for version 2). Please note that scripts without scenes must inherit from either SceneTree or MainLoop to run (https://godot.readthedocs.io/en/3.0/getting_started/editor/command_line_tutorial.html#running-a-script).

The console output of Godot will be redirected to Emacs after running a script/scene/project. Either change to its buffer (or use the default C-c C-z keybinding to switch to it) to see it.

To use the new features, you first have to provide the path and filename of Godot binary, either using setq (as below) or Emacs customize.

(setq godot-gdscript-shell-interpreter "godot.x11.tools.64"
      godot-gdscript-shell-exec-path '("~/path/to/godot/bin/"))

New functions names and keybindings are as follows:

(define-key map "\C-c\C-g" 'godot-gdscript-run-godot-editor)
(define-key map "\C-c\C-p" 'godot-gdscript-run-project-in-godot)
(define-key map "\C-c\C-s" 'godot-gdscript-run-current-scene-in-godot)
(define-key map "\C-c\C-e" 'godot-gdscript-edit-current-scene-in-godot)
(define-key map "\C-c\C-r" 'godot-gdscript-run-current-script-in-godot)
(define-key map "\C-c\C-dp" 'godot-gdscript-run-project-in-godot-debug-mode)
(define-key map "\C-c\C-ds" 'godot-gdscript-run-current-scene-in-godot-debug-mode)
(define-key map "\C-c\C-z" 'godot-gdscript-shell-switch-to-shell)

They could probably be shortened in future, but I will keep them for this prototype.

I use Evil instead of vanilla Emacs keybindings, so please tell me if the assigned ones do not follow keybindings standards or are not ergonomic enough. If you have any other suggestions, please tell me!

Best regards, Franco

tavurth commented 6 years ago

Ah, that's interesting, I didn't see those functions.

What I've noticed about my workflow with Godot is that the live-edit tool is beautiful, but saving files in Emacs doesn't trigger the live-edit update. It requires first switching to the godot main application and saving any asset there.

If you'd like to try it out, you can enable live-edit from the debug menu at the top of the main godot window. Then when changing any assets location or script, and hitting save, some text in the console about updating should be seen, and the game window will change accordingly.

Would you have any ideas about functionality already included in this mode which could help with sending the update request to the running live-edit prototype?

francogarcia commented 6 years ago

Hello, @tavurth , How are you?

Ah, that's interesting, I didn't see those functions.

To be fair, I created them after you mentioned the issue!

What I've noticed about my workflow with Godot is that the live-edit tool is beautiful, but saving files in Emacs doesn't trigger the live-edit update. It requires first switching to the godot main application and saving any asset there.

This is currently a know issue in Godot tracker: https://github.com/godotengine/godot/issues/10946. There are workarounds for it, such as creating a custom debugger session in command line, connecting to it with a socket, them sending the update request.

As far as I know, there are not other alternatives yet. From the command-line side, maybe it is possible if you are willing to use something like https://github.com/neikeq/gd-autocomplete-service, as it would enable external communication with Godot Engine. I used it for Company autocompletion in the past (https://github.com/francogarcia/company-godot-gdscript.el) (which has to be updated to work with Godot 3); perhaps it could also help to trigger the event.

Best regards, Franco

tavurth commented 6 years ago

Thanks for the link!

Seems like this code would be a good fit to port over into emacs-lisp, I'll perhaps take a look into that later this week and make a PR here if I'm successful.

require 'socket'
require 'listen'

cmd = "\x20\x00\x00\x00\x13\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x0e\x00\x00\x00reload_scripts\x00\x00"

server = TCPServer.open 6008

puts "Listening on port 6008"

loop do
  client = server.accept
  listener = Listen.to('scripts') do |modified|
    if modified
      puts "File modified: #{modified}"
      client.write(cmd)
    end
  end
  listener.start
  # Read content to avoid using kernel memory
  while line = client.recv(1024)
    next if line.length < 10
    # Debug data packets
    #puts "###"
    #puts line.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join(':')
  end
end

Edit: From that link it looks like they're actively working on a PR to fix it in Godot, which would be preferable than an external script solution.

https://github.com/godotengine/godot/pull/20910

tavurth commented 6 years ago

I guess we can close this issue for now.

When gdscript has an integrated interpreter I'll take a look at this again, and see if we can get those functions working.

francogarcia commented 6 years ago

Hello, @tavurth , How are you?

Edit: From that link it looks like they're actively working on a PR to fix it in Godot, which would be preferable than an external script solution.

Yes, they are. For the time being, a quicker solution than converting the previous code snippet to Emacs Lisp would be writing an automation script and add it as a save hook in Emacs. The script could switch to Godot editor, save a scene, then switch back to Emacs. (For instance, Alt + Tab to Godot, Ctrl + S to save the scene, then Alt + Tab back).

In Windows, this should be simple to do with AutoHotkey. In Linux, with xdotool. I do not have any idea on Mac, though. I think there is a Python library for desktop automation, which could provide a multi-platform solution (a quick search leads to this: https://automatetheboringstuff.com/chapter18/).

If going with the automation script route, an even better workflow could be switching to the running game window instead of back to Godot. This way, you could provide input directly to test it. (Of course, you could get creating and write macros to automatically test your game the same way; or, alternatively, write a GDScript with commands and timestamps for replaying).

Best regards, Franco

tavurth commented 6 years ago

Thanks for the tip, that's a really useful program!

#! /bin/bash

## We're required to send our game's window title to this script
if [ $# -eq 0 ];
then
    echo "Game window title required, use:"
    echo "$0 My Game Name"
    exit 1
fi

GAME_TITLE=$1

## First we'll switch to the godot engine and save the files
## This sends the update information over to the running game-window
WID=`xdotool search "Godot Engine" | head -1`
xdotool windowactivate --sync $WID
xdotool key --clearmodifiers ctrl+s

## Now we'll switch to the users game-window
WID=`xdotool search "$GAME_TITLE" | head -1`
xdotool windowactivate --sync $WID

I tried to bind it into by `godot-gdscript-mode hook by doing:

(add-hook 'godot-gdscript-mode
   #'(lambda () (add-hook 'before-save-hook
                 #'(lambda () (shell-command "bash ~/.spacemacs.git/update-godot.sh my-game-name")))))

However, it doesn't seem to fire correctly, and so for now I'm using

;; Update the godot engine
  (global-set-key
   (kbd "M-m o r")
   (lambda ()
     (interactive)
     (shell-command "bash ~/.spacemacs.git/update-godot.sh my-game-name")))
francogarcia commented 6 years ago

Hello, @tavurth , How are you?

Glad you liked! You have created a neat script. I would change Emacs hook to after-save-hook, though, as you are interested in updating the Window after the file changes (otherwise, you would reload the same script file).

I have added a function to retrieve the project's name from the configuration file, so you can avoid changing your configuration when working on another project. For auto reloading after saving, you could do something like this:

(use-package godot-gdscript
  :mode ("\\.gd\\'" . godot-gdscript-mode)
  :defer t
  :init
  (progn
    (defun franco/godot-gdscript-mode-default-settings ()
      (setq mode-name "Godot-GDScript"
            godot-gdscript-shell-interpreter "godot.x11.tools.64"
            godot-gdscript-shell-exec-path `(,(franco/projects "C++/Godot/godot/bin/"))
            godot-gdscript-shell-interpreter-args ""
            indent-tabs-mode nil
            tab-width 4)
      ;; Make C-j work the same way as RET.
      (local-set-key (kbd "C-j") 'newline-and-indent))

    (defun franco/reload-godot-after-save ()
      "Auto-reload the project with `xdotool', then focus on
e game window."
      (add-hook
       'after-save-hook
       #'(lambda ()
           (shell-command (concat "bash ~/tmp/update-godot.sh " (godot-gdscript-get-project-name))))
       nil 'make-it-local)))

    (spacemacs/add-all-to-hook 'godot-gdscript-mode-hook
                               'franco/godot-gdscript-mode-default-settings
                               'franco/reload-godot-after-save)
  :config
  (progn
    ;; Keybindings.
    (spacemacs/declare-prefix-for-mode 'godot-gdscript-mode "mc" "execute")
    (spacemacs/declare-prefix-for-mode 'godot-gdscript-mode "md" "debug")

    (spacemacs/set-leader-keys-for-major-mode 'godot-gdscript-mode
      "," 'godot-gdscript-run-project-in-godot

      "cg" 'godot-gdscript-run-godot-editor
      "cp" 'godot-gdscript-run-project-in-godot
      "ce"  'godot-gdscript-edit-current-scene-in-godot
      "cs" 'godot-gdscript-run-current-scene-in-godot
      "cs" 'godot-gdscript-run-current-script-in-godot

      "dp" 'godot-gdscript-run-project-in-godot-debug-mode
      "ds" 'godot-gdscript-run-current-scene-in-godot-debug-mode
      "dd" 'godot-gdscript-shell-switch-to-shell

      "gi" 'imenu))))

The inner lambda registers the update only on GDScript mode buffers. If you want to update the game when saving any buffers, you may remove it.

The remaining of the code is from my personal Spacemacs layer for Godot. You may find M-x imenu useful to jumping to functions definitions. If you use Helm, M-x helm-semantic-or-imenu allows for fuzzy matching.

Best regards, Franco