crop borders finally works
This commit is contained in:
parent
c2a3e7859d
commit
36f95bb57b
57 changed files with 10310 additions and 243 deletions
92
.github/workflows/release.yml
vendored
92
.github/workflows/release.yml
vendored
|
|
@ -25,6 +25,26 @@ jobs:
|
|||
with:
|
||||
channel: 'stable'
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Protobuf compiler
|
||||
uses: arduino/setup-protoc@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Java toolchain (Only Android target)
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "11"
|
||||
|
||||
- name: Install the CLI tool
|
||||
run: cargo install rinf
|
||||
|
||||
- name: Generate message files
|
||||
run: rinf message
|
||||
|
||||
- name: Setup Android keystore
|
||||
run: |
|
||||
echo "${{ secrets.SIGNING_KEY_STORE_BASE64 }}" | base64 -di > android/app/upload-keystore.jks
|
||||
|
|
@ -68,6 +88,20 @@ jobs:
|
|||
uses: subosito/flutter-action@v2.10.0
|
||||
with:
|
||||
channel: 'stable'
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Protobuf compiler
|
||||
uses: arduino/setup-protoc@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install the CLI tool
|
||||
run: cargo install rinf
|
||||
|
||||
- name: Generate message files
|
||||
run: rinf message
|
||||
|
||||
- name: flutter pub get
|
||||
run: |
|
||||
|
|
@ -110,6 +144,20 @@ jobs:
|
|||
with:
|
||||
channel: 'stable'
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Protobuf compiler
|
||||
uses: arduino/setup-protoc@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install the CLI tool
|
||||
run: cargo install rinf
|
||||
|
||||
- name: Generate message files
|
||||
run: rinf message
|
||||
|
||||
- name: flutter pub get
|
||||
run: |
|
||||
git config --global core.longpaths true
|
||||
|
|
@ -150,6 +198,20 @@ jobs:
|
|||
with:
|
||||
channel: 'stable'
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Protobuf compiler
|
||||
uses: arduino/setup-protoc@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install the CLI tool
|
||||
run: cargo install rinf
|
||||
|
||||
- name: Generate message files
|
||||
run: rinf message
|
||||
|
||||
- name: flutter pub get
|
||||
run: |
|
||||
git config --global core.longpaths true
|
||||
|
|
@ -177,7 +239,7 @@ jobs:
|
|||
allowUpdates: true
|
||||
|
||||
|
||||
build-and-release-linux_zip:
|
||||
build-and-release-linux-zip:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -196,6 +258,20 @@ jobs:
|
|||
with:
|
||||
channel: 'stable'
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Protobuf compiler
|
||||
uses: arduino/setup-protoc@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install the CLI tool
|
||||
run: cargo install rinf
|
||||
|
||||
- name: Generate message files
|
||||
run: rinf message
|
||||
|
||||
- name: flutter pub get
|
||||
run: flutter pub get
|
||||
|
||||
|
|
@ -237,6 +313,20 @@ jobs:
|
|||
uses: subosito/flutter-action@v2.10.0
|
||||
with:
|
||||
channel: 'stable'
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Protobuf compiler
|
||||
uses: arduino/setup-protoc@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install the CLI tool
|
||||
run: cargo install rinf
|
||||
|
||||
- name: Generate message files
|
||||
run: rinf message
|
||||
|
||||
- name: flutter pub get
|
||||
run: flutter pub get
|
||||
|
|
|
|||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -42,3 +42,10 @@ app.*.map.json
|
|||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# Rust related
|
||||
.cargo/
|
||||
target/
|
||||
|
||||
# Generated messages
|
||||
*/**/messages/
|
||||
|
|
|
|||
1355
Cargo.lock
generated
Normal file
1355
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# This file is used for telling Rust-related tools
|
||||
# where various Rust crates are.
|
||||
# This also unifies `./target` output folder and
|
||||
# various Rust configurations.
|
||||
|
||||
[workspace]
|
||||
members = ["./native/*"]
|
||||
resolver = "2"
|
||||
46
README.md
46
README.md
|
|
@ -62,3 +62,49 @@ Get the app from our [releases page](https://github.com/kodjodevf/mangayomi/rele
|
|||
## Disclaimer
|
||||
|
||||
The developer of this application does not have any affiliation with the content providers available.
|
||||
|
||||
## Using Rust Inside Flutter
|
||||
|
||||
This project leverages Flutter for GUI and Rust for the backend logic,
|
||||
utilizing the capabilities of the
|
||||
[Rinf](https://pub.dev/packages/rinf) framework.
|
||||
|
||||
To run and build this app, you need to have
|
||||
[Flutter SDK](https://docs.flutter.dev/get-started/install),
|
||||
[Rust toolchain](https://www.rust-lang.org/tools/install),
|
||||
and [Protobuf compiler](https://grpc.io/docs/protoc-installation)
|
||||
installed on your system.
|
||||
You can check that your system is ready with the commands below.
|
||||
Note that all the Flutter subcomponents should be installed.
|
||||
|
||||
```bash
|
||||
rustc --version
|
||||
protoc --version
|
||||
flutter doctor
|
||||
```
|
||||
|
||||
You also need to have the CLI tool for Rinf ready.
|
||||
|
||||
```bash
|
||||
cargo install rinf
|
||||
```
|
||||
|
||||
Messages sent between Dart and Rust are implemented using Protobuf.
|
||||
If you have newly cloned the project repository
|
||||
or made changes to the `.proto` files in the `./messages` directory,
|
||||
run the following command:
|
||||
|
||||
```bash
|
||||
rinf message
|
||||
```
|
||||
|
||||
Now you can run and build this app just like any other Flutter projects.
|
||||
|
||||
```bash
|
||||
flutter run
|
||||
```
|
||||
|
||||
For detailed instructions on writing Rust and Flutter together,
|
||||
please refer to Rinf's [documentation](https://rinf-docs.cunarist.com).
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_
|
|||
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:rinf/rinf.dart';
|
||||
|
||||
// Global instance of the Isar database.
|
||||
late Isar isar;
|
||||
|
|
@ -26,12 +27,14 @@ class MyHttpoverrides extends HttpOverrides {
|
|||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
return super.createHttpClient(context)
|
||||
..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||||
..badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry point of the application.
|
||||
void main(List<String> args) async {
|
||||
await Rinf.ensureInitialized();
|
||||
// Override the default HTTP client.
|
||||
HttpOverrides.global = MyHttpoverrides();
|
||||
// If running on desktop platforms and web view title bar widget is active, exit.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:typed_data';
|
|||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/crop_borders_provider.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
|
|
@ -11,6 +12,7 @@ import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
|||
|
||||
class ImageViewCenter extends ConsumerWidget {
|
||||
final UChapDataPreload datas;
|
||||
final bool cropBorders;
|
||||
final Widget? Function(ExtendedImageState state) loadStateChanged;
|
||||
final Function(ExtendedImageGestureState state) onDoubleTap;
|
||||
final GestureConfig Function(ExtendedImageState state)
|
||||
|
|
@ -18,6 +20,7 @@ class ImageViewCenter extends ConsumerWidget {
|
|||
const ImageViewCenter({
|
||||
super.key,
|
||||
required this.datas,
|
||||
required this.cropBorders,
|
||||
required this.loadStateChanged,
|
||||
required this.onDoubleTap,
|
||||
required this.initGestureConfigHandler,
|
||||
|
|
@ -25,7 +28,17 @@ class ImageViewCenter extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return _imageView(datas.isLocale!, datas.archiveImage, ref);
|
||||
return StreamBuilder<Uint8List?>(
|
||||
stream: ref
|
||||
.watch(cropBordersProvider(datas: datas, cropBorder: cropBorders)
|
||||
.future)
|
||||
.asStream(),
|
||||
builder: (context, snapshot) {
|
||||
final hasData = snapshot.hasData && snapshot.data != null;
|
||||
|
||||
return _imageView(hasData ? hasData : datas.isLocale!,
|
||||
hasData ? snapshot.data : datas.archiveImage, ref);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _imageView(bool isLocale, Uint8List? archiveImage, WidgetRef ref) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:typed_data';
|
|||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/crop_borders_provider.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
|
|
@ -15,17 +16,32 @@ import 'package:mangayomi/modules/manga/reader/widgets/circular_progress_indicat
|
|||
|
||||
class ImageViewVertical extends ConsumerWidget {
|
||||
final UChapDataPreload datas;
|
||||
final bool cropBorders;
|
||||
|
||||
final Function(bool) failedToLoadImage;
|
||||
|
||||
const ImageViewVertical(
|
||||
{super.key, required this.datas, required this.failedToLoadImage});
|
||||
{super.key,
|
||||
required this.datas,
|
||||
required this.cropBorders,
|
||||
required this.failedToLoadImage});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: _imageView(datas.isLocale!, datas.archiveImage, context, ref));
|
||||
child: StreamBuilder<Uint8List?>(
|
||||
stream: ref
|
||||
.watch(
|
||||
cropBordersProvider(datas: datas, cropBorder: cropBorders)
|
||||
.future)
|
||||
.asStream(),
|
||||
builder: (context, snapshot) {
|
||||
final hasData = snapshot.hasData && snapshot.data != null;
|
||||
|
||||
return _imageView(hasData ? hasData : datas.isLocale!,
|
||||
hasData ? snapshot.data : datas.archiveImage, context, ref);
|
||||
}));
|
||||
}
|
||||
|
||||
Widget _imageView(bool isLocale, Uint8List? archiveImage,
|
||||
|
|
|
|||
|
|
@ -1,129 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
// import 'package:image/image.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'auto_crop_image_provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<Uint8List?> autoCropImageBorder(AutoCropImageBorderRef ref,
|
||||
{required UChapDataPreload datas, required bool cropBorder}) async {
|
||||
Uint8List? imageBytes;
|
||||
if (cropBorder) {
|
||||
if (datas.archiveImage != null) {
|
||||
imageBytes = datas.archiveImage;
|
||||
} else if (datas.isLocale!) {
|
||||
imageBytes = File('${datas.path!.path}${padIndex(datas.index! + 1)}.jpg')
|
||||
.readAsBytesSync();
|
||||
} else {
|
||||
// String path = "";
|
||||
// File? cachedImage;
|
||||
// if (datas.url != null) {
|
||||
// cachedImage = await getCachedImageFile(datas.url!);
|
||||
// }
|
||||
// if (cachedImage != null) {
|
||||
// path = cachedImage.path;
|
||||
// }
|
||||
// if (path.isNotEmpty) {
|
||||
// data = (File(path).readAsBytesSync(), receivePort.sendPort);
|
||||
// } else {
|
||||
// data = (null, receivePort.sendPort);
|
||||
// }
|
||||
}
|
||||
if (imageBytes == null) {
|
||||
return null;
|
||||
}
|
||||
final res = await compute(cropImageWithThread, imageBytes);
|
||||
return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Uint8List?> cropImageWithThread(
|
||||
Uint8List? imageBytes,
|
||||
) async {
|
||||
// Command crop = Command();
|
||||
// crop.decodeImage(imageBytes!);
|
||||
// Command encode = Command();
|
||||
// encode.subCommand = crop;
|
||||
|
||||
// final image = await encode.getImageThread();
|
||||
// int left = 0;
|
||||
// int top = 0;
|
||||
// int right = image!.width;
|
||||
// int bottom = image.height;
|
||||
|
||||
// // Find left coordinate
|
||||
// for (int x = 0; x < image.width; x++) {
|
||||
// bool stop = false;
|
||||
// for (int y = 0; y < image.height; y++) {
|
||||
// if (image.getPixel(x, y).toString() != "(255, 255, 255)") {
|
||||
// stop = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (stop) {
|
||||
// left = x;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Find top coordinate
|
||||
// for (int y = 0; y < image.height; y++) {
|
||||
// bool stop = false;
|
||||
// for (int x = 0; x < image.width; x++) {
|
||||
// if (image.getPixel(x, y).toString() != "(255, 255, 255)") {
|
||||
// stop = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (stop) {
|
||||
// top = y;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Find right coordinate
|
||||
// for (int x = image.width - 1; x >= 0; x--) {
|
||||
// bool stop = false;
|
||||
// for (int y = 0; y < image.height; y++) {
|
||||
// if (image.getPixel(x, y).toString() != "(255, 255, 255)") {
|
||||
// stop = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (stop) {
|
||||
// right = x;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Find bottom coordinate
|
||||
// for (int y = image.height - 1; y >= 0; y--) {
|
||||
// bool stop = false;
|
||||
// for (int x = 0; x < image.width; x++) {
|
||||
// if (image.getPixel(x, y).toString() != "(255, 255, 255)") {
|
||||
// stop = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (stop) {
|
||||
// bottom = y;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// crop.copyCrop(
|
||||
// x: left,
|
||||
// y: top,
|
||||
// width: right - left,
|
||||
// height: bottom - top,
|
||||
// );
|
||||
|
||||
// encode.subCommand = crop;
|
||||
// encode.encodeJpg();
|
||||
|
||||
// return encode.getBytesThread();
|
||||
return null;
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import 'dart:io';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mangayomi/messages/crop_borders.pb.dart' as crop_borders;
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
||||
import 'package:rinf/rinf.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'crop_borders_provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<Uint8List?> cropBorders(CropBordersRef ref,
|
||||
{required UChapDataPreload datas, required bool cropBorder}) async {
|
||||
Uint8List? imageBytes;
|
||||
|
||||
if (cropBorder) {
|
||||
if (datas.archiveImage != null) {
|
||||
imageBytes = datas.archiveImage;
|
||||
} else if (datas.isLocale!) {
|
||||
imageBytes = File('${datas.path!.path}${padIndex(datas.index! + 1)}.jpg')
|
||||
.readAsBytesSync();
|
||||
} else {
|
||||
File? cachedImage;
|
||||
if (datas.url != null) {
|
||||
cachedImage = await getCachedImageFile(datas.url!);
|
||||
}
|
||||
imageBytes = cachedImage?.readAsBytesSync();
|
||||
}
|
||||
if (imageBytes == null) {
|
||||
return null;
|
||||
}
|
||||
final requestMessage = crop_borders.ReadRequest(
|
||||
image: imageBytes,
|
||||
);
|
||||
final rustRequest = RustRequest(
|
||||
resource: crop_borders.ID,
|
||||
operation: RustOperation.Read,
|
||||
message: requestMessage.writeToBuffer());
|
||||
final rustResponse = await requestToRust(rustRequest);
|
||||
|
||||
return rustResponse.blob;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'auto_crop_image_provider.dart';
|
||||
part of 'crop_borders_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$autoCropImageBorderHash() =>
|
||||
r'298f7adb70ff125c339dd793dca5be8394b96355';
|
||||
String _$cropBordersHash() => r'01a2567a5461a9ef1fa1c9d7ee547220b16d8861';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
@ -30,29 +29,29 @@ class _SystemHash {
|
|||
}
|
||||
}
|
||||
|
||||
/// See also [autoCropImageBorder].
|
||||
@ProviderFor(autoCropImageBorder)
|
||||
const autoCropImageBorderProvider = AutoCropImageBorderFamily();
|
||||
/// See also [cropBorders].
|
||||
@ProviderFor(cropBorders)
|
||||
const cropBordersProvider = CropBordersFamily();
|
||||
|
||||
/// See also [autoCropImageBorder].
|
||||
class AutoCropImageBorderFamily extends Family<AsyncValue<Uint8List?>> {
|
||||
/// See also [autoCropImageBorder].
|
||||
const AutoCropImageBorderFamily();
|
||||
/// See also [cropBorders].
|
||||
class CropBordersFamily extends Family<AsyncValue<Uint8List?>> {
|
||||
/// See also [cropBorders].
|
||||
const CropBordersFamily();
|
||||
|
||||
/// See also [autoCropImageBorder].
|
||||
AutoCropImageBorderProvider call({
|
||||
/// See also [cropBorders].
|
||||
CropBordersProvider call({
|
||||
required UChapDataPreload datas,
|
||||
required bool cropBorder,
|
||||
}) {
|
||||
return AutoCropImageBorderProvider(
|
||||
return CropBordersProvider(
|
||||
datas: datas,
|
||||
cropBorder: cropBorder,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoCropImageBorderProvider getProviderOverride(
|
||||
covariant AutoCropImageBorderProvider provider,
|
||||
CropBordersProvider getProviderOverride(
|
||||
covariant CropBordersProvider provider,
|
||||
) {
|
||||
return call(
|
||||
datas: provider.datas,
|
||||
|
|
@ -72,35 +71,35 @@ class AutoCropImageBorderFamily extends Family<AsyncValue<Uint8List?>> {
|
|||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'autoCropImageBorderProvider';
|
||||
String? get name => r'cropBordersProvider';
|
||||
}
|
||||
|
||||
/// See also [autoCropImageBorder].
|
||||
class AutoCropImageBorderProvider extends FutureProvider<Uint8List?> {
|
||||
/// See also [autoCropImageBorder].
|
||||
AutoCropImageBorderProvider({
|
||||
/// See also [cropBorders].
|
||||
class CropBordersProvider extends FutureProvider<Uint8List?> {
|
||||
/// See also [cropBorders].
|
||||
CropBordersProvider({
|
||||
required UChapDataPreload datas,
|
||||
required bool cropBorder,
|
||||
}) : this._internal(
|
||||
(ref) => autoCropImageBorder(
|
||||
ref as AutoCropImageBorderRef,
|
||||
(ref) => cropBorders(
|
||||
ref as CropBordersRef,
|
||||
datas: datas,
|
||||
cropBorder: cropBorder,
|
||||
),
|
||||
from: autoCropImageBorderProvider,
|
||||
name: r'autoCropImageBorderProvider',
|
||||
from: cropBordersProvider,
|
||||
name: r'cropBordersProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$autoCropImageBorderHash,
|
||||
dependencies: AutoCropImageBorderFamily._dependencies,
|
||||
: _$cropBordersHash,
|
||||
dependencies: CropBordersFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AutoCropImageBorderFamily._allTransitiveDependencies,
|
||||
CropBordersFamily._allTransitiveDependencies,
|
||||
datas: datas,
|
||||
cropBorder: cropBorder,
|
||||
);
|
||||
|
||||
AutoCropImageBorderProvider._internal(
|
||||
CropBordersProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
|
|
@ -116,12 +115,12 @@ class AutoCropImageBorderProvider extends FutureProvider<Uint8List?> {
|
|||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<Uint8List?> Function(AutoCropImageBorderRef provider) create,
|
||||
FutureOr<Uint8List?> Function(CropBordersRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: AutoCropImageBorderProvider._internal(
|
||||
(ref) => create(ref as AutoCropImageBorderRef),
|
||||
override: CropBordersProvider._internal(
|
||||
(ref) => create(ref as CropBordersRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
|
|
@ -135,12 +134,12 @@ class AutoCropImageBorderProvider extends FutureProvider<Uint8List?> {
|
|||
|
||||
@override
|
||||
FutureProviderElement<Uint8List?> createElement() {
|
||||
return _AutoCropImageBorderProviderElement(this);
|
||||
return _CropBordersProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AutoCropImageBorderProvider &&
|
||||
return other is CropBordersProvider &&
|
||||
other.datas == datas &&
|
||||
other.cropBorder == cropBorder;
|
||||
}
|
||||
|
|
@ -155,7 +154,7 @@ class AutoCropImageBorderProvider extends FutureProvider<Uint8List?> {
|
|||
}
|
||||
}
|
||||
|
||||
mixin AutoCropImageBorderRef on FutureProviderRef<Uint8List?> {
|
||||
mixin CropBordersRef on FutureProviderRef<Uint8List?> {
|
||||
/// The parameter `datas` of this provider.
|
||||
UChapDataPreload get datas;
|
||||
|
||||
|
|
@ -163,14 +162,14 @@ mixin AutoCropImageBorderRef on FutureProviderRef<Uint8List?> {
|
|||
bool get cropBorder;
|
||||
}
|
||||
|
||||
class _AutoCropImageBorderProviderElement
|
||||
extends FutureProviderElement<Uint8List?> with AutoCropImageBorderRef {
|
||||
_AutoCropImageBorderProviderElement(super.provider);
|
||||
class _CropBordersProviderElement extends FutureProviderElement<Uint8List?>
|
||||
with CropBordersRef {
|
||||
_CropBordersProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
UChapDataPreload get datas => (origin as AutoCropImageBorderProvider).datas;
|
||||
UChapDataPreload get datas => (origin as CropBordersProvider).datas;
|
||||
@override
|
||||
bool get cropBorder => (origin as AutoCropImageBorderProvider).cropBorder;
|
||||
bool get cropBorder => (origin as CropBordersProvider).cropBorder;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
|
@ -11,6 +11,7 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/crop_borders_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/sources/utils/utils.dart';
|
||||
|
|
@ -244,12 +245,15 @@ class _MangaChapterPageGalleryState
|
|||
if (_currentIndex! >= 0 && _currentIndex! < _uChapDataPreload.length) {
|
||||
if (_readerController.chapter.id !=
|
||||
_uChapDataPreload[_currentIndex!].chapter!.id) {
|
||||
setState(() {
|
||||
_readerController = ReaderController(
|
||||
chapter: _uChapDataPreload[_currentIndex!].chapter!);
|
||||
|
||||
_chapterUrlModel = _uChapDataPreload[_currentIndex!].chapterUrlModel!;
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_readerController = ReaderController(
|
||||
chapter: _uChapDataPreload[_currentIndex!].chapter!);
|
||||
chapter = _uChapDataPreload[_currentIndex!].chapter!;
|
||||
_chapterUrlModel =
|
||||
_uChapDataPreload[_currentIndex!].chapterUrlModel!;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ref.read(currentIndexProvider(chapter).notifier).setCurrentIndex(
|
||||
|
|
@ -315,14 +319,11 @@ class _MangaChapterPageGalleryState
|
|||
}
|
||||
if (mounted) {
|
||||
uChapDataPreloadL.addAll(uChapDataPreloadP);
|
||||
setState(() {
|
||||
_uChapDataPreload = uChapDataPreloadL;
|
||||
_chapterUrlModel = chapterData;
|
||||
_readerController = ReaderController(chapter: chap);
|
||||
_readerController = ReaderController(
|
||||
chapter: _uChapDataPreload[_currentIndex!].chapter!);
|
||||
chapter = chap;
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_uChapDataPreload = uChapDataPreloadL;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
|
@ -345,7 +346,6 @@ class _MangaChapterPageGalleryState
|
|||
_preloadImage(_currentIndex! - i);
|
||||
}
|
||||
}
|
||||
// _initCropBorders();
|
||||
}
|
||||
|
||||
void _onPageChanged(int index) {
|
||||
|
|
@ -355,12 +355,14 @@ class _MangaChapterPageGalleryState
|
|||
}
|
||||
|
||||
if (_readerController.chapter.id != _uChapDataPreload[index].chapter!.id) {
|
||||
setState(() {
|
||||
_readerController =
|
||||
ReaderController(chapter: _uChapDataPreload[index].chapter!);
|
||||
|
||||
_chapterUrlModel = _uChapDataPreload[index].chapterUrlModel!;
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_readerController =
|
||||
ReaderController(chapter: _uChapDataPreload[index].chapter!);
|
||||
chapter = _uChapDataPreload[_currentIndex!].chapter!;
|
||||
_chapterUrlModel = _uChapDataPreload[index].chapterUrlModel!;
|
||||
});
|
||||
}
|
||||
}
|
||||
_currentIndex = index;
|
||||
|
||||
|
|
@ -491,29 +493,31 @@ class _MangaChapterPageGalleryState
|
|||
}
|
||||
|
||||
void _toggleScale(Offset tapPosition) {
|
||||
setState(() {
|
||||
if (_scaleAnimationController.isAnimating) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_photoViewController.scale == 1.0) {
|
||||
_scalePosition = _computeAlignmentByTapOffset(tapPosition);
|
||||
|
||||
if (_scaleAnimationController.isCompleted) {
|
||||
_scaleAnimationController.reset();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (_scaleAnimationController.isAnimating) {
|
||||
return;
|
||||
}
|
||||
|
||||
_scaleAnimationController.forward();
|
||||
return;
|
||||
}
|
||||
if (_photoViewController.scale == 1.0) {
|
||||
_scalePosition = _computeAlignmentByTapOffset(tapPosition);
|
||||
|
||||
if (_photoViewController.scale == 2.0) {
|
||||
_scaleAnimationController.reverse();
|
||||
return;
|
||||
}
|
||||
if (_scaleAnimationController.isCompleted) {
|
||||
_scaleAnimationController.reset();
|
||||
}
|
||||
|
||||
_photoViewScaleStateController.reset();
|
||||
});
|
||||
_scaleAnimationController.forward();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_photoViewController.scale == 2.0) {
|
||||
_scaleAnimationController.reverse();
|
||||
return;
|
||||
}
|
||||
|
||||
_photoViewScaleStateController.reset();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Axis _scrollDirection = Axis.vertical;
|
||||
|
|
@ -947,7 +951,7 @@ class _MangaChapterPageGalleryState
|
|||
'${currentIndex + 1} / ${_readerController.getPageLength(_chapterUrlModel.pageUrls)}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13.0,
|
||||
fontSize: 15.0,
|
||||
shadows: <Shadow>[
|
||||
Shadow(offset: Offset(0.0, 0.0), blurRadius: 7.0)
|
||||
],
|
||||
|
|
@ -1104,6 +1108,8 @@ class _MangaChapterPageGalleryState
|
|||
readerMode == ReaderMode.webtoon;
|
||||
}
|
||||
|
||||
final List<UChapDataPreload> _cropBorderCheckList = [];
|
||||
|
||||
final StreamController<double> _rebuildDetail =
|
||||
StreamController<double>.broadcast();
|
||||
late AnimationController _doubleClickAnimationController;
|
||||
|
|
@ -1119,9 +1125,29 @@ class _MangaChapterPageGalleryState
|
|||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
_processCropBorders() async {
|
||||
for (var datas in _uChapDataPreload) {
|
||||
if (!_cropBorderCheckList.contains(datas)) {
|
||||
_cropBorderCheckList.add(datas);
|
||||
ref.watch(cropBordersProvider(datas: datas, cropBorder: true));
|
||||
ref.watch(cropBordersProvider(datas: datas, cropBorder: false));
|
||||
} else {
|
||||
if (!datas.isLocale!) {
|
||||
final res = await ref.watch(
|
||||
cropBordersProvider(datas: datas, cropBorder: true).future);
|
||||
if (res == null) {
|
||||
ref.invalidate(cropBordersProvider(datas: datas, cropBorder: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_processCropBorders();
|
||||
final backgroundColor = ref.watch(backgroundColorStateProvider);
|
||||
final cropBorders = ref.watch(cropBordersStateProvider);
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
|
|
@ -1217,6 +1243,7 @@ class _MangaChapterPageGalleryState
|
|||
failedToLoadImage: (value) {
|
||||
// _failedToLoadImage.value = value;
|
||||
},
|
||||
cropBorders: cropBorders,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -1409,6 +1436,7 @@ class _MangaChapterPageGalleryState
|
|||
|
||||
_doubleClickAnimationController.forward();
|
||||
},
|
||||
cropBorders: cropBorders,
|
||||
);
|
||||
},
|
||||
itemCount: _uChapDataPreload.length,
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
import 'dart:ui';
|
||||
|
||||
Image cropRect({
|
||||
required Image image,
|
||||
required int x,
|
||||
required int y,
|
||||
required int width,
|
||||
required int height,
|
||||
}) {
|
||||
final recorder = PictureRecorder();
|
||||
Canvas(recorder).drawImageRect(
|
||||
image,
|
||||
Rect.fromLTRB(
|
||||
x.toDouble(),
|
||||
y.toDouble(),
|
||||
x.toDouble() + width.toDouble(),
|
||||
y.toDouble() + height.toDouble(),
|
||||
),
|
||||
Rect.fromLTRB(0, 0, width.toDouble(), height.toDouble()),
|
||||
Paint(),
|
||||
);
|
||||
|
||||
final croppedImage = recorder.endRecording().toImageSync(width, height);
|
||||
return croppedImage;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
media_kit_native_event_loop
|
||||
rinf
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
|
|
|||
6
messages/crop_borders.proto
Normal file
6
messages/crop_borders.proto
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
syntax = "proto3";
|
||||
package crop_borders;
|
||||
|
||||
message ReadRequest {
|
||||
bytes image = 1;
|
||||
}
|
||||
7
native/README.md
Normal file
7
native/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Rust Crates
|
||||
|
||||
This folder contains Rust crates. Entry point of the Rust logic is the `hub` library crate. These crates are integrated and compiled into the Flutter app by [Rinf](https://github.com/cunarist/rinf) framework.
|
||||
|
||||
- Do NOT change the name of the `hub` crate. Compilation presets expect the entry library crate to be located at `./native/hub`.
|
||||
- Do NOT modify the `bridge` module inside `./native/hub/src` unless you know what you're doing.
|
||||
- You CAN name crates other than `hub` as you want.
|
||||
44
native/hub/Cargo.toml
Normal file
44
native/hub/Cargo.toml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
[package]
|
||||
# Do not change the name of this crate.
|
||||
name = "hub"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
# `lib` is required for non-library targets,
|
||||
# such as tests and benchmarks.
|
||||
# `cdylib` is for Linux, Android, Windows, and web.
|
||||
# `staticlib` is for iOS and macOS.
|
||||
crate-type = ["lib", "cdylib", "staticlib"]
|
||||
|
||||
# These are dependencies for non-web platforms.
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
libc = "0.2"
|
||||
dart-sys = { version = "4.0.2" }
|
||||
allo-isolate = { version = "0.1.20", features = ["zero-copy"] }
|
||||
tokio = { version = "1.28.2", features = ["rt-multi-thread", "time"] }
|
||||
os-thread-local = "0.1.3"
|
||||
backtrace = "0.3.69"
|
||||
|
||||
# These are dependencies for the web.
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
wasm-bindgen = { version = "0.2.87" }
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
js-sys = "0.3.64"
|
||||
web-sys = { version = "0.3.64", features = [
|
||||
"DedicatedWorkerGlobalScope",
|
||||
"MessagePort",
|
||||
"Blob",
|
||||
"BlobPropertyBag",
|
||||
"Worker",
|
||||
"Url",
|
||||
"BroadcastChannel",
|
||||
] }
|
||||
async_wasm_task = "0.2.3"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = "1.11.0"
|
||||
lazy_static = "1.4.0"
|
||||
tokio = { version = "1.28.2", features = ["sync", "macros"] }
|
||||
prost = "0.12.0"
|
||||
image = "0.24.7"
|
||||
243
native/hub/src/bridge/api.rs
Normal file
243
native/hub/src/bridge/api.rs
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use crate::bridge::bridge_engine::StreamSink;
|
||||
use lazy_static::lazy_static;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use tokio::sync::mpsc::channel;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
/// Available operations that a `RustRequest` object can hold.
|
||||
/// There are 4 options, `Create`,`Read`,`Update`, and `Delete`.
|
||||
pub enum RustOperation {
|
||||
Create,
|
||||
Read,
|
||||
Update,
|
||||
Delete,
|
||||
}
|
||||
|
||||
/// Holds the data that Rust streams to Dart.
|
||||
#[derive(Clone)]
|
||||
pub struct RustSignal {
|
||||
pub resource: i32,
|
||||
pub message: Option<Vec<u8>>,
|
||||
pub blob: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
/// Request object that is sent from Dart to Rust.
|
||||
pub struct RustRequest {
|
||||
pub resource: i32,
|
||||
pub operation: RustOperation,
|
||||
pub message: Option<Vec<u8>>,
|
||||
pub blob: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
/// Wrapper for `RustRequest` with a unique ID.
|
||||
pub struct RustRequestUnique {
|
||||
pub id: i32,
|
||||
pub request: RustRequest,
|
||||
}
|
||||
|
||||
/// Response object that is sent from Rust to Dart.
|
||||
#[derive(Clone)]
|
||||
pub struct RustResponse {
|
||||
pub successful: bool,
|
||||
pub message: Option<Vec<u8>>,
|
||||
pub blob: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Default for RustResponse {
|
||||
/// Empty response with the successful value of false.
|
||||
fn default() -> RustResponse {
|
||||
RustResponse {
|
||||
successful: false,
|
||||
message: None,
|
||||
blob: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for `RustResponse` with a unique ID.
|
||||
#[derive(Clone)]
|
||||
pub struct RustResponseUnique {
|
||||
pub id: i32,
|
||||
pub response: RustResponse,
|
||||
}
|
||||
|
||||
type Cell<T> = RefCell<Option<T>>;
|
||||
type SharedCell<T> = Arc<Mutex<Cell<T>>>;
|
||||
|
||||
type RustSignalStream = StreamSink<RustSignal>;
|
||||
type RustResponseStream = StreamSink<RustResponseUnique>;
|
||||
type RustReportStream = StreamSink<String>;
|
||||
type RustRequestSender = Sender<RustRequestUnique>;
|
||||
type RustRequestReceiver = Receiver<RustRequestUnique>;
|
||||
|
||||
// Native: Main thread
|
||||
// Web: Worker thread
|
||||
thread_local! {
|
||||
pub static REQUEST_SENDER: Cell<RustRequestSender> = RefCell::new(None);
|
||||
}
|
||||
|
||||
// Native: `tokio` runtime threads
|
||||
// Web: Worker thread
|
||||
thread_local! {
|
||||
pub static SIGNAL_STREAM: Cell<RustSignalStream> = RefCell::new(None);
|
||||
pub static RESPONSE_STREAM: Cell<RustResponseStream> = RefCell::new(None);
|
||||
pub static REPORT_STREAM: Cell<RustReportStream> = RefCell::new(None);
|
||||
}
|
||||
|
||||
// Native: All threads
|
||||
// Web: Worker thread
|
||||
lazy_static! {
|
||||
pub static ref SIGNAL_STREAM_SHARED: SharedCell<RustSignalStream> =
|
||||
Arc::new(Mutex::new(RefCell::new(None)));
|
||||
pub static ref RESPONSE_STREAM_SHARED: SharedCell<RustResponseStream> =
|
||||
Arc::new(Mutex::new(RefCell::new(None)));
|
||||
pub static ref REPORT_STREAM_SHARED: SharedCell<RustReportStream> =
|
||||
Arc::new(Mutex::new(RefCell::new(None)));
|
||||
pub static ref REQUST_RECEIVER_SHARED: SharedCell<RustRequestReceiver> =
|
||||
Arc::new(Mutex::new(RefCell::new(None)));
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
lazy_static! {
|
||||
pub static ref TOKIO_RUNTIME: os_thread_local::ThreadLocal<Cell<tokio::runtime::Runtime>> =
|
||||
os_thread_local::ThreadLocal::new(|| RefCell::new(None));
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
thread_local! {
|
||||
pub static IS_MAIN_STARTED: RefCell<bool> = RefCell::new(false);
|
||||
}
|
||||
|
||||
/// Returns a stream object in Dart that listens to Rust.
|
||||
pub fn prepare_rust_signal_stream(signal_stream: StreamSink<RustSignal>) {
|
||||
let cell = SIGNAL_STREAM_SHARED.lock().unwrap();
|
||||
cell.replace(Some(signal_stream));
|
||||
}
|
||||
|
||||
/// Returns a stream object in Dart that gives responses from Rust.
|
||||
pub fn prepare_rust_response_stream(response_stream: StreamSink<RustResponseUnique>) {
|
||||
let cell = RESPONSE_STREAM_SHARED.lock().unwrap();
|
||||
cell.replace(Some(response_stream));
|
||||
}
|
||||
|
||||
/// Returns a stream object in Dart that gives strings to print from Rust.
|
||||
pub fn prepare_rust_report_stream(print_stream: StreamSink<String>) {
|
||||
let cell = REPORT_STREAM_SHARED.lock().unwrap();
|
||||
cell.replace(Some(print_stream));
|
||||
}
|
||||
|
||||
/// Prepare channels that are used in the Rust world.
|
||||
pub fn prepare_channels() {
|
||||
let (request_sender, request_receiver) = channel(1024);
|
||||
REQUEST_SENDER.with(move |inner| {
|
||||
inner.replace(Some(request_sender));
|
||||
});
|
||||
let cell = REQUST_RECEIVER_SHARED.lock().unwrap();
|
||||
cell.replace(Some(request_receiver));
|
||||
}
|
||||
|
||||
/// Check if the streams are ready in Rust.
|
||||
/// This should be done before starting the Rust logic.
|
||||
pub fn check_rust_streams() -> bool {
|
||||
let mut are_all_ready = true;
|
||||
let cell = SIGNAL_STREAM_SHARED.lock().unwrap();
|
||||
if cell.borrow().is_none() {
|
||||
are_all_ready = false;
|
||||
};
|
||||
let cell = RESPONSE_STREAM_SHARED.lock().unwrap();
|
||||
if cell.borrow().is_none() {
|
||||
are_all_ready = false;
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let cell = REPORT_STREAM_SHARED.lock().unwrap();
|
||||
if cell.borrow().is_none() {
|
||||
are_all_ready = false;
|
||||
};
|
||||
}
|
||||
are_all_ready
|
||||
}
|
||||
|
||||
/// Start the main function of Rust.
|
||||
pub fn start_rust_logic() {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
std::panic::set_hook(Box::new(|panic_info| {
|
||||
let mut frames_filtered = Vec::new();
|
||||
backtrace::trace(|frame| {
|
||||
// Filter some backtrace frames
|
||||
// as those from infrastructure functions are not needed.
|
||||
let mut should_keep_tracing = true;
|
||||
backtrace::resolve_frame(frame, |symbol| {
|
||||
if let Some(symbol_name) = symbol.name() {
|
||||
let name = symbol_name.to_string();
|
||||
let name_trimmed = name.trim_start_matches('_');
|
||||
if name_trimmed.starts_with("rust_begin_unwind") {
|
||||
frames_filtered.clear();
|
||||
return;
|
||||
}
|
||||
if name_trimmed.starts_with("rust_try") {
|
||||
should_keep_tracing = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
let backtrace_frame = backtrace::BacktraceFrame::from(frame.to_owned());
|
||||
frames_filtered.push(backtrace_frame);
|
||||
});
|
||||
should_keep_tracing
|
||||
});
|
||||
let mut backtrace_filtered = backtrace::Backtrace::from(frames_filtered);
|
||||
backtrace_filtered.resolve();
|
||||
crate::debug_print!(
|
||||
"A panic occurred in Rust.\n{}\n{:?}",
|
||||
panic_info,
|
||||
backtrace_filtered
|
||||
);
|
||||
}));
|
||||
TOKIO_RUNTIME.with(move |inner| {
|
||||
let tokio_runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
tokio_runtime.spawn(crate::main());
|
||||
inner.replace(Some(tokio_runtime));
|
||||
});
|
||||
}
|
||||
#[cfg(target_family = "wasm")]
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
std::panic::set_hook(Box::new(|panic_info| {
|
||||
crate::debug_print!("A panic occurred in Rust.\n{}", panic_info);
|
||||
}));
|
||||
IS_MAIN_STARTED.with(move |ref_cell| {
|
||||
let is_started = *ref_cell.borrow();
|
||||
if !is_started {
|
||||
wasm_bindgen_futures::spawn_local(crate::main());
|
||||
ref_cell.replace(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop and terminate all Rust tasks.
|
||||
pub fn stop_rust_logic() {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
TOKIO_RUNTIME.with(move |ref_cell| {
|
||||
ref_cell.replace(None);
|
||||
});
|
||||
}
|
||||
|
||||
/// Send a request to Rust and receive a response in Dart.
|
||||
pub fn request_to_rust(request_unique: RustRequestUnique) {
|
||||
REQUEST_SENDER.with(move |inner| {
|
||||
let borrowed = inner.borrow();
|
||||
let sender = borrowed.as_ref().unwrap();
|
||||
sender.try_send(request_unique).ok();
|
||||
});
|
||||
}
|
||||
23
native/hub/src/bridge/bridge_engine/dart_api/BUILD.gn
Normal file
23
native/hub/src/bridge/bridge_engine/dart_api/BUILD.gn
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
||||
# for details. All rights reserved. Use of this source code is governed by a
|
||||
# BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import("../../sdk_args.gni")
|
||||
|
||||
# This rule copies header files to include/
|
||||
copy("copy_headers") {
|
||||
visibility = [ "../../sdk:copy_headers" ]
|
||||
|
||||
sources = [
|
||||
"dart_api.h",
|
||||
"dart_api_dl.c",
|
||||
"dart_api_dl.h",
|
||||
"dart_native_api.h",
|
||||
"dart_tools_api.h",
|
||||
"dart_version.h",
|
||||
"internal/dart_api_dl_impl.h",
|
||||
]
|
||||
|
||||
outputs =
|
||||
[ "$root_out_dir/$dart_sdk_output/include/{{source_target_relative}}" ]
|
||||
}
|
||||
6
native/hub/src/bridge/bridge_engine/dart_api/README.md
Normal file
6
native/hub/src/bridge/bridge_engine/dart_api/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
This folder contains a copy of Dart SDK [include/](https://github.com/dart-lang/sdk/tree/master/runtime/include) folder.
|
||||
|
||||
Current version of Dart API is `2.0`. On breaking changes the major version is increased. Minor versions are allowed to be
|
||||
different. If the DartVM has a higher minor version, it will provide more symbols than we initialize here.
|
||||
|
||||
Note that you might need to update if Dart SDK makes an incompatible change to its DL C API.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
||||
* for details. All rights reserved. Use of this source code is governed by a
|
||||
* BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_INCLUDE_ANALYZE_SNAPSHOT_API_H_
|
||||
#define RUNTIME_INCLUDE_ANALYZE_SNAPSHOT_API_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace dart {
|
||||
namespace snapshot_analyzer {
|
||||
typedef struct {
|
||||
const uint8_t* vm_snapshot_data;
|
||||
const uint8_t* vm_snapshot_instructions;
|
||||
const uint8_t* vm_isolate_data;
|
||||
const uint8_t* vm_isolate_instructions;
|
||||
} Dart_SnapshotAnalyzerInformation;
|
||||
|
||||
void Dart_DumpSnapshotInformationAsJson(char** buffer,
|
||||
intptr_t* buffer_length,
|
||||
Dart_SnapshotAnalyzerInformation* info);
|
||||
|
||||
void Dart_DumpSnapshotInformationPP(Dart_SnapshotAnalyzerInformation* info);
|
||||
|
||||
} // namespace snapshot_analyzer
|
||||
} // namespace dart
|
||||
|
||||
#endif // RUNTIME_INCLUDE_ANALYZE_SNAPSHOT_API_H_
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef RUNTIME_INCLUDE_BIN_DART_IO_API_H_
|
||||
#define RUNTIME_INCLUDE_BIN_DART_IO_API_H_
|
||||
|
||||
#include "dart_tools_api.h"
|
||||
|
||||
namespace dart {
|
||||
namespace bin {
|
||||
|
||||
// Bootstraps 'dart:io'.
|
||||
void BootstrapDartIo();
|
||||
|
||||
// Cleans up 'dart:io'.
|
||||
void CleanupDartIo();
|
||||
|
||||
// Lets dart:io know where the system temporary directory is located.
|
||||
// Currently only wired up on Android.
|
||||
void SetSystemTempDirectory(const char* system_temp);
|
||||
|
||||
// Tells the system whether to capture Stdout events.
|
||||
void SetCaptureStdout(bool value);
|
||||
|
||||
// Tells the system whether to capture Stderr events.
|
||||
void SetCaptureStderr(bool value);
|
||||
|
||||
// Should Stdout events be captured?
|
||||
bool ShouldCaptureStdout();
|
||||
|
||||
// Should Stderr events be captured?
|
||||
bool ShouldCaptureStderr();
|
||||
|
||||
// Set the executable name used by Platform.executable.
|
||||
void SetExecutableName(const char* executable_name);
|
||||
|
||||
// Set the arguments used by Platform.executableArguments.
|
||||
void SetExecutableArguments(int script_index, char** argv);
|
||||
|
||||
// Set dart:io implementation specific fields of Dart_EmbedderInformation.
|
||||
void GetIOEmbedderInformation(Dart_EmbedderInformation* info);
|
||||
|
||||
// Appropriate to assign to Dart_InitializeParams.file_open/read/write/close.
|
||||
void* OpenFile(const char* name, bool write);
|
||||
void ReadFile(uint8_t** data, intptr_t* file_len, void* stream);
|
||||
void WriteFile(const void* buffer, intptr_t num_bytes, void* stream);
|
||||
void CloseFile(void* stream);
|
||||
|
||||
// Generates 'length' random bytes into 'buffer'. Returns true on success
|
||||
// and false on failure. This is appropriate to assign to
|
||||
// Dart_InitializeParams.entropy_source.
|
||||
bool GetEntropy(uint8_t* buffer, intptr_t length);
|
||||
|
||||
// Performs a lookup of the I/O Dart_NativeFunction with a specified 'name' and
|
||||
// 'argument_count'. Returns NULL if no I/O native function with a matching
|
||||
// name and parameter count is found.
|
||||
Dart_NativeFunction LookupIONative(Dart_Handle name,
|
||||
int argument_count,
|
||||
bool* auto_setup_scope);
|
||||
|
||||
// Returns the symbol for I/O native function 'nf'. Returns NULL if 'nf' is not
|
||||
// a valid I/O native function.
|
||||
const uint8_t* LookupIONativeSymbol(Dart_NativeFunction nf);
|
||||
|
||||
} // namespace bin
|
||||
} // namespace dart
|
||||
|
||||
#endif // RUNTIME_INCLUDE_BIN_DART_IO_API_H_
|
||||
4168
native/hub/src/bridge/bridge_engine/dart_api/dart_api.h
Normal file
4168
native/hub/src/bridge/bridge_engine/dart_api/dart_api.h
Normal file
File diff suppressed because it is too large
Load diff
59
native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.c
Normal file
59
native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.c
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
* for details. All rights reserved. Use of this source code is governed by a
|
||||
* BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "dart_api_dl.h" /* NOLINT */
|
||||
#include "dart_version.h" /* NOLINT */
|
||||
#include "internal/dart_api_dl_impl.h" /* NOLINT */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define DART_API_DL_DEFINITIONS(name, R, A) name##_Type name##_DL = NULL;
|
||||
|
||||
DART_API_ALL_DL_SYMBOLS(DART_API_DL_DEFINITIONS)
|
||||
|
||||
#undef DART_API_DL_DEFINITIONS
|
||||
|
||||
typedef void* DartApiEntry_function;
|
||||
|
||||
DartApiEntry_function FindFunctionPointer(const DartApiEntry* entries,
|
||||
const char* name) {
|
||||
while (entries->name != NULL) {
|
||||
if (strcmp(entries->name, name) == 0) return entries->function;
|
||||
entries++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
intptr_t Dart_InitializeApiDL(void* data) {
|
||||
DartApi* dart_api_data = (DartApi*)data;
|
||||
|
||||
if (dart_api_data->major != DART_API_DL_MAJOR_VERSION) {
|
||||
// If the DartVM we're running on does not have the same version as this
|
||||
// file was compiled against, refuse to initialize. The symbols are not
|
||||
// compatible.
|
||||
return -1;
|
||||
}
|
||||
// Minor versions are allowed to be different.
|
||||
// If the DartVM has a higher minor version, it will provide more symbols
|
||||
// than we initialize here.
|
||||
// If the DartVM has a lower minor version, it will not provide all symbols.
|
||||
// In that case, we leave the missing symbols un-initialized. Those symbols
|
||||
// should not be used by the Dart and native code. The client is responsible
|
||||
// for checking the minor version number himself based on which symbols it
|
||||
// is using.
|
||||
// (If we would error out on this case, recompiling native code against a
|
||||
// newer SDK would break all uses on older SDKs, which is too strict.)
|
||||
|
||||
const DartApiEntry* dart_api_function_pointers = dart_api_data->functions;
|
||||
|
||||
#define DART_API_DL_INIT(name, R, A) \
|
||||
name##_DL = \
|
||||
(name##_Type)(FindFunctionPointer(dart_api_function_pointers, #name));
|
||||
DART_API_ALL_DL_SYMBOLS(DART_API_DL_INIT)
|
||||
#undef DART_API_DL_INIT
|
||||
|
||||
return 0;
|
||||
}
|
||||
152
native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.h
Normal file
152
native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.h
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
* for details. All rights reserved. Use of this source code is governed by a
|
||||
* BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_INCLUDE_DART_API_DL_H_
|
||||
#define RUNTIME_INCLUDE_DART_API_DL_H_
|
||||
|
||||
#include "dart_api.h" /* NOLINT */
|
||||
#include "dart_native_api.h" /* NOLINT */
|
||||
|
||||
/** \mainpage Dynamically Linked Dart API
|
||||
*
|
||||
* This exposes a subset of symbols from dart_api.h and dart_native_api.h
|
||||
* available in every Dart embedder through dynamic linking.
|
||||
*
|
||||
* All symbols are postfixed with _DL to indicate that they are dynamically
|
||||
* linked and to prevent conflicts with the original symbol.
|
||||
*
|
||||
* Link `dart_api_dl.c` file into your library and invoke
|
||||
* `Dart_InitializeApiDL` with `NativeApi.initializeApiDLData`.
|
||||
*/
|
||||
|
||||
DART_EXPORT intptr_t Dart_InitializeApiDL(void* data);
|
||||
|
||||
// ============================================================================
|
||||
// IMPORTANT! Never update these signatures without properly updating
|
||||
// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION.
|
||||
//
|
||||
// Verbatim copy of `dart_native_api.h` and `dart_api.h` symbol names and types
|
||||
// to trigger compile-time errors if the symbols in those files are updated
|
||||
// without updating these.
|
||||
//
|
||||
// Function return and argument types, and typedefs are carbon copied. Structs
|
||||
// are typechecked nominally in C/C++, so they are not copied, instead a
|
||||
// comment is added to their definition.
|
||||
typedef int64_t Dart_Port_DL;
|
||||
|
||||
typedef void (*Dart_NativeMessageHandler_DL)(Dart_Port_DL dest_port_id,
|
||||
Dart_CObject* message);
|
||||
|
||||
// dart_native_api.h symbols can be called on any thread.
|
||||
#define DART_NATIVE_API_DL_SYMBOLS(F) \
|
||||
/***** dart_native_api.h *****/ \
|
||||
/* Dart_Port */ \
|
||||
F(Dart_PostCObject, bool, (Dart_Port_DL port_id, Dart_CObject * message)) \
|
||||
F(Dart_PostInteger, bool, (Dart_Port_DL port_id, int64_t message)) \
|
||||
F(Dart_NewNativePort, Dart_Port_DL, \
|
||||
(const char* name, Dart_NativeMessageHandler_DL handler, \
|
||||
bool handle_concurrently)) \
|
||||
F(Dart_CloseNativePort, bool, (Dart_Port_DL native_port_id))
|
||||
|
||||
// dart_api.h symbols can only be called on Dart threads.
|
||||
#define DART_API_DL_SYMBOLS(F) \
|
||||
/***** dart_api.h *****/ \
|
||||
/* Errors */ \
|
||||
F(Dart_IsError, bool, (Dart_Handle handle)) \
|
||||
F(Dart_IsApiError, bool, (Dart_Handle handle)) \
|
||||
F(Dart_IsUnhandledExceptionError, bool, (Dart_Handle handle)) \
|
||||
F(Dart_IsCompilationError, bool, (Dart_Handle handle)) \
|
||||
F(Dart_IsFatalError, bool, (Dart_Handle handle)) \
|
||||
F(Dart_GetError, const char*, (Dart_Handle handle)) \
|
||||
F(Dart_ErrorHasException, bool, (Dart_Handle handle)) \
|
||||
F(Dart_ErrorGetException, Dart_Handle, (Dart_Handle handle)) \
|
||||
F(Dart_ErrorGetStackTrace, Dart_Handle, (Dart_Handle handle)) \
|
||||
F(Dart_NewApiError, Dart_Handle, (const char* error)) \
|
||||
F(Dart_NewCompilationError, Dart_Handle, (const char* error)) \
|
||||
F(Dart_NewUnhandledExceptionError, Dart_Handle, (Dart_Handle exception)) \
|
||||
F(Dart_PropagateError, void, (Dart_Handle handle)) \
|
||||
/* Dart_Handle, Dart_PersistentHandle, Dart_WeakPersistentHandle */ \
|
||||
F(Dart_HandleFromPersistent, Dart_Handle, (Dart_PersistentHandle object)) \
|
||||
F(Dart_HandleFromWeakPersistent, Dart_Handle, \
|
||||
(Dart_WeakPersistentHandle object)) \
|
||||
F(Dart_NewPersistentHandle, Dart_PersistentHandle, (Dart_Handle object)) \
|
||||
F(Dart_SetPersistentHandle, void, \
|
||||
(Dart_PersistentHandle obj1, Dart_Handle obj2)) \
|
||||
F(Dart_DeletePersistentHandle, void, (Dart_PersistentHandle object)) \
|
||||
F(Dart_NewWeakPersistentHandle, Dart_WeakPersistentHandle, \
|
||||
(Dart_Handle object, void* peer, intptr_t external_allocation_size, \
|
||||
Dart_HandleFinalizer callback)) \
|
||||
F(Dart_DeleteWeakPersistentHandle, void, (Dart_WeakPersistentHandle object)) \
|
||||
F(Dart_UpdateExternalSize, void, \
|
||||
(Dart_WeakPersistentHandle object, intptr_t external_allocation_size)) \
|
||||
F(Dart_NewFinalizableHandle, Dart_FinalizableHandle, \
|
||||
(Dart_Handle object, void* peer, intptr_t external_allocation_size, \
|
||||
Dart_HandleFinalizer callback)) \
|
||||
F(Dart_DeleteFinalizableHandle, void, \
|
||||
(Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object)) \
|
||||
F(Dart_UpdateFinalizableExternalSize, void, \
|
||||
(Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object, \
|
||||
intptr_t external_allocation_size)) \
|
||||
/* Dart_Port */ \
|
||||
F(Dart_Post, bool, (Dart_Port_DL port_id, Dart_Handle object)) \
|
||||
F(Dart_NewSendPort, Dart_Handle, (Dart_Port_DL port_id)) \
|
||||
F(Dart_SendPortGetId, Dart_Handle, \
|
||||
(Dart_Handle port, Dart_Port_DL * port_id)) \
|
||||
/* Scopes */ \
|
||||
F(Dart_EnterScope, void, (void)) \
|
||||
F(Dart_ExitScope, void, (void)) \
|
||||
/* Objects */ \
|
||||
F(Dart_IsNull, bool, (Dart_Handle))
|
||||
|
||||
#define DART_API_ALL_DL_SYMBOLS(F) \
|
||||
DART_NATIVE_API_DL_SYMBOLS(F) \
|
||||
DART_API_DL_SYMBOLS(F)
|
||||
// IMPORTANT! Never update these signatures without properly updating
|
||||
// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION.
|
||||
//
|
||||
// End of verbatim copy.
|
||||
// ============================================================================
|
||||
|
||||
// Copy of definition of DART_EXPORT without 'used' attribute.
|
||||
//
|
||||
// The 'used' attribute cannot be used with DART_API_ALL_DL_SYMBOLS because
|
||||
// they are not function declarations, but variable declarations with a
|
||||
// function pointer type.
|
||||
//
|
||||
// The function pointer variables are initialized with the addresses of the
|
||||
// functions in the VM. If we were to use function declarations instead, we
|
||||
// would need to forward the call to the VM adding indirection.
|
||||
#if defined(__CYGWIN__)
|
||||
#error Tool chain and platform not supported.
|
||||
#elif defined(_WIN32)
|
||||
#if defined(DART_SHARED_LIB)
|
||||
#define DART_EXPORT_DL DART_EXTERN_C __declspec(dllexport)
|
||||
#else
|
||||
#define DART_EXPORT_DL DART_EXTERN_C
|
||||
#endif
|
||||
#else
|
||||
#if __GNUC__ >= 4
|
||||
#if defined(DART_SHARED_LIB)
|
||||
#define DART_EXPORT_DL DART_EXTERN_C __attribute__((visibility("default")))
|
||||
#else
|
||||
#define DART_EXPORT_DL DART_EXTERN_C
|
||||
#endif
|
||||
#else
|
||||
#error Tool chain not supported.
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define DART_API_DL_DECLARATIONS(name, R, A) \
|
||||
typedef R(*name##_Type) A; \
|
||||
DART_EXPORT_DL name##_Type name##_DL;
|
||||
|
||||
DART_API_ALL_DL_SYMBOLS(DART_API_DL_DECLARATIONS)
|
||||
|
||||
#undef DART_API_DL_DECLARATIONS
|
||||
|
||||
#undef DART_EXPORT_DL
|
||||
|
||||
#endif /* RUNTIME_INCLUDE_DART_API_DL_H_ */ /* NOLINT */
|
||||
108
native/hub/src/bridge/bridge_engine/dart_api/dart_embedder_api.h
Normal file
108
native/hub/src/bridge/bridge_engine/dart_api/dart_embedder_api.h
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef RUNTIME_INCLUDE_DART_EMBEDDER_API_H_
|
||||
#define RUNTIME_INCLUDE_DART_EMBEDDER_API_H_
|
||||
|
||||
#include "include/dart_api.h"
|
||||
#include "include/dart_tools_api.h"
|
||||
|
||||
namespace dart {
|
||||
namespace embedder {
|
||||
|
||||
// Initialize all subsystems of the embedder.
|
||||
//
|
||||
// Must be called before the `Dart_Initialize()` call to initialize the
|
||||
// Dart VM.
|
||||
//
|
||||
// Returns true on success and false otherwise, in which case error would
|
||||
// contain error message.
|
||||
DART_WARN_UNUSED_RESULT bool InitOnce(char** error);
|
||||
|
||||
// Cleans up all subsystems of the embedder.
|
||||
//
|
||||
// Must be called after the `Dart_Cleanup()` call to initialize the
|
||||
// Dart VM.
|
||||
void Cleanup();
|
||||
|
||||
// Common arguments that are passed to isolate creation callback and to
|
||||
// API methods that create isolates.
|
||||
struct IsolateCreationData {
|
||||
// URI for the main script that will be running in the isolate.
|
||||
const char* script_uri;
|
||||
|
||||
// Advisory name of the main method that will be run by isolate.
|
||||
// Only used for error messages.
|
||||
const char* main;
|
||||
|
||||
// Isolate creation flags. Might be absent.
|
||||
Dart_IsolateFlags* flags;
|
||||
|
||||
// Isolate group callback data.
|
||||
void* isolate_group_data;
|
||||
|
||||
// Isolate callback data.
|
||||
void* isolate_data;
|
||||
};
|
||||
|
||||
// Create and initialize kernel-service isolate. This method should be used
|
||||
// when VM invokes isolate creation callback with DART_KERNEL_ISOLATE_NAME as
|
||||
// script_uri.
|
||||
// The isolate is created from the given snapshot (might be kernel data or
|
||||
// app-jit snapshot).
|
||||
DART_WARN_UNUSED_RESULT Dart_Isolate
|
||||
CreateKernelServiceIsolate(const IsolateCreationData& data,
|
||||
const uint8_t* buffer,
|
||||
intptr_t buffer_size,
|
||||
char** error);
|
||||
|
||||
// Service isolate configuration.
|
||||
struct VmServiceConfiguration {
|
||||
enum {
|
||||
kBindHttpServerToAFreePort = 0,
|
||||
kDoNotAutoStartHttpServer = -1
|
||||
};
|
||||
|
||||
// Address to which HTTP server will be bound.
|
||||
const char* ip;
|
||||
|
||||
// Default port. See enum above for special values.
|
||||
int port;
|
||||
|
||||
// If non-null, connection information for the VM service will be output to a
|
||||
// file in JSON format at the location specified.
|
||||
const char* write_service_info_filename;
|
||||
|
||||
// TODO(vegorov) document these ones.
|
||||
bool dev_mode;
|
||||
bool deterministic;
|
||||
bool disable_auth_codes;
|
||||
};
|
||||
|
||||
// Create and initialize vm-service isolate from the given AOT snapshot, which
|
||||
// is expected to contain all necessary 'vm-service' libraries.
|
||||
// This method should be used when VM invokes isolate creation callback with
|
||||
// DART_VM_SERVICE_ISOLATE_NAME as script_uri.
|
||||
DART_WARN_UNUSED_RESULT Dart_Isolate
|
||||
CreateVmServiceIsolate(const IsolateCreationData& data,
|
||||
const VmServiceConfiguration& config,
|
||||
const uint8_t* isolate_data,
|
||||
const uint8_t* isolate_instr,
|
||||
char** error);
|
||||
|
||||
// Create and initialize vm-service isolate from the given kernel binary, which
|
||||
// is expected to contain all necessary 'vm-service' libraries.
|
||||
// This method should be used when VM invokes isolate creation callback with
|
||||
// DART_VM_SERVICE_ISOLATE_NAME as script_uri.
|
||||
DART_WARN_UNUSED_RESULT Dart_Isolate
|
||||
CreateVmServiceIsolateFromKernel(const IsolateCreationData& data,
|
||||
const VmServiceConfiguration& config,
|
||||
const uint8_t* kernel_buffer,
|
||||
intptr_t kernel_buffer_size,
|
||||
char** error);
|
||||
|
||||
} // namespace embedder
|
||||
} // namespace dart
|
||||
|
||||
#endif // RUNTIME_INCLUDE_DART_EMBEDDER_API_H_
|
||||
207
native/hub/src/bridge/bridge_engine/dart_api/dart_native_api.h
Normal file
207
native/hub/src/bridge/bridge_engine/dart_api/dart_native_api.h
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
* for details. All rights reserved. Use of this source code is governed by a
|
||||
* BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_INCLUDE_DART_NATIVE_API_H_
|
||||
#define RUNTIME_INCLUDE_DART_NATIVE_API_H_
|
||||
|
||||
#include "dart_api.h" /* NOLINT */
|
||||
|
||||
/*
|
||||
* ==========================================
|
||||
* Message sending/receiving from native code
|
||||
* ==========================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Dart_CObject is used for representing Dart objects as native C
|
||||
* data outside the Dart heap. These objects are totally detached from
|
||||
* the Dart heap. Only a subset of the Dart objects have a
|
||||
* representation as a Dart_CObject.
|
||||
*
|
||||
* The string encoding in the 'value.as_string' is UTF-8.
|
||||
*
|
||||
* All the different types from dart:typed_data are exposed as type
|
||||
* kTypedData. The specific type from dart:typed_data is in the type
|
||||
* field of the as_typed_data structure. The length in the
|
||||
* as_typed_data structure is always in bytes.
|
||||
*
|
||||
* The data for kTypedData is copied on message send and ownership remains with
|
||||
* the caller. The ownership of data for kExternalTyped is passed to the VM on
|
||||
* message send and returned when the VM invokes the
|
||||
* Dart_HandleFinalizer callback; a non-NULL callback must be provided.
|
||||
*
|
||||
* Note that Dart_CObject_kNativePointer is intended for internal use by
|
||||
* dart:io implementation and has no connection to dart:ffi Pointer class.
|
||||
* It represents a pointer to a native resource of a known type.
|
||||
* The receiving side will only see this pointer as an integer and will not
|
||||
* see the specified finalizer.
|
||||
* The specified finalizer will only be invoked if the message is not delivered.
|
||||
*/
|
||||
typedef enum {
|
||||
Dart_CObject_kNull = 0,
|
||||
Dart_CObject_kBool,
|
||||
Dart_CObject_kInt32,
|
||||
Dart_CObject_kInt64,
|
||||
Dart_CObject_kDouble,
|
||||
Dart_CObject_kString,
|
||||
Dart_CObject_kArray,
|
||||
Dart_CObject_kTypedData,
|
||||
Dart_CObject_kExternalTypedData,
|
||||
Dart_CObject_kSendPort,
|
||||
Dart_CObject_kCapability,
|
||||
Dart_CObject_kNativePointer,
|
||||
Dart_CObject_kUnsupported,
|
||||
Dart_CObject_kUnmodifiableExternalTypedData,
|
||||
Dart_CObject_kNumberOfTypes
|
||||
} Dart_CObject_Type;
|
||||
// This enum is versioned by DART_API_DL_MAJOR_VERSION, only add at the end
|
||||
// and bump the DART_API_DL_MINOR_VERSION.
|
||||
|
||||
typedef struct _Dart_CObject {
|
||||
Dart_CObject_Type type;
|
||||
union {
|
||||
bool as_bool;
|
||||
int32_t as_int32;
|
||||
int64_t as_int64;
|
||||
double as_double;
|
||||
const char* as_string;
|
||||
struct {
|
||||
Dart_Port id;
|
||||
Dart_Port origin_id;
|
||||
} as_send_port;
|
||||
struct {
|
||||
int64_t id;
|
||||
} as_capability;
|
||||
struct {
|
||||
intptr_t length;
|
||||
struct _Dart_CObject** values;
|
||||
} as_array;
|
||||
struct {
|
||||
Dart_TypedData_Type type;
|
||||
intptr_t length; /* in elements, not bytes */
|
||||
const uint8_t* values;
|
||||
} as_typed_data;
|
||||
struct {
|
||||
Dart_TypedData_Type type;
|
||||
intptr_t length; /* in elements, not bytes */
|
||||
uint8_t* data;
|
||||
void* peer;
|
||||
Dart_HandleFinalizer callback;
|
||||
} as_external_typed_data;
|
||||
struct {
|
||||
intptr_t ptr;
|
||||
intptr_t size;
|
||||
Dart_HandleFinalizer callback;
|
||||
} as_native_pointer;
|
||||
} value;
|
||||
} Dart_CObject;
|
||||
// This struct is versioned by DART_API_DL_MAJOR_VERSION, bump the version when
|
||||
// changing this struct.
|
||||
|
||||
/**
|
||||
* Posts a message on some port. The message will contain the Dart_CObject
|
||||
* object graph rooted in 'message'.
|
||||
*
|
||||
* While the message is being sent the state of the graph of Dart_CObject
|
||||
* structures rooted in 'message' should not be accessed, as the message
|
||||
* generation will make temporary modifications to the data. When the message
|
||||
* has been sent the graph will be fully restored.
|
||||
*
|
||||
* If true is returned, the message was enqueued, and finalizers for external
|
||||
* typed data will eventually run, even if the receiving isolate shuts down
|
||||
* before processing the message. If false is returned, the message was not
|
||||
* enqueued and ownership of external typed data in the message remains with the
|
||||
* caller.
|
||||
*
|
||||
* This function may be called on any thread when the VM is running (that is,
|
||||
* after Dart_Initialize has returned and before Dart_Cleanup has been called).
|
||||
*
|
||||
* \param port_id The destination port.
|
||||
* \param message The message to send.
|
||||
*
|
||||
* \return True if the message was posted.
|
||||
*/
|
||||
DART_EXPORT bool Dart_PostCObject(Dart_Port port_id, Dart_CObject* message);
|
||||
|
||||
/**
|
||||
* Posts a message on some port. The message will contain the integer 'message'.
|
||||
*
|
||||
* \param port_id The destination port.
|
||||
* \param message The message to send.
|
||||
*
|
||||
* \return True if the message was posted.
|
||||
*/
|
||||
DART_EXPORT bool Dart_PostInteger(Dart_Port port_id, int64_t message);
|
||||
|
||||
/**
|
||||
* A native message handler.
|
||||
*
|
||||
* This handler is associated with a native port by calling
|
||||
* Dart_NewNativePort.
|
||||
*
|
||||
* The message received is decoded into the message structure. The
|
||||
* lifetime of the message data is controlled by the caller. All the
|
||||
* data references from the message are allocated by the caller and
|
||||
* will be reclaimed when returning to it.
|
||||
*/
|
||||
typedef void (*Dart_NativeMessageHandler)(Dart_Port dest_port_id,
|
||||
Dart_CObject* message);
|
||||
|
||||
/**
|
||||
* Creates a new native port. When messages are received on this
|
||||
* native port, then they will be dispatched to the provided native
|
||||
* message handler.
|
||||
*
|
||||
* \param name The name of this port in debugging messages.
|
||||
* \param handler The C handler to run when messages arrive on the port.
|
||||
* \param handle_concurrently Is it okay to process requests on this
|
||||
* native port concurrently?
|
||||
*
|
||||
* \return If successful, returns the port id for the native port. In
|
||||
* case of error, returns ILLEGAL_PORT.
|
||||
*/
|
||||
DART_EXPORT Dart_Port Dart_NewNativePort(const char* name,
|
||||
Dart_NativeMessageHandler handler,
|
||||
bool handle_concurrently);
|
||||
/* TODO(turnidge): Currently handle_concurrently is ignored. */
|
||||
|
||||
/**
|
||||
* Closes the native port with the given id.
|
||||
*
|
||||
* The port must have been allocated by a call to Dart_NewNativePort.
|
||||
*
|
||||
* \param native_port_id The id of the native port to close.
|
||||
*
|
||||
* \return Returns true if the port was closed successfully.
|
||||
*/
|
||||
DART_EXPORT bool Dart_CloseNativePort(Dart_Port native_port_id);
|
||||
|
||||
/*
|
||||
* ==================
|
||||
* Verification Tools
|
||||
* ==================
|
||||
*/
|
||||
|
||||
/**
|
||||
* Forces all loaded classes and functions to be compiled eagerly in
|
||||
* the current isolate..
|
||||
*
|
||||
* TODO(turnidge): Document.
|
||||
*/
|
||||
DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_CompileAll(void);
|
||||
|
||||
/**
|
||||
* Finalizes all classes.
|
||||
*/
|
||||
DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_FinalizeAllClasses(void);
|
||||
|
||||
/* This function is intentionally undocumented.
|
||||
*
|
||||
* It should not be used outside internal tests.
|
||||
*/
|
||||
DART_EXPORT void* Dart_ExecuteInternalCommand(const char* command, void* arg);
|
||||
|
||||
#endif /* INCLUDE_DART_NATIVE_API_H_ */ /* NOLINT */
|
||||
582
native/hub/src/bridge/bridge_engine/dart_api/dart_tools_api.h
Normal file
582
native/hub/src/bridge/bridge_engine/dart_api/dart_tools_api.h
Normal file
|
|
@ -0,0 +1,582 @@
|
|||
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef RUNTIME_INCLUDE_DART_TOOLS_API_H_
|
||||
#define RUNTIME_INCLUDE_DART_TOOLS_API_H_
|
||||
|
||||
#include "dart_api.h" /* NOLINT */
|
||||
|
||||
/** \mainpage Dart Tools Embedding API Reference
|
||||
*
|
||||
* This reference describes the Dart embedding API for tools. Tools include
|
||||
* a debugger, service protocol, and timeline.
|
||||
*
|
||||
* NOTE: The APIs described in this file are unstable and subject to change.
|
||||
*
|
||||
* This reference is generated from the header include/dart_tools_api.h.
|
||||
*/
|
||||
|
||||
/*
|
||||
* ========
|
||||
* Debugger
|
||||
* ========
|
||||
*/
|
||||
|
||||
/**
|
||||
* ILLEGAL_ISOLATE_ID is a number guaranteed never to be associated with a
|
||||
* valid isolate.
|
||||
*/
|
||||
#define ILLEGAL_ISOLATE_ID ILLEGAL_PORT
|
||||
|
||||
/**
|
||||
* ILLEGAL_ISOLATE_GROUP_ID is a number guaranteed never to be associated with a
|
||||
* valid isolate group.
|
||||
*/
|
||||
#define ILLEGAL_ISOLATE_GROUP_ID 0
|
||||
|
||||
/*
|
||||
* =======
|
||||
* Service
|
||||
* =======
|
||||
*/
|
||||
|
||||
/**
|
||||
* A service request callback function.
|
||||
*
|
||||
* These callbacks, registered by the embedder, are called when the VM receives
|
||||
* a service request it can't handle and the service request command name
|
||||
* matches one of the embedder registered handlers.
|
||||
*
|
||||
* The return value of the callback indicates whether the response
|
||||
* should be used as a regular result or an error result.
|
||||
* Specifically, if the callback returns true, a regular JSON-RPC
|
||||
* response is built in the following way:
|
||||
*
|
||||
* {
|
||||
* "jsonrpc": "2.0",
|
||||
* "result": <json_object>,
|
||||
* "id": <some sequence id>,
|
||||
* }
|
||||
*
|
||||
* If the callback returns false, a JSON-RPC error is built like this:
|
||||
*
|
||||
* {
|
||||
* "jsonrpc": "2.0",
|
||||
* "error": <json_object>,
|
||||
* "id": <some sequence id>,
|
||||
* }
|
||||
*
|
||||
* \param method The rpc method name.
|
||||
* \param param_keys Service requests can have key-value pair parameters. The
|
||||
* keys and values are flattened and stored in arrays.
|
||||
* \param param_values The values associated with the keys.
|
||||
* \param num_params The length of the param_keys and param_values arrays.
|
||||
* \param user_data The user_data pointer registered with this handler.
|
||||
* \param result A C string containing a valid JSON object. The returned
|
||||
* pointer will be freed by the VM by calling free.
|
||||
*
|
||||
* \return True if the result is a regular JSON-RPC response, false if the
|
||||
* result is a JSON-RPC error.
|
||||
*/
|
||||
typedef bool (*Dart_ServiceRequestCallback)(const char* method,
|
||||
const char** param_keys,
|
||||
const char** param_values,
|
||||
intptr_t num_params,
|
||||
void* user_data,
|
||||
const char** json_object);
|
||||
|
||||
/**
|
||||
* Register a Dart_ServiceRequestCallback to be called to handle
|
||||
* requests for the named rpc on a specific isolate. The callback will
|
||||
* be invoked with the current isolate set to the request target.
|
||||
*
|
||||
* \param method The name of the method that this callback is responsible for.
|
||||
* \param callback The callback to invoke.
|
||||
* \param user_data The user data passed to the callback.
|
||||
*
|
||||
* NOTE: If multiple callbacks with the same name are registered, only
|
||||
* the last callback registered will be remembered.
|
||||
*/
|
||||
DART_EXPORT void Dart_RegisterIsolateServiceRequestCallback(
|
||||
const char* method,
|
||||
Dart_ServiceRequestCallback callback,
|
||||
void* user_data);
|
||||
|
||||
/**
|
||||
* Register a Dart_ServiceRequestCallback to be called to handle
|
||||
* requests for the named rpc. The callback will be invoked without a
|
||||
* current isolate.
|
||||
*
|
||||
* \param method The name of the command that this callback is responsible for.
|
||||
* \param callback The callback to invoke.
|
||||
* \param user_data The user data passed to the callback.
|
||||
*
|
||||
* NOTE: If multiple callbacks with the same name are registered, only
|
||||
* the last callback registered will be remembered.
|
||||
*/
|
||||
DART_EXPORT void Dart_RegisterRootServiceRequestCallback(
|
||||
const char* method,
|
||||
Dart_ServiceRequestCallback callback,
|
||||
void* user_data);
|
||||
|
||||
/**
|
||||
* Embedder information which can be requested by the VM for internal or
|
||||
* reporting purposes.
|
||||
*
|
||||
* The pointers in this structure are not going to be cached or freed by the VM.
|
||||
*/
|
||||
|
||||
#define DART_EMBEDDER_INFORMATION_CURRENT_VERSION (0x00000001)
|
||||
|
||||
typedef struct {
|
||||
int32_t version;
|
||||
const char* name; // [optional] The name of the embedder
|
||||
int64_t current_rss; // [optional] the current RSS of the embedder
|
||||
int64_t max_rss; // [optional] the maximum RSS of the embedder
|
||||
} Dart_EmbedderInformation;
|
||||
|
||||
/**
|
||||
* Callback provided by the embedder that is used by the VM to request
|
||||
* information.
|
||||
*
|
||||
* \return Returns a pointer to a Dart_EmbedderInformation structure.
|
||||
* The embedder keeps the ownership of the structure and any field in it.
|
||||
* The embedder must ensure that the structure will remain valid until the
|
||||
* next invocation of the callback.
|
||||
*/
|
||||
typedef void (*Dart_EmbedderInformationCallback)(
|
||||
Dart_EmbedderInformation* info);
|
||||
|
||||
/**
|
||||
* Register a Dart_ServiceRequestCallback to be called to handle
|
||||
* requests for the named rpc. The callback will be invoked without a
|
||||
* current isolate.
|
||||
*
|
||||
* \param method The name of the command that this callback is responsible for.
|
||||
* \param callback The callback to invoke.
|
||||
* \param user_data The user data passed to the callback.
|
||||
*
|
||||
* NOTE: If multiple callbacks are registered, only the last callback registered
|
||||
* will be remembered.
|
||||
*/
|
||||
DART_EXPORT void Dart_SetEmbedderInformationCallback(
|
||||
Dart_EmbedderInformationCallback callback);
|
||||
|
||||
/**
|
||||
* Invoke a vm-service method and wait for its result.
|
||||
*
|
||||
* \param request_json The utf8-encoded json-rpc request.
|
||||
* \param request_json_length The length of the json-rpc request.
|
||||
*
|
||||
* \param response_json The returned utf8-encoded json response, must be
|
||||
* free()ed by caller.
|
||||
* \param response_json_length The length of the returned json response.
|
||||
* \param error An optional error, must be free()ed by caller.
|
||||
*
|
||||
* \return Whether the call was successfully performed.
|
||||
*
|
||||
* NOTE: This method does not need a current isolate and must not have the
|
||||
* vm-isolate being the current isolate. It must be called after
|
||||
* Dart_Initialize() and before Dart_Cleanup().
|
||||
*/
|
||||
DART_EXPORT bool Dart_InvokeVMServiceMethod(uint8_t* request_json,
|
||||
intptr_t request_json_length,
|
||||
uint8_t** response_json,
|
||||
intptr_t* response_json_length,
|
||||
char** error);
|
||||
|
||||
/*
|
||||
* ========
|
||||
* Event Streams
|
||||
* ========
|
||||
*/
|
||||
|
||||
/**
|
||||
* A callback invoked when the VM service gets a request to listen to
|
||||
* some stream.
|
||||
*
|
||||
* \return Returns true iff the embedder supports the named stream id.
|
||||
*/
|
||||
typedef bool (*Dart_ServiceStreamListenCallback)(const char* stream_id);
|
||||
|
||||
/**
|
||||
* A callback invoked when the VM service gets a request to cancel
|
||||
* some stream.
|
||||
*/
|
||||
typedef void (*Dart_ServiceStreamCancelCallback)(const char* stream_id);
|
||||
|
||||
/**
|
||||
* Adds VM service stream callbacks.
|
||||
*
|
||||
* \param listen_callback A function pointer to a listen callback function.
|
||||
* A listen callback function should not be already set when this function
|
||||
* is called. A NULL value removes the existing listen callback function
|
||||
* if any.
|
||||
*
|
||||
* \param cancel_callback A function pointer to a cancel callback function.
|
||||
* A cancel callback function should not be already set when this function
|
||||
* is called. A NULL value removes the existing cancel callback function
|
||||
* if any.
|
||||
*
|
||||
* \return Success if the callbacks were added. Otherwise, returns an
|
||||
* error handle.
|
||||
*/
|
||||
DART_EXPORT char* Dart_SetServiceStreamCallbacks(
|
||||
Dart_ServiceStreamListenCallback listen_callback,
|
||||
Dart_ServiceStreamCancelCallback cancel_callback);
|
||||
|
||||
/**
|
||||
* Sends a data event to clients of the VM Service.
|
||||
*
|
||||
* A data event is used to pass an array of bytes to subscribed VM
|
||||
* Service clients. For example, in the standalone embedder, this is
|
||||
* function used to provide WriteEvents on the Stdout and Stderr
|
||||
* streams.
|
||||
*
|
||||
* If the embedder passes in a stream id for which no client is
|
||||
* subscribed, then the event is ignored.
|
||||
*
|
||||
* \param stream_id The id of the stream on which to post the event.
|
||||
*
|
||||
* \param event_kind A string identifying what kind of event this is.
|
||||
* For example, 'WriteEvent'.
|
||||
*
|
||||
* \param bytes A pointer to an array of bytes.
|
||||
*
|
||||
* \param bytes_length The length of the byte array.
|
||||
*
|
||||
* \return NULL if the arguments are well formed. Otherwise, returns an
|
||||
* error string. The caller is responsible for freeing the error message.
|
||||
*/
|
||||
DART_EXPORT char* Dart_ServiceSendDataEvent(const char* stream_id,
|
||||
const char* event_kind,
|
||||
const uint8_t* bytes,
|
||||
intptr_t bytes_length);
|
||||
|
||||
/*
|
||||
* ========
|
||||
* Reload support
|
||||
* ========
|
||||
*
|
||||
* These functions are used to implement reloading in the Dart VM.
|
||||
* This is an experimental feature, so embedders should be prepared
|
||||
* for these functions to change.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A callback which determines whether the file at some url has been
|
||||
* modified since some time. If the file cannot be found, true should
|
||||
* be returned.
|
||||
*/
|
||||
typedef bool (*Dart_FileModifiedCallback)(const char* url, int64_t since);
|
||||
|
||||
DART_EXPORT char* Dart_SetFileModifiedCallback(
|
||||
Dart_FileModifiedCallback file_modified_callback);
|
||||
|
||||
/**
|
||||
* Returns true if isolate is currently reloading.
|
||||
*/
|
||||
DART_EXPORT bool Dart_IsReloading();
|
||||
|
||||
/*
|
||||
* ========
|
||||
* Timeline
|
||||
* ========
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enable tracking of specified timeline category. This is operational
|
||||
* only when systrace timeline functionality is turned on.
|
||||
*
|
||||
* \param categories A comma separated list of categories that need to
|
||||
* be enabled, the categories are
|
||||
* "all" : All categories
|
||||
* "API" - Execution of Dart C API functions
|
||||
* "Compiler" - Execution of Dart JIT compiler
|
||||
* "CompilerVerbose" - More detailed Execution of Dart JIT compiler
|
||||
* "Dart" - Execution of Dart code
|
||||
* "Debugger" - Execution of Dart debugger
|
||||
* "Embedder" - Execution of Dart embedder code
|
||||
* "GC" - Execution of Dart Garbage Collector
|
||||
* "Isolate" - Dart Isolate lifecycle execution
|
||||
* "VM" - Execution in Dart VM runtime code
|
||||
* "" - None
|
||||
*
|
||||
* When "all" is specified all the categories are enabled.
|
||||
* When a comma separated list of categories is specified, the categories
|
||||
* that are specified will be enabled and the rest will be disabled.
|
||||
* When "" is specified all the categories are disabled.
|
||||
* The category names are case sensitive.
|
||||
* eg: Dart_EnableTimelineCategory("all");
|
||||
* Dart_EnableTimelineCategory("GC,API,Isolate");
|
||||
* Dart_EnableTimelineCategory("GC,Debugger,Dart");
|
||||
*
|
||||
* \return True if the categories were successfully enabled, False otherwise.
|
||||
*/
|
||||
DART_EXPORT bool Dart_SetEnabledTimelineCategory(const char* categories);
|
||||
|
||||
/**
|
||||
* Returns a timestamp in microseconds. This timestamp is suitable for
|
||||
* passing into the timeline system, and uses the same monotonic clock
|
||||
* as dart:developer's Timeline.now.
|
||||
*
|
||||
* \return A timestamp that can be passed to the timeline system.
|
||||
*/
|
||||
DART_EXPORT int64_t Dart_TimelineGetMicros();
|
||||
|
||||
/**
|
||||
* Returns a raw timestamp in from the monotonic clock.
|
||||
*
|
||||
* \return A raw timestamp from the monotonic clock.
|
||||
*/
|
||||
DART_EXPORT int64_t Dart_TimelineGetTicks();
|
||||
|
||||
/**
|
||||
* Returns the frequency of the monotonic clock.
|
||||
*
|
||||
* \return The frequency of the monotonic clock.
|
||||
*/
|
||||
DART_EXPORT int64_t Dart_TimelineGetTicksFrequency();
|
||||
|
||||
typedef enum {
|
||||
Dart_Timeline_Event_Begin, // Phase = 'B'.
|
||||
Dart_Timeline_Event_End, // Phase = 'E'.
|
||||
Dart_Timeline_Event_Instant, // Phase = 'i'.
|
||||
Dart_Timeline_Event_Duration, // Phase = 'X'.
|
||||
Dart_Timeline_Event_Async_Begin, // Phase = 'b'.
|
||||
Dart_Timeline_Event_Async_End, // Phase = 'e'.
|
||||
Dart_Timeline_Event_Async_Instant, // Phase = 'n'.
|
||||
Dart_Timeline_Event_Counter, // Phase = 'C'.
|
||||
Dart_Timeline_Event_Flow_Begin, // Phase = 's'.
|
||||
Dart_Timeline_Event_Flow_Step, // Phase = 't'.
|
||||
Dart_Timeline_Event_Flow_End, // Phase = 'f'.
|
||||
} Dart_Timeline_Event_Type;
|
||||
|
||||
/**
|
||||
* Add a timeline event to the embedder stream.
|
||||
*
|
||||
* \param label The name of the event. Its lifetime must extend at least until
|
||||
* Dart_Cleanup.
|
||||
* \param timestamp0 The first timestamp of the event.
|
||||
* \param timestamp1_or_async_id The second timestamp of the event or
|
||||
* the async id.
|
||||
* \param argument_count The number of argument names and values.
|
||||
* \param argument_names An array of names of the arguments. The lifetime of the
|
||||
* names must extend at least until Dart_Cleanup. The array may be reclaimed
|
||||
* when this call returns.
|
||||
* \param argument_values An array of values of the arguments. The values and
|
||||
* the array may be reclaimed when this call returns.
|
||||
*/
|
||||
DART_EXPORT void Dart_TimelineEvent(const char* label,
|
||||
int64_t timestamp0,
|
||||
int64_t timestamp1_or_async_id,
|
||||
Dart_Timeline_Event_Type type,
|
||||
intptr_t argument_count,
|
||||
const char** argument_names,
|
||||
const char** argument_values);
|
||||
|
||||
/**
|
||||
* Associates a name with the current thread. This name will be used to name
|
||||
* threads in the timeline. Can only be called after a call to Dart_Initialize.
|
||||
*
|
||||
* \param name The name of the thread.
|
||||
*/
|
||||
DART_EXPORT void Dart_SetThreadName(const char* name);
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
const char* value;
|
||||
} Dart_TimelineRecorderEvent_Argument;
|
||||
|
||||
#define DART_TIMELINE_RECORDER_CURRENT_VERSION (0x00000001)
|
||||
|
||||
typedef struct {
|
||||
/* Set to DART_TIMELINE_RECORDER_CURRENT_VERSION */
|
||||
int32_t version;
|
||||
|
||||
/* The event's type / phase. */
|
||||
Dart_Timeline_Event_Type type;
|
||||
|
||||
/* The event's timestamp according to the same clock as
|
||||
* Dart_TimelineGetMicros. For a duration event, this is the beginning time.
|
||||
*/
|
||||
int64_t timestamp0;
|
||||
|
||||
/* For a duration event, this is the end time. For an async event, this is the
|
||||
* async id. */
|
||||
int64_t timestamp1_or_async_id;
|
||||
|
||||
/* The current isolate of the event, as if by Dart_GetMainPortId, or
|
||||
* ILLEGAL_PORT if the event had no current isolate. */
|
||||
Dart_Port isolate;
|
||||
|
||||
/* The current isolate group of the event, as if by
|
||||
* Dart_CurrentIsolateGroupId, or ILLEGAL_PORT if the event had no current
|
||||
* isolate group. */
|
||||
Dart_IsolateGroupId isolate_group;
|
||||
|
||||
/* The name / label of the event. */
|
||||
const char* label;
|
||||
|
||||
/* The stream / category of the event. */
|
||||
const char* stream;
|
||||
|
||||
intptr_t argument_count;
|
||||
Dart_TimelineRecorderEvent_Argument* arguments;
|
||||
} Dart_TimelineRecorderEvent;
|
||||
|
||||
/**
|
||||
* Callback provided by the embedder to handle the completion of timeline
|
||||
* events.
|
||||
*
|
||||
* \param event A timeline event that has just been completed. The VM keeps
|
||||
* ownership of the event and any field in it (i.e., the embedder should copy
|
||||
* any values it needs after the callback returns).
|
||||
*/
|
||||
typedef void (*Dart_TimelineRecorderCallback)(
|
||||
Dart_TimelineRecorderEvent* event);
|
||||
|
||||
/**
|
||||
* Register a `Dart_TimelineRecorderCallback` to be called as timeline events
|
||||
* are completed.
|
||||
*
|
||||
* The callback will be invoked without a current isolate.
|
||||
*
|
||||
* The callback will be invoked on the thread completing the event. Because
|
||||
* `Dart_TimelineEvent` may be called by any thread, the callback may be called
|
||||
* on any thread.
|
||||
*
|
||||
* The callback may be invoked at any time after `Dart_Initialize` is called and
|
||||
* before `Dart_Cleanup` returns.
|
||||
*
|
||||
* If multiple callbacks are registered, only the last callback registered
|
||||
* will be remembered. Providing a NULL callback will clear the registration
|
||||
* (i.e., a NULL callback produced a no-op instead of a crash).
|
||||
*
|
||||
* Setting a callback is insufficient to receive events through the callback. The
|
||||
* VM flag `timeline_recorder` must also be set to `callback`.
|
||||
*/
|
||||
DART_EXPORT void Dart_SetTimelineRecorderCallback(
|
||||
Dart_TimelineRecorderCallback callback);
|
||||
|
||||
/*
|
||||
* =======
|
||||
* Metrics
|
||||
* =======
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return metrics gathered for the VM and individual isolates.
|
||||
*/
|
||||
DART_EXPORT int64_t
|
||||
Dart_IsolateGroupHeapOldUsedMetric(Dart_IsolateGroup group); // Byte
|
||||
DART_EXPORT int64_t
|
||||
Dart_IsolateGroupHeapOldCapacityMetric(Dart_IsolateGroup group); // Byte
|
||||
DART_EXPORT int64_t
|
||||
Dart_IsolateGroupHeapOldExternalMetric(Dart_IsolateGroup group); // Byte
|
||||
DART_EXPORT int64_t
|
||||
Dart_IsolateGroupHeapNewUsedMetric(Dart_IsolateGroup group); // Byte
|
||||
DART_EXPORT int64_t
|
||||
Dart_IsolateGroupHeapNewCapacityMetric(Dart_IsolateGroup group); // Byte
|
||||
DART_EXPORT int64_t
|
||||
Dart_IsolateGroupHeapNewExternalMetric(Dart_IsolateGroup group); // Byte
|
||||
|
||||
/*
|
||||
* ========
|
||||
* UserTags
|
||||
* ========
|
||||
*/
|
||||
|
||||
/*
|
||||
* Gets the current isolate's currently set UserTag instance.
|
||||
*
|
||||
* \return The currently set UserTag instance.
|
||||
*/
|
||||
DART_EXPORT Dart_Handle Dart_GetCurrentUserTag();
|
||||
|
||||
/*
|
||||
* Gets the current isolate's default UserTag instance.
|
||||
*
|
||||
* \return The default UserTag with label 'Default'
|
||||
*/
|
||||
DART_EXPORT Dart_Handle Dart_GetDefaultUserTag();
|
||||
|
||||
/*
|
||||
* Creates a new UserTag instance.
|
||||
*
|
||||
* \param label The name of the new UserTag.
|
||||
*
|
||||
* \return The newly created UserTag instance or an error handle.
|
||||
*/
|
||||
DART_EXPORT Dart_Handle Dart_NewUserTag(const char* label);
|
||||
|
||||
/*
|
||||
* Updates the current isolate's UserTag to a new value.
|
||||
*
|
||||
* \param user_tag The UserTag to be set as the current UserTag.
|
||||
*
|
||||
* \return The previously set UserTag instance or an error handle.
|
||||
*/
|
||||
DART_EXPORT Dart_Handle Dart_SetCurrentUserTag(Dart_Handle user_tag);
|
||||
|
||||
/*
|
||||
* Returns the label of a given UserTag instance.
|
||||
*
|
||||
* \param user_tag The UserTag from which the label will be retrieved.
|
||||
*
|
||||
* \return The UserTag's label. NULL if the user_tag is invalid. The caller is
|
||||
* responsible for freeing the returned label.
|
||||
*/
|
||||
DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_GetUserTagLabel(
|
||||
Dart_Handle user_tag);
|
||||
|
||||
/*
|
||||
* =======
|
||||
* Heap Snapshot
|
||||
* =======
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback provided by the caller of `Dart_WriteHeapSnapshot` which is
|
||||
* used to write out chunks of the requested heap snapshot.
|
||||
*
|
||||
* \param context An opaque context which was passed to `Dart_WriteHeapSnapshot`
|
||||
* together with this callback.
|
||||
*
|
||||
* \param buffer Pointer to the buffer containing a chunk of the snapshot.
|
||||
* The callback owns the buffer and needs to `free` it.
|
||||
*
|
||||
* \param size Number of bytes in the `buffer` to be written.
|
||||
*
|
||||
* \param is_last Set to `true` for the last chunk. The callback will not
|
||||
* be invoked again after it was invoked once with `is_last` set to `true`.
|
||||
*/
|
||||
typedef void (*Dart_HeapSnapshotWriteChunkCallback)(void* context,
|
||||
uint8_t* buffer,
|
||||
intptr_t size,
|
||||
bool is_last);
|
||||
|
||||
/**
|
||||
* Generate heap snapshot of the current isolate group and stream it into the
|
||||
* given `callback`. VM would produce snapshot in chunks and send these chunks
|
||||
* one by one back to the embedder by invoking the provided `callback`.
|
||||
*
|
||||
* This API enables embedder to stream snapshot into a file or socket without
|
||||
* allocating a buffer to hold the whole snapshot in memory.
|
||||
*
|
||||
* The isolate group will be paused for the duration of this operation.
|
||||
*
|
||||
* \param write Callback used to write chunks of the heap snapshot.
|
||||
*
|
||||
* \param context Opaque context which would be passed on each invocation of
|
||||
* `write` callback.
|
||||
*
|
||||
* \returns `nullptr` if the operation is successful otherwise error message.
|
||||
* Caller owns error message string and needs to `free` it.
|
||||
*/
|
||||
DART_EXPORT char* Dart_WriteHeapSnapshot(
|
||||
Dart_HeapSnapshotWriteChunkCallback write,
|
||||
void* context);
|
||||
|
||||
#endif // RUNTIME_INCLUDE_DART_TOOLS_API_H_
|
||||
16
native/hub/src/bridge/bridge_engine/dart_api/dart_version.h
Normal file
16
native/hub/src/bridge/bridge_engine/dart_api/dart_version.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
* for details. All rights reserved. Use of this source code is governed by a
|
||||
* BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_INCLUDE_DART_VERSION_H_
|
||||
#define RUNTIME_INCLUDE_DART_VERSION_H_
|
||||
|
||||
// On breaking changes the major version is increased.
|
||||
// On backwards compatible changes the minor version is increased.
|
||||
// The versioning covers the symbols exposed in dart_api_dl.h
|
||||
#define DART_API_DL_MAJOR_VERSION 2
|
||||
#define DART_API_DL_MINOR_VERSION 2
|
||||
|
||||
#endif /* RUNTIME_INCLUDE_DART_VERSION_H_ */ /* NOLINT */
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
* for details. All rights reserved. Use of this source code is governed by a
|
||||
* BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_
|
||||
#define RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
void (*function)(void);
|
||||
} DartApiEntry;
|
||||
|
||||
typedef struct {
|
||||
const int major;
|
||||
const int minor;
|
||||
const DartApiEntry* const functions;
|
||||
} DartApi;
|
||||
|
||||
#endif /* RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ */ /* NOLINT */
|
||||
104
native/hub/src/bridge/bridge_engine/ffi/io.rs
Normal file
104
native/hub/src/bridge/bridge_engine/ffi/io.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use crate::bridge::bridge_engine::Channel;
|
||||
|
||||
pub use super::DartAbi;
|
||||
pub use super::MessagePort;
|
||||
pub use allo_isolate::*;
|
||||
use dart_sys::Dart_DeletePersistentHandle_DL;
|
||||
use dart_sys::Dart_Handle;
|
||||
use dart_sys::Dart_HandleFromPersistent_DL;
|
||||
use dart_sys::Dart_InitializeApiDL;
|
||||
use dart_sys::Dart_NewPersistentHandle_DL;
|
||||
use dart_sys::Dart_PersistentHandle;
|
||||
use libc::c_void;
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should never be called manually.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn new_dart_opaque(handle: Dart_Handle) -> usize {
|
||||
Dart_NewPersistentHandle_DL.expect("dart_api_dl has not been initialized")(handle) as _
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should never be called manually.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn get_dart_object(ptr: usize) -> Dart_Handle {
|
||||
let handle = ptr as _;
|
||||
let res = Dart_HandleFromPersistent_DL.expect("dart_api_dl has not been initialized")(handle);
|
||||
Dart_DeletePersistentHandle_DL.expect("dart_api_dl has not been initialized")(handle);
|
||||
res
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should never be called manually.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn drop_dart_object(ptr: usize) {
|
||||
Dart_DeletePersistentHandle_DL.expect("dart_api_dl has not been initialized")(ptr as _);
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should never be called manually.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn init_frb_dart_api_dl(data: *mut c_void) -> isize {
|
||||
Dart_InitializeApiDL(data)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Option for correct drop.
|
||||
pub struct DartHandleWrap(Option<Dart_PersistentHandle>);
|
||||
|
||||
impl DartHandleWrap {
|
||||
pub fn from_raw(ptr: Dart_PersistentHandle) -> Self {
|
||||
Self(Some(ptr))
|
||||
}
|
||||
|
||||
pub fn into_raw(mut self) -> Dart_PersistentHandle {
|
||||
self.0.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DartHandleWrap> for Dart_PersistentHandle {
|
||||
fn from(warp: DartHandleWrap) -> Self {
|
||||
warp.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DartHandleWrap {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.0 {
|
||||
unsafe {
|
||||
Dart_DeletePersistentHandle_DL.expect("dart_api_dl has not been initialized")(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DartOpaqueBase {
|
||||
inner: DartHandleWrap,
|
||||
drop_port: Option<MessagePort>,
|
||||
}
|
||||
|
||||
impl DartOpaqueBase {
|
||||
pub fn new(handle: Dart_PersistentHandle, drop_port: Option<MessagePort>) -> Self {
|
||||
Self {
|
||||
inner: DartHandleWrap::from_raw(handle),
|
||||
drop_port,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_raw(self) -> Dart_PersistentHandle {
|
||||
self.inner.into_raw()
|
||||
}
|
||||
|
||||
pub fn unwrap(self) -> DartHandleWrap {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub fn channel(&self) -> Option<Channel> {
|
||||
Some(Channel::new(self.drop_port?))
|
||||
}
|
||||
}
|
||||
262
native/hub/src/bridge/bridge_engine/ffi/mod.rs
Normal file
262
native/hub/src/bridge/bridge_engine/ffi/mod.rs
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
#[cfg(target_family = "wasm")]
|
||||
pub type DartAbi = wasm_bindgen::JsValue;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub type DartAbi = allo_isolate::ffi::DartCObject;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use dart_sys::Dart_PersistentHandle;
|
||||
|
||||
use std::{mem, ops, sync::Arc, thread::ThreadId};
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub use allo_isolate::IntoDart;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub type MessagePort = web::PortLike;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub type MessagePort = i64;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub type OpaqueMessagePort = wasm_bindgen::JsValue;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub type OpaqueMessagePort = i64;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub type DartWrapObject = wasm_bindgen::JsValue;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub type DartWrapObject = DartHandleWrap;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub type DartObject = wasm_bindgen::JsValue;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub type DartObject = Dart_PersistentHandle;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub mod web;
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub use web::*;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub type Channel = allo_isolate::Isolate;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod io;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub use io::*;
|
||||
|
||||
use crate::bridge::bridge_engine::DartSafe;
|
||||
|
||||
/// A wrapper to transfer ownership of T to Dart.
|
||||
///
|
||||
/// This type is equivalent to an [`Option<Arc<T>>`]. The inner pointer may
|
||||
/// be None if a nullptr is received from Dart, signifying that this pointer
|
||||
/// has been disposed.
|
||||
///
|
||||
/// Extensions for [`sync::RwLock`] and [`sync::Mutex`] are provided.
|
||||
///
|
||||
/// ## Naming the inner type
|
||||
///
|
||||
/// When an `RustOpaque<T>` is transformed into a Dart type, T's string
|
||||
/// representation undergoes some transformations to become a valid Dart type:
|
||||
/// - Rust keywords (dyn, 'static, DartSafe, etc.) are automatically removed.
|
||||
/// - ASCII alphanumerics are kept, all other characters are ignored.
|
||||
///
|
||||
/// ## Trait objects
|
||||
///
|
||||
/// Trait objects may be put behind opaque pointers, but they must implement
|
||||
/// [`DartSafe`] to be safely sent to Dart. For example, this declaration can
|
||||
/// be used across the FFI border:
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::*;
|
||||
/// use std::fmt::Debug;
|
||||
/// use std::panic::{UnwindSafe, RefUnwindSafe};
|
||||
///
|
||||
/// // Rust does not allow multiple non-auto traits in trait objects, so this
|
||||
/// // is one workaround.
|
||||
/// pub trait DartDebug: DartSafe + Debug {}
|
||||
///
|
||||
/// impl<T: DartSafe + Debug> DartDebug for T {}
|
||||
///
|
||||
/// pub struct DebugWrapper(pub RustOpaque<Box<dyn DartDebug>>);
|
||||
///
|
||||
/// // creating a DebugWrapper using the opaque_dyn macro
|
||||
/// let wrap = DebugWrapper(opaque_dyn!("foobar"));
|
||||
/// // it's possible to name it directly
|
||||
/// pub struct DebugWrapper2(pub RustOpaque<Box<dyn Debug + Send + Sync + UnwindSafe + RefUnwindSafe>>);
|
||||
/// ```
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug)]
|
||||
pub struct RustOpaque<T: ?Sized + DartSafe> {
|
||||
ptr: Option<Arc<T>>,
|
||||
}
|
||||
|
||||
impl<T: DartSafe> RustOpaque<T> {
|
||||
pub fn try_unwrap(self) -> Result<T, Self> {
|
||||
if let Some(ptr) = self.ptr {
|
||||
Arc::try_unwrap(ptr).map_err(RustOpaque::from)
|
||||
} else {
|
||||
panic!("Use after free.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + DartSafe> Clone for RustOpaque<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
ptr: self.ptr.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should never be called manually.
|
||||
/// Retrieving an opaque pointer from Dart is an implementation detail, so this
|
||||
/// function is not guaranteed to be API-stable.
|
||||
pub unsafe fn opaque_from_dart<T: DartSafe>(ptr: *const T) -> RustOpaque<T> {
|
||||
// The raw pointer is the same one created from Arc::into_raw,
|
||||
// owned and artificially incremented by Dart.
|
||||
RustOpaque {
|
||||
ptr: (!ptr.is_null()).then(|| Arc::from_raw(ptr)),
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + DartSafe> ops::Deref for RustOpaque<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
if let Some(ptr) = &self.ptr {
|
||||
ptr.as_ref()
|
||||
} else {
|
||||
panic!("Use after free.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + DartSafe> From<Arc<T>> for RustOpaque<T> {
|
||||
fn from(ptr: Arc<T>) -> Self {
|
||||
Self { ptr: Some(ptr) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DartSafe> RustOpaque<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
ptr: Some(Arc::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DartSafe> From<RustOpaque<T>> for DartAbi {
|
||||
fn from(value: RustOpaque<T>) -> Self {
|
||||
let ptr = if let Some(ptr) = value.ptr {
|
||||
Arc::into_raw(ptr)
|
||||
} else {
|
||||
std::ptr::null()
|
||||
};
|
||||
let size = mem::size_of::<T>();
|
||||
|
||||
vec![ptr.into_dart(), size.into_dart()].into_dart()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DartOpaque {
|
||||
/// Dart object
|
||||
handle: Option<DartOpaqueBase>,
|
||||
|
||||
/// The ID of the thread on which the Dart Object was created.
|
||||
thread_id: ThreadId,
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// The implementation checks the current thread
|
||||
/// and delegates it to the Dart thread when it is drops.
|
||||
unsafe impl Send for DartOpaque {}
|
||||
unsafe impl Sync for DartOpaque {}
|
||||
|
||||
impl DartOpaque {
|
||||
/// Creates a new [DartOpaque].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [DartObject] must be created on the current thread.
|
||||
pub unsafe fn new(handle: DartObject, port: OpaqueMessagePort) -> Self {
|
||||
Self {
|
||||
handle: Some(DartOpaqueBase::new(handle, Some(port))),
|
||||
thread_id: std::thread::current().id(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [DartOpaque] for sending to dart.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [DartObject] must be created on the current thread.
|
||||
///
|
||||
/// The [DartOpaque] created by this method must not be dropped
|
||||
/// on a non-parent [DartObject] thread.
|
||||
pub unsafe fn new_non_droppable(handle: DartObject) -> Self {
|
||||
Self {
|
||||
handle: Some(DartOpaqueBase::new(handle, None)),
|
||||
thread_id: std::thread::current().id(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to get a Dart [DartObject].
|
||||
/// Returns the [DartObject] if the [DartOpaque] was created on the current thread.
|
||||
pub fn try_unwrap(mut self) -> Result<DartWrapObject, Self> {
|
||||
if std::thread::current().id() == self.thread_id {
|
||||
Ok(self.handle.take().unwrap().unwrap())
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DartOpaque> for DartAbi {
|
||||
fn from(mut data: DartOpaque) -> Self {
|
||||
data.handle.take().unwrap().into_raw().into_dart()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DartOpaque {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.handle.take() {
|
||||
if std::thread::current().id() != self.thread_id {
|
||||
if let Some(channel) = inner.channel() {
|
||||
let ptr = inner.into_raw();
|
||||
|
||||
if !channel.post(ptr) {
|
||||
println!("Drop DartOpaque after closing the port.");
|
||||
};
|
||||
} else {
|
||||
println!("Drop non droppable DartOpaque.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro helper to instantiate an `RustOpaque<dyn Trait>`, as Rust does not
|
||||
/// support custom DSTs on stable.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use std::fmt::Debug;
|
||||
/// use crate::*;
|
||||
///
|
||||
/// pub trait MyDebug: DartSafe + Debug {}
|
||||
///
|
||||
/// impl<T: DartSafe + Debug> MyDebug for T {}
|
||||
///
|
||||
/// let opaque: RustOpaque<Box<dyn MyDebug>> = opaque_dyn!("foobar");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! opaque_dyn {
|
||||
($ex:expr) => {
|
||||
$crate::RustOpaque::new(::std::boxed::Box::new($ex))
|
||||
};
|
||||
}
|
||||
409
native/hub/src/bridge/bridge_engine/ffi/web.rs
Normal file
409
native/hub/src/bridge/bridge_engine/ffi/web.rs
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
use std::iter::FromIterator;
|
||||
|
||||
use super::DartAbi;
|
||||
use super::MessagePort;
|
||||
use crate::bridge::bridge_engine::support;
|
||||
pub use crate::bridge::bridge_engine::wasm_bindgen_src::transfer::*;
|
||||
use crate::bridge::bridge_engine::DartOpaque;
|
||||
use crate::bridge::bridge_engine::DartSafe;
|
||||
use crate::bridge::bridge_engine::RustOpaque;
|
||||
pub use js_sys;
|
||||
pub use js_sys::Array as JsArray;
|
||||
use js_sys::*;
|
||||
pub use wasm_bindgen;
|
||||
pub use wasm_bindgen::closure::Closure;
|
||||
pub use wasm_bindgen::prelude::*;
|
||||
pub use wasm_bindgen::JsCast;
|
||||
use web_sys::BroadcastChannel;
|
||||
|
||||
pub use crate::bridge::bridge_engine::wasm_bindgen_src::transfer::*;
|
||||
pub trait IntoDart {
|
||||
fn into_dart(self) -> DartAbi;
|
||||
}
|
||||
|
||||
pub trait IntoDartExceptPrimitive: IntoDart {}
|
||||
impl IntoDartExceptPrimitive for JsValue {}
|
||||
impl<T: DartSafe> IntoDartExceptPrimitive for RustOpaque<T> {}
|
||||
impl IntoDartExceptPrimitive for DartOpaque {}
|
||||
impl IntoDartExceptPrimitive for String {}
|
||||
impl<T: IntoDart> IntoDartExceptPrimitive for Option<T> {}
|
||||
|
||||
impl IntoDart for () {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
JsValue::undefined()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! delegate {
|
||||
($( $ty:ty )*) => {$(
|
||||
impl IntoDart for $ty {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
DartAbi::from(self)
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
macro_rules! delegate_buffer {
|
||||
($( $ty:ty => $buffer:ty )*) => {$(
|
||||
impl IntoDart for $ty {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
<$buffer>::from(self.as_slice()).into()
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
// Orphan rules disallow blanket implementations, so we have to manually delegate here.
|
||||
delegate! {
|
||||
bool
|
||||
i8 u8 i16 u16 i32 u32 i64 u64 i128 u128 isize usize
|
||||
f32 f64
|
||||
&str String JsValue
|
||||
}
|
||||
delegate_buffer! {
|
||||
Vec<i8> => js_sys::Int8Array
|
||||
Vec<u8> => js_sys::Uint8Array
|
||||
Vec<i16> => js_sys::Int16Array
|
||||
Vec<u16> => js_sys::Uint16Array
|
||||
Vec<i32> => js_sys::Int32Array
|
||||
Vec<u32> => js_sys::Uint32Array
|
||||
Vec<f32> => js_sys::Float32Array
|
||||
Vec<f64> => js_sys::Float64Array
|
||||
ZeroCopyBuffer<Vec<i8>> => js_sys::Int8Array
|
||||
ZeroCopyBuffer<Vec<u8>> => js_sys::Uint8Array
|
||||
ZeroCopyBuffer<Vec<i16>> => js_sys::Int16Array
|
||||
ZeroCopyBuffer<Vec<u16>> => js_sys::Uint16Array
|
||||
ZeroCopyBuffer<Vec<i32>> => js_sys::Int32Array
|
||||
ZeroCopyBuffer<Vec<u32>> => js_sys::Uint32Array
|
||||
ZeroCopyBuffer<Vec<f32>> => js_sys::Float32Array
|
||||
ZeroCopyBuffer<Vec<f64>> => js_sys::Float64Array
|
||||
}
|
||||
|
||||
impl<T: IntoDartExceptPrimitive> IntoDart for Vec<T> {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
Array::from_iter(self.into_iter().map(IntoDart::into_dart)).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoDart> IntoDart for Option<T> {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
self.map(T::into_dart).unwrap_or_else(JsValue::null)
|
||||
}
|
||||
}
|
||||
impl<T> IntoDart for *const T {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
(self as usize).into_dart()
|
||||
}
|
||||
}
|
||||
impl<T> IntoDart for *mut T {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
(self as usize).into_dart()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DartSafe> IntoDart for RustOpaque<T> {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDart for DartOpaque {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: IntoDartExceptPrimitive> IntoDart for [T; N] {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
let boxed: Box<[T]> = Box::new(self);
|
||||
boxed.into_vec().into_dart()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_dart_for_primitive {
|
||||
($($prim:ty)*) => {$(
|
||||
impl<const N: usize> IntoDart for [$prim; N] {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
Vec::from(self).into_dart()
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
impl_into_dart_for_primitive!(i8 u8 i16 u16 i32 u32 f32 f64);
|
||||
|
||||
macro_rules! delegate_big_buffers {
|
||||
($($buf:ty => $buffer:ty)*) => {$(
|
||||
impl IntoDart for $buf {
|
||||
fn into_dart(self) -> DartAbi {
|
||||
let buf: &[i32] = bytemuck::cast_slice(&self[..]);
|
||||
let buf = Int32Array::from(buf);
|
||||
<$buffer>::new(&buf.buffer()).into()
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
delegate_big_buffers! {
|
||||
Vec<i64> => BigInt64Array
|
||||
Vec<u64> => BigUint64Array
|
||||
}
|
||||
|
||||
macro_rules! impl_into_dart_for_tuple {
|
||||
($( ($($T:ident)+) )*) => {$(
|
||||
impl<$($T: IntoDart),+> IntoDart for ($($T),+,) {
|
||||
#[allow(non_snake_case)]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
let ($($T),+,) = self;
|
||||
vec![$($T.into_dart()),+].into_dart()
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($T: IntoDart),+> IntoDartExceptPrimitive for ($($T),+,) {}
|
||||
)*};
|
||||
}
|
||||
|
||||
impl_into_dart_for_tuple! {
|
||||
(A)
|
||||
(A B)
|
||||
(A B C)
|
||||
(A B C D)
|
||||
(A B C D E)
|
||||
(A B C D E F)
|
||||
(A B C D E F G)
|
||||
(A B C D E F G H)
|
||||
(A B C D E F G H I)
|
||||
(A B C D E F G H I J)
|
||||
}
|
||||
|
||||
impl IntoDart for ZeroCopyBuffer<Vec<i64>> {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
self.0.into_dart()
|
||||
}
|
||||
}
|
||||
impl IntoDart for ZeroCopyBuffer<Vec<u64>> {
|
||||
#[inline]
|
||||
fn into_dart(self) -> DartAbi {
|
||||
self.0.into_dart()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Channel {
|
||||
port: MessagePort,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn new(port: MessagePort) -> Self {
|
||||
Self { port }
|
||||
}
|
||||
pub fn post(&self, msg: impl IntoDart) -> bool {
|
||||
self.port
|
||||
.post_message(&msg.into_dart())
|
||||
.map_err(|err| {
|
||||
crate::console_error!("post: {:?}", err);
|
||||
})
|
||||
.is_ok()
|
||||
}
|
||||
pub(crate) fn broadcast_name(&self) -> Option<String> {
|
||||
self.port
|
||||
.dyn_ref::<BroadcastChannel>()
|
||||
.map(|channel| channel.name())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "error")]
|
||||
pub fn error(msg: &str);
|
||||
}
|
||||
|
||||
type RawClosure<T> = Box<dyn FnOnce(&[T]) + Send + 'static>;
|
||||
|
||||
pub struct TransferClosure<T> {
|
||||
pub(crate) data: Vec<T>,
|
||||
pub(crate) transfer: Vec<T>,
|
||||
pub(crate) closure: RawClosure<T>,
|
||||
}
|
||||
|
||||
pub struct TransferClosurePayload<T> {
|
||||
pub(crate) func: RawClosure<T>,
|
||||
}
|
||||
|
||||
impl TransferClosure<JsValue> {
|
||||
pub fn new(
|
||||
data: Vec<JsValue>,
|
||||
transfer: Vec<JsValue>,
|
||||
closure: impl FnOnce(&[JsValue]) + Send + 'static,
|
||||
) -> Self {
|
||||
let closure = Box::new(closure);
|
||||
Self {
|
||||
data,
|
||||
transfer,
|
||||
closure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ZeroCopyBuffer<T>(pub T);
|
||||
|
||||
impl<T> ZeroCopyBuffer<Vec<T>> {
|
||||
#[inline]
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal implementations for transferables on WASM platforms.
|
||||
pub trait Transfer {
|
||||
/// Recover the self value from a [JsValue].
|
||||
fn deserialize(value: &JsValue) -> Self;
|
||||
/// Transform the self value into a [JsValue].
|
||||
fn serialize(self) -> JsValue;
|
||||
/// Extract items that are valid to be passed as the "transfer" argument.
|
||||
fn transferables(&self) -> Vec<JsValue>;
|
||||
}
|
||||
|
||||
impl<T: Transfer> Transfer for Option<T> {
|
||||
fn deserialize(value: &JsValue) -> Self {
|
||||
(!value.is_undefined() && !value.is_null()).then(|| T::deserialize(value))
|
||||
}
|
||||
fn serialize(self) -> JsValue {
|
||||
self.map(T::serialize).unwrap_or_default()
|
||||
}
|
||||
fn transferables(&self) -> Vec<JsValue> {
|
||||
self.as_ref().map(T::transferables).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Transfer for PortLike {
|
||||
fn deserialize(value: &JsValue) -> Self {
|
||||
if let Some(name) = value.as_string() {
|
||||
BroadcastChannel::new(&name).unwrap().unchecked_into()
|
||||
} else if value.dyn_ref::<web_sys::MessagePort>().is_some() {
|
||||
value.unchecked_ref::<Self>().clone()
|
||||
} else {
|
||||
panic!("Not a PortLike: {:?}", value)
|
||||
}
|
||||
}
|
||||
fn serialize(self) -> JsValue {
|
||||
if let Some(channel) = self.dyn_ref::<BroadcastChannel>() {
|
||||
channel.name().into()
|
||||
} else {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
fn transferables(&self) -> Vec<JsValue> {
|
||||
if let Some(port) = self.dyn_ref::<web_sys::MessagePort>() {
|
||||
vec![port.clone().into()]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transfer for ArrayBuffer {
|
||||
fn deserialize(value: &JsValue) -> Self {
|
||||
value.dyn_ref().cloned().unwrap()
|
||||
}
|
||||
fn serialize(self) -> JsValue {
|
||||
self.into()
|
||||
}
|
||||
fn transferables(&self) -> Vec<JsValue> {
|
||||
vec![self.into()]
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
/// Objects implementing the interface of [`web_sys::MessagePort`].
|
||||
///
|
||||
/// Attempts to coerce [`JsValue`]s into this interface using [`dyn_into`][JsCast::dyn_into]
|
||||
/// or [`dyn_ref`][JsCast::dyn_ref] will fail at runtime.
|
||||
#[derive(Clone)]
|
||||
pub type PortLike;
|
||||
#[wasm_bindgen(method, catch, js_name = "postMessage")]
|
||||
pub fn post_message(this: &PortLike, value: &JsValue) -> Result<(), JsValue>;
|
||||
#[wasm_bindgen(method, catch)]
|
||||
pub fn close(this: &PortLike) -> Result<(), JsValue>;
|
||||
}
|
||||
|
||||
impl PortLike {
|
||||
/// Create a [`BroadcastChannel`] with the specified name.
|
||||
pub fn broadcast(name: &str) -> Self {
|
||||
BroadcastChannel::new(name)
|
||||
.expect("Failed to create broadcast channel")
|
||||
.unchecked_into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Copied from https://github.com/chemicstry/wasm_thread/blob/main/src/script_path.js
|
||||
pub fn script_path() -> Option<String> {
|
||||
js_sys::eval(
|
||||
r"
|
||||
(() => {
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
let parts = e.stack.match(/(?:\(|@)(\S+):\d+:\d+/);
|
||||
return parts[1];
|
||||
}
|
||||
})()",
|
||||
)
|
||||
.ok()?
|
||||
.as_string()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// TODO: need doc
|
||||
#[wasm_bindgen]
|
||||
pub unsafe fn get_dart_object(ptr: usize) -> JsValue {
|
||||
*support::box_from_leak_ptr(ptr as _)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// TODO: need doc
|
||||
#[wasm_bindgen]
|
||||
pub unsafe fn drop_dart_object(ptr: usize) {
|
||||
drop(support::box_from_leak_ptr::<JsValue>(ptr as _));
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DartOpaqueBase {
|
||||
inner: Box<JsValue>,
|
||||
drop_port: Option<String>,
|
||||
}
|
||||
|
||||
impl DartOpaqueBase {
|
||||
pub fn new(handle: JsValue, port: Option<JsValue>) -> Self {
|
||||
Self {
|
||||
inner: Box::new(handle),
|
||||
drop_port: port.map(|p| p.dyn_ref::<BroadcastChannel>().unwrap().name()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap(self) -> JsValue {
|
||||
*self.inner
|
||||
}
|
||||
|
||||
pub fn into_raw(self) -> *mut JsValue {
|
||||
Box::into_raw(self.inner)
|
||||
}
|
||||
|
||||
pub fn channel(&self) -> Option<Channel> {
|
||||
Some(Channel::new(PortLike::broadcast(self.drop_port.as_ref()?)))
|
||||
}
|
||||
}
|
||||
316
native/hub/src/bridge/bridge_engine/handler.rs
Normal file
316
native/hub/src/bridge/bridge_engine/handler.rs
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
//! Wrappers and executors for Rust functions.
|
||||
|
||||
use std::any::Any;
|
||||
use std::panic;
|
||||
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use crate::bridge::bridge_engine::ffi::{IntoDart, MessagePort};
|
||||
|
||||
use crate::bridge::bridge_engine::rust2dart::{IntoIntoDart, Rust2Dart, TaskCallback};
|
||||
use crate::bridge::bridge_engine::support::WireSyncReturn;
|
||||
use crate::bridge::bridge_engine::SyncReturn;
|
||||
use crate::spawn_bridge_task;
|
||||
|
||||
/// The types of return values for a particular Rust function.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum FfiCallMode {
|
||||
/// The default mode, returns a Dart `Future<T>`.
|
||||
Normal,
|
||||
/// Used by `SyncReturn<T>` to skip spawning workers.
|
||||
Sync,
|
||||
/// Returns a Dart `Stream<T>`.
|
||||
Stream,
|
||||
}
|
||||
|
||||
/// Supporting information to identify a function's operating mode.
|
||||
#[derive(Clone)]
|
||||
pub struct WrapInfo {
|
||||
/// A Dart `SendPort`. [None] if the mode is [FfiCallMode::Sync].
|
||||
pub port: Option<MessagePort>,
|
||||
/// Usually the name of the function.
|
||||
pub debug_name: &'static str,
|
||||
/// The call mode of this function.
|
||||
pub mode: FfiCallMode,
|
||||
}
|
||||
/// Provide your own handler to customize how to execute your function calls, etc.
|
||||
pub trait Handler {
|
||||
/// Prepares the arguments, executes a Rust function and sets up its return value.
|
||||
///
|
||||
/// Why separate `PrepareFn` and `TaskFn`: because some things cannot be [`Send`] (e.g. raw
|
||||
/// pointers), so those can be done in `PrepareFn`, while the real work is done in `TaskFn` with [`Send`].
|
||||
///
|
||||
/// The generated code depends on the fact that `PrepareFn` is synchronous to maintain
|
||||
/// correctness, therefore implementors of [`Handler`] must also uphold this property.
|
||||
///
|
||||
/// If a Rust function returns [`SyncReturn`], it must be called with
|
||||
/// [`wrap_sync`](Handler::wrap_sync) instead.
|
||||
fn wrap<PrepareFn, TaskFn, TaskRet, D>(&self, wrap_info: WrapInfo, prepare: PrepareFn)
|
||||
where
|
||||
PrepareFn: FnOnce() -> TaskFn + UnwindSafe,
|
||||
TaskFn: FnOnce(TaskCallback) -> Result<TaskRet, BridgeError> + Send + UnwindSafe + 'static,
|
||||
TaskRet: IntoIntoDart<D>,
|
||||
D: IntoDart;
|
||||
|
||||
/// Same as [`wrap`][Handler::wrap], but the Rust function must return a [SyncReturn] and
|
||||
/// need not implement [Send].
|
||||
fn wrap_sync<SyncTaskFn, TaskRet>(
|
||||
&self,
|
||||
wrap_info: WrapInfo,
|
||||
sync_task: SyncTaskFn,
|
||||
) -> WireSyncReturn
|
||||
where
|
||||
SyncTaskFn: FnOnce() -> Result<SyncReturn<TaskRet>, BridgeError> + UnwindSafe,
|
||||
TaskRet: IntoDart;
|
||||
}
|
||||
|
||||
/// The simple handler uses a simple thread pool to execute tasks.
|
||||
pub struct SimpleHandler<E: Executor, EH: ErrorHandler> {
|
||||
executor: E,
|
||||
error_handler: EH,
|
||||
}
|
||||
|
||||
impl<E: Executor, H: ErrorHandler> SimpleHandler<E, H> {
|
||||
/// Create a new default handler.
|
||||
pub fn new(executor: E, error_handler: H) -> Self {
|
||||
SimpleHandler {
|
||||
executor,
|
||||
error_handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The default handler used by the generated code.
|
||||
pub type DefaultHandler =
|
||||
SimpleHandler<BridgeTaskExecutor<ReportDartErrorHandler>, ReportDartErrorHandler>;
|
||||
|
||||
impl Default for DefaultHandler {
|
||||
fn default() -> Self {
|
||||
Self::new(
|
||||
BridgeTaskExecutor::new(ReportDartErrorHandler),
|
||||
ReportDartErrorHandler,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Executor, EH: ErrorHandler> Handler for SimpleHandler<E, EH> {
|
||||
fn wrap<PrepareFn, TaskFn, TaskRet, D>(&self, wrap_info: WrapInfo, prepare: PrepareFn)
|
||||
where
|
||||
PrepareFn: FnOnce() -> TaskFn + UnwindSafe,
|
||||
TaskFn: FnOnce(TaskCallback) -> Result<TaskRet, BridgeError> + Send + UnwindSafe + 'static,
|
||||
TaskRet: IntoIntoDart<D>,
|
||||
D: IntoDart,
|
||||
{
|
||||
// NOTE This extra [catch_unwind] **SHOULD** be put outside **ALL** code!
|
||||
// Why do this: As nomicon says, unwind across languages is undefined behavior (UB).
|
||||
// Therefore, we should wrap a [catch_unwind] outside of *each and every* line of code
|
||||
// that can cause panic. Otherwise we may touch UB.
|
||||
// Why do not report error or something like that if this outer [catch_unwind] really
|
||||
// catches something: Because if we report error, that line of code itself can cause panic
|
||||
// as well. Then that new panic will go across language boundary and cause UB.
|
||||
// ref https://doc.rust-lang.org/nomicon/unwinding.html
|
||||
let _ = panic::catch_unwind(move || {
|
||||
let wrap_info2 = wrap_info.clone();
|
||||
if let Err(error) = panic::catch_unwind(move || {
|
||||
let task = prepare();
|
||||
self.executor.execute(wrap_info2, task);
|
||||
}) {
|
||||
self.error_handler
|
||||
.handle_error(wrap_info.port.unwrap(), BridgeError::Panic(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn wrap_sync<SyncTaskFn, TaskRet>(
|
||||
&self,
|
||||
wrap_info: WrapInfo,
|
||||
sync_task: SyncTaskFn,
|
||||
) -> WireSyncReturn
|
||||
where
|
||||
TaskRet: IntoDart,
|
||||
SyncTaskFn: FnOnce() -> Result<SyncReturn<TaskRet>, BridgeError> + UnwindSafe,
|
||||
{
|
||||
// NOTE This extra [catch_unwind] **SHOULD** be put outside **ALL** code!
|
||||
// For reason, see comments in [wrap]
|
||||
panic::catch_unwind(move || {
|
||||
let catch_unwind_result = panic::catch_unwind(move || {
|
||||
match self.executor.execute_sync(wrap_info, sync_task) {
|
||||
Ok(data) => wire_sync_from_data(data.0, true),
|
||||
Err(_err) => self
|
||||
.error_handler
|
||||
.handle_error_sync(BridgeError::ResultError),
|
||||
}
|
||||
});
|
||||
catch_unwind_result.unwrap_or_else(|error| {
|
||||
self.error_handler
|
||||
.handle_error_sync(BridgeError::Panic(error))
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|_| wire_sync_from_data(None::<()>, false))
|
||||
}
|
||||
}
|
||||
|
||||
/// An executor model for Rust functions.
|
||||
///
|
||||
/// For example, the default model is [ThreadPoolExecutor]
|
||||
/// which runs each function in a separate thread.
|
||||
pub trait Executor: RefUnwindSafe {
|
||||
/// Executes a Rust function and transforms its return value into a Dart-compatible
|
||||
/// value, i.e. types that implement [`IntoDart`].
|
||||
fn execute<TaskFn, TaskRet, D>(&self, wrap_info: WrapInfo, task: TaskFn)
|
||||
where
|
||||
TaskFn: FnOnce(TaskCallback) -> Result<TaskRet, BridgeError> + Send + UnwindSafe + 'static,
|
||||
TaskRet: IntoIntoDart<D>,
|
||||
D: IntoDart;
|
||||
|
||||
/// Executes a Rust function that returns a [SyncReturn].
|
||||
fn execute_sync<SyncTaskFn, TaskRet>(
|
||||
&self,
|
||||
wrap_info: WrapInfo,
|
||||
sync_task: SyncTaskFn,
|
||||
) -> Result<SyncReturn<TaskRet>, BridgeError>
|
||||
where
|
||||
SyncTaskFn: FnOnce() -> Result<SyncReturn<TaskRet>, BridgeError> + UnwindSafe,
|
||||
TaskRet: IntoDart;
|
||||
}
|
||||
|
||||
/// The default executor used.
|
||||
pub struct BridgeTaskExecutor<EH: ErrorHandler> {
|
||||
error_handler: EH,
|
||||
}
|
||||
|
||||
impl<EH: ErrorHandler> BridgeTaskExecutor<EH> {
|
||||
pub fn new(error_handler: EH) -> Self {
|
||||
BridgeTaskExecutor { error_handler }
|
||||
}
|
||||
}
|
||||
|
||||
impl<EH: ErrorHandler> Executor for BridgeTaskExecutor<EH> {
|
||||
fn execute<TaskFn, TaskRet, D>(&self, wrap_info: WrapInfo, task: TaskFn)
|
||||
where
|
||||
TaskFn: FnOnce(TaskCallback) -> Result<TaskRet, BridgeError> + Send + UnwindSafe + 'static,
|
||||
TaskRet: IntoIntoDart<D>,
|
||||
D: IntoDart,
|
||||
{
|
||||
let eh = self.error_handler;
|
||||
let eh2 = self.error_handler;
|
||||
|
||||
let WrapInfo { port, mode, .. } = wrap_info;
|
||||
|
||||
spawn_bridge_task!(|port: Option<MessagePort>| {
|
||||
let port2 = port.as_ref().cloned();
|
||||
let thread_result = panic::catch_unwind(move || {
|
||||
let port2 = port2.expect("(worker) thread");
|
||||
#[allow(clippy::clone_on_copy)]
|
||||
let rust2dart = Rust2Dart::new(port2.clone());
|
||||
|
||||
let ret = task(TaskCallback::new(rust2dart.clone()))
|
||||
.map(|e| e.into_into_dart().into_dart());
|
||||
|
||||
match ret {
|
||||
Ok(result) => {
|
||||
match mode {
|
||||
FfiCallMode::Normal => {
|
||||
rust2dart.success(result);
|
||||
}
|
||||
FfiCallMode::Stream => {
|
||||
// nothing - ignore the return value of a Stream-typed function
|
||||
}
|
||||
FfiCallMode::Sync => {
|
||||
panic!("FfiCallMode::Sync should not call execute, please call execute_sync instead")
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_error) => {
|
||||
eh2.handle_error(port2, BridgeError::ResultError);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if let Err(error) = thread_result {
|
||||
eh.handle_error(port.expect("(worker) eh"), BridgeError::Panic(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn execute_sync<SyncTaskFn, TaskRet>(
|
||||
&self,
|
||||
_wrap_info: WrapInfo,
|
||||
sync_task: SyncTaskFn,
|
||||
) -> Result<SyncReturn<TaskRet>, BridgeError>
|
||||
where
|
||||
SyncTaskFn: FnOnce() -> Result<SyncReturn<TaskRet>, BridgeError> + UnwindSafe,
|
||||
TaskRet: IntoDart,
|
||||
{
|
||||
sync_task()
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that occur from normal code execution.
|
||||
#[derive(Debug)]
|
||||
pub enum BridgeError {
|
||||
ResultError,
|
||||
/// Exceptional errors from panicking.
|
||||
Panic(Box<dyn Any + Send>),
|
||||
}
|
||||
|
||||
impl BridgeError {
|
||||
/// The identifier of the type of error.
|
||||
pub fn code(&self) -> &'static str {
|
||||
match self {
|
||||
BridgeError::ResultError => "RESULT_ERROR",
|
||||
BridgeError::Panic(_) => "PANIC_ERROR",
|
||||
}
|
||||
}
|
||||
|
||||
/// The message of the error.
|
||||
pub fn message(&self) -> String {
|
||||
match self {
|
||||
BridgeError::ResultError => "There was a result error inside the bridge".into(),
|
||||
BridgeError::Panic(panic_err) => match panic_err.downcast_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match panic_err.downcast_ref::<String>() {
|
||||
Some(s) => &s[..],
|
||||
None => "Box<dyn Any>",
|
||||
},
|
||||
}
|
||||
.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handler model that sends back the error to a Dart `SendPort`.
|
||||
///
|
||||
/// For example, instead of using the default [`ReportDartErrorHandler`],
|
||||
/// you could implement your own handler that logs each error to stderr,
|
||||
/// or to an external logging service.
|
||||
pub trait ErrorHandler: UnwindSafe + RefUnwindSafe + Copy + Send + 'static {
|
||||
/// The default error handler.
|
||||
fn handle_error(&self, port: MessagePort, error: BridgeError);
|
||||
|
||||
/// Special handler only used for synchronous code.
|
||||
fn handle_error_sync(&self, error: BridgeError) -> WireSyncReturn;
|
||||
}
|
||||
|
||||
/// The default error handler used by generated code.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ReportDartErrorHandler;
|
||||
|
||||
impl ErrorHandler for ReportDartErrorHandler {
|
||||
fn handle_error(&self, port: MessagePort, error: BridgeError) {
|
||||
Rust2Dart::new(port).error(error.code().to_string(), error.message());
|
||||
}
|
||||
|
||||
fn handle_error_sync(&self, error: BridgeError) -> WireSyncReturn {
|
||||
wire_sync_from_data(format!("{}: {}", error.code(), error.message()), false)
|
||||
}
|
||||
}
|
||||
|
||||
fn wire_sync_from_data<T: IntoDart>(data: T, success: bool) -> WireSyncReturn {
|
||||
let sync_return = vec![data.into_dart(), success.into_dart()].into_dart();
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
return crate::bridge::bridge_engine::support::new_leak_box_ptr(sync_return);
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
return sync_return;
|
||||
}
|
||||
179
native/hub/src/bridge/bridge_engine/into_into_dart.rs
Normal file
179
native/hub/src/bridge/bridge_engine/into_into_dart.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
use crate::bridge::bridge_engine::{ffi::*, DartSafe};
|
||||
|
||||
/// Basically the Into trait.
|
||||
/// We need this separate trait because we need to implement it for Vec<T> etc.
|
||||
/// These blanket implementations allow us to accept external types in various places.
|
||||
/// The initial reason for this was to allow mirrored types in StreamSink<>.
|
||||
pub trait IntoIntoDart<D: IntoDart> {
|
||||
fn into_into_dart(self) -> D;
|
||||
}
|
||||
|
||||
impl<T, D> IntoIntoDart<Vec<D>> for Vec<T>
|
||||
where
|
||||
T: IntoIntoDart<D>,
|
||||
Vec<D>: IntoDart,
|
||||
D: IntoDart,
|
||||
{
|
||||
fn into_into_dart(self) -> Vec<D> {
|
||||
self.into_iter().map(|e| e.into_into_dart()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> IntoIntoDart<Option<D>> for Option<T>
|
||||
where
|
||||
T: IntoIntoDart<D>,
|
||||
D: IntoDart,
|
||||
{
|
||||
fn into_into_dart(self) -> Option<D> {
|
||||
self.map(|e| e.into_into_dart())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoIntoDart<RustOpaque<T>> for RustOpaque<T>
|
||||
where
|
||||
T: DartSafe,
|
||||
{
|
||||
fn into_into_dart(self) -> RustOpaque<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> IntoIntoDart<ZeroCopyBuffer<D>> for ZeroCopyBuffer<T>
|
||||
where
|
||||
T: IntoIntoDart<D>,
|
||||
D: IntoDart,
|
||||
ZeroCopyBuffer<D>: IntoDart,
|
||||
{
|
||||
fn into_into_dart(self) -> ZeroCopyBuffer<D> {
|
||||
ZeroCopyBuffer(self.0.into_into_dart())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const C: usize> IntoIntoDart<[T; C]> for [T; C]
|
||||
where
|
||||
T: IntoDart,
|
||||
[T; C]: IntoDart,
|
||||
{
|
||||
fn into_into_dart(self) -> [T; C] {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoIntoDart<T> for Box<T>
|
||||
where
|
||||
T: IntoDart,
|
||||
{
|
||||
fn into_into_dart(self) -> T {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
// These tuple impls should probably be a macro,
|
||||
// but that is not easily possible with macro_rules because of the field access.
|
||||
impl<A, AD, B, BD> IntoIntoDart<(AD, BD)> for (A, B)
|
||||
where
|
||||
A: IntoIntoDart<AD>,
|
||||
AD: IntoDart,
|
||||
B: IntoIntoDart<BD>,
|
||||
BD: IntoDart,
|
||||
{
|
||||
fn into_into_dart(self) -> (AD, BD) {
|
||||
(self.0.into_into_dart(), self.1.into_into_dart())
|
||||
}
|
||||
}
|
||||
impl<A, AD, B, BD, C, CD> IntoIntoDart<(AD, BD, CD)> for (A, B, C)
|
||||
where
|
||||
A: IntoIntoDart<AD>,
|
||||
AD: IntoDart,
|
||||
B: IntoIntoDart<BD>,
|
||||
BD: IntoDart,
|
||||
C: IntoIntoDart<CD>,
|
||||
CD: IntoDart,
|
||||
{
|
||||
fn into_into_dart(self) -> (AD, BD, CD) {
|
||||
(
|
||||
self.0.into_into_dart(),
|
||||
self.1.into_into_dart(),
|
||||
self.2.into_into_dart(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<A, AD, B, BD, C, CD, D, DD> IntoIntoDart<(AD, BD, CD, DD)> for (A, B, C, D)
|
||||
where
|
||||
A: IntoIntoDart<AD>,
|
||||
AD: IntoDart,
|
||||
B: IntoIntoDart<BD>,
|
||||
BD: IntoDart,
|
||||
C: IntoIntoDart<CD>,
|
||||
CD: IntoDart,
|
||||
D: IntoIntoDart<DD>,
|
||||
DD: IntoDart,
|
||||
{
|
||||
fn into_into_dart(self) -> (AD, BD, CD, DD) {
|
||||
(
|
||||
self.0.into_into_dart(),
|
||||
self.1.into_into_dart(),
|
||||
self.2.into_into_dart(),
|
||||
self.3.into_into_dart(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<A, AD, B, BD, C, CD, D, DD, E, ED> IntoIntoDart<(AD, BD, CD, DD, ED)> for (A, B, C, D, E)
|
||||
where
|
||||
A: IntoIntoDart<AD>,
|
||||
AD: IntoDart,
|
||||
B: IntoIntoDart<BD>,
|
||||
BD: IntoDart,
|
||||
C: IntoIntoDart<CD>,
|
||||
CD: IntoDart,
|
||||
D: IntoIntoDart<DD>,
|
||||
DD: IntoDart,
|
||||
E: IntoIntoDart<ED>,
|
||||
ED: IntoDart,
|
||||
{
|
||||
fn into_into_dart(self) -> (AD, BD, CD, DD, ED) {
|
||||
(
|
||||
self.0.into_into_dart(),
|
||||
self.1.into_into_dart(),
|
||||
self.2.into_into_dart(),
|
||||
self.3.into_into_dart(),
|
||||
self.4.into_into_dart(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// more generic impls do not work because they crate possibly conflicting trait impls
|
||||
// this is why here are some more specific impls
|
||||
|
||||
// Implementations for simple types
|
||||
macro_rules! impl_into_into_dart {
|
||||
($t:ty) => {
|
||||
impl IntoIntoDart<$t> for $t {
|
||||
fn into_into_dart(self) -> $t {
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Impls for primitive types are taken from the IntoDart trait
|
||||
|
||||
impl_into_into_dart!(u8);
|
||||
impl_into_into_dart!(i8);
|
||||
impl_into_into_dart!(u16);
|
||||
impl_into_into_dart!(i16);
|
||||
impl_into_into_dart!(u32);
|
||||
impl_into_into_dart!(i32);
|
||||
impl_into_into_dart!(u64);
|
||||
impl_into_into_dart!(i64);
|
||||
impl_into_into_dart!(f32);
|
||||
impl_into_into_dart!(f64);
|
||||
impl_into_into_dart!(bool);
|
||||
impl_into_into_dart!(());
|
||||
impl_into_into_dart!(usize);
|
||||
impl_into_into_dart!(String);
|
||||
impl_into_into_dart!(DartOpaque);
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
impl_into_into_dart!(allo_isolate::ffi::DartCObject);
|
||||
#[cfg(target_family = "wasm")]
|
||||
impl_into_into_dart!(wasm_bindgen::JsValue);
|
||||
73
native/hub/src/bridge/bridge_engine/macros.rs
Normal file
73
native/hub/src/bridge/bridge_engine/macros.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#[macro_export]
|
||||
macro_rules! spawn_bridge_task {
|
||||
($($tt:tt)*) => {{
|
||||
let bridge_task = $crate::transfer!($($tt)*);
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{
|
||||
bridge_task();
|
||||
}
|
||||
#[cfg(target_family = "wasm")]
|
||||
{
|
||||
$crate::bridge::bridge_engine::wasm_bindgen_src::worker::WEB_WORKER.with(|worker| {
|
||||
let _ = bridge_task.apply(worker);
|
||||
});
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// On WASM, [JsValue][wasm_bindgen::JsValue]s cannot be shared between scopes
|
||||
/// but instead can be ["transferred"].
|
||||
/// Rust however is not aware of transferables and therefore cannot capture these values.
|
||||
/// This macro wraps a closure and returns a [TransferClosure][crate::ffi::TransferClosure]
|
||||
/// on WASM platforms which will capture these special values,
|
||||
/// or a normal [FnOnce] on other platforms.
|
||||
/// Note that the parameter names must match available variables/bindings from the outer scope.
|
||||
///
|
||||
/// ["transferred"]: https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
||||
#[macro_export]
|
||||
macro_rules! transfer {
|
||||
(|| $block:block) => {{
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{ move || $block }
|
||||
#[cfg(target_family = "wasm")]
|
||||
{
|
||||
$crate::ffi::TransferClosure::new(vec![], vec![], move |_: &[JsValue]| $block)
|
||||
}
|
||||
}};
|
||||
(|$($param:ident: $ty:ty),*| $block:block) => {{
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{
|
||||
move || $block
|
||||
}
|
||||
#[cfg(target_family = "wasm")]
|
||||
{
|
||||
use wasm_bindgen::JsValue;
|
||||
use $crate::bridge::bridge_engine::ffi::Transfer;
|
||||
#[allow(unused_variables)]
|
||||
let worker = move |transfer: &[JsValue]| {
|
||||
let idx = 0;
|
||||
$(
|
||||
let $param = <$ty>::deserialize(&transfer[idx]);
|
||||
let idx = idx + 1;
|
||||
)*
|
||||
$block
|
||||
};
|
||||
let transferables = [$($param.transferables()),*].concat();
|
||||
$crate::bridge::bridge_engine::ffi::TransferClosure::new(
|
||||
vec![$($param.serialize()),*],
|
||||
transferables,
|
||||
worker,
|
||||
)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! console_error {
|
||||
($lit:literal) => {
|
||||
$crate::error($lit)
|
||||
};
|
||||
($($tt:tt)*) => {
|
||||
$crate::bridge::bridge_engine::error(&format!($($tt)*))
|
||||
};
|
||||
}
|
||||
24
native/hub/src/bridge/bridge_engine/mod.rs
Normal file
24
native/hub/src/bridge/bridge_engine/mod.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
pub use handler::{FfiCallMode, Handler, WrapInfo};
|
||||
pub use rust2dart::StreamSink;
|
||||
pub mod ffi;
|
||||
pub use ffi::*;
|
||||
pub mod handler;
|
||||
mod into_into_dart;
|
||||
mod macros;
|
||||
pub mod rust2dart;
|
||||
pub mod support;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub mod wasm_bindgen_src;
|
||||
|
||||
/// Use this struct in return type of your function, in order to tell the code generator
|
||||
/// the function should return synchronously. Otherwise, it is by default asynchronously.
|
||||
pub struct SyncReturn<T>(pub T);
|
||||
|
||||
/// Marker trait for types that are safe to share with Dart and can be dropped
|
||||
/// safely in case of a panic.
|
||||
pub trait DartSafe: UnwindSafe + RefUnwindSafe {}
|
||||
|
||||
impl<T: UnwindSafe + RefUnwindSafe> DartSafe for T {}
|
||||
150
native/hub/src/bridge/bridge_engine/rust2dart.rs
Normal file
150
native/hub/src/bridge/bridge_engine/rust2dart.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
//! Manages receiving and sending values across the FFI boundary.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// The representation of a Dart object outside of the Dart heap.
|
||||
///
|
||||
/// Its implementation lies with the Dart language and therefore should not be
|
||||
/// depended on to be stable.
|
||||
pub use crate::bridge::bridge_engine::ffi::*;
|
||||
pub use crate::bridge::bridge_engine::into_into_dart::IntoIntoDart;
|
||||
|
||||
/// A wrapper around a Dart [`Isolate`].
|
||||
#[derive(Clone)]
|
||||
pub struct Rust2Dart {
|
||||
pub(crate) channel: Channel,
|
||||
}
|
||||
|
||||
const RUST2DART_ACTION_SUCCESS: i32 = 0;
|
||||
const RUST2DART_ACTION_ERROR: i32 = 1;
|
||||
const RUST2DART_ACTION_CLOSE_STREAM: i32 = 2;
|
||||
|
||||
// API signatures is similar to Flutter Android's callback
|
||||
// https://api.flutter.dev/javadoc/io/flutter/plugin/common/MethodChannel.Result.html
|
||||
impl Rust2Dart {
|
||||
/// Create a new wrapper from a raw port.
|
||||
pub fn new(port: MessagePort) -> Self {
|
||||
Rust2Dart {
|
||||
channel: Channel::new(port),
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a success message back to the specified port.
|
||||
pub fn success(&self, result: impl IntoDart) -> bool {
|
||||
self.channel.post(vec![
|
||||
RUST2DART_ACTION_SUCCESS.into_dart(),
|
||||
result.into_dart(),
|
||||
])
|
||||
}
|
||||
|
||||
/// Send an error back to the specified port.
|
||||
pub fn error(&self, error_code: String, error_message: String) -> bool {
|
||||
self.error_full(error_code, error_message, ())
|
||||
}
|
||||
|
||||
/// Send a detailed error back to the specified port.
|
||||
pub fn error_full(
|
||||
&self,
|
||||
error_code: String,
|
||||
error_message: String,
|
||||
error_details: impl IntoDart,
|
||||
) -> bool {
|
||||
self.channel.post(vec![
|
||||
RUST2DART_ACTION_ERROR.into_dart(),
|
||||
error_code.into_dart(),
|
||||
error_message.into_dart(),
|
||||
error_details.into_dart(),
|
||||
])
|
||||
}
|
||||
|
||||
/// Close the stream and ignore further messages.
|
||||
pub fn close_stream(&self) -> bool {
|
||||
self.channel
|
||||
.post(vec![RUST2DART_ACTION_CLOSE_STREAM.into_dart()])
|
||||
}
|
||||
}
|
||||
|
||||
/// A callback that receives the return value of Rust functions.
|
||||
pub struct TaskCallback {
|
||||
rust2dart: Rust2Dart,
|
||||
}
|
||||
|
||||
impl TaskCallback {
|
||||
/// Create a new callback from a port wrapper.
|
||||
pub fn new(rust2dart: Rust2Dart) -> Self {
|
||||
Self { rust2dart }
|
||||
}
|
||||
|
||||
/// Create a new [StreamSink] of the specified type.
|
||||
pub fn stream_sink<T, D>(&self) -> StreamSink<T>
|
||||
where
|
||||
T: IntoIntoDart<D>,
|
||||
D: IntoDart,
|
||||
{
|
||||
StreamSink::new(self.rust2dart.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a [`web_sys::BroadcastChannel`].
|
||||
#[derive(Clone)]
|
||||
pub struct ChannelHandle(pub String);
|
||||
|
||||
impl ChannelHandle {
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub fn port(&self) -> MessagePort {
|
||||
PortLike::broadcast(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A sink to send asynchronous data back to Dart.
|
||||
/// Represented as a Dart
|
||||
/// [`Stream`](https://api.dart.dev/stable/dart-async/Stream-class.html).
|
||||
#[derive(Clone)]
|
||||
pub struct StreamSink<T> {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
rust2dart: Rust2Dart,
|
||||
#[cfg(target_family = "wasm")]
|
||||
handle: ChannelHandle,
|
||||
_phantom_data: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> StreamSink<T> {
|
||||
/// Create a new sink from a port wrapper.
|
||||
pub fn new(rust2dart: Rust2Dart) -> Self {
|
||||
#[cfg(target_family = "wasm")]
|
||||
let name = rust2dart
|
||||
.channel
|
||||
.broadcast_name()
|
||||
.expect("Not a BroadcastChannel");
|
||||
Self {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
rust2dart,
|
||||
#[cfg(target_family = "wasm")]
|
||||
handle: ChannelHandle(name),
|
||||
_phantom_data: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn rust2dart(&self) -> Rust2Dart {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
return self.rust2dart.clone();
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
Rust2Dart::new(self.handle.port())
|
||||
}
|
||||
|
||||
/// Add data to the stream. Returns false when data could not be sent,
|
||||
/// or the stream has been closed.
|
||||
pub fn add<D: IntoDart>(&self, value: T) -> bool
|
||||
where
|
||||
T: IntoIntoDart<D>,
|
||||
{
|
||||
self.rust2dart().success(value.into_into_dart().into_dart())
|
||||
}
|
||||
|
||||
/// Close the stream and ignore further messages. Returns false when
|
||||
/// the stream could not be closed, or when it has already been closed.
|
||||
pub fn close(&self) -> bool {
|
||||
self.rust2dart().close_stream()
|
||||
}
|
||||
}
|
||||
75
native/hub/src/bridge/bridge_engine/support.rs
Normal file
75
native/hub/src/bridge/bridge_engine/support.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
//! Functions that support auto-generated Rust code.
|
||||
//! These functions are *not* meant to be used by humans directly.
|
||||
#![doc(hidden)]
|
||||
|
||||
use std::mem;
|
||||
|
||||
pub use crate::bridge::bridge_engine::ffi::*;
|
||||
pub use lazy_static::lazy_static;
|
||||
|
||||
pub use crate::bridge::bridge_engine::handler::DefaultHandler;
|
||||
|
||||
// ref https://stackoverflow.com/questions/39224904/how-to-expose-a-rust-vect-to-ffi
|
||||
pub fn new_leak_vec_ptr<T: Clone>(fill: T, length: i32) -> *mut T {
|
||||
into_leak_vec_ptr(vec![fill; length as usize]).0
|
||||
}
|
||||
|
||||
pub fn into_leak_vec_ptr<T: Clone>(mut v: Vec<T>) -> (*mut T, i32) {
|
||||
v.shrink_to_fit();
|
||||
assert!(v.len() == v.capacity());
|
||||
let ptr = v.as_mut_ptr();
|
||||
let len = v.len() as i32;
|
||||
mem::forget(v);
|
||||
(ptr, len)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Use it in pair with [new_leak_vec_ptr].
|
||||
pub unsafe fn vec_from_leak_ptr<T>(ptr: *mut T, len: i32) -> Vec<T> {
|
||||
Vec::from_raw_parts(ptr, len as usize, len as usize)
|
||||
}
|
||||
|
||||
/// Convert [Vec<T>] to array length `N`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if length of [Vec<T>] != `N`.
|
||||
pub fn from_vec_to_array<T, const N: usize>(v: Vec<T>) -> [T; N] {
|
||||
core::convert::TryInto::try_into(v)
|
||||
.unwrap_or_else(|v: Vec<T>| panic!("Expected a Vec of length {} but it was {}", N, v.len()))
|
||||
}
|
||||
|
||||
// ref: doc of [Box::into_raw]
|
||||
pub fn new_leak_box_ptr<T>(t: T) -> *mut T {
|
||||
let x: Box<T> = Box::new(t);
|
||||
Box::into_raw(x)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Use it in pair with [new_leak_box_ptr].
|
||||
pub unsafe fn box_from_leak_ptr<T>(ptr: *mut T) -> Box<T> {
|
||||
Box::from_raw(ptr)
|
||||
}
|
||||
|
||||
/// Cast a byte buffer into a boxed slice of the target type without making any copies.
|
||||
/// Panics if the cast fails.
|
||||
pub fn slice_from_byte_buffer<T: bytemuck::Pod>(buffer: Vec<u8>) -> Box<[T]> {
|
||||
let buf = Box::leak(buffer.into_boxed_slice());
|
||||
match bytemuck::try_cast_slice_mut(buf) {
|
||||
Ok(buf) => unsafe { Box::from_raw(buf) },
|
||||
Err(err) => {
|
||||
// clean up before panicking
|
||||
unsafe { core::ptr::drop_in_place(buf) }
|
||||
panic!("cast error: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use allo_isolate::ffi::DartCObject;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub type WireSyncReturn = *mut DartCObject;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub type WireSyncReturn = wasm_bindgen::JsValue;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
//! Code originally sourced from wasm-bindgen's repository.
|
||||
|
||||
pub mod transfer;
|
||||
pub mod worker;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
use crate::bridge::bridge_engine::ffi::web::*;
|
||||
use js_sys::{global, Array};
|
||||
use std::iter::FromIterator;
|
||||
use web_sys::{DedicatedWorkerGlobalScope, Worker};
|
||||
|
||||
impl TransferClosure<JsValue> {
|
||||
/// Posts a <code>[*mut [TransferClosurePayload], ...[JsValue]]</code> message to this worker.
|
||||
///
|
||||
/// The worker's `onmessage` should run the corresponding [`receive_transfer_closure`]
|
||||
/// to receive the message.
|
||||
pub fn apply(self, worker: &Worker) -> Result<(), JsValue> {
|
||||
let transfer = self.transfer.into_iter().filter(|value| value.is_truthy());
|
||||
let transfer = Array::from_iter(transfer);
|
||||
let data = Array::from_iter(self.data);
|
||||
// The worker is responsible for cleaning up the leak here.
|
||||
let payload = Box::into_raw(Box::new(TransferClosurePayload { func: self.closure }));
|
||||
data.unshift(&JsValue::from(payload as i32));
|
||||
worker
|
||||
.post_message_with_transfer(&data, &transfer)
|
||||
.map_err(|err| {
|
||||
// post message failed, ownership remains with us.
|
||||
drop(unsafe { Box::from_raw(payload) });
|
||||
err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Safety
|
||||
/// This function reclaims a raw pointer created by [`TransferClosure`], and therefore
|
||||
/// should **only** be used in conjunction with it.
|
||||
/// Furthermore, the WASM module in the worker must have been initialized with the shared
|
||||
/// memory from the host JS scope.
|
||||
// wasm_bindgen cannot work with unsafe functions, hence the clippy ignore.
|
||||
#[wasm_bindgen]
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
pub fn receive_transfer_closure(
|
||||
payload: *mut TransferClosurePayload<JsValue>,
|
||||
transfer: Box<[JsValue]>,
|
||||
) -> Result<(), JsValue> {
|
||||
let closure = unsafe { Box::from_raw(payload) };
|
||||
(closure.func)(&transfer);
|
||||
// Once we're done here, notify the host scope so that it can reclaim this worker.
|
||||
global()
|
||||
.unchecked_into::<DedicatedWorkerGlobalScope>()
|
||||
.post_message(&JsValue::UNDEFINED)
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
use crate::bridge::bridge_engine::script_path;
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{Blob, BlobPropertyBag, Url, Worker};
|
||||
|
||||
thread_local! {
|
||||
pub static WEB_WORKER: Worker = create_worker();
|
||||
}
|
||||
|
||||
fn create_worker() -> Worker {
|
||||
let script = format!(
|
||||
"
|
||||
importScripts('{}');
|
||||
onmessage = event => {{
|
||||
let init = wasm_bindgen(...event.data).catch(err => {{
|
||||
setTimeout(() => {{ throw err }})
|
||||
throw err
|
||||
}})
|
||||
onmessage = async event => {{
|
||||
await init
|
||||
let [payload, ...transfer] = event.data
|
||||
try {{
|
||||
wasm_bindgen.receive_transfer_closure(payload, transfer)
|
||||
}} catch (err) {{
|
||||
if (transfer[0] && typeof transfer[0].postMessage === 'function') {{
|
||||
transfer[0].postMessage([1, 'ABORT', err.toString(), err.stack])
|
||||
}}
|
||||
setTimeout(() => {{ throw err }})
|
||||
postMessage(null)
|
||||
throw err
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
",
|
||||
script_path().unwrap()
|
||||
);
|
||||
let blob = Blob::new_with_blob_sequence_and_options(
|
||||
&Array::from_iter([JsValue::from(script)]).into(),
|
||||
BlobPropertyBag::new().type_("text/javascript"),
|
||||
)
|
||||
.unwrap();
|
||||
let url = Url::create_object_url_with_blob(&blob).unwrap();
|
||||
let worker = Worker::new(&url).unwrap();
|
||||
let module = wasm_bindgen::module();
|
||||
let memory = wasm_bindgen::memory();
|
||||
worker
|
||||
.post_message(&Array::from_iter([module, memory]))
|
||||
.unwrap();
|
||||
worker
|
||||
}
|
||||
174
native/hub/src/bridge/bridge_generated.io.rs
Normal file
174
native/hub/src/bridge/bridge_generated.io.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
use super::*;
|
||||
// Section: wire functions
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wire_prepare_rust_signal_stream(port_: i64) {
|
||||
wire_prepare_rust_signal_stream_impl(port_)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wire_prepare_rust_response_stream(port_: i64) {
|
||||
wire_prepare_rust_response_stream_impl(port_)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wire_prepare_rust_report_stream(port_: i64) {
|
||||
wire_prepare_rust_report_stream_impl(port_)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wire_prepare_channels(port_: i64) {
|
||||
wire_prepare_channels_impl(port_)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wire_check_rust_streams(port_: i64) {
|
||||
wire_check_rust_streams_impl(port_)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wire_start_rust_logic(port_: i64) {
|
||||
wire_start_rust_logic_impl(port_)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wire_stop_rust_logic(port_: i64) {
|
||||
wire_stop_rust_logic_impl(port_)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wire_request_to_rust(port_: i64, request_unique: *mut wire_RustRequestUnique) {
|
||||
wire_request_to_rust_impl(port_, request_unique)
|
||||
}
|
||||
|
||||
// Section: allocate functions
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn new_box_autoadd_rust_request_unique_0() -> *mut wire_RustRequestUnique {
|
||||
support::new_leak_box_ptr(wire_RustRequestUnique::new_with_null_ptr())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list {
|
||||
let ans = wire_uint_8_list {
|
||||
ptr: support::new_leak_vec_ptr(Default::default(), len),
|
||||
len,
|
||||
};
|
||||
support::new_leak_box_ptr(ans)
|
||||
}
|
||||
|
||||
// Section: related functions
|
||||
|
||||
// Section: impl Wire2Api
|
||||
|
||||
impl Wire2Api<RustRequestUnique> for *mut wire_RustRequestUnique {
|
||||
fn wire2api(self) -> RustRequestUnique {
|
||||
let wrap = unsafe { support::box_from_leak_ptr(self) };
|
||||
Wire2Api::<RustRequestUnique>::wire2api(*wrap).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Wire2Api<RustRequest> for wire_RustRequest {
|
||||
fn wire2api(self) -> RustRequest {
|
||||
RustRequest {
|
||||
resource: self.resource.wire2api(),
|
||||
operation: self.operation.wire2api(),
|
||||
message: self.message.wire2api(),
|
||||
blob: self.blob.wire2api(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Wire2Api<RustRequestUnique> for wire_RustRequestUnique {
|
||||
fn wire2api(self) -> RustRequestUnique {
|
||||
RustRequestUnique {
|
||||
id: self.id.wire2api(),
|
||||
request: self.request.wire2api(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Wire2Api<Vec<u8>> for *mut wire_uint_8_list {
|
||||
fn wire2api(self) -> Vec<u8> {
|
||||
unsafe {
|
||||
let wrap = support::box_from_leak_ptr(self);
|
||||
support::vec_from_leak_ptr(wrap.ptr, wrap.len)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Section: wire structs
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct wire_RustRequest {
|
||||
resource: i32,
|
||||
operation: i32,
|
||||
message: *mut wire_uint_8_list,
|
||||
blob: *mut wire_uint_8_list,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct wire_RustRequestUnique {
|
||||
id: i32,
|
||||
request: wire_RustRequest,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct wire_uint_8_list {
|
||||
ptr: *mut u8,
|
||||
len: i32,
|
||||
}
|
||||
|
||||
// Section: impl NewWithNullPtr
|
||||
|
||||
pub trait NewWithNullPtr {
|
||||
fn new_with_null_ptr() -> Self;
|
||||
}
|
||||
|
||||
impl<T> NewWithNullPtr for *mut T {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl NewWithNullPtr for wire_RustRequest {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
resource: Default::default(),
|
||||
operation: Default::default(),
|
||||
message: core::ptr::null_mut(),
|
||||
blob: core::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for wire_RustRequest {
|
||||
fn default() -> Self {
|
||||
Self::new_with_null_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
impl NewWithNullPtr for wire_RustRequestUnique {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
id: Default::default(),
|
||||
request: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for wire_RustRequestUnique {
|
||||
fn default() -> Self {
|
||||
Self::new_with_null_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
// Section: sync execution mode utility
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn free_WireSyncReturn(ptr: support::WireSyncReturn) {
|
||||
unsafe {
|
||||
let _ = support::box_from_leak_ptr(ptr);
|
||||
};
|
||||
}
|
||||
245
native/hub/src/bridge/bridge_generated.rs
Normal file
245
native/hub/src/bridge/bridge_generated.rs
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
#![allow(
|
||||
non_camel_case_types,
|
||||
unused,
|
||||
clippy::redundant_closure,
|
||||
clippy::useless_conversion,
|
||||
clippy::unit_arg,
|
||||
clippy::double_parens,
|
||||
non_snake_case,
|
||||
clippy::too_many_arguments
|
||||
)]
|
||||
// AUTO GENERATED FILE, DO NOT EDIT.
|
||||
// Generated by flutter_rust_bridge_codegen@ 1.80.1.
|
||||
|
||||
use crate::bridge::api::*;
|
||||
use crate::bridge::bridge_engine::rust2dart::IntoIntoDart;
|
||||
use crate::bridge::bridge_engine::*;
|
||||
use core::panic::UnwindSafe;
|
||||
use std::ffi::c_void;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Section: imports
|
||||
|
||||
// Section: wire functions
|
||||
|
||||
fn wire_prepare_rust_signal_stream_impl(port_: MessagePort) {
|
||||
BRIDGE_HANDLER.wrap::<_, _, _, ()>(
|
||||
WrapInfo {
|
||||
debug_name: "prepare_rust_signal_stream",
|
||||
port: Some(port_),
|
||||
mode: FfiCallMode::Stream,
|
||||
},
|
||||
move || {
|
||||
move |task_callback| {
|
||||
Ok(prepare_rust_signal_stream(
|
||||
task_callback.stream_sink::<_, RustSignal>(),
|
||||
))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire_prepare_rust_response_stream_impl(port_: MessagePort) {
|
||||
BRIDGE_HANDLER.wrap::<_, _, _, ()>(
|
||||
WrapInfo {
|
||||
debug_name: "prepare_rust_response_stream",
|
||||
port: Some(port_),
|
||||
mode: FfiCallMode::Stream,
|
||||
},
|
||||
move || {
|
||||
move |task_callback| {
|
||||
Ok(prepare_rust_response_stream(
|
||||
task_callback.stream_sink::<_, RustResponseUnique>(),
|
||||
))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire_prepare_rust_report_stream_impl(port_: MessagePort) {
|
||||
BRIDGE_HANDLER.wrap::<_, _, _, ()>(
|
||||
WrapInfo {
|
||||
debug_name: "prepare_rust_report_stream",
|
||||
port: Some(port_),
|
||||
mode: FfiCallMode::Stream,
|
||||
},
|
||||
move || {
|
||||
move |task_callback| {
|
||||
Ok(prepare_rust_report_stream(
|
||||
task_callback.stream_sink::<_, String>(),
|
||||
))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire_prepare_channels_impl(port_: MessagePort) {
|
||||
BRIDGE_HANDLER.wrap::<_, _, _, ()>(
|
||||
WrapInfo {
|
||||
debug_name: "prepare_channels",
|
||||
port: Some(port_),
|
||||
mode: FfiCallMode::Normal,
|
||||
},
|
||||
move || move |task_callback| Ok(prepare_channels()),
|
||||
)
|
||||
}
|
||||
fn wire_check_rust_streams_impl(port_: MessagePort) {
|
||||
BRIDGE_HANDLER.wrap::<_, _, _, bool>(
|
||||
WrapInfo {
|
||||
debug_name: "check_rust_streams",
|
||||
port: Some(port_),
|
||||
mode: FfiCallMode::Normal,
|
||||
},
|
||||
move || move |task_callback| Ok(check_rust_streams()),
|
||||
)
|
||||
}
|
||||
fn wire_start_rust_logic_impl(port_: MessagePort) {
|
||||
BRIDGE_HANDLER.wrap::<_, _, _, ()>(
|
||||
WrapInfo {
|
||||
debug_name: "start_rust_logic",
|
||||
port: Some(port_),
|
||||
mode: FfiCallMode::Normal,
|
||||
},
|
||||
move || move |task_callback| Ok(start_rust_logic()),
|
||||
)
|
||||
}
|
||||
fn wire_stop_rust_logic_impl(port_: MessagePort) {
|
||||
BRIDGE_HANDLER.wrap::<_, _, _, ()>(
|
||||
WrapInfo {
|
||||
debug_name: "stop_rust_logic",
|
||||
port: Some(port_),
|
||||
mode: FfiCallMode::Normal,
|
||||
},
|
||||
move || move |task_callback| Ok(stop_rust_logic()),
|
||||
)
|
||||
}
|
||||
fn wire_request_to_rust_impl(
|
||||
port_: MessagePort,
|
||||
request_unique: impl Wire2Api<RustRequestUnique> + UnwindSafe,
|
||||
) {
|
||||
BRIDGE_HANDLER.wrap::<_, _, _, ()>(
|
||||
WrapInfo {
|
||||
debug_name: "request_to_rust",
|
||||
port: Some(port_),
|
||||
mode: FfiCallMode::Normal,
|
||||
},
|
||||
move || {
|
||||
let api_request_unique = request_unique.wire2api();
|
||||
move |task_callback| Ok(request_to_rust(api_request_unique))
|
||||
},
|
||||
)
|
||||
}
|
||||
// Section: wrapper structs
|
||||
|
||||
// Section: static checks
|
||||
|
||||
// Section: allocate functions
|
||||
|
||||
// Section: related functions
|
||||
|
||||
// Section: impl Wire2Api
|
||||
|
||||
pub trait Wire2Api<T> {
|
||||
fn wire2api(self) -> T;
|
||||
}
|
||||
|
||||
impl<T, S> Wire2Api<Option<T>> for *mut S
|
||||
where
|
||||
*mut S: Wire2Api<T>,
|
||||
{
|
||||
fn wire2api(self) -> Option<T> {
|
||||
(!self.is_null()).then(|| self.wire2api())
|
||||
}
|
||||
}
|
||||
|
||||
impl Wire2Api<i32> for i32 {
|
||||
fn wire2api(self) -> i32 {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Wire2Api<RustOperation> for i32 {
|
||||
fn wire2api(self) -> RustOperation {
|
||||
match self {
|
||||
0 => RustOperation::Create,
|
||||
1 => RustOperation::Read,
|
||||
2 => RustOperation::Update,
|
||||
3 => RustOperation::Delete,
|
||||
_ => unreachable!("Invalid variant for RustOperation: {}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Wire2Api<u8> for u8 {
|
||||
fn wire2api(self) -> u8 {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Section: impl IntoDart
|
||||
|
||||
impl support::IntoDart for RustResponse {
|
||||
fn into_dart(self) -> support::DartAbi {
|
||||
vec![
|
||||
self.successful.into_into_dart().into_dart(),
|
||||
self.message.into_dart(),
|
||||
self.blob.into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
}
|
||||
impl support::IntoDartExceptPrimitive for RustResponse {}
|
||||
impl rust2dart::IntoIntoDart<RustResponse> for RustResponse {
|
||||
fn into_into_dart(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl support::IntoDart for RustResponseUnique {
|
||||
fn into_dart(self) -> support::DartAbi {
|
||||
vec![
|
||||
self.id.into_into_dart().into_dart(),
|
||||
self.response.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
}
|
||||
impl support::IntoDartExceptPrimitive for RustResponseUnique {}
|
||||
impl rust2dart::IntoIntoDart<RustResponseUnique> for RustResponseUnique {
|
||||
fn into_into_dart(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl support::IntoDart for RustSignal {
|
||||
fn into_dart(self) -> support::DartAbi {
|
||||
vec![
|
||||
self.resource.into_into_dart().into_dart(),
|
||||
self.message.into_dart(),
|
||||
self.blob.into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
}
|
||||
impl support::IntoDartExceptPrimitive for RustSignal {}
|
||||
impl rust2dart::IntoIntoDart<RustSignal> for RustSignal {
|
||||
fn into_into_dart(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Section: executor
|
||||
|
||||
support::lazy_static! {
|
||||
pub static ref BRIDGE_HANDLER: support::DefaultHandler = Default::default();
|
||||
}
|
||||
|
||||
/// cbindgen:ignore
|
||||
#[cfg(target_family = "wasm")]
|
||||
#[path = "bridge_generated.web.rs"]
|
||||
mod web;
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub use web::*;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[path = "bridge_generated.io.rs"]
|
||||
mod io;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub use io::*;
|
||||
120
native/hub/src/bridge/bridge_generated.web.rs
Normal file
120
native/hub/src/bridge/bridge_generated.web.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
use super::*;
|
||||
// Section: wire functions
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn wire_prepare_rust_signal_stream(port_: MessagePort) {
|
||||
wire_prepare_rust_signal_stream_impl(port_)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn wire_prepare_rust_response_stream(port_: MessagePort) {
|
||||
wire_prepare_rust_response_stream_impl(port_)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn wire_prepare_rust_report_stream(port_: MessagePort) {
|
||||
wire_prepare_rust_report_stream_impl(port_)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn wire_prepare_channels(port_: MessagePort) {
|
||||
wire_prepare_channels_impl(port_)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn wire_check_rust_streams(port_: MessagePort) {
|
||||
wire_check_rust_streams_impl(port_)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn wire_start_rust_logic(port_: MessagePort) {
|
||||
wire_start_rust_logic_impl(port_)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn wire_stop_rust_logic(port_: MessagePort) {
|
||||
wire_stop_rust_logic_impl(port_)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn wire_request_to_rust(port_: MessagePort, request_unique: JsValue) {
|
||||
wire_request_to_rust_impl(port_, request_unique)
|
||||
}
|
||||
|
||||
// Section: allocate functions
|
||||
|
||||
// Section: related functions
|
||||
|
||||
// Section: impl Wire2Api
|
||||
|
||||
impl Wire2Api<Option<Vec<u8>>> for Option<Box<[u8]>> {
|
||||
fn wire2api(self) -> Option<Vec<u8>> {
|
||||
self.map(Wire2Api::wire2api)
|
||||
}
|
||||
}
|
||||
|
||||
impl Wire2Api<RustRequest> for JsValue {
|
||||
fn wire2api(self) -> RustRequest {
|
||||
let self_ = self.dyn_into::<JsArray>().unwrap();
|
||||
assert_eq!(
|
||||
self_.length(),
|
||||
4,
|
||||
"Expected 4 elements, got {}",
|
||||
self_.length()
|
||||
);
|
||||
RustRequest {
|
||||
resource: self_.get(0).wire2api(),
|
||||
operation: self_.get(1).wire2api(),
|
||||
message: self_.get(2).wire2api(),
|
||||
blob: self_.get(3).wire2api(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Wire2Api<RustRequestUnique> for JsValue {
|
||||
fn wire2api(self) -> RustRequestUnique {
|
||||
let self_ = self.dyn_into::<JsArray>().unwrap();
|
||||
assert_eq!(
|
||||
self_.length(),
|
||||
2,
|
||||
"Expected 2 elements, got {}",
|
||||
self_.length()
|
||||
);
|
||||
RustRequestUnique {
|
||||
id: self_.get(0).wire2api(),
|
||||
request: self_.get(1).wire2api(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Wire2Api<Vec<u8>> for Box<[u8]> {
|
||||
fn wire2api(self) -> Vec<u8> {
|
||||
self.into_vec()
|
||||
}
|
||||
}
|
||||
// Section: impl Wire2Api for JsValue
|
||||
|
||||
impl Wire2Api<i32> for JsValue {
|
||||
fn wire2api(self) -> i32 {
|
||||
self.unchecked_into_f64() as _
|
||||
}
|
||||
}
|
||||
impl Wire2Api<Option<Vec<u8>>> for JsValue {
|
||||
fn wire2api(self) -> Option<Vec<u8>> {
|
||||
(!self.is_undefined() && !self.is_null()).then(|| self.wire2api())
|
||||
}
|
||||
}
|
||||
impl Wire2Api<RustOperation> for JsValue {
|
||||
fn wire2api(self) -> RustOperation {
|
||||
(self.unchecked_into_f64() as i32).wire2api()
|
||||
}
|
||||
}
|
||||
impl Wire2Api<u8> for JsValue {
|
||||
fn wire2api(self) -> u8 {
|
||||
self.unchecked_into_f64() as _
|
||||
}
|
||||
}
|
||||
impl Wire2Api<Vec<u8>> for JsValue {
|
||||
fn wire2api(self) -> Vec<u8> {
|
||||
self.unchecked_into::<js_sys::Uint8Array>().to_vec().into()
|
||||
}
|
||||
}
|
||||
94
native/hub/src/bridge/mod.rs
Normal file
94
native/hub/src/bridge/mod.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
//! This module supports communication with Dart.
|
||||
//! More specifically, sending responses and
|
||||
//! stream signals to Dart are supported.
|
||||
//! DO NOT EDIT.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use api::RustResponseUnique;
|
||||
use api::RustSignal;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
|
||||
pub mod api;
|
||||
pub mod bridge_engine;
|
||||
mod bridge_generated;
|
||||
|
||||
/// This function is expected to be used only once
|
||||
/// during the initialization of the Rust logic.
|
||||
pub fn get_request_receiver() -> Receiver<api::RustRequestUnique> {
|
||||
let cell = api::REQUST_RECEIVER_SHARED.lock().unwrap();
|
||||
let option = cell.replace(None);
|
||||
option.unwrap()
|
||||
}
|
||||
|
||||
/// Sending the signal will notify the Flutter widgets
|
||||
/// and trigger the rebuild.
|
||||
/// No memory copy is involved as the bytes are moved directly to Dart.
|
||||
pub fn send_rust_signal(rust_signal: RustSignal) {
|
||||
api::SIGNAL_STREAM.with(|inner| {
|
||||
let mut borrowed = inner.borrow_mut();
|
||||
let option = borrowed.as_ref();
|
||||
if let Some(stream) = option {
|
||||
stream.add(rust_signal);
|
||||
} else {
|
||||
let cell = api::SIGNAL_STREAM_SHARED.lock().unwrap();
|
||||
let stream = cell.borrow().as_ref().unwrap().clone();
|
||||
stream.add(rust_signal);
|
||||
borrowed.replace(stream);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Sends a response to Dart with a unique interaction ID
|
||||
/// to remember which request that response corresponds to.
|
||||
/// No memory copy is involved as the bytes are moved directly to Dart.
|
||||
pub fn respond_to_dart(response_unique: RustResponseUnique) {
|
||||
api::RESPONSE_STREAM.with(|inner| {
|
||||
let mut borrowed = inner.borrow_mut();
|
||||
let option = borrowed.as_ref();
|
||||
if let Some(stream) = option {
|
||||
stream.add(response_unique);
|
||||
} else {
|
||||
let cell = api::RESPONSE_STREAM_SHARED.lock().unwrap();
|
||||
let stream = cell.borrow().as_ref().unwrap().clone();
|
||||
stream.add(response_unique);
|
||||
borrowed.replace(stream);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Delegates the printing operation to Flutter,
|
||||
/// which excels at handling various platforms
|
||||
/// including web and mobile emulators.
|
||||
/// When debugging, using this macro is recommended over `println!()`,
|
||||
/// as it seamlessly adapts to different environments.
|
||||
/// Note that this macro does nothing in release mode.
|
||||
#[macro_export]
|
||||
macro_rules! debug_print {
|
||||
( $( $t:tt )* ) => {
|
||||
let rust_report = format!( $( $t )* );
|
||||
#[cfg(debug_assertions)]
|
||||
$crate::bridge::send_rust_report(rust_report.into());
|
||||
#[cfg(not(debug_assertions))]
|
||||
let _ = rust_report;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a string to Dart that should be printed in the CLI.
|
||||
/// Do NOT use this function directly in the code.
|
||||
/// Use `debug_print!` macro instead.
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn send_rust_report(rust_report: String) {
|
||||
api::REPORT_STREAM.with(|inner| {
|
||||
let mut borrowed = inner.borrow_mut();
|
||||
let option = borrowed.as_ref();
|
||||
if let Some(stream) = option {
|
||||
stream.add(rust_report);
|
||||
} else {
|
||||
let cell = api::REPORT_STREAM_SHARED.lock().unwrap();
|
||||
let stream = cell.borrow().as_ref().unwrap().clone();
|
||||
stream.add(rust_report);
|
||||
borrowed.replace(stream);
|
||||
}
|
||||
});
|
||||
}
|
||||
143
native/hub/src/imagecrop.rs
Normal file
143
native/hub/src/imagecrop.rs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
use crate::bridge::api::{RustOperation, RustRequest, RustResponse};
|
||||
use image::{DynamicImage, GenericImageView, ImageOutputFormat, ImageResult, Rgba};
|
||||
use prost::Message;
|
||||
use std::io::Cursor;
|
||||
pub struct Point {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
}
|
||||
|
||||
pub struct ImageCrop {
|
||||
pub original: DynamicImage,
|
||||
}
|
||||
|
||||
impl ImageCrop {
|
||||
pub fn open(file: Vec<u8>) -> ImageResult<ImageCrop> {
|
||||
Ok(ImageCrop {
|
||||
original: image::load_from_memory(file.as_slice()).expect("error decoding image"),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn calculate_corners(&self) -> (Point, Point) {
|
||||
(self.top_left_corner(), self.bottom_right_corner())
|
||||
}
|
||||
|
||||
fn is_white(pixel: Rgba<u8>) -> bool {
|
||||
pixel[0] != 255 && pixel[1] != 255 && pixel[2] != 255
|
||||
}
|
||||
|
||||
fn top_left_corner(&self) -> Point {
|
||||
Point {
|
||||
x: self.top_left_corner_x(),
|
||||
y: self.top_left_corner_y(),
|
||||
}
|
||||
}
|
||||
|
||||
fn top_left_corner_x(&self) -> u32 {
|
||||
for x in 0..(self.original.dimensions().0) {
|
||||
for y in 0..(self.original.dimensions().1) {
|
||||
let pixel = self.original.get_pixel(x, y);
|
||||
if Self::is_white(pixel) {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn top_left_corner_y(&self) -> u32 {
|
||||
for y in 0..(self.original.dimensions().1) {
|
||||
for x in 0..(self.original.dimensions().0) {
|
||||
let pixel = self.original.get_pixel(x, y);
|
||||
if Self::is_white(pixel) {
|
||||
return y;
|
||||
}
|
||||
}
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn bottom_right_corner(&self) -> Point {
|
||||
Point {
|
||||
x: self.bottom_right_corner_x(),
|
||||
y: self.bottom_right_corner_y(),
|
||||
}
|
||||
}
|
||||
|
||||
fn bottom_right_corner_x(&self) -> u32 {
|
||||
let mut x = self.original.dimensions().0 as i32 - 1;
|
||||
// Using while loop as currently there is no reliable built-in
|
||||
// way to use custom negative steps when specifying range
|
||||
while x >= 0 {
|
||||
let mut y = self.original.dimensions().1 as i32 - 1;
|
||||
while y >= 0 {
|
||||
let pixel = self.original.get_pixel(x as u32, y as u32);
|
||||
if Self::is_white(pixel) {
|
||||
return x as u32 + 1;
|
||||
}
|
||||
y -= 1;
|
||||
}
|
||||
x -= 1;
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn bottom_right_corner_y(&self) -> u32 {
|
||||
let mut y = self.original.dimensions().1 as i32 - 1;
|
||||
// Using while loop as currently there is no reliable built-in
|
||||
// way to use custom negative steps when specifying range
|
||||
while y >= 0 {
|
||||
let mut x = self.original.dimensions().0 as i32 - 1;
|
||||
while x >= 0 {
|
||||
let pixel = self.original.get_pixel(x as u32, y as u32);
|
||||
if Self::is_white(pixel) {
|
||||
return y as u32 + 1;
|
||||
}
|
||||
x -= 1;
|
||||
}
|
||||
y -= 1;
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
fn crop_image(image: Vec<u8>) -> DynamicImage {
|
||||
let mut image =
|
||||
ImageCrop::open(image).expect(&format!("Failed to load image"));
|
||||
|
||||
let (top_left_corner, bottom_right_corner) = image.calculate_corners();
|
||||
|
||||
let sub_image = image.original.crop(
|
||||
top_left_corner.x,
|
||||
top_left_corner.y,
|
||||
bottom_right_corner.x - top_left_corner.x,
|
||||
bottom_right_corner.y - top_left_corner.y,
|
||||
);
|
||||
|
||||
return sub_image;
|
||||
}
|
||||
|
||||
pub async fn start_croping(rust_request: RustRequest) -> RustResponse {
|
||||
use crate::messages::crop_borders::ReadRequest;
|
||||
|
||||
match rust_request.operation {
|
||||
RustOperation::Create => RustResponse::default(),
|
||||
RustOperation::Read => {
|
||||
let message_bytes = rust_request.message.unwrap();
|
||||
let request_message = ReadRequest::decode(message_bytes.as_slice()).unwrap();
|
||||
|
||||
let res = crop_image(request_message.image);
|
||||
let mut image_data: Vec<u8> = Vec::new();
|
||||
res.write_to(&mut Cursor::new(&mut image_data), ImageOutputFormat::Png)
|
||||
.unwrap();
|
||||
|
||||
RustResponse {
|
||||
successful: true,
|
||||
message: None,
|
||||
blob: Some(image_data),
|
||||
}
|
||||
}
|
||||
RustOperation::Delete => RustResponse::default(),
|
||||
RustOperation::Update => RustResponse::default(),
|
||||
}
|
||||
}
|
||||
24
native/hub/src/lib.rs
Normal file
24
native/hub/src/lib.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use bridge::respond_to_dart;
|
||||
use web_alias::*;
|
||||
use with_request::handle_request;
|
||||
|
||||
mod bridge;
|
||||
mod imagecrop;
|
||||
mod messages;
|
||||
mod web_alias;
|
||||
mod with_request;
|
||||
|
||||
/// This `hub` crate is the entry point for the Rust logic.
|
||||
/// Always use non-blocking async functions such as `tokio::fs::File::open`.
|
||||
async fn main() {
|
||||
// This is `tokio::sync::mpsc::Reciver` that receives the requests from Dart.
|
||||
let mut request_receiver = bridge::get_request_receiver();
|
||||
// Repeat `crate::spawn` anywhere in your code
|
||||
// if more concurrent tasks are needed.
|
||||
while let Some(request_unique) = request_receiver.recv().await {
|
||||
crate::spawn(async {
|
||||
let response_unique = handle_request(request_unique).await;
|
||||
respond_to_dart(response_unique);
|
||||
});
|
||||
}
|
||||
}
|
||||
115
native/hub/src/web_alias.rs
Normal file
115
native/hub/src/web_alias.rs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
//! The web has many restrictions due to its sandboxed environment
|
||||
//! which prevents the use of
|
||||
//! threads, atomics, time, file IO, network IO,
|
||||
//! and many other native functionalities.
|
||||
//! Consequently, certain features are missing from various crates
|
||||
//! including Rust's `std` due to these limitations.
|
||||
//!
|
||||
//! To address this issue, this module offers various imports
|
||||
//! with the **same names** as the original native ones,
|
||||
//! providing workarounds for these constraints.
|
||||
//!
|
||||
//! You might encounter situations
|
||||
//! where you cannot use native Rust code directly on the web.
|
||||
//! Add more custom web aliases here if needed.
|
||||
//! Refer to the links below to understand how to interact with JavaScript.
|
||||
//! - https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-js-imports/js_name.html
|
||||
//! - https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-js-imports/js_namespace.html
|
||||
//!
|
||||
//! Rust code is executed in a **web worker**.
|
||||
//! Therefore, you cannot access the global `window` JavaScript object
|
||||
//! just like when you work in the main thread of JavaScript.
|
||||
//! Refer to the link below to check which web APIs are available in a web worker.
|
||||
//! You'll be surprised by various capabilities that modern JavaScript has.
|
||||
//! - https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers
|
||||
//!
|
||||
//! Also, there are many crates at `crates.io`
|
||||
//! that mimic native functionalities on the web.
|
||||
//! Use them if you do not want to write code that interacts with JavaScript yourself.
|
||||
//!
|
||||
//! If your app is not targeting web, you can simply remove this module.
|
||||
|
||||
#![allow(dead_code, unused_imports, unused_macros)]
|
||||
|
||||
// On native platforms,`tokio`'s multicore async runtime
|
||||
// allows millions of concurrent tasks to run at the same time.
|
||||
// On the web, concurrent tasks are executed
|
||||
// in JavaScript's single-threaded event loop.
|
||||
// Crate `wasm_bindgen_futures` has the ability
|
||||
// to convert Rust `Future`s into JavaScript `Promise`s.
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub(crate) fn spawn<F, T>(future: F) -> tokio::task::JoinHandle<T>
|
||||
where
|
||||
F: std::future::Future<Output = T> + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
tokio::task::spawn(future)
|
||||
}
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub(crate) fn spawn<F, T>(future: F) -> async_wasm_task::JoinHandle<T>
|
||||
where
|
||||
F: std::future::Future<Output = T> + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
async_wasm_task::spawn(future)
|
||||
}
|
||||
|
||||
// Sometimes, running CPU-intensive blocking tasks is necessary.
|
||||
// It is better to spawn them
|
||||
// in a totally separate thread pool for parallelization.
|
||||
// On the web, `async_wasm_task` crate does this job
|
||||
// by interacting with JavaScript and web workers.
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub(crate) fn spawn_blocking<C, T>(callable: C) -> tokio::task::JoinHandle<T>
|
||||
where
|
||||
C: FnOnce() -> T + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
tokio::task::spawn_blocking(callable)
|
||||
}
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub(crate) fn spawn_blocking<C, T>(callable: C) -> async_wasm_task::JoinHandle<T>
|
||||
where
|
||||
C: FnOnce() -> T + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
async_wasm_task::spawn_blocking(callable)
|
||||
}
|
||||
|
||||
// To avoid blocking inside a long-running function,
|
||||
// you have to yield to the async event loop regularly.
|
||||
// On the web, `async_wasm_task` crate does this job
|
||||
// by interacting with JavaScript.
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub async fn yield_now() {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub async fn yield_now() {
|
||||
async_wasm_task::yield_now().await;
|
||||
}
|
||||
|
||||
// On the web, `tokio` cannot access the system to check the passed time.
|
||||
// The JavaScript function `setTimeout()` performs this task.
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub async fn sleep(duration: std::time::Duration) {
|
||||
tokio::time::sleep(duration).await;
|
||||
}
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub async fn sleep(duration: std::time::Duration) {
|
||||
use wasm_bindgen::prelude::*;
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = setTimeout)]
|
||||
fn set_timeout(callback: &js_sys::Function, milliseconds: f64);
|
||||
}
|
||||
let milliseconds = duration.as_millis() as f64;
|
||||
let promise = js_sys::Promise::new(&mut |resolve, _reject| {
|
||||
set_timeout(&resolve, milliseconds);
|
||||
});
|
||||
let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
|
||||
}
|
||||
28
native/hub/src/with_request.rs
Normal file
28
native/hub/src/with_request.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
//! This module runs the corresponding function
|
||||
//! when a `RustRequest` was received from Dart
|
||||
//! and returns `RustResponse`.
|
||||
|
||||
use crate::bridge::api::{RustRequestUnique, RustResponse, RustResponseUnique};
|
||||
use crate::imagecrop;
|
||||
use crate::messages;
|
||||
|
||||
pub async fn handle_request(request_unique: RustRequestUnique) -> RustResponseUnique {
|
||||
// Get the request data.
|
||||
let rust_request = request_unique.request;
|
||||
let interaction_id = request_unique.id;
|
||||
|
||||
// Run the function that corresponds to the address.
|
||||
let rust_resource = rust_request.resource;
|
||||
let rust_response = match rust_resource {
|
||||
messages::crop_borders::ID => {
|
||||
imagecrop::start_croping(rust_request).await // ADD THIS BLOCK
|
||||
}
|
||||
_ => RustResponse::default(),
|
||||
};
|
||||
|
||||
// Return the response.
|
||||
RustResponseUnique {
|
||||
id: interaction_id,
|
||||
response: rust_response,
|
||||
}
|
||||
}
|
||||
12
native/sample_crate/Cargo.toml
Normal file
12
native/sample_crate/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "sample_crate"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))'.dependencies]
|
||||
machineid-rs = "1.2.4"
|
||||
|
||||
[dependencies]
|
||||
num = "0.4"
|
||||
image = "0.24.3"
|
||||
chrono = { version = "0.4.31", features = ["wasmbind"] }
|
||||
35
native/sample_crate/src/lib.rs
Normal file
35
native/sample_crate/src/lib.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//! This crate is only for demonstration purposes.
|
||||
//! You might want to remove this crate in production.
|
||||
|
||||
pub use mandelbrot::{mandelbrot, Point, Size};
|
||||
|
||||
mod mandelbrot;
|
||||
|
||||
// This is just a simple Rust function.
|
||||
|
||||
pub fn add_seven(before: i32) -> i32 {
|
||||
before + 7
|
||||
}
|
||||
|
||||
// `machineid_rs` only supports desktop platforms.
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
|
||||
pub fn get_hardward_id() -> Option<String> {
|
||||
let mut builder = machineid_rs::IdBuilder::new(machineid_rs::Encryption::MD5);
|
||||
builder
|
||||
.add_component(machineid_rs::HWIDComponent::SystemID)
|
||||
.add_component(machineid_rs::HWIDComponent::CPUCores);
|
||||
let hwid = builder.build("mykey").unwrap();
|
||||
Some(hwid)
|
||||
}
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
pub fn get_hardward_id() -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
// `chrono` supports all platforms when `wasmbind` feature is enabled.
|
||||
|
||||
use chrono::{offset, DateTime};
|
||||
pub fn get_current_time() -> DateTime<offset::Local> {
|
||||
offset::Local::now()
|
||||
}
|
||||
172
native/sample_crate/src/mandelbrot.rs
Normal file
172
native/sample_crate/src/mandelbrot.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
//! Mandelbrot is copied and modified from
|
||||
//! https://github.com/ProgrammingRust/mandelbrot/blob/task-queue/src/main.rs and
|
||||
//! https://github.com/Ducolnd/rust-mandelbrot/blob/master/src/main.rs
|
||||
|
||||
use image::codecs::png::PngEncoder;
|
||||
use image::*;
|
||||
use num::Complex;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Size {
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Point {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
/// Try to determine if `c` is in the Mandelbrot set, using at most `limit`
|
||||
/// iterations to decide.
|
||||
///
|
||||
/// If `c` is not a member, return `Some(i)`, where `i` is the number of
|
||||
/// iterations it took for `c` to leave the circle of radius two centered on the
|
||||
/// origin. If `c` seems to be a member (more precisely, if we reached the
|
||||
/// iteration limit without being able to prove that `c` is not a member),
|
||||
/// return `None`.
|
||||
fn escape_time(c: Complex<f64>, limit: usize) -> Option<usize> {
|
||||
let mut z = Complex { re: 0.0, im: 0.0 };
|
||||
for i in 0..limit {
|
||||
if z.norm_sqr() > 4.0 {
|
||||
return Some(i);
|
||||
}
|
||||
z = z * z + c;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Given the row and column of a pixel in the output image, return the
|
||||
/// corresponding point on the complex plane.
|
||||
///
|
||||
/// `bounds` is a pair giving the width and height of the image in pixels.
|
||||
/// `pixel` is a (column, row) pair indicating a particular pixel in that image.
|
||||
/// The `upper_left` and `lower_right` parameters are points on the complex
|
||||
/// plane designating the area our image covers.
|
||||
fn pixel_to_point(
|
||||
bounds: (usize, usize),
|
||||
pixel: (usize, usize),
|
||||
upper_left: Complex<f64>,
|
||||
lower_right: Complex<f64>,
|
||||
) -> Complex<f64> {
|
||||
let (width, height) = (
|
||||
lower_right.re - upper_left.re,
|
||||
upper_left.im - lower_right.im,
|
||||
);
|
||||
Complex {
|
||||
re: upper_left.re + pixel.0 as f64 * width / bounds.0 as f64,
|
||||
im: upper_left.im - pixel.1 as f64 * height / bounds.1 as f64,
|
||||
// Why subtraction here? pixel.1 increases as we go down,
|
||||
// but the imaginary component increases as we go up.
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pixel_to_point() {
|
||||
assert_eq!(
|
||||
pixel_to_point(
|
||||
(100, 200),
|
||||
(25, 175),
|
||||
Complex { re: -1.0, im: 1.0 },
|
||||
Complex { re: 1.0, im: -1.0 },
|
||||
),
|
||||
Complex {
|
||||
re: -0.5,
|
||||
im: -0.75,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// Render a rectangle of the Mandelbrot set into a buffer of pixels.
|
||||
///
|
||||
/// The `bounds` argument gives the width and height of the buffer `pixels`,
|
||||
/// which holds one grayscale pixel per byte. The `upper_left` and `lower_right`
|
||||
/// arguments specify points on the complex plane corresponding to the upper-
|
||||
/// left and lower-right corners of the pixel buffer.
|
||||
fn render(
|
||||
pixels: &mut [u8],
|
||||
bounds: (usize, usize),
|
||||
upper_left: Complex<f64>,
|
||||
lower_right: Complex<f64>,
|
||||
) {
|
||||
assert_eq!(pixels.len(), bounds.0 * bounds.1);
|
||||
|
||||
for row in 0..bounds.1 {
|
||||
for column in 0..bounds.0 {
|
||||
let point = pixel_to_point(bounds, (column, row), upper_left, lower_right);
|
||||
pixels[row * bounds.0 + column] = match escape_time(point, 255) {
|
||||
None => 0,
|
||||
Some(count) => 255 - count as u8,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn colorize(grey_pixels: &[u8]) -> Vec<u8> {
|
||||
let mut ans = vec![0u8; grey_pixels.len() * 3];
|
||||
for i in 0..grey_pixels.len() {
|
||||
let (r, g, b) = colorize_pixel(grey_pixels[i]);
|
||||
ans[i * 3] = r;
|
||||
ans[i * 3 + 1] = g;
|
||||
ans[i * 3 + 2] = b;
|
||||
}
|
||||
ans
|
||||
}
|
||||
|
||||
const A: f64 = 1.0 * (1.0 / std::f64::consts::LOG2_10);
|
||||
const B: f64 = (1.0 / (3.0 * std::f64::consts::SQRT_2)) * (1.0 / std::f64::consts::LOG2_10);
|
||||
|
||||
pub fn colorize_pixel(it: u8) -> (u8, u8, u8) {
|
||||
if it == 0 {
|
||||
return (0, 0, 0);
|
||||
}
|
||||
let it = it as f64;
|
||||
|
||||
let c: f64 = (1.0_f64 / ((7.0 * 3.0_f64).powf(1.0 / 8.0))) * (1.0 / std::f64::consts::LOG2_10);
|
||||
|
||||
let r = 255.0 * ((1.0 - (A * it).cos()) / 2.0);
|
||||
let g = 255.0 * ((1.0 - (B * it).cos()) / 2.0);
|
||||
let b = 255.0 * ((1.0 - (c * it).cos()) / 2.0);
|
||||
|
||||
// print!(" {:?} ", [r, g, b]);
|
||||
|
||||
(r as u8, b as u8, g as u8)
|
||||
}
|
||||
|
||||
/// Write the buffer `pixels`, whose dimensions are given by `bounds`, to the
|
||||
/// file named `filename`.
|
||||
fn write_image(pixels: &[u8], bounds: (usize, usize)) -> Option<Vec<u8>> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let encoder = PngEncoder::new(&mut buf);
|
||||
#[allow(deprecated)]
|
||||
let result = encoder.encode(pixels, bounds.0 as u32, bounds.1 as u32, ColorType::Rgb8);
|
||||
|
||||
match result {
|
||||
Ok(_) => Some(buf),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mandelbrot(image_size: Size, zoom_point: Point, scale: f64) -> Option<Vec<u8>> {
|
||||
let bounds = (image_size.width as usize, image_size.height as usize);
|
||||
let upper_left = Complex::new(zoom_point.x - scale, zoom_point.y - scale);
|
||||
let lower_right = Complex::new(zoom_point.x + scale, zoom_point.y + scale);
|
||||
|
||||
let mut pixels = vec![0; bounds.0 * bounds.1];
|
||||
let bands = pixels.chunks_mut(bounds.0 * bounds.1).enumerate();
|
||||
|
||||
for (i, band) in bands {
|
||||
let top = bounds.1 * i;
|
||||
let height = band.len() / bounds.0;
|
||||
let band_bounds = (bounds.0, height);
|
||||
let band_upper_left = pixel_to_point(bounds, (0, top), upper_left, lower_right);
|
||||
let band_lower_right =
|
||||
pixel_to_point(bounds, (bounds.0, top + height), upper_left, lower_right);
|
||||
render(band, band_bounds, band_upper_left, band_lower_right);
|
||||
}
|
||||
|
||||
write_image(&colorize(&pixels), bounds)
|
||||
}
|
||||
16
pubspec.lock
16
pubspec.lock
|
|
@ -1052,6 +1052,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
protobuf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: protobuf
|
||||
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1092,6 +1100,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
rinf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: rinf
|
||||
sha256: "71162fe4f4619cddf8eac869d56ceca50fe6e9d0ae249f3ce1bbf1f5457844ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.13.1"
|
||||
riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -41,12 +41,8 @@ dependencies:
|
|||
archive: ^3.3.9
|
||||
file_picker: ^5.3.3
|
||||
path_provider: ^2.1.0
|
||||
# image: ^4.0.17
|
||||
scrollable_positioned_list: ^0.3.5
|
||||
dart_eval: ^0.6.5
|
||||
# git:
|
||||
# url: https://github.com/kodjodevf/dart_eval.git
|
||||
# ref: 05c54b2
|
||||
json_path: ^0.6.2
|
||||
bot_toast: ^4.0.4
|
||||
flutter_web_auth_2: ^2.1.5
|
||||
|
|
@ -57,6 +53,8 @@ dependencies:
|
|||
media_kit_video: ^1.2.1
|
||||
media_kit_libs_video: ^1.0.3
|
||||
crypto: ^3.0.3
|
||||
rinf: ^4.13.1
|
||||
protobuf: ^3.1.0
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
media_kit_native_event_loop
|
||||
rinf
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
|
|
|||
Loading…
Reference in a new issue