Как изменить способ ввода в браузере emscripten из window.prompt на что-то более разумное?

У меня есть функция C++, которая когда-то вызывала потребляет ввод из стандартного ввода. Экспорт этой функции в javascript с использованием emscripten вызывает вызовы window.prompt.

Взаимодействие с подсказкой браузера - действительно утомительная задача. Прежде всего, вы можете вставить только одну строку за раз. Во-вторых, единственный способ указать EOF - нажать "Отмена". Последний, но не менее важный способ (в случае моей функции) заставить его перестать запрашивать ввод у пользователя через window.prompt - установить флажок, предотвращающий появление дополнительных запросов.

Для меня лучшим способом ввода было бы чтение какого-нибудь блоба. Я знаю, что могу взломать library.js, но вижу некоторые проблемы:

  1. Чтение BLOB-объектов является асинхронным.
  2. Чтобы прочитать блоб, сначала нужно открыть файл, который пользователь должен выбрать первым.
  3. Я действительно не знаю, как запретить моей функции читать этот BLOB-объект вечно - нет флажка, как в случае с window.prompt, и я не уверен, остановит ли EOF остановку, если это не произошло в случае window.prompt (только помогла проверка флажка).

Лучшим решением будет какой-то обратный вызов, но я хотел бы увидеть подсказки от более опытных пользователей.

2 ответа

Можно было бы использовать API файловой системы Emscripten, например, вызвав FS.init в функции preRun модуля, передавая пользовательскую функцию в качестве стандартного ввода.

var Module = {
  preRun: function() {
    function stdin() {
      // Return ASCII code of character, or null if no input
    }

    var stdout = null; // Keep as default
    var stderr = null;  // Keep as default
    FS.init(stdin, stdout, stderr);
  }
};

Функция довольно низкого уровня: она должна иметь дело с одним символом за раз. Чтобы прочитать некоторые данные из BLOB-объекта, вы можете сделать что-то вроде:

var data = new Int8Array([1,2,3,4,5]);
var blob = new Blob([array], {type: 'application/octet-binary'});
var reader = new FileReader();
var result;
reader.addEventListener("loadend", function() {
  result = new Int8Array(reader.result);
});
var i = 0;
var Module = {
  preRun: function() {
    function stdin() {
      if (if < result.byteLength {
        var code = result[i];
        ++i;
        return code;
      } else {
        return null;
      }
    }

    var stdout = null; // Keep as default
    var stderr = null; // Keep as default
    FS.init(stdin, stdout, stderr);
  }
};

Обратите внимание (как вы намекали), из-за асинхронного характера считывателя может возникнуть условие гонки: считыватель должен быть загружен, прежде чем вы сможете ожидать данные на стандартном входе. Возможно, вам придется реализовать какой-то механизм, чтобы избежать этого в реальном случае. В зависимости от ваших конкретных требований, вы можете сделать так, чтобы программа Emscripten на самом деле не вызывала main() пока у вас нет данных:

var fileRead = false;
var initialised = false;
var result;

var array =  new Int8Array([1,2,3,4,5]);
var blob = new Blob([array], {type: 'application/octet-binary'});
var reader = new FileReader();
reader.addEventListener("loadend", function() {
   result = new Int8Array(reader.result);
   fileRead = true;
   runIfCan();
});
reader.readAsArrayBuffer(blob);

var i = 0;
var Module = {
   preRun: function() {
      function stdin() {
         if (i < result.byteLength)
         {
            var code = result[i];
            ++i;
            return code;
         } else{
            return null;
         }
      }

      var stdout = null;
      var stderr = null;
      FS.init(stdin, stdout, stderr);
      initialised = true;
      runIfCan();
   },
   noInitialRun: true
};

function runIfCan() {
   if (fileRead && initialised) {
      // Module.run() doesn't seem to work here
      Module.callMain();
   }
}

Примечание: это версия моего ответа в разделе "Предоставление стандартного ввода в HTML-программу с шифрованием"?, но с акцентом на стандартный ввод и добавлением частей о передаче данных из BLOB-объекта.

Из того, что я понимаю, вы можете попробовать следующее:

  1. Реализуйте выбор файла в Javascript и доступ к нему через Javascript Blob интерфейс.
  2. Выделите немного памяти в Emscripten

    var buf = Module._malloc( blob.size );
    
  3. Напишите содержание вашего Blob в возвращенную область памяти из Javascript.

    Module.HEAPU8.set( new Uint8Array(blob), buf );
    
  4. Передайте эту область памяти второй скомпилированной функции Emscripten, которая затем обрабатывает содержимое файла и

  5. Освободить выделенную память.

    Module._free( buf );
    

Лучше всего сначала прочитать вики.

Другие вопросы по тегам