Skip to content

Instantly share code, notes, and snippets.

@carlynorama
Last active February 3, 2024 07:09
Show Gist options
  • Save carlynorama/1957ac97907e296b3f89406cd12a1f78 to your computer and use it in GitHub Desktop.
Save carlynorama/1957ac97907e296b3f89406cd12a1f78 to your computer and use it in GitHub Desktop.
FixedArray
//
// FixedWidth.swift
// TestingTwo
//
// Created by Carlyn Maw on 2/2/24.
// Inspired by 25:52 of WWDC 2020 "Safely Manage Pointers in Swift."
// In the subscript, using .load(fromByteOffset:as) prevents rebinding of memory for access. This struct can point to memory bound as a different type without overriding.
//TODO: Add tuplebride to help with C
//examples: https://github.com/carlynorama/UnsafeWrapCSampler/blob/main/Sources/Swift/TupleBridge.swift
import Foundation
struct FixedSizeArray<Element> : RandomAccessCollection {
let count:Int
var startIndex: Int { 0 }
var endIndex: Int { count }
//What is the best storage type?
private var dataBlob:Data
}
extension FixedSizeArray {
init(_ count:Int, default d:Element, initializer:() -> [Element] = { [] }) {
self.count = count
var result = initializer().prefix(count)
//if result.count > count { return nil }
for _ in 0...(count - result.count) {
result.append(d)
}
self.dataBlob = result.withUnsafeMutableBufferPointer { pointer in
Data(buffer: pointer)
}
}
init(initializer:() -> [Element]) {
var tmp = initializer()
self.dataBlob = tmp.withUnsafeMutableBufferPointer { pointer in
Data(buffer: pointer)
}
self.count = dataBlob.withUnsafeBytes { bytes in
let tmpCount = bytes.count / MemoryLayout<Element>.stride
precondition(tmpCount * MemoryLayout<Element>.stride == bytes.count)
precondition(Int(bitPattern: bytes.baseAddress).isMultiple(of: MemoryLayout<Element>.alignment))
return tmpCount
}
}
init(dataBlob: Data, as: Element.Type) {
self.dataBlob = dataBlob
self.count = dataBlob.withUnsafeBytes { bytes in
let tmpCount = bytes.count / MemoryLayout<Element>.stride
precondition(tmpCount * MemoryLayout<Element>.stride == bytes.count)
precondition(Int(bitPattern: bytes.baseAddress).isMultiple(of: MemoryLayout<Element>.alignment))
return tmpCount
}
}
}
extension FixedSizeArray {
subscript(position: Int) -> Element {
get {
dataBlob.withUnsafeBytes { rawPointer in
let bufferPointer = rawPointer.assumingMemoryBound(to: Element.self)
return bufferPointer[position]
}
}
set {
let startIndex = dataBlob.startIndex + position * MemoryLayout<Element>.stride
let endIndex = startIndex + MemoryLayout<Element>.stride
withUnsafePointer(to: newValue) { sourceValuePointer in
dataBlob.replaceSubrange(startIndex..<endIndex, with: sourceValuePointer, count: MemoryLayout<Element>.stride)
}
}
}
var all:[Element] {
loadFixedSizeCArray(source: dataBlob, ofType: Element.self) ?? []
}
//Okay to use assumingMemoryBound here IF using type ACTUALLY bound to.
//Else see UnsafeBufferView struct example using .loadBytes to recast read values
private func fetchFixedSizeCArray<T, R>(source:T, boundToType:R.Type) -> [R] {
withUnsafeBytes(of: source) { (rawPointer) -> [R] in
let bufferPointer = rawPointer.assumingMemoryBound(to: boundToType)
return [R](bufferPointer)
}
}
//TODO: Test non-numerics
private func loadFixedSizeCArray<T, R>(source:T, ofType:R.Type) -> [R]? {
withUnsafeBytes(of: source) { (rawPointer) -> [R]? in
rawPointer.baseAddress?.load(as: [R].self)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment