Skip to content

Commit

Permalink
vaxis: add aio event loop and example
Browse files Browse the repository at this point in the history
disabled by default, aio/coro might not be that mature yet.
however, appreciated if people give it a go and report issues.
  • Loading branch information
Cloudef committed Jun 25, 2024
1 parent b215134 commit 1499ad7
Show file tree
Hide file tree
Showing 5 changed files with 453 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#17a13f7cd96229161e2575a255c7636957daeea4",
.hash = "12203fc8f7ea940e801fa74671ee19fa48f8eb70698bd88a143a443a0cd3fa174bd5",
.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);

try 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
try 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(),
});

try 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]);
}

try 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 (try loop.popEvent()) |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 1499ad7

Please sign in to comment.