#Io.Writer drain is called with empty slice when using a buffer

1 messages · Page 1 of 1 (latest)

gaunt matrix
#

i was trying to make an Io.Writer implementation but when i pass a buffer, drain gets called with an empty slice. i tried to make a minimum example

pub fn writer(buffer: []u8) Writer {
    return .{
        .interface = Io.Writer{
            .buffer = buffer,
            .vtable = &.{ .drain = Writer.drain },
        },
    };
}

pub const Writer = struct {
    interface: Io.Writer,

    fn drain(_: *Io.Writer, data: []const []const u8, _: usize) !usize {
        std.debug.print("data.len = {d}, data[0].len = {d}\n", .{ data.len, data[0].len });
        return data[0].len;
    }
};

with buffer:

    var buffer: [1024]u8 = undefined;
    var writer = writer(&buffer);

    try writer.interface.writeAll("meow?");
    try writer.interface.flush();

i get

data.len = 1, data[0].len = 0
data.len = 1, data[0].len = 0
... repeated

but if i pass empty array as buffer it works fine, data[0].len is > 0. what am i doing wrong here? probably something stupid

carmine bramble
gaunt matrix
#

because docs say returned total bytes must be only written unbuffered bytes

#

but they add buffered bytes as well

carmine bramble
#

hm you're right I'll have to investigate that more

#

consume does return 0 though if the buffer has not been entirely consumed at least

mental gyro
#

For example, Here is the code to an example Writer I created last week. It's only been tested as much as the tests at the bottom.

pub const TestWriter = struct {
    interface: std.Io.Writer,

    pub fn initStreaming(buffer: []u8) @This() {
        return .{
            .interface = .{
                .vtable = &.{ .drain = drain },
                .buffer = buffer,
            },
        };
    }

    /// drain processes the buffer and any additional data. In this case
    /// we are passing data to std err.
    pub fn drain(
        writer: *std.Io.Writer,
        data: []const []const u8,
        splat: usize,
    ) std.Io.Writer.Error!usize {
        const self: *@This() = @alignCast(@fieldParentPtr("interface", writer));

        // Drain the buffer first
        if (self.interface.end > 0) {
            std.log.err("drain: buffer '{s}'", .{self.interface.buffer[0..self.interface.end]});
            self.interface.end = 0;
        }

        // Drain data fields
        var count: usize = 0;
        if (data.len == 0) return count;

        // The right most data field is repeated
        // `splat` number of times.
        const left = data[0 .. data.len - 1];
        const right = data[data.len - 1];

        for (left) |value| {
            if (value.len > 0) {
                std.log.err("drain: data '{s}'", .{value});
                count += value.len;
            }
        }

        for (0..splat) |_| {
            if (right.len > 0) {
                std.log.err("drain: data '{s}'", .{right});
                count += right.len;
            }
        }

        return count;
    }
};

const std = @import("std");
mental gyro
#

test "test" {
    var buffer: [8]u8 = undefined;
    var t = TestWriter.initStreaming(&buffer);
    try t.interface.writeAll("bob ");
    try t.interface.writeAll("is ");
    try t.interface.writeAll("fishing in");
    try t.interface.writeAll("the ocean");
    try t.interface.writeAll("at");
    try t.interface.writeAll("1");
    try t.interface.flush();
}

test "splat" {
    var buffer: [8]u8 = undefined;
    var t = TestWriter.initStreaming(&buffer);
    _ = try t.interface.writeSplat(&.{ "a", "b", "c", "d", "e", "f", "g", "h" }, 1);
    try t.interface.flush();

    t = TestWriter.initStreaming(&buffer);
    _ = try t.interface.writeSplat(&.{ "a", "b", "c", "d", "e", "f", "g", "h" }, 0);
    try t.interface.flush();

    t = TestWriter.initStreaming(&buffer);
    _ = try t.interface.writeSplat(&.{ "a", "b", "c", "d", "e", "f", "g", "h" }, 2);
    try t.interface.flush();
}

const std = @import("std");
feral oak
#

I still don't quite get how you can not implement flush or rebase.
And I'm trying to understand the purpose of adding an interface, but here is my working uart writer.

const std = @import("std");
const Writer = std.Io.Writer;

const UART0: usize = 0x1000_0000;
var uart_reg: *volatile u8 = @ptrFromInt(UART0);
var buffer: [1024]u8 = undefined;

pub const Error = error{
    WriteFailed,
};

pub fn println(comptime fmt: []const u8, args: anytype) void {
    var writer: std.Io.Writer = uart_writer(&buffer);
    defer writer.flush() catch unreachable;
    std.Io.Writer.print(&writer, fmt, args) catch unreachable;
    uart_reg.* = '\n';
}

fn uart_writer(input_buffer: []u8) std.Io.Writer {
    return .{
        .vtable = &.{
            .drain = iterDrain,
            .flush = flusher,
            .rebase = failingRebase,
        },
        .buffer = input_buffer,
        .end = 0,
    };
}

pub fn failingRebase(w: *Writer, preserve: usize, capacity: usize) Error!void {
    _ = w;
    _ = preserve;
    _ = capacity;
    return error.WriteFailed;
}

pub fn iterDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
    _ = splat;
    _ = w;
    if (data.len == 0) return 0;
    for (data) |bytes| {
        for (bytes) |c| {
            uart_reg.* = c;
        }
    }
    return data.len;
}

pub fn flusher(w: *Writer) Error!void {
    if (w.end == 0) return;
    _ = try w.vtable.drain(w, &.{w.buffer[0..w.end]}, 0);
    return;
}
mental gyro
#

Your drain function needs to do all of the things in the example code I pasted above because the main zig writer code (that you dont control) is expecting your drain function to do all that.

#

I recommend starting with the sample code I provided, and modifying the std.log.err calls to do your actual writing.

rough hazel
feral oak
feral oak
#

yea its just setting w.end to 0

feral oak
#

Here is my final.

...
pub fn println(comptime fmt: []const u8, args: anytype) void {
    std.Io.Writer.print(&global_state.writer, fmt, args) catch unreachable;
    global_state.writer.flush() catch unreachable;
}

pub fn iterDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
    if (w.end != 0) {
        for (w.buffer[0..w.end]) |c| {
            uart_reg.* = c;
        }
        w.end = 0;
    }
    var count: usize = 0;
    if (data.len == 0) {
        uart_reg.* = '\n';
        return count;
    }

    const left = data[0 .. data.len - 1];
    const right = data[data.len - 1];

    for (left) |value| {
        if (value.len > 0) {
            for (left[0..left.len]) |bytes| {
                for (bytes) |c| {
                    uart_reg.* = c;
                }
            }
            count += value.len;
        }
    }

    if (right.len > 0) {
        for (0..splat) |_| {
            for (right[0..right.len]) |c| {
                uart_reg.* = c;
            }
            count += right.len;
        }
    }
    uart_reg.* = '\n';

    return count;
}

pub fn flusher(w: *Writer) Error!void {
    if (w.end == 0) return;
    _ = try w.vtable.drain(w, &.{}, 0);
    return;
}
#

at first i didn't implement the splat i thought mine was simple enough, but that is something i have to respect because std.Io.Writer.print is going to adhere to passing split