diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce145e2..056f9c1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/.gitignore b/.gitignore index 24476c5..3faab82 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,10 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# Rust related +.cargo/ +target/ + +# Generated messages +*/**/messages/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4118396 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1355 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "allo-isolate" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56b7997817c178b853573e8bdfb6c3afe02810b43f17d766d6703560074b0c3" +dependencies = [ + "atomic", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async_wasm_task" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8daff5f108e2aaea387e7bbd080c3c61fb9533b6b78345b8ff49d11bd31892ad" +dependencies = [ + "futures-channel", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dart-sys" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8b5680b5c2cc52f50acb2457d9b3a3b58adcca785db13a0e3655626f601de6" +dependencies = [ + "cc", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hub" +version = "0.1.0" +dependencies = [ + "allo-isolate", + "async_wasm_task", + "backtrace", + "bytemuck", + "dart-sys", + "image", + "js-sys", + "lazy_static", + "libc", + "os-thread-local", + "prost", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "qoi", + "tiff", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "machineid-rs" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ceb4d434d69d7199abc3036541ba6ef86767a4356e3077d5a3419f85b70b14" +dependencies = [ + "hex", + "hmac", + "md-5", + "serde", + "serde_json", + "sha-1", + "sha2", + "sysinfo", + "uuid", + "whoami", + "winreg", + "wmi", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "os-thread-local" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7fc7fa9ea7dc8907f9b10e730106ed0011926e7f5abb382530ac91d1af2b7c" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "png" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "sample_crate" +version = "0.1.0" +dependencies = [ + "chrono", + "image", + "machineid-rs", + "num", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysinfo" +version = "0.29.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "tokio" +version = "1.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +dependencies = [ + "backtrace", + "num_cpus", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "uuid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +dependencies = [ + "getrandom", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.38", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-interface" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winreg" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "wmi" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daffb44abb7d2e87a1233aa17fdbde0d55b890b32a23a1f908895b87fa6f1a00" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror", + "windows", +] + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cf8e466 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index e0e1c49..cadb03b 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,49 @@ Get the app from our [releases page](https://github.com/kodjodevf/mangayomi/rele ## Disclaimer The developer of this application does not have any affiliation with the content providers available. + +## Using Rust Inside Flutter + +This project leverages Flutter for GUI and Rust for the backend logic, +utilizing the capabilities of the +[Rinf](https://pub.dev/packages/rinf) framework. + +To run and build this app, you need to have +[Flutter SDK](https://docs.flutter.dev/get-started/install), +[Rust toolchain](https://www.rust-lang.org/tools/install), +and [Protobuf compiler](https://grpc.io/docs/protoc-installation) +installed on your system. +You can check that your system is ready with the commands below. +Note that all the Flutter subcomponents should be installed. + +```bash +rustc --version +protoc --version +flutter doctor +``` + +You also need to have the CLI tool for Rinf ready. + +```bash +cargo install rinf +``` + +Messages sent between Dart and Rust are implemented using Protobuf. +If you have newly cloned the project repository +or made changes to the `.proto` files in the `./messages` directory, +run the following command: + +```bash +rinf message +``` + +Now you can run and build this app just like any other Flutter projects. + +```bash +flutter run +``` + +For detailed instructions on writing Rust and Flutter together, +please refer to Rinf's [documentation](https://rinf-docs.cunarist.com). + + diff --git a/android/app/build.gradle b/android/app/build.gradle index d123be0..e16cb50 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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 diff --git a/lib/main.dart b/lib/main.dart index e11598f..91cc104 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 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. diff --git a/lib/modules/manga/reader/image_view_center.dart b/lib/modules/manga/reader/image_view_center.dart index f7c1c22..ae11b87 100644 --- a/lib/modules/manga/reader/image_view_center.dart +++ b/lib/modules/manga/reader/image_view_center.dart @@ -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( + 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) { diff --git a/lib/modules/manga/reader/image_view_vertical.dart b/lib/modules/manga/reader/image_view_vertical.dart index f1d5751..5111311 100644 --- a/lib/modules/manga/reader/image_view_vertical.dart +++ b/lib/modules/manga/reader/image_view_vertical.dart @@ -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( + 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, diff --git a/lib/modules/manga/reader/providers/auto_crop_image_provider.dart b/lib/modules/manga/reader/providers/auto_crop_image_provider.dart deleted file mode 100644 index fa64f4a..0000000 --- a/lib/modules/manga/reader/providers/auto_crop_image_provider.dart +++ /dev/null @@ -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 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 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; -} diff --git a/lib/modules/manga/reader/providers/crop_borders_provider.dart b/lib/modules/manga/reader/providers/crop_borders_provider.dart new file mode 100644 index 0000000..17e40db --- /dev/null +++ b/lib/modules/manga/reader/providers/crop_borders_provider.dart @@ -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 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; +} diff --git a/lib/modules/manga/reader/providers/auto_crop_image_provider.g.dart b/lib/modules/manga/reader/providers/crop_borders_provider.g.dart similarity index 62% rename from lib/modules/manga/reader/providers/auto_crop_image_provider.g.dart rename to lib/modules/manga/reader/providers/crop_borders_provider.g.dart index f5932d1..44a9bd6 100644 --- a/lib/modules/manga/reader/providers/auto_crop_image_provider.g.dart +++ b/lib/modules/manga/reader/providers/crop_borders_provider.g.dart @@ -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> { - /// See also [autoCropImageBorder]. - const AutoCropImageBorderFamily(); +/// See also [cropBorders]. +class CropBordersFamily extends Family> { + /// 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> { _allTransitiveDependencies; @override - String? get name => r'autoCropImageBorderProvider'; + String? get name => r'cropBordersProvider'; } -/// See also [autoCropImageBorder]. -class AutoCropImageBorderProvider extends FutureProvider { - /// See also [autoCropImageBorder]. - AutoCropImageBorderProvider({ +/// See also [cropBorders]. +class CropBordersProvider extends FutureProvider { + /// 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 { @override Override overrideWith( - FutureOr Function(AutoCropImageBorderRef provider) create, + FutureOr 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 { @override FutureProviderElement 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 { } } -mixin AutoCropImageBorderRef on FutureProviderRef { +mixin CropBordersRef on FutureProviderRef { /// The parameter `datas` of this provider. UChapDataPreload get datas; @@ -163,14 +162,14 @@ mixin AutoCropImageBorderRef on FutureProviderRef { bool get cropBorder; } -class _AutoCropImageBorderProviderElement - extends FutureProviderElement with AutoCropImageBorderRef { - _AutoCropImageBorderProviderElement(super.provider); +class _CropBordersProviderElement extends FutureProviderElement + 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 diff --git a/lib/modules/manga/reader/reader_view.dart b/lib/modules/manga/reader/reader_view.dart index 32733a1..dcb4c87 100644 --- a/lib/modules/manga/reader/reader_view.dart +++ b/lib/modules/manga/reader/reader_view.dart @@ -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(offset: Offset(0.0, 0.0), blurRadius: 7.0) ], @@ -1104,6 +1108,8 @@ class _MangaChapterPageGalleryState readerMode == ReaderMode.webtoon; } + final List _cropBorderCheckList = []; + final StreamController _rebuildDetail = StreamController.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, diff --git a/lib/utils/crop_rect.dart b/lib/utils/crop_rect.dart deleted file mode 100644 index 8cc7e90..0000000 --- a/lib/utils/crop_rect.dart +++ /dev/null @@ -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; -} diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index a89171b..f706f29 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -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) diff --git a/messages/crop_borders.proto b/messages/crop_borders.proto new file mode 100644 index 0000000..10cd845 --- /dev/null +++ b/messages/crop_borders.proto @@ -0,0 +1,6 @@ +syntax = "proto3"; +package crop_borders; + +message ReadRequest { + bytes image = 1; +} diff --git a/native/README.md b/native/README.md new file mode 100644 index 0000000..f3b821f --- /dev/null +++ b/native/README.md @@ -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. diff --git a/native/hub/Cargo.toml b/native/hub/Cargo.toml new file mode 100644 index 0000000..e02f53c --- /dev/null +++ b/native/hub/Cargo.toml @@ -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" diff --git a/native/hub/src/bridge/api.rs b/native/hub/src/bridge/api.rs new file mode 100644 index 0000000..94043f3 --- /dev/null +++ b/native/hub/src/bridge/api.rs @@ -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>, + pub blob: Option>, +} + +/// Request object that is sent from Dart to Rust. +pub struct RustRequest { + pub resource: i32, + pub operation: RustOperation, + pub message: Option>, + pub blob: Option>, +} + +/// 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>, + pub blob: Option>, +} + +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 = RefCell>; +type SharedCell = Arc>>; + +type RustSignalStream = StreamSink; +type RustResponseStream = StreamSink; +type RustReportStream = StreamSink; +type RustRequestSender = Sender; +type RustRequestReceiver = Receiver; + +// Native: Main thread +// Web: Worker thread +thread_local! { + pub static REQUEST_SENDER: Cell = RefCell::new(None); +} + +// Native: `tokio` runtime threads +// Web: Worker thread +thread_local! { + pub static SIGNAL_STREAM: Cell = RefCell::new(None); + pub static RESPONSE_STREAM: Cell = RefCell::new(None); + pub static REPORT_STREAM: Cell = RefCell::new(None); +} + +// Native: All threads +// Web: Worker thread +lazy_static! { + pub static ref SIGNAL_STREAM_SHARED: SharedCell = + Arc::new(Mutex::new(RefCell::new(None))); + pub static ref RESPONSE_STREAM_SHARED: SharedCell = + Arc::new(Mutex::new(RefCell::new(None))); + pub static ref REPORT_STREAM_SHARED: SharedCell = + Arc::new(Mutex::new(RefCell::new(None))); + pub static ref REQUST_RECEIVER_SHARED: SharedCell = + Arc::new(Mutex::new(RefCell::new(None))); +} + +#[cfg(not(target_family = "wasm"))] +lazy_static! { + pub static ref TOKIO_RUNTIME: os_thread_local::ThreadLocal> = + os_thread_local::ThreadLocal::new(|| RefCell::new(None)); +} + +#[cfg(target_family = "wasm")] +thread_local! { + pub static IS_MAIN_STARTED: RefCell = RefCell::new(false); +} + +/// Returns a stream object in Dart that listens to Rust. +pub fn prepare_rust_signal_stream(signal_stream: StreamSink) { + 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) { + 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) { + 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(); + }); +} diff --git a/native/hub/src/bridge/bridge_engine/dart_api/BUILD.gn b/native/hub/src/bridge/bridge_engine/dart_api/BUILD.gn new file mode 100644 index 0000000..2b10262 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/BUILD.gn @@ -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}}" ] +} diff --git a/native/hub/src/bridge/bridge_engine/dart_api/README.md b/native/hub/src/bridge/bridge_engine/dart_api/README.md new file mode 100644 index 0000000..fb63149 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/README.md @@ -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. diff --git a/native/hub/src/bridge/bridge_engine/dart_api/analyze_snapshot_api.h b/native/hub/src/bridge/bridge_engine/dart_api/analyze_snapshot_api.h new file mode 100644 index 0000000..0e68d5c --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/analyze_snapshot_api.h @@ -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 + +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_ diff --git a/native/hub/src/bridge/bridge_engine/dart_api/bin/dart_io_api.h b/native/hub/src/bridge/bridge_engine/dart_api/bin/dart_io_api.h new file mode 100644 index 0000000..cc64797 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/bin/dart_io_api.h @@ -0,0 +1,69 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef RUNTIME_INCLUDE_BIN_DART_IO_API_H_ +#define RUNTIME_INCLUDE_BIN_DART_IO_API_H_ + +#include "dart_tools_api.h" + +namespace dart { +namespace bin { + +// Bootstraps 'dart:io'. +void BootstrapDartIo(); + +// Cleans up 'dart:io'. +void CleanupDartIo(); + +// Lets dart:io know where the system temporary directory is located. +// Currently only wired up on Android. +void SetSystemTempDirectory(const char* system_temp); + +// Tells the system whether to capture Stdout events. +void SetCaptureStdout(bool value); + +// Tells the system whether to capture Stderr events. +void SetCaptureStderr(bool value); + +// Should Stdout events be captured? +bool ShouldCaptureStdout(); + +// Should Stderr events be captured? +bool ShouldCaptureStderr(); + +// Set the executable name used by Platform.executable. +void SetExecutableName(const char* executable_name); + +// Set the arguments used by Platform.executableArguments. +void SetExecutableArguments(int script_index, char** argv); + +// Set dart:io implementation specific fields of Dart_EmbedderInformation. +void GetIOEmbedderInformation(Dart_EmbedderInformation* info); + +// Appropriate to assign to Dart_InitializeParams.file_open/read/write/close. +void* OpenFile(const char* name, bool write); +void ReadFile(uint8_t** data, intptr_t* file_len, void* stream); +void WriteFile(const void* buffer, intptr_t num_bytes, void* stream); +void CloseFile(void* stream); + +// Generates 'length' random bytes into 'buffer'. Returns true on success +// and false on failure. This is appropriate to assign to +// Dart_InitializeParams.entropy_source. +bool GetEntropy(uint8_t* buffer, intptr_t length); + +// Performs a lookup of the I/O Dart_NativeFunction with a specified 'name' and +// 'argument_count'. Returns NULL if no I/O native function with a matching +// name and parameter count is found. +Dart_NativeFunction LookupIONative(Dart_Handle name, + int argument_count, + bool* auto_setup_scope); + +// Returns the symbol for I/O native function 'nf'. Returns NULL if 'nf' is not +// a valid I/O native function. +const uint8_t* LookupIONativeSymbol(Dart_NativeFunction nf); + +} // namespace bin +} // namespace dart + +#endif // RUNTIME_INCLUDE_BIN_DART_IO_API_H_ diff --git a/native/hub/src/bridge/bridge_engine/dart_api/dart_api.h b/native/hub/src/bridge/bridge_engine/dart_api/dart_api.h new file mode 100644 index 0000000..73e5dad --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/dart_api.h @@ -0,0 +1,4168 @@ +/* + * Copyright (c) 2012, 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_H_ +#define RUNTIME_INCLUDE_DART_API_H_ + +/** \mainpage Dart Embedding API Reference + * + * This reference describes the Dart Embedding API, which is used to embed the + * Dart Virtual Machine within C/C++ applications. + * + * This reference is generated from the header include/dart_api.h. + */ + +/* __STDC_FORMAT_MACROS has to be defined before including to + * enable platform independent printf format specifiers. */ +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#include +#include +#include + +#ifdef __cplusplus +#define DART_EXTERN_C extern "C" +#else +#define DART_EXTERN_C extern +#endif + +#if defined(__CYGWIN__) +#error Tool chain and platform not supported. +#elif defined(_WIN32) +#if defined(DART_SHARED_LIB) +#define DART_EXPORT DART_EXTERN_C __declspec(dllexport) +#else +#define DART_EXPORT DART_EXTERN_C +#endif +#else +#if __GNUC__ >= 4 +#if defined(DART_SHARED_LIB) +#define DART_EXPORT \ + DART_EXTERN_C __attribute__((visibility("default"))) __attribute((used)) +#else +#define DART_EXPORT DART_EXTERN_C +#endif +#else +#error Tool chain not supported. +#endif +#endif + +#if __GNUC__ +#define DART_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#elif _MSC_VER +#define DART_WARN_UNUSED_RESULT _Check_return_ +#else +#define DART_WARN_UNUSED_RESULT +#endif + +/* + * ======= + * Handles + * ======= + */ + +/** + * An isolate is the unit of concurrency in Dart. Each isolate has + * its own memory and thread of control. No state is shared between + * isolates. Instead, isolates communicate by message passing. + * + * Each thread keeps track of its current isolate, which is the + * isolate which is ready to execute on the current thread. The + * current isolate may be NULL, in which case no isolate is ready to + * execute. Most of the Dart apis require there to be a current + * isolate in order to function without error. The current isolate is + * set by any call to Dart_CreateIsolateGroup or Dart_EnterIsolate. + */ +typedef struct _Dart_Isolate* Dart_Isolate; +typedef struct _Dart_IsolateGroup* Dart_IsolateGroup; + +/** + * An object reference managed by the Dart VM garbage collector. + * + * Because the garbage collector may move objects, it is unsafe to + * refer to objects directly. Instead, we refer to objects through + * handles, which are known to the garbage collector and updated + * automatically when the object is moved. Handles should be passed + * by value (except in cases like out-parameters) and should never be + * allocated on the heap. + * + * Most functions in the Dart Embedding API return a handle. When a + * function completes normally, this will be a valid handle to an + * object in the Dart VM heap. This handle may represent the result of + * the operation or it may be a special valid handle used merely to + * indicate successful completion. Note that a valid handle may in + * some cases refer to the null object. + * + * --- Error handles --- + * + * When a function encounters a problem that prevents it from + * completing normally, it returns an error handle (See Dart_IsError). + * An error handle has an associated error message that gives more + * details about the problem (See Dart_GetError). + * + * There are four kinds of error handles that can be produced, + * depending on what goes wrong: + * + * - Api error handles are produced when an api function is misused. + * This happens when a Dart embedding api function is called with + * invalid arguments or in an invalid context. + * + * - Unhandled exception error handles are produced when, during the + * execution of Dart code, an exception is thrown but not caught. + * Prototypically this would occur during a call to Dart_Invoke, but + * it can occur in any function which triggers the execution of Dart + * code (for example, Dart_ToString). + * + * An unhandled exception error provides access to an exception and + * stacktrace via the functions Dart_ErrorGetException and + * Dart_ErrorGetStackTrace. + * + * - Compilation error handles are produced when, during the execution + * of Dart code, a compile-time error occurs. As above, this can + * occur in any function which triggers the execution of Dart code. + * + * - Fatal error handles are produced when the system wants to shut + * down the current isolate. + * + * --- Propagating errors --- + * + * When an error handle is returned from the top level invocation of + * Dart code in a program, the embedder must handle the error as they + * see fit. Often, the embedder will print the error message produced + * by Dart_Error and exit the program. + * + * When an error is returned while in the body of a native function, + * it can be propagated up the call stack by calling + * Dart_PropagateError, Dart_SetReturnValue, or Dart_ThrowException. + * Errors should be propagated unless there is a specific reason not + * to. If an error is not propagated then it is ignored. For + * example, if an unhandled exception error is ignored, that + * effectively "catches" the unhandled exception. Fatal errors must + * always be propagated. + * + * When an error is propagated, any current scopes created by + * Dart_EnterScope will be exited. + * + * Using Dart_SetReturnValue to propagate an exception is somewhat + * more convenient than using Dart_PropagateError, and should be + * preferred for reasons discussed below. + * + * Dart_PropagateError and Dart_ThrowException do not return. Instead + * they transfer control non-locally using a setjmp-like mechanism. + * This can be inconvenient if you have resources that you need to + * clean up before propagating the error. + * + * When relying on Dart_PropagateError, we often return error handles + * rather than propagating them from helper functions. Consider the + * following contrived example: + * + * 1 Dart_Handle isLongStringHelper(Dart_Handle arg) { + * 2 intptr_t* length = 0; + * 3 result = Dart_StringLength(arg, &length); + * 4 if (Dart_IsError(result)) { + * 5 return result; + * 6 } + * 7 return Dart_NewBoolean(length > 100); + * 8 } + * 9 + * 10 void NativeFunction_isLongString(Dart_NativeArguments args) { + * 11 Dart_EnterScope(); + * 12 AllocateMyResource(); + * 13 Dart_Handle arg = Dart_GetNativeArgument(args, 0); + * 14 Dart_Handle result = isLongStringHelper(arg); + * 15 if (Dart_IsError(result)) { + * 16 FreeMyResource(); + * 17 Dart_PropagateError(result); + * 18 abort(); // will not reach here + * 19 } + * 20 Dart_SetReturnValue(result); + * 21 FreeMyResource(); + * 22 Dart_ExitScope(); + * 23 } + * + * In this example, we have a native function which calls a helper + * function to do its work. On line 5, the helper function could call + * Dart_PropagateError, but that would not give the native function a + * chance to call FreeMyResource(), causing a leak. Instead, the + * helper function returns the error handle to the caller, giving the + * caller a chance to clean up before propagating the error handle. + * + * When an error is propagated by calling Dart_SetReturnValue, the + * native function will be allowed to complete normally and then the + * exception will be propagated only once the native call + * returns. This can be convenient, as it allows the C code to clean + * up normally. + * + * The example can be written more simply using Dart_SetReturnValue to + * propagate the error. + * + * 1 Dart_Handle isLongStringHelper(Dart_Handle arg) { + * 2 intptr_t* length = 0; + * 3 result = Dart_StringLength(arg, &length); + * 4 if (Dart_IsError(result)) { + * 5 return result + * 6 } + * 7 return Dart_NewBoolean(length > 100); + * 8 } + * 9 + * 10 void NativeFunction_isLongString(Dart_NativeArguments args) { + * 11 Dart_EnterScope(); + * 12 AllocateMyResource(); + * 13 Dart_Handle arg = Dart_GetNativeArgument(args, 0); + * 14 Dart_SetReturnValue(isLongStringHelper(arg)); + * 15 FreeMyResource(); + * 16 Dart_ExitScope(); + * 17 } + * + * In this example, the call to Dart_SetReturnValue on line 14 will + * either return the normal return value or the error (potentially + * generated on line 3). The call to FreeMyResource on line 15 will + * execute in either case. + * + * --- Local and persistent handles --- + * + * Local handles are allocated within the current scope (see + * Dart_EnterScope) and go away when the current scope exits. Unless + * otherwise indicated, callers should assume that all functions in + * the Dart embedding api return local handles. + * + * Persistent handles are allocated within the current isolate. They + * can be used to store objects across scopes. Persistent handles have + * the lifetime of the current isolate unless they are explicitly + * deallocated (see Dart_DeletePersistentHandle). + * The type Dart_Handle represents a handle (both local and persistent). + * The type Dart_PersistentHandle is a Dart_Handle and it is used to + * document that a persistent handle is expected as a parameter to a call + * or the return value from a call is a persistent handle. + * + * FinalizableHandles are persistent handles which are auto deleted when + * the object is garbage collected. It is never safe to use these handles + * unless you know the object is still reachable. + * + * WeakPersistentHandles are persistent handles which are automatically set + * to point Dart_Null when the object is garbage collected. They are not auto + * deleted, so it is safe to use them after the object has become unreachable. + */ +typedef struct _Dart_Handle* Dart_Handle; +typedef Dart_Handle Dart_PersistentHandle; +typedef struct _Dart_WeakPersistentHandle* Dart_WeakPersistentHandle; +typedef struct _Dart_FinalizableHandle* Dart_FinalizableHandle; +// These structs are versioned by DART_API_DL_MAJOR_VERSION, bump the +// version when changing this struct. + +typedef void (*Dart_HandleFinalizer)(void* isolate_callback_data, void* peer); + +/** + * Is this an error handle? + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsError(Dart_Handle handle); + +/** + * Is this an api error handle? + * + * Api error handles are produced when an api function is misused. + * This happens when a Dart embedding api function is called with + * invalid arguments or in an invalid context. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsApiError(Dart_Handle handle); + +/** + * Is this an unhandled exception error handle? + * + * Unhandled exception error handles are produced when, during the + * execution of Dart code, an exception is thrown but not caught. + * This can occur in any function which triggers the execution of Dart + * code. + * + * See Dart_ErrorGetException and Dart_ErrorGetStackTrace. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsUnhandledExceptionError(Dart_Handle handle); + +/** + * Is this a compilation error handle? + * + * Compilation error handles are produced when, during the execution + * of Dart code, a compile-time error occurs. This can occur in any + * function which triggers the execution of Dart code. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsCompilationError(Dart_Handle handle); + +/** + * Is this a fatal error handle? + * + * Fatal error handles are produced when the system wants to shut down + * the current isolate. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsFatalError(Dart_Handle handle); + +/** + * Gets the error message from an error handle. + * + * Requires there to be a current isolate. + * + * \return A C string containing an error message if the handle is + * error. An empty C string ("") if the handle is valid. This C + * String is scope allocated and is only valid until the next call + * to Dart_ExitScope. +*/ +DART_EXPORT const char* Dart_GetError(Dart_Handle handle); + +/** + * Is this an error handle for an unhandled exception? + */ +DART_EXPORT bool Dart_ErrorHasException(Dart_Handle handle); + +/** + * Gets the exception Object from an unhandled exception error handle. + */ +DART_EXPORT Dart_Handle Dart_ErrorGetException(Dart_Handle handle); + +/** + * Gets the stack trace Object from an unhandled exception error handle. + */ +DART_EXPORT Dart_Handle Dart_ErrorGetStackTrace(Dart_Handle handle); + +/** + * Produces an api error handle with the provided error message. + * + * Requires there to be a current isolate. + * + * \param error the error message. + */ +DART_EXPORT Dart_Handle Dart_NewApiError(const char* error); +DART_EXPORT Dart_Handle Dart_NewCompilationError(const char* error); + +/** + * Produces a new unhandled exception error handle. + * + * Requires there to be a current isolate. + * + * \param exception An instance of a Dart object to be thrown or + * an ApiError or CompilationError handle. + * When an ApiError or CompilationError handle is passed in + * a string object of the error message is created and it becomes + * the Dart object to be thrown. + */ +DART_EXPORT Dart_Handle Dart_NewUnhandledExceptionError(Dart_Handle exception); + +/** + * Propagates an error. + * + * If the provided handle is an unhandled exception error, this + * function will cause the unhandled exception to be rethrown. This + * will proceed in the standard way, walking up Dart frames until an + * appropriate 'catch' block is found, executing 'finally' blocks, + * etc. + * + * If the error is not an unhandled exception error, we will unwind + * the stack to the next C frame. Intervening Dart frames will be + * discarded; specifically, 'finally' blocks will not execute. This + * is the standard way that compilation errors (and the like) are + * handled by the Dart runtime. + * + * In either case, when an error is propagated any current scopes + * created by Dart_EnterScope will be exited. + * + * See the additional discussion under "Propagating Errors" at the + * beginning of this file. + * + * \param handle An error handle (See Dart_IsError) + * + * On success, this function does not return. On failure, the + * process is terminated. + */ +DART_EXPORT void Dart_PropagateError(Dart_Handle handle); + +/** + * Converts an object to a string. + * + * May generate an unhandled exception error. + * + * \return The converted string if no error occurs during + * the conversion. If an error does occur, an error handle is + * returned. + */ +DART_EXPORT Dart_Handle Dart_ToString(Dart_Handle object); + +/** + * Checks to see if two handles refer to identically equal objects. + * + * If both handles refer to instances, this is equivalent to using the top-level + * function identical() from dart:core. Otherwise, returns whether the two + * argument handles refer to the same object. + * + * \param obj1 An object to be compared. + * \param obj2 An object to be compared. + * + * \return True if the objects are identically equal. False otherwise. + */ +DART_EXPORT bool Dart_IdentityEquals(Dart_Handle obj1, Dart_Handle obj2); + +/** + * Allocates a handle in the current scope from a persistent handle. + */ +DART_EXPORT Dart_Handle Dart_HandleFromPersistent(Dart_PersistentHandle object); + +/** + * Allocates a handle in the current scope from a weak persistent handle. + * + * This will be a handle to Dart_Null if the object has been garbage collected. + */ +DART_EXPORT Dart_Handle +Dart_HandleFromWeakPersistent(Dart_WeakPersistentHandle object); + +/** + * Allocates a persistent handle for an object. + * + * This handle has the lifetime of the current isolate unless it is + * explicitly deallocated by calling Dart_DeletePersistentHandle. + * + * Requires there to be a current isolate. + */ +DART_EXPORT Dart_PersistentHandle Dart_NewPersistentHandle(Dart_Handle object); + +/** + * Assign value of local handle to a persistent handle. + * + * Requires there to be a current isolate. + * + * \param obj1 A persistent handle whose value needs to be set. + * \param obj2 An object whose value needs to be set to the persistent handle. + */ +DART_EXPORT void Dart_SetPersistentHandle(Dart_PersistentHandle obj1, + Dart_Handle obj2); + +/** + * Deallocates a persistent handle. + * + * Requires there to be a current isolate group. + */ +DART_EXPORT void Dart_DeletePersistentHandle(Dart_PersistentHandle object); + +/** + * Allocates a weak persistent handle for an object. + * + * This handle has the lifetime of the current isolate. The handle can also be + * explicitly deallocated by calling Dart_DeleteWeakPersistentHandle. + * + * If the object becomes unreachable the callback is invoked with the peer as + * argument. The callback can be executed on any thread, will have a current + * isolate group, but will not have a current isolate. The callback can only + * call Dart_DeletePersistentHandle or Dart_DeleteWeakPersistentHandle. This + * gives the embedder the ability to cleanup data associated with the object. + * The handle will point to the Dart_Null object after the finalizer has been + * run. It is illegal to call into the VM with any other Dart_* functions from + * the callback. If the handle is deleted before the object becomes + * unreachable, the callback is never invoked. + * + * Requires there to be a current isolate. + * + * \param object An object with identity. + * \param peer A pointer to a native object or NULL. This value is + * provided to callback when it is invoked. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A function pointer that will be invoked sometime + * after the object is garbage collected, unless the handle has been deleted. + * A valid callback needs to be specified it cannot be NULL. + * + * \return The weak persistent handle or NULL. NULL is returned in case of bad + * parameters. + */ +DART_EXPORT Dart_WeakPersistentHandle +Dart_NewWeakPersistentHandle(Dart_Handle object, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Deletes the given weak persistent [object] handle. + * + * Requires there to be a current isolate group. + */ +DART_EXPORT void Dart_DeleteWeakPersistentHandle( + Dart_WeakPersistentHandle object); + +/** + * Updates the external memory size for the given weak persistent handle. + * + * May trigger garbage collection. + */ +DART_EXPORT void Dart_UpdateExternalSize(Dart_WeakPersistentHandle object, + intptr_t external_allocation_size); + +/** + * Allocates a finalizable handle for an object. + * + * This handle has the lifetime of the current isolate group unless the object + * pointed to by the handle is garbage collected, in this case the VM + * automatically deletes the handle after invoking the callback associated + * with the handle. The handle can also be explicitly deallocated by + * calling Dart_DeleteFinalizableHandle. + * + * If the object becomes unreachable the callback is invoked with the + * the peer as argument. The callback can be executed on any thread, will have + * an isolate group, but will not have a current isolate. The callback can only + * call Dart_DeletePersistentHandle or Dart_DeleteWeakPersistentHandle. + * This gives the embedder the ability to cleanup data associated with the + * object and clear out any cached references to the handle. All references to + * this handle after the callback will be invalid. It is illegal to call into + * the VM with any other Dart_* functions from the callback. If the handle is + * deleted before the object becomes unreachable, the callback is never + * invoked. + * + * Requires there to be a current isolate. + * + * \param object An object with identity. + * \param peer A pointer to a native object or NULL. This value is + * provided to callback when it is invoked. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A function pointer that will be invoked sometime + * after the object is garbage collected, unless the handle has been deleted. + * A valid callback needs to be specified it cannot be NULL. + * + * \return The finalizable handle or NULL. NULL is returned in case of bad + * parameters. + */ +DART_EXPORT Dart_FinalizableHandle +Dart_NewFinalizableHandle(Dart_Handle object, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Deletes the given finalizable [object] handle. + * + * The caller has to provide the actual Dart object the handle was created from + * to prove the object (and therefore the finalizable handle) is still alive. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_DeleteFinalizableHandle(Dart_FinalizableHandle object, + Dart_Handle strong_ref_to_object); + +/** + * Updates the external memory size for the given finalizable handle. + * + * The caller has to provide the actual Dart object the handle was created from + * to prove the object (and therefore the finalizable handle) is still alive. + * + * May trigger garbage collection. + */ +DART_EXPORT void Dart_UpdateFinalizableExternalSize( + Dart_FinalizableHandle object, + Dart_Handle strong_ref_to_object, + intptr_t external_allocation_size); + +/* + * ========================== + * Initialization and Globals + * ========================== + */ + +/** + * Gets the version string for the Dart VM. + * + * The version of the Dart VM can be accessed without initializing the VM. + * + * \return The version string for the embedded Dart VM. + */ +DART_EXPORT const char* Dart_VersionString(void); + +/** + * Isolate specific flags are set when creating a new isolate using the + * Dart_IsolateFlags structure. + * + * Current version of flags is encoded in a 32-bit integer with 16 bits used + * for each part. + */ + +#define DART_FLAGS_CURRENT_VERSION (0x0000000c) + +typedef struct { + int32_t version; + bool enable_asserts; + bool use_field_guards; + bool use_osr; + bool obfuscate; + bool load_vmservice_library; + bool copy_parent_code; + bool null_safety; + bool is_system_isolate; + bool snapshot_is_dontneed_safe; + bool branch_coverage; +} Dart_IsolateFlags; + +/** + * Initialize Dart_IsolateFlags with correct version and default values. + */ +DART_EXPORT void Dart_IsolateFlagsInitialize(Dart_IsolateFlags* flags); + +/** + * An isolate creation and initialization callback function. + * + * This callback, provided by the embedder, is called when the VM + * needs to create an isolate. The callback should create an isolate + * by calling Dart_CreateIsolateGroup and load any scripts required for + * execution. + * + * This callback may be called on a different thread than the one + * running the parent isolate. + * + * When the function returns NULL, it is the responsibility of this + * function to ensure that Dart_ShutdownIsolate has been called if + * required (for example, if the isolate was created successfully by + * Dart_CreateIsolateGroup() but the root library fails to load + * successfully, then the function should call Dart_ShutdownIsolate + * before returning). + * + * When the function returns NULL, the function should set *error to + * a malloc-allocated buffer containing a useful error message. The + * caller of this function (the VM) will make sure that the buffer is + * freed. + * + * \param script_uri The uri of the main source file or snapshot to load. + * Either the URI of the parent isolate set in Dart_CreateIsolateGroup for + * Isolate.spawn, or the argument to Isolate.spawnUri canonicalized by the + * library tag handler of the parent isolate. + * The callback is responsible for loading the program by a call to + * Dart_LoadScriptFromKernel. + * \param main The name of the main entry point this isolate will + * eventually run. This is provided for advisory purposes only to + * improve debugging messages. The main function is not invoked by + * this function. + * \param package_root Ignored. + * \param package_config Uri of the package configuration file (either in format + * of .packages or .dart_tool/package_config.json) for this isolate + * to resolve package imports against. If this parameter is not passed the + * package resolution of the parent isolate should be used. + * \param flags Default flags for this isolate being spawned. Either inherited + * from the spawning isolate or passed as parameters when spawning the + * isolate from Dart code. + * \param isolate_data The isolate data which was passed to the + * parent isolate when it was created by calling Dart_CreateIsolateGroup(). + * \param error A structure into which the embedder can place a + * C string containing an error message in the case of failures. + * + * \return The embedder returns NULL if the creation and + * initialization was not successful and the isolate if successful. + */ +typedef Dart_Isolate (*Dart_IsolateGroupCreateCallback)( + const char* script_uri, + const char* main, + const char* package_root, + const char* package_config, + Dart_IsolateFlags* flags, + void* isolate_data, + char** error); + +/** + * An isolate initialization callback function. + * + * This callback, provided by the embedder, is called when the VM has created an + * isolate within an existing isolate group (i.e. from the same source as an + * existing isolate). + * + * The callback should setup native resolvers and might want to set a custom + * message handler via [Dart_SetMessageNotifyCallback] and mark the isolate as + * runnable. + * + * This callback may be called on a different thread than the one + * running the parent isolate. + * + * When the function returns `false`, it is the responsibility of this + * function to ensure that `Dart_ShutdownIsolate` has been called. + * + * When the function returns `false`, the function should set *error to + * a malloc-allocated buffer containing a useful error message. The + * caller of this function (the VM) will make sure that the buffer is + * freed. + * + * \param child_isolate_data The callback data to associate with the new + * child isolate. + * \param error A structure into which the embedder can place a + * C string containing an error message in the case the initialization fails. + * + * \return The embedder returns true if the initialization was successful and + * false otherwise (in which case the VM will terminate the isolate). + */ +typedef bool (*Dart_InitializeIsolateCallback)(void** child_isolate_data, + char** error); + +/** + * An isolate shutdown callback function. + * + * This callback, provided by the embedder, is called before the vm + * shuts down an isolate. The isolate being shutdown will be the current + * isolate. It is safe to run Dart code. + * + * This function should be used to dispose of native resources that + * are allocated to an isolate in order to avoid leaks. + * + * \param isolate_group_data The same callback data which was passed to the + * isolate group when it was created. + * \param isolate_data The same callback data which was passed to the isolate + * when it was created. + */ +typedef void (*Dart_IsolateShutdownCallback)(void* isolate_group_data, + void* isolate_data); + +/** + * An isolate cleanup callback function. + * + * This callback, provided by the embedder, is called after the vm + * shuts down an isolate. There will be no current isolate and it is *not* + * safe to run Dart code. + * + * This function should be used to dispose of native resources that + * are allocated to an isolate in order to avoid leaks. + * + * \param isolate_group_data The same callback data which was passed to the + * isolate group when it was created. + * \param isolate_data The same callback data which was passed to the isolate + * when it was created. + */ +typedef void (*Dart_IsolateCleanupCallback)(void* isolate_group_data, + void* isolate_data); + +/** + * An isolate group cleanup callback function. + * + * This callback, provided by the embedder, is called after the vm + * shuts down an isolate group. + * + * This function should be used to dispose of native resources that + * are allocated to an isolate in order to avoid leaks. + * + * \param isolate_group_data The same callback data which was passed to the + * isolate group when it was created. + * + */ +typedef void (*Dart_IsolateGroupCleanupCallback)(void* isolate_group_data); + +/** + * A thread start callback function. + * This callback, provided by the embedder, is called after a thread in the + * vm thread pool starts. + * This function could be used to adjust thread priority or attach native + * resources to the thread. + */ +typedef void (*Dart_ThreadStartCallback)(void); + +/** + * A thread death callback function. + * This callback, provided by the embedder, is called before a thread in the + * vm thread pool exits. + * This function could be used to dispose of native resources that + * are associated and attached to the thread, in order to avoid leaks. + */ +typedef void (*Dart_ThreadExitCallback)(void); + +/** + * Opens a file for reading or writing. + * + * Callback provided by the embedder for file operations. If the + * embedder does not allow file operations this callback can be + * NULL. + * + * \param name The name of the file to open. + * \param write A boolean variable which indicates if the file is to + * opened for writing. If there is an existing file it needs to truncated. + */ +typedef void* (*Dart_FileOpenCallback)(const char* name, bool write); + +/** + * Read contents of file. + * + * Callback provided by the embedder for file operations. If the + * embedder does not allow file operations this callback can be + * NULL. + * + * \param data Buffer allocated in the callback into which the contents + * of the file are read into. It is the responsibility of the caller to + * free this buffer. + * \param file_length A variable into which the length of the file is returned. + * In the case of an error this value would be -1. + * \param stream Handle to the opened file. + */ +typedef void (*Dart_FileReadCallback)(uint8_t** data, + intptr_t* file_length, + void* stream); + +/** + * Write data into file. + * + * Callback provided by the embedder for file operations. If the + * embedder does not allow file operations this callback can be + * NULL. + * + * \param data Buffer which needs to be written into the file. + * \param length Length of the buffer. + * \param stream Handle to the opened file. + */ +typedef void (*Dart_FileWriteCallback)(const void* data, + intptr_t length, + void* stream); + +/** + * Closes the opened file. + * + * Callback provided by the embedder for file operations. If the + * embedder does not allow file operations this callback can be + * NULL. + * + * \param stream Handle to the opened file. + */ +typedef void (*Dart_FileCloseCallback)(void* stream); + +typedef bool (*Dart_EntropySource)(uint8_t* buffer, intptr_t length); + +/** + * Callback provided by the embedder that is used by the vmservice isolate + * to request the asset archive. The asset archive must be an uncompressed tar + * archive that is stored in a Uint8List. + * + * If the embedder has no vmservice isolate assets, the callback can be NULL. + * + * \return The embedder must return a handle to a Uint8List containing an + * uncompressed tar archive or null. + */ +typedef Dart_Handle (*Dart_GetVMServiceAssetsArchive)(void); + +/** + * The current version of the Dart_InitializeFlags. Should be incremented every + * time Dart_InitializeFlags changes in a binary incompatible way. + */ +#define DART_INITIALIZE_PARAMS_CURRENT_VERSION (0x00000007) + +/** Forward declaration */ +struct Dart_CodeObserver; + +/** + * Callback provided by the embedder that is used by the VM to notify on code + * object creation, *before* it is invoked the first time. + * This is useful for embedders wanting to e.g. keep track of PCs beyond + * the lifetime of the garbage collected code objects. + * Note that an address range may be used by more than one code object over the + * lifecycle of a process. Clients of this function should record timestamps for + * these compilation events and when collecting PCs to disambiguate reused + * address ranges. + */ +typedef void (*Dart_OnNewCodeCallback)(struct Dart_CodeObserver* observer, + const char* name, + uintptr_t base, + uintptr_t size); + +typedef struct Dart_CodeObserver { + void* data; + + Dart_OnNewCodeCallback on_new_code; +} Dart_CodeObserver; + +/** + * Optional callback provided by the embedder that is used by the VM to + * implement registration of kernel blobs for the subsequent Isolate.spawnUri + * If no callback is provided, the registration of kernel blobs will throw + * an error. + * + * \param kernel_buffer A buffer which contains a kernel program. Callback + * should copy the contents of `kernel_buffer` as + * it may be freed immediately after registration. + * \param kernel_buffer_size The size of `kernel_buffer`. + * + * \return A C string representing URI which can be later used + * to spawn a new isolate. This C String should be scope allocated + * or owned by the embedder. + * Returns NULL if embedder runs out of memory. + */ +typedef const char* (*Dart_RegisterKernelBlobCallback)( + const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size); + +/** + * Optional callback provided by the embedder that is used by the VM to + * unregister kernel blobs. + * If no callback is provided, the unregistration of kernel blobs will throw + * an error. + * + * \param kernel_blob_uri URI of the kernel blob to unregister. + */ +typedef void (*Dart_UnregisterKernelBlobCallback)(const char* kernel_blob_uri); + +/** + * Describes how to initialize the VM. Used with Dart_Initialize. + */ +typedef struct { + /** + * Identifies the version of the struct used by the client. + * should be initialized to DART_INITIALIZE_PARAMS_CURRENT_VERSION. + */ + int32_t version; + + /** + * A buffer containing snapshot data, or NULL if no snapshot is provided. + * + * If provided, the buffer must remain valid until Dart_Cleanup returns. + */ + const uint8_t* vm_snapshot_data; + + /** + * A buffer containing a snapshot of precompiled instructions, or NULL if + * no snapshot is provided. + * + * If provided, the buffer must remain valid until Dart_Cleanup returns. + */ + const uint8_t* vm_snapshot_instructions; + + /** + * A function to be called during isolate group creation. + * See Dart_IsolateGroupCreateCallback. + */ + Dart_IsolateGroupCreateCallback create_group; + + /** + * A function to be called during isolate + * initialization inside an existing isolate group. + * See Dart_InitializeIsolateCallback. + */ + Dart_InitializeIsolateCallback initialize_isolate; + + /** + * A function to be called right before an isolate is shutdown. + * See Dart_IsolateShutdownCallback. + */ + Dart_IsolateShutdownCallback shutdown_isolate; + + /** + * A function to be called after an isolate was shutdown. + * See Dart_IsolateCleanupCallback. + */ + Dart_IsolateCleanupCallback cleanup_isolate; + + /** + * A function to be called after an isolate group is + * shutdown. See Dart_IsolateGroupCleanupCallback. + */ + Dart_IsolateGroupCleanupCallback cleanup_group; + + Dart_ThreadStartCallback thread_start; + Dart_ThreadExitCallback thread_exit; + Dart_FileOpenCallback file_open; + Dart_FileReadCallback file_read; + Dart_FileWriteCallback file_write; + Dart_FileCloseCallback file_close; + Dart_EntropySource entropy_source; + + /** + * A function to be called by the service isolate when it requires the + * vmservice assets archive. See Dart_GetVMServiceAssetsArchive. + */ + Dart_GetVMServiceAssetsArchive get_service_assets; + + bool start_kernel_isolate; + + /** + * An external code observer callback function. The observer can be invoked + * as early as during the Dart_Initialize() call. + */ + Dart_CodeObserver* code_observer; + + /** + * Kernel blob registration callback function. See Dart_RegisterKernelBlobCallback. + */ + Dart_RegisterKernelBlobCallback register_kernel_blob; + + /** + * Kernel blob unregistration callback function. See Dart_UnregisterKernelBlobCallback. + */ + Dart_UnregisterKernelBlobCallback unregister_kernel_blob; +} Dart_InitializeParams; + +/** + * Initializes the VM. + * + * \param params A struct containing initialization information. The version + * field of the struct must be DART_INITIALIZE_PARAMS_CURRENT_VERSION. + * + * \return NULL if initialization is successful. Returns an error message + * otherwise. The caller is responsible for freeing the error message. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_Initialize( + Dart_InitializeParams* params); + +/** + * Cleanup state in the VM before process termination. + * + * \return NULL if cleanup is successful. Returns an error message otherwise. + * The caller is responsible for freeing the error message. + * + * NOTE: This function must not be called on a thread that was created by the VM + * itself. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_Cleanup(void); + +/** + * Sets command line flags. Should be called before Dart_Initialize. + * + * \param argc The length of the arguments array. + * \param argv An array of arguments. + * + * \return NULL if successful. Returns an error message otherwise. + * The caller is responsible for freeing the error message. + * + * NOTE: This call does not store references to the passed in c-strings. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_SetVMFlags(int argc, + const char** argv); + +/** + * Returns true if the named VM flag is of boolean type, specified, and set to + * true. + * + * \param flag_name The name of the flag without leading punctuation + * (example: "enable_asserts"). + */ +DART_EXPORT bool Dart_IsVMFlagSet(const char* flag_name); + +/* + * ======== + * Isolates + * ======== + */ + +/** + * Creates a new isolate. The new isolate becomes the current isolate. + * + * A snapshot can be used to restore the VM quickly to a saved state + * and is useful for fast startup. If snapshot data is provided, the + * isolate will be started using that snapshot data. Requires a core snapshot or + * an app snapshot created by Dart_CreateSnapshot or + * Dart_CreatePrecompiledSnapshot* from a VM with the same version. + * + * Requires there to be no current isolate. + * + * \param script_uri The main source file or snapshot this isolate will load. + * The VM will provide this URI to the Dart_IsolateGroupCreateCallback when a + * child isolate is created by Isolate.spawn. The embedder should use a URI + * that allows it to load the same program into such a child isolate. + * \param name A short name for the isolate to improve debugging messages. + * Typically of the format 'foo.dart:main()'. + * \param isolate_snapshot_data Buffer containing the snapshot data of the + * isolate or NULL if no snapshot is provided. If provided, the buffer must + * remain valid until the isolate shuts down. + * \param isolate_snapshot_instructions Buffer containing the snapshot + * instructions of the isolate or NULL if no snapshot is provided. If + * provided, the buffer must remain valid until the isolate shuts down. + * \param flags Pointer to VM specific flags or NULL for default flags. + * \param isolate_group_data Embedder group data. This data can be obtained + * by calling Dart_IsolateGroupData and will be passed to the + * Dart_IsolateShutdownCallback, Dart_IsolateCleanupCallback, and + * Dart_IsolateGroupCleanupCallback. + * \param isolate_data Embedder data. This data will be passed to + * the Dart_IsolateGroupCreateCallback when new isolates are spawned from + * this parent isolate. + * \param error Returns NULL if creation is successful, an error message + * otherwise. The caller is responsible for calling free() on the error + * message. + * + * \return The new isolate on success, or NULL if isolate creation failed. + */ +DART_EXPORT Dart_Isolate +Dart_CreateIsolateGroup(const char* script_uri, + const char* name, + const uint8_t* isolate_snapshot_data, + const uint8_t* isolate_snapshot_instructions, + Dart_IsolateFlags* flags, + void* isolate_group_data, + void* isolate_data, + char** error); +/** + * Creates a new isolate inside the isolate group of [group_member]. + * + * Requires there to be no current isolate. + * + * \param group_member An isolate from the same group into which the newly created + * isolate should be born into. Other threads may not have entered / enter this + * member isolate. + * \param name A short name for the isolate for debugging purposes. + * \param shutdown_callback A callback to be called when the isolate is being + * shutdown (may be NULL). + * \param cleanup_callback A callback to be called when the isolate is being + * cleaned up (may be NULL). + * \param child_isolate_data The embedder-specific data associated with this isolate. + * \param error Set to NULL if creation is successful, set to an error + * message otherwise. The caller is responsible for calling free() on the + * error message. + * + * \return The newly created isolate on success, or NULL if isolate creation + * failed. + * + * If successful, the newly created isolate will become the current isolate. + */ +DART_EXPORT Dart_Isolate +Dart_CreateIsolateInGroup(Dart_Isolate group_member, + const char* name, + Dart_IsolateShutdownCallback shutdown_callback, + Dart_IsolateCleanupCallback cleanup_callback, + void* child_isolate_data, + char** error); + +/* TODO(turnidge): Document behavior when there is already a current + * isolate. */ + +/** + * Creates a new isolate from a Dart Kernel file. The new isolate + * becomes the current isolate. + * + * Requires there to be no current isolate. + * + * \param script_uri The main source file or snapshot this isolate will load. + * The VM will provide this URI to the Dart_IsolateGroupCreateCallback when a + * child isolate is created by Isolate.spawn. The embedder should use a URI that + * allows it to load the same program into such a child isolate. + * \param name A short name for the isolate to improve debugging messages. + * Typically of the format 'foo.dart:main()'. + * \param kernel_buffer A buffer which contains a kernel/DIL program. Must + * remain valid until isolate shutdown. + * \param kernel_buffer_size The size of `kernel_buffer`. + * \param flags Pointer to VM specific flags or NULL for default flags. + * \param isolate_group_data Embedder group data. This data can be obtained + * by calling Dart_IsolateGroupData and will be passed to the + * Dart_IsolateShutdownCallback, Dart_IsolateCleanupCallback, and + * Dart_IsolateGroupCleanupCallback. + * \param isolate_data Embedder data. This data will be passed to + * the Dart_IsolateGroupCreateCallback when new isolates are spawned from + * this parent isolate. + * \param error Returns NULL if creation is successful, an error message + * otherwise. The caller is responsible for calling free() on the error + * message. + * + * \return The new isolate on success, or NULL if isolate creation failed. + */ +DART_EXPORT Dart_Isolate +Dart_CreateIsolateGroupFromKernel(const char* script_uri, + const char* name, + const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size, + Dart_IsolateFlags* flags, + void* isolate_group_data, + void* isolate_data, + char** error); +/** + * Shuts down the current isolate. After this call, the current isolate is NULL. + * Any current scopes created by Dart_EnterScope will be exited. Invokes the + * shutdown callback and any callbacks of remaining weak persistent handles. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_ShutdownIsolate(void); +/* TODO(turnidge): Document behavior when there is no current isolate. */ + +/** + * Returns the current isolate. Will return NULL if there is no + * current isolate. + */ +DART_EXPORT Dart_Isolate Dart_CurrentIsolate(void); + +/** + * Returns the callback data associated with the current isolate. This + * data was set when the isolate got created or initialized. + */ +DART_EXPORT void* Dart_CurrentIsolateData(void); + +/** + * Returns the callback data associated with the given isolate. This + * data was set when the isolate got created or initialized. + */ +DART_EXPORT void* Dart_IsolateData(Dart_Isolate isolate); + +/** + * Returns the current isolate group. Will return NULL if there is no + * current isolate group. + */ +DART_EXPORT Dart_IsolateGroup Dart_CurrentIsolateGroup(void); + +/** + * Returns the callback data associated with the current isolate group. This + * data was passed to the isolate group when it was created. + */ +DART_EXPORT void* Dart_CurrentIsolateGroupData(void); + +/** + * Gets an id that uniquely identifies current isolate group. + * + * It is the responsibility of the caller to free the returned ID. + */ +typedef int64_t Dart_IsolateGroupId; +DART_EXPORT Dart_IsolateGroupId Dart_CurrentIsolateGroupId(); + +/** + * Returns the callback data associated with the specified isolate group. This + * data was passed to the isolate when it was created. + * The embedder is responsible for ensuring the consistency of this data + * with respect to the lifecycle of an isolate group. + */ +DART_EXPORT void* Dart_IsolateGroupData(Dart_Isolate isolate); + +/** + * Returns the debugging name for the current isolate. + * + * This name is unique to each isolate and should only be used to make + * debugging messages more comprehensible. + */ +DART_EXPORT Dart_Handle Dart_DebugName(void); + +/** + * Returns the debugging name for the current isolate. + * + * This name is unique to each isolate and should only be used to make + * debugging messages more comprehensible. + * + * The returned string is scope allocated and is only valid until the next call + * to Dart_ExitScope. + */ +DART_EXPORT const char* Dart_DebugNameToCString(void); + +/** + * Returns the ID for an isolate which is used to query the service protocol. + * + * It is the responsibility of the caller to free the returned ID. + */ +DART_EXPORT const char* Dart_IsolateServiceId(Dart_Isolate isolate); + +/** + * Enters an isolate. After calling this function, + * the current isolate will be set to the provided isolate. + * + * Requires there to be no current isolate. Multiple threads may not be in + * the same isolate at once. + */ +DART_EXPORT void Dart_EnterIsolate(Dart_Isolate isolate); + +/** + * Kills the given isolate. + * + * This function has the same effect as dart:isolate's + * Isolate.kill(priority:immediate). + * It can interrupt ordinary Dart code but not native code. If the isolate is + * in the middle of a long running native function, the isolate will not be + * killed until control returns to Dart. + * + * Does not require a current isolate. It is safe to kill the current isolate if + * there is one. + */ +DART_EXPORT void Dart_KillIsolate(Dart_Isolate isolate); + +/** + * Notifies the VM that the embedder expects to be idle until |deadline|. The VM + * may use this time to perform garbage collection or other tasks to avoid + * delays during execution of Dart code in the future. + * + * |deadline| is measured in microseconds against the system's monotonic time. + * This clock can be accessed via Dart_TimelineGetMicros(). + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_NotifyIdle(int64_t deadline); + +typedef void (*Dart_HeapSamplingReportCallback)(void* context, + intptr_t heap_size, + const char* cls_name, + void* data); + +typedef void* (*Dart_HeapSamplingCreateCallback)( + Dart_Isolate isolate, + Dart_IsolateGroup isolate_group); +typedef void (*Dart_HeapSamplingDeleteCallback)(void* data); + +/** + * Starts the heap sampling profiler for each thread in the VM. + */ +DART_EXPORT void Dart_EnableHeapSampling(); + +/* + * Stops the heap sampling profiler for each thread in the VM. + */ +DART_EXPORT void Dart_DisableHeapSampling(); + +/* Registers callbacks are invoked once per sampled allocation upon object + * allocation and garbage collection. + * + * |create_callback| can be used to associate additional data with the sampled + * allocation, such as a stack trace. This data pointer will be passed to + * |delete_callback| to allow for proper disposal when the object associated + * with the allocation sample is collected. + * + * The provided callbacks must not call into the VM and should do as little + * work as possible to avoid performance penalities during object allocation and + * garbage collection. + * + * NOTE: It is a fatal error to set either callback to null once they have been + * initialized. + */ +DART_EXPORT void Dart_RegisterHeapSamplingCallback( + Dart_HeapSamplingCreateCallback create_callback, + Dart_HeapSamplingDeleteCallback delete_callback); + +/* + * Reports the surviving allocation samples for all live isolate groups in the + * VM. + * + * When the callback is invoked: + * - |context| will be the context object provided when invoking + * |Dart_ReportSurvivingAllocations|. This can be safely set to null if not + * required. + * - |heap_size| will be equal to the size of the allocated object associated + * with the sample. + * - |cls_name| will be a C String representing + * the class name of the allocated object. This string is valid for the + * duration of the call to Dart_ReportSurvivingAllocations and can be + * freed by the VM at any point after the method returns. + * - |data| will be set to the data associated with the sample by + * |Dart_HeapSamplingCreateCallback|. + */ +DART_EXPORT void Dart_ReportSurvivingAllocations( + Dart_HeapSamplingReportCallback callback, + void* context); + +/* + * Sets the average heap sampling rate based on a number of |bytes| for each + * thread. + * + * In other words, approximately every |bytes| allocated will create a sample. + * Defaults to 512 KiB. + */ +DART_EXPORT void Dart_SetHeapSamplingPeriod(intptr_t bytes); + +/** + * Notifies the VM that the embedder expects the application's working set has + * recently shrunk significantly and is not expected to rise in the near future. + * The VM may spend O(heap-size) time performing clean up work. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_NotifyDestroyed(void); + +/** + * Notifies the VM that the system is running low on memory. + * + * Does not require a current isolate. Only valid after calling Dart_Initialize. + */ +DART_EXPORT void Dart_NotifyLowMemory(void); + +typedef enum { + /** + * Balanced + */ + Dart_PerformanceMode_Default, + /** + * Optimize for low latency, at the expense of throughput and memory overhead + * by performing work in smaller batches (requiring more overhead) or by + * delaying work (requiring more memory). An embedder should not remain in + * this mode indefinitely. + */ + Dart_PerformanceMode_Latency, + /** + * Optimize for high throughput, at the expense of latency and memory overhead + * by performing work in larger batches with more intervening growth. + */ + Dart_PerformanceMode_Throughput, + /** + * Optimize for low memory, at the expensive of throughput and latency by more + * frequently performing work. + */ + Dart_PerformanceMode_Memory, +} Dart_PerformanceMode; + +/** + * Set the desired performance trade-off. + * + * Requires a current isolate. + * + * Returns the previous performance mode. + */ +DART_EXPORT Dart_PerformanceMode +Dart_SetPerformanceMode(Dart_PerformanceMode mode); + +/** + * Starts the CPU sampling profiler. + */ +DART_EXPORT void Dart_StartProfiling(void); + +/** + * Stops the CPU sampling profiler. + * + * Note that some profile samples might still be taken after this function + * returns due to the asynchronous nature of the implementation on some + * platforms. + */ +DART_EXPORT void Dart_StopProfiling(void); + +/** + * Notifies the VM that the current thread should not be profiled until a + * matching call to Dart_ThreadEnableProfiling is made. + * + * NOTE: By default, if a thread has entered an isolate it will be profiled. + * This function should be used when an embedder knows a thread is about + * to make a blocking call and wants to avoid unnecessary interrupts by + * the profiler. + */ +DART_EXPORT void Dart_ThreadDisableProfiling(void); + +/** + * Notifies the VM that the current thread should be profiled. + * + * NOTE: It is only legal to call this function *after* calling + * Dart_ThreadDisableProfiling. + * + * NOTE: By default, if a thread has entered an isolate it will be profiled. + */ +DART_EXPORT void Dart_ThreadEnableProfiling(void); + +/** + * Register symbol information for the Dart VM's profiler and crash dumps. + * + * This consumes the output of //topaz/runtime/dart/profiler_symbols, which + * should be treated as opaque. + */ +DART_EXPORT void Dart_AddSymbols(const char* dso_name, + void* buffer, + intptr_t buffer_size); + +/** + * Exits an isolate. After this call, Dart_CurrentIsolate will + * return NULL. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_ExitIsolate(void); +/* TODO(turnidge): We don't want users of the api to be able to exit a + * "pure" dart isolate. Implement and document. */ + +/** + * Creates a full snapshot of the current isolate heap. + * + * A full snapshot is a compact representation of the dart vm isolate heap + * and dart isolate heap states. These snapshots are used to initialize + * the vm isolate on startup and fast initialization of an isolate. + * A Snapshot of the heap is created before any dart code has executed. + * + * Requires there to be a current isolate. Not available in the precompiled + * runtime (check Dart_IsPrecompiledRuntime). + * + * \param vm_snapshot_data_buffer Returns a pointer to a buffer containing the + * vm snapshot. This buffer is scope allocated and is only valid + * until the next call to Dart_ExitScope. + * \param vm_snapshot_data_size Returns the size of vm_snapshot_data_buffer. + * \param isolate_snapshot_data_buffer Returns a pointer to a buffer containing + * the isolate snapshot. This buffer is scope allocated and is only valid + * until the next call to Dart_ExitScope. + * \param isolate_snapshot_data_size Returns the size of + * isolate_snapshot_data_buffer. + * \param is_core Create a snapshot containing core libraries. + * Such snapshot should be agnostic to null safety mode. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateSnapshot(uint8_t** vm_snapshot_data_buffer, + intptr_t* vm_snapshot_data_size, + uint8_t** isolate_snapshot_data_buffer, + intptr_t* isolate_snapshot_data_size, + bool is_core); + +/** + * Returns whether the buffer contains a kernel file. + * + * \param buffer Pointer to a buffer that might contain a kernel binary. + * \param buffer_size Size of the buffer. + * + * \return Whether the buffer contains a kernel binary (full or partial). + */ +DART_EXPORT bool Dart_IsKernel(const uint8_t* buffer, intptr_t buffer_size); + +/** + * Make isolate runnable. + * + * When isolates are spawned, this function is used to indicate that + * the creation and initialization (including script loading) of the + * isolate is complete and the isolate can start. + * This function expects there to be no current isolate. + * + * \param isolate The isolate to be made runnable. + * + * \return NULL if successful. Returns an error message otherwise. The caller + * is responsible for freeing the error message. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_IsolateMakeRunnable( + Dart_Isolate isolate); + +/* + * ================== + * Messages and Ports + * ================== + */ + +/** + * A port is used to send or receive inter-isolate messages + */ +typedef int64_t Dart_Port; + +/** + * ILLEGAL_PORT is a port number guaranteed never to be associated with a valid + * port. + */ +#define ILLEGAL_PORT ((Dart_Port)0) + +/** + * A message notification callback. + * + * This callback allows the embedder to provide a custom wakeup mechanism for + * the delivery of inter-isolate messages. This function is called once per + * message on an arbitrary thread. It is the responsibility of the embedder to + * eventually call Dart_HandleMessage once per callback received with the + * destination isolate set as the current isolate to process the message. + */ +typedef void (*Dart_MessageNotifyCallback)(Dart_Isolate destination_isolate); + +/** + * Allows embedders to provide a custom wakeup mechanism for the delivery of + * inter-isolate messages. This setting only applies to the current isolate. + * + * This mechanism is optional: if not provided, the isolate will be scheduled on + * a VM-managed thread pool. An embedder should provide this callback if it + * wants to run an isolate on a specific thread or to interleave handling of + * inter-isolate messages with other event sources. + * + * Most embedders will only call this function once, before isolate + * execution begins. If this function is called after isolate + * execution begins, the embedder is responsible for threading issues. + */ +DART_EXPORT void Dart_SetMessageNotifyCallback( + Dart_MessageNotifyCallback message_notify_callback); +/* TODO(turnidge): Consider moving this to isolate creation so that it + * is impossible to mess up. */ + +/** + * Query the current message notify callback for the isolate. + * + * \return The current message notify callback for the isolate. + */ +DART_EXPORT Dart_MessageNotifyCallback Dart_GetMessageNotifyCallback(void); + +/** + * The VM's default message handler supports pausing an isolate before it + * processes the first message and right after the it processes the isolate's + * final message. This can be controlled for all isolates by two VM flags: + * + * `--pause-isolates-on-start` + * `--pause-isolates-on-exit` + * + * Additionally, Dart_SetShouldPauseOnStart and Dart_SetShouldPauseOnExit can be + * used to control this behaviour on a per-isolate basis. + * + * When an embedder is using a Dart_MessageNotifyCallback the embedder + * needs to cooperate with the VM so that the service protocol can report + * accurate information about isolates and so that tools such as debuggers + * work reliably. + * + * The following functions can be used to implement pausing on start and exit. + */ + +/** + * If the VM flag `--pause-isolates-on-start` was passed this will be true. + * + * \return A boolean value indicating if pause on start was requested. + */ +DART_EXPORT bool Dart_ShouldPauseOnStart(void); + +/** + * Override the VM flag `--pause-isolates-on-start` for the current isolate. + * + * \param should_pause Should the isolate be paused on start? + * + * NOTE: This must be called before Dart_IsolateMakeRunnable. + */ +DART_EXPORT void Dart_SetShouldPauseOnStart(bool should_pause); + +/** + * Is the current isolate paused on start? + * + * \return A boolean value indicating if the isolate is paused on start. + */ +DART_EXPORT bool Dart_IsPausedOnStart(void); + +/** + * Called when the embedder has paused the current isolate on start and when + * the embedder has resumed the isolate. + * + * \param paused Is the isolate paused on start? + */ +DART_EXPORT void Dart_SetPausedOnStart(bool paused); + +/** + * If the VM flag `--pause-isolates-on-exit` was passed this will be true. + * + * \return A boolean value indicating if pause on exit was requested. + */ +DART_EXPORT bool Dart_ShouldPauseOnExit(void); + +/** + * Override the VM flag `--pause-isolates-on-exit` for the current isolate. + * + * \param should_pause Should the isolate be paused on exit? + * + */ +DART_EXPORT void Dart_SetShouldPauseOnExit(bool should_pause); + +/** + * Is the current isolate paused on exit? + * + * \return A boolean value indicating if the isolate is paused on exit. + */ +DART_EXPORT bool Dart_IsPausedOnExit(void); + +/** + * Called when the embedder has paused the current isolate on exit and when + * the embedder has resumed the isolate. + * + * \param paused Is the isolate paused on exit? + */ +DART_EXPORT void Dart_SetPausedOnExit(bool paused); + +/** + * Called when the embedder has caught a top level unhandled exception error + * in the current isolate. + * + * NOTE: It is illegal to call this twice on the same isolate without first + * clearing the sticky error to null. + * + * \param error The unhandled exception error. + */ +DART_EXPORT void Dart_SetStickyError(Dart_Handle error); + +/** + * Does the current isolate have a sticky error? + */ +DART_EXPORT bool Dart_HasStickyError(void); + +/** + * Gets the sticky error for the current isolate. + * + * \return A handle to the sticky error object or null. + */ +DART_EXPORT Dart_Handle Dart_GetStickyError(void); + +/** + * Handles the next pending message for the current isolate. + * + * May generate an unhandled exception error. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_HandleMessage(void); + +/** + * Drains the microtask queue, then blocks the calling thread until the current + * isolate receives a message, then handles all messages. + * + * \param timeout_millis When non-zero, the call returns after the indicated + number of milliseconds even if no message was received. + * \return A valid handle if no error occurs, otherwise an error handle. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_WaitForEvent(int64_t timeout_millis); + +/** + * Handles any pending messages for the vm service for the current + * isolate. + * + * This function may be used by an embedder at a breakpoint to avoid + * pausing the vm service. + * + * This function can indirectly cause the message notify callback to + * be called. + * + * \return true if the vm service requests the program resume + * execution, false otherwise + */ +DART_EXPORT bool Dart_HandleServiceMessages(void); + +/** + * Does the current isolate have pending service messages? + * + * \return true if the isolate has pending service messages, false otherwise. + */ +DART_EXPORT bool Dart_HasServiceMessages(void); + +/** + * Processes any incoming messages for the current isolate. + * + * This function may only be used when the embedder has not provided + * an alternate message delivery mechanism with + * Dart_SetMessageCallbacks. It is provided for convenience. + * + * This function waits for incoming messages for the current + * isolate. As new messages arrive, they are handled using + * Dart_HandleMessage. The routine exits when all ports to the + * current isolate are closed. + * + * \return A valid handle if the run loop exited successfully. If an + * exception or other error occurs while processing messages, an + * error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_RunLoop(void); + +/** + * Lets the VM run message processing for the isolate. + * + * This function expects there to a current isolate and the current isolate + * must not have an active api scope. The VM will take care of making the + * isolate runnable (if not already), handles its message loop and will take + * care of shutting the isolate down once it's done. + * + * \param errors_are_fatal Whether uncaught errors should be fatal. + * \param on_error_port A port to notify on uncaught errors (or ILLEGAL_PORT). + * \param on_exit_port A port to notify on exit (or ILLEGAL_PORT). + * \param error A non-NULL pointer which will hold an error message if the call + * fails. The error has to be free()ed by the caller. + * + * \return If successful the VM takes ownership of the isolate and takes care + * of its message loop. If not successful the caller retains ownership of the + * isolate. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT bool Dart_RunLoopAsync( + bool errors_are_fatal, + Dart_Port on_error_port, + Dart_Port on_exit_port, + char** error); + +/* TODO(turnidge): Should this be removed from the public api? */ + +/** + * Gets the main port id for the current isolate. + */ +DART_EXPORT Dart_Port Dart_GetMainPortId(void); + +/** + * Does the current isolate have live ReceivePorts? + * + * A ReceivePort is live when it has not been closed. + */ +DART_EXPORT bool Dart_HasLivePorts(void); + +/** + * Posts a message for some isolate. The message is a serialized + * object. + * + * Requires there to be a current isolate. + * + * For posting messages outside of an isolate see \ref Dart_PostCObject. + * + * \param port_id The destination port. + * \param object An object from the current isolate. + * + * \return True if the message was posted. + */ +DART_EXPORT bool Dart_Post(Dart_Port port_id, Dart_Handle object); + +/** + * Returns a new SendPort with the provided port id. + * + * \param port_id The destination port. + * + * \return A new SendPort if no errors occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewSendPort(Dart_Port port_id); + +/** + * Gets the SendPort id for the provided SendPort. + * \param port A SendPort object whose id is desired. + * \param port_id Returns the id of the SendPort. + * \return Success if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_SendPortGetId(Dart_Handle port, + Dart_Port* port_id); + +/* + * ====== + * Scopes + * ====== + */ + +/** + * Enters a new scope. + * + * All new local handles will be created in this scope. Additionally, + * some functions may return "scope allocated" memory which is only + * valid within this scope. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_EnterScope(void); + +/** + * Exits a scope. + * + * The previous scope (if any) becomes the current scope. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_ExitScope(void); + +/** + * The Dart VM uses "zone allocation" for temporary structures. Zones + * support very fast allocation of small chunks of memory. The chunks + * cannot be deallocated individually, but instead zones support + * deallocating all chunks in one fast operation. + * + * This function makes it possible for the embedder to allocate + * temporary data in the VMs zone allocator. + * + * Zone allocation is possible: + * 1. when inside a scope where local handles can be allocated + * 2. when processing a message from a native port in a native port + * handler + * + * All the memory allocated this way will be reclaimed either on the + * next call to Dart_ExitScope or when the native port handler exits. + * + * \param size Size of the memory to allocate. + * + * \return A pointer to the allocated memory. NULL if allocation + * failed. Failure might due to is no current VM zone. + */ +DART_EXPORT uint8_t* Dart_ScopeAllocate(intptr_t size); + +/* + * ======= + * Objects + * ======= + */ + +/** + * Returns the null object. + * + * \return A handle to the null object. + */ +DART_EXPORT Dart_Handle Dart_Null(void); + +/** + * Is this object null? + */ +DART_EXPORT bool Dart_IsNull(Dart_Handle object); + +/** + * Returns the empty string object. + * + * \return A handle to the empty string object. + */ +DART_EXPORT Dart_Handle Dart_EmptyString(void); + +/** + * Returns types that are not classes, and which therefore cannot be looked up + * as library members by Dart_GetType. + * + * \return A handle to the dynamic, void or Never type. + */ +DART_EXPORT Dart_Handle Dart_TypeDynamic(void); +DART_EXPORT Dart_Handle Dart_TypeVoid(void); +DART_EXPORT Dart_Handle Dart_TypeNever(void); + +/** + * Checks if the two objects are equal. + * + * The result of the comparison is returned through the 'equal' + * parameter. The return value itself is used to indicate success or + * failure, not equality. + * + * May generate an unhandled exception error. + * + * \param obj1 An object to be compared. + * \param obj2 An object to be compared. + * \param equal Returns the result of the equality comparison. + * + * \return A valid handle if no error occurs during the comparison. + */ +DART_EXPORT Dart_Handle Dart_ObjectEquals(Dart_Handle obj1, + Dart_Handle obj2, + bool* equal); + +/** + * Is this object an instance of some type? + * + * The result of the test is returned through the 'instanceof' parameter. + * The return value itself is used to indicate success or failure. + * + * \param object An object. + * \param type A type. + * \param instanceof Return true if 'object' is an instance of type 'type'. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_ObjectIsType(Dart_Handle object, + Dart_Handle type, + bool* instanceof); + +/** + * Query object type. + * + * \param object Some Object. + * + * \return true if Object is of the specified type. + */ +DART_EXPORT bool Dart_IsInstance(Dart_Handle object); +DART_EXPORT bool Dart_IsNumber(Dart_Handle object); +DART_EXPORT bool Dart_IsInteger(Dart_Handle object); +DART_EXPORT bool Dart_IsDouble(Dart_Handle object); +DART_EXPORT bool Dart_IsBoolean(Dart_Handle object); +DART_EXPORT bool Dart_IsString(Dart_Handle object); +DART_EXPORT bool Dart_IsStringLatin1(Dart_Handle object); /* (ISO-8859-1) */ +DART_EXPORT bool Dart_IsExternalString(Dart_Handle object); +DART_EXPORT bool Dart_IsList(Dart_Handle object); +DART_EXPORT bool Dart_IsMap(Dart_Handle object); +DART_EXPORT bool Dart_IsLibrary(Dart_Handle object); +DART_EXPORT bool Dart_IsType(Dart_Handle handle); +DART_EXPORT bool Dart_IsFunction(Dart_Handle handle); +DART_EXPORT bool Dart_IsVariable(Dart_Handle handle); +DART_EXPORT bool Dart_IsTypeVariable(Dart_Handle handle); +DART_EXPORT bool Dart_IsClosure(Dart_Handle object); +DART_EXPORT bool Dart_IsTypedData(Dart_Handle object); +DART_EXPORT bool Dart_IsByteBuffer(Dart_Handle object); +DART_EXPORT bool Dart_IsFuture(Dart_Handle object); + +/* + * ========= + * Instances + * ========= + */ + +/* + * For the purposes of the embedding api, not all objects returned are + * Dart language objects. Within the api, we use the term 'Instance' + * to indicate handles which refer to true Dart language objects. + * + * TODO(turnidge): Reorganize the "Object" section above, pulling down + * any functions that more properly belong here. */ + +/** + * Gets the type of a Dart language object. + * + * \param instance Some Dart object. + * + * \return If no error occurs, the type is returned. Otherwise an + * error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_InstanceGetType(Dart_Handle instance); + +/** + * Returns the name for the provided class type. + * + * \return A valid string handle if no error occurs during the + * operation. + */ +DART_EXPORT Dart_Handle Dart_ClassName(Dart_Handle cls_type); + +/** + * Returns the name for the provided function or method. + * + * \return A valid string handle if no error occurs during the + * operation. + */ +DART_EXPORT Dart_Handle Dart_FunctionName(Dart_Handle function); + +/** + * Returns a handle to the owner of a function. + * + * The owner of an instance method or a static method is its defining + * class. The owner of a top-level function is its defining + * library. The owner of the function of a non-implicit closure is the + * function of the method or closure that defines the non-implicit + * closure. + * + * \return A valid handle to the owner of the function, or an error + * handle if the argument is not a valid handle to a function. + */ +DART_EXPORT Dart_Handle Dart_FunctionOwner(Dart_Handle function); + +/** + * Determines whether a function handle refers to a static function + * of method. + * + * For the purposes of the embedding API, a top-level function is + * implicitly declared static. + * + * \param function A handle to a function or method declaration. + * \param is_static Returns whether the function or method is declared static. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_FunctionIsStatic(Dart_Handle function, + bool* is_static); + +/** + * Is this object a closure resulting from a tear-off (closurized method)? + * + * Returns true for closures produced when an ordinary method is accessed + * through a getter call. Returns false otherwise, in particular for closures + * produced from local function declarations. + * + * \param object Some Object. + * + * \return true if Object is a tear-off. + */ +DART_EXPORT bool Dart_IsTearOff(Dart_Handle object); + +/** + * Retrieves the function of a closure. + * + * \return A handle to the function of the closure, or an error handle if the + * argument is not a closure. + */ +DART_EXPORT Dart_Handle Dart_ClosureFunction(Dart_Handle closure); + +/** + * Returns a handle to the library which contains class. + * + * \return A valid handle to the library with owns class, null if the class + * has no library or an error handle if the argument is not a valid handle + * to a class type. + */ +DART_EXPORT Dart_Handle Dart_ClassLibrary(Dart_Handle cls_type); + +/* + * ============================= + * Numbers, Integers and Doubles + * ============================= + */ + +/** + * Does this Integer fit into a 64-bit signed integer? + * + * \param integer An integer. + * \param fits Returns true if the integer fits into a 64-bit signed integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerFitsIntoInt64(Dart_Handle integer, + bool* fits); + +/** + * Does this Integer fit into a 64-bit unsigned integer? + * + * \param integer An integer. + * \param fits Returns true if the integer fits into a 64-bit unsigned integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerFitsIntoUint64(Dart_Handle integer, + bool* fits); + +/** + * Returns an Integer with the provided value. + * + * \param value The value of the integer. + * + * \return The Integer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewInteger(int64_t value); + +/** + * Returns an Integer with the provided value. + * + * \param value The unsigned value of the integer. + * + * \return The Integer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewIntegerFromUint64(uint64_t value); + +/** + * Returns an Integer with the provided value. + * + * \param value The value of the integer represented as a C string + * containing a hexadecimal number. + * + * \return The Integer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewIntegerFromHexCString(const char* value); + +/** + * Gets the value of an Integer. + * + * The integer must fit into a 64-bit signed integer, otherwise an error occurs. + * + * \param integer An Integer. + * \param value Returns the value of the Integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerToInt64(Dart_Handle integer, + int64_t* value); + +/** + * Gets the value of an Integer. + * + * The integer must fit into a 64-bit unsigned integer, otherwise an + * error occurs. + * + * \param integer An Integer. + * \param value Returns the value of the Integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerToUint64(Dart_Handle integer, + uint64_t* value); + +/** + * Gets the value of an integer as a hexadecimal C string. + * + * \param integer An Integer. + * \param value Returns the value of the Integer as a hexadecimal C + * string. This C string is scope allocated and is only valid until + * the next call to Dart_ExitScope. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerToHexCString(Dart_Handle integer, + const char** value); + +/** + * Returns a Double with the provided value. + * + * \param value A double. + * + * \return The Double object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewDouble(double value); + +/** + * Gets the value of a Double + * + * \param double_obj A Double + * \param value Returns the value of the Double. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_DoubleValue(Dart_Handle double_obj, double* value); + +/** + * Returns a closure of static function 'function_name' in the class 'class_name' + * in the exported namespace of specified 'library'. + * + * \param library Library object + * \param cls_type Type object representing a Class + * \param function_name Name of the static function in the class + * + * \return A valid Dart instance if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_GetStaticMethodClosure(Dart_Handle library, + Dart_Handle cls_type, + Dart_Handle function_name); + +/* + * ======== + * Booleans + * ======== + */ + +/** + * Returns the True object. + * + * Requires there to be a current isolate. + * + * \return A handle to the True object. + */ +DART_EXPORT Dart_Handle Dart_True(void); + +/** + * Returns the False object. + * + * Requires there to be a current isolate. + * + * \return A handle to the False object. + */ +DART_EXPORT Dart_Handle Dart_False(void); + +/** + * Returns a Boolean with the provided value. + * + * \param value true or false. + * + * \return The Boolean object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewBoolean(bool value); + +/** + * Gets the value of a Boolean + * + * \param boolean_obj A Boolean + * \param value Returns the value of the Boolean. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_BooleanValue(Dart_Handle boolean_obj, bool* value); + +/* + * ======= + * Strings + * ======= + */ + +/** + * Gets the length of a String. + * + * \param str A String. + * \param length Returns the length of the String. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringLength(Dart_Handle str, intptr_t* length); + +/** + * Returns a String built from the provided C string + * (There is an implicit assumption that the C string passed in contains + * UTF-8 encoded characters and '\0' is considered as a termination + * character). + * + * \param str A C String + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromCString(const char* str); +/* TODO(turnidge): Document what happens when we run out of memory + * during this call. */ + +/** + * Returns a String built from an array of UTF-8 encoded characters. + * + * \param utf8_array An array of UTF-8 encoded characters. + * \param length The length of the codepoints array. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromUTF8(const uint8_t* utf8_array, + intptr_t length); + +/** + * Returns a String built from an array of UTF-16 encoded characters. + * + * \param utf16_array An array of UTF-16 encoded characters. + * \param length The length of the codepoints array. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromUTF16(const uint16_t* utf16_array, + intptr_t length); + +/** + * Returns a String built from an array of UTF-32 encoded characters. + * + * \param utf32_array An array of UTF-32 encoded characters. + * \param length The length of the codepoints array. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromUTF32(const int32_t* utf32_array, + intptr_t length); + +/** + * Returns a String which references an external array of + * Latin-1 (ISO-8859-1) encoded characters. + * + * \param latin1_array Array of Latin-1 encoded characters. This must not move. + * \param length The length of the characters array. + * \param peer An external pointer to associate with this string. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A callback to be called when this string is finalized. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle +Dart_NewExternalLatin1String(const uint8_t* latin1_array, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Returns a String which references an external array of UTF-16 encoded + * characters. + * + * \param utf16_array An array of UTF-16 encoded characters. This must not move. + * \param length The length of the characters array. + * \param peer An external pointer to associate with this string. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A callback to be called when this string is finalized. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle +Dart_NewExternalUTF16String(const uint16_t* utf16_array, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Gets the C string representation of a String. + * (It is a sequence of UTF-8 encoded values with a '\0' termination.) + * + * \param str A string. + * \param cstr Returns the String represented as a C string. + * This C string is scope allocated and is only valid until + * the next call to Dart_ExitScope. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToCString(Dart_Handle str, + const char** cstr); + +/** + * Gets a UTF-8 encoded representation of a String. + * + * Any unpaired surrogate code points in the string will be converted as + * replacement characters (U+FFFD, 0xEF 0xBF 0xBD in UTF-8). If you need + * to preserve unpaired surrogates, use the Dart_StringToUTF16 function. + * + * \param str A string. + * \param utf8_array Returns the String represented as UTF-8 code + * units. This UTF-8 array is scope allocated and is only valid + * until the next call to Dart_ExitScope. + * \param length Used to return the length of the array which was + * actually used. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToUTF8(Dart_Handle str, + uint8_t** utf8_array, + intptr_t* length); + +/** + * Gets the data corresponding to the string object. This function returns + * the data only for Latin-1 (ISO-8859-1) string objects. For all other + * string objects it returns an error. + * + * \param str A string. + * \param latin1_array An array allocated by the caller, used to return + * the string data. + * \param length Used to pass in the length of the provided array. + * Used to return the length of the array which was actually used. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToLatin1(Dart_Handle str, + uint8_t* latin1_array, + intptr_t* length); + +/** + * Gets the UTF-16 encoded representation of a string. + * + * \param str A string. + * \param utf16_array An array allocated by the caller, used to return + * the array of UTF-16 encoded characters. + * \param length Used to pass in the length of the provided array. + * Used to return the length of the array which was actually used. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToUTF16(Dart_Handle str, + uint16_t* utf16_array, + intptr_t* length); + +/** + * Gets the storage size in bytes of a String. + * + * \param str A String. + * \param size Returns the storage size in bytes of the String. + * This is the size in bytes needed to store the String. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringStorageSize(Dart_Handle str, intptr_t* size); + +/** + * Retrieves some properties associated with a String. + * Properties retrieved are: + * - character size of the string (one or two byte) + * - length of the string + * - peer pointer of string if it is an external string. + * \param str A String. + * \param char_size Returns the character size of the String. + * \param str_len Returns the length of the String. + * \param peer Returns the peer pointer associated with the String or 0 if + * there is no peer pointer for it. + * \return Success if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_StringGetProperties(Dart_Handle str, + intptr_t* char_size, + intptr_t* str_len, + void** peer); + +/* + * ===== + * Lists + * ===== + */ + +/** + * Returns a List of the desired length. + * + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewList(intptr_t length); + +typedef enum { + Dart_CoreType_Dynamic, + Dart_CoreType_Int, + Dart_CoreType_String, +} Dart_CoreType_Id; + +// TODO(bkonyi): convert this to use nullable types once NNBD is enabled. +/** + * Returns a List of the desired length with the desired legacy element type. + * + * \param element_type_id The type of elements of the list. + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns an error + * handle. + */ +DART_EXPORT Dart_Handle Dart_NewListOf(Dart_CoreType_Id element_type_id, + intptr_t length); + +/** + * Returns a List of the desired length with the desired element type. + * + * \param element_type Handle to a nullable type object. E.g., from + * Dart_GetType or Dart_GetNullableType. + * + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewListOfType(Dart_Handle element_type, + intptr_t length); + +/** + * Returns a List of the desired length with the desired element type, filled + * with the provided object. + * + * \param element_type Handle to a type object. E.g., from Dart_GetType. + * + * \param fill_object Handle to an object of type 'element_type' that will be + * used to populate the list. This parameter can only be Dart_Null() if the + * length of the list is 0 or 'element_type' is a nullable type. + * + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewListOfTypeFilled(Dart_Handle element_type, + Dart_Handle fill_object, + intptr_t length); + +/** + * Gets the length of a List. + * + * May generate an unhandled exception error. + * + * \param list A List. + * \param length Returns the length of the List. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_ListLength(Dart_Handle list, intptr_t* length); + +/** + * Gets the Object at some index of a List. + * + * If the index is out of bounds, an error occurs. + * + * May generate an unhandled exception error. + * + * \param list A List. + * \param index A valid index into the List. + * + * \return The Object in the List at the specified index if no error + * occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_ListGetAt(Dart_Handle list, intptr_t index); + +/** +* Gets a range of Objects from a List. +* +* If any of the requested index values are out of bounds, an error occurs. +* +* May generate an unhandled exception error. +* +* \param list A List. +* \param offset The offset of the first item to get. +* \param length The number of items to get. +* \param result A pointer to fill with the objects. +* +* \return Success if no error occurs during the operation. +*/ +DART_EXPORT Dart_Handle Dart_ListGetRange(Dart_Handle list, + intptr_t offset, + intptr_t length, + Dart_Handle* result); + +/** + * Sets the Object at some index of a List. + * + * If the index is out of bounds, an error occurs. + * + * May generate an unhandled exception error. + * + * \param list A List. + * \param index A valid index into the List. + * \param value The Object to put in the List. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_ListSetAt(Dart_Handle list, + intptr_t index, + Dart_Handle value); + +/** + * May generate an unhandled exception error. + */ +DART_EXPORT Dart_Handle Dart_ListGetAsBytes(Dart_Handle list, + intptr_t offset, + uint8_t* native_array, + intptr_t length); + +/** + * May generate an unhandled exception error. + */ +DART_EXPORT Dart_Handle Dart_ListSetAsBytes(Dart_Handle list, + intptr_t offset, + const uint8_t* native_array, + intptr_t length); + +/* + * ==== + * Maps + * ==== + */ + +/** + * Gets the Object at some key of a Map. + * + * May generate an unhandled exception error. + * + * \param map A Map. + * \param key An Object. + * + * \return The value in the map at the specified key, null if the map does not + * contain the key, or an error handle. + */ +DART_EXPORT Dart_Handle Dart_MapGetAt(Dart_Handle map, Dart_Handle key); + +/** + * Returns whether the Map contains a given key. + * + * May generate an unhandled exception error. + * + * \param map A Map. + * + * \return A handle on a boolean indicating whether map contains the key. + * Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_MapContainsKey(Dart_Handle map, Dart_Handle key); + +/** + * Gets the list of keys of a Map. + * + * May generate an unhandled exception error. + * + * \param map A Map. + * + * \return The list of key Objects if no error occurs. Otherwise returns an + * error handle. + */ +DART_EXPORT Dart_Handle Dart_MapKeys(Dart_Handle map); + +/* + * ========== + * Typed Data + * ========== + */ + +typedef enum { + Dart_TypedData_kByteData = 0, + Dart_TypedData_kInt8, + Dart_TypedData_kUint8, + Dart_TypedData_kUint8Clamped, + Dart_TypedData_kInt16, + Dart_TypedData_kUint16, + Dart_TypedData_kInt32, + Dart_TypedData_kUint32, + Dart_TypedData_kInt64, + Dart_TypedData_kUint64, + Dart_TypedData_kFloat32, + Dart_TypedData_kFloat64, + Dart_TypedData_kInt32x4, + Dart_TypedData_kFloat32x4, + Dart_TypedData_kFloat64x2, + Dart_TypedData_kInvalid +} Dart_TypedData_Type; + +/** + * Return type if this object is a TypedData object. + * + * \return kInvalid if the object is not a TypedData object or the appropriate + * Dart_TypedData_Type. + */ +DART_EXPORT Dart_TypedData_Type Dart_GetTypeOfTypedData(Dart_Handle object); + +/** + * Return type if this object is an external TypedData object. + * + * \return kInvalid if the object is not an external TypedData object or + * the appropriate Dart_TypedData_Type. + */ +DART_EXPORT Dart_TypedData_Type +Dart_GetTypeOfExternalTypedData(Dart_Handle object); + +/** + * Returns a TypedData object of the desired length and type. + * + * \param type The type of the TypedData object. + * \param length The length of the TypedData object (length in type units). + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewTypedData(Dart_TypedData_Type type, + intptr_t length); + +/** + * Returns a TypedData object which references an external data array. + * + * \param type The type of the data array. + * \param data A data array. This array must not move. + * \param length The length of the data array (length in type units). + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewExternalTypedData(Dart_TypedData_Type type, + void* data, + intptr_t length); + +/** + * Returns a TypedData object which references an external data array. + * + * \param type The type of the data array. + * \param data A data array. This array must not move. + * \param length The length of the data array (length in type units). + * \param peer A pointer to a native object or NULL. This value is + * provided to callback when it is invoked. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A function pointer that will be invoked sometime + * after the object is garbage collected, unless the handle has been deleted. + * A valid callback needs to be specified it cannot be NULL. + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle +Dart_NewExternalTypedDataWithFinalizer(Dart_TypedData_Type type, + void* data, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); +DART_EXPORT Dart_Handle Dart_NewUnmodifiableExternalTypedDataWithFinalizer( + Dart_TypedData_Type type, + const void* data, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Returns a ByteBuffer object for the typed data. + * + * \param typed_data The TypedData object. + * + * \return The ByteBuffer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewByteBuffer(Dart_Handle typed_data); + +/** + * Acquires access to the internal data address of a TypedData object. + * + * \param object The typed data object whose internal data address is to + * be accessed. + * \param type The type of the object is returned here. + * \param data The internal data address is returned here. + * \param len Size of the typed array is returned here. + * + * Notes: + * When the internal address of the object is acquired any calls to a + * Dart API function that could potentially allocate an object or run + * any Dart code will return an error. + * + * Any Dart API functions for accessing the data should not be called + * before the corresponding release. In particular, the object should + * not be acquired again before its release. This leads to undefined + * behavior. + * + * \return Success if the internal data address is acquired successfully. + * Otherwise, returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_TypedDataAcquireData(Dart_Handle object, + Dart_TypedData_Type* type, + void** data, + intptr_t* len); + +/** + * Releases access to the internal data address that was acquired earlier using + * Dart_TypedDataAcquireData. + * + * \param object The typed data object whose internal data address is to be + * released. + * + * \return Success if the internal data address is released successfully. + * Otherwise, returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_TypedDataReleaseData(Dart_Handle object); + +/** + * Returns the TypedData object associated with the ByteBuffer object. + * + * \param byte_buffer The ByteBuffer object. + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetDataFromByteBuffer(Dart_Handle byte_buffer); + +/* + * ============================================================ + * Invoking Constructors, Methods, Closures and Field accessors + * ============================================================ + */ + +/** + * Invokes a constructor, creating a new object. + * + * This function allows hidden constructors (constructors with leading + * underscores) to be called. + * + * \param type Type of object to be constructed. + * \param constructor_name The name of the constructor to invoke. Use + * Dart_Null() or Dart_EmptyString() to invoke the unnamed constructor. + * This name should not include the name of the class. + * \param number_of_arguments Size of the arguments array. + * \param arguments An array of arguments to the constructor. + * + * \return If the constructor is called and completes successfully, + * then the new object. If an error occurs during execution, then an + * error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_New(Dart_Handle type, + Dart_Handle constructor_name, + int number_of_arguments, + Dart_Handle* arguments); + +/** + * Allocate a new object without invoking a constructor. + * + * \param type The type of an object to be allocated. + * + * \return The new object. If an error occurs during execution, then an + * error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_Allocate(Dart_Handle type); + +/** + * Allocate a new object without invoking a constructor, and sets specified + * native fields. + * + * \param type The type of an object to be allocated. + * \param num_native_fields The number of native fields to set. + * \param native_fields An array containing the value of native fields. + * + * \return The new object. If an error occurs during execution, then an + * error handle is returned. + */ +DART_EXPORT Dart_Handle +Dart_AllocateWithNativeFields(Dart_Handle type, + intptr_t num_native_fields, + const intptr_t* native_fields); + +/** + * Invokes a method or function. + * + * The 'target' parameter may be an object, type, or library. If + * 'target' is an object, then this function will invoke an instance + * method. If 'target' is a type, then this function will invoke a + * static method. If 'target' is a library, then this function will + * invoke a top-level function from that library. + * NOTE: This API call cannot be used to invoke methods of a type object. + * + * This function ignores visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param target An object, type, or library. + * \param name The name of the function or method to invoke. + * \param number_of_arguments Size of the arguments array. + * \param arguments An array of arguments to the function. + * + * \return If the function or method is called and completes + * successfully, then the return value is returned. If an error + * occurs during execution, then an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_Invoke(Dart_Handle target, + Dart_Handle name, + int number_of_arguments, + Dart_Handle* arguments); +/* TODO(turnidge): Document how to invoke operators. */ + +/** + * Invokes a Closure with the given arguments. + * + * May generate an unhandled exception error. + * + * \return If no error occurs during execution, then the result of + * invoking the closure is returned. If an error occurs during + * execution, then an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_InvokeClosure(Dart_Handle closure, + int number_of_arguments, + Dart_Handle* arguments); + +/** + * Invokes a Generative Constructor on an object that was previously + * allocated using Dart_Allocate/Dart_AllocateWithNativeFields. + * + * The 'object' parameter must be an object. + * + * This function ignores visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param object An object. + * \param name The name of the constructor to invoke. + * Use Dart_Null() or Dart_EmptyString() to invoke the unnamed constructor. + * \param number_of_arguments Size of the arguments array. + * \param arguments An array of arguments to the function. + * + * \return If the constructor is called and completes + * successfully, then the object is returned. If an error + * occurs during execution, then an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_InvokeConstructor(Dart_Handle object, + Dart_Handle name, + int number_of_arguments, + Dart_Handle* arguments); + +/** + * Gets the value of a field. + * + * The 'container' parameter may be an object, type, or library. If + * 'container' is an object, then this function will access an + * instance field. If 'container' is a type, then this function will + * access a static field. If 'container' is a library, then this + * function will access a top-level variable. + * NOTE: This API call cannot be used to access fields of a type object. + * + * This function ignores field visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param container An object, type, or library. + * \param name A field name. + * + * \return If no error occurs, then the value of the field is + * returned. Otherwise an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_GetField(Dart_Handle container, Dart_Handle name); + +/** + * Sets the value of a field. + * + * The 'container' parameter may actually be an object, type, or + * library. If 'container' is an object, then this function will + * access an instance field. If 'container' is a type, then this + * function will access a static field. If 'container' is a library, + * then this function will access a top-level variable. + * NOTE: This API call cannot be used to access fields of a type object. + * + * This function ignores field visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param container An object, type, or library. + * \param name A field name. + * \param value The new field value. + * + * \return A valid handle if no error occurs. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_SetField(Dart_Handle container, Dart_Handle name, Dart_Handle value); + +/* + * ========== + * Exceptions + * ========== + */ + +/* + * TODO(turnidge): Remove these functions from the api and replace all + * uses with Dart_NewUnhandledExceptionError. */ + +/** + * Throws an exception. + * + * This function causes a Dart language exception to be thrown. This + * will proceed in the standard way, walking up Dart frames until an + * appropriate 'catch' block is found, executing 'finally' blocks, + * etc. + * + * If an error handle is passed into this function, the error is + * propagated immediately. See Dart_PropagateError for a discussion + * of error propagation. + * + * If successful, this function does not return. Note that this means + * that the destructors of any stack-allocated C++ objects will not be + * called. If there are no Dart frames on the stack, an error occurs. + * + * \return An error handle if the exception was not thrown. + * Otherwise the function does not return. + */ +DART_EXPORT Dart_Handle Dart_ThrowException(Dart_Handle exception); + +/** + * Rethrows an exception. + * + * Rethrows an exception, unwinding all dart frames on the stack. If + * successful, this function does not return. Note that this means + * that the destructors of any stack-allocated C++ objects will not be + * called. If there are no Dart frames on the stack, an error occurs. + * + * \return An error handle if the exception was not thrown. + * Otherwise the function does not return. + */ +DART_EXPORT Dart_Handle Dart_ReThrowException(Dart_Handle exception, + Dart_Handle stacktrace); + +/* + * =========================== + * Native fields and functions + * =========================== + */ + +/** + * Gets the number of native instance fields in an object. + */ +DART_EXPORT Dart_Handle Dart_GetNativeInstanceFieldCount(Dart_Handle obj, + int* count); + +/** + * Gets the value of a native field. + * + * TODO(turnidge): Document. + */ +DART_EXPORT Dart_Handle Dart_GetNativeInstanceField(Dart_Handle obj, + int index, + intptr_t* value); + +/** + * Sets the value of a native field. + * + * TODO(turnidge): Document. + */ +DART_EXPORT Dart_Handle Dart_SetNativeInstanceField(Dart_Handle obj, + int index, + intptr_t value); + +/** + * The arguments to a native function. + * + * This object is passed to a native function to represent its + * arguments and return value. It allows access to the arguments to a + * native function by index. It also allows the return value of a + * native function to be set. + */ +typedef struct _Dart_NativeArguments* Dart_NativeArguments; + +/** + * Extracts current isolate group data from the native arguments structure. + */ +DART_EXPORT void* Dart_GetNativeIsolateGroupData(Dart_NativeArguments args); + +typedef enum { + Dart_NativeArgument_kBool = 0, + Dart_NativeArgument_kInt32, + Dart_NativeArgument_kUint32, + Dart_NativeArgument_kInt64, + Dart_NativeArgument_kUint64, + Dart_NativeArgument_kDouble, + Dart_NativeArgument_kString, + Dart_NativeArgument_kInstance, + Dart_NativeArgument_kNativeFields, +} Dart_NativeArgument_Type; + +typedef struct _Dart_NativeArgument_Descriptor { + uint8_t type; + uint8_t index; +} Dart_NativeArgument_Descriptor; + +typedef union _Dart_NativeArgument_Value { + bool as_bool; + int32_t as_int32; + uint32_t as_uint32; + int64_t as_int64; + uint64_t as_uint64; + double as_double; + struct { + Dart_Handle dart_str; + void* peer; + } as_string; + struct { + intptr_t num_fields; + intptr_t* values; + } as_native_fields; + Dart_Handle as_instance; +} Dart_NativeArgument_Value; + +enum { + kNativeArgNumberPos = 0, + kNativeArgNumberSize = 8, + kNativeArgTypePos = kNativeArgNumberPos + kNativeArgNumberSize, + kNativeArgTypeSize = 8, +}; + +#define BITMASK(size) ((1 << size) - 1) +#define DART_NATIVE_ARG_DESCRIPTOR(type, position) \ + (((type & BITMASK(kNativeArgTypeSize)) << kNativeArgTypePos) | \ + (position & BITMASK(kNativeArgNumberSize))) + +/** + * Gets the native arguments based on the types passed in and populates + * the passed arguments buffer with appropriate native values. + * + * \param args the Native arguments block passed into the native call. + * \param num_arguments length of argument descriptor array and argument + * values array passed in. + * \param arg_descriptors an array that describes the arguments that + * need to be retrieved. For each argument to be retrieved the descriptor + * contains the argument number (0, 1 etc.) and the argument type + * described using Dart_NativeArgument_Type, e.g: + * DART_NATIVE_ARG_DESCRIPTOR(Dart_NativeArgument_kBool, 1) indicates + * that the first argument is to be retrieved and it should be a boolean. + * \param arg_values array into which the native arguments need to be + * extracted into, the array is allocated by the caller (it could be + * stack allocated to avoid the malloc/free performance overhead). + * + * \return Success if all the arguments could be extracted correctly, + * returns an error handle if there were any errors while extracting the + * arguments (mismatched number of arguments, incorrect types, etc.). + */ +DART_EXPORT Dart_Handle +Dart_GetNativeArguments(Dart_NativeArguments args, + int num_arguments, + const Dart_NativeArgument_Descriptor* arg_descriptors, + Dart_NativeArgument_Value* arg_values); + +/** + * Gets the native argument at some index. + */ +DART_EXPORT Dart_Handle Dart_GetNativeArgument(Dart_NativeArguments args, + int index); +/* TODO(turnidge): Specify the behavior of an out-of-bounds access. */ + +/** + * Gets the number of native arguments. + */ +DART_EXPORT int Dart_GetNativeArgumentCount(Dart_NativeArguments args); + +/** + * Gets all the native fields of the native argument at some index. + * \param args Native arguments structure. + * \param arg_index Index of the desired argument in the structure above. + * \param num_fields size of the intptr_t array 'field_values' passed in. + * \param field_values intptr_t array in which native field values are returned. + * \return Success if the native fields where copied in successfully. Otherwise + * returns an error handle. On success the native field values are copied + * into the 'field_values' array, if the argument at 'arg_index' is a + * null object then 0 is copied as the native field values into the + * 'field_values' array. + */ +DART_EXPORT Dart_Handle +Dart_GetNativeFieldsOfArgument(Dart_NativeArguments args, + int arg_index, + int num_fields, + intptr_t* field_values); + +/** + * Gets the native field of the receiver. + */ +DART_EXPORT Dart_Handle Dart_GetNativeReceiver(Dart_NativeArguments args, + intptr_t* value); + +/** + * Gets a string native argument at some index. + * \param args Native arguments structure. + * \param arg_index Index of the desired argument in the structure above. + * \param peer Returns the peer pointer if the string argument has one. + * \return Success if the string argument has a peer, if it does not + * have a peer then the String object is returned. Otherwise returns + * an error handle (argument is not a String object). + */ +DART_EXPORT Dart_Handle Dart_GetNativeStringArgument(Dart_NativeArguments args, + int arg_index, + void** peer); + +/** + * Gets an integer native argument at some index. + * \param args Native arguments structure. + * \param index Index of the desired argument in the structure above. + * \param value Returns the integer value if the argument is an Integer. + * \return Success if no error occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetNativeIntegerArgument(Dart_NativeArguments args, + int index, + int64_t* value); + +/** + * Gets a boolean native argument at some index. + * \param args Native arguments structure. + * \param index Index of the desired argument in the structure above. + * \param value Returns the boolean value if the argument is a Boolean. + * \return Success if no error occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetNativeBooleanArgument(Dart_NativeArguments args, + int index, + bool* value); + +/** + * Gets a double native argument at some index. + * \param args Native arguments structure. + * \param index Index of the desired argument in the structure above. + * \param value Returns the double value if the argument is a double. + * \return Success if no error occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetNativeDoubleArgument(Dart_NativeArguments args, + int index, + double* value); + +/** + * Sets the return value for a native function. + * + * If retval is an Error handle, then error will be propagated once + * the native functions exits. See Dart_PropagateError for a + * discussion of how different types of errors are propagated. + */ +DART_EXPORT void Dart_SetReturnValue(Dart_NativeArguments args, + Dart_Handle retval); + +DART_EXPORT void Dart_SetWeakHandleReturnValue(Dart_NativeArguments args, + Dart_WeakPersistentHandle rval); + +DART_EXPORT void Dart_SetBooleanReturnValue(Dart_NativeArguments args, + bool retval); + +DART_EXPORT void Dart_SetIntegerReturnValue(Dart_NativeArguments args, + int64_t retval); + +DART_EXPORT void Dart_SetDoubleReturnValue(Dart_NativeArguments args, + double retval); + +/** + * A native function. + */ +typedef void (*Dart_NativeFunction)(Dart_NativeArguments arguments); + +/** + * Native entry resolution callback. + * + * For libraries and scripts which have native functions, the embedder + * can provide a native entry resolver. This callback is used to map a + * name/arity to a Dart_NativeFunction. If no function is found, the + * callback should return NULL. + * + * The parameters to the native resolver function are: + * \param name a Dart string which is the name of the native function. + * \param num_of_arguments is the number of arguments expected by the + * native function. + * \param auto_setup_scope is a boolean flag that can be set by the resolver + * to indicate if this function needs a Dart API scope (see Dart_EnterScope/ + * Dart_ExitScope) to be setup automatically by the VM before calling into + * the native function. By default most native functions would require this + * to be true but some light weight native functions which do not call back + * into the VM through the Dart API may not require a Dart scope to be + * setup automatically. + * + * \return A valid Dart_NativeFunction which resolves to a native entry point + * for the native function. + * + * See Dart_SetNativeResolver. + */ +typedef Dart_NativeFunction (*Dart_NativeEntryResolver)(Dart_Handle name, + int num_of_arguments, + bool* auto_setup_scope); +/* TODO(turnidge): Consider renaming to NativeFunctionResolver or + * NativeResolver. */ + +/** + * Native entry symbol lookup callback. + * + * For libraries and scripts which have native functions, the embedder + * can provide a callback for mapping a native entry to a symbol. This callback + * maps a native function entry PC to the native function name. If no native + * entry symbol can be found, the callback should return NULL. + * + * The parameters to the native reverse resolver function are: + * \param nf A Dart_NativeFunction. + * + * \return A const UTF-8 string containing the symbol name or NULL. + * + * See Dart_SetNativeResolver. + */ +typedef const uint8_t* (*Dart_NativeEntrySymbol)(Dart_NativeFunction nf); + +/** + * FFI Native C function pointer resolver callback. + * + * See Dart_SetFfiNativeResolver. + */ +typedef void* (*Dart_FfiNativeResolver)(const char* name, uintptr_t args_n); + +/* + * =========== + * Environment + * =========== + */ + +/** + * An environment lookup callback function. + * + * \param name The name of the value to lookup in the environment. + * + * \return A valid handle to a string if the name exists in the + * current environment or Dart_Null() if not. + */ +typedef Dart_Handle (*Dart_EnvironmentCallback)(Dart_Handle name); + +/** + * Sets the environment callback for the current isolate. This + * callback is used to lookup environment values by name in the + * current environment. This enables the embedder to supply values for + * the const constructors bool.fromEnvironment, int.fromEnvironment + * and String.fromEnvironment. + */ +DART_EXPORT Dart_Handle +Dart_SetEnvironmentCallback(Dart_EnvironmentCallback callback); + +/** + * Sets the callback used to resolve native functions for a library. + * + * \param library A library. + * \param resolver A native entry resolver. + * + * \return A valid handle if the native resolver was set successfully. + */ +DART_EXPORT Dart_Handle +Dart_SetNativeResolver(Dart_Handle library, + Dart_NativeEntryResolver resolver, + Dart_NativeEntrySymbol symbol); +/* TODO(turnidge): Rename to Dart_LibrarySetNativeResolver? */ + +/** + * Returns the callback used to resolve native functions for a library. + * + * \param library A library. + * \param resolver a pointer to a Dart_NativeEntryResolver + * + * \return A valid handle if the library was found. + */ +DART_EXPORT Dart_Handle +Dart_GetNativeResolver(Dart_Handle library, Dart_NativeEntryResolver* resolver); + +/** + * Returns the callback used to resolve native function symbols for a library. + * + * \param library A library. + * \param resolver a pointer to a Dart_NativeEntrySymbol. + * + * \return A valid handle if the library was found. + */ +DART_EXPORT Dart_Handle Dart_GetNativeSymbol(Dart_Handle library, + Dart_NativeEntrySymbol* resolver); + +/** + * Sets the callback used to resolve FFI native functions for a library. + * The resolved functions are expected to be a C function pointer of the + * correct signature (as specified in the `@FfiNative()` function + * annotation in Dart code). + * + * NOTE: This is an experimental feature and might change in the future. + * + * \param library A library. + * \param resolver A native function resolver. + * + * \return A valid handle if the native resolver was set successfully. + */ +DART_EXPORT Dart_Handle +Dart_SetFfiNativeResolver(Dart_Handle library, Dart_FfiNativeResolver resolver); + +/* + * ===================== + * Scripts and Libraries + * ===================== + */ + +typedef enum { + Dart_kCanonicalizeUrl = 0, + Dart_kImportTag, + Dart_kKernelTag, +} Dart_LibraryTag; + +/** + * The library tag handler is a multi-purpose callback provided by the + * embedder to the Dart VM. The embedder implements the tag handler to + * provide the ability to load Dart scripts and imports. + * + * -- TAGS -- + * + * Dart_kCanonicalizeUrl + * + * This tag indicates that the embedder should canonicalize 'url' with + * respect to 'library'. For most embedders, the + * Dart_DefaultCanonicalizeUrl function is a sufficient implementation + * of this tag. The return value should be a string holding the + * canonicalized url. + * + * Dart_kImportTag + * + * This tag is used to load a library from IsolateMirror.loadUri. The embedder + * should call Dart_LoadLibraryFromKernel to provide the library to the VM. The + * return value should be an error or library (the result from + * Dart_LoadLibraryFromKernel). + * + * Dart_kKernelTag + * + * This tag is used to load the intermediate file (kernel) generated by + * the Dart front end. This tag is typically used when a 'hot-reload' + * of an application is needed and the VM is 'use dart front end' mode. + * The dart front end typically compiles all the scripts, imports and part + * files into one intermediate file hence we don't use the source/import or + * script tags. The return value should be an error or a TypedData containing + * the kernel bytes. + * + */ +typedef Dart_Handle (*Dart_LibraryTagHandler)( + Dart_LibraryTag tag, + Dart_Handle library_or_package_map_url, + Dart_Handle url); + +/** + * Sets library tag handler for the current isolate. This handler is + * used to handle the various tags encountered while loading libraries + * or scripts in the isolate. + * + * \param handler Handler code to be used for handling the various tags + * encountered while loading libraries or scripts in the isolate. + * + * \return If no error occurs, the handler is set for the isolate. + * Otherwise an error handle is returned. + * + * TODO(turnidge): Document. + */ +DART_EXPORT Dart_Handle +Dart_SetLibraryTagHandler(Dart_LibraryTagHandler handler); + +/** + * Handles deferred loading requests. When this handler is invoked, it should + * eventually load the deferred loading unit with the given id and call + * Dart_DeferredLoadComplete or Dart_DeferredLoadCompleteError. It is + * recommended that the loading occur asynchronously, but it is permitted to + * call Dart_DeferredLoadComplete or Dart_DeferredLoadCompleteError before the + * handler returns. + * + * If an error is returned, it will be propagated through + * `prefix.loadLibrary()`. This is useful for synchronous + * implementations, which must propagate any unwind errors from + * Dart_DeferredLoadComplete or Dart_DeferredLoadComplete. Otherwise the handler + * should return a non-error such as `Dart_Null()`. + */ +typedef Dart_Handle (*Dart_DeferredLoadHandler)(intptr_t loading_unit_id); + +/** + * Sets the deferred load handler for the current isolate. This handler is + * used to handle loading deferred imports in an AppJIT or AppAOT program. + */ +DART_EXPORT Dart_Handle +Dart_SetDeferredLoadHandler(Dart_DeferredLoadHandler handler); + +/** + * Notifies the VM that a deferred load completed successfully. This function + * will eventually cause the corresponding `prefix.loadLibrary()` futures to + * complete. + * + * Requires the current isolate to be the same current isolate during the + * invocation of the Dart_DeferredLoadHandler. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_DeferredLoadComplete(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions); + +/** + * Notifies the VM that a deferred load failed. This function + * will eventually cause the corresponding `prefix.loadLibrary()` futures to + * complete with an error. + * + * If `transient` is true, future invocations of `prefix.loadLibrary()` will + * trigger new load requests. If false, futures invocation will complete with + * the same error. + * + * Requires the current isolate to be the same current isolate during the + * invocation of the Dart_DeferredLoadHandler. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_DeferredLoadCompleteError(intptr_t loading_unit_id, + const char* error_message, + bool transient); + +/** + * Canonicalizes a url with respect to some library. + * + * The url is resolved with respect to the library's url and some url + * normalizations are performed. + * + * This canonicalization function should be sufficient for most + * embedders to implement the Dart_kCanonicalizeUrl tag. + * + * \param base_url The base url relative to which the url is + * being resolved. + * \param url The url being resolved and canonicalized. This + * parameter is a string handle. + * + * \return If no error occurs, a String object is returned. Otherwise + * an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_DefaultCanonicalizeUrl(Dart_Handle base_url, + Dart_Handle url); + +/** + * Loads the root library for the current isolate. + * + * Requires there to be no current root library. + * + * \param kernel_buffer A buffer which contains a kernel binary (see + * pkg/kernel/binary.md). Must remain valid until isolate group shutdown. + * \param kernel_size Length of the passed in buffer. + * + * \return A handle to the root library, or an error. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_LoadScriptFromKernel(const uint8_t* kernel_buffer, intptr_t kernel_size); + +/** + * Gets the library for the root script for the current isolate. + * + * If the root script has not yet been set for the current isolate, + * this function returns Dart_Null(). This function never returns an + * error handle. + * + * \return Returns the root Library for the current isolate or Dart_Null(). + */ +DART_EXPORT Dart_Handle Dart_RootLibrary(void); + +/** + * Sets the root library for the current isolate. + * + * \return Returns an error handle if `library` is not a library handle. + */ +DART_EXPORT Dart_Handle Dart_SetRootLibrary(Dart_Handle library); + +/** + * Lookup or instantiate a legacy type by name and type arguments from a + * Library. + * + * \param library The library containing the class or interface. + * \param class_name The class name for the type. + * \param number_of_type_arguments Number of type arguments. + * For non parametric types the number of type arguments would be 0. + * \param type_arguments Pointer to an array of type arguments. + * For non parametric types a NULL would be passed in for this argument. + * + * \return If no error occurs, the type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_GetType(Dart_Handle library, + Dart_Handle class_name, + intptr_t number_of_type_arguments, + Dart_Handle* type_arguments); + +/** + * Lookup or instantiate a nullable type by name and type arguments from + * Library. + * + * \param library The library containing the class or interface. + * \param class_name The class name for the type. + * \param number_of_type_arguments Number of type arguments. + * For non parametric types the number of type arguments would be 0. + * \param type_arguments Pointer to an array of type arguments. + * For non parametric types a NULL would be passed in for this argument. + * + * \return If no error occurs, the type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_GetNullableType(Dart_Handle library, + Dart_Handle class_name, + intptr_t number_of_type_arguments, + Dart_Handle* type_arguments); + +/** + * Lookup or instantiate a non-nullable type by name and type arguments from + * Library. + * + * \param library The library containing the class or interface. + * \param class_name The class name for the type. + * \param number_of_type_arguments Number of type arguments. + * For non parametric types the number of type arguments would be 0. + * \param type_arguments Pointer to an array of type arguments. + * For non parametric types a NULL would be passed in for this argument. + * + * \return If no error occurs, the type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle +Dart_GetNonNullableType(Dart_Handle library, + Dart_Handle class_name, + intptr_t number_of_type_arguments, + Dart_Handle* type_arguments); + +/** + * Creates a nullable version of the provided type. + * + * \param type The type to be converted to a nullable type. + * + * \return If no error occurs, a nullable type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_TypeToNullableType(Dart_Handle type); + +/** + * Creates a non-nullable version of the provided type. + * + * \param type The type to be converted to a non-nullable type. + * + * \return If no error occurs, a non-nullable type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_TypeToNonNullableType(Dart_Handle type); + +/** + * A type's nullability. + * + * \param type A Dart type. + * \param result An out parameter containing the result of the check. True if + * the type is of the specified nullability, false otherwise. + * + * \return Returns an error handle if type is not of type Type. + */ +DART_EXPORT Dart_Handle Dart_IsNullableType(Dart_Handle type, bool* result); +DART_EXPORT Dart_Handle Dart_IsNonNullableType(Dart_Handle type, bool* result); +DART_EXPORT Dart_Handle Dart_IsLegacyType(Dart_Handle type, bool* result); + +/** + * Lookup a class or interface by name from a Library. + * + * \param library The library containing the class or interface. + * \param class_name The name of the class or interface. + * + * \return If no error occurs, the class or interface is + * returned. Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_GetClass(Dart_Handle library, + Dart_Handle class_name); +/* TODO(asiva): The above method needs to be removed once all uses + * of it are removed from the embedder code. */ + +/** + * Returns an import path to a Library, such as "file:///test.dart" or + * "dart:core". + */ +DART_EXPORT Dart_Handle Dart_LibraryUrl(Dart_Handle library); + +/** + * Returns a URL from which a Library was loaded. + */ +DART_EXPORT Dart_Handle Dart_LibraryResolvedUrl(Dart_Handle library); + +/** + * \return An array of libraries. + */ +DART_EXPORT Dart_Handle Dart_GetLoadedLibraries(void); + +DART_EXPORT Dart_Handle Dart_LookupLibrary(Dart_Handle url); +/* TODO(turnidge): Consider returning Dart_Null() when the library is + * not found to distinguish that from a true error case. */ + +/** + * Report an loading error for the library. + * + * \param library The library that failed to load. + * \param error The Dart error instance containing the load error. + * + * \return If the VM handles the error, the return value is + * a null handle. If it doesn't handle the error, the error + * object is returned. + */ +DART_EXPORT Dart_Handle Dart_LibraryHandleError(Dart_Handle library, + Dart_Handle error); + +/** + * Called by the embedder to load a partial program. Does not set the root + * library. + * + * \param kernel_buffer A buffer which contains a kernel binary (see + * pkg/kernel/binary.md). Must remain valid until isolate shutdown. + * \param kernel_buffer_size Length of the passed in buffer. + * + * \return A handle to the main library of the compilation unit, or an error. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_LoadLibraryFromKernel(const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size); +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_LoadLibrary(Dart_Handle kernel_buffer); + +/** + * Indicates that all outstanding load requests have been satisfied. + * This finalizes all the new classes loaded and optionally completes + * deferred library futures. + * + * Requires there to be a current isolate. + * + * \param complete_futures Specify true if all deferred library + * futures should be completed, false otherwise. + * + * \return Success if all classes have been finalized and deferred library + * futures are completed. Otherwise, returns an error. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_FinalizeLoading(bool complete_futures); + +/* + * ===== + * Peers + * ===== + */ + +/** + * The peer field is a lazily allocated field intended for storage of + * an uncommonly used values. Most instances types can have a peer + * field allocated. The exceptions are subtypes of Null, num, and + * bool. + */ + +/** + * Returns the value of peer field of 'object' in 'peer'. + * + * \param object An object. + * \param peer An out parameter that returns the value of the peer + * field. + * + * \return Returns an error if 'object' is a subtype of Null, num, or + * bool. + */ +DART_EXPORT Dart_Handle Dart_GetPeer(Dart_Handle object, void** peer); + +/** + * Sets the value of the peer field of 'object' to the value of + * 'peer'. + * + * \param object An object. + * \param peer A value to store in the peer field. + * + * \return Returns an error if 'object' is a subtype of Null, num, or + * bool. + */ +DART_EXPORT Dart_Handle Dart_SetPeer(Dart_Handle object, void* peer); + +/* + * ====== + * Kernel + * ====== + */ + +/** + * Experimental support for Dart to Kernel parser isolate. + * + * TODO(hausner): Document finalized interface. + * + */ + +// TODO(33433): Remove kernel service from the embedding API. + +typedef enum { + Dart_KernelCompilationStatus_Unknown = -1, + Dart_KernelCompilationStatus_Ok = 0, + Dart_KernelCompilationStatus_Error = 1, + Dart_KernelCompilationStatus_Crash = 2, + Dart_KernelCompilationStatus_MsgFailed = 3, +} Dart_KernelCompilationStatus; + +typedef struct { + Dart_KernelCompilationStatus status; + bool null_safety; + char* error; + uint8_t* kernel; + intptr_t kernel_size; +} Dart_KernelCompilationResult; + +typedef enum { + Dart_KernelCompilationVerbosityLevel_Error = 0, + Dart_KernelCompilationVerbosityLevel_Warning, + Dart_KernelCompilationVerbosityLevel_Info, + Dart_KernelCompilationVerbosityLevel_All, +} Dart_KernelCompilationVerbosityLevel; + +DART_EXPORT bool Dart_IsKernelIsolate(Dart_Isolate isolate); +DART_EXPORT bool Dart_KernelIsolateIsRunning(void); +DART_EXPORT Dart_Port Dart_KernelPort(void); + +/** + * Compiles the given `script_uri` to a kernel file. + * + * \param platform_kernel A buffer containing the kernel of the platform (e.g. + * `vm_platform_strong.dill`). The VM does not take ownership of this memory. + * + * \param platform_kernel_size The length of the platform_kernel buffer. + * + * \param snapshot_compile Set to `true` when the compilation is for a snapshot. + * This is used by the frontend to determine if compilation related information + * should be printed to console (e.g., null safety mode). + * + * \param verbosity Specifies the logging behavior of the kernel compilation + * service. + * + * \return Returns the result of the compilation. + * + * On a successful compilation the returned [Dart_KernelCompilationResult] has + * a status of [Dart_KernelCompilationStatus_Ok] and the `kernel`/`kernel_size` + * fields are set. The caller takes ownership of the malloc()ed buffer. + * + * On a failed compilation the `error` might be set describing the reason for + * the failed compilation. The caller takes ownership of the malloc()ed + * error. + * + * Requires there to be a current isolate. + */ +DART_EXPORT Dart_KernelCompilationResult +Dart_CompileToKernel(const char* script_uri, + const uint8_t* platform_kernel, + const intptr_t platform_kernel_size, + bool incremental_compile, + bool snapshot_compile, + const char* package_config, + Dart_KernelCompilationVerbosityLevel verbosity); + +typedef struct { + const char* uri; + const char* source; +} Dart_SourceFile; + +DART_EXPORT Dart_KernelCompilationResult Dart_KernelListDependencies(void); + +/** + * Sets the kernel buffer which will be used to load Dart SDK sources + * dynamically at runtime. + * + * \param platform_kernel A buffer containing kernel which has sources for the + * Dart SDK populated. Note: The VM does not take ownership of this memory. + * + * \param platform_kernel_size The length of the platform_kernel buffer. + */ +DART_EXPORT void Dart_SetDartLibrarySourcesKernel( + const uint8_t* platform_kernel, + const intptr_t platform_kernel_size); + +/** + * Detect the null safety opt-in status. + * + * When running from source, it is based on the opt-in status of `script_uri`. + * When running from a kernel buffer, it is based on the mode used when + * generating `kernel_buffer`. + * When running from an appJIT or AOT snapshot, it is based on the mode used + * when generating `snapshot_data`. + * + * \param script_uri Uri of the script that contains the source code + * + * \param package_config Uri of the package configuration file (either in format + * of .packages or .dart_tool/package_config.json) for the null safety + * detection to resolve package imports against. If this parameter is not + * passed the package resolution of the parent isolate should be used. + * + * \param original_working_directory current working directory when the VM + * process was launched, this is used to correctly resolve the path specified + * for package_config. + * + * \param snapshot_data Buffer containing the snapshot data of the + * isolate or NULL if no snapshot is provided. If provided, the buffers must + * remain valid until the isolate shuts down. + * + * \param snapshot_instructions Buffer containing the snapshot instructions of + * the isolate or NULL if no snapshot is provided. If provided, the buffers + * must remain valid until the isolate shuts down. + * + * \param kernel_buffer A buffer which contains a kernel/DIL program. Must + * remain valid until isolate shutdown. + * + * \param kernel_buffer_size The size of `kernel_buffer`. + * + * \return Returns true if the null safety is opted in by the input being + * run `script_uri`, `snapshot_data` or `kernel_buffer`. + * + */ +DART_EXPORT bool Dart_DetectNullSafety(const char* script_uri, + const char* package_config, + const char* original_working_directory, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions, + const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size); + +#define DART_KERNEL_ISOLATE_NAME "kernel-service" + +/* + * ======= + * Service + * ======= + */ + +#define DART_VM_SERVICE_ISOLATE_NAME "vm-service" + +/** + * Returns true if isolate is the service isolate. + * + * \param isolate An isolate + * + * \return Returns true if 'isolate' is the service isolate. + */ +DART_EXPORT bool Dart_IsServiceIsolate(Dart_Isolate isolate); + +/** + * Writes the CPU profile to the timeline as a series of 'instant' events. + * + * Note that this is an expensive operation. + * + * \param main_port The main port of the Isolate whose profile samples to write. + * \param error An optional error, must be free()ed by caller. + * + * \return Returns true if the profile is successfully written and false + * otherwise. + */ +DART_EXPORT bool Dart_WriteProfileToTimeline(Dart_Port main_port, char** error); + +/* + * ============== + * Precompilation + * ============== + */ + +/** + * Compiles all functions reachable from entry points and marks + * the isolate to disallow future compilation. + * + * Entry points should be specified using `@pragma("vm:entry-point")` + * annotation. + * + * \return An error handle if a compilation error or runtime error running const + * constructors was encountered. + */ +DART_EXPORT Dart_Handle Dart_Precompile(void); + +typedef void (*Dart_CreateLoadingUnitCallback)( + void* callback_data, + intptr_t loading_unit_id, + void** write_callback_data, + void** write_debug_callback_data); +typedef void (*Dart_StreamingWriteCallback)(void* callback_data, + const uint8_t* buffer, + intptr_t size); +typedef void (*Dart_StreamingCloseCallback)(void* callback_data); + +DART_EXPORT Dart_Handle Dart_LoadingUnitLibraryUris(intptr_t loading_unit_id); + +// On Darwin systems, 'dlsym' adds an '_' to the beginning of the symbol name. +// Use the '...CSymbol' definitions for resolving through 'dlsym'. The actual +// symbol names in the objects are given by the '...AsmSymbol' definitions. +#if defined(__APPLE__) +#define kSnapshotBuildIdCSymbol "kDartSnapshotBuildId" +#define kVmSnapshotDataCSymbol "kDartVmSnapshotData" +#define kVmSnapshotInstructionsCSymbol "kDartVmSnapshotInstructions" +#define kVmSnapshotBssCSymbol "kDartVmSnapshotBss" +#define kIsolateSnapshotDataCSymbol "kDartIsolateSnapshotData" +#define kIsolateSnapshotInstructionsCSymbol "kDartIsolateSnapshotInstructions" +#define kIsolateSnapshotBssCSymbol "kDartIsolateSnapshotBss" +#else +#define kSnapshotBuildIdCSymbol "_kDartSnapshotBuildId" +#define kVmSnapshotDataCSymbol "_kDartVmSnapshotData" +#define kVmSnapshotInstructionsCSymbol "_kDartVmSnapshotInstructions" +#define kVmSnapshotBssCSymbol "_kDartVmSnapshotBss" +#define kIsolateSnapshotDataCSymbol "_kDartIsolateSnapshotData" +#define kIsolateSnapshotInstructionsCSymbol "_kDartIsolateSnapshotInstructions" +#define kIsolateSnapshotBssCSymbol "_kDartIsolateSnapshotBss" +#endif + +#define kSnapshotBuildIdAsmSymbol "_kDartSnapshotBuildId" +#define kVmSnapshotDataAsmSymbol "_kDartVmSnapshotData" +#define kVmSnapshotInstructionsAsmSymbol "_kDartVmSnapshotInstructions" +#define kVmSnapshotBssAsmSymbol "_kDartVmSnapshotBss" +#define kIsolateSnapshotDataAsmSymbol "_kDartIsolateSnapshotData" +#define kIsolateSnapshotInstructionsAsmSymbol \ + "_kDartIsolateSnapshotInstructions" +#define kIsolateSnapshotBssAsmSymbol "_kDartIsolateSnapshotBss" + +/** + * Creates a precompiled snapshot. + * - A root library must have been loaded. + * - Dart_Precompile must have been called. + * + * Outputs an assembly file defining the symbols listed in the definitions + * above. + * + * The assembly should be compiled as a static or shared library and linked or + * loaded by the embedder. Running this snapshot requires a VM compiled with + * DART_PRECOMPILED_SNAPSHOT. The kDartVmSnapshotData and + * kDartVmSnapshotInstructions should be passed to Dart_Initialize. The + * kDartIsolateSnapshotData and kDartIsolateSnapshotInstructions should be + * passed to Dart_CreateIsolateGroup. + * + * The callback will be invoked one or more times to provide the assembly code. + * + * If stripped is true, then the assembly code will not include DWARF + * debugging sections. + * + * If debug_callback_data is provided, debug_callback_data will be used with + * the callback to provide separate debugging information. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback, + void* callback_data, + bool stripped, + void* debug_callback_data); +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsAssemblies( + Dart_CreateLoadingUnitCallback next_callback, + void* next_callback_data, + bool stripped, + Dart_StreamingWriteCallback write_callback, + Dart_StreamingCloseCallback close_callback); + +/** + * Creates a precompiled snapshot. + * - A root library must have been loaded. + * - Dart_Precompile must have been called. + * + * Outputs an ELF shared library defining the symbols + * - _kDartVmSnapshotData + * - _kDartVmSnapshotInstructions + * - _kDartIsolateSnapshotData + * - _kDartIsolateSnapshotInstructions + * + * The shared library should be dynamically loaded by the embedder. + * Running this snapshot requires a VM compiled with DART_PRECOMPILED_SNAPSHOT. + * The kDartVmSnapshotData and kDartVmSnapshotInstructions should be passed to + * Dart_Initialize. The kDartIsolateSnapshotData and + * kDartIsolateSnapshotInstructions should be passed to Dart_CreateIsolate. + * + * The callback will be invoked one or more times to provide the binary output. + * + * If stripped is true, then the binary output will not include DWARF + * debugging sections. + * + * If debug_callback_data is provided, debug_callback_data will be used with + * the callback to provide separate debugging information. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback, + void* callback_data, + bool stripped, + void* debug_callback_data); +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsElfs(Dart_CreateLoadingUnitCallback next_callback, + void* next_callback_data, + bool stripped, + Dart_StreamingWriteCallback write_callback, + Dart_StreamingCloseCallback close_callback); + +/** + * Like Dart_CreateAppAOTSnapshotAsAssembly, but only includes + * kDartVmSnapshotData and kDartVmSnapshotInstructions. It also does + * not strip DWARF information from the generated assembly or allow for + * separate debug information. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateVMAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback, + void* callback_data); + +/** + * Sorts the class-ids in depth first traversal order of the inheritance + * tree. This is a costly operation, but it can make method dispatch + * more efficient and is done before writing snapshots. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_SortClasses(void); + +/** + * Creates a snapshot that caches compiled code and type feedback for faster + * startup and quicker warmup in a subsequent process. + * + * Outputs a snapshot in two pieces. The pieces should be passed to + * Dart_CreateIsolateGroup in a VM using the same VM snapshot pieces used in the + * current VM. The instructions piece must be loaded with read and execute + * permissions; the data piece may be loaded as read-only. + * + * - Requires the VM to have not been started with --precompilation. + * - Not supported when targeting IA32. + * - The VM writing the snapshot and the VM reading the snapshot must be the + * same version, must be built in the same DEBUG/RELEASE/PRODUCT mode, must + * be targeting the same architecture, and must both be in checked mode or + * both in unchecked mode. + * + * The buffers are scope allocated and are only valid until the next call to + * Dart_ExitScope. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppJITSnapshotAsBlobs(uint8_t** isolate_snapshot_data_buffer, + intptr_t* isolate_snapshot_data_size, + uint8_t** isolate_snapshot_instructions_buffer, + intptr_t* isolate_snapshot_instructions_size); + +/** + * Like Dart_CreateAppJITSnapshotAsBlobs, but also creates a new VM snapshot. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateCoreJITSnapshotAsBlobs( + uint8_t** vm_snapshot_data_buffer, + intptr_t* vm_snapshot_data_size, + uint8_t** vm_snapshot_instructions_buffer, + intptr_t* vm_snapshot_instructions_size, + uint8_t** isolate_snapshot_data_buffer, + intptr_t* isolate_snapshot_data_size, + uint8_t** isolate_snapshot_instructions_buffer, + intptr_t* isolate_snapshot_instructions_size); + +/** + * Get obfuscation map for precompiled code. + * + * Obfuscation map is encoded as a JSON array of pairs (original name, + * obfuscated name). + * + * \return Returns an error handler if the VM was built in a mode that does not + * support obfuscation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_GetObfuscationMap(uint8_t** buffer, intptr_t* buffer_length); + +/** + * Returns whether the VM only supports running from precompiled snapshots and + * not from any other kind of snapshot or from source (that is, the VM was + * compiled with DART_PRECOMPILED_RUNTIME). + */ +DART_EXPORT bool Dart_IsPrecompiledRuntime(void); + +/** + * Print a native stack trace. Used for crash handling. + * + * If context is NULL, prints the current stack trace. Otherwise, context + * should be a CONTEXT* (Windows) or ucontext_t* (POSIX) from a signal handler + * running on the current thread. + */ +DART_EXPORT void Dart_DumpNativeStackTrace(void* context); + +/** + * Indicate that the process is about to abort, and the Dart VM should not + * attempt to cleanup resources. + */ +DART_EXPORT void Dart_PrepareToAbort(void); + +/** + * Callback provided by the embedder that is used by the VM to + * produce footnotes appended to DWARF stack traces. + * + * Whenever VM formats a stack trace as a string it would call this callback + * passing raw program counters for each frame in the stack trace. + * + * Embedder can then return a string which if not-null will be appended to the + * formatted stack trace. + * + * Returned string is expected to be `malloc()` allocated. VM takes ownership + * of the returned string and will `free()` it. + * + * \param addresses raw program counter addresses for each frame + * \param count number of elements in the addresses array + */ +typedef char* (*Dart_DwarfStackTraceFootnoteCallback)(void* addresses[], + intptr_t count); + +/** + * Configure DWARF stack trace footnote callback. + */ +DART_EXPORT void Dart_SetDwarfStackTraceFootnoteCallback( + Dart_DwarfStackTraceFootnoteCallback callback); + +#endif /* INCLUDE_DART_API_H_ */ /* NOLINT */ diff --git a/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.c b/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.c new file mode 100644 index 0000000..c4a68f4 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.c @@ -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 + +#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; +} diff --git a/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.h b/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.h new file mode 100644 index 0000000..ba50d99 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.h @@ -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 */ diff --git a/native/hub/src/bridge/bridge_engine/dart_api/dart_embedder_api.h b/native/hub/src/bridge/bridge_engine/dart_api/dart_embedder_api.h new file mode 100644 index 0000000..e565ebf --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/dart_embedder_api.h @@ -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_ diff --git a/native/hub/src/bridge/bridge_engine/dart_api/dart_native_api.h b/native/hub/src/bridge/bridge_engine/dart_api/dart_native_api.h new file mode 100644 index 0000000..79194e0 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/dart_native_api.h @@ -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 */ diff --git a/native/hub/src/bridge/bridge_engine/dart_api/dart_tools_api.h b/native/hub/src/bridge/bridge_engine/dart_api/dart_tools_api.h new file mode 100644 index 0000000..6136a41 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/dart_tools_api.h @@ -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": , + * "id": , + * } + * + * If the callback returns false, a JSON-RPC error is built like this: + * + * { + * "jsonrpc": "2.0", + * "error": , + * "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_ diff --git a/native/hub/src/bridge/bridge_engine/dart_api/dart_version.h b/native/hub/src/bridge/bridge_engine/dart_api/dart_version.h new file mode 100644 index 0000000..680fb53 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/dart_version.h @@ -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 */ diff --git a/native/hub/src/bridge/bridge_engine/dart_api/internal/dart_api_dl_impl.h b/native/hub/src/bridge/bridge_engine/dart_api/internal/dart_api_dl_impl.h new file mode 100644 index 0000000..e4a5689 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/dart_api/internal/dart_api_dl_impl.h @@ -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 */ diff --git a/native/hub/src/bridge/bridge_engine/ffi/io.rs b/native/hub/src/bridge/bridge_engine/ffi/io.rs new file mode 100644 index 0000000..3a96696 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/ffi/io.rs @@ -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); + +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 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, +} + +impl DartOpaqueBase { + pub fn new(handle: Dart_PersistentHandle, drop_port: Option) -> 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 { + Some(Channel::new(self.drop_port?)) + } +} diff --git a/native/hub/src/bridge/bridge_engine/ffi/mod.rs b/native/hub/src/bridge/bridge_engine/ffi/mod.rs new file mode 100644 index 0000000..946856b --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/ffi/mod.rs @@ -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>`]. 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` 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 DartDebug for T {} +/// +/// pub struct DebugWrapper(pub RustOpaque>); +/// +/// // 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>); +/// ``` +#[repr(transparent)] +#[derive(Debug)] +pub struct RustOpaque { + ptr: Option>, +} + +impl RustOpaque { + pub fn try_unwrap(self) -> Result { + if let Some(ptr) = self.ptr { + Arc::try_unwrap(ptr).map_err(RustOpaque::from) + } else { + panic!("Use after free.") + } + } +} + +impl Clone for RustOpaque { + 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(ptr: *const T) -> RustOpaque { + // 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 ops::Deref for RustOpaque { + type Target = T; + + fn deref(&self) -> &Self::Target { + if let Some(ptr) = &self.ptr { + ptr.as_ref() + } else { + panic!("Use after free.") + } + } +} + +impl From> for RustOpaque { + fn from(ptr: Arc) -> Self { + Self { ptr: Some(ptr) } + } +} + +impl RustOpaque { + pub fn new(value: T) -> Self { + Self { + ptr: Some(Arc::new(value)), + } + } +} + +impl From> for DartAbi { + fn from(value: RustOpaque) -> Self { + let ptr = if let Some(ptr) = value.ptr { + Arc::into_raw(ptr) + } else { + std::ptr::null() + }; + let size = mem::size_of::(); + + vec![ptr.into_dart(), size.into_dart()].into_dart() + } +} + +#[derive(Debug)] +pub struct DartOpaque { + /// Dart object + handle: Option, + + /// 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 { + if std::thread::current().id() == self.thread_id { + Ok(self.handle.take().unwrap().unwrap()) + } else { + Err(self) + } + } +} + +impl From 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`, as Rust does not +/// support custom DSTs on stable. +/// +/// Example: +/// ```rust +/// use std::fmt::Debug; +/// use crate::*; +/// +/// pub trait MyDebug: DartSafe + Debug {} +/// +/// impl MyDebug for T {} +/// +/// let opaque: RustOpaque> = opaque_dyn!("foobar"); +/// ``` +#[macro_export] +macro_rules! opaque_dyn { + ($ex:expr) => { + $crate::RustOpaque::new(::std::boxed::Box::new($ex)) + }; +} diff --git a/native/hub/src/bridge/bridge_engine/ffi/web.rs b/native/hub/src/bridge/bridge_engine/ffi/web.rs new file mode 100644 index 0000000..8f26cfa --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/ffi/web.rs @@ -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 IntoDartExceptPrimitive for RustOpaque {} +impl IntoDartExceptPrimitive for DartOpaque {} +impl IntoDartExceptPrimitive for String {} +impl IntoDartExceptPrimitive for Option {} + +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 => js_sys::Int8Array + Vec => js_sys::Uint8Array + Vec => js_sys::Int16Array + Vec => js_sys::Uint16Array + Vec => js_sys::Int32Array + Vec => js_sys::Uint32Array + Vec => js_sys::Float32Array + Vec => js_sys::Float64Array + ZeroCopyBuffer> => js_sys::Int8Array + ZeroCopyBuffer> => js_sys::Uint8Array + ZeroCopyBuffer> => js_sys::Int16Array + ZeroCopyBuffer> => js_sys::Uint16Array + ZeroCopyBuffer> => js_sys::Int32Array + ZeroCopyBuffer> => js_sys::Uint32Array + ZeroCopyBuffer> => js_sys::Float32Array + ZeroCopyBuffer> => js_sys::Float64Array +} + +impl IntoDart for Vec { + #[inline] + fn into_dart(self) -> DartAbi { + Array::from_iter(self.into_iter().map(IntoDart::into_dart)).into() + } +} + +impl IntoDart for Option { + #[inline] + fn into_dart(self) -> DartAbi { + self.map(T::into_dart).unwrap_or_else(JsValue::null) + } +} +impl IntoDart for *const T { + #[inline] + fn into_dart(self) -> DartAbi { + (self as usize).into_dart() + } +} +impl IntoDart for *mut T { + #[inline] + fn into_dart(self) -> DartAbi { + (self as usize).into_dart() + } +} + +impl IntoDart for RustOpaque { + #[inline] + fn into_dart(self) -> DartAbi { + self.into() + } +} + +impl IntoDart for DartOpaque { + #[inline] + fn into_dart(self) -> DartAbi { + self.into() + } +} + +impl 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 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 => BigInt64Array + Vec => 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> { + #[inline] + fn into_dart(self) -> DartAbi { + self.0.into_dart() + } +} +impl IntoDart for ZeroCopyBuffer> { + #[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 { + self.port + .dyn_ref::() + .map(|channel| channel.name()) + } +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console, js_name = "error")] + pub fn error(msg: &str); +} + +type RawClosure = Box; + +pub struct TransferClosure { + pub(crate) data: Vec, + pub(crate) transfer: Vec, + pub(crate) closure: RawClosure, +} + +pub struct TransferClosurePayload { + pub(crate) func: RawClosure, +} + +impl TransferClosure { + pub fn new( + data: Vec, + transfer: Vec, + closure: impl FnOnce(&[JsValue]) + Send + 'static, + ) -> Self { + let closure = Box::new(closure); + Self { + data, + transfer, + closure, + } + } +} + +#[derive(Debug)] +pub struct ZeroCopyBuffer(pub T); + +impl ZeroCopyBuffer> { + #[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; +} + +impl Transfer for Option { + 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 { + 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::().is_some() { + value.unchecked_ref::().clone() + } else { + panic!("Not a PortLike: {:?}", value) + } + } + fn serialize(self) -> JsValue { + if let Some(channel) = self.dyn_ref::() { + channel.name().into() + } else { + self.into() + } + } + fn transferables(&self) -> Vec { + if let Some(port) = self.dyn_ref::() { + 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 { + 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 { + 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::(ptr as _)); +} + +#[derive(Debug)] +pub struct DartOpaqueBase { + inner: Box, + drop_port: Option, +} + +impl DartOpaqueBase { + pub fn new(handle: JsValue, port: Option) -> Self { + Self { + inner: Box::new(handle), + drop_port: port.map(|p| p.dyn_ref::().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 { + Some(Channel::new(PortLike::broadcast(self.drop_port.as_ref()?))) + } +} diff --git a/native/hub/src/bridge/bridge_engine/handler.rs b/native/hub/src/bridge/bridge_engine/handler.rs new file mode 100644 index 0000000..f679035 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/handler.rs @@ -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`. + Normal, + /// Used by `SyncReturn` to skip spawning workers. + Sync, + /// Returns a Dart `Stream`. + 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, + /// 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(&self, wrap_info: WrapInfo, prepare: PrepareFn) + where + PrepareFn: FnOnce() -> TaskFn + UnwindSafe, + TaskFn: FnOnce(TaskCallback) -> Result + Send + UnwindSafe + 'static, + TaskRet: IntoIntoDart, + D: IntoDart; + + /// Same as [`wrap`][Handler::wrap], but the Rust function must return a [SyncReturn] and + /// need not implement [Send]. + fn wrap_sync( + &self, + wrap_info: WrapInfo, + sync_task: SyncTaskFn, + ) -> WireSyncReturn + where + SyncTaskFn: FnOnce() -> Result, BridgeError> + UnwindSafe, + TaskRet: IntoDart; +} + +/// The simple handler uses a simple thread pool to execute tasks. +pub struct SimpleHandler { + executor: E, + error_handler: EH, +} + +impl SimpleHandler { + /// 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, ReportDartErrorHandler>; + +impl Default for DefaultHandler { + fn default() -> Self { + Self::new( + BridgeTaskExecutor::new(ReportDartErrorHandler), + ReportDartErrorHandler, + ) + } +} + +impl Handler for SimpleHandler { + fn wrap(&self, wrap_info: WrapInfo, prepare: PrepareFn) + where + PrepareFn: FnOnce() -> TaskFn + UnwindSafe, + TaskFn: FnOnce(TaskCallback) -> Result + Send + UnwindSafe + 'static, + TaskRet: IntoIntoDart, + 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( + &self, + wrap_info: WrapInfo, + sync_task: SyncTaskFn, + ) -> WireSyncReturn + where + TaskRet: IntoDart, + SyncTaskFn: FnOnce() -> Result, 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(&self, wrap_info: WrapInfo, task: TaskFn) + where + TaskFn: FnOnce(TaskCallback) -> Result + Send + UnwindSafe + 'static, + TaskRet: IntoIntoDart, + D: IntoDart; + + /// Executes a Rust function that returns a [SyncReturn]. + fn execute_sync( + &self, + wrap_info: WrapInfo, + sync_task: SyncTaskFn, + ) -> Result, BridgeError> + where + SyncTaskFn: FnOnce() -> Result, BridgeError> + UnwindSafe, + TaskRet: IntoDart; +} + +/// The default executor used. +pub struct BridgeTaskExecutor { + error_handler: EH, +} + +impl BridgeTaskExecutor { + pub fn new(error_handler: EH) -> Self { + BridgeTaskExecutor { error_handler } + } +} + +impl Executor for BridgeTaskExecutor { + fn execute(&self, wrap_info: WrapInfo, task: TaskFn) + where + TaskFn: FnOnce(TaskCallback) -> Result + Send + UnwindSafe + 'static, + TaskRet: IntoIntoDart, + D: IntoDart, + { + let eh = self.error_handler; + let eh2 = self.error_handler; + + let WrapInfo { port, mode, .. } = wrap_info; + + spawn_bridge_task!(|port: Option| { + 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( + &self, + _wrap_info: WrapInfo, + sync_task: SyncTaskFn, + ) -> Result, BridgeError> + where + SyncTaskFn: FnOnce() -> Result, 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), +} + +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::() { + Some(s) => &s[..], + None => "Box", + }, + } + .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(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; +} diff --git a/native/hub/src/bridge/bridge_engine/into_into_dart.rs b/native/hub/src/bridge/bridge_engine/into_into_dart.rs new file mode 100644 index 0000000..54a1614 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/into_into_dart.rs @@ -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 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 { + fn into_into_dart(self) -> D; +} + +impl IntoIntoDart> for Vec +where + T: IntoIntoDart, + Vec: IntoDart, + D: IntoDart, +{ + fn into_into_dart(self) -> Vec { + self.into_iter().map(|e| e.into_into_dart()).collect() + } +} + +impl IntoIntoDart> for Option +where + T: IntoIntoDart, + D: IntoDart, +{ + fn into_into_dart(self) -> Option { + self.map(|e| e.into_into_dart()) + } +} + +impl IntoIntoDart> for RustOpaque +where + T: DartSafe, +{ + fn into_into_dart(self) -> RustOpaque { + self + } +} + +impl IntoIntoDart> for ZeroCopyBuffer +where + T: IntoIntoDart, + D: IntoDart, + ZeroCopyBuffer: IntoDart, +{ + fn into_into_dart(self) -> ZeroCopyBuffer { + ZeroCopyBuffer(self.0.into_into_dart()) + } +} + +impl IntoIntoDart<[T; C]> for [T; C] +where + T: IntoDart, + [T; C]: IntoDart, +{ + fn into_into_dart(self) -> [T; C] { + self + } +} + +impl IntoIntoDart for Box +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 IntoIntoDart<(AD, BD)> for (A, B) +where + A: IntoIntoDart, + AD: IntoDart, + B: IntoIntoDart, + BD: IntoDart, +{ + fn into_into_dart(self) -> (AD, BD) { + (self.0.into_into_dart(), self.1.into_into_dart()) + } +} +impl IntoIntoDart<(AD, BD, CD)> for (A, B, C) +where + A: IntoIntoDart, + AD: IntoDart, + B: IntoIntoDart, + BD: IntoDart, + C: IntoIntoDart, + 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 IntoIntoDart<(AD, BD, CD, DD)> for (A, B, C, D) +where + A: IntoIntoDart, + AD: IntoDart, + B: IntoIntoDart, + BD: IntoDart, + C: IntoIntoDart, + CD: IntoDart, + D: IntoIntoDart
, + 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 IntoIntoDart<(AD, BD, CD, DD, ED)> for (A, B, C, D, E) +where + A: IntoIntoDart, + AD: IntoDart, + B: IntoIntoDart, + BD: IntoDart, + C: IntoIntoDart, + CD: IntoDart, + D: IntoIntoDart
, + DD: IntoDart, + E: IntoIntoDart, + 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); diff --git a/native/hub/src/bridge/bridge_engine/macros.rs b/native/hub/src/bridge/bridge_engine/macros.rs new file mode 100644 index 0000000..5b8181d --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/macros.rs @@ -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)*)) + }; +} diff --git a/native/hub/src/bridge/bridge_engine/mod.rs b/native/hub/src/bridge/bridge_engine/mod.rs new file mode 100644 index 0000000..cb66f3d --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/mod.rs @@ -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(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 DartSafe for T {} diff --git a/native/hub/src/bridge/bridge_engine/rust2dart.rs b/native/hub/src/bridge/bridge_engine/rust2dart.rs new file mode 100644 index 0000000..8e1bc6c --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/rust2dart.rs @@ -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(&self) -> StreamSink + where + T: IntoIntoDart, + 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 { + #[cfg(not(target_family = "wasm"))] + rust2dart: Rust2Dart, + #[cfg(target_family = "wasm")] + handle: ChannelHandle, + _phantom_data: PhantomData, +} + +impl StreamSink { + /// 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(&self, value: T) -> bool + where + T: IntoIntoDart, + { + 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() + } +} diff --git a/native/hub/src/bridge/bridge_engine/support.rs b/native/hub/src/bridge/bridge_engine/support.rs new file mode 100644 index 0000000..53c2b2e --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/support.rs @@ -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(fill: T, length: i32) -> *mut T { + into_leak_vec_ptr(vec![fill; length as usize]).0 +} + +pub fn into_leak_vec_ptr(mut v: Vec) -> (*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(ptr: *mut T, len: i32) -> Vec { + Vec::from_raw_parts(ptr, len as usize, len as usize) +} + +/// Convert [Vec] to array length `N`. +/// +/// # Panics +/// +/// Panics if length of [Vec] != `N`. +pub fn from_vec_to_array(v: Vec) -> [T; N] { + core::convert::TryInto::try_into(v) + .unwrap_or_else(|v: Vec| 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) -> *mut T { + let x: Box = Box::new(t); + Box::into_raw(x) +} + +/// # Safety +/// Use it in pair with [new_leak_box_ptr]. +pub unsafe fn box_from_leak_ptr(ptr: *mut T) -> Box { + 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(buffer: Vec) -> 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; diff --git a/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/mod.rs b/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/mod.rs new file mode 100644 index 0000000..7a2cc6d --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/mod.rs @@ -0,0 +1,4 @@ +//! Code originally sourced from wasm-bindgen's repository. + +pub mod transfer; +pub mod worker; diff --git a/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/transfer.rs b/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/transfer.rs new file mode 100644 index 0000000..66ceed8 --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/transfer.rs @@ -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 { + /// Posts a [*mut [TransferClosurePayload], ...[JsValue]] 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, + 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::() + .post_message(&JsValue::UNDEFINED) +} diff --git a/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/worker.rs b/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/worker.rs new file mode 100644 index 0000000..feb0d7b --- /dev/null +++ b/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/worker.rs @@ -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 +} diff --git a/native/hub/src/bridge/bridge_generated.io.rs b/native/hub/src/bridge/bridge_generated.io.rs new file mode 100644 index 0000000..7a8e792 --- /dev/null +++ b/native/hub/src/bridge/bridge_generated.io.rs @@ -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 for *mut wire_RustRequestUnique { + fn wire2api(self) -> RustRequestUnique { + let wrap = unsafe { support::box_from_leak_ptr(self) }; + Wire2Api::::wire2api(*wrap).into() + } +} + +impl Wire2Api 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 for wire_RustRequestUnique { + fn wire2api(self) -> RustRequestUnique { + RustRequestUnique { + id: self.id.wire2api(), + request: self.request.wire2api(), + } + } +} + +impl Wire2Api> for *mut wire_uint_8_list { + fn wire2api(self) -> Vec { + 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 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); + }; +} diff --git a/native/hub/src/bridge/bridge_generated.rs b/native/hub/src/bridge/bridge_generated.rs new file mode 100644 index 0000000..aaad870 --- /dev/null +++ b/native/hub/src/bridge/bridge_generated.rs @@ -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 + 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 { + fn wire2api(self) -> T; +} + +impl Wire2Api> for *mut S +where + *mut S: Wire2Api, +{ + fn wire2api(self) -> Option { + (!self.is_null()).then(|| self.wire2api()) + } +} + +impl Wire2Api for i32 { + fn wire2api(self) -> i32 { + self + } +} + +impl Wire2Api 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 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 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 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 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::*; diff --git a/native/hub/src/bridge/bridge_generated.web.rs b/native/hub/src/bridge/bridge_generated.web.rs new file mode 100644 index 0000000..60c21fc --- /dev/null +++ b/native/hub/src/bridge/bridge_generated.web.rs @@ -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>> for Option> { + fn wire2api(self) -> Option> { + self.map(Wire2Api::wire2api) + } +} + +impl Wire2Api for JsValue { + fn wire2api(self) -> RustRequest { + let self_ = self.dyn_into::().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 for JsValue { + fn wire2api(self) -> RustRequestUnique { + let self_ = self.dyn_into::().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> for Box<[u8]> { + fn wire2api(self) -> Vec { + self.into_vec() + } +} +// Section: impl Wire2Api for JsValue + +impl Wire2Api for JsValue { + fn wire2api(self) -> i32 { + self.unchecked_into_f64() as _ + } +} +impl Wire2Api>> for JsValue { + fn wire2api(self) -> Option> { + (!self.is_undefined() && !self.is_null()).then(|| self.wire2api()) + } +} +impl Wire2Api for JsValue { + fn wire2api(self) -> RustOperation { + (self.unchecked_into_f64() as i32).wire2api() + } +} +impl Wire2Api for JsValue { + fn wire2api(self) -> u8 { + self.unchecked_into_f64() as _ + } +} +impl Wire2Api> for JsValue { + fn wire2api(self) -> Vec { + self.unchecked_into::().to_vec().into() + } +} diff --git a/native/hub/src/bridge/mod.rs b/native/hub/src/bridge/mod.rs new file mode 100644 index 0000000..87c47b0 --- /dev/null +++ b/native/hub/src/bridge/mod.rs @@ -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 { + 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); + } + }); +} diff --git a/native/hub/src/imagecrop.rs b/native/hub/src/imagecrop.rs new file mode 100644 index 0000000..95c5846 --- /dev/null +++ b/native/hub/src/imagecrop.rs @@ -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) -> ImageResult { + 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) -> 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) -> 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 = 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(), + } +} diff --git a/native/hub/src/lib.rs b/native/hub/src/lib.rs new file mode 100644 index 0000000..80efacb --- /dev/null +++ b/native/hub/src/lib.rs @@ -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); + }); + } +} diff --git a/native/hub/src/web_alias.rs b/native/hub/src/web_alias.rs new file mode 100644 index 0000000..944446c --- /dev/null +++ b/native/hub/src/web_alias.rs @@ -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(future: F) -> tokio::task::JoinHandle +where + F: std::future::Future + Send + 'static, + T: Send + 'static, +{ + tokio::task::spawn(future) +} +#[cfg(target_family = "wasm")] +pub(crate) fn spawn(future: F) -> async_wasm_task::JoinHandle +where + F: std::future::Future + '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(callable: C) -> tokio::task::JoinHandle +where + C: FnOnce() -> T + Send + 'static, + T: Send + 'static, +{ + tokio::task::spawn_blocking(callable) +} +#[cfg(target_family = "wasm")] +pub(crate) fn spawn_blocking(callable: C) -> async_wasm_task::JoinHandle +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; +} diff --git a/native/hub/src/with_request.rs b/native/hub/src/with_request.rs new file mode 100644 index 0000000..b548ee9 --- /dev/null +++ b/native/hub/src/with_request.rs @@ -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, + } +} diff --git a/native/sample_crate/Cargo.toml b/native/sample_crate/Cargo.toml new file mode 100644 index 0000000..7557f00 --- /dev/null +++ b/native/sample_crate/Cargo.toml @@ -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"] } diff --git a/native/sample_crate/src/lib.rs b/native/sample_crate/src/lib.rs new file mode 100644 index 0000000..ca86580 --- /dev/null +++ b/native/sample_crate/src/lib.rs @@ -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 { + 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 { + None +} + +// `chrono` supports all platforms when `wasmbind` feature is enabled. + +use chrono::{offset, DateTime}; +pub fn get_current_time() -> DateTime { + offset::Local::now() +} diff --git a/native/sample_crate/src/mandelbrot.rs b/native/sample_crate/src/mandelbrot.rs new file mode 100644 index 0000000..42556f2 --- /dev/null +++ b/native/sample_crate/src/mandelbrot.rs @@ -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, limit: usize) -> Option { + 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, + lower_right: Complex, +) -> Complex { + 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, + lower_right: Complex, +) { + 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 { + 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> { + 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> { + 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) +} diff --git a/pubspec.lock b/pubspec.lock index 5e90d44..7c5fe1b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1052,6 +1052,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + protobuf: + dependency: "direct main" + description: + name: protobuf + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" + source: hosted + version: "3.1.0" pub_semver: dependency: transitive description: @@ -1092,6 +1100,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.1" + rinf: + dependency: "direct main" + description: + name: rinf + sha256: "71162fe4f4619cddf8eac869d56ceca50fe6e9d0ae249f3ce1bbf1f5457844ec" + url: "https://pub.dev" + source: hosted + version: "4.13.1" riverpod: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1d642da..d4a8876 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index dbd786d..eb98eaa 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -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)