Hello all, I was wondering what an approach might be for polling a buffer for a key-down event? Would I use std.io?
I have a pause function…
Current tragic state:
read first byte of user input from \n terminated string (sigh)
q → exit program
otherwise → continue execution
Preferred awesome state:
poll key-down buffer for key-down event
ESC → exit program
other key down → continue execution
no input received → perform some other action (in my use case - animate ansi text fx), then return to polling
what might be an approach? I suppose a key-down buffer is optional at this point, could make the end-user hold key-down until key-down polling finds it…but would be nice if they don’t have to.
pub fn pause() void {
//todo - poll / read a keystroke w/out echo, \n etc
emit(color_reset);
emit("Press return to continue...");
var b: u8 = undefined;
b = stdin.readByte() catch undefined;
if (b == 'q') {
//exit cleanly
complete();
std.os.exit(0);
}
}
What is the catch undefined for? It seems to me like it should be catch unreachable if it is impossible to happen, otherwise it should be catch 0 or something that has guaranteed behavior in your control flow. I know undefined is typically 0’s or 0xA’s, but it could be anything, including ‘q’. Unless you literally mean, “I don’t care at all what the value is or what behavior we choose. YOLO”
I’m trying to do something similar as well the best thing I found was they way the project zig-spoon by Leon Plickat does it in the demos for his library.
Something along the line of:
// Open tty
var tty = try std.fs.cwd().openFile("/dev/tty", .{ .mode = .read_write });
// No idea
var fds: [1]std.os.pollfd = undefined;
// No idea 2
fds[0] = .{
.fd = tty.handle,
.events = std.os.POLL.IN,
.revents = undefined,
};
// Buffer to holf the user input
var buffer: [16]u8 = undefined;
while (run) {
_ = try std.os.poll(&fds, -1);
_ = try std.os.read(tty.handle, &buffer);
input.parse(&buffer);
...
I think you should be able to do this with stdio instead of tty.
But I am a beginner and don’t fully understand that code honestly but maybe it helps you go in the right direction.
my philosophy is processing should continue as far as it can, absorbing as many errors as possible, until the process has no choice but to abort because the faults are too severe…I usually prefer for the program to crash out, as that provides the specific detail on the issue vs hiding it in some lazily written exception processing, but it does require a bit of elite level debugging…so it’s an art.
catch undefined permits this behavior…if, for some incredible reason, readByte fails, the process will not abort but continue…until that undefined value is a real problem, at which point THEN it will abort.
This can make some issues hard to troubleshoot, but from an end-user point of view, nothing is worse than having a program abort because something you don’t care about didn’t work. So it is my way of absorbing some development complexity in exchange for end-user satisfaction.
In zig undefined means “It is invalid to access this data”. If you access data that is undefined, you will get undefined behavior. From the language documentation:
undefined means the value could be anything, even something that is nonsense according to the type. Translated into English, undefined means “Not a meaningful value. Using this value would be a bug. The value will be unused, or overwritten before being used.”
So undefined isn’t doing what you are hoping for, or doing it far worse than just using an optional would be. With an optional you are then able to recognize that the read_byte failed and skip or do whatever handling you need to. With it set to undefined you will get, at best, a panic/crash, and at worst invalid data being processed.
To answer your question, There will be two parts to what you are trying to do[1]
First: since you are reading from a tty, you will need to put the tty in ‘non-canonical’ mode. By default TTY’s will only write out data on a new line. You can change that to stream data on new bytes, by putting the tty into non-canonical mode. man termios has more information on what that means.
Second: There is a std.io.poll function, which will give you a poller on a file.
Be careful with this one: macOS does not allow tty polling, you have to use select. Why? No good reason, Apple just broke it one day and never bothered to fix it.
One of many system specific wrinkles which lead people to like cross-platform libraries for this kind of thing.
Most undefined behavior … can be detected at runtime. In these cases, Zig has safety checks. When a safety check fails, Zig crashes with a stack trace… Documentation - The Zig Programming Language
Agree
yes, it’s definitely a low-code dilligaf mode - however, it is deliberate, I am transferring undefined handling from application logic to the compiler / language. It is a YOLO approach, which I would argue is … okish.
As a philosophy, I don’t see it such an abstract difference between ‘undefined is bad’ / making sure we have a case for each/most undefined possibilities and ‘dilligaf undefined’ / letting the runtime detect->crash (or not). They are both valid, and have their upsides and downsides.
Use-cases drive how much effort we need to spend on undefined - in my case, the answer is no effort at all
so…on this front, I would argue that zig is not doing the right thing; - the function should work as intended. That is the value of cross-platform language! The language and it’s libraries do the right thing, even when the OS is sloppy (as is often the case).
Driving “os-special” into the application layer defeats the point of a common platform…
There are many abstractions needed to write truely cross-platform applications that are out of scope for the standard library. Standardizing the behaviour of poll is just one of many. File systems have many differences for example. Terminals have too many differences to count. Not to mention graphics APIs, audio APIs, toolkits, etc…
The standard library provides access to system APIs in a way that allows developers to write cross-platform code, but it does not provide one API that works exactly the same everywhere. That would be much too high-level for the standard library of a systems programming langauage.
Higher level libraries (like libvaxis for example) are very much able to provide such cross-platform APIs though and they do.
Zig std does provide some cross-platform abstractions, this is mentioned in the documentation of std.posix:
This is more cross platform than using OS-specific APIs, however, it is lower-level and less portable than other namespaces such as std.fs and std.process.
It’s likely that that std.posix will be refactored at some point, you can find the issues tracking that on the board with the obvious search.
On this specific topic I agree with @neurocyte that Zig doesn’t need a ‘blessed’ cross-platform solution for polling tty. Maybe that will change: async might come back, that might come with an ‘official’ event loop, and then it’s justifiable for the core project to take on the maintenance burden.
At the bottom of the link from my last post you’ll find #16382, which is tracking adding select(2) to std. I think it should be available on all POSIX systems, personally, even though there are behavioral differences in terms of limits between them, but what else is new.
My hope is that now that the build system is becoming fully integrated, and has a functional dependency manager built in, we’ll see more small and focused Zig libraries published to cover this kind of thing. A libpolltty would be a nice thing to have, there’s a place for maximalist projects like Vaxis as well but small libraries which do one thing well are quite valuable.
The standard library provides access to system APIs in a way that allows developers to write cross-platform code, but it does not provide one API that works exactly the same everywhere.
Why not? Why wouldn’t the value of a cross-platform language with a standard library be just that?
why wouldn’t a defining requirement of a cross platform language with a standard library be - the application layer functions without change for multiple platform targets?
My hope is that now that the build system is becoming fully integrated, and has a functional dependency manager built in, we’ll see more small and focused Zig libraries published to cover this kind of thing.
aww yeah…that is a great idea! hopefully some patterns could be co-opted into a std lib.
there just aren’t that many ways to do certain things, and far safer to have a std approach to those core things (dates, serialization, terminal basics, etc)…especially when they are pretty simple.
Perhaps one day AI can generate and update cross platform zig lib code? That way these niche but common things don’t have to be the burden of a focused team.
A gap in industry is a systems language that does exactly as we describe - write code once and it runs anywhere.
One factor in industry expense boils down to knowledge - when tribal knowledge is required to solve a problem (ie OS specific system handling in the app layer), the more expensive it is to maintain.
undefined isn’t just about letting the program continue. It’s about not caring at all what the value is. That means the compiler is going to let whatever value happened to be sitting there sit there. If you do catch 0 it would still let execution continue but it would actually do something predictable.
The purpose of the POSIX standard is that programs like Zig should be able to rely on the functions being implemented correctly. The standard is also adhered to. And since MAC OS 10.5 (Leopard – May 18, 2007), all versions are officially certified as compliant with the Unix 03/POSIX standard.
In this respect, it is definitely a mistake on the part of MAC OS if certain functions are not implemented correctly. It doesn’t make sense for Zig to build a workaround. Then we won’t need standards anymore.
well…yes and no! Mistakes are a POV; unique is the defect solely driven by Engineering vs Business.
Theoritcal POV - yes
From a theoretical POV, replete with a tweed jacket, leather elbow patches, a pipe, roaring fire, whiskey, and fellow philosophers engaged in debate, where a standard is a standard, yes, I agree.
System POV - No
From a systems POV, a practical and operational POV, where capabilities need to operate, the role of a cross-platform capability is to provide unified function despite platform quirks and idiosyncrasies.
Quirk-arounds are negative impacts
It is much worse to require ‘special sauce’ to be continually reinvented in the system/app layer, where one individual’s imagination becomes tribal debt for the next and may not apply to the system just next door.
Need is not demand
We should not demand cross-platform functions as a priority – “Hey, this stuff needs to happen right now!” The Zig team def has things to do, and cross-platform operations are not easy (to get right for vX, to keep right on vX.1+).