main
go 144 lines 2.96 KB
Raw
1 package utils
2
3 import (
4 "maps"
5 "slices"
6 "sync/atomic"
7 )
8
9 // Snapshot stores an immutable value snapshot for lock-free reads.
10 // Use a snapshot function when T contains mutable maps, slices, or pointers.
11 // Do not copy a Snapshot after first use.
12 type Snapshot[T any] struct {
13 value atomic.Pointer[T]
14 snapshot func(T) T
15 }
16
17 func NewSnapshot[T any](initial T, snapshot ...func(T) T) *Snapshot[T] {
18 s := &Snapshot[T]{}
19 if len(snapshot) > 0 {
20 s.snapshot = snapshot[0]
21 }
22 s.Store(initial)
23 return s
24 }
25
26 func (s *Snapshot[T]) Load() T {
27 if s == nil {
28 var zero T
29 return zero
30 }
31 value := s.value.Load()
32 if value == nil {
33 var zero T
34 return zero
35 }
36 return s.snapshotValue(*value)
37 }
38
39 func (s *Snapshot[T]) Store(value T) {
40 if s == nil {
41 return
42 }
43 value = s.snapshotValue(value)
44 s.value.Store(&value)
45 }
46
47 func (s *Snapshot[T]) Swap(value T) T {
48 if s == nil {
49 var zero T
50 return zero
51 }
52 value = s.snapshotValue(value)
53 previous := s.value.Swap(&value)
54 if previous == nil {
55 var zero T
56 return zero
57 }
58 return s.snapshotValue(*previous)
59 }
60
61 func (s *Snapshot[T]) snapshotValue(value T) T {
62 if s != nil && s.snapshot != nil {
63 return s.snapshot(value)
64 }
65 return value
66 }
67
68 // Update applies a copy-on-write update and stores the resulting snapshot.
69 // The update function may be called more than once under contention, so it
70 // must not perform side effects.
71 func (s *Snapshot[T]) Update(update func(T) T) T {
72 if s == nil {
73 var zero T
74 return zero
75 }
76 for {
77 currentPtr := s.value.Load()
78 var current T
79 if currentPtr != nil {
80 current = s.snapshotValue(*currentPtr)
81 }
82
83 next := update(current)
84 next = s.snapshotValue(next)
85 if s.value.CompareAndSwap(currentPtr, &next) {
86 return s.snapshotValue(next)
87 }
88 }
89 }
90
91 // UpdateIf stores the returned snapshot only when update returns true.
92 // The update function may be called more than once under contention, so it
93 // must not perform side effects.
94 func (s *Snapshot[T]) UpdateIf(update func(T) (T, bool)) (T, bool) {
95 if s == nil {
96 var zero T
97 return zero, false
98 }
99 for {
100 currentPtr := s.value.Load()
101 var current T
102 if currentPtr != nil {
103 current = s.snapshotValue(*currentPtr)
104 }
105
106 next, ok := update(current)
107 if !ok {
108 return current, false
109 }
110 next = s.snapshotValue(next)
111 if s.value.CompareAndSwap(currentPtr, &next) {
112 return s.snapshotValue(next), true
113 }
114 }
115 }
116
117 // UpdateCopy copies the current snapshot, applies a local mutation to the copy,
118 // and stores the copy. The update function may be called more than once under
119 // contention, so it must not perform side effects.
120 func (s *Snapshot[T]) UpdateCopy(update func(*T)) T {
121 return s.Update(func(current T) T {
122 next := current
123 if update != nil {
124 update(&next)
125 }
126 return next
127 })
128 }
129
130 func CloneSlice[T any](values []T) []T {
131 return slices.Clone(values)
132 }
133
134 func CloneMap[K comparable, V any](values map[K]V) map[K]V {
135 return maps.Clone(values)
136 }
137
138 func ClonePtr[T any](value *T) *T {
139 if value == nil {
140 return nil
141 }
142 next := *value
143 return &next
144 }