-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
vaxis: add aio event loop and example
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
Showing
5 changed files
with
453 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
Oops, something went wrong.