Skip to content

Commit

Permalink
vaxis: add aio event loop and example
Browse files Browse the repository at this point in the history
aio support is disabled by default. The reason is that it's still not
very well ironed out and I feel like there will be quite bit of breakage
coming up still.

It's here for the curious.
  • Loading branch information
Cloudef committed Jun 24, 2024
1 parent b215134 commit 6f46bf3
Show file tree
Hide file tree
Showing 5 changed files with 438 additions and 0 deletions.
11 changes: 11 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ pub fn build(b: *std.Build) void {
const include_libxev = b.option(bool, "libxev", "Enable support for libxev library (default: true)") orelse true;
const include_images = b.option(bool, "images", "Enable support for images (default: true)") orelse true;
const include_text_input = b.option(bool, "text_input", "Enable support for the TextInput widget (default: true)") orelse true;
const include_aio = b.option(bool, "aio", "Enable support for zig-aio library (default: false)") orelse false;

const options = b.addOptions();
options.addOption(bool, "libxev", include_libxev);
options.addOption(bool, "images", include_images);
options.addOption(bool, "text_input", include_text_input);
options.addOption(bool, "aio", include_aio);

const options_mod = options.createModule();

Expand All @@ -33,6 +35,10 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
.target = target,
}) else null;
const aio_dep = if (include_aio) b.lazyDependency("aio", .{
.optimize = optimize,
.target = target,
}) else null;

// Module
const vaxis_mod = b.addModule("vaxis", .{
Expand All @@ -46,6 +52,8 @@ pub fn build(b: *std.Build) void {
if (zigimg_dep) |dep| vaxis_mod.addImport("zigimg", dep.module("zigimg"));
if (gap_buffer_dep) |dep| vaxis_mod.addImport("gap_buffer", dep.module("gap_buffer"));
if (xev_dep) |dep| vaxis_mod.addImport("xev", dep.module("xev"));
if (aio_dep) |dep| vaxis_mod.addImport("aio", dep.module("aio"));
if (aio_dep) |dep| vaxis_mod.addImport("coro", dep.module("coro"));
vaxis_mod.addImport("build_options", options_mod);

// Examples
Expand All @@ -59,6 +67,7 @@ pub fn build(b: *std.Build) void {
vaxis,
vt,
xev,
aio,
};
const example_option = b.option(Example, "example", "Example to run (default: text_input)") orelse .text_input;
const example_step = b.step("example", "Run example");
Expand All @@ -73,6 +82,8 @@ pub fn build(b: *std.Build) void {
});
example.root_module.addImport("vaxis", vaxis_mod);
if (xev_dep) |dep| example.root_module.addImport("xev", dep.module("xev"));
if (aio_dep) |dep| example.root_module.addImport("aio", dep.module("aio"));
if (aio_dep) |dep| example.root_module.addImport("coro", dep.module("coro"));

const example_run = b.addRunArtifact(example);
example_step.dependOn(&example_run.step);
Expand Down
5 changes: 5 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
.hash = "12207b7a5b538ffb7fb18f954ae17d2f8490b6e3778a9e30564ad82c58ee8da52361",
.lazy = true,
},
.aio = .{
.url = "git+https://github.com/Cloudef/zig-aio#844dd30dc313a7575968cbe75d634bbe68569c97",
.hash = "1220711d82d08b98e747962b3584f4be759d2c2b3a503146e522e0a0029664e96801",
.lazy = true,
},
},
.paths = .{
"LICENSE",
Expand Down
168 changes: 168 additions & 0 deletions examples/aio.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
const builtin = @import("builtin");
const std = @import("std");
const vaxis = @import("vaxis");
const aio = @import("aio");
const coro = @import("coro");

pub const panic = vaxis.panic_handler;

const Event = union(enum) {
key_press: vaxis.Key,
winsize: vaxis.Winsize,
};

const Loop = vaxis.aio.Loop(Event);

const Video = enum { no_state, ready, end };
const Audio = enum { no_state, ready, end };

fn downloadTask(allocator: std.mem.Allocator, url: []const u8) ![]const u8 {
var client: std.http.Client = .{ .allocator = allocator };
defer client.deinit();
var body = std.ArrayList(u8).init(allocator);
_ = try client.fetch(.{
.location = .{ .url = url },
.response_storage = .{ .dynamic = &body },
.max_append_size = 1.6e+7,
});
return try body.toOwnedSlice();
}

fn audioTask(allocator: std.mem.Allocator) !void {
// var child = std.process.Child.init(&.{ "aplay", "-Dplug:default", "-q", "-f", "S16_LE", "-r", "8000" }, allocator);
var child = std.process.Child.init(&.{ "mpv", "--audio-samplerate=16000", "--audio-channels=mono", "--audio-format=s16", "-" }, allocator);
child.stdin_behavior = .Pipe;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Ignore;
child.spawn() catch return; // no sound
defer _ = child.kill() catch {};

const sound = blk: {
var tpool: coro.ThreadPool = .{};
try tpool.start(allocator, 1);
defer tpool.deinit();
break :blk try tpool.yieldForCompletition(downloadTask, .{ allocator, "https://keroserene.net/lol/roll.s16" });
};
defer allocator.free(sound);

coro.yield(Audio.ready);

var audio_off: usize = 0;
while (audio_off < sound.len) {
var written: usize = 0;
try coro.io.single(aio.Write{ .file = child.stdin.?, .buffer = sound[audio_off..], .out_written = &written });
audio_off += written;
}

// really only required to keep the child process alive
coro.yield(Audio.end);
}

fn videoTask(writer: std.io.AnyWriter) !void {
var socket: std.posix.socket_t = undefined;
try coro.io.single(aio.Socket{
.domain = std.posix.AF.INET,
.flags = std.posix.SOCK.STREAM | std.posix.SOCK.CLOEXEC,
.protocol = std.posix.IPPROTO.TCP,
.out_socket = &socket,
});
defer std.posix.close(socket);

const address = std.net.Address.initIp4(.{ 44, 224, 41, 160 }, 1987);
try coro.io.single(aio.Connect{
.socket = socket,
.addr = &address.any,
.addrlen = address.getOsSockLen(),
});

coro.yield(Video.ready);

var buf: [1024]u8 = undefined;
while (true) {
var read: usize = 0;
try coro.io.single(aio.Recv{ .socket = socket, .buffer = &buf, .out_read = &read });
if (read == 0) break;
_ = try writer.write(buf[0..read]);
}

coro.yield(Video.end);
}

fn loadingTask(vx: *vaxis.Vaxis, writer: std.io.AnyWriter) !void {
var color_idx: u8 = 30;
var dir: enum { up, down } = .up;

while (true) {
try coro.io.single(aio.Timeout{ .ns = 8 * std.time.ns_per_ms });

const style: vaxis.Style = .{ .fg = .{ .rgb = [_]u8{ color_idx, color_idx, color_idx } } };
const segment: vaxis.Segment = .{ .text = vaxis.logo, .style = style };

const win = vx.window();
win.clear();

var loc = vaxis.widgets.alignment.center(win, 28, 4);
_ = try loc.printSegment(segment, .{ .wrap = .grapheme });

switch (dir) {
.up => {
color_idx += 1;
if (color_idx == 255) dir = .down;
},
.down => {
color_idx -= 1;
if (color_idx == 30) dir = .up;
},
}

try vx.render(writer);
}
}

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();

var scheduler = try coro.Scheduler.init(allocator, .{});
defer scheduler.deinit();

var tty = try vaxis.Tty.init();
defer tty.deinit();

var vx = try vaxis.init(allocator, .{});
defer vx.deinit(allocator, tty.anyWriter());

var loop = try Loop.init();
try loop.spawn(&scheduler, &vx, &tty, null, .{});
defer loop.deinit(&vx, &tty);

try vx.enterAltScreen(tty.anyWriter());
try vx.queryTerminalSend(tty.anyWriter());

var buffered_tty_writer = tty.bufferedWriter();
const loading = try scheduler.spawn(loadingTask, .{ &vx, buffered_tty_writer.writer().any() }, .{});
const audio = try scheduler.spawn(audioTask, .{allocator}, .{});
const video = try scheduler.spawn(videoTask, .{buffered_tty_writer.writer().any()}, .{});

main: while (try scheduler.tick(.blocking) > 0) {
while (loop.tryEvent()) |event| switch (event) {
.key_press => |key| {
if (key.matches('c', .{ .ctrl = true })) {
break :main;
}
},
.winsize => |ws| try vx.resize(allocator, buffered_tty_writer.writer().any(), ws),
};

if (audio.state(Video) == .ready and video.state(Audio) == .ready) {
loading.complete(.cancel) catch {};
audio.wakeup();
video.wakeup();
} else if (audio.state(Audio) == .end and video.state(Video) == .end) {
break :main;
}

try buffered_tty_writer.flush();
}
}
Loading

0 comments on commit 6f46bf3

Please sign in to comment.