libxev-sys 0.0.1-rc.2

Low-level FFI bindings to libxev (built from vendored sources via Zig).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
const dynamicpkg = @This();
const std = @import("std");
const builtin = @import("builtin");
const posix = std.posix;
const main = @import("main.zig");
const AllBackend = main.Backend;
const looppkg = @import("loop.zig");

/// Creates the Xev API based on a set of backend types that allows
/// for dynamic choice between the various candidate backends (assuming
/// they're available). For example, on Linux, this allows a consumer to
/// choose between io_uring and epoll, or for automatic fallback to epoll
/// to occur.
///
/// If only one backend is given, the returned type is equivalent to
/// the static API with no runtime conditional logic, so there is no downside
/// to always using the dynamic choice variant if you wish to support
/// dynamic choice.
///
/// The goal of this API is to match the static Xev() (in main.zig)
/// API as closely as possible. It can't be exact since this is an abstraction
/// and therefore must be limited to the least common denominator of all
/// backends.
///
/// Since the static Xev API exposes types that can be initialized on their
/// own (i.e. xev.Async.init()), this API does the same. Since we need to
/// know the backend to use, this uses a global variable. The backend to use
/// defaults to the first candidate backend and DOES NOT test that it is
/// available. The API user should call `detect()` to set the backend to
/// the first available backend.
///
/// Additionally, a caller can manually set the backend to use, but this
/// must be done before initializing any other types.
pub fn Xev(comptime bes: []const AllBackend) type {
    if (bes.len == 0) @compileError("no backends provided");

    // Ensure that if we only have one candidate that we have no
    // overhead to use the dynamic API.
    if (bes.len == 1) return bes[0].Api();

    return struct {
        const Dynamic = @This();

        /// This is a flag that can be used to detect that you're using
        /// a dynamic API and not the static API via `hasDecl`.
        pub const dynamic = true;

        pub const candidates = bes;

        /// Backend becomes the subset of the full xev.Backend that
        /// is available to this dynamic API.
        pub const Backend = EnumSubset(AllBackend, bes);

        /// Forward some global types so that users can replace
        /// @import("xev") with the dynamic package and everything
        /// works.
        pub const ThreadPool = main.ThreadPool;

        /// The shared structures
        pub const Options = looppkg.Options;
        pub const RunMode = looppkg.RunMode;
        pub const CallbackAction = looppkg.CallbackAction;
        pub const CompletionState = looppkg.CompletionState;
        pub const Completion = DynamicCompletion(Dynamic);
        pub const PollEvent = DynamicPollEvent(Dynamic);
        pub const ReadBuffer = DynamicReadBuffer(Dynamic);
        pub const WriteBuffer = DynamicWriteBuffer(Dynamic);
        pub const WriteQueue = DynamicWriteQueue(Dynamic);
        pub const WriteRequest = Dynamic.Union(&.{"WriteRequest"});

        /// Error types
        pub const AcceptError = Dynamic.ErrorSet(&.{"AcceptError"});
        pub const CancelError = Dynamic.ErrorSet(&.{"CancelError"});
        pub const CloseError = Dynamic.ErrorSet(&.{"CloseError"});
        pub const ConnectError = Dynamic.ErrorSet(&.{"ConnectError"});
        pub const PollError = Dynamic.ErrorSet(&.{"PollError"});
        pub const ShutdownError = Dynamic.ErrorSet(&.{"ShutdownError"});
        pub const WriteError = Dynamic.ErrorSet(&.{"WriteError"});
        pub const ReadError = Dynamic.ErrorSet(&.{"ReadError"});

        /// Core types
        pub const Async = @import("watcher/async.zig").Async(Dynamic);
        pub const File = @import("watcher/file.zig").File(Dynamic);
        pub const Process = @import("watcher/process.zig").Process(Dynamic);
        pub const Stream = @import("watcher/stream.zig").GenericStream(Dynamic);
        pub const Timer = @import("watcher/timer.zig").Timer(Dynamic);
        pub const TCP = @import("watcher/tcp.zig").TCP(Dynamic);
        pub const UDP = @import("watcher/udp.zig").UDP(Dynamic);

        /// The backend that is in use.
        pub var backend: Backend = subset(bes[bes.len - 1]);

        /// Detect the preferred backend based on the system and set it
        /// for use. "Preferred" is the first available (API exists) candidate
        /// backend in the list.
        pub fn detect() error{NoAvailableBackends}!void {
            inline for (bes) |be| {
                if (be.Api().available()) {
                    backend = subset(be);
                    return;
                }
            }

            return error.NoAvailableBackends;
        }

        /// Manually set the backend to use, but if the backend is not
        /// available, this will not change the backend in use.
        pub fn prefer(be: AllBackend) bool {
            inline for (bes) |candidate| {
                if (candidate == be) {
                    const api = candidate.Api();
                    if (!api.available()) return false;
                    backend = subset(candidate);
                    return true;
                }
            }

            return false;
        }

        pub const Loop = struct {
            backend: Loop.Union,

            pub const Union = Dynamic.Union(&.{"Loop"});

            pub fn init(opts: Options) !Loop {
                return .{ .backend = switch (backend) {
                    inline else => |tag| backend: {
                        const api = (comptime superset(tag)).Api();
                        break :backend @unionInit(
                            Loop.Union,
                            @tagName(tag),
                            try api.Loop.init(opts),
                        );
                    },
                } };
            }

            pub fn deinit(self: *Loop) void {
                switch (backend) {
                    inline else => |tag| @field(
                        self.backend,
                        @tagName(tag),
                    ).deinit(),
                }
            }

            pub fn stop(self: *Loop) void {
                switch (backend) {
                    inline else => |tag| @field(
                        self.backend,
                        @tagName(tag),
                    ).stop(),
                }
            }

            pub fn stopped(self: *Loop) bool {
                return switch (backend) {
                    inline else => |tag| @field(
                        self.backend,
                        @tagName(tag),
                    ).stopped(),
                };
            }

            pub fn run(self: *Loop, mode: RunMode) !void {
                switch (backend) {
                    inline else => |tag| try @field(
                        self.backend,
                        @tagName(tag),
                    ).run(mode),
                }
            }
        };

        /// Helpers to convert between the subset/superset of backends.
        pub fn subset(comptime be: AllBackend) Backend {
            return @enumFromInt(@intFromEnum(be));
        }

        pub fn superset(comptime be: Backend) AllBackend {
            return @enumFromInt(@intFromEnum(be));
        }

        pub fn Union(comptime field: []const []const u8) type {
            return dynamicpkg.Union(bes, field, false);
        }

        pub fn TaggedUnion(comptime field: []const []const u8) type {
            return dynamicpkg.Union(bes, field, true);
        }

        pub fn ErrorSet(comptime field: []const []const u8) type {
            return dynamicpkg.ErrorSet(bes, field);
        }

        test {
            @import("std").testing.refAllDecls(@This());
        }

        test "completion is zero-able" {
            const c: Completion = .{};
            _ = c;
        }

        test "detect" {
            const testing = std.testing;
            try detect();
            inline for (bes) |be| {
                if (@intFromEnum(be) == @intFromEnum(backend)) {
                    try testing.expect(be.Api().available());
                    break;
                }
            } else try testing.expect(false);
        }

        test "prefer" {
            const testing = std.testing;
            try testing.expect(prefer(bes[0]));
            inline for (bes) |be| {
                if (@intFromEnum(be) == @intFromEnum(backend)) {
                    try testing.expect(be.Api().available());
                    break;
                }
            } else try testing.expect(false);
        }

        test "loop basics" {
            try detect();
            var l = try Loop.init(.{});
            defer l.deinit();
            try l.run(.until_done);
            l.stop();
            try std.testing.expect(l.stopped());
        }
    };
}

fn DynamicCompletion(comptime dynamic: type) type {
    return struct {
        const Self = @This();

        // Completions, unlike almost any other dynamic type in this
        // file, are tagged unions. This adds a minimal overhead to
        // the completion but is necessary to ensure that we have
        // zero-initialized the correct type for where it matters
        // (timers).
        pub const Union = dynamic.TaggedUnion(&.{"Completion"});

        value: Self.Union = @unionInit(
            Self.Union,
            @tagName(dynamic.candidates[dynamic.candidates.len - 1]),
            .{},
        ),

        pub fn init() Self {
            return .{ .value = switch (dynamic.backend) {
                inline else => |tag| value: {
                    const api = (comptime dynamic.superset(tag)).Api();
                    break :value @unionInit(
                        Self.Union,
                        @tagName(tag),
                        api.Completion.init(),
                    );
                },
            } };
        }

        pub fn state(self: *Self) dynamic.CompletionState {
            return switch (self.value) {
                inline else => |*v| v.state(),
            };
        }

        /// This ensures that the tag is currently set for this
        /// completion. If it is not, it will be set to the given
        /// tag with a zero-initialized value.
        ///
        /// This lets users zero-intialize the completion and then
        /// call any watcher API on it without having to worry about
        /// the correct detected backend tag being set.
        pub fn ensureTag(self: *Self, comptime tag: dynamic.Backend) void {
            if (self.value == tag) return;
            self.value = @unionInit(
                Self.Union,
                @tagName(tag),
                .{},
            );
        }
    };
}

fn DynamicReadBuffer(comptime dynamic: type) type {
    // Our read buffer supports a least common denominator of
    // read targets available by all backends. The direct backend
    // may support more read targets but if you need that you should
    // use the backend directly and not through the dynamic API.
    return union(enum) {
        const Self = @This();

        slice: []u8,
        array: [32]u8,

        /// Convert a backend-specific read buffer to this.
        pub fn fromBackend(
            comptime be: dynamic.Backend,
            buf: dynamic.superset(be).Api().ReadBuffer,
        ) Self {
            return switch (buf) {
                inline else => |data, tag| @unionInit(
                    Self,
                    @tagName(tag),
                    data,
                ),
            };
        }

        /// Convert this read buffer to the backend-specific
        /// buffer.
        pub fn toBackend(
            self: Self,
            comptime be: dynamic.Backend,
        ) dynamic.superset(be).Api().ReadBuffer {
            return switch (self) {
                inline else => |data, tag| @unionInit(
                    dynamic.superset(be).Api().ReadBuffer,
                    @tagName(tag),
                    data,
                ),
            };
        }
    };
}

fn DynamicWriteBuffer(comptime dynamic: type) type {
    // Our write buffer supports a least common denominator of
    // write targets available by all backends. The direct backend
    // may support more write targets but if you need that you should
    // use the backend directly and not through the dynamic API.
    return union(enum) {
        const Self = @This();

        slice: []const u8,
        array: struct { array: [32]u8, len: usize },

        /// Convert a backend-specific write buffer to this.
        pub fn fromBackend(
            comptime be: dynamic.Backend,
            buf: dynamic.superset(be).Api().WriteBuffer,
        ) Self {
            return switch (buf) {
                .slice => |v| .{ .slice = v },
                .array => |v| .{ .array = .{ .array = v.array, .len = v.len } },
            };
        }

        /// Convert this write buffer to the backend-specific
        /// buffer.
        pub fn toBackend(
            self: Self,
            comptime be: dynamic.Backend,
        ) dynamic.superset(be).Api().WriteBuffer {
            return switch (self) {
                .slice => |v| .{ .slice = v },
                .array => |v| .{ .array = .{ .array = v.array, .len = v.len } },
            };
        }
    };
}

fn DynamicWriteQueue(comptime xev: type) type {
    return struct {
        const Self = @This();
        pub const Union = xev.TaggedUnion(&.{"WriteQueue"});

        value: Self.Union = @unionInit(
            Self.Union,
            @tagName(xev.candidates[xev.candidates.len - 1]),
            .{},
        ),

        pub fn ensureTag(self: *Self, comptime tag: xev.Backend) void {
            if (self.value == tag) return;
            self.value = @unionInit(
                Self.Union,
                @tagName(tag),
                .{},
            );
        }
    };
}

fn DynamicPollEvent(comptime xev: type) type {
    return enum {
        const Self = @This();

        read,

        pub fn fromBackend(
            comptime tag: xev.Backend,
            event: xev.superset(tag).Api().PollEvent,
        ) Self {
            return switch (event) {
                inline else => |event_tag| @field(
                    Self,
                    @tagName(event_tag),
                ),
            };
        }

        pub fn toBackend(
            self: Self,
            comptime tag: xev.Backend,
        ) xev.superset(tag).Api().PollEvent {
            return switch (self) {
                inline else => |event_tag| @field(
                    (comptime xev.superset(tag)).Api().PollEvent,
                    @tagName(event_tag),
                ),
            };
        }
    };
}

/// Create an exhaustive enum that is a subset of another enum.
/// Preserves the same backing type and integer values for the
/// subset making it easy to convert between the two.
fn EnumSubset(comptime T: type, comptime values: []const T) type {
    const tag_type = @typeInfo(T).@"enum".tag_type;
    var field_names: [values.len][:0]const u8 = undefined;
    var field_values: [values.len]tag_type = undefined;
    for (values, 0..) |value, i| {
        field_names[i] = @tagName(value);
        field_values[i] = @intFromEnum(value);
    }

    return @Enum(tag_type, .exhaustive, &field_names, &field_values);
}

/// Creates a union type that can hold the implementation of a given
/// backend by common field name. Example, for Async: Union(bes, "Async")
/// produces:
///
///   union {
///     io_uring: IO_Uring.Async,
///     epoll: Epoll.Async,
///     ...
///   }
///
/// The union is untagged to save an extra bit of memory per
/// instance since we have the active backend from the outer struct.
fn Union(
    comptime bes: []const AllBackend,
    comptime field: []const []const u8,
    comptime tagged: bool,
) type {
    // Keep track of the largest field size, see the conditional
    // below using this variable for more info.
    var largest: usize = 0;

    var field_names: [bes.len + 1][:0]const u8 = undefined;
    var field_types: [bes.len + 1]type = undefined;
    var field_attrs: [bes.len + 1]std.builtin.Type.UnionField.Attributes = @splat(.{});
    for (bes, 0..) |be, i| {
        var T: type = be.Api();
        for (field) |f| T = @field(T, f);
        largest = @max(largest, @sizeOf(T));
        field_names[i] = @tagName(be);
        field_types[i] = T;
        field_attrs[i] = .{ .@"align" = @alignOf(T) };
    }

    // If our union only has zero-sized types, we need to add some
    // non-zero sized padding entry. This avoids a Zig 0.13 compiler
    // crash when trying to create a zero-sized union using @unionInit
    // from a switch of a comptime-generated enum. I wasn't able to
    // minimize this. In future Zig versions we can remove this and if
    // our examples can build with Dynamic then we're good.
    var count: usize = bes.len;
    if (largest == 0) {
        field_names[count] = "_zig_bug_padding";
        field_types[count] = u8;
        field_attrs[count] = .{ .@"align" = @alignOf(u8) };
        count += 1;
    }

    return @Union(
        .auto,
        if (tagged) EnumSubset(AllBackend, bes) else null,
        field_names[0..count],
        field_types[0..count],
        field_attrs[0..count],
    );
}

/// Create a new error set from a list of error sets within
/// the given backends at the given field name. For example,
/// to merge all xev.Async.WaitErrors:
///
///   ErrorSet(bes, &.{"Async", "WaitError"});
///
fn ErrorSet(
    comptime bes: []const AllBackend,
    comptime field: []const []const u8,
) type {
    var Set: type = error{};
    for (bes) |be| {
        var NextSet: type = be.Api();
        for (field) |f| NextSet = @field(NextSet, f);
        Set = Set || NextSet;
    }

    return Set;
}