nandi/frqpublic Fork 0
627b785fbf6cef0c29d54b82a1ffb5e870ca9f51
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

host_ffi.dart · 228 lines · 7.9 KBDart Blame HistoryRaw
A web version, from the same core 23846db nandi 14h ago1/// The native half of the seam: `libfrqcore.so`, through `dart:ffi`.
2///
3/// Split out of `frq_core.dart` so that file can be imported where there is
4/// no `dart:ffi` to import — a browser. `host.dart` picks this or `host_js`
5/// and nothing above it knows which.
6///
7/// The two rules of the ABI live here, wrapped so no call site repeats them:
8/// `frq_init` runs once before anything else, and every string the core
9/// returns is ours to free with `frq_free`.
10library;
11
12import 'dart:convert';
13import 'dart:ffi';
14import 'dart:io';
15
16// ---------------------------------------------------------------- the ABI
17
18typedef _InitNative = Void Function();
19typedef _InitDart = void Function();
20
21typedef _FreeNative = Void Function(Pointer<Uint8>);
22typedef _FreeDart = void Function(Pointer<Uint8>);
23
24typedef _VersionNative = Pointer<Uint8> Function();
25typedef _VersionDart = Pointer<Uint8> Function();
26
27typedef _Str1Native = Pointer<Uint8> Function(Pointer<Uint8>);
28typedef _Str1Dart = Pointer<Uint8> Function(Pointer<Uint8>);
29
30typedef _Str2Native = Pointer<Uint8> Function(Pointer<Uint8>, Pointer<Uint8>);
31typedef _Str2Dart = Pointer<Uint8> Function(Pointer<Uint8>, Pointer<Uint8>);
32
33typedef _Str0Native = Pointer<Uint8> Function();
34typedef _Str0Dart = Pointer<Uint8> Function();
35
36typedef _VoidNative = Void Function();
37typedef _VoidDart = void Function();
38
39/// Where to look for the library, in order.
40///
41/// Android resolves a bare soname out of the APK's `lib/<abi>/`. A desktop
42/// build has no such rule, so the bare name is tried first (it works when the
43/// object sits beside the executable or on the loader path) and then the
44/// development path `just nim-lib` writes to. Named explicitly rather than by
45/// exporting `LD_LIBRARY_PATH` from a launcher, because a variable set in a
46/// wrapper script is a thing that works until someone starts the binary
47/// another way.
48DynamicLibrary _open() {
49 if (Platform.isAndroid) return DynamicLibrary.open('libfrqcore.so');
50 // The development paths are relative to whichever directory the process
51 // started in: `dart test` runs from `dart/frq_core`, a built desktop bundle
52 // from the repo root. All of them are tried rather than guessing which
53 // invocation this is, because the failure mode is a StateError at first use
54 // rather than anything a type checker would have caught.
55 for (final p in [
56 'libfrqcore.so',
57 'build/nim/libfrqcore.so',
58 '../build/nim/libfrqcore.so', // `flutter test`, from flutter/
59 '../../build/nim/libfrqcore.so', // `dart test`, from dart/frq_core/
60 ]) {
61 try {
62 return DynamicLibrary.open(p);
63 } on ArgumentError {
64 continue;
65 }
66 }
67 throw StateError(
68 'libfrqcore.so not found — build it with `just nim-lib`, or ship it '
69 'beside the executable');
70}
71
72final DynamicLibrary _lib = () {
73 final lib = _open();
74 lib.lookupFunction<_InitNative, _InitDart>('frq_init')();
75 return lib;
76}();
77
78// Every entry point resolved once, here, rather than on each call.
79//
80// `lookupFunction` is a dlsym plus a freshly built trampoline closure every
81// time it runs. At 10Hz for `poll` and once per keystroke for `dispatch` that
82// is measurable and, more to the point, free to avoid — these are `final`, so
83// they cost one lookup for the life of the process.
84final _free = _lib.lookupFunction<_FreeNative, _FreeDart>('frq_free');
85final _version = _lib.lookupFunction<_VersionNative, _VersionDart>('frq_version');
86final _tagValue = _lib.lookupFunction<_Str2Native, _Str2Dart>('frq_irc_tag_value');
87final _traceFn = _lib.lookupFunction<_Str2Native, _Str2Dart>('frq_trace');
88final _connOpen = _lib.lookupFunction<_ConnOpenNative, _ConnOpenDart>('frq_conn_open');
89final _connSend = _lib.lookupFunction<_Str1Native, _Str1Dart>('frq_conn_send');
90final _connCloseFn = _lib.lookupFunction<_VoidNative, _VoidDart>('frq_conn_close');
91final _connRecvFn = _lib.lookupFunction<_Str0Native, _Str0Dart>('frq_conn_recv');
92final _connEventFn = _lib.lookupFunction<_Str0Native, _Str0Dart>('frq_conn_event');
93final _uiRender = _lib.lookupFunction<_Str0Native, _Str0Dart>('frq_ui_render');
94final _uiPoll = _lib.lookupFunction<_Str0Native, _Str0Dart>('frq_ui_poll');
95final _uiDispatch = _lib.lookupFunction<_Str1Native, _Str1Dart>('frq_ui_dispatch');
96final _uiDemo = _lib.lookupFunction<_VoidNative, _VoidDart>('frq_ui_demo');
97final _uiReset = _lib.lookupFunction<_VoidNative, _VoidDart>('frq_ui_reset');
98final _str1 = <String, _Str1Dart>{};
99
100/// The bytes at [p] as a string, with [p] freed afterwards. Null in, null out.
101String? _takeString(Pointer<Uint8> p) {
102 if (p == nullptr) return null;
103 try {
104 // Walk to the NUL rather than asking for a length the ABI does not carry.
105 var len = 0;
106 while (p[len] != 0) {
107 len++;
108 }
109 return utf8.decode(p.asTypedList(len));
110 } finally {
111 _free(p);
112 }
113}
114
115/// [s] as a NUL-terminated C string that the CALLER must free with [_freeArg].
116///
117/// Allocated with `malloc` from Dart's side, so it is freed from Dart's side —
118/// the mirror of the rule for what comes back. The core never takes ownership
119/// of an argument.
120Pointer<Uint8> _toC(String s) {
121 final bytes = utf8.encode(s);
122 final p = _malloc(bytes.length + 1).cast<Uint8>();
123 for (var i = 0; i < bytes.length; i++) {
124 p[i] = bytes[i];
125 }
126 p[bytes.length] = 0;
127 return p;
128}
129
130// malloc/free out of libc rather than package:ffi's allocator, for the same
131// reason the rest of this file avoids that package: one less dependency to
132// carry to three targets, for two symbols that are always there.
133final DynamicLibrary _libc =
134 Platform.isWindows ? DynamicLibrary.open('msvcrt.dll') : DynamicLibrary.process();
135final _malloc = _libc
136 .lookupFunction<Pointer<Void> Function(IntPtr), Pointer<Void> Function(int)>('malloc');
137final _freeArg =
138 _libc.lookupFunction<Void Function(Pointer<Uint8>), void Function(Pointer<Uint8>)>('free');
139
140/// Call a two-strings-in, one-string-out entry point.
141///
142/// The mirror of [_call1], and it exists for the same reason: the
143/// `_toC`/`try`/`finally`/`_freeArg` dance is four lines of ownership
144/// bookkeeping that no call site should repeat.
145String? _call2(_Str2Dart f, String x, String y) {
146 final a = _toC(x);
147 final b = _toC(y);
148 try {
149 return _takeString(f(a, b));
150 } finally {
151 _freeArg(a);
152 _freeArg(b);
153 }
154}
155
156/// Call a one-string-in, one-string-out entry point.
157String? _call1(String symbol, String arg) {
158 final f = _str1.putIfAbsent(
159 symbol, () => _lib.lookupFunction<_Str1Native, _Str1Dart>(symbol));
160 final a = _toC(arg);
161 try {
162 return _takeString(f(a));
163 } finally {
164 _freeArg(a);
165 }
166}
167
168
169typedef _ConnOpenNative = Void Function(Pointer<Uint8>, Int32, Int32);
170typedef _ConnOpenDart = void Function(Pointer<Uint8>, int, int);
171
172// ------------------------------------------------------------------ the host
173//
174// What `frq_core.dart` calls, and what `host_js.dart` answers with the same
175// names. Everything above is how; this is what.
176
177String? uiRender() => _takeString(_uiRender());
178String? uiPoll() => _takeString(_uiPoll());
179
180String? uiDispatch(String event) {
181 final a = _toC(event);
182 try {
183 return _takeString(_uiDispatch(a));
184 } finally {
185 _freeArg(a);
186 }
187}
188
189void uiDemo() => _uiDemo();
190void uiReset() => _uiReset();
191
192/// Static storage on the Nim side: the one return value that is NOT freed.
193String hostVersion() {
194 final p = _version();
195 var len = 0;
196 while (p[len] != 0) {
197 len++;
198 }
199 return utf8.decode(p.asTypedList(len));
200}
201
202String? str1(String symbol, String arg) => _call1(symbol, arg);
203String? tagValueOf(String tags, String key) => _call2(_tagValue, tags, key);
204void traceTo(String topic, String msg) {
205 _call2(_traceFn, topic, msg);
206}
207
208void connOpenAt(String host, int port, bool tls) {
209 final h = _toC(host);
210 try {
211 _connOpen(h, port, tls ? 1 : 0);
212 } finally {
213 _freeArg(h);
214 }
215}
216
217void connSendLine(String line) {
218 final a = _toC(line);
219 try {
220 _connSend(a);
221 } finally {
222 _freeArg(a);
223 }
224}
225
226void connCloseNow() => _connCloseFn();
227String? connRecvLine() => _takeString(_connRecvFn());
228String? connEventNext() => _takeString(_connEventFn());