tree.odin (8645B)
1 package scan 2 3 import "core:mem" 4 import "core:mem/virtual" 5 import "core:strings" 6 import "core:sync" 7 import "core:unicode/utf16" 8 import "core:unicode/utf8" 9 10 // A file or directory, in the form every reader reduces to. 11 Node :: struct { 12 parent: u32, // index of the holding directory; a root points at itself 13 name: string, // UTF-8, owned by the tree 14 size: u64, // logical length 15 disk: u64, // bytes actually allocated 16 flags: Node_Flags, 17 } 18 19 Node_Flag :: enum u8 { 20 Used, // written by a reader; an untouched slot has this clear 21 // The parent link is final. A reader that learns of a child before its holding 22 // directory leaves this clear until the parent turns up, because until then the 23 // node cannot be told apart from one that has no parent at all. 24 Settled, 25 Directory, 26 Reparse, // junction, symlink or cloud placeholder: its target is counted elsewhere 27 Extra_Name, // another name for a node already counted, so rolling up must skip it 28 } 29 Node_Flags :: distinct bit_set[Node_Flag;u8] 30 31 // Nodes live in fixed blocks, so growing never moves one out from under a worker 32 // that is writing it. The outer array is sized once for the same reason. 33 @(private) 34 BLOCK_SHIFT :: 16 35 @(private) 36 BLOCK_NODES :: 1 << BLOCK_SHIFT 37 @(private) 38 MAX_BLOCKS :: 8192 39 40 @(private) 41 Block :: [BLOCK_NODES]Node 42 43 /* 44 What every reader writes into, and the only thing above this line reads. 45 46 A reader that knows its size up front calls `reserve` and indexes by its own 47 identifier. One that discovers as it goes calls `claim` for a run of slots. Either 48 way a slot belongs to one worker, so filling it needs no lock. 49 */ 50 Tree :: struct { 51 blocks: [MAX_BLOCKS]^Block, 52 count: u32, // slots handed out 53 committed: u32, // slots backed by an allocated block 54 mutex: sync.Mutex, // guards block allocation only 55 arenas: []virtual.Arena, // one per writer; names live here for the tree's life 56 root: u32, 57 // Polled by a UI rather than pushed to it, so sampling costs a reader nothing. 58 nodes_done: u64, 59 bytes_done: u64, 60 cancel: b32, 61 restated: b32, 62 allocator: mem.Allocator, 63 } 64 65 // One writer per worker. Holding two in the same thread, or one in two, is the 66 // mistake this type exists to make visible. 67 Writer :: struct { 68 tree: ^Tree, 69 arena: ^virtual.Arena, 70 } 71 72 tree_init :: proc(t: ^Tree, writers := 1, allocator := context.allocator) -> Error { 73 t.allocator = allocator 74 arenas, err := make([]virtual.Arena, max(writers, 1), allocator) 75 if err != nil { 76 return .Out_Of_Memory 77 } 78 t.arenas = arenas 79 for &a in t.arenas { 80 if virtual.arena_init_growing(&a) != nil { 81 tree_destroy(t) 82 return .Out_Of_Memory 83 } 84 } 85 return .None 86 } 87 88 tree_destroy :: proc(t: ^Tree) { 89 for i in 0 ..< t.committed >> BLOCK_SHIFT { 90 free(t.blocks[i], t.allocator) 91 } 92 for &a in t.arenas { 93 virtual.arena_destroy(&a) 94 } 95 delete(t.arenas, t.allocator) 96 t^ = {} 97 } 98 99 writer :: proc(t: ^Tree, index := 0) -> Writer { 100 return {tree = t, arena = &t.arenas[index]} 101 } 102 103 // Make `n` slots addressable at once, for a reader that already knows how many it 104 // has and wants to index by its own numbering. 105 reserve :: proc(t: ^Tree, n: u32) -> Error { 106 if !grow(t, n) { 107 return .Out_Of_Memory 108 } 109 t.count = max(t.count, n) 110 return .None 111 } 112 113 // Take the next `n` slots. They belong to this writer until the scan ends. 114 claim :: proc(w: ^Writer, n: u32) -> (first: u32, err: Error) { 115 first = sync.atomic_add(&w.tree.count, n) 116 if !grow(w.tree, first + n) { 117 return 0, .Out_Of_Memory 118 } 119 return first, .None 120 } 121 122 node :: proc(t: ^Tree, i: u32) -> ^Node { 123 return &t.blocks[i >> BLOCK_SHIFT][i & (BLOCK_NODES - 1)] 124 } 125 126 /* 127 Finish a node, once everything else about it is written. 128 129 Flags go last and atomically, so a reader that sees them is looking at a node whose 130 name, sizes and parent are already in place. Without that order a watcher could charge 131 a node for a size that had not been stored yet. 132 */ 133 node_publish :: proc(n: ^Node, flags: Node_Flags) { 134 sync.atomic_store((^u8)(&n.flags), transmute(u8)flags) 135 } 136 137 // A node's flags, read as a watcher must. Pairs with `node_publish`: an untouched slot 138 // reads back empty, and a written one reads back whole. 139 node_flags :: proc(n: ^Node) -> Node_Flags { 140 return transmute(Node_Flags)sync.atomic_load((^u8)(&n.flags)) 141 } 142 143 // Copy a name into this writer's arena, where it stays valid for the tree's life. 144 intern :: proc(w: ^Writer, name: string) -> string { 145 buf, err := make([]byte, len(name), virtual.arena_allocator(w.arena)) 146 if err != nil { 147 return "" 148 } 149 copy(buf, name) 150 return string(buf) 151 } 152 153 // The same, for a name that arrives as UTF-16 as it does on Windows. 154 intern_utf16 :: proc(w: ^Writer, name: []u16) -> string { 155 runes: [256]rune 156 n := utf16.decode(runes[:], name) 157 total := 0 158 for r in runes[:n] { 159 total += utf8.rune_size(r) 160 } 161 buf, err := make([]byte, total, virtual.arena_allocator(w.arena)) 162 if err != nil { 163 return "" 164 } 165 off := 0 166 for r in runes[:n] { 167 enc, size := utf8.encode_rune(r) 168 copy(buf[off:], enc[:size]) 169 off += size 170 } 171 return string(buf) 172 } 173 174 // Workers worth starting for `items` units of work, never more than `most`. A reader 175 // that discovers work in generations calls this per generation, since an early one 176 // may hold a single directory. 177 width_for :: proc(items, most: int) -> int { 178 return max(min(items, most), 1) 179 } 180 181 /* 182 Slots that can be addressed right now. 183 184 `claim` takes its range before allocating the block that backs it, so the count alone 185 runs ahead of what exists. Whichever is smaller is the part a watcher may look at; a 186 slot claimed but not yet written reads back with no flags set and is simply skipped. 187 */ 188 slots :: proc(t: ^Tree) -> u32 { 189 return min(sync.atomic_load(&t.count), sync.atomic_load(&t.committed)) 190 } 191 192 // A reader checks this between units of work so a UI can call the scan off. 193 cancelled :: proc(t: ^Tree) -> bool { 194 return bool(sync.atomic_load(&t.cancel)) 195 } 196 197 cancel :: proc(t: ^Tree) { 198 sync.atomic_store(&t.cancel, true) 199 } 200 201 /* 202 Say that what was read out of this tree before may be wrong. 203 204 A reader that can only learn a node's final size late has to write some of them twice, 205 and anything derived from the first writing has to be thrown away rather than added 206 to. Set once the restatement is complete, so whoever acts on it sees the finished 207 tree rather than one mid-rewrite. 208 209 This is the only thing above the readers that has to be understood about any of them. 210 */ 211 restate :: proc(t: ^Tree) { 212 sync.atomic_store(&t.restated, true) 213 } 214 215 // Whether the tree has been restated since this was last asked, clearing it. 216 taken_restated :: proc(t: ^Tree) -> bool { 217 return bool(sync.atomic_exchange(&t.restated, false)) 218 } 219 220 progress :: proc(t: ^Tree, nodes, bytes: u64) { 221 sync.atomic_add(&t.nodes_done, nodes) 222 sync.atomic_add(&t.bytes_done, bytes) 223 } 224 225 @(private) 226 grow :: proc(t: ^Tree, needed: u32) -> bool { 227 if needed <= sync.atomic_load(&t.committed) { 228 return true 229 } 230 sync.guard(&t.mutex) 231 for t.committed < needed { 232 i := t.committed >> BLOCK_SHIFT 233 if int(i) >= MAX_BLOCKS { 234 return false 235 } 236 b, err := new(Block, t.allocator) 237 if err != nil { 238 return false 239 } 240 t.blocks[i] = b 241 sync.atomic_store(&t.committed, t.committed + BLOCK_NODES) 242 } 243 return true 244 } 245 246 // Paths are written for the machine showing them rather than for the filesystem they 247 // came from, so an NTFS volume read from unix still prints something the shell there 248 // would take back. 249 @(private) 250 SEPARATOR :: `\` when ODIN_OS == .Windows else "/" 251 252 /* 253 Rebuild a node's path by following parents to a root. 254 255 A root points at itself, which is also how an orphan is marked: a node whose real 256 parent could not be resolved is rooted at itself and its path starts at its own name. 257 Depth is capped because a corrupt or racing source can produce a cycle. 258 */ 259 path :: proc(t: ^Tree, i: u32, allocator := context.allocator) -> string { 260 chain: [64]u32 261 depth := 0 262 cur := i 263 for depth < len(chain) { 264 chain[depth] = cur 265 depth += 1 266 n := node(t, cur) 267 if n.parent == cur || n.parent >= slots(t) { 268 break 269 } 270 cur = n.parent 271 } 272 273 sb := strings.builder_make(allocator) 274 for j := depth - 1; j >= 0; j -= 1 { 275 // Reading the flags first is what makes the name safe to read at all. 276 if .Used not_in node_flags(node(t, chain[j])) { 277 continue 278 } 279 name := node(t, chain[j]).name 280 if name == "" { 281 continue 282 } 283 // The root carries its own separator, so joining blindly would double it. 284 if strings.builder_len(sb) > 0 && !strings.has_suffix(strings.to_string(sb), SEPARATOR) { 285 strings.write_string(&sb, SEPARATOR) 286 } 287 strings.write_string(&sb, name) 288 } 289 if strings.builder_len(sb) == 0 { 290 strings.write_string(&sb, SEPARATOR) 291 } 292 return strings.to_string(sb) 293 }