| ... | ... | @@ -24,8 +24,6 @@ supports_ansi_escape_codes: bool, |
| 24 | 24 | |
| 25 | 25 | root: Node, |
| 26 | 26 | |
| 27 | | /// Protects all the state shared between the update thread and the public API calls. |
| 28 | | mutex: std.Thread.Mutex, |
| 29 | 27 | update_thread: ?std.Thread, |
| 30 | 28 | |
| 31 | 29 | /// Atomically set by SIGWINCH as well as the root done() function. |
| ... | ... | @@ -33,6 +31,7 @@ redraw_event: std.Thread.ResetEvent, |
| 33 | 31 | /// Ensure there is only 1 global Progress object. |
| 34 | 32 | initialized: bool, |
| 35 | 33 | /// Indicates a request to shut down and reset global state. |
| 34 | /// Accessed atomically. |
| 36 | 35 | done: bool, |
| 37 | 36 | |
| 38 | 37 | refresh_rate_ns: u64, |
| ... | ... | @@ -65,10 +64,13 @@ pub const Options = struct { |
| 65 | 64 | /// Represents one unit of progress. Each node can have children nodes, or |
| 66 | 65 | /// one can use integers with `update`. |
| 67 | 66 | pub const Node = struct { |
| 68 | | parent: ?*Node, |
| 67 | mutex: std.Thread.Mutex, |
| 68 | /// Links to the parent and child nodes. |
| 69 | parent_list_node: std.DoublyLinkedList(void).Node, |
| 70 | /// Links to the prev and next sibling nodes. |
| 71 | sibling_list_node: std.DoublyLinkedList(void).Node, |
| 72 | |
| 69 | 73 | name: []const u8, |
| 70 | | /// Must be handled atomically to be thread-safe. |
| 71 | | recently_updated_child: ?*Node = null, |
| 72 | 74 | /// Must be handled atomically to be thread-safe. 0 means null. |
| 73 | 75 | unprotected_estimated_total_items: usize, |
| 74 | 76 | /// Must be handled atomically to be thread-safe. |
| ... | ... | @@ -78,50 +80,57 @@ pub const Node = struct { |
| 78 | 80 | |
| 79 | 81 | /// Create a new child progress node. Thread-safe. |
| 80 | 82 | /// |
| 81 | | /// Call `Node.end` when done. |
| 83 | /// It is expected for the memory of the result to be stored in the |
| 84 | /// caller's stack and therefore is required to call `activate` immediately |
| 85 | /// on the result after initializing the memory location and `end` when done. |
| 82 | 86 | /// |
| 83 | 87 | /// Passing 0 for `estimated_total_items` means unknown. |
| 84 | 88 | pub fn start(self: *Node, name: []const u8, estimated_total_items: usize) Node { |
| 85 | 89 | return .{ |
| 86 | | .parent = self, |
| 90 | .mutex = .{}, |
| 91 | .parent_list_node = .{ |
| 92 | .prev = &self.parent_list_node, |
| 93 | .next = null, |
| 94 | .data = {}, |
| 95 | }, |
| 96 | .sibling_list_node = .{ .data = {} }, |
| 87 | 97 | .name = name, |
| 88 | 98 | .unprotected_estimated_total_items = estimated_total_items, |
| 89 | 99 | .unprotected_completed_items = 0, |
| 90 | 100 | }; |
| 91 | 101 | } |
| 92 | 102 | |
| 103 | /// To be called exactly once after `start`. |
| 104 | pub fn activate(n: *Node) void { |
| 105 | const p = n.parent().?; |
| 106 | p.mutex.lock(); |
| 107 | defer p.mutex.unlock(); |
| 108 | assert(p.parent_list_node.next == null); |
| 109 | p.parent_list_node.next = &n.parent_list_node; |
| 110 | } |
| 111 | |
| 93 | 112 | /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe. |
| 94 | 113 | pub fn completeOne(self: *Node) void { |
| 95 | 114 | _ = @atomicRmw(usize, &self.unprotected_completed_items, .Add, 1, .monotonic); |
| 96 | | self.activate(); |
| 97 | 115 | } |
| 98 | 116 | |
| 99 | 117 | /// Finish a started `Node`. Thread-safe. |
| 100 | | pub fn end(self: *Node) void { |
| 101 | | if (self.parent) |parent| { |
| 102 | | parent.completeOne(); |
| 118 | pub fn end(child: *Node) void { |
| 119 | if (child.parent()) |p| { |
| 120 | // Make sure the other thread doesn't access this memory that is |
| 121 | // about to be released. |
| 122 | child.mutex.lock(); |
| 123 | |
| 124 | const other = if (child.sibling_list_node.next) |n| n else child.sibling_list_node.prev; |
| 125 | _ = @cmpxchgStrong(std.DoublyLinkedList(void).Node, &p.parent_list_node.next, child, other, .seq_cst, .seq_cst); |
| 126 | p.completeOne(); |
| 103 | 127 | } else { |
| 104 | | { |
| 105 | | global_progress.mutex.lock(); |
| 106 | | defer global_progress.mutex.unlock(); |
| 107 | | global_progress.done = true; |
| 108 | | } |
| 128 | @atomicStore(bool, &global_progress.done, true, .seq_cst); |
| 109 | 129 | global_progress.redraw_event.set(); |
| 110 | 130 | if (global_progress.update_thread) |thread| thread.join(); |
| 111 | 131 | } |
| 112 | 132 | } |
| 113 | 133 | |
| 114 | | /// Tell the parent node that this node is actively being worked on. Thread-safe. |
| 115 | | pub fn activate(self: *Node) void { |
| 116 | | var parent = self.parent; |
| 117 | | var child = self; |
| 118 | | while (parent) |p| { |
| 119 | | @atomicStore(?*Node, &p.recently_updated_child, child, .release); |
| 120 | | child = p; |
| 121 | | parent = p.parent; |
| 122 | | } |
| 123 | | } |
| 124 | | |
| 125 | 134 | /// Thread-safe. 0 means unknown. |
| 126 | 135 | pub fn setEstimatedTotalItems(self: *Node, count: usize) void { |
| 127 | 136 | @atomicStore(usize, &self.unprotected_estimated_total_items, count, .monotonic); |
| ... | ... | @@ -131,6 +140,11 @@ pub const Node = struct { |
| 131 | 140 | pub fn setCompletedItems(self: *Node, completed_items: usize) void { |
| 132 | 141 | @atomicStore(usize, &self.unprotected_completed_items, completed_items, .monotonic); |
| 133 | 142 | } |
| 143 | |
| 144 | fn parent(child: *Node) ?*Node { |
| 145 | const parent_node = child.parent_list_node.prev orelse return null; |
| 146 | return @fieldParentPtr("parent_list_node", parent_node); |
| 147 | } |
| 134 | 148 | }; |
| 135 | 149 | |
| 136 | 150 | var global_progress: Progress = .{ |
| ... | ... | @@ -138,7 +152,6 @@ var global_progress: Progress = .{ |
| 138 | 152 | .is_windows_terminal = false, |
| 139 | 153 | .supports_ansi_escape_codes = false, |
| 140 | 154 | .root = undefined, |
| 141 | | .mutex = .{}, |
| 142 | 155 | .update_thread = null, |
| 143 | 156 | .redraw_event = .{}, |
| 144 | 157 | .initialized = false, |
| ... | ... | @@ -169,7 +182,9 @@ pub fn start(options: Options) *Node { |
| 169 | 182 | global_progress.terminal = stderr; |
| 170 | 183 | } |
| 171 | 184 | global_progress.root = .{ |
| 172 | | .parent = null, |
| 185 | .mutex = .{}, |
| 186 | .parent_list_node = .{ .data = {} }, |
| 187 | .sibling_list_node = .{ .data = {} }, |
| 173 | 188 | .name = options.root_name, |
| 174 | 189 | .unprotected_estimated_total_items = options.estimated_total_items, |
| 175 | 190 | .unprotected_completed_items = 0, |
| ... | ... | @@ -220,10 +235,8 @@ fn updateThreadRun() void { |
| 220 | 235 | maybeUpdateSize(resize_flag); |
| 221 | 236 | |
| 222 | 237 | const buffer = b: { |
| 223 | | global_progress.mutex.lock(); |
| 224 | | defer global_progress.mutex.unlock(); |
| 225 | | |
| 226 | | if (global_progress.done) return clearTerminal(); |
| 238 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| 239 | return clearTerminal(); |
| 227 | 240 | |
| 228 | 241 | break :b computeRedraw(); |
| 229 | 242 | }; |
| ... | ... | @@ -235,10 +248,8 @@ fn updateThreadRun() void { |
| 235 | 248 | maybeUpdateSize(resize_flag); |
| 236 | 249 | |
| 237 | 250 | const buffer = b: { |
| 238 | | global_progress.mutex.lock(); |
| 239 | | defer global_progress.mutex.unlock(); |
| 240 | | |
| 241 | | if (global_progress.done) return clearTerminal(); |
| 251 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| 252 | return clearTerminal(); |
| 242 | 253 | |
| 243 | 254 | break :b computeRedraw(); |
| 244 | 255 | }; |
| ... | ... | @@ -270,7 +281,6 @@ fn computeRedraw() []u8 { |
| 270 | 281 | i = prefix.len; |
| 271 | 282 | |
| 272 | 283 | // Walk the tree and write the progress output to the buffer. |
| 273 | | |
| 274 | 284 | var node: *Node = &global_progress.root; |
| 275 | 285 | while (true) { |
| 276 | 286 | const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .monotonic); |