| ... | ... | @@ -6,6 +6,13 @@ const Writer = std.io.Writer; |
| 6 | 6 | const testing = std.testing; |
| 7 | 7 | |
| 8 | 8 | /// Underlying stream to send bytes to. |
| 9 | /// |
| 10 | /// A write will only be sent here if it could not fit into `buffer`, or if it |
| 11 | /// is a `writeFile`. |
| 12 | /// |
| 13 | /// `unbuffered_writer` may modify `buffer` if the number of bytes returned |
| 14 | /// equals number of bytes provided. This property is exploited by |
| 15 | /// `std.io.AllocatingWriter` for example. |
| 9 | 16 | unbuffered_writer: Writer, |
| 10 | 17 | /// User-provided storage that must outlive this `BufferedWriter`. |
| 11 | 18 | /// |
| ... | ... | @@ -27,6 +34,7 @@ pub fn writer(bw: *BufferedWriter) Writer { |
| 27 | 34 | .context = bw, |
| 28 | 35 | .vtable = &.{ |
| 29 | 36 | .writev = passthru_writev, |
| 37 | .splat = passthru_splat, |
| 30 | 38 | .writeFile = passthru_writeFile, |
| 31 | 39 | }, |
| 32 | 40 | }; |
| ... | ... | @@ -34,6 +42,7 @@ pub fn writer(bw: *BufferedWriter) Writer { |
| 34 | 42 | |
| 35 | 43 | const fixed_vtable: Writer.VTable = .{ |
| 36 | 44 | .writev = fixed_writev, |
| 45 | .splat = fixed_splat, |
| 37 | 46 | .writeFile = fixed_writeFile, |
| 38 | 47 | }; |
| 39 | 48 | |
| ... | ... | @@ -62,7 +71,8 @@ pub fn reset(bw: *BufferedWriter) void { |
| 62 | 71 | } |
| 63 | 72 | |
| 64 | 73 | pub fn flush(bw: *BufferedWriter) anyerror!void { |
| 65 | | try bw.unbuffered_writer.writeAll(bw.buffer[0..bw.end]); |
| 74 | const send_buffer = bw.buffer[0..bw.end]; |
| 75 | try bw.unbuffered_writer.writeAll(send_buffer); |
| 66 | 76 | bw.end = 0; |
| 67 | 77 | } |
| 68 | 78 | |
| ... | ... | @@ -120,6 +130,102 @@ fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize |
| 120 | 130 | return end - start_end; |
| 121 | 131 | } |
| 122 | 132 | |
| 133 | fn passthru_splat(context: *anyopaque, headers: []const []const u8, pattern: []const u8, n: usize) anyerror!usize { |
| 134 | const bw: *BufferedWriter = @alignCast(@ptrCast(context)); |
| 135 | const buffer = bw.buffer; |
| 136 | const start_end = bw.end; |
| 137 | |
| 138 | var end = bw.end; |
| 139 | for (headers, 0..) |bytes, i| { |
| 140 | const new_end = end + bytes.len; |
| 141 | if (new_end <= buffer.len) { |
| 142 | @branchHint(.likely); |
| 143 | @memcpy(buffer[end..new_end], bytes); |
| 144 | end = new_end; |
| 145 | continue; |
| 146 | } |
| 147 | if (end == 0) return bw.unbuffered_writer.splat(headers, pattern, n); |
| 148 | var buffers: [max_buffers_len][]const u8 = undefined; |
| 149 | buffers[0] = buffer[0..end]; |
| 150 | const remaining_headers = headers[i..]; |
| 151 | const remaining_buffers = buffers[1..]; |
| 152 | const len: usize = @min(remaining_headers.len, remaining_buffers.len); |
| 153 | @memcpy(remaining_buffers[0..len], remaining_headers[0..len]); |
| 154 | const send_buffers = buffers[0 .. len + 1]; |
| 155 | if (len >= remaining_headers.len) { |
| 156 | @branchHint(.likely); |
| 157 | // Made it past the headers, so we can call `splat`. |
| 158 | const written = try bw.unbuffered_writer.splat(send_buffers, pattern, n); |
| 159 | if (written < end) { |
| 160 | @branchHint(.unlikely); |
| 161 | const remainder = buffer[written..end]; |
| 162 | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); |
| 163 | bw.end = remainder.len; |
| 164 | return end - start_end; |
| 165 | } |
| 166 | bw.end = 0; |
| 167 | return written - start_end; |
| 168 | } |
| 169 | const written = try bw.unbuffered_writer.writev(send_buffers); |
| 170 | if (written < end) { |
| 171 | @branchHint(.unlikely); |
| 172 | const remainder = buffer[written..end]; |
| 173 | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); |
| 174 | bw.end = remainder.len; |
| 175 | return end - start_end; |
| 176 | } |
| 177 | bw.end = 0; |
| 178 | return written - start_end; |
| 179 | } |
| 180 | |
| 181 | switch (pattern.len) { |
| 182 | 0 => { |
| 183 | bw.end = end; |
| 184 | return end - start_end; |
| 185 | }, |
| 186 | 1 => { |
| 187 | const new_end = end + n; |
| 188 | if (new_end <= buffer.len) { |
| 189 | @branchHint(.likely); |
| 190 | @memset(buffer[end..new_end], pattern[0]); |
| 191 | bw.end = new_end; |
| 192 | return end - start_end; |
| 193 | } |
| 194 | const written = try bw.unbuffered_writer.splat(buffer[0..end], pattern, n); |
| 195 | if (written < end) { |
| 196 | @branchHint(.unlikely); |
| 197 | const remainder = buffer[written..end]; |
| 198 | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); |
| 199 | bw.end = remainder.len; |
| 200 | return end - start_end; |
| 201 | } |
| 202 | bw.end = 0; |
| 203 | return written - start_end; |
| 204 | }, |
| 205 | else => { |
| 206 | const new_end = end + pattern.len * n; |
| 207 | if (new_end <= buffer.len) { |
| 208 | @branchHint(.likely); |
| 209 | while (end < new_end) : (end += pattern.len) { |
| 210 | @memcpy(buffer[end..][0..pattern.len], pattern); |
| 211 | } |
| 212 | bw.end = end; |
| 213 | return end - start_end; |
| 214 | } |
| 215 | const written = try bw.unbuffered_writer.splat(buffer[0..end], pattern, n); |
| 216 | if (written < end) { |
| 217 | @branchHint(.unlikely); |
| 218 | const remainder = buffer[written..end]; |
| 219 | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); |
| 220 | bw.end = remainder.len; |
| 221 | return end - start_end; |
| 222 | } |
| 223 | bw.end = 0; |
| 224 | return written - start_end; |
| 225 | }, |
| 226 | } |
| 227 | } |
| 228 | |
| 123 | 229 | fn fixed_writev(context: *anyopaque, data: []const []const u8) anyerror!usize { |
| 124 | 230 | const bw: *BufferedWriter = @alignCast(@ptrCast(context)); |
| 125 | 231 | // When this function is called it means the buffer got full, so it's time |
| ... | ... | @@ -131,6 +237,19 @@ fn fixed_writev(context: *anyopaque, data: []const []const u8) anyerror!usize { |
| 131 | 237 | return error.NoSpaceLeft; |
| 132 | 238 | } |
| 133 | 239 | |
| 240 | fn fixed_splat(context: *anyopaque, headers: []const []const u8, pattern: []const u8, n: usize) anyerror!usize { |
| 241 | const bw: *BufferedWriter = @alignCast(@ptrCast(context)); |
| 242 | const dest = bw.buffer[bw.end..]; |
| 243 | if (headers.len > 0) { |
| 244 | @memcpy(dest, headers[0][0..dest.len]); |
| 245 | } else switch (pattern.len) { |
| 246 | 0 => unreachable, |
| 247 | 1 => @memset(dest, pattern[0]), |
| 248 | else => for (0..n) |i| @memcpy(dest[i * pattern.len ..][0..pattern.len], pattern), |
| 249 | } |
| 250 | return error.NoSpaceLeft; |
| 251 | } |
| 252 | |
| 134 | 253 | pub fn write(bw: *BufferedWriter, bytes: []const u8) anyerror!usize { |
| 135 | 254 | const buffer = bw.buffer; |
| 136 | 255 | const end = bw.end; |
| ... | ... | @@ -210,71 +329,7 @@ pub fn splatByteAll(bw: *BufferedWriter, byte: u8, n: usize) anyerror!void { |
| 210 | 329 | /// |
| 211 | 330 | /// Does maximum of one underlying `Writer.VTable.writev`. |
| 212 | 331 | pub fn splatByte(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize { |
| 213 | | const buffer = bw.buffer; |
| 214 | | const end = bw.end; |
| 215 | | |
| 216 | | const new_end = end + n; |
| 217 | | if (new_end <= buffer.len) { |
| 218 | | @branchHint(.likely); |
| 219 | | @memset(buffer[end..][0..n], byte); |
| 220 | | bw.end = new_end; |
| 221 | | return n; |
| 222 | | } |
| 223 | | |
| 224 | | if (n <= buffer.len) { |
| 225 | | const written = try bw.unbuffered_writer.write(buffer[0..end]); |
| 226 | | if (written < end) { |
| 227 | | @branchHint(.unlikely); |
| 228 | | const remainder = buffer[written..end]; |
| 229 | | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); |
| 230 | | bw.end = remainder.len; |
| 231 | | return 0; |
| 232 | | } |
| 233 | | assert(bw.buffer.ptr == buffer.ptr); // TODO this is not a valid assertion |
| 234 | | @memset(buffer[0..n], byte); |
| 235 | | bw.end = n; |
| 236 | | return n; |
| 237 | | } |
| 238 | | |
| 239 | | // First try to use only the unused buffer region, to make an attempt for a |
| 240 | | // single `writev`. |
| 241 | | const free_space = buffer[end..]; |
| 242 | | var remaining = n - free_space.len; |
| 243 | | @memset(free_space, byte); |
| 244 | | var buffers: [max_buffers_len][]const u8 = undefined; |
| 245 | | buffers[0] = buffer; |
| 246 | | var buffer_i: usize = 1; |
| 247 | | while (remaining > free_space.len and buffer_i < buffers.len) { |
| 248 | | buffers[buffer_i] = free_space; |
| 249 | | buffer_i += 1; |
| 250 | | remaining -= free_space.len; |
| 251 | | } |
| 252 | | if (remaining > 0 and buffer_i < buffers.len) { |
| 253 | | buffers[buffer_i] = free_space[0..remaining]; |
| 254 | | buffer_i += 1; |
| 255 | | const written = try bw.unbuffered_writer.writev(buffers[0..buffer_i]); |
| 256 | | if (written < end) { |
| 257 | | @branchHint(.unlikely); |
| 258 | | const remainder = buffer[written..end]; |
| 259 | | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); |
| 260 | | bw.end = remainder.len; |
| 261 | | return 0; |
| 262 | | } |
| 263 | | bw.end = 0; |
| 264 | | return written - end; |
| 265 | | } |
| 266 | | |
| 267 | | const written = try bw.unbuffered_writer.writev(buffers[0..buffer_i]); |
| 268 | | if (written < end) { |
| 269 | | @branchHint(.unlikely); |
| 270 | | const remainder = buffer[written..end]; |
| 271 | | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); |
| 272 | | bw.end = remainder.len; |
| 273 | | return 0; |
| 274 | | } |
| 275 | | |
| 276 | | bw.end = 0; |
| 277 | | return written - end; |
| 332 | return passthru_splat(bw, &.{}, &.{byte}, n); |
| 278 | 333 | } |
| 279 | 334 | |
| 280 | 335 | /// Writes the same slice many times, performing the underlying write call as |
| ... | ... | @@ -288,40 +343,7 @@ pub fn splatBytesAll(bw: *BufferedWriter, bytes: []const u8, n: usize) anyerror! |
| 288 | 343 | /// |
| 289 | 344 | /// Does maximum of one underlying `Writer.VTable.writev`. |
| 290 | 345 | pub fn splatBytes(bw: *BufferedWriter, bytes: []const u8, n: usize) anyerror!usize { |
| 291 | | const buffer = bw.buffer; |
| 292 | | const start_end = bw.end; |
| 293 | | var end = start_end; |
| 294 | | var remaining = n; |
| 295 | | while (remaining > 0 and end + bytes.len <= buffer.len) { |
| 296 | | @memcpy(buffer[end..][0..bytes.len], bytes); |
| 297 | | end += bytes.len; |
| 298 | | remaining -= 1; |
| 299 | | } |
| 300 | | |
| 301 | | if (remaining == 0) { |
| 302 | | bw.end = end; |
| 303 | | return end - start_end; |
| 304 | | } |
| 305 | | |
| 306 | | var buffers: [max_buffers_len][]const u8 = undefined; |
| 307 | | var buffer_i: usize = 1; |
| 308 | | buffers[0] = buffer[0..end]; |
| 309 | | const remaining_buffers = buffers[1..]; |
| 310 | | const buffers_len: usize = @min(remaining, remaining_buffers.len); |
| 311 | | @memset(remaining_buffers[0..buffers_len], bytes); |
| 312 | | remaining -= buffers_len; |
| 313 | | buffer_i += buffers_len; |
| 314 | | |
| 315 | | const written = try bw.unbuffered_writer.writev(buffers[0..buffer_i]); |
| 316 | | if (written < end) { |
| 317 | | @branchHint(.unlikely); |
| 318 | | const remainder = buffer[written..end]; |
| 319 | | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); |
| 320 | | bw.end = remainder.len; |
| 321 | | return end - start_end; |
| 322 | | } |
| 323 | | bw.end = 0; |
| 324 | | return written - start_end; |
| 346 | return passthru_splat(bw, &.{}, bytes, n); |
| 325 | 347 | } |
| 326 | 348 | |
| 327 | 349 | /// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes. |