Skip to content

Commit 352a363

Browse files
Reef-7sanchitmonga22
authored andcommitted
Address review feedback: fix voice agent race, and resolve FFI signature mismatches
1 parent 1a2b032 commit 352a363

3 files changed

Lines changed: 183 additions & 126 deletions

File tree

sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_llm.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ class DartBridgeLLM {
150150
final namePtr = modelName.toNativeUtf8();
151151

152152
try {
153+
_logger.debug('Calling rac_llm_component_load_model with handle=$handle');
153154
final result = NativeFunctions.llmLoadModel(handle, pathPtr, idPtr, namePtr);
154155
_logger.debug(
155156
'rac_llm_component_load_model returned: $result (${RacResultCode.getMessage(result)})');
@@ -454,7 +455,7 @@ void _streamingIsolateEntry(_StreamingIsolateParams params) {
454455
// Set systemPrompt if provided
455456
if (params.systemPrompt != null && params.systemPrompt!.isNotEmpty) {
456457
systemPromptPtr = params.systemPrompt!.toNativeUtf8();
457-
optionsPtr.ref.systemPrompt = systemPromptPtr!;
458+
optionsPtr.ref.systemPrompt = systemPromptPtr;
458459
} else {
459460
optionsPtr.ref.systemPrompt = nullptr;
460461
}
@@ -521,7 +522,7 @@ void _streamingIsolateEntry(_StreamingIsolateParams params) {
521522
calloc.free(promptPtr);
522523
calloc.free(optionsPtr);
523524
if (systemPromptPtr != null) {
524-
calloc.free(systemPromptPtr!);
525+
calloc.free(systemPromptPtr);
525526
}
526527
_isolateSendPort = null;
527528
}
@@ -590,7 +591,7 @@ _IsolateGenerationResult _generateInIsolate(
590591
// Set systemPrompt if provided
591592
if (systemPrompt != null && systemPrompt.isNotEmpty) {
592593
systemPromptPtr = systemPrompt.toNativeUtf8();
593-
optionsPtr.ref.systemPrompt = systemPromptPtr!;
594+
optionsPtr.ref.systemPrompt = systemPromptPtr;
594595
} else {
595596
optionsPtr.ref.systemPrompt = nullptr;
596597
}
@@ -626,7 +627,7 @@ _IsolateGenerationResult _generateInIsolate(
626627
calloc.free(optionsPtr);
627628
calloc.free(resultPtr);
628629
if (systemPromptPtr != null) {
629-
calloc.free(systemPromptPtr!);
630+
calloc.free(systemPromptPtr);
630631
}
631632
}
632633
}

sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ import 'package:runanywhere/native/ffi_types.dart';
2020
import 'package:runanywhere/native/native_functions.dart';
2121
import 'package:runanywhere/native/platform_loader.dart';
2222

23-
/// Voice agent handle type (opaque pointer to rac_voice_agent struct).
24-
typedef RacVoiceAgentHandle = Pointer<Void>;
25-
2623
/// VoiceAgent component bridge for C++ interop.
2724
///
2825
/// Orchestrates LLM, STT, TTS, and VAD components for voice conversations.
@@ -45,7 +42,8 @@ class DartBridgeVoiceAgent {
4542

4643
// MARK: - State
4744

48-
RacVoiceAgentHandle? _handle;
45+
RacHandle? _handle;
46+
Future<RacHandle>? _initFuture;
4947
final _logger = SDKLogger('DartBridge.VoiceAgent');
5048

5149
/// Event stream controller
@@ -60,11 +58,20 @@ class DartBridgeVoiceAgent {
6058
///
6159
/// Requires LLM, STT, TTS, and VAD components to be available.
6260
/// Uses shared component handles (matches Swift CppBridge+VoiceAgent.swift).
63-
Future<RacVoiceAgentHandle> getHandle() async {
61+
Future<RacHandle> getHandle() async {
6462
if (_handle != null) {
6563
return _handle!;
6664
}
6765

66+
final initFuture = _initFuture;
67+
if (initFuture != null) {
68+
await initFuture;
69+
return _handle!;
70+
}
71+
72+
final completer = Completer<RacHandle>();
73+
_initFuture = completer.future;
74+
6875
// Use shared component handles (matches Swift approach)
6976
// This allows the voice agent to use already-loaded models from the
7077
// individual component bridges (STT, LLM, TTS, VAD)
@@ -77,7 +84,7 @@ class DartBridgeVoiceAgent {
7784
'Creating voice agent with shared handles: LLM=$llmHandle, STT=$sttHandle, TTS=$ttsHandle, VAD=$vadHandle');
7885

7986
try {
80-
final handlePtr = calloc<RacVoiceAgentHandle>();
87+
final handlePtr = calloc<RacHandle>();
8188
try {
8289
final result = NativeFunctions.voiceAgentCreate(
8390
llmHandle, sttHandle, ttsHandle, vadHandle, handlePtr);
@@ -90,12 +97,18 @@ class DartBridgeVoiceAgent {
9097

9198
_handle = handlePtr.value;
9299
_logger.info('Voice agent created with shared component handles');
100+
completer.complete(_handle!);
101+
_initFuture = null;
93102
return _handle!;
94103
} finally {
95104
calloc.free(handlePtr);
96105
}
97-
} catch (e) {
106+
} catch (e, st) {
98107
_logger.error('Failed to create voice agent handle: $e');
108+
if (!completer.isCompleted) {
109+
completer.completeError(e, st);
110+
}
111+
_initFuture = null;
99112
rethrow;
100113
}
101114
}
@@ -300,7 +313,7 @@ class DartBridgeVoiceAgent {
300313
/// Static helper for processing voice turn in an isolate.
301314
/// The C++ API expects raw audio bytes (PCM16), not float samples.
302315
static Future<VoiceTurnResult> _processVoiceTurnInIsolate(
303-
RacVoiceAgentHandle handle,
316+
RacHandle handle,
304317
Uint8List audioData,
305318
) async {
306319
// Allocate native memory for audio data (raw PCM16 bytes)
@@ -311,16 +324,8 @@ class DartBridgeVoiceAgent {
311324
// Efficient bulk copy of audio bytes
312325
audioPtr.asTypedList(audioData.length).setAll(0, audioData);
313326

314-
final lib = PlatformLoader.loadCommons();
315-
final processFn = lib.lookupFunction<
316-
Int32 Function(RacVoiceAgentHandle, Pointer<Void>, IntPtr,
317-
Pointer<RacVoiceAgentResultStruct>),
318-
int Function(RacVoiceAgentHandle, Pointer<Void>, int,
319-
Pointer<RacVoiceAgentResultStruct>)>(
320-
'rac_voice_agent_process_voice_turn');
321-
322-
final status =
323-
processFn(handle, audioPtr.cast<Void>(), audioData.length, resultPtr);
327+
final status = _processVoiceTurnFn(
328+
handle, audioPtr.cast<Void>(), audioData.length, resultPtr);
324329

325330
if (status != RAC_SUCCESS) {
326331
throw StateError(
@@ -329,20 +334,14 @@ class DartBridgeVoiceAgent {
329334
}
330335

331336
// Parse result while still in isolate (before freeing memory)
332-
return _parseVoiceTurnResultStatic(resultPtr.ref, lib);
337+
return _parseVoiceTurnResultStatic(resultPtr.ref, _voiceAgentLib);
333338
} finally {
334339
// Free audio data
335340
calloc.free(audioPtr);
336341

337342
// Free result struct - the C++ side allocates strings/audio that need freeing
338-
final lib = PlatformLoader.loadCommons();
339343
try {
340-
final freeFn = lib.lookupFunction<
341-
Void Function(Pointer<RacVoiceAgentResultStruct>),
342-
void Function(Pointer<RacVoiceAgentResultStruct>)>(
343-
'rac_voice_agent_result_free',
344-
);
345-
freeFn(resultPtr);
344+
_voiceAgentResultFreeFn?.call(resultPtr);
346345
} catch (e) {
347346
// Function may not exist, just free the struct
348347
}
@@ -600,3 +599,34 @@ final class RacVoiceAgentResultStruct extends Struct {
600599
@IntPtr()
601600
external int synthesizedAudioSize; // size_t (size in bytes)
602601
}
602+
603+
// MARK: - Isolate-scoped FFI caches
604+
605+
// These are intentionally top-level statics so each isolate initializes them
606+
// once on first use. This keeps symbol lookups out of hot paths while preserving
607+
// the existing isolate execution model.
608+
final DynamicLibrary _voiceAgentLib = PlatformLoader.loadCommons();
609+
610+
final int Function(
611+
RacHandle,
612+
Pointer<Void>,
613+
int,
614+
Pointer<RacVoiceAgentResultStruct>,
615+
) _processVoiceTurnFn = _voiceAgentLib.lookupFunction<
616+
Int32 Function(RacHandle, Pointer<Void>, IntPtr,
617+
Pointer<RacVoiceAgentResultStruct>),
618+
int Function(RacHandle, Pointer<Void>, int,
619+
Pointer<RacVoiceAgentResultStruct>)>('rac_voice_agent_process_voice_turn');
620+
621+
final void Function(Pointer<RacVoiceAgentResultStruct>)?
622+
_voiceAgentResultFreeFn = (() {
623+
try {
624+
return _voiceAgentLib.lookupFunction<
625+
Void Function(Pointer<RacVoiceAgentResultStruct>),
626+
void Function(Pointer<RacVoiceAgentResultStruct>)>(
627+
'rac_voice_agent_result_free',
628+
);
629+
} catch (_) {
630+
return null;
631+
}
632+
})();

0 commit comments

Comments
 (0)