crop borders finally works

This commit is contained in:
kodjomoustapha 2023-10-21 14:11:49 +01:00
parent c2a3e7859d
commit 36f95bb57b
57 changed files with 10310 additions and 243 deletions

View file

@ -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
View file

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

File diff suppressed because it is too large Load diff

8
Cargo.toml Normal file
View 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"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
syntax = "proto3";
package crop_borders;
message ReadRequest {
bytes image = 1;
}

7
native/README.md Normal file
View 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
View 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"

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

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

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

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

View 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 */

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

View 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 */

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

View 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 */

View file

@ -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 */

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

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

View 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()?)))
}
}

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

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

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

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

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

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

View file

@ -0,0 +1,4 @@
//! Code originally sourced from wasm-bindgen's repository.
pub mod transfer;
pub mod worker;

View file

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

View file

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

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

View 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::*;

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

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

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

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

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

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

View file

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

View file

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

View file

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