pre work for web support: bindings separation, native preparations
This commit is contained in:
parent
68d1e05288
commit
595501ca1d
12 changed files with 316 additions and 210 deletions
|
@ -1,10 +1,10 @@
|
||||||
# Run with `flutter pub run ffigen --config ffigen_linux.yaml`.
|
# Run with `flutter pub run ffigen --config ffigen_linux.yaml`.
|
||||||
name: StockfishChessEngineBindings
|
name: StockfishChessEngineCBindings
|
||||||
description: |
|
description: |
|
||||||
Bindings for `src/stockfish.h`.
|
Bindings for `src/stockfish.h`.
|
||||||
|
|
||||||
Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
|
Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
|
||||||
output: "lib/stockfish_bindings_generated.dart"
|
output: "lib/stockfish_c_bindings_generated.dart"
|
||||||
headers:
|
headers:
|
||||||
entry-points:
|
entry-points:
|
||||||
- "src/stockfish.h"
|
- "src/stockfish.h"
|
||||||
|
|
|
@ -1,75 +1,27 @@
|
||||||
// Using code from https://github.com/ArjanAswal/Stockfish/blob/master/lib/src/stockfish.dart
|
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:ffi';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:isolate';
|
|
||||||
import 'dart:developer' as developer;
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:ffi/ffi.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';
|
||||||
|
|
||||||
import 'stockfish_bindings_generated.dart';
|
final StockfishChessEngineAbstractBindings _bindings =
|
||||||
import 'stockfish_state.dart';
|
StockfishChessEngineBindings();
|
||||||
|
|
||||||
const String _libName = 'flutter_stockfish_plugin';
|
|
||||||
//const String _releaseType = kDebugMode ? 'Debug' : 'Release';
|
|
||||||
|
|
||||||
/// The dynamic library in which the symbols for [StockfishChessEngineBindings] can be found.
|
|
||||||
final DynamicLibrary _dylib = () {
|
|
||||||
if (Platform.isMacOS || Platform.isIOS) {
|
|
||||||
return DynamicLibrary.open('$_libName.framework/$_libName');
|
|
||||||
}
|
|
||||||
if (Platform.isAndroid || Platform.isLinux) {
|
|
||||||
return DynamicLibrary.open('lib$_libName.so');
|
|
||||||
}
|
|
||||||
if (Platform.isWindows) {
|
|
||||||
return DynamicLibrary.open('$_libName.dll');
|
|
||||||
}
|
|
||||||
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
|
|
||||||
}();
|
|
||||||
|
|
||||||
/// The bindings to the native functions in [_dylib].
|
|
||||||
final StockfishChessEngineBindings _bindings =
|
|
||||||
StockfishChessEngineBindings(_dylib);
|
|
||||||
|
|
||||||
/// A wrapper for C++ engine.
|
|
||||||
class Stockfish {
|
class Stockfish {
|
||||||
final Completer<Stockfish>? completer;
|
final _state = StockfishStateClass();
|
||||||
|
|
||||||
final _state = _StockfishState();
|
Stockfish._() {
|
||||||
final _stdoutController = StreamController<String>.broadcast();
|
_state.setValue(StockfishState.starting);
|
||||||
final _mainPort = ReceivePort();
|
_bindings.stockfishMain(() {
|
||||||
final _stdoutPort = ReceivePort();
|
_state.setValue(StockfishState.ready);
|
||||||
|
}).then((exitCode) {
|
||||||
late StreamSubscription _mainSubscription;
|
_state.setValue(
|
||||||
late StreamSubscription _stdoutSubscription;
|
exitCode == 0 ? StockfishState.disposed : StockfishState.error);
|
||||||
|
_instance = null;
|
||||||
Stockfish._({this.completer}) {
|
}, onError: (error) {
|
||||||
_mainSubscription =
|
_state.setValue(StockfishState.error);
|
||||||
_mainPort.listen((message) => _cleanUp(message is int ? message : 1));
|
_instance = null;
|
||||||
_stdoutSubscription = _stdoutPort.listen((message) {
|
|
||||||
if (message is String) {
|
|
||||||
_stdoutController.sink.add(message);
|
|
||||||
} else {
|
|
||||||
developer.log('The stdout isolate sent $message', name: 'Stockfish');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
compute(_spawnIsolates, [_mainPort.sendPort, _stdoutPort.sendPort]).then(
|
|
||||||
(success) {
|
|
||||||
final state = success ? StockfishState.ready : StockfishState.error;
|
|
||||||
_state._setValue(state);
|
|
||||||
if (state == StockfishState.ready) {
|
|
||||||
completer?.complete(this);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: (error) {
|
|
||||||
developer.log('The init isolate encountered an error $error',
|
|
||||||
name: 'Stockfish');
|
|
||||||
_cleanUp(1);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Stockfish? _instance;
|
static Stockfish? _instance;
|
||||||
|
@ -82,7 +34,6 @@ class Stockfish {
|
||||||
if (_instance != null) {
|
if (_instance != null) {
|
||||||
throw StateError('Multiple instances are not supported, yet.');
|
throw StateError('Multiple instances are not supported, yet.');
|
||||||
}
|
}
|
||||||
|
|
||||||
_instance = Stockfish._();
|
_instance = Stockfish._();
|
||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
@ -91,31 +42,27 @@ class Stockfish {
|
||||||
ValueListenable<StockfishState> get state => _state;
|
ValueListenable<StockfishState> get state => _state;
|
||||||
|
|
||||||
/// The standard output stream.
|
/// The standard output stream.
|
||||||
Stream<String> get stdout => _stdoutController.stream;
|
Stream<String> get stdout => _bindings.read;
|
||||||
|
|
||||||
/// The standard input sink.
|
/// The standard input sink.
|
||||||
set stdin(String line) {
|
set stdin(String line) {
|
||||||
final stateValue = _state.value;
|
final stateValue = state.value;
|
||||||
if (stateValue != StockfishState.ready) {
|
if (stateValue != StockfishState.ready) {
|
||||||
throw StateError('Stockfish is not ready ($stateValue)');
|
throw StateError('Stockfish is not ready ($stateValue)');
|
||||||
}
|
}
|
||||||
|
_bindings.write(line);
|
||||||
final unicodePointer = '$line\n'.toNativeUtf8();
|
|
||||||
final pointer = unicodePointer.cast<Char>();
|
|
||||||
_bindings.stockfish_stdin_write(pointer);
|
|
||||||
calloc.free(unicodePointer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops the C++ engine.
|
/// Stops the C++ 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) {
|
void _cleanUp(int exitCode) {
|
||||||
_stdoutController.close();
|
/*_stdoutController.close();
|
||||||
|
|
||||||
_mainSubscription.cancel();
|
_mainSubscription.cancel();
|
||||||
_stdoutSubscription.cancel();
|
_stdoutSubscription.cancel();
|
||||||
|
@ -123,95 +70,6 @@ class Stockfish {
|
||||||
_state._setValue(
|
_state._setValue(
|
||||||
exitCode == 0 ? StockfishState.disposed : StockfishState.error);
|
exitCode == 0 ? StockfishState.disposed : StockfishState.error);
|
||||||
|
|
||||||
_instance = null;
|
_instance = null;*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a C++ 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StockfishState extends ChangeNotifier
|
|
||||||
implements ValueListenable<StockfishState> {
|
|
||||||
StockfishState _value = StockfishState.starting;
|
|
||||||
|
|
||||||
@override
|
|
||||||
StockfishState get value => _value;
|
|
||||||
|
|
||||||
_setValue(StockfishState v) {
|
|
||||||
if (v == _value) return;
|
|
||||||
_value = v;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _isolateMain(SendPort mainPort) {
|
|
||||||
final exitCode = _bindings.stockfish_main();
|
|
||||||
mainPort.send(exitCode);
|
|
||||||
|
|
||||||
developer.log('nativeMain returns $exitCode', name: 'Stockfish');
|
|
||||||
}
|
|
||||||
|
|
||||||
void _isolateStdout(SendPort stdoutPort) {
|
|
||||||
String previous = '';
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
final pointer = _bindings.stockfish_stdout_read();
|
|
||||||
|
|
||||||
if (pointer.address == 0) {
|
|
||||||
developer.log('nativeStdoutRead returns NULL', name: 'Stockfish');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List newContentCharList;
|
|
||||||
|
|
||||||
final newContentLength = pointer.cast<Utf8>().length;
|
|
||||||
newContentCharList = Uint8List.view(
|
|
||||||
pointer.cast<Uint8>().asTypedList(newContentLength).buffer,
|
|
||||||
0,
|
|
||||||
newContentLength);
|
|
||||||
|
|
||||||
final newContent = utf8.decode(newContentCharList);
|
|
||||||
|
|
||||||
final data = previous + newContent;
|
|
||||||
final lines = data.split('\n');
|
|
||||||
previous = lines.removeLast();
|
|
||||||
for (final line in lines) {
|
|
||||||
stdoutPort.send(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _spawnIsolates(List<SendPort> mainAndStdout) async {
|
|
||||||
final initResult = _bindings.stockfish_init();
|
|
||||||
if (initResult != 0) {
|
|
||||||
developer.log('initResult=$initResult', name: 'Stockfish');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Isolate.spawn(_isolateStdout, mainAndStdout[1]);
|
|
||||||
} catch (error) {
|
|
||||||
developer.log('Failed to spawn stdout isolate: $error', name: 'Stockfish');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Isolate.spawn(_isolateMain, mainAndStdout[0]);
|
|
||||||
} catch (error) {
|
|
||||||
developer.log('Failed to spawn main isolate: $error', name: 'Stockfish');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
9
lib/stockfish_bindings.dart
Normal file
9
lib/stockfish_bindings.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
abstract class StockfishChessEngineAbstractBindings {
|
||||||
|
final stdoutController = StreamController<String>.broadcast();
|
||||||
|
Future<int> stockfishMain(Function active);
|
||||||
|
void cleanUp(int exitCode);
|
||||||
|
void write(String line);
|
||||||
|
Stream<String> get read => stdoutController.stream;
|
||||||
|
}
|
|
@ -5,23 +5,24 @@
|
||||||
// AUTO GENERATED FILE, DO NOT EDIT.
|
// AUTO GENERATED FILE, DO NOT EDIT.
|
||||||
//
|
//
|
||||||
// Generated by `package:ffigen`.
|
// Generated by `package:ffigen`.
|
||||||
|
// ignore_for_file: type=lint
|
||||||
import 'dart:ffi' as ffi;
|
import 'dart:ffi' as ffi;
|
||||||
|
|
||||||
/// Bindings for `src/stockfish.h`.
|
/// Bindings for `src/stockfish.h`.
|
||||||
///
|
///
|
||||||
/// Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
|
/// Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
|
||||||
///
|
///
|
||||||
class StockfishChessEngineBindings {
|
class StockfishChessEngineCBindings {
|
||||||
/// Holds the symbol lookup function.
|
/// Holds the symbol lookup function.
|
||||||
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
||||||
_lookup;
|
_lookup;
|
||||||
|
|
||||||
/// The symbols are looked up in [dynamicLibrary].
|
/// The symbols are looked up in [dynamicLibrary].
|
||||||
StockfishChessEngineBindings(ffi.DynamicLibrary dynamicLibrary)
|
StockfishChessEngineCBindings(ffi.DynamicLibrary dynamicLibrary)
|
||||||
: _lookup = dynamicLibrary.lookup;
|
: _lookup = dynamicLibrary.lookup;
|
||||||
|
|
||||||
/// The symbols are looked up with [lookup].
|
/// The symbols are looked up with [lookup].
|
||||||
StockfishChessEngineBindings.fromLookup(
|
StockfishChessEngineCBindings.fromLookup(
|
||||||
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
||||||
lookup)
|
lookup)
|
||||||
: _lookup = lookup;
|
: _lookup = lookup;
|
||||||
|
@ -56,15 +57,19 @@ class StockfishChessEngineBindings {
|
||||||
late final _stockfish_stdin_write = _stockfish_stdin_writePtr
|
late final _stockfish_stdin_write = _stockfish_stdin_writePtr
|
||||||
.asFunction<int Function(ffi.Pointer<ffi.Char>)>();
|
.asFunction<int Function(ffi.Pointer<ffi.Char>)>();
|
||||||
|
|
||||||
ffi.Pointer<ffi.Char> stockfish_stdout_read() {
|
ffi.Pointer<ffi.Char> stockfish_stdout_read(
|
||||||
return _stockfish_stdout_read();
|
int trygetline,
|
||||||
|
) {
|
||||||
|
return _stockfish_stdout_read(
|
||||||
|
trygetline,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _stockfish_stdout_readPtr =
|
late final _stockfish_stdout_readPtr =
|
||||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
|
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(ffi.Int)>>(
|
||||||
'stockfish_stdout_read');
|
'stockfish_stdout_read');
|
||||||
late final _stockfish_stdout_read =
|
late final _stockfish_stdout_read = _stockfish_stdout_readPtr
|
||||||
_stockfish_stdout_readPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
.asFunction<ffi.Pointer<ffi.Char> Function(int)>();
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef ssize_t = __ssize_t;
|
typedef ssize_t = __ssize_t;
|
148
lib/stockfish_native_bindings.dart
Normal file
148
lib/stockfish_native_bindings.dart
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
import 'dart:developer' as developer;
|
||||||
|
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_stockfish_plugin/stockfish_bindings.dart';
|
||||||
|
import 'package:flutter_stockfish_plugin/stockfish_c_bindings_generated.dart';
|
||||||
|
|
||||||
|
const String _libName = 'flutter_stockfish_plugin';
|
||||||
|
|
||||||
|
/// The dynamic library in which the symbols for [StockfishChessEngineCBindings] can be found.
|
||||||
|
final DynamicLibrary _dylib = () {
|
||||||
|
if (Platform.isMacOS || Platform.isIOS) {
|
||||||
|
return DynamicLibrary.open('$_libName.framework/$_libName');
|
||||||
|
}
|
||||||
|
if (Platform.isAndroid || Platform.isLinux) {
|
||||||
|
return DynamicLibrary.open('lib$_libName.so');
|
||||||
|
}
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
return DynamicLibrary.open('$_libName.dll');
|
||||||
|
}
|
||||||
|
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
|
||||||
|
}();
|
||||||
|
|
||||||
|
/// The bindings to the native functions in [_dylib].
|
||||||
|
final StockfishChessEngineCBindings _bindings =
|
||||||
|
StockfishChessEngineCBindings(_dylib);
|
||||||
|
|
||||||
|
class StockfishChessEngineBindings
|
||||||
|
extends StockfishChessEngineAbstractBindings {
|
||||||
|
final _mainPort = ReceivePort();
|
||||||
|
final _stdoutPort = ReceivePort();
|
||||||
|
late StreamSubscription _mainSubscription;
|
||||||
|
late StreamSubscription _stdoutSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> stockfishMain(Function active) {
|
||||||
|
final completer = Completer<int>();
|
||||||
|
_mainSubscription = _mainPort.listen((message) {
|
||||||
|
cleanUp(message is int ? message : 1);
|
||||||
|
completer.complete(message is int ? message : 1);
|
||||||
|
});
|
||||||
|
_stdoutSubscription = _stdoutPort.listen((message) {
|
||||||
|
if (message is String) {
|
||||||
|
stdoutController.sink.add(message);
|
||||||
|
} else {
|
||||||
|
developer.log('The stdout isolate sent $message', name: 'Stockfish');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
compute(_spawnIsolates, [_mainPort.sendPort, _stdoutPort.sendPort]).then(
|
||||||
|
(success) {
|
||||||
|
if (success) {
|
||||||
|
active();
|
||||||
|
} else {
|
||||||
|
completer.completeError('Unable to create Isolates');
|
||||||
|
cleanUp(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
developer.log('The init isolate encountered an error $error',
|
||||||
|
name: 'Stockfish');
|
||||||
|
cleanUp(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(String line) {
|
||||||
|
final unicodePointer = '$line\n'.toNativeUtf8();
|
||||||
|
final pointer = unicodePointer.cast<Char>();
|
||||||
|
_bindings.stockfish_stdin_write(pointer);
|
||||||
|
calloc.free(unicodePointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void cleanUp(int exitCode) {
|
||||||
|
stdoutController.close();
|
||||||
|
_mainSubscription.cancel();
|
||||||
|
_stdoutSubscription.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _isolateMain(SendPort mainPort) {
|
||||||
|
final exitCode = _bindings.stockfish_main();
|
||||||
|
mainPort.send(exitCode);
|
||||||
|
|
||||||
|
developer.log('nativeMain returns $exitCode', name: 'Stockfish');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _isolateStdout(SendPort stdoutPort) {
|
||||||
|
String previous = '';
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
final pointer = _bindings.stockfish_stdout_read(0);
|
||||||
|
|
||||||
|
if (pointer.address == 0) {
|
||||||
|
developer.log('nativeStdoutRead returns NULL', name: 'Stockfish');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List newContentCharList;
|
||||||
|
|
||||||
|
final newContentLength = pointer.cast<Utf8>().length;
|
||||||
|
newContentCharList = Uint8List.view(
|
||||||
|
pointer.cast<Uint8>().asTypedList(newContentLength).buffer,
|
||||||
|
0,
|
||||||
|
newContentLength);
|
||||||
|
|
||||||
|
final newContent = utf8.decode(newContentCharList);
|
||||||
|
|
||||||
|
final data = previous + newContent;
|
||||||
|
final lines = data.split('\n');
|
||||||
|
previous = lines.removeLast();
|
||||||
|
for (final line in lines) {
|
||||||
|
stdoutPort.send(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _spawnIsolates(List<SendPort> mainAndStdout) async {
|
||||||
|
final initResult = _bindings.stockfish_init();
|
||||||
|
if (initResult != 0) {
|
||||||
|
developer.log('initResult=$initResult', name: 'Stockfish');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Isolate.spawn(_isolateStdout, mainAndStdout[1]);
|
||||||
|
} catch (error) {
|
||||||
|
developer.log('Failed to spawn stdout isolate: $error', name: 'Stockfish');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Isolate.spawn(_isolateMain, mainAndStdout[0]);
|
||||||
|
} catch (error) {
|
||||||
|
developer.log('Failed to spawn main isolate: $error', name: 'Stockfish');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -1,4 +1,20 @@
|
||||||
// Taken from https://github.com/ArjanAswal/Stockfish/blob/master/lib/src/stockfish_state.dart
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class StockfishStateClass extends ChangeNotifier
|
||||||
|
implements ValueListenable<StockfishState> {
|
||||||
|
StockfishState _value = StockfishState.starting;
|
||||||
|
|
||||||
|
@override
|
||||||
|
StockfishState get value => _value;
|
||||||
|
|
||||||
|
setValue(StockfishState v) {
|
||||||
|
if (v == _value) return;
|
||||||
|
_value = v;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following is taken from https://github.com/ArjanAswal/Stockfish/blob/master/lib/src/stockfish_state.dart
|
||||||
|
|
||||||
/// C++ engine state.
|
/// C++ engine state.
|
||||||
enum StockfishState {
|
enum StockfishState {
|
||||||
|
|
25
lib/stockfish_web_bindings.dart
Normal file
25
lib/stockfish_web_bindings.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_stockfish_plugin/stockfish_bindings.dart';
|
||||||
|
|
||||||
|
class StockfishChessEngineBindings
|
||||||
|
extends StockfishChessEngineAbstractBindings {
|
||||||
|
@override
|
||||||
|
void cleanUp(int exitCode) {
|
||||||
|
// TODO: implement cleanUp
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> stockfishMain(Function active) {
|
||||||
|
// TODO: implement stockfishMain
|
||||||
|
final completer = Completer<int>();
|
||||||
|
//completer.complete(0);
|
||||||
|
active();
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(String line) {
|
||||||
|
// TODO: implement write
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
set(COMMON_FLAGS "/std:c++17 /LTCG")
|
set(COMMON_FLAGS "/std:c++17 /LTCG")
|
||||||
|
|
||||||
set(SIMD_FLAGS "/arch:AVX2 /arch:SSE /DUSE_POPCNT /DUSE_SSE41 /DUSE_SSSE3 /DUSE_SSE2")
|
set(SIMD_FLAGS "/arch:AVX2 /DUSE_POPCNT /arch:SSE /DUSE_SSE2 /DUSE_SSSE3 /DUSE_SSE41")
|
||||||
|
|
||||||
if (CMAKE_SIZEOF_VOID_P EQUAL 8)
|
if (CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
message(STATUS "Adding x86_64 specific flags")
|
message(STATUS "Adding x86_64 specific flags")
|
||||||
|
@ -25,9 +25,9 @@ if(MSVC)
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ox /DNDEBUG")
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ox /DNDEBUG")
|
||||||
|
|
||||||
else()
|
else()
|
||||||
set(COMMON_FLAGS "-Wall -Wcast-qual -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 "-msse -msse3 -mpopcnt -DUSE_POPCNT -DUSE_SSE41 -msse4.1 -DUSE_SSSE3 -mssse3 -DUSE_SSE2 -msse2")
|
set(SIMD_FLAGS "-mpopcnt -DUSE_POPCNT -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")
|
||||||
|
|
|
@ -15,9 +15,11 @@
|
||||||
#include "fixes.h"
|
#include "fixes.h"
|
||||||
#include "stockfish.h"
|
#include "stockfish.h"
|
||||||
|
|
||||||
const char *QUITOK = "quitok\n";
|
const char* QUITOK = "quitok\n";
|
||||||
|
|
||||||
int main(int, char **);
|
void runMain() {}
|
||||||
|
|
||||||
|
int main(int, char**);
|
||||||
|
|
||||||
int stockfish_init() {
|
int stockfish_init() {
|
||||||
fakein.open();
|
fakein.open();
|
||||||
|
@ -27,8 +29,11 @@ int stockfish_init() {
|
||||||
|
|
||||||
int stockfish_main() {
|
int stockfish_main() {
|
||||||
int argc = 1;
|
int argc = 1;
|
||||||
char *argv[] = {(char *)""};
|
char* empty = (char*)malloc(0);
|
||||||
|
*empty = 0;
|
||||||
|
char* argv[] = {empty};
|
||||||
int exitCode = main(argc, argv);
|
int exitCode = main(argc, argv);
|
||||||
|
free(empty);
|
||||||
|
|
||||||
fakeout << QUITOK << "\n";
|
fakeout << QUITOK << "\n";
|
||||||
|
|
||||||
|
@ -44,7 +49,7 @@ int stockfish_main() {
|
||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
return val.length();
|
return val.length();
|
||||||
|
@ -52,9 +57,15 @@ ssize_t stockfish_stdin_write(char *data) {
|
||||||
|
|
||||||
std::string data;
|
std::string data;
|
||||||
|
|
||||||
const char *stockfish_stdout_read() {
|
const char* stockfish_stdout_read(int trygetline) {
|
||||||
if (getline(fakeout, data)) {
|
if (trygetline) {
|
||||||
return data.c_str();
|
if (fakeout.try_get_line(data)) {
|
||||||
|
return data.c_str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (getline(fakeout, data)) {
|
||||||
|
return data.c_str();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
|
@ -20,29 +20,34 @@
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define FFI_PLUGIN_EXPORT __declspec(dllexport)
|
#define FFI_PLUGIN_EXPORT __declspec(dllexport)
|
||||||
#else
|
#else
|
||||||
#define FFI_PLUGIN_EXPORT __attribute__((visibility("default"))) __attribute__((used))
|
#define FFI_PLUGIN_EXPORT \
|
||||||
|
__attribute__((visibility("default"))) __attribute__((used))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Initialisation of Stockfish.
|
// Initialisation of Stockfish.
|
||||||
#ifndef _ffigen
|
#ifndef _ffigen
|
||||||
extern "C"
|
extern "C"
|
||||||
#endif
|
#endif
|
||||||
FFI_PLUGIN_EXPORT int stockfish_init();
|
FFI_PLUGIN_EXPORT int
|
||||||
|
stockfish_init();
|
||||||
|
|
||||||
// Stockfish main loop.
|
// Stockfish main loop.
|
||||||
#ifndef _ffigen
|
#ifndef _ffigen
|
||||||
extern "C"
|
extern "C"
|
||||||
#endif
|
#endif
|
||||||
FFI_PLUGIN_EXPORT int stockfish_main();
|
FFI_PLUGIN_EXPORT int
|
||||||
|
stockfish_main();
|
||||||
|
|
||||||
// Writing to Stockfish STDIN.
|
// Writing to Stockfish STDIN.
|
||||||
#ifndef _ffigen
|
#ifndef _ffigen
|
||||||
extern "C"
|
extern "C"
|
||||||
#endif
|
#endif
|
||||||
FFI_PLUGIN_EXPORT ssize_t stockfish_stdin_write(char *data);
|
FFI_PLUGIN_EXPORT ssize_t
|
||||||
|
stockfish_stdin_write(char* data);
|
||||||
|
|
||||||
// Reading Stockfish STDOUT
|
// Reading Stockfish STDOUT
|
||||||
#ifndef _ffigen
|
#ifndef _ffigen
|
||||||
extern "C"
|
extern "C"
|
||||||
#endif
|
#endif
|
||||||
FFI_PLUGIN_EXPORT const char * stockfish_stdout_read();
|
FFI_PLUGIN_EXPORT const char*
|
||||||
|
stockfish_stdout_read(int trygetline);
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
bool FakeStream::try_get_line(std::string& val) {
|
bool FakeStream::try_get_line(std::string& val) {
|
||||||
std::unique_lock<std::mutex> lock(mutex_guard);
|
std::unique_lock<std::mutex> lock(mutex_guard);
|
||||||
if (string_queue.empty() || closed) return false;
|
if (string_queue.empty() || closed)
|
||||||
|
return false;
|
||||||
val = string_queue.front();
|
val = string_queue.front();
|
||||||
string_queue.pop();
|
string_queue.pop();
|
||||||
return true;
|
return true;
|
||||||
|
@ -20,18 +21,27 @@ void FakeStream::close() {
|
||||||
while (!string_queue.empty()) {
|
while (!string_queue.empty()) {
|
||||||
string_queue.pop();
|
string_queue.pop();
|
||||||
}
|
}
|
||||||
mutex_signal.notify_one();
|
mutex_signal.notify_all();
|
||||||
|
}
|
||||||
|
bool FakeStream::is_closed() {
|
||||||
|
return closed;
|
||||||
}
|
}
|
||||||
bool FakeStream::is_closed() { return closed; }
|
|
||||||
|
|
||||||
std::streambuf* FakeStream::rdbuf() { return nullptr; }
|
std::streambuf* FakeStream::rdbuf() {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::streambuf* FakeStream::rdbuf(std::streambuf* buf) { return nullptr; }
|
std::streambuf* FakeStream::rdbuf(std::streambuf* buf) {
|
||||||
|
(void)buf;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool std::getline(FakeStream& is, std::string& str) {
|
bool std::getline(FakeStream& is, std::string& str) {
|
||||||
if (is.is_closed()) return false;
|
if (is.is_closed())
|
||||||
|
return false;
|
||||||
is >> str;
|
is >> str;
|
||||||
if (is.is_closed()) return false;
|
if (is.is_closed())
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,18 +6,24 @@
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten.h>
|
||||||
|
#include <emscripten/threading.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline std::string stringify(const T& input) {
|
inline std::string stringify(const T& input) {
|
||||||
std::ostringstream output; // from www .ja va 2s . com
|
std::ostringstream output; // from www .ja va 2s . com
|
||||||
output << input;
|
output << input;
|
||||||
return output.str();
|
return std::string(output.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeStream {
|
class FakeStream {
|
||||||
public:
|
public:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
FakeStream& operator<<(const T& val) {
|
FakeStream& operator<<(const T& val) {
|
||||||
if (closed) return *this;
|
if (closed)
|
||||||
|
return *this;
|
||||||
std::lock_guard<std::mutex> lock(mutex_guard);
|
std::lock_guard<std::mutex> lock(mutex_guard);
|
||||||
string_queue.push(stringify(val));
|
string_queue.push(stringify(val));
|
||||||
mutex_signal.notify_one();
|
mutex_signal.notify_one();
|
||||||
|
@ -25,29 +31,46 @@ class FakeStream {
|
||||||
};
|
};
|
||||||
template <typename T>
|
template <typename T>
|
||||||
FakeStream& operator>>(T& val) {
|
FakeStream& operator>>(T& val) {
|
||||||
if (closed) return *this;
|
if (closed)
|
||||||
|
return *this;
|
||||||
std::unique_lock<std::mutex> lock(mutex_guard);
|
std::unique_lock<std::mutex> lock(mutex_guard);
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
if (emscripten_is_main_runtime_thread()) {
|
||||||
|
lock.unlock();
|
||||||
|
while (true) {
|
||||||
|
lock = std::unique_lock<std::mutex>(mutex_guard);
|
||||||
|
if (!string_queue.empty() || closed)
|
||||||
|
break;
|
||||||
|
lock.unlock();
|
||||||
|
emscripten_sleep(10);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mutex_signal.wait(
|
||||||
|
lock, [this] { return !string_queue.empty() || closed; });
|
||||||
|
}
|
||||||
|
#else
|
||||||
mutex_signal.wait(lock,
|
mutex_signal.wait(lock,
|
||||||
[this] { return !string_queue.empty() || closed; });
|
[this] { return !string_queue.empty() || closed; });
|
||||||
if (closed) return *this;
|
#endif
|
||||||
|
if (closed)
|
||||||
|
return *this;
|
||||||
val = string_queue.front();
|
val = string_queue.front();
|
||||||
string_queue.pop();
|
string_queue.pop();
|
||||||
return *this;
|
return *this;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool try_get_line(std::string& val);
|
bool try_get_line(std::string&);
|
||||||
|
|
||||||
void open();
|
void open();
|
||||||
void close();
|
void close();
|
||||||
bool is_closed();
|
bool is_closed();
|
||||||
|
|
||||||
std::streambuf* rdbuf();
|
std::streambuf* rdbuf();
|
||||||
std::streambuf* rdbuf(std::streambuf* __sb);
|
std::streambuf* rdbuf(std::streambuf*);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool closed = false;
|
bool closed = false;
|
||||||
std::queue<std::string> string_queue;
|
std::queue<std::string> string_queue;
|
||||||
//std::string line;
|
|
||||||
std::mutex mutex_guard;
|
std::mutex mutex_guard;
|
||||||
std::condition_variable mutex_signal;
|
std::condition_variable mutex_signal;
|
||||||
};
|
};
|
||||||
|
@ -56,10 +79,6 @@ namespace std {
|
||||||
bool getline(FakeStream& is, std::string& str);
|
bool getline(FakeStream& is, std::string& str);
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
// #define endl fakeendl
|
|
||||||
// #define cout fakeout
|
|
||||||
// #define cin fakein
|
|
||||||
|
|
||||||
extern FakeStream fakeout;
|
extern FakeStream fakeout;
|
||||||
extern FakeStream fakein;
|
extern FakeStream fakein;
|
||||||
extern std::string fakeendl;
|
extern std::string fakeendl;
|
||||||
|
|
Loading…
Reference in a new issue