|
1 | 1 | import 'dart:ffi'; |
2 | 2 |
|
3 | | -import '../../modelinfo/entity_definition.dart'; |
4 | | -import '../store.dart'; |
5 | 3 | import 'bindings.dart'; |
| 4 | +import 'helpers.dart'; |
6 | 5 |
|
7 | | -// ignore_for_file: public_member_api_docs |
| 6 | +/// Callback for reading data one-by-one, see [visit]. |
| 7 | +typedef VisitCallback = bool Function(Pointer<Uint8> data, int size); |
8 | 8 |
|
9 | | -/// When you want to pass a dart callback to a C function you cannot use lambdas |
10 | | -/// and instead the callback must be a static function - giving a lambda to |
11 | | -/// [Pointer.fromFunction()] won't compile: |
12 | | -/// Error: fromFunction expects a static function as parameter. |
13 | | -/// dart:ffi only supports calling static Dart functions from native code. |
| 9 | +/// Currently FFI's Pointer.fromFunction only allows to pass a static Dart |
| 10 | +/// callback function. When passing a closure it would throw at runtime: |
| 11 | +/// Error: fromFunction expects a static function as parameter. |
| 12 | +/// dart:ffi only supports calling static Dart functions from native code. |
| 13 | +/// Closures and tear-offs are not supported because they can capture context. |
14 | 14 | /// |
15 | | -/// With Dart being all synchronous and not sharing memory at all within a |
16 | | -/// single isolate, we can just alter a single global callback variable. |
17 | | -/// Therefore, let's have a single static function [_forwarder] converted to a |
18 | | -/// native visitor pointer [_nativeVisitor], calling [_callback] in the end. |
19 | | -
|
20 | | -bool Function(Pointer<Uint8> data, int size) _callback = _callback; |
| 15 | +/// So given that and |
| 16 | +/// - it is required that each query has its own callback, |
| 17 | +/// - it is possible that as part of visiting results another query is created |
| 18 | +/// and visits its results (e.g. query run in entity constructor or setter) and |
| 19 | +/// - Dart code within an isolate is executed synchronously: |
| 20 | +/// |
| 21 | +/// Create a single static callback function [_callbackWrapper] that wraps |
| 22 | +/// the actual Dart callback of the query currently visiting results. |
| 23 | +/// Keep callbacks on a [_callbackStack] to restore the callback of an outer |
| 24 | +/// query once a nested query is finished visiting results. |
| 25 | +List<VisitCallback> _callbackStack = []; |
21 | 26 |
|
22 | | -bool _forwarder(Pointer<Uint8> dataPtr, int size, Pointer<Void> _) => |
23 | | - _callback(dataPtr, size); |
| 27 | +bool _callbackWrapper(Pointer<Uint8> dataPtr, int size, Pointer<Void> _) => |
| 28 | + _callbackStack.last(dataPtr, size); |
24 | 29 |
|
25 | | -final Pointer<obx_data_visitor> _nativeVisitor = |
26 | | - Pointer.fromFunction(_forwarder, false); |
| 30 | +final Pointer<obx_data_visitor> _callbackWrapperPtr = |
| 31 | + Pointer.fromFunction(_callbackWrapper, false); |
27 | 32 |
|
28 | | -/// The callback for reading data one-by-one. |
| 33 | +/// Visits query results. |
29 | 34 | /// |
| 35 | +/// Pass a [callback] for reading data one-by-one: |
30 | 36 | /// - [data] is the read data buffer. |
31 | 37 | /// - [size] specifies the length of the read data. |
32 | 38 | /// - Return true to keep going, false to cancel. |
33 | 39 | @pragma('vm:prefer-inline') |
34 | | -Pointer<obx_data_visitor> dataVisitor( |
35 | | - bool Function(Pointer<Uint8> data, int size) callback) { |
36 | | - _callback = callback; |
37 | | - return _nativeVisitor; |
| 40 | +void visit(Pointer<OBX_query> queryPtr, VisitCallback callback) { |
| 41 | + // Keep callback in case another query is created and visits results |
| 42 | + // within the callback. |
| 43 | + _callbackStack.add(callback); |
| 44 | + final code = C.query_visit(queryPtr, _callbackWrapperPtr, nullptr); |
| 45 | + _callbackStack.removeLast(); |
| 46 | + // Clean callback from stack before potentially throwing. |
| 47 | + checkObx(code); |
38 | 48 | } |
39 | 49 |
|
40 | | -@pragma('vm:prefer-inline') |
41 | | -Pointer<obx_data_visitor> objectCollector<T>(List<T> list, Store store, |
42 | | - EntityDefinition<T> entity, ObjectCollectorError outError) => |
43 | | - dataVisitor((Pointer<Uint8> data, int size) { |
44 | | - try { |
45 | | - list.add(entity.objectFromData(store, data, size)); |
46 | | - return true; |
47 | | - } catch (e) { |
48 | | - outError.error = e; |
49 | | - return false; |
50 | | - } |
51 | | - }); |
52 | | - |
53 | | -class ObjectCollectorError { |
| 50 | +/// Can be used with [visit] to get an error out of the callback. |
| 51 | +class ObjectVisitorError { |
| 52 | + /// Set this e.g. to an exception that occurred inside the callback. |
54 | 53 | Object? error; |
55 | 54 |
|
| 55 | + /// Call once visiting is finished. If an exception is set to [error] will |
| 56 | + /// re-throw it. |
56 | 57 | void throwIfError() { |
57 | 58 | if (error != null) throw error!; |
58 | 59 | } |
|
0 commit comments