Skip to content

Instantly share code, notes, and snippets.

@JadenGeller
Last active March 11, 2024 05:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JadenGeller/2c94515b425ffb86b3e7919f3f58f5c7 to your computer and use it in GitHub Desktop.
Save JadenGeller/2c94515b425ffb86b3e7919f3f58f5c7 to your computer and use it in GitHub Desktop.
useful for buffering models for a scroll view!
// import https://gist.github.com/JadenGeller/66585051f8de54d9f2a3df1acfd87c32#file-pinnedcollection-swift
import DequeModule
struct AdaptiveRangeBuffer<Index: Strideable, Element>: RandomAccessCollection, MutableCollection where Index.Stride == Int {
var storage: PinnedCollection<Deque<Element>, Index>
var defaultValue: (Index) -> Element
var willRemoveRange: (Slice<Self>) -> Void
init(indices: Range<Index>, defaultValue: @escaping (Index) -> Element, willRemoveRange: @escaping (Slice<Self>) -> Void = { _ in }) {
self.defaultValue = defaultValue
self.willRemoveRange = willRemoveRange
self.storage = Deque(indices.lazy.map(defaultValue)).reindex(from: indices.startIndex).pinningIndices()
}
var startIndex: Index {
get {
storage.startIndex
}
set {
let distance = startIndex.distance(to: newValue)
if distance >= 0 {
willRemoveRange(self[startIndex..<newValue])
storage.removeFirst(distance)
} else {
let newIndices = startIndex.advanced(by: distance)..<startIndex
storage.prepend(contentsOf: newIndices.lazy.map(defaultValue))
}
}
}
var endIndex: Index {
get {
storage.endIndex
}
set {
let distance = endIndex.distance(to: newValue)
if distance <= 0 {
willRemoveRange(self[newValue..<endIndex])
storage.removeLast(-distance)
} else {
let newIndices = endIndex..<endIndex.advanced(by: distance)
storage.append(contentsOf: newIndices.lazy.map(defaultValue))
}
}
}
var indices: Range<Index> {
get {
storage.indices
}
set {
guard newValue.overlaps(indices) else {
willRemoveRange(self[...])
self = .init(indices: newValue, defaultValue: defaultValue, willRemoveRange: willRemoveRange)
return
}
if newValue.lowerBound > startIndex { // shrink first
startIndex = newValue.lowerBound
endIndex = newValue.upperBound
} else {
endIndex = newValue.upperBound
startIndex = newValue.lowerBound
}
}
}
subscript(position: Index) -> Element {
get {
storage[position]
}
set {
storage[position] = newValue
}
}
}
extension AdaptiveRangeBuffer {
mutating func slide(toContain index: Index) {
if index < startIndex {
let offset = startIndex.distance(to: index)
indices = startIndex.advanced(by: offset)..<endIndex.advanced(by: offset)
}
else if index >= endIndex {
// FIXME: Consider migrating to closed rainge to simplify
let offset = endIndex.distance(to: index) + 1 // to contain!
indices = startIndex.advanced(by: offset)..<endIndex.advanced(by: offset)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment