mirror of
https://github.com/ziglang/zig.git
synced 2025-12-08 15:23:14 +00:00
181 lines
6.8 KiB
Zig
181 lines
6.8 KiB
Zig
//! TODO rename to AllocatingWriter.
|
|
//! While it is possible to use `std.ArrayList` as the underlying writer when
|
|
//! using `std.io.BufferedWriter` by populating the `std.io.Writer` interface
|
|
//! and then using an empty buffer, it means that every use of
|
|
//! `std.io.BufferedWriter` will go through the vtable, including for
|
|
//! functions such as `writeByte`. This API instead maintains
|
|
//! `std.io.BufferedWriter` state such that it writes to the unused capacity of
|
|
//! an array list, filling it up completely before making a call through the
|
|
//! vtable, causing a resize. Consequently, the same, optimized, non-generic
|
|
//! machine code that uses `std.io.BufferedReader`, such as formatted printing,
|
|
//! takes the hot paths when using this API.
|
|
|
|
const std = @import("../std.zig");
|
|
const AllocatingWriter = @This();
|
|
const assert = std.debug.assert;
|
|
|
|
/// This is missing the data stored in `buffered_writer`. See `getWritten` for
|
|
/// returning a slice that includes both.
|
|
written: []u8,
|
|
allocator: std.mem.Allocator,
|
|
buffered_writer: std.io.BufferedWriter,
|
|
|
|
const vtable: std.io.Writer.VTable = .{
|
|
.writeSplat = writeSplat,
|
|
.writeFile = writeFile,
|
|
};
|
|
|
|
/// Sets the `AllocatingWriter` to an empty state.
|
|
pub fn init(aw: *AllocatingWriter, allocator: std.mem.Allocator) *std.io.BufferedWriter {
|
|
aw.* = .{
|
|
.written = &.{},
|
|
.allocator = allocator,
|
|
.buffered_writer = .{
|
|
.unbuffered_writer = .{
|
|
.context = aw,
|
|
.vtable = &vtable,
|
|
},
|
|
.buffer = &.{},
|
|
},
|
|
};
|
|
return &aw.buffered_writer;
|
|
}
|
|
|
|
/// Replaces `array_list` with empty, taking ownership of the memory.
|
|
pub fn fromArrayList(
|
|
aw: *AllocatingWriter,
|
|
allocator: std.mem.Allocator,
|
|
array_list: *std.ArrayListUnmanaged(u8),
|
|
) *std.io.BufferedWriter {
|
|
aw.* = .{
|
|
.written = array_list.items,
|
|
.allocator = allocator,
|
|
.buffered_writer = .{
|
|
.unbuffered_writer = .{
|
|
.context = aw,
|
|
.vtable = &vtable,
|
|
},
|
|
.buffer = array_list.unusedCapacitySlice(),
|
|
},
|
|
};
|
|
array_list.* = .empty;
|
|
return &aw.buffered_writer;
|
|
}
|
|
|
|
/// Returns an array list that takes ownership of the allocated memory.
|
|
/// Resets the `AllocatingWriter` to an empty state.
|
|
pub fn toArrayList(aw: *AllocatingWriter) std.ArrayListUnmanaged(u8) {
|
|
const bw = &aw.buffered_writer;
|
|
const written = aw.written;
|
|
const result: std.ArrayListUnmanaged(u8) = .{
|
|
.items = written.ptr[0 .. written.len + bw.end],
|
|
.capacity = written.len + bw.buffer.len,
|
|
};
|
|
aw.written = &.{};
|
|
bw.buffer = &.{};
|
|
bw.end = 0;
|
|
return result;
|
|
}
|
|
|
|
fn setArrayList(aw: *AllocatingWriter, list: std.ArrayListUnmanaged(u8)) void {
|
|
aw.written = list.items;
|
|
aw.buffered_writer.buffer = list.unusedCapacitySlice();
|
|
}
|
|
|
|
pub fn getWritten(aw: *AllocatingWriter) []u8 {
|
|
const bw = &aw.buffered_writer;
|
|
const end = aw.buffered_writer.end;
|
|
const result = aw.written.ptr[0 .. aw.written.len + end];
|
|
bw.buffer = bw.buffer[end..];
|
|
bw.end = 0;
|
|
return result;
|
|
}
|
|
|
|
pub fn clearRetainingCapacity(aw: *AllocatingWriter) void {
|
|
const bw = &aw.buffered_writer;
|
|
bw.buffer = aw.written.ptr[0 .. aw.written.len + bw.buffer.len];
|
|
bw.end = 0;
|
|
aw.written.len = 0;
|
|
}
|
|
|
|
fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
|
|
const aw: *AllocatingWriter = @alignCast(@ptrCast(context));
|
|
const start_len = aw.written.len;
|
|
const bw = &aw.buffered_writer;
|
|
const skip_first = data[0].ptr == aw.written.ptr + start_len;
|
|
const items_len = if (skip_first) start_len + data[0].len else start_len;
|
|
var list: std.ArrayListUnmanaged(u8) = .{
|
|
.items = aw.written.ptr[0..items_len],
|
|
.capacity = start_len + bw.buffer.len,
|
|
};
|
|
defer setArrayList(aw, list);
|
|
const rest = data[1 .. data.len - 1];
|
|
const pattern = data[data.len - 1];
|
|
var new_capacity: usize = list.capacity + pattern.len * splat;
|
|
for (rest) |bytes| new_capacity += bytes.len;
|
|
try list.ensureTotalCapacity(aw.allocator, new_capacity + 1);
|
|
for (rest) |bytes| list.appendSliceAssumeCapacity(bytes);
|
|
appendPatternAssumeCapacity(&list, pattern, splat);
|
|
aw.written = list.items;
|
|
bw.buffer = list.unusedCapacitySlice();
|
|
return list.items.len - start_len;
|
|
}
|
|
|
|
fn appendPatternAssumeCapacity(list: *std.ArrayListUnmanaged(u8), pattern: []const u8, splat: usize) void {
|
|
if (pattern.len == 1) {
|
|
list.appendNTimesAssumeCapacity(pattern[0], splat);
|
|
} else {
|
|
for (0..splat) |_| list.appendSliceAssumeCapacity(pattern);
|
|
}
|
|
}
|
|
|
|
fn writeFile(
|
|
context: *anyopaque,
|
|
file: std.fs.File,
|
|
offset: u64,
|
|
len: std.io.Writer.VTable.FileLen,
|
|
headers_and_trailers_full: []const []const u8,
|
|
headers_len_full: usize,
|
|
) anyerror!usize {
|
|
const aw: *AllocatingWriter = @alignCast(@ptrCast(context));
|
|
const gpa = aw.allocator;
|
|
var list = aw.toArrayList();
|
|
defer setArrayList(aw, list);
|
|
const start_len = list.items.len;
|
|
const headers_and_trailers, const headers_len = if (headers_len_full >= 1) b: {
|
|
assert(headers_and_trailers_full[0].ptr == list.items.ptr + start_len);
|
|
list.items.len += headers_and_trailers_full[0].len;
|
|
break :b .{ headers_and_trailers_full[1..], headers_len_full - 1 };
|
|
} else .{ headers_and_trailers_full, headers_len_full };
|
|
const trailers = headers_and_trailers[headers_len..];
|
|
if (len == .entire_file) {
|
|
var new_capacity: usize = list.capacity + std.atomic.cache_line;
|
|
for (headers_and_trailers) |bytes| new_capacity += bytes.len;
|
|
try list.ensureTotalCapacity(gpa, new_capacity);
|
|
for (headers_and_trailers[0..headers_len]) |bytes| list.appendSliceAssumeCapacity(bytes);
|
|
const dest = list.items.ptr[list.items.len..list.capacity];
|
|
const n = try file.pread(dest, offset);
|
|
if (n == 0) {
|
|
new_capacity = list.capacity;
|
|
for (trailers) |bytes| new_capacity += bytes.len;
|
|
try list.ensureTotalCapacity(gpa, new_capacity);
|
|
for (trailers) |bytes| list.appendSliceAssumeCapacity(bytes);
|
|
return list.items.len - start_len;
|
|
}
|
|
list.items.len += n;
|
|
return list.items.len - start_len;
|
|
}
|
|
var new_capacity: usize = list.capacity + len.int();
|
|
for (headers_and_trailers) |bytes| new_capacity += bytes.len;
|
|
try list.ensureTotalCapacity(gpa, new_capacity);
|
|
for (headers_and_trailers[0..headers_len]) |bytes| list.appendSliceAssumeCapacity(bytes);
|
|
const dest = list.items.ptr[list.items.len..][0..len.int()];
|
|
const n = try file.pread(dest, offset);
|
|
list.items.len += n;
|
|
if (n < dest.len) {
|
|
return list.items.len - start_len;
|
|
}
|
|
for (trailers) |bytes| list.appendSliceAssumeCapacity(bytes);
|
|
return list.items.len - start_len;
|
|
}
|