code only

This commit is contained in:
jusax23 2023-10-28 20:46:47 +02:00
commit dbc707f23f
Signed by: jusax23
GPG key ID: 499E2AA870C1CD41
10 changed files with 605 additions and 0 deletions

223
lib/stockfish.dart Normal file
View file

@ -0,0 +1,223 @@
// 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:ffi/ffi.dart';
import 'stockfish_bindings_generated.dart';
import 'stockfish_state.dart';
const String _libName = 'stockfish';
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) {
return DynamicLibrary.open('lib$_libName.so');
}
if (Platform.isLinux) {
return DynamicLibrary.open(
'${File(Platform.resolvedExecutable).parent.parent.path}/plugins/flutter_stockfish_plugin/shared/lib$_libName.so');
}
if (Platform.isWindows) {
return DynamicLibrary.open(
'${File(Platform.resolvedExecutable).parent.parent.parent.path}/plugins/flutter_stockfish_plugin/shared/$_releaseType/$_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 {
final Completer<Stockfish>? completer;
final _state = _StockfishState();
final _stdoutController = StreamController<String>.broadcast();
final _mainPort = ReceivePort();
final _stdoutPort = ReceivePort();
late StreamSubscription _mainSubscription;
late StreamSubscription _stdoutSubscription;
Stockfish._({this.completer}) {
_mainSubscription =
_mainPort.listen((message) => _cleanUp(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) {
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;
/// Creates a C++ engine.
///
/// This may throws a [StateError] if an active instance is being used.
/// Owner must [dispose] it before a new instance can be created.
factory Stockfish() {
if (_instance != null) {
throw StateError('Multiple instances are not supported, yet.');
}
_instance = Stockfish._();
return _instance!;
}
/// The current state of the underlying C++ engine.
ValueListenable<StockfishState> get state => _state;
/// The standard output stream.
Stream<String> get stdout => _stdoutController.stream;
/// The standard input sink.
set stdin(String line) {
final stateValue = _state.value;
if (stateValue != StockfishState.ready) {
throw StateError('Stockfish is not ready ($stateValue)');
}
final unicodePointer = '$line\n'.toNativeUtf8();
final pointer = unicodePointer.cast<Char>();
_bindings.stockfish_stdin_write(pointer);
calloc.free(unicodePointer);
}
/// Stops the C++ 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 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');
print("stoped");
}
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;
}

View file

@ -0,0 +1,71 @@
// ignore_for_file: always_specify_types
// ignore_for_file: camel_case_types
// ignore_for_file: non_constant_identifier_names
// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
import 'dart:ffi' as ffi;
/// Bindings for `src/stockfish.h`.
///
/// Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
///
class StockfishChessEngineBindings {
/// Holds the symbol lookup function.
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
_lookup;
/// The symbols are looked up in [dynamicLibrary].
StockfishChessEngineBindings(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup;
/// The symbols are looked up with [lookup].
StockfishChessEngineBindings.fromLookup(
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
lookup)
: _lookup = lookup;
int stockfish_init() {
return _stockfish_init();
}
late final _stockfish_initPtr =
_lookup<ffi.NativeFunction<ffi.Int Function()>>('stockfish_init');
late final _stockfish_init = _stockfish_initPtr.asFunction<int Function()>();
int stockfish_main() {
return _stockfish_main();
}
late final _stockfish_mainPtr =
_lookup<ffi.NativeFunction<ffi.Int Function()>>('stockfish_main');
late final _stockfish_main = _stockfish_mainPtr.asFunction<int Function()>();
int stockfish_stdin_write(
ffi.Pointer<ffi.Char> data,
) {
return _stockfish_stdin_write(
data,
);
}
late final _stockfish_stdin_writePtr =
_lookup<ffi.NativeFunction<ssize_t Function(ffi.Pointer<ffi.Char>)>>(
'stockfish_stdin_write');
late final _stockfish_stdin_write = _stockfish_stdin_writePtr
.asFunction<int Function(ffi.Pointer<ffi.Char>)>();
ffi.Pointer<ffi.Char> stockfish_stdout_read() {
return _stockfish_stdout_read();
}
late final _stockfish_stdout_readPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
'stockfish_stdout_read');
late final _stockfish_stdout_read =
_stockfish_stdout_readPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
}
typedef ssize_t = __ssize_t;
typedef __ssize_t = ffi.Long;

16
lib/stockfish_state.dart Normal file
View file

@ -0,0 +1,16 @@
// Taken from https://github.com/ArjanAswal/Stockfish/blob/master/lib/src/stockfish_state.dart
/// C++ engine state.
enum StockfishState {
/// Engine has been stopped.
disposed,
/// An error occured (engine could not start).
error,
/// Engine is running.
ready,
/// Engine is starting.
starting,
}

22
linux/CMakeLists.txt Normal file
View file

@ -0,0 +1,22 @@
# The Flutter tooling requires that developers have CMake 3.22 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.22)
# Project-level configuration.
set(PROJECT_NAME "stockfish")
project(${PROJECT_NAME} LANGUAGES CXX)
# Invoke the build for native code shared with the other target platforms.
# This can be changed to accomodate different builds.
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
# List of absolute paths to libraries that should be bundled with the plugin.
# This list could contain prebuilt libraries, or libraries created by an
# external build triggered from this build file.
set(stockfish_bundled_libraries
# Defined in ../src/CMakeLists.txt.
# This can be changed to accomodate different builds.
$<TARGET_FILE:stockfish>
PARENT_SCOPE
)

41
src/CMakeLists.txt Normal file
View file

@ -0,0 +1,41 @@
# 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(stockfish VERSION 0.0.1 LANGUAGES CXX)
file(GLOB_RECURSE cppPaths "Stockfish/src/*.cpp")
set(CMAKE_CXX_STANDARD 17)
set(NNUE_NAME nn-0000000000a0.nnue)
add_library(stockfish SHARED
"stockfish.cpp"
"stream_fix.cpp"
${cppPaths}
)
set_target_properties(stockfish PROPERTIES
PUBLIC_HEADER stockfish.h
OUTPUT_NAME "stockfish"
)
if(MSVC)
add_definitions(/FI"stream_fix.h")
else()
add_definitions(-include stream_fix.h)
endif()
target_compile_definitions(stockfish PUBLIC DART_SHARED_LIB)
target_include_directories(stockfish
PUBLIC
"./"
)
if (MSVC)
file(DOWNLOAD https://tests.stockfishchess.org/api/nn/${NNUE_NAME} ${CMAKE_BINARY_DIR}/runner/Debug/${NNUE_NAME})
file(COPY ${CMAKE_BINARY_DIR}/runner/Debug/${NNUE_NAME} DESTINATION ${CMAKE_BINARY_DIR}/runner/Release)
else ()
file(DOWNLOAD https://tests.stockfishchess.org/api/nn/${NNUE_NAME} ${CMAKE_BINARY_DIR}/${NNUE_NAME})
endif ()

66
src/stockfish.cpp Normal file
View file

@ -0,0 +1,66 @@
#include <cstdio>
#include <iostream>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
#ifdef _WIN64
#define ssize_t __int64
#else
#define ssize_t long
#endif
#else
#include <unistd.h>
#endif
#define BUFFER_SIZE 1024
#include "stockfish.h"
const char *QUITOK = "quitok\n";
int main(int, char **);
int stockfish_init() {
std::cout << "Init Stockfish: Nothing todo!";
return 0;
}
int stockfish_main() {
int argc = 1;
char *argv[] = {(char *)""};
int exitCode = main(argc, argv);
fakeout << QUITOK << "\n";
usleep(100000);
fakeout.close();
fakein.close();
return exitCode;
}
ssize_t stockfish_stdin_write(char *data) {
std::string val(data);
fakein << val << fakeendl;
return val.length();
}
std::string data;
char buffer[BUFFER_SIZE + 1];
char *stockfish_stdout_read() {
if (getline(fakeout, data)) {
size_t len = data.length();
size_t i;
for (i = 0; i < len && i < BUFFER_SIZE; i++) {
buffer[i] = data[i];
}
buffer[i] = 0;
return buffer;
}
return NULL;
}

46
src/stockfish.h Normal file
View file

@ -0,0 +1,46 @@
//#define _ffigen
#ifdef _WIN32
#include <BaseTsd.h>
#else
#include <stdint.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <pthread.h>
#include <unistd.h>
#endif
#ifdef _WIN32
#define FFI_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FFI_PLUGIN_EXPORT __attribute__((visibility("default"))) __attribute__((used))
#endif
// Initialisation of Stockfish.
#ifndef _ffigen
extern "C"
#endif
FFI_PLUGIN_EXPORT int stockfish_init();
// Stockfish main loop.
#ifndef _ffigen
extern "C"
#endif
FFI_PLUGIN_EXPORT int stockfish_main();
// Writing to Stockfish STDIN.
#ifndef _ffigen
extern "C"
#endif
FFI_PLUGIN_EXPORT ssize_t stockfish_stdin_write(char *data);
// Reading Stockfish STDOUT
#ifndef _ffigen
extern "C"
#endif
FFI_PLUGIN_EXPORT char * stockfish_stdout_read();

31
src/stream_fix.cpp Normal file
View file

@ -0,0 +1,31 @@
#include "stream_fix.h"
bool FakeStream::try_get_line(std::string& val) {
std::unique_lock<std::mutex> lock(mutex_guard);
if (string_queue.empty() || closed) return false;
val = string_queue.front();
string_queue.pop();
return true;
}
void FakeStream::close() {
std::lock_guard<std::mutex> lock(mutex_guard);
closed = true;
mutex_signal.notify_one();
}
bool FakeStream::is_closed() { return closed; }
std::streambuf* FakeStream::rdbuf() { return nullptr; }
std::streambuf* FakeStream::rdbuf(std::streambuf* __sb) { return nullptr; }
bool std::getline(FakeStream& is, std::string& str) {
if (is.is_closed()) return false;
is >> str;
if (is.is_closed()) return false;
return true;
}
FakeStream fakeout;
FakeStream fakein;
std::string fakeendl("\n");

66
src/stream_fix.h Normal file
View file

@ -0,0 +1,66 @@
#ifndef _STREAM_FIX_H_
#define _STREAM_FIX_H_
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <sstream>
template <typename T>
inline std::string stringify(const T& input) {
std::ostringstream output; // from www .ja va 2s . com
output << input;
return output.str();
}
class FakeStream {
public:
template <typename T>
FakeStream& operator<<(const T& val) {
if (closed) return *this;
std::lock_guard<std::mutex> lock(mutex_guard);
string_queue.push(stringify(val));
mutex_signal.notify_one();
return *this;
};
template <typename T>
FakeStream& operator>>(T& val) {
if (closed) return *this;
std::unique_lock<std::mutex> lock(mutex_guard);
mutex_signal.wait(lock,
[this] { return !string_queue.empty() || closed; });
if (closed) return *this;
val = string_queue.front();
string_queue.pop();
return *this;
};
bool try_get_line(std::string& val);
void close();
bool is_closed();
std::streambuf* rdbuf();
std::streambuf* rdbuf(std::streambuf* __sb);
private:
bool closed = false;
std::queue<std::string> string_queue;
std::string line;
std::mutex mutex_guard;
std::condition_variable mutex_signal;
};
namespace std {
bool getline(FakeStream& is, std::string& str);
} // namespace std
// #define endl fakeendl
// #define cout fakeout
// #define cin fakein
extern FakeStream fakeout;
extern FakeStream fakein;
extern std::string fakeendl;
#endif

23
windows/CMakeLists.txt Normal file
View file

@ -0,0 +1,23 @@
# The Flutter tooling requires that developers have a version of Visual Studio
# installed that includes CMake 3.22 or later. 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.22)
# Project-level configuration.
set(PROJECT_NAME "stockfish")
project(${PROJECT_NAME} LANGUAGES CXX)
# Invoke the build for native code shared with the other target platforms.
# This can be changed to accomodate different builds.
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
# List of absolute paths to libraries that should be bundled with the plugin.
# This list could contain prebuilt libraries, or libraries created by an
# external build triggered from this build file.
set(stockfish_bundled_libraries
# Defined in ../src/CMakeLists.txt.
# This can be changed to accomodate different builds.
$<TARGET_FILE:stockfish>
PARENT_SCOPE
)