go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/prjmanager/copyonwrite/cow.go (about) 1 // Copyright 2021 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package copyonwrite providers helpers for modifying slices in Copy-on-Write way. 16 package copyonwrite 17 18 import ( 19 "fmt" 20 "sort" 21 ) 22 23 // Slice abstracts out copy-on-write slices of pointers to objects. 24 type Slice interface { 25 Len() int 26 At(index int) any 27 Append(v any) Slice 28 // CloneShallow returns new slice of the given capacity with elements copied 29 // from [:length] of this slice. 30 CloneShallow(length, capacity int) Slice 31 } 32 33 // SortedSlice is a sorted Slice. 34 type SortedSlice interface { 35 Slice 36 sort.Interface 37 // LessElements returns true if element `a` must be before `b`. 38 // 39 // Unlike sort.Interface's Less, this compares arbitrary elements. 40 // 41 // Why not use sort.Interface's Less? 42 // Because during merge phase of (newly created, updated) slices, it's 43 // necessary to compare elements from different slices. OK, OK, it's possible 44 // to move both elements to one slice and re-use Less. For example, a temp 45 // slice of 2 elements can be always allocated for this very purpose, or one 46 // can smartly re-use resulting slice for this. However, either way it's 47 // slower and more complicated than requiring boilerplate in each SortedSlice 48 // type, which can be re-used in Less() implementation. 49 LessElements(a, b any) bool 50 } 51 52 // Deletion is special return value for Modifier to imply deletion. 53 var Deletion = deletion{} 54 55 type deletion struct{} 56 57 // Modifier takes a pointer to an existing COW object and returns: 58 // - special DeleteMe, if this object should be deleted from Slice; 59 // - the same object, if there are no modifications; 60 // - new object, if there were modifications. If working with SortedSlice, 61 // the new object must have the same sorting key. 62 type Modifier func(any) any 63 64 // Update modifies existing elements and adds new ones to Slice. 65 // 66 // If given Slice implements SortedSlice, assumes and preserves its sorted 67 // order. 68 // 69 // COWModifier, if not nil, is called on each existing object to modify or 70 // possible delete it. 71 // 72 // Sorts toAdd slice if using SortedSlice. 73 // 74 // Returns (new slice, true) if it was modified. 75 // Returns (old slice, false) otherwise. 76 func Update(in Slice, modifier Modifier, toAdd Slice) (Slice, bool) { 77 inLen := 0 78 if in != nil { 79 inLen = in.Len() 80 } 81 if modifier == nil { 82 modifier = noopModifier 83 } 84 cLen := 0 85 if toAdd != nil { 86 cLen = toAdd.Len() 87 } 88 _, isSorted := in.(SortedSlice) 89 if isSorted && cLen > 0 { 90 sortable, ok := toAdd.(SortedSlice) 91 if !ok { 92 panic(fmt.Errorf("Different types for in and toAdd slices: %T vs %T", in, toAdd)) 93 } 94 sort.Sort(sortable) 95 } 96 switch { 97 case inLen == 0 && cLen == 0: 98 return in, false 99 case inLen == 0: 100 return toAdd, true 101 case cLen == 0: 102 return modifyRemaining(in, 0, modifier, nil) 103 case !isSorted: 104 return modifyRemaining(in, 0, modifier, toAdd) 105 } 106 107 // Merge two sorted sequences into `out` while modifying `in` elements at the 108 // same time. 109 less := in.(SortedSlice).LessElements 110 out := in.CloneShallow(0, inLen+cLen) 111 i, c := 0, 0 112 for { 113 if c == cLen { 114 return modifyRemaining(in, i, modifier, out) 115 } 116 if i == inLen { 117 for ; c < cLen; c++ { 118 out = out.Append(toAdd.At(c)) 119 } 120 return out, true 121 } 122 iOld := in.At(i) 123 cNew := toAdd.At(c) 124 if less(cNew, iOld) { 125 c++ 126 out = out.Append(cNew) 127 continue 128 } 129 i++ 130 if iNew := modifier(iOld); iNew != Deletion { 131 out = out.Append(iNew) 132 } 133 } 134 } 135 136 func noopModifier(v any) any { return v } 137 138 func modifyRemaining(in Slice, i int, modifier Modifier, out Slice) (Slice, bool) { 139 l := in.Len() 140 for ; i < l; i++ { 141 before := in.At(i) 142 after := modifier(before) 143 if out == nil && after == before { 144 continue // no changes so far 145 } 146 if out == nil { 147 out = in.CloneShallow(i, l) 148 } 149 if after != Deletion { 150 out = out.Append(after) 151 } 152 } 153 if out == nil { 154 return in, false 155 } 156 return out, true 157 }