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 }