sonar

Scan files at memory bandwidth speed.
Log | Files | Refs

snapshot.odin (1893B)


      1 package main
      2 
      3 import "core:sync"
      4 
      5 Row :: struct {
      6 	node:  u32,
      7 	bytes: u64,
      8 }
      9 
     10 // What a UI draws: a fixed number of rows and a few counters, whatever the size of
     11 // the volume behind it.
     12 Snapshot :: struct {
     13 	rows:     [TOP_N]Row,
     14 	count:    int,
     15 	nodes:    u64, // records folded so far
     16 	bytes:    u64,
     17 	pending:  int, // nodes waiting on an ancestor
     18 	elapsed:  f64, // milliseconds since the scan began
     19 	complete: bool,
     20 }
     21 
     22 /*
     23 Hands a snapshot from the thread building it to the thread drawing it.
     24 
     25 A sequence number is odd only while a write is in flux. A reader copies, then checks
     26 the number is unchanged and even. A frame therefore never waits on a build: it gets
     27 the new answer, or keeps the one it had.
     28 */
     29 Publisher :: struct {
     30 	seq:  u32,
     31 	data: Snapshot,
     32 }
     33 
     34 publish :: proc(p: ^Publisher, s: Snapshot) {
     35 	sync.atomic_add(&p.seq, 1)
     36 	p.data = s
     37 	sync.atomic_add(&p.seq, 1)
     38 }
     39 
     40 /*
     41 Attempts before giving up.
     42 
     43 Enough to cover one write, which copies a few hundred bytes and so takes tens of
     44 nanoseconds against under ten for an attempt. Beyond that the builder has been
     45 descheduled partway through and no amount of spinning will help, while a caller that
     46 keeps last frame's answer has lost nothing.
     47 */
     48 @(private)
     49 ATTEMPTS :: 4
     50 
     51 // The latest complete snapshot. Fails while a write is in flux, which is the
     52 // caller's cue to keep the frame it already has.
     53 current :: proc(p: ^Publisher) -> (s: Snapshot, ok: bool) {
     54 	for _ in 0 ..< ATTEMPTS {
     55 		before := sync.atomic_load(&p.seq)
     56 		if before & 1 != 0 {
     57 			continue // a write is in progress
     58 		}
     59 		// Both loads are sequentially consistent, which is what stops the compiler
     60 		// hoisting this copy above the first or sinking it below the second. Moving it
     61 		// either way turns a correct seqlock into one that returns torn data silently.
     62 		s = p.data
     63 		if sync.atomic_load(&p.seq) == before {
     64 			return s, true
     65 		}
     66 	}
     67 	return {}, false
     68 }