Skip to content

Commit

Permalink
feat: Add experimental web FFI.
Browse files Browse the repository at this point in the history
  • Loading branch information
iphydf committed Feb 10, 2025
1 parent 53dc6d0 commit 73233b6
Show file tree
Hide file tree
Showing 38 changed files with 1,121 additions and 372 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ jobs:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- run: ./tools/prepare-web
- run: flutter build web --wasm
- run: flutter build web

windows-build:
runs-on: windows-2022
Expand Down
2 changes: 1 addition & 1 deletion .netlify/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ ARG TARGET=release

COPY --chown=builder:builder . .
RUN tools/prepare-web \
&& flutter build web --wasm "--$TARGET" \
&& flutter build web "--$TARGET" \
&& mv build/web _site \
&& cp web/_headers _site/
3 changes: 3 additions & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ words:
- blocksize
- btox
- dylib
- emscripten
- ffigen
- iphydf
- kannywood
- libtoxcore
- malloc
- messagepack
- msgpack
- nospam
- randombytes
- robinlinden
Expand Down
2 changes: 2 additions & 0 deletions lib/api/toxcore/tox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ final class ApiException<T extends Enum> implements Exception {
}

abstract class Tox {
const Tox();

String get address;

bool get isAlive;
Expand Down
23 changes: 20 additions & 3 deletions lib/btox_app.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import 'package:btox/api/toxcore/tox.dart';
import 'package:btox/db/database.dart';
import 'package:btox/pages/contact_list_page.dart';
import 'package:btox/pages/create_profile_page.dart';
import 'package:btox/pages/select_profile_page.dart';
import 'package:btox/providers/database.dart';
import 'package:btox/providers/sodium.dart';
import 'package:btox/providers/tox.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sodium/sodium.dart';

part 'btox_app.g.dart';

@riverpod
Future<(Database, Sodium, ToxConstants)> appInit(Ref ref) async {
return (
await ref.watch(databaseProvider.future),
await ref.watch(sodiumProvider.future),
await ref.watch(toxConstantsProvider.future),
);
}

final class BtoxApp extends ConsumerWidget {
const BtoxApp({super.key});
Expand All @@ -21,7 +36,7 @@ final class BtoxApp extends ConsumerWidget {
brightness: Brightness.dark,
primarySwatch: Colors.blue,
),
home: ref.watch(databaseProvider).when(
home: ref.watch(appInitProvider).when(
loading: () => const Scaffold(
body: Center(
child: CircularProgressIndicator(),
Expand All @@ -32,15 +47,16 @@ final class BtoxApp extends ConsumerWidget {
child: Text('Error: $error'),
),
),
data: (database) {
final constants = ref.read(toxConstantsProvider);
data: (init) {
final (database, sodium, constants) = init;
return StreamBuilder<List<Profile>>(
stream: database.watchProfiles(),
builder: (context, snapshot) {
final profiles = snapshot.data ?? const [];
if (profiles.isEmpty) {
return CreateProfilePage(
constants: constants,
sodium: sodium,
database: database,
);
}
Expand All @@ -50,6 +66,7 @@ final class BtoxApp extends ConsumerWidget {
if (activeProfiles.isEmpty) {
return SelectProfilePage(
constants: constants,
sodium: sodium,
database: database,
profiles: profiles,
);
Expand Down
4 changes: 3 additions & 1 deletion lib/db/shared.dart
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export 'web.dart' if (dart.library.ffi) 'native.dart';
export 'unsupported.dart'
if (dart.library.ffi) 'native.dart'
if (dart.library.js_interop) 'web.dart';
5 changes: 5 additions & 0 deletions lib/db/unsupported.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:btox/db/database.dart';

Future<Database> constructDb(String databaseKey) async {
throw UnimplementedError('Database not supported on this platform');
}
35 changes: 32 additions & 3 deletions lib/ffi/tox_constants.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
export 'tox_constants.web.dart'
if (dart.library.html) 'tox_constants.web.dart'
if (dart.library.ffi) 'tox_constants.native.dart';
import 'package:btox/api/toxcore/tox.dart';
import 'package:btox/ffi/tox_library.dart';

ToxConstants toxcoreConstants(ToxLibrary lib) {
return ToxConstants(
addressSize: lib.ffi.tox_address_size(),
conferenceIdSize: lib.ffi.tox_conference_id_size(),
fileIdLength: lib.ffi.tox_file_id_length(),
groupChatIdSize: lib.ffi.tox_group_chat_id_size(),
groupMaxCustomLosslessPacketLength:
lib.ffi.tox_group_max_custom_lossless_packet_length(),
groupMaxCustomLossyPacketLength:
lib.ffi.tox_group_max_custom_lossy_packet_length(),
groupMaxGroupNameLength: lib.ffi.tox_group_max_group_name_length(),
groupMaxMessageLength: lib.ffi.tox_group_max_message_length(),
groupMaxPartLength: lib.ffi.tox_group_max_part_length(),
groupMaxPasswordSize: lib.ffi.tox_group_max_password_size(),
groupMaxTopicLength: lib.ffi.tox_group_max_topic_length(),
groupPeerPublicKeySize: lib.ffi.tox_group_peer_public_key_size(),
hashLength: lib.ffi.tox_hash_length(),
maxCustomPacketSize: lib.ffi.tox_max_custom_packet_size(),
maxFilenameLength: lib.ffi.tox_max_filename_length(),
maxFriendRequestLength: lib.ffi.tox_max_friend_request_length(),
maxHostnameLength: lib.ffi.tox_max_hostname_length(),
maxMessageLength: lib.ffi.tox_max_message_length(),
maxNameLength: lib.ffi.tox_max_name_length(),
maxStatusMessageLength: lib.ffi.tox_max_status_message_length(),
nospamSize: lib.ffi.tox_nospam_size(),
publicKeySize: lib.ffi.tox_public_key_size(),
secretKeySize: lib.ffi.tox_secret_key_size(),
);
}
30 changes: 0 additions & 30 deletions lib/ffi/tox_constants.native.dart

This file was deleted.

28 changes: 0 additions & 28 deletions lib/ffi/tox_constants.web.dart

This file was deleted.

3 changes: 0 additions & 3 deletions lib/ffi/tox_ffi.dart

This file was deleted.

18 changes: 0 additions & 18 deletions lib/ffi/tox_ffi.native.dart

This file was deleted.

13 changes: 0 additions & 13 deletions lib/ffi/tox_ffi.web.dart

This file was deleted.

3 changes: 3 additions & 0 deletions lib/ffi/tox_library.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export 'tox_library.unsupported.dart'
if (dart.library.ffi) 'tox_library.ffi.dart'
if (dart.library.js_interop) 'tox_library.js.dart';
34 changes: 34 additions & 0 deletions lib/ffi/tox_library.ffi.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'dart:ffi';
import 'dart:io';

import 'package:btox/ffi/generated/toxcore.ffi.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

export 'dart:ffi';

export 'package:btox/ffi/generated/toxcore.ffi.dart';

part 'tox_library.ffi.g.dart';

@riverpod
Future<ToxLibrary> toxFfi(Ref ref) async {
return ToxLibrary(
malloc,
ToxFfi(Platform.isAndroid
? DynamicLibrary.open('libtoxcore.so')
: DynamicLibrary.process()),
);
}

typedef CChar = Char;

typedef CEnum = UnsignedInt;

final class ToxLibrary {
final Allocator allocator;
final ToxFfi ffi;

const ToxLibrary(this.allocator, this.ffi);
}
83 changes: 83 additions & 0 deletions lib/ffi/tox_library.js.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import 'package:btox/ffi/generated/toxcore.js.dart';
import 'package:btox/logger.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:wasm_ffi/ffi.dart';

export 'package:btox/ffi/generated/toxcore.js.dart';
export 'package:wasm_ffi/ffi.dart';

part 'tox_library.js.g.dart';

const _logger = Logger(['ToxLibrary']);

@riverpod
Future<ToxLibrary> toxFfi(Ref ref) async {
_logger.d('Loading libtoxcore.wasm');
final library = await DynamicLibrary.open('libtoxcore.js');
_logger.d('Registering Tox types');
_registerToxTypes();
_logger.d('Successfully loaded libtoxcore.js');
return ToxLibrary(library.allocator, ToxFfi(library));
}

void _registerToxTypes() {
registerOpaqueType<Tox>();
registerOpaqueType<Tox_Options>();
registerOpaqueType<Tox_Event_Conference_Connected>();
registerOpaqueType<Tox_Event_Conference_Invite>();
registerOpaqueType<Tox_Event_Conference_Message>();
registerOpaqueType<Tox_Event_Conference_Peer_List_Changed>();
registerOpaqueType<Tox_Event_Conference_Peer_Name>();
registerOpaqueType<Tox_Event_Conference_Title>();
registerOpaqueType<Tox_Event_File_Chunk_Request>();
registerOpaqueType<Tox_Event_File_Recv>();
registerOpaqueType<Tox_Event_File_Recv_Chunk>();
registerOpaqueType<Tox_Event_File_Recv_Control>();
registerOpaqueType<Tox_Event_Friend_Connection_Status>();
registerOpaqueType<Tox_Event_Friend_Lossless_Packet>();
registerOpaqueType<Tox_Event_Friend_Lossy_Packet>();
registerOpaqueType<Tox_Event_Friend_Message>();
registerOpaqueType<Tox_Event_Friend_Name>();
registerOpaqueType<Tox_Event_Friend_Read_Receipt>();
registerOpaqueType<Tox_Event_Friend_Request>();
registerOpaqueType<Tox_Event_Friend_Status>();
registerOpaqueType<Tox_Event_Friend_Status_Message>();
registerOpaqueType<Tox_Event_Friend_Typing>();
registerOpaqueType<Tox_Event_Self_Connection_Status>();
registerOpaqueType<Tox_Event_Group_Peer_Name>();
registerOpaqueType<Tox_Event_Group_Peer_Status>();
registerOpaqueType<Tox_Event_Group_Topic>();
registerOpaqueType<Tox_Event_Group_Privacy_State>();
registerOpaqueType<Tox_Event_Group_Voice_State>();
registerOpaqueType<Tox_Event_Group_Topic_Lock>();
registerOpaqueType<Tox_Event_Group_Peer_Limit>();
registerOpaqueType<Tox_Event_Group_Password>();
registerOpaqueType<Tox_Event_Group_Message>();
registerOpaqueType<Tox_Event_Group_Private_Message>();
registerOpaqueType<Tox_Event_Group_Custom_Packet>();
registerOpaqueType<Tox_Event_Group_Custom_Private_Packet>();
registerOpaqueType<Tox_Event_Group_Invite>();
registerOpaqueType<Tox_Event_Group_Peer_Join>();
registerOpaqueType<Tox_Event_Group_Peer_Exit>();
registerOpaqueType<Tox_Event_Group_Self_Join>();
registerOpaqueType<Tox_Event_Group_Join_Fail>();
registerOpaqueType<Tox_Event_Group_Moderation>();
registerOpaqueType<Tox_Event_Dht_Nodes_Response>();
registerOpaqueType<Tox_Event>();
registerOpaqueType<Tox_Events>();
registerOpaqueType<Tox_System>();
registerOpaqueType<ToxAV>();
registerOpaqueType<Tox_Pass_Key>();
}

typedef CChar = Int8;

typedef CEnum = Uint32;

final class ToxLibrary {
final Allocator allocator;
final ToxFfi ffi;

const ToxLibrary(this.allocator, this.ffi);
}
25 changes: 25 additions & 0 deletions lib/ffi/tox_library.unsupported.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:btox/ffi/generated/toxcore.js.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:wasm_ffi/ffi.dart';

export 'package:btox/ffi/generated/toxcore.js.dart';
export 'package:wasm_ffi/ffi.dart';

part 'tox_library.unsupported.g.dart';

@riverpod
Future<ToxLibrary> toxFfi(Ref ref) async {
throw UnimplementedError('Tox FFI is not implemented on this platform');
}

typedef CChar = Int8;

typedef CEnum = Uint32;

final class ToxLibrary {
final Allocator allocator;
final ToxFfi ffi;

const ToxLibrary(this.allocator, this.ffi);
}
Loading

0 comments on commit 73233b6

Please sign in to comment.