queue: add initial queue and app structure
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
parent
48362a307c
commit
891bab73dd
4 changed files with 210 additions and 14 deletions
20
src/main.zig
20
src/main.zig
|
@ -15,13 +15,12 @@ pub fn main() !void {
|
||||||
var tty = try Tty.init();
|
var tty = try Tty.init();
|
||||||
defer tty.deinit();
|
defer tty.deinit();
|
||||||
|
|
||||||
const pipe = try std.os.pipe();
|
|
||||||
// run our tty read loop in it's own thread
|
// run our tty read loop in it's own thread
|
||||||
const read_thread = try std.Thread.spawn(.{}, Tty.run, .{ &tty, pipe[0] });
|
const read_thread = try std.Thread.spawn(.{}, Tty.run, .{ &tty, Event, eventCallback });
|
||||||
try read_thread.setName("tty");
|
try read_thread.setName("tty");
|
||||||
|
|
||||||
std.time.sleep(100_000_000_0);
|
std.time.sleep(100_000_000_00);
|
||||||
_ = try std.os.write(pipe[1], "q");
|
tty.stop();
|
||||||
read_thread.join();
|
read_thread.join();
|
||||||
|
|
||||||
try stdout.print("Run `zig build test` to run the tests.\n", .{});
|
try stdout.print("Run `zig build test` to run the tests.\n", .{});
|
||||||
|
@ -29,9 +28,14 @@ pub fn main() !void {
|
||||||
try bw.flush(); // don't forget to flush!
|
try bw.flush(); // don't forget to flush!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Event = union(enum) {
|
||||||
|
key: u8,
|
||||||
|
mouse: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn eventCallback(_: Event) void {}
|
||||||
|
|
||||||
test "simple test" {
|
test "simple test" {
|
||||||
var list = std.ArrayList(i32).init(std.testing.allocator);
|
_ = @import("odditui.zig");
|
||||||
defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
|
_ = @import("queue.zig");
|
||||||
try list.append(42);
|
|
||||||
try std.testing.expectEqual(@as(i32, 42), list.pop());
|
|
||||||
}
|
}
|
||||||
|
|
47
src/odditui.zig
Normal file
47
src/odditui.zig
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const queue = @import("queue.zig");
|
||||||
|
|
||||||
|
/// App is the entrypoint for an odditui application. The provided type T should
|
||||||
|
/// be a tagged union which contains all of the events the application will
|
||||||
|
/// handle. Odditui will look for the following fields on the union and, if
|
||||||
|
/// found, emit them via the "nextEvent" method
|
||||||
|
pub fn App(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
/// the event queue for this App
|
||||||
|
//
|
||||||
|
// TODO: is 512 ok?
|
||||||
|
queue: queue.Queue(T, 512) = .{},
|
||||||
|
|
||||||
|
/// Runtime options
|
||||||
|
const Options = struct {};
|
||||||
|
|
||||||
|
/// Initialize an App with runtime options
|
||||||
|
pub fn init(_: Options) Self {
|
||||||
|
return Self{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns the next available event, blocking until one is available
|
||||||
|
pub fn nextEvent(self: *Self) T {
|
||||||
|
return self.queue.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// posts an event into the event queue. Will block if there is not
|
||||||
|
/// capacity for the event
|
||||||
|
pub fn postEvent(self: *Self, event: T) void {
|
||||||
|
self.queue.push(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "App: event queueing" {
|
||||||
|
const Event = union(enum) {
|
||||||
|
key,
|
||||||
|
};
|
||||||
|
var app: App(Event) = App(Event).init(.{});
|
||||||
|
app.postEvent(.key);
|
||||||
|
const event = app.nextEvent();
|
||||||
|
try std.testing.expect(event == .key);
|
||||||
|
}
|
93
src/queue.zig
Normal file
93
src/queue.zig
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const atomic = std.atomic;
|
||||||
|
const Futex = std.Thread.Futex;
|
||||||
|
|
||||||
|
pub fn Queue(
|
||||||
|
comptime T: type,
|
||||||
|
comptime size: usize,
|
||||||
|
) type {
|
||||||
|
return struct {
|
||||||
|
buf: [size]T = undefined,
|
||||||
|
|
||||||
|
read_index: usize = 0,
|
||||||
|
write_index: usize = 0,
|
||||||
|
|
||||||
|
mutex: std.Thread.Mutex = .{},
|
||||||
|
// blocks when the buffer is full or empty
|
||||||
|
futex: atomic.Value(u32) = atomic.Value(u32).init(0),
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn pop(self: *Self) T {
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
if (self.isEmpty()) {
|
||||||
|
// If we don't have any items, we unlock and wait
|
||||||
|
self.mutex.unlock();
|
||||||
|
Futex.wait(&self.futex, 0);
|
||||||
|
// regain our lock
|
||||||
|
self.mutex.lock();
|
||||||
|
}
|
||||||
|
if (self.isFull()) {
|
||||||
|
// If we are full, wake up the push
|
||||||
|
defer Futex.wake(&self.futex, 1);
|
||||||
|
}
|
||||||
|
const i = self.read_index;
|
||||||
|
self.read_index += 1;
|
||||||
|
self.read_index = self.read_index % self.buf.len;
|
||||||
|
return self.buf[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(self: *Self, item: T) void {
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
if (self.isFull()) {
|
||||||
|
self.mutex.unlock();
|
||||||
|
Futex.wait(&self.futex, 0);
|
||||||
|
self.mutex.lock();
|
||||||
|
}
|
||||||
|
if (self.isEmpty()) {
|
||||||
|
defer Futex.wake(&self.futex, 1);
|
||||||
|
}
|
||||||
|
const i = self.write_index;
|
||||||
|
self.write_index += 1;
|
||||||
|
self.write_index = self.write_index % self.buf.len;
|
||||||
|
self.buf[i] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the ring buffer is empty and `false` otherwise.
|
||||||
|
pub fn isEmpty(self: Self) bool {
|
||||||
|
return self.write_index == self.read_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the ring buffer is full and `false` otherwise.
|
||||||
|
pub fn isFull(self: Self) bool {
|
||||||
|
return self.mask2(self.write_index + self.buf.len) == self.read_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length
|
||||||
|
pub fn len(self: Self) usize {
|
||||||
|
const wrap_offset = 2 * self.buf.len * @intFromBool(self.write_index < self.read_index);
|
||||||
|
const adjusted_write_index = self.write_index + wrap_offset;
|
||||||
|
return adjusted_write_index - self.read_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `index` modulo the length of the backing slice.
|
||||||
|
pub fn mask(self: Self, index: usize) usize {
|
||||||
|
return index % self.buf.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `index` modulo twice the length of the backing slice.
|
||||||
|
pub fn mask2(self: Self, index: usize) usize {
|
||||||
|
return index % (2 * self.buf.len);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Queue: simple push / pop" {
|
||||||
|
var queue: Queue(u8, 16) = .{};
|
||||||
|
queue.push(1);
|
||||||
|
const pop = queue.pop();
|
||||||
|
try std.testing.expectEqual(1, pop);
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const os = std.os;
|
const os = std.os;
|
||||||
const xev = @import("xev");
|
|
||||||
|
|
||||||
const log = std.log.scoped(.tty);
|
const log = std.log.scoped(.tty);
|
||||||
|
|
||||||
|
@ -12,6 +11,9 @@ termios: os.termios,
|
||||||
/// The file descriptor we are using for I/O
|
/// The file descriptor we are using for I/O
|
||||||
fd: os.fd_t,
|
fd: os.fd_t,
|
||||||
|
|
||||||
|
/// the write end of a pipe to signal the tty should exit it's run loop
|
||||||
|
quit_fd: ?os.fd_t = null,
|
||||||
|
|
||||||
/// initializes a Tty instance by opening /dev/tty and "making it raw"
|
/// initializes a Tty instance by opening /dev/tty and "making it raw"
|
||||||
pub fn init() !Tty {
|
pub fn init() !Tty {
|
||||||
// Open our tty
|
// Open our tty
|
||||||
|
@ -34,23 +36,38 @@ pub fn deinit(self: *Tty) void {
|
||||||
os.close(self.fd);
|
os.close(self.fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// stops the run loop
|
||||||
|
pub fn stop(self: *Tty) void {
|
||||||
|
if (self.quit_fd) |fd| {
|
||||||
|
_ = std.os.write(fd, "q") catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// read input from the tty
|
/// read input from the tty
|
||||||
pub fn run(self: *Tty, quit: os.fd_t) !void {
|
pub fn run(self: *Tty, comptime T: type, comptime _: fn (ev: T) void) !void {
|
||||||
defer os.close(quit);
|
// create a pipe so we can signal to exit the run loop
|
||||||
|
const pipe = try os.pipe();
|
||||||
|
defer os.close(pipe[0]);
|
||||||
|
defer os.close(pipe[1]);
|
||||||
|
|
||||||
|
self.quit_fd = pipe[1];
|
||||||
|
|
||||||
|
var parser: Parser = .{};
|
||||||
|
|
||||||
var buf: [1024]u8 = undefined;
|
var buf: [1024]u8 = undefined;
|
||||||
var pollfds: [2]std.os.pollfd = .{
|
var pollfds: [2]std.os.pollfd = .{
|
||||||
.{ .fd = self.fd, .events = std.os.POLL.IN, .revents = undefined },
|
.{ .fd = self.fd, .events = std.os.POLL.IN, .revents = undefined },
|
||||||
.{ .fd = quit, .events = std.os.POLL.IN, .revents = undefined },
|
.{ .fd = pipe[0], .events = std.os.POLL.IN, .revents = undefined },
|
||||||
};
|
};
|
||||||
while (true) {
|
while (true) {
|
||||||
_ = try std.os.poll(&pollfds, -1);
|
_ = try std.os.poll(&pollfds, -1);
|
||||||
if (pollfds[1].revents & std.os.POLL.IN != 0) {
|
if (pollfds[1].revents & std.os.POLL.IN != 0) {
|
||||||
log.info("read thread got quit signal", .{});
|
log.info("quitting read thread", .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const n = try os.read(self.fd, &buf);
|
const n = try os.read(self.fd, &buf);
|
||||||
log.err("{s}", .{buf[0..n]});
|
parser.parse(self, buf[0..n]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,3 +110,38 @@ pub fn makeRaw(fd: os.fd_t) !os.termios {
|
||||||
try os.tcsetattr(fd, .FLUSH, raw);
|
try os.tcsetattr(fd, .FLUSH, raw);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// parses vt input. Retains some state so we need an object for it
|
||||||
|
const Parser = struct {
|
||||||
|
const log = std.log.scoped(.parser);
|
||||||
|
|
||||||
|
// the state of the parser
|
||||||
|
const State = enum {
|
||||||
|
ground,
|
||||||
|
escape,
|
||||||
|
csi,
|
||||||
|
osc,
|
||||||
|
dcs,
|
||||||
|
sos,
|
||||||
|
pm,
|
||||||
|
apc,
|
||||||
|
ss2,
|
||||||
|
ss3,
|
||||||
|
};
|
||||||
|
|
||||||
|
state: State = .ground,
|
||||||
|
|
||||||
|
fn parse(self: *Parser, tty: *Tty, input: []u8) void {
|
||||||
|
_ = tty; // autofix
|
||||||
|
var i: usize = 0;
|
||||||
|
const start: usize = 0;
|
||||||
|
_ = start; // autofix
|
||||||
|
while (i < input.len) : (i += 1) {
|
||||||
|
const b = input[i];
|
||||||
|
switch (self.state) {
|
||||||
|
.ground => Parser.log.err("0x{x}\r", .{b}),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue