authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-18 21:51:18-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-20 15:08:59-07:00
logaa6ef10cc657e2bbe59c362f27d7a557c43d7fae
treed76bbb5d6912c53e3bad0cf8281459c24bbf3249
parentb2f8631a3c9b2cc04a4c78f38d164130be2fb1ae

std.Progress: make the API thread-safe

We generally get away with atomic primitives, however a lock is required around the refresh function since it traverses the Node graph, and we need to be sure no references to Nodes remain after end() is called.

6 files changed, 273 insertions(+), 238 deletions(-)

CMakeLists.txt+1-1
...@@ -410,7 +410,7 @@ set(ZIG_STAGE2_SOURCES...@@ -410,7 +410,7 @@ set(ZIG_STAGE2_SOURCES
410 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig"410 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig"
411 "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig"411 "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig"
412 "${CMAKE_SOURCE_DIR}/lib/std/process.zig"412 "${CMAKE_SOURCE_DIR}/lib/std/process.zig"
413 "${CMAKE_SOURCE_DIR}/lib/std/progress.zig"413 "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig"
414 "${CMAKE_SOURCE_DIR}/lib/std/rand.zig"414 "${CMAKE_SOURCE_DIR}/lib/std/rand.zig"
415 "${CMAKE_SOURCE_DIR}/lib/std/reset_event.zig"415 "${CMAKE_SOURCE_DIR}/lib/std/reset_event.zig"
416 "${CMAKE_SOURCE_DIR}/lib/std/sort.zig"416 "${CMAKE_SOURCE_DIR}/lib/std/sort.zig"
lib/std/Progress.zig+264-229
...@@ -3,263 +3,298 @@...@@ -3,263 +3,298 @@
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.5// and substantial portions of the software.
6
7//! This API non-allocating, non-fallible, and thread-safe.
8//! The tradeoff is that users of this API must provide the storage
9//! for each `Progress.Node`.
10//!
11//! Initialize the struct directly, overriding these fields as desired:
12//! * `refresh_rate_ms`
13//! * `initial_delay_ms`
14
6const std = @import("std");15const std = @import("std");
7const windows = std.os.windows;16const windows = std.os.windows;
8const testing = std.testing;17const testing = std.testing;
9const assert = std.debug.assert;18const assert = std.debug.assert;
19const Progress = @This();
1020
11/// This API is non-allocating and non-fallible. The tradeoff is that users of21/// `null` if the current node (and its children) should
12/// this API must provide the storage for each `Progress.Node`.22/// not print on update()
13/// Initialize the struct directly, overriding these fields as desired:23terminal: ?std.fs.File = undefined,
14/// * `refresh_rate_ms`
15/// * `initial_delay_ms`
16pub const Progress = struct {
17 /// `null` if the current node (and its children) should
18 /// not print on update()
19 terminal: ?std.fs.File = undefined,
20
21 /// Whether the terminal supports ANSI escape codes.
22 supports_ansi_escape_codes: bool = false,
23
24 root: Node = undefined,
25
26 /// Keeps track of how much time has passed since the beginning.
27 /// Used to compare with `initial_delay_ms` and `refresh_rate_ms`.
28 timer: std.time.Timer = undefined,
29
30 /// When the previous refresh was written to the terminal.
31 /// Used to compare with `refresh_rate_ms`.
32 prev_refresh_timestamp: u64 = undefined,
33
34 /// This buffer represents the maximum number of bytes written to the terminal
35 /// with each refresh.
36 output_buffer: [100]u8 = undefined,
37
38 /// How many nanoseconds between writing updates to the terminal.
39 refresh_rate_ns: u64 = 50 * std.time.ns_per_ms,
40
41 /// How many nanoseconds to keep the output hidden
42 initial_delay_ns: u64 = 500 * std.time.ns_per_ms,
43
44 done: bool = true,
45
46 /// Keeps track of how many columns in the terminal have been output, so that
47 /// we can move the cursor back later.
48 columns_written: usize = undefined,
49
50 /// Represents one unit of progress. Each node can have children nodes, or
51 /// one can use integers with `update`.
52 pub const Node = struct {
53 context: *Progress,
54 parent: ?*Node,
55 completed_items: usize,
56 name: []const u8,
57 recently_updated_child: ?*Node = null,
58
59 /// This field may be updated freely.
60 estimated_total_items: ?usize,
61
62 /// Create a new child progress node.
63 /// Call `Node.end` when done.
64 /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
65 /// API to set `self.parent.recently_updated_child` with the return value.
66 /// Until that is fixed you probably want to call `activate` on the return value.
67 pub fn start(self: *Node, name: []const u8, estimated_total_items: ?usize) Node {
68 return Node{
69 .context = self.context,
70 .parent = self,
71 .completed_items = 0,
72 .name = name,
73 .estimated_total_items = estimated_total_items,
74 };
75 }
7624
77 /// This is the same as calling `start` and then `end` on the returned `Node`.25/// Whether the terminal supports ANSI escape codes.
78 pub fn completeOne(self: *Node) void {26supports_ansi_escape_codes: bool = false,
79 if (self.parent) |parent| parent.recently_updated_child = self;
80 self.completed_items += 1;
81 self.context.maybeRefresh();
82 }
8327
84 pub fn end(self: *Node) void {28root: Node = undefined,
85 self.context.maybeRefresh();
86 if (self.parent) |parent| {
87 if (parent.recently_updated_child) |parent_child| {
88 if (parent_child == self) {
89 parent.recently_updated_child = null;
90 }
91 }
92 parent.completeOne();
93 } else {
94 self.context.done = true;
95 self.context.refresh();
96 }
97 }
9829
99 /// Tell the parent node that this node is actively being worked on.30/// Keeps track of how much time has passed since the beginning.
100 pub fn activate(self: *Node) void {31/// Used to compare with `initial_delay_ms` and `refresh_rate_ms`.
101 if (self.parent) |parent| parent.recently_updated_child = self;32timer: std.time.Timer = undefined,
102 }33
103 };34/// When the previous refresh was written to the terminal.
35/// Used to compare with `refresh_rate_ms`.
36prev_refresh_timestamp: u64 = undefined,
37
38/// This buffer represents the maximum number of bytes written to the terminal
39/// with each refresh.
40output_buffer: [100]u8 = undefined,
41
42/// How many nanoseconds between writing updates to the terminal.
43refresh_rate_ns: u64 = 50 * std.time.ns_per_ms,
44
45/// How many nanoseconds to keep the output hidden
46initial_delay_ns: u64 = 500 * std.time.ns_per_ms,
47
48done: bool = true,
49
50/// Protects the `refresh` function, as well as `node.recently_updated_child`.
51/// Without this, callsites would call `Node.end` and then free `Node` memory
52/// while it was still being accessed by the `refresh` function.
53update_lock: std.Mutex = .{},
54
55/// Keeps track of how many columns in the terminal have been output, so that
56/// we can move the cursor back later.
57columns_written: usize = undefined,
10458
105 /// Create a new progress node.59/// Represents one unit of progress. Each node can have children nodes, or
60/// one can use integers with `update`.
61pub const Node = struct {
62 context: *Progress,
63 parent: ?*Node,
64 name: []const u8,
65 /// Must be handled atomically to be thread-safe.
66 recently_updated_child: ?*Node = null,
67 /// Must be handled atomically to be thread-safe. 0 means null.
68 unprotected_estimated_total_items: usize,
69 /// Must be handled atomically to be thread-safe.
70 unprotected_completed_items: usize,
71
72 /// Create a new child progress node. Thread-safe.
106 /// Call `Node.end` when done.73 /// Call `Node.end` when done.
107 /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this74 /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
108 /// API to return Progress rather than accept it as a parameter.75 /// API to set `self.parent.recently_updated_child` with the return value.
109 pub fn start(self: *Progress, name: []const u8, estimated_total_items: ?usize) !*Node {76 /// Until that is fixed you probably want to call `activate` on the return value.
110 const stderr = std.io.getStdErr();77 /// Passing 0 for `estimated_total_items` means unknown.
111 self.terminal = null;78 pub fn start(self: *Node, name: []const u8, estimated_total_items: usize) Node {
112 if (stderr.supportsAnsiEscapeCodes()) {79 return Node{
113 self.terminal = stderr;80 .context = self.context,
114 self.supports_ansi_escape_codes = true;81 .parent = self,
115 } else if (std.builtin.os.tag == .windows and stderr.isTty()) {
116 self.terminal = stderr;
117 }
118 self.root = Node{
119 .context = self,
120 .parent = null,
121 .completed_items = 0,
122 .name = name,82 .name = name,
123 .estimated_total_items = estimated_total_items,83 .unprotected_estimated_total_items = estimated_total_items,
84 .unprotected_completed_items = 0,
124 };85 };
125 self.columns_written = 0;
126 self.prev_refresh_timestamp = 0;
127 self.timer = try std.time.Timer.start();
128 self.done = false;
129 return &self.root;
130 }86 }
13187
132 /// Updates the terminal if enough time has passed since last update.88 /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe.
133 pub fn maybeRefresh(self: *Progress) void {89 pub fn completeOne(self: *Node) void {
134 const now = self.timer.read();90 self.activate();
135 if (now < self.initial_delay_ns) return;91 _ = @atomicRmw(usize, &self.unprotected_completed_items, .Add, 1, .Monotonic);
136 if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;92 self.context.maybeRefresh();
137 self.refresh();
138 }93 }
13994
140 /// Updates the terminal and resets `self.next_refresh_timestamp`.95 /// Finish a started `Node`. Thread-safe.
141 pub fn refresh(self: *Progress) void {96 pub fn end(self: *Node) void {
142 const file = self.terminal orelse return;97 self.context.maybeRefresh();
14398 if (self.parent) |parent| {
144 const prev_columns_written = self.columns_written;99 {
145 var end: usize = 0;100 const held = self.context.update_lock.acquire();
146 if (self.columns_written > 0) {101 defer held.release();
147 // restore the cursor position by moving the cursor102 _ = @cmpxchgStrong(?*Node, &parent.recently_updated_child, self, null, .Monotonic, .Monotonic);
148 // `columns_written` cells to the left, then clear the rest of the103 }
149 // line104 parent.completeOne();
150 if (self.supports_ansi_escape_codes) {105 } else {
151 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", .{self.columns_written}) catch unreachable).len;106 self.context.done = true;
152 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;107 self.context.refresh();
153 } else if (std.builtin.os.tag == .windows) winapi: {
154 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
155 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
156 unreachable;
157
158 var cursor_pos = windows.COORD{
159 .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
160 .Y = info.dwCursorPosition.Y,
161 };
162
163 if (cursor_pos.X < 0)
164 cursor_pos.X = 0;
165
166 const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X);
167
168 var written: windows.DWORD = undefined;
169 if (windows.kernel32.FillConsoleOutputAttribute(
170 file.handle,
171 info.wAttributes,
172 fill_chars,
173 cursor_pos,
174 &written,
175 ) != windows.TRUE) {
176 // Stop trying to write to this file.
177 self.terminal = null;
178 break :winapi;
179 }
180 if (windows.kernel32.FillConsoleOutputCharacterA(
181 file.handle,
182 ' ',
183 fill_chars,
184 cursor_pos,
185 &written,
186 ) != windows.TRUE) unreachable;
187
188 if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE)
189 unreachable;
190 } else unreachable;
191
192 self.columns_written = 0;
193 }108 }
109 }
194110
195 if (!self.done) {111 /// Tell the parent node that this node is actively being worked on. Thread-safe.
196 var need_ellipse = false;112 pub fn activate(self: *Node) void {
197 var maybe_node: ?*Node = &self.root;113 if (self.parent) |parent| {
198 while (maybe_node) |node| {114 @atomicStore(?*Node, &parent.recently_updated_child, self, .Monotonic);
199 if (need_ellipse) {
200 self.bufWrite(&end, "... ", .{});
201 }
202 need_ellipse = false;
203 if (node.name.len != 0 or node.estimated_total_items != null) {
204 if (node.name.len != 0) {
205 self.bufWrite(&end, "{}", .{node.name});
206 need_ellipse = true;
207 }
208 if (node.estimated_total_items) |total| {
209 if (need_ellipse) self.bufWrite(&end, " ", .{});
210 self.bufWrite(&end, "[{}/{}] ", .{ node.completed_items + 1, total });
211 need_ellipse = false;
212 } else if (node.completed_items != 0) {
213 if (need_ellipse) self.bufWrite(&end, " ", .{});
214 self.bufWrite(&end, "[{}] ", .{node.completed_items + 1});
215 need_ellipse = false;
216 }
217 }
218 maybe_node = node.recently_updated_child;
219 }
220 if (need_ellipse) {
221 self.bufWrite(&end, "... ", .{});
222 }
223 }115 }
116 }
224117
225 _ = file.write(self.output_buffer[0..end]) catch |e| {118 /// Thread-safe. 0 means unknown.
226 // Stop trying to write to this file once it errors.119 pub fn setEstimatedTotalItems(self: *Node, count: usize) void {
227 self.terminal = null;120 @atomicStore(usize, &self.unprotected_estimated_total_items, count, .Monotonic);
228 };
229 self.prev_refresh_timestamp = self.timer.read();
230 }121 }
231122
232 pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {123 /// Thread-safe.
233 const file = self.terminal orelse return;124 pub fn setCompletedItems(self: *Node, completed_items: usize) void {
234 self.refresh();125 @atomicStore(usize, &self.unprotected_completed_items, completed_items, .Monotonic);
235 file.outStream().print(format, args) catch {126 }
236 self.terminal = null;127};
237 return;128
238 };129/// Create a new progress node.
130/// Call `Node.end` when done.
131/// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
132/// API to return Progress rather than accept it as a parameter.
133/// `estimated_total_items` value of 0 means unknown.
134pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) !*Node {
135 const stderr = std.io.getStdErr();
136 self.terminal = null;
137 if (stderr.supportsAnsiEscapeCodes()) {
138 self.terminal = stderr;
139 self.supports_ansi_escape_codes = true;
140 } else if (std.builtin.os.tag == .windows and stderr.isTty()) {
141 self.terminal = stderr;
142 }
143 self.root = Node{
144 .context = self,
145 .parent = null,
146 .name = name,
147 .unprotected_estimated_total_items = estimated_total_items,
148 .unprotected_completed_items = 0,
149 };
150 self.columns_written = 0;
151 self.prev_refresh_timestamp = 0;
152 self.timer = try std.time.Timer.start();
153 self.done = false;
154 return &self.root;
155}
156
157/// Updates the terminal if enough time has passed since last update. Thread-safe.
158pub fn maybeRefresh(self: *Progress) void {
159 const now = self.timer.read();
160 if (now < self.initial_delay_ns) return;
161 const held = self.update_lock.tryAcquire() orelse return;
162 defer held.release();
163 if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
164 return self.refreshWithHeldLock();
165}
166
167/// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe.
168pub fn refresh(self: *Progress) void {
169 const held = self.update_lock.tryAcquire() orelse return;
170 defer held.release();
171
172 return self.refreshWithHeldLock();
173}
174
175fn refreshWithHeldLock(self: *Progress) void {
176 const file = self.terminal orelse return;
177
178 const prev_columns_written = self.columns_written;
179 var end: usize = 0;
180 if (self.columns_written > 0) {
181 // restore the cursor position by moving the cursor
182 // `columns_written` cells to the left, then clear the rest of the
183 // line
184 if (self.supports_ansi_escape_codes) {
185 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len;
186 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
187 } else if (std.builtin.os.tag == .windows) winapi: {
188 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
189 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
190 unreachable;
191
192 var cursor_pos = windows.COORD{
193 .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
194 .Y = info.dwCursorPosition.Y,
195 };
196
197 if (cursor_pos.X < 0)
198 cursor_pos.X = 0;
199
200 const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X);
201
202 var written: windows.DWORD = undefined;
203 if (windows.kernel32.FillConsoleOutputAttribute(
204 file.handle,
205 info.wAttributes,
206 fill_chars,
207 cursor_pos,
208 &written,
209 ) != windows.TRUE) {
210 // Stop trying to write to this file.
211 self.terminal = null;
212 break :winapi;
213 }
214 if (windows.kernel32.FillConsoleOutputCharacterA(
215 file.handle,
216 ' ',
217 fill_chars,
218 cursor_pos,
219 &written,
220 ) != windows.TRUE) unreachable;
221
222 if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE)
223 unreachable;
224 } else unreachable;
225
239 self.columns_written = 0;226 self.columns_written = 0;
240 }227 }
241228
242 fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {229 if (!self.done) {
243 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {230 var need_ellipse = false;
244 const amt = written.len;231 var maybe_node: ?*Node = &self.root;
245 end.* += amt;232 while (maybe_node) |node| {
246 self.columns_written += amt;233 if (need_ellipse) {
247 } else |err| switch (err) {234 self.bufWrite(&end, "... ", .{});
248 error.NoSpaceLeft => {235 }
249 self.columns_written += self.output_buffer.len - end.*;236 need_ellipse = false;
250 end.* = self.output_buffer.len;237 const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .Monotonic);
251 },238 const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .Monotonic);
239 if (node.name.len != 0 or eti > 0) {
240 if (node.name.len != 0) {
241 self.bufWrite(&end, "{s}", .{node.name});
242 need_ellipse = true;
243 }
244 if (eti > 0) {
245 if (need_ellipse) self.bufWrite(&end, " ", .{});
246 self.bufWrite(&end, "[{d}/{d}] ", .{ completed_items + 1, eti });
247 need_ellipse = false;
248 } else if (completed_items != 0) {
249 if (need_ellipse) self.bufWrite(&end, " ", .{});
250 self.bufWrite(&end, "[{d}] ", .{completed_items + 1});
251 need_ellipse = false;
252 }
253 }
254 maybe_node = @atomicLoad(?*Node, &node.recently_updated_child, .Monotonic);
252 }255 }
253 const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11;256 if (need_ellipse) {
254 const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;257 self.bufWrite(&end, "... ", .{});
255 if (end.* > max_end) {
256 const suffix = "... ";
257 self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;
258 std.mem.copy(u8, self.output_buffer[max_end..], suffix);
259 end.* = max_end + suffix.len;
260 }258 }
261 }259 }
262};260
261 _ = file.write(self.output_buffer[0..end]) catch |e| {
262 // Stop trying to write to this file once it errors.
263 self.terminal = null;
264 };
265 self.prev_refresh_timestamp = self.timer.read();
266}
267
268pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
269 const file = self.terminal orelse return;
270 self.refresh();
271 file.outStream().print(format, args) catch {
272 self.terminal = null;
273 return;
274 };
275 self.columns_written = 0;
276}
277
278fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
279 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
280 const amt = written.len;
281 end.* += amt;
282 self.columns_written += amt;
283 } else |err| switch (err) {
284 error.NoSpaceLeft => {
285 self.columns_written += self.output_buffer.len - end.*;
286 end.* = self.output_buffer.len;
287 },
288 }
289 const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11;
290 const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;
291 if (end.* > max_end) {
292 const suffix = "... ";
293 self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;
294 std.mem.copy(u8, self.output_buffer[max_end..], suffix);
295 end.* = max_end + suffix.len;
296 }
297}
263298
264test "basic functionality" {299test "basic functionality" {
265 var disable = true;300 var disable = true;
...@@ -300,7 +335,7 @@ test "basic functionality" {...@@ -300,7 +335,7 @@ test "basic functionality" {
300 std.time.sleep(5 * std.time.ns_per_ms);335 std.time.sleep(5 * std.time.ns_per_ms);
301 }336 }
302 {337 {
303 var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", null);338 var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", 0);
304 node.activate();339 node.activate();
305 std.time.sleep(10 * std.time.ns_per_ms);340 std.time.sleep(10 * std.time.ns_per_ms);
306 progress.refresh();341 progress.refresh();
lib/std/special/test_runner.zig+1-1
...@@ -36,7 +36,7 @@ pub fn main() anyerror!void {...@@ -36,7 +36,7 @@ pub fn main() anyerror!void {
36 }36 }
37 std.testing.log_level = .warn;37 std.testing.log_level = .warn;
3838
39 var test_node = root_node.start(test_fn.name, null);39 var test_node = root_node.start(test_fn.name, 0);
40 test_node.activate();40 test_node.activate();
41 progress.refresh();41 progress.refresh();
42 if (progress.terminal == null) {42 if (progress.terminal == null) {
lib/std/std.zig+1-1
...@@ -29,7 +29,7 @@ pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayE...@@ -29,7 +29,7 @@ pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayE
29pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;29pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;
30pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian;30pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian;
31pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;31pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
32pub const Progress = @import("progress.zig").Progress;32pub const Progress = @import("Progress.zig");
33pub const ResetEvent = @import("reset_event.zig").ResetEvent;33pub const ResetEvent = @import("reset_event.zig").ResetEvent;
34pub const SemanticVersion = @import("SemanticVersion.zig");34pub const SemanticVersion = @import("SemanticVersion.zig");
35pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;35pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
src/Compilation.zig+2-2
...@@ -1378,7 +1378,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {...@@ -1378,7 +1378,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
13781378
1379pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void {1379pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void {
1380 var progress: std.Progress = .{};1380 var progress: std.Progress = .{};
1381 var main_progress_node = try progress.start("", null);1381 var main_progress_node = try progress.start("", 0);
1382 defer main_progress_node.end();1382 defer main_progress_node.end();
1383 if (self.color == .off) progress.terminal = null;1383 if (self.color == .off) progress.terminal = null;
13841384
...@@ -1811,7 +1811,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *...@@ -1811,7 +1811,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *
1811 const c_source_basename = std.fs.path.basename(c_object.src.src_path);1811 const c_source_basename = std.fs.path.basename(c_object.src.src_path);
18121812
1813 c_comp_progress_node.activate();1813 c_comp_progress_node.activate();
1814 var child_progress_node = c_comp_progress_node.start(c_source_basename, null);1814 var child_progress_node = c_comp_progress_node.start(c_source_basename, 0);
1815 child_progress_node.activate();1815 child_progress_node.activate();
1816 defer child_progress_node.end();1816 defer child_progress_node.end();
18171817
src/stage1.zig+4-4
...@@ -293,7 +293,7 @@ export fn stage2_progress_start_root(...@@ -293,7 +293,7 @@ export fn stage2_progress_start_root(
293) *std.Progress.Node {293) *std.Progress.Node {
294 return progress.start(294 return progress.start(
295 name_ptr[0..name_len],295 name_ptr[0..name_len],
296 if (estimated_total_items == 0) null else estimated_total_items,296 estimated_total_items,
297 ) catch @panic("timer unsupported");297 ) catch @panic("timer unsupported");
298}298}
299299
...@@ -312,7 +312,7 @@ export fn stage2_progress_start(...@@ -312,7 +312,7 @@ export fn stage2_progress_start(
312 const child_node = std.heap.c_allocator.create(std.Progress.Node) catch @panic("out of memory");312 const child_node = std.heap.c_allocator.create(std.Progress.Node) catch @panic("out of memory");
313 child_node.* = node.start(313 child_node.* = node.start(
314 name_ptr[0..name_len],314 name_ptr[0..name_len],
315 if (estimated_total_items == 0) null else estimated_total_items,315 estimated_total_items,
316 );316 );
317 child_node.activate();317 child_node.activate();
318 return child_node;318 return child_node;
...@@ -333,8 +333,8 @@ export fn stage2_progress_complete_one(node: *std.Progress.Node) void {...@@ -333,8 +333,8 @@ export fn stage2_progress_complete_one(node: *std.Progress.Node) void {
333333
334// ABI warning334// ABI warning
335export fn stage2_progress_update_node(node: *std.Progress.Node, done_count: usize, total_count: usize) void {335export fn stage2_progress_update_node(node: *std.Progress.Node, done_count: usize, total_count: usize) void {
336 node.completed_items = done_count;336 node.setCompletedItems(done_count);
337 node.estimated_total_items = total_count;337 node.setEstimatedTotalItems(total_count);
338 node.activate();338 node.activate();
339 node.context.maybeRefresh();339 node.context.maybeRefresh();
340}340}