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
|
||||
**/*.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).
|
||||
|
||||
## 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
|
||||
|
||||
* 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_stockfish_plugin/stockfish_bindings.dart';
|
||||
import 'package:flutter_stockfish_plugin/stockfish_native_bindings.dart'
|
||||
if (dart.library.html) 'package:flutter_stockfish_plugin/stockfish_web_bindings.dart';
|
||||
import 'package:flutter_stockfish_plugin/stockfish_state.dart';
|
||||
|
||||
final StockfishChessEngineAbstractBindings _bindings =
|
||||
StockfishChessEngineBindings();
|
||||
|
||||
class Stockfish {
|
||||
final _state = StockfishStateClass();
|
||||
final StockfishChessEngineAbstractBindings _bindings =
|
||||
StockfishChessEngineBindings();
|
||||
|
||||
Stockfish._() {
|
||||
Stockfish._({Completer<Stockfish>? completer}) {
|
||||
_state.setValue(StockfishState.starting);
|
||||
_bindings.stockfishMain(() {
|
||||
_state.setValue(StockfishState.ready);
|
||||
completer?.complete(this);
|
||||
}).then((exitCode) {
|
||||
_state.setValue(
|
||||
exitCode == 0 ? StockfishState.disposed : StockfishState.error);
|
||||
|
@ -21,12 +23,13 @@ class Stockfish {
|
|||
}, onError: (error) {
|
||||
_state.setValue(StockfishState.error);
|
||||
_instance = null;
|
||||
completer?.completeError(error);
|
||||
});
|
||||
}
|
||||
|
||||
static Stockfish? _instance;
|
||||
|
||||
/// Creates a C++ engine.
|
||||
/// Creates the stockfish engine.
|
||||
///
|
||||
/// This may throws a [StateError] if an active instance is being used.
|
||||
/// Owner must [dispose] it before a new instance can be created.
|
||||
|
@ -38,7 +41,7 @@ class Stockfish {
|
|||
return _instance!;
|
||||
}
|
||||
|
||||
/// The current state of the underlying C++ engine.
|
||||
/// The current state of the underlying stockfish engine.
|
||||
ValueListenable<StockfishState> get state => _state;
|
||||
|
||||
/// The standard output stream.
|
||||
|
@ -53,23 +56,25 @@ class Stockfish {
|
|||
_bindings.write(line);
|
||||
}
|
||||
|
||||
/// Stops the C++ engine.
|
||||
/// Stops the stockfish engine.
|
||||
void dispose() {
|
||||
final stateValue = state.value;
|
||||
if (stateValue == StockfishState.ready) {
|
||||
stdin = 'quit';
|
||||
}
|
||||
}
|
||||
|
||||
void _cleanUp(int exitCode) {
|
||||
/*_stdoutController.close();
|
||||
|
||||
_mainSubscription.cancel();
|
||||
_stdoutSubscription.cancel();
|
||||
|
||||
_state._setValue(
|
||||
exitCode == 0 ? StockfishState.disposed : StockfishState.error);
|
||||
|
||||
_instance = null;*/
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the stockfish engine asynchronously.
|
||||
///
|
||||
/// This method is different from the factory method [Stockfish] that
|
||||
/// it will wait for the engine to be ready before returning the instance.
|
||||
Future<Stockfish> stockfishAsync() {
|
||||
if (Stockfish._instance != null) {
|
||||
return Future.error(StateError('Only one instance can be used at a time'));
|
||||
}
|
||||
|
||||
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');
|
||||
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(
|
||||
ffi.Pointer<ffi.Char> data,
|
||||
) {
|
||||
|
@ -74,3 +93,4 @@ class StockfishChessEngineCBindings {
|
|||
|
||||
typedef ssize_t = __ssize_t;
|
||||
typedef __ssize_t = ffi.Long;
|
||||
typedef Dart__ssize_t = int;
|
||||
|
|
|
@ -64,6 +64,7 @@ class StockfishChessEngineBindings
|
|||
onError: (error) {
|
||||
developer.log('The init isolate encountered an error $error',
|
||||
name: 'Stockfish');
|
||||
completer.completeError(error);
|
||||
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';
|
||||
|
||||
class StockfishChessEngineBindings
|
||||
extends StockfishChessEngineAbstractBindings {
|
||||
@override
|
||||
void cleanUp(int exitCode) {
|
||||
// TODO: implement cleanUp
|
||||
Future<void>? loadJs;
|
||||
StockfishChessEngineBindings() {
|
||||
loadJs = loadJsFileIfNeeded();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> stockfishMain(Function active) {
|
||||
// TODO: implement stockfishMain
|
||||
void cleanUp(int exitCode) {
|
||||
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>();
|
||||
//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();
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
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:
|
||||
# -------------------
|
||||
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:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
|
|
|
@ -27,7 +27,7 @@ if(MSVC)
|
|||
else()
|
||||
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")
|
||||
message(STATUS "Adding x86_64 specific flags")
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "fixes.h"
|
||||
#include "stockfish.h"
|
||||
#include <thread>
|
||||
|
||||
const char* QUITOK = "quitok\n";
|
||||
|
||||
|
@ -27,7 +28,10 @@ int stockfish_init() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int _last_main_state = -2;
|
||||
|
||||
int stockfish_main() {
|
||||
_last_main_state = -1;
|
||||
int argc = 1;
|
||||
char* empty = (char*)malloc(0);
|
||||
*empty = 0;
|
||||
|
@ -46,9 +50,19 @@ int stockfish_main() {
|
|||
fakeout.close();
|
||||
fakein.close();
|
||||
|
||||
_last_main_state = 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) {
|
||||
std::string val(data);
|
||||
fakein << val << fakeendl;
|
||||
|
|
|
@ -38,6 +38,20 @@ extern "C"
|
|||
FFI_PLUGIN_EXPORT int
|
||||
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.
|
||||
#ifndef _ffigen
|
||||
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