Start working on a better shell mechanism for Windows

It uses a Perl script to keep a bash session open, which attaches to a
port the game keeps open. This avoids having to start a new Git bash for
each command, improving the execution speed by a factor of 3-4.
This commit is contained in:
blinry 2021-02-23 13:06:58 +01:00
parent 2a82b617be
commit 53c249d059
8 changed files with 145 additions and 15 deletions

View file

@ -20,6 +20,8 @@ func _ready():
get_tree().set_auto_accept_quit(false)
else:
game.toggle_music()
start_remote_shell()
global_shell = Shell.new()
@ -33,7 +35,26 @@ func _ready():
copy_script_to_game_env("hint")
load_state()
func start_remote_shell():
var user_dir = ProjectSettings.globalize_path("user://")
var script_content = helpers.read_file("res://scripts/net-test")
var target_filename = user_dir + "net-test"
var target_file = File.new()
target_file.open(target_filename, File.WRITE)
target_file.store_string(script_content)
target_file.close()
helpers.exec_async(_perl_executable(), [target_filename])
func _perl_executable():
if OS.get_name() == "Windows":
return "dependencies/windows/git/usr/bin/perl.exe"
else:
return "perl"
func shell_received(text):
print(text)
func _notification(what):
if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
#get_tree().quit() # default behavio
@ -99,3 +120,7 @@ func toggle_music():
music.volume_db -= 100
else:
music.volume_db += 100
func shell_test(command):
return $ShellServer.send(command)

View file

@ -1,7 +1,8 @@
[gd_scene load_steps=3 format=2]
[gd_scene load_steps=4 format=2]
[ext_resource path="res://scenes/game.gd" type="Script" id=1]
[ext_resource path="res://music/gigantic-greasy-giraffe.ogg" type="AudioStream" id=2]
[ext_resource path="res://scenes/tcp_server_shell.tscn" type="PackedScene" id=3]
[node name="Node" type="Node"]
script = ExtResource( 1 )
@ -10,3 +11,6 @@ script = ExtResource( 1 )
stream = ExtResource( 2 )
volume_db = -15.0
autoplay = true
[node name="ShellServer" parent="." instance=ExtResource( 3 )]
port = 6666

View file

@ -35,6 +35,9 @@ func exec(command, args=[], crash_on_fail=true):
return {"output": output, "exit_code": exit_code}
func exec_async(command, args=[]):
OS.execute(command, args, false)
# Return the contents of a file. If no fallback_string is provided, crash when
# the file doesn't exist.
func read_file(path, fallback_string=null):

View file

@ -1,6 +1,7 @@
extends Node
signal data_received(string)
signal new_connection
export var port: int
@ -21,21 +22,24 @@ func _process(_delta):
helpers.crash("Dropping active connection")
_c = _s.take_connection()
_connected = true
emit_signal("new_connection")
print("connected!")
if _connected:
if _c.get_status() != StreamPeerTCP.STATUS_CONNECTED:
_connected = false
print("disconnected")
var available = _c.get_available_bytes()
while available > 0:
var data = _c.get_utf8_string(available)
emit_signal("data_received", data)
available = _c.get_available_bytes()
# var available = _c.get_available_bytes()
# while available > 0:
# var data = _c.get_utf8_string(available)
# emit_signal("data_received", data)
# available = _c.get_available_bytes()
func send(text):
if _connected:
text += "\n"
_c.put_data(text.to_utf8())
_c.put_utf8_string(text)
var response = _c.get_utf8_string()
emit_signal("data_received", response)
else:
helpers.crash("Trying to send data on closed connection")

View file

@ -0,0 +1,50 @@
extends Node
signal data_received(string)
signal new_connection
export var port: int
var _s = TCP_Server.new()
var _c
var _connected = false
func _ready():
start()
func start():
_s.listen(port)
func _process(_delta):
if _s.is_connection_available():
if _connected:
_c.disconnect_from_host()
helpers.crash("Dropping active connection")
_c = _s.take_connection()
_connected = true
emit_signal("new_connection")
print("connected!")
if _connected:
if _c.get_status() != StreamPeerTCP.STATUS_CONNECTED:
_connected = false
print("disconnected")
# var available = _c.get_available_bytes()
# while available > 0:
# var data = _c.get_utf8_string(available)
# emit_signal("data_received", data)
# available = _c.get_available_bytes()
func send(text):
if _connected:
_c.put_utf8_string(text)
var response = _c.get_utf8_string()
var exit_code = _c.get_u32()
var shell_command = ShellCommand.new()
shell_command.command = text
shell_command.output = response
shell_command.exit_code = exit_code
return shell_command
else:
helpers.crash("Trying to send data on closed connection")

View file

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://scenes/tcp_server_shell.gd" type="Script" id=1]
[node name="TCPServerShell" type="Node"]
script = ExtResource( 1 )

View file

@ -85,8 +85,9 @@ func send_command(command):
editor_regex.compile("^(vim?|gedit|emacs|kate|nano|code) ")
command = editor_regex.sub(command, "fake-editor ")
var cmd = repository.shell.run_async(command, false)
yield(cmd, "done")
# var cmd = repository.shell.run_async(command, false)
# yield(cmd, "done")
var cmd = game.shell_test(command)
call_deferred("command_done", cmd)
func command_done(cmd):

View file

@ -1,24 +1,61 @@
#!/usr/bin/env perl
use IPC::Open2;
use IO::Socket;
my $pid = open2(my $out, my $in, 'bash');
$socket = IO::Socket::INET->new(PeerAddr => "127.0.0.1",
PeerPort => 6666,
Proto => "tcp",
Type => SOCK_STREAM);
$s = "Hey äöü!";
my $send_length = pack("L", length($s));
$socket->send($send_length);
$socket->send($s);
#$s = "Hey äöü!";
#
#my $send_length = pack("L", length($s));
#$socket->send($send_length);
#$socket->send($s);
while(true) {
#STDOUT->flush();
my $len;
$socket->recv($len, 4);
my $actual_len = unpack("L", $len);
if ($actual_len == 0) {
print("not connected");
exit;
}
print("still connected");
my $s2;
$socket->recv($s2, ord($len));
print($s2);
STDOUT->flush();
#$s = `bash -c '$s2' 2>&1`;
$s = "";
$command = $s2 . "\necho MAGIC\n";
print $in $command;
inner_while: while (true) {
$line = <$out>;
if ($line eq "MAGIC\n") {
STDOUT->flush();
last inner_while;
}
$s = $s . $line;
}
print "got complete output";
print $s;
print length($s);
STDOUT->flush();
my $send_length = pack("L", length($s));
$socket->send($send_length);
$socket->send($s);
my $exit_code = pack("L", 0);
$socket->send($exit_code);
}