web implementation, missing js and wasm artifacts
This commit is contained in:
parent
bdeee62de8
commit
8f9f220c40
12 changed files with 306 additions and 30 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -44,4 +44,9 @@ src/-lstdc++.res
|
||||||
# Neural network for the NNUE evaluation
|
# Neural network for the NNUE evaluation
|
||||||
**/*.nnue
|
**/*.nnue
|
||||||
|
|
||||||
flags.txt
|
flags.txt
|
||||||
|
|
||||||
|
CMakeFiles
|
||||||
|
cmake_install.cmake
|
||||||
|
CMakeCache.txt
|
||||||
|
Makefile
|
15
README.md
15
README.md
|
@ -28,6 +28,21 @@ stockfish.dispose();
|
||||||
|
|
||||||
A complete Example can be found at [stockfish_chess_engine](https://github.com/loloof64/StockfishChessEngineFlutter).
|
A complete Example can be found at [stockfish_chess_engine](https://github.com/loloof64/StockfishChessEngineFlutter).
|
||||||
|
|
||||||
|
## Web support
|
||||||
|
Web support is currently experimental. It uses a version of stockfish compiled with [emscripten](https://emscripten.org/).
|
||||||
|
|
||||||
|
In order to make multithreading available, the site must run in a secure environment.
|
||||||
|
The following headers must be set for this:
|
||||||
|
|
||||||
|
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||||
|
- `Cross-Origin-Opener-Policy: same-origin`
|
||||||
|
|
||||||
|
Problems:
|
||||||
|
- The current version includes the `.js`, `.wasm` and neuralnetwork data as assets.
|
||||||
|
These files are bundled with every build on every platform, even if they are not needed.
|
||||||
|
This approach wasts about 41MB, if not striped out by hand.
|
||||||
|
|
||||||
|
|
||||||
## Goal of this fork of stockfish_chess_engine
|
## Goal of this fork of stockfish_chess_engine
|
||||||
|
|
||||||
* Avoid limitation. This version does not redirect stdout and stdin of the app for communication with stockfish.
|
* Avoid limitation. This version does not redirect stdout and stdin of the app for communication with stockfish.
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_stockfish_plugin/stockfish_bindings.dart';
|
import 'package:flutter_stockfish_plugin/stockfish_bindings.dart';
|
||||||
import 'package:flutter_stockfish_plugin/stockfish_native_bindings.dart'
|
import 'package:flutter_stockfish_plugin/stockfish_native_bindings.dart'
|
||||||
if (dart.library.html) 'package:flutter_stockfish_plugin/stockfish_web_bindings.dart';
|
if (dart.library.html) 'package:flutter_stockfish_plugin/stockfish_web_bindings.dart';
|
||||||
import 'package:flutter_stockfish_plugin/stockfish_state.dart';
|
import 'package:flutter_stockfish_plugin/stockfish_state.dart';
|
||||||
|
|
||||||
final StockfishChessEngineAbstractBindings _bindings =
|
|
||||||
StockfishChessEngineBindings();
|
|
||||||
|
|
||||||
class Stockfish {
|
class Stockfish {
|
||||||
final _state = StockfishStateClass();
|
final _state = StockfishStateClass();
|
||||||
|
final StockfishChessEngineAbstractBindings _bindings =
|
||||||
|
StockfishChessEngineBindings();
|
||||||
|
|
||||||
Stockfish._() {
|
Stockfish._({Completer<Stockfish>? completer}) {
|
||||||
_state.setValue(StockfishState.starting);
|
_state.setValue(StockfishState.starting);
|
||||||
_bindings.stockfishMain(() {
|
_bindings.stockfishMain(() {
|
||||||
_state.setValue(StockfishState.ready);
|
_state.setValue(StockfishState.ready);
|
||||||
|
completer?.complete(this);
|
||||||
}).then((exitCode) {
|
}).then((exitCode) {
|
||||||
_state.setValue(
|
_state.setValue(
|
||||||
exitCode == 0 ? StockfishState.disposed : StockfishState.error);
|
exitCode == 0 ? StockfishState.disposed : StockfishState.error);
|
||||||
|
@ -21,12 +23,13 @@ class Stockfish {
|
||||||
}, onError: (error) {
|
}, onError: (error) {
|
||||||
_state.setValue(StockfishState.error);
|
_state.setValue(StockfishState.error);
|
||||||
_instance = null;
|
_instance = null;
|
||||||
|
completer?.completeError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static Stockfish? _instance;
|
static Stockfish? _instance;
|
||||||
|
|
||||||
/// Creates a C++ engine.
|
/// Creates the stockfish engine.
|
||||||
///
|
///
|
||||||
/// This may throws a [StateError] if an active instance is being used.
|
/// This may throws a [StateError] if an active instance is being used.
|
||||||
/// Owner must [dispose] it before a new instance can be created.
|
/// Owner must [dispose] it before a new instance can be created.
|
||||||
|
@ -38,7 +41,7 @@ class Stockfish {
|
||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The current state of the underlying C++ engine.
|
/// The current state of the underlying stockfish engine.
|
||||||
ValueListenable<StockfishState> get state => _state;
|
ValueListenable<StockfishState> get state => _state;
|
||||||
|
|
||||||
/// The standard output stream.
|
/// The standard output stream.
|
||||||
|
@ -53,23 +56,25 @@ class Stockfish {
|
||||||
_bindings.write(line);
|
_bindings.write(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops the C++ engine.
|
/// Stops the stockfish engine.
|
||||||
void dispose() {
|
void dispose() {
|
||||||
final stateValue = state.value;
|
final stateValue = state.value;
|
||||||
if (stateValue == StockfishState.ready) {
|
if (stateValue == StockfishState.ready) {
|
||||||
stdin = 'quit';
|
stdin = 'quit';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void _cleanUp(int exitCode) {
|
|
||||||
/*_stdoutController.close();
|
/// Creates the stockfish engine asynchronously.
|
||||||
|
///
|
||||||
_mainSubscription.cancel();
|
/// This method is different from the factory method [Stockfish] that
|
||||||
_stdoutSubscription.cancel();
|
/// it will wait for the engine to be ready before returning the instance.
|
||||||
|
Future<Stockfish> stockfishAsync() {
|
||||||
_state._setValue(
|
if (Stockfish._instance != null) {
|
||||||
exitCode == 0 ? StockfishState.disposed : StockfishState.error);
|
return Future.error(StateError('Only one instance can be used at a time'));
|
||||||
|
}
|
||||||
_instance = null;*/
|
|
||||||
}
|
final completer = Completer<Stockfish>();
|
||||||
|
Stockfish._instance = Stockfish._(completer: completer);
|
||||||
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,25 @@ class StockfishChessEngineCBindings {
|
||||||
_lookup<ffi.NativeFunction<ffi.Int Function()>>('stockfish_main');
|
_lookup<ffi.NativeFunction<ffi.Int Function()>>('stockfish_main');
|
||||||
late final _stockfish_main = _stockfish_mainPtr.asFunction<int Function()>();
|
late final _stockfish_main = _stockfish_mainPtr.asFunction<int Function()>();
|
||||||
|
|
||||||
|
void stockfish_start_main() {
|
||||||
|
return _stockfish_start_main();
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _stockfish_start_mainPtr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stockfish_start_main');
|
||||||
|
late final _stockfish_start_main =
|
||||||
|
_stockfish_start_mainPtr.asFunction<void Function()>();
|
||||||
|
|
||||||
|
int stockfish_last_main_state() {
|
||||||
|
return _stockfish_last_main_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _stockfish_last_main_statePtr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Int Function()>>(
|
||||||
|
'stockfish_last_main_state');
|
||||||
|
late final _stockfish_last_main_state =
|
||||||
|
_stockfish_last_main_statePtr.asFunction<int Function()>();
|
||||||
|
|
||||||
int stockfish_stdin_write(
|
int stockfish_stdin_write(
|
||||||
ffi.Pointer<ffi.Char> data,
|
ffi.Pointer<ffi.Char> data,
|
||||||
) {
|
) {
|
||||||
|
@ -74,3 +93,4 @@ class StockfishChessEngineCBindings {
|
||||||
|
|
||||||
typedef ssize_t = __ssize_t;
|
typedef ssize_t = __ssize_t;
|
||||||
typedef __ssize_t = ffi.Long;
|
typedef __ssize_t = ffi.Long;
|
||||||
|
typedef Dart__ssize_t = int;
|
||||||
|
|
|
@ -64,6 +64,7 @@ class StockfishChessEngineBindings
|
||||||
onError: (error) {
|
onError: (error) {
|
||||||
developer.log('The init isolate encountered an error $error',
|
developer.log('The init isolate encountered an error $error',
|
||||||
name: 'Stockfish');
|
name: 'Stockfish');
|
||||||
|
completer.completeError(error);
|
||||||
cleanUp(1);
|
cleanUp(1);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,25 +1,78 @@
|
||||||
import 'dart:async';
|
// ignore_for_file: avoid_web_libraries_in_flutter
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:js' as js;
|
||||||
|
import 'dart:html' as html;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_stockfish_plugin/stockfish_bindings.dart';
|
import 'package:flutter_stockfish_plugin/stockfish_bindings.dart';
|
||||||
|
|
||||||
class StockfishChessEngineBindings
|
class StockfishChessEngineBindings
|
||||||
extends StockfishChessEngineAbstractBindings {
|
extends StockfishChessEngineAbstractBindings {
|
||||||
@override
|
Future<void>? loadJs;
|
||||||
void cleanUp(int exitCode) {
|
StockfishChessEngineBindings() {
|
||||||
// TODO: implement cleanUp
|
loadJs = loadJsFileIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> stockfishMain(Function active) {
|
void cleanUp(int exitCode) {
|
||||||
// TODO: implement stockfishMain
|
stdoutController.close();
|
||||||
|
js.context.callMethod("stop_listening", []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> stockfishMain(Function active) async {
|
||||||
|
if (loadJs != null) {
|
||||||
|
await loadJs;
|
||||||
|
loadJs = null;
|
||||||
|
}
|
||||||
final completer = Completer<int>();
|
final completer = Completer<int>();
|
||||||
//completer.complete(0);
|
js.context.callMethod("start_listening", [
|
||||||
|
(line) => stdoutController.sink.add(line),
|
||||||
|
(state) {
|
||||||
|
cleanUp(state is int ? state : 1);
|
||||||
|
completer.complete(state is int ? state : 1);
|
||||||
|
}
|
||||||
|
]);
|
||||||
active();
|
active();
|
||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(String line) {
|
void write(String line) {
|
||||||
// TODO: implement write
|
js.context.callMethod("write", [line]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _jsloaded = false;
|
||||||
|
|
||||||
|
Future<void> loadJsFileIfNeeded() async {
|
||||||
|
if (kIsWeb && !_jsloaded) {
|
||||||
|
final stockfishScript = html.document.createElement("script");
|
||||||
|
stockfishScript.setAttribute("src",
|
||||||
|
"assets/packages/flutter_stockfish_plugin/web/flutter_stockfish_plugin.js");
|
||||||
|
html.document.head?.append(stockfishScript);
|
||||||
|
|
||||||
|
await stockfishScript.onLoad.first;
|
||||||
|
|
||||||
|
final jsBindingsScript = html.document.createElement("script");
|
||||||
|
jsBindingsScript.setAttribute(
|
||||||
|
"src", "assets/packages/flutter_stockfish_plugin/web/js_bindings.js");
|
||||||
|
html.document.head?.append(jsBindingsScript);
|
||||||
|
|
||||||
|
await jsBindingsScript.onLoad.first;
|
||||||
|
|
||||||
|
await _stockfishWaitReady();
|
||||||
|
|
||||||
|
//js.context.callMethod("t_cb", [test]);
|
||||||
|
_jsloaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _stockfishWaitReady() {
|
||||||
|
final completer = Completer<dynamic>();
|
||||||
|
js.context.callMethod('wait_ready', [
|
||||||
|
completer.complete,
|
||||||
|
]);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
|
@ -46,7 +46,11 @@ flutter:
|
||||||
web:
|
web:
|
||||||
# -------------------
|
# -------------------
|
||||||
assets:
|
assets:
|
||||||
- web/test.js
|
- web/js_bindings.js
|
||||||
|
- web/stockfish_data.bin
|
||||||
|
- web/flutter_stockfish_plugin.js
|
||||||
|
- web/flutter_stockfish_plugin.wasm
|
||||||
|
- web/flutter_stockfish_plugin.worker.js
|
||||||
# To add assets to your plugin package, add an assets section, like this:
|
# To add assets to your plugin package, add an assets section, like this:
|
||||||
# assets:
|
# assets:
|
||||||
# - images/a_dot_burr.jpeg
|
# - images/a_dot_burr.jpeg
|
||||||
|
|
|
@ -27,7 +27,7 @@ if(MSVC)
|
||||||
else()
|
else()
|
||||||
set(COMMON_FLAGS "-Wall -Wcast-qual -Wno-main -fno-exceptions -std=c++17 -pedantic -Wextra -Wshadow -Wmissing-declarations -flto -DUSE_PTHREADS")
|
set(COMMON_FLAGS "-Wall -Wcast-qual -Wno-main -fno-exceptions -std=c++17 -pedantic -Wextra -Wshadow -Wmissing-declarations -flto -DUSE_PTHREADS")
|
||||||
|
|
||||||
set(SIMD_FLAGS "-mpopcnt -DUSE_POPCNT -msse -DUSE_SSE2 -msse2 -msse3 -DUSE_SSSE3 -mssse3 -DUSE_SSE41 -msse4.1 -DUSE_SSE42 -msse4.2")
|
set(SIMD_FLAGS "-mpopcnt -DUSE_POPCNT -mavx -msse -DUSE_SSE2 -msse2 -msse3 -DUSE_SSSE3 -mssse3 -DUSE_SSE41 -msse4.1 -DUSE_SSE42 -msse4.2")
|
||||||
|
|
||||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
|
if (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
|
||||||
message(STATUS "Adding x86_64 specific flags")
|
message(STATUS "Adding x86_64 specific flags")
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
#include "fixes.h"
|
#include "fixes.h"
|
||||||
#include "stockfish.h"
|
#include "stockfish.h"
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
const char* QUITOK = "quitok\n";
|
const char* QUITOK = "quitok\n";
|
||||||
|
|
||||||
|
@ -27,7 +28,10 @@ int stockfish_init() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _last_main_state = -2;
|
||||||
|
|
||||||
int stockfish_main() {
|
int stockfish_main() {
|
||||||
|
_last_main_state = -1;
|
||||||
int argc = 1;
|
int argc = 1;
|
||||||
char* empty = (char*)malloc(0);
|
char* empty = (char*)malloc(0);
|
||||||
*empty = 0;
|
*empty = 0;
|
||||||
|
@ -46,9 +50,19 @@ int stockfish_main() {
|
||||||
fakeout.close();
|
fakeout.close();
|
||||||
fakein.close();
|
fakein.close();
|
||||||
|
|
||||||
|
_last_main_state = exitCode;
|
||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stockfish_start_main(){
|
||||||
|
std::thread t(stockfish_main);
|
||||||
|
t.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
int stockfish_last_main_state(){
|
||||||
|
return _last_main_state;
|
||||||
|
}
|
||||||
|
|
||||||
ssize_t stockfish_stdin_write(char* data) {
|
ssize_t stockfish_stdin_write(char* data) {
|
||||||
std::string val(data);
|
std::string val(data);
|
||||||
fakein << val << fakeendl;
|
fakein << val << fakeendl;
|
||||||
|
|
|
@ -38,6 +38,20 @@ extern "C"
|
||||||
FFI_PLUGIN_EXPORT int
|
FFI_PLUGIN_EXPORT int
|
||||||
stockfish_main();
|
stockfish_main();
|
||||||
|
|
||||||
|
// Stockfish start main loop.
|
||||||
|
#ifndef _ffigen
|
||||||
|
extern "C"
|
||||||
|
#endif
|
||||||
|
FFI_PLUGIN_EXPORT void
|
||||||
|
stockfish_start_main();
|
||||||
|
|
||||||
|
// Stockfish last main loop state.
|
||||||
|
#ifndef _ffigen
|
||||||
|
extern "C"
|
||||||
|
#endif
|
||||||
|
FFI_PLUGIN_EXPORT int
|
||||||
|
stockfish_last_main_state();
|
||||||
|
|
||||||
// Writing to Stockfish STDIN.
|
// Writing to Stockfish STDIN.
|
||||||
#ifndef _ffigen
|
#ifndef _ffigen
|
||||||
extern "C"
|
extern "C"
|
||||||
|
|
52
web/CMakeLists.txt
Normal file
52
web/CMakeLists.txt
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# The Flutter tooling requires that developers have CMake 3.18 or later
|
||||||
|
# installed. You should not increase this version, as doing so will cause
|
||||||
|
# the plugin to fail to compile for some customers of the plugin.
|
||||||
|
cmake_minimum_required(VERSION 3.18)
|
||||||
|
|
||||||
|
project(flutter_stockfish_plugin)
|
||||||
|
file(GLOB_RECURSE cppPaths "../src/Stockfish/src/*.cpp")
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
set(NNUE_NAME nn-5af11540bbfe.nnue)
|
||||||
|
|
||||||
|
add_definitions(-DNNUE_EMBEDDING_OFF) # embeding nnue network is currently not supported.
|
||||||
|
|
||||||
|
set(EMSCRIPTEN_PATH "$ENV{EMSDK}/upstream/emscripten" CACHE STRING "Path to Emscripten")
|
||||||
|
set(CMAKE_TOOLCHAIN_FILE "${EMSCRIPTEN_PATH}/cmake/Modules/Platform/Emscripten.cmake" CACHE STRING "Emscripten toolchain file")
|
||||||
|
set(CMAKE_CXX_COMPILER "${EMSCRIPTEN_PATH}/em++")
|
||||||
|
|
||||||
|
set(COMMON_FLAGS "-Wall -Wcast-qual -Wno-main -fno-exceptions -std=c++17 -pedantic -Wextra -Wshadow -Wmissing-declarations -flto")
|
||||||
|
set(SIMD_FLAGS "${CMAKE_CXX_FLAGS} -msimd128 -mavx -msse -DUSE_SSE2 -msse2 -msse3 -DUSE_SSSE3 -mssse3 -DUSE_SSE41 -msse4.1 -DUSE_SSE42 -msse4.2")
|
||||||
|
|
||||||
|
set(EM_FLAGS "${EM_FLAGS} -s WASM=1 -sASYNCIFY")
|
||||||
|
set(EM_FLAGS "${EM_FLAGS} -s EXPORTED_RUNTIME_METHODS=ccall,cwrap")
|
||||||
|
set(EM_FLAGS "${EM_FLAGS} -s TOTAL_STACK=8MB -s INITIAL_MEMORY=512MB -s ALLOW_MEMORY_GROWTH")
|
||||||
|
set(EM_FLAGS "${EM_FLAGS} -s PTHREAD_POOL_SIZE=32")
|
||||||
|
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMMON_FLAGS} ${SIMD_FLAGS} -O3 -DNDEBUG -s USE_PTHREADS=1 -Dmain=runMain")
|
||||||
|
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}
|
||||||
|
"../src/stockfish.cpp"
|
||||||
|
"../src/stream_fix.cpp"
|
||||||
|
"../src/small_fixes.cpp"
|
||||||
|
${cppPaths}
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "${EM_FLAGS}")
|
||||||
|
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}.js")
|
||||||
|
|
||||||
|
add_definitions(-include ../src/fixes.h)
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME}
|
||||||
|
PUBLIC
|
||||||
|
"./"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
file(DOWNLOAD https://tests.stockfishchess.org/api/nn/${NNUE_NAME} ${CMAKE_BINARY_DIR}/stockfish_data.bin)
|
||||||
|
|
||||||
|
|
||||||
|
|
93
web/js_bindings.js
Normal file
93
web/js_bindings.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
const nnue_name = "nn-5af11540bbfe.nnue";
|
||||||
|
|
||||||
|
let s_read, s_write, s_main, s_init, s_state;
|
||||||
|
|
||||||
|
let ready = false;
|
||||||
|
let ready_cb = null;
|
||||||
|
|
||||||
|
Module.onRuntimeInitialized = async function () {
|
||||||
|
let data = await fetch("assets/packages/flutter_stockfish_plugin/web/stockfish_data.bin");
|
||||||
|
let b = new Uint8Array(await data.arrayBuffer());
|
||||||
|
FS.createDataFile("/", nnue_name, b, true, false, true);
|
||||||
|
s_read = Module.cwrap("stockfish_stdout_read", "char*", ["bool"], { async: false });
|
||||||
|
s_write = Module.cwrap("stockfish_stdin_write", "ssize_t", ["char*"], { async: false });
|
||||||
|
s_main = Module.cwrap("stockfish_start_main", "void", [], { async: false });
|
||||||
|
s_init = Module.cwrap("stockfish_init", "int", [], { async: false });
|
||||||
|
s_state = Module.cwrap("stockfish_last_main_state", "int", [], { async: false });
|
||||||
|
ready = true;
|
||||||
|
if (ready_cb) ready_cb();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function wait_ready(res) {
|
||||||
|
if (ready) return void res();
|
||||||
|
ready_cb = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _listener_id = -1;
|
||||||
|
let _listener_line_cb = (_) => { };
|
||||||
|
let _listener_state_cb = (_) => { };
|
||||||
|
let _last_state = -2;
|
||||||
|
function _stockfish_listener() {
|
||||||
|
let state = s_state();
|
||||||
|
if(state >= 0 && _last_state != state){
|
||||||
|
_listener_state_cb(state);
|
||||||
|
}
|
||||||
|
_last_state = state;
|
||||||
|
let out = readline();
|
||||||
|
while (out.length != 0) {
|
||||||
|
_listener_line_cb(out);
|
||||||
|
out = readline();
|
||||||
|
}
|
||||||
|
_listener_id = setTimeout(_stockfish_listener, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function start_listening(line_cb = (_) => { }, state_cb = (_) => { }) {
|
||||||
|
requestAnimationFrame(_stockfish_listener);
|
||||||
|
_listener_line_cb = line_cb;
|
||||||
|
_listener_state_cb = state_cb;
|
||||||
|
s_init();
|
||||||
|
s_main();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop_listening() {
|
||||||
|
if (_listener_id != -1) clearTimeout(_listener_id);
|
||||||
|
_listener_id = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _read() {
|
||||||
|
let ptr = s_read(true);
|
||||||
|
if (ptr == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return UTF8ToString(ptr);
|
||||||
|
}
|
||||||
|
var read_buffer = "";
|
||||||
|
function readline() {
|
||||||
|
if (!read_buffer.includes("\n"))
|
||||||
|
while (true) {
|
||||||
|
let next = _read();
|
||||||
|
if (next === -1) break;
|
||||||
|
read_buffer += next;
|
||||||
|
if (next.includes("\n")) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = read_buffer.indexOf("\n");
|
||||||
|
let out = "";
|
||||||
|
if (index == -1) {
|
||||||
|
out = read_buffer;
|
||||||
|
read_buffer = "";
|
||||||
|
} else {
|
||||||
|
out = read_buffer.substring(0, index);
|
||||||
|
read_buffer = read_buffer.substring(index + 1);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function write(string) {
|
||||||
|
let buffer = _malloc(string.length + 1);
|
||||||
|
stringToUTF8(string, buffer, string.length + 1);
|
||||||
|
let out = s_write(buffer);
|
||||||
|
_free(buffer);
|
||||||
|
return out;
|
||||||
|
}
|
Loading…
Reference in a new issue