Небуферизованный менеджер ввода в Go?

Я создаю простую консольную игру в Go. Я хочу каким-то образом принимать небуферизованный ввод (например, вы вводите одну клавишу, и она немедленно возвращается). Я начал с этого кода:

func InitInput() {
  exec.Command("stty", "-f", "/dev/tty", "cbreak", "min", "1").Run()
  exec.Command("stty", "-f", "/dev/tty", "-echo").Run()
}
func StopInput() {
  exec.Command("stty", "-f", "/dev/tty", "echo").Run()
}
func GetInput() string {
  var b []byte = make([]byte, 1)
  for {
    os.Stdin.Read(b)
    return string(b)
  }
}

Это было потрясающе, но работает только на ОС *nix и требует 3 функции. Затем кто-то порекомендовал мне этот код:

/*
// Works also for 64 bits
#ifdef _WIN32

// Lib for console management in windows
#include "conio.h"

#else

// Libs terminal management in Unix, Linux...
#include <stdio.h>
#include <unistd.h>
#include <termios.h>

// Implement reading a key pressed in terminal
char getch(){
    char ch = 0;
    struct termios old = {0};
    fflush(stdout);
    if( tcgetattr(0, &old) < 0 ) perror("tcsetattr()");
    old.c_lflag &= ~ICANON;
    old.c_lflag &= ~ECHO;
    old.c_cc[VMIN] = 1;
    old.c_cc[VTIME] = 0;
    if( tcsetattr(0, TCSANOW, &old) < 0 ) perror("tcsetattr ICANON");
    if( read(0, &ch,1) < 0 ) perror("read()");
    old.c_lflag |= ICANON;
    old.c_lflag |= ECHO;
    if(tcsetattr(0, TCSADRAIN, &old) < 0) perror("tcsetattr ~ICANON");
    return ch;
}
#endif
*/
import "C"

И тогда вам нужна только 1 функция:

func GetInput() string {
    return string(byte(C.getch()))
}

Это работает отлично, за исключением того, что из-за того, как работает cgo, он очень медленный, что не идеально для игры. Кроме того, исходный код для проверки новой строки, if extras.GetInput() == "\n" {}, больше не работает. Есть ли способ заставить односимвольный небуферизованный менеджер ввода работать в Go без использования большой, толстой внешней библиотеки?


person Kognise    schedule 16.02.2018    source источник
comment
Вы имеете в виду как github.com/nsf/termbox-go?   -  person icza    schedule 16.02.2018
comment
@icza Это пример того, чего я не хочу. Это огромная, раздутая библиотека, в которой мне нужен только один легкий менеджер ввода.   -  person Kognise    schedule 16.02.2018
comment
Компилятор go не включает много вещей, которые вы не используете из импортированного пакета, поэтому я не вижу в этом проблемы.   -  person icza    schedule 16.02.2018
comment
И termbox-go довольно легковесен: Termbox — это библиотека, предоставляющая минималистичный API...   -  person icza    schedule 16.02.2018
comment
Честно говоря, маленький API не обязательно означает маленькую библиотеку. Это просто означает, что библиотека не очень много экспортирует.   -  person Adrian    schedule 16.02.2018
comment
Я сомневаюсь, что вам действительно нужен небуферизованный ввод для игры. Я бы не хотел, чтобы программа игнорировала нажатия клавиш, пока она обрабатывает (думая, если хотите) мой последний ввод.   -  person Peter    schedule 16.02.2018
comment
@icza Я почти уверен, что termbox-go занимает экран терминала. Я не хочу этого.   -  person Kognise    schedule 16.02.2018
comment
@Peter Может быть, я неправильно понимаю терминологию. Мне нужен диспетчер ввода, который будет возвращать ввод, как только он будет введен, без необходимости пользователю нажимать ввод.   -  person Kognise    schedule 16.02.2018
comment
У меня был тот же вопрос: stackoverflow.com/questions/15159118/   -  person Kaveh Shahbazian    schedule 17.02.2018
comment
@KavehShahbazian Опять же, я не хочу termbox-go. Прочтите это.   -  person Kognise    schedule 19.02.2018


Ответы (1)


Я написал кое-какую консольную обработку из исходных кодов на C, Perl и PHP и собирался сделать это на Python.

Потом я открыл для себя Go и задаюсь теми же вопросами. Согласен с автором темы, надо сделать минимум. Я еще не пробовал ваш код (тот, который включает код термио).

Есть много причин, почему это может быть медленно:

1 / Вы делаете вызов C каждый раз, когда нажимаете клавишу: вы можете изменить эту топологию: сначала вы переводите консоль в режим без буферизации и возвращаете сохраненные параметры в Golang.

2/ Вы нажимаете клавиши в соответствии с этим кодом:

package main
import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    // disable input buffering
    exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
    // do not display entered characters on the screen
    exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
    // restore the echoing state when exiting
    defer exec.Command("stty", "-F", "/dev/tty", "echo").Run()

    var b []byte = make([]byte, 1)
    LOOP:
    for {
        os.Stdin.Read(b)
        var n uint8 = b[0]
        switch n {
        case 27, 113, 81 : fmt.Println("ESC, or 'q' or 'Q' was hitted!")
            break LOOP
        default:
            fmt.Printf("You typed : %d\n", n)
        }
    }
}

Затем, наконец, вы восстанавливаете настройки консоли.

В этом коде: я использую stty, но я полагаю, что мог бы заменить его двумя вызовами подпрограмм C. Что ж, надо попробовать реализовать...

Также вопрос: должны ли мы возвращать байт, char или widechar? Это может быть сложнее наверняка.

Обратите внимание, что у вас также есть расширенные клавиши на клавиатуре, и вы должны прочитать вперед один или несколько байтов, чтобы получить всю последовательность, но это не проблема.

person Jean-Yves DANIEL    schedule 03.01.2021