pre work for web support: bindings separation, native preparations

This commit is contained in:
jusax23 2024-02-23 18:58:31 +01:00
parent 68d1e05288
commit 595501ca1d
Signed by: jusax23
GPG key ID: 4A6CED31031AE931
12 changed files with 316 additions and 210 deletions

View file

@ -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"

View file

@ -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;
}

View 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;
}

View file

@ -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;

View 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;
}

View file

@ -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 {

View 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
}
}

View file

@ -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")

View file

@ -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;
} }

View file

@ -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);

View file

@ -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;
} }

View file

@ -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;