For communicating with a BLE characteristic, I have a Swift struct that looks like:
struct Packet {
var control1:UInt8 = 0
var control2:UInt8 = 0
var payload:(UInt8,UInt8,UInt8,UInt8,UInt8,UInt8,UInt8,UInt8,UInt8,UInt8,UInt8,UInt8,UInt8,UInt8,UInt8,UInt8) = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
init(control1:UInt8, control2:UInt8) {
self.control1 = control1
self.control2 = control2
}
}
I have payload defined as a tuple, because that seems to be the only way to have an array (of bytes in this case) of fixed size embedded in a Swift struct. Verbose, but whatever.
I have a big ol' source:[UInt8] that I want to pull swatches of into that Packet struct, so I can send them via BLE to the remote device. When I do:
var packet = Packet(control1: self.pageIndex, control2: sentenceIndex)
let offset = (Int(self.pageIndex) * self.pageSize) + (Int(sentenceIndex) * self.sentenceSize)
let limit = offset + self.sentenceSize
packet.payload = self.source[offset..<limit]
For the last line, I get the rather confusing error:
Cannot subscript a value of type '[UInt8]'
Cryptic I say, because it actually can. If I take the assignment to the packet.payload out, it has no problem subscripting the value.
What I'm really interested in at a higher level, is how one puts together a struct with a fixed size array of bytes, and then copies swatches of a large buffer into those. I would like to both understand the above, as well as know how to solve my problem.
UPDATE:
I ended up backing up a little, influenced by both answers below, and rethinking. My main driving force was that I wanted a simple/clever way to have convert a struct with an internal array to/from NSData, primary in BLE communications. What I ended up doing was:
struct Packet {
var pageIndex:UInt8 = 0
var sentenceIndex:UInt8 = 0
var payload:ArraySlice<UInt8> = []
var nsdata:NSData {
let bytes:[UInt8] = [self.pageIndex, self.sentenceIndex] + self.payload
return NSData(bytes: bytes, length: bytes.count)
}
}
Not the most efficient because I have to create the intermediate [UInt8] array, but I decided that a simple way to convert didn't exist, that I'd have to do things with as conversions or memcpy and friends.
I'm not sure which of the two below to mark as an answer, since both influenced what I ended up with.
There are two ugly/simple solutions:
To assign each member of the tuple separately:
var offset = ...
packet.payload = (source[offset++], source[offset++], ... , source[offset++])
To just copy the raw memory (recommended)
var values = Array(source[offset..<limit])
memcpy(&packet.payload, &values, sentenceSize)
Note that it's possible to create an array from a tuple:
func tupleToArray<T>(tuple: Any, t: T.Type) -> [T] {
return Mirror(reflecting: tuple).children.flatMap{ $0.value as? T }
}
tupleToArray((1, 2, 3, 4, 5), t: Int.self) // [1, 2, 3, 4, 5]
But the other way around doesn't work, as Swift's reflection is read-only.
Another much more complicated but more beautiful solution would be to use Dependent Types, which enables you to have arrays with compile-time known length. Check out this great blog post, in which he also mentions this post on the Apple Developer forums which is basically what you'd need:
let vector = 3.0 ⋮ 4.0 ⋮ 5.0 // [3.0, 4.0, 5.0]
vector[1] // 4.0
vector.count // 3
sizeofValue(vector) // 3 * 8 ( same size as a tuple with 3 elements)
First of all don't use tuples to create contiguous arrays of memory. Go ahead and use the [UInt8] type. I would recommend using a stride function to create your indices for you like this. You will have to handle the case of your data source not being a multiple of the Packet payload size.
struct Packet {
var control1: UInt8 = 0
var control2: UInt8 = 0
static let size = 16
var payload = [UInt8].init(count: Packet.size, repeatedValue: 0)
init(control1: UInt8, control2: UInt8) {
self.control1 = control1
self.control2 = control2
}
}
// random values between 0...255
let blob = (0..<(Packet.size * 3)).map{_ in UInt8(arc4random_uniform(UInt32(UInt8.max)))}
for index in 0.stride(through: blob.count - 1, by: Packet.size) {
var packet = Packet(control1: 4, control2: 5)
packet.payload[0..<Packet.size] = blob[index..<index + Packet.size]
print(packet.payload)
}
As far as the cannot subscript error, I encountered that too. I suspect that this has changed recently. I was able to eliminate the error by matching the packet indice slice with the data source slice.
UPDATE
A commenter correctly pointed out that Packet structure contained a reference to an Array and therefore did not meet the OP's need. While I was focused more on iterating through a large data source using stride, here is an alternative using an untyped [UInt8] for such a simple data structure.
// payload size in count of UInt8
let size = 16
// field offsets
let control1 = 0
let control2 = 1
let payload = 2..<(2 + size)
// random values between 0...255
let blob = (0..<size * 3).map{_ in UInt8(arc4random_uniform(UInt32(UInt8.max)))}
for index in 0.stride(through: blob.count - 1, by: size) {
var buffer = [UInt8](count: 2 + size, repeatedValue: 0)
buffer[control1] = 255
buffer[control2] = 0
buffer[payload] = blob[index..<index + size]
let data = NSData(bytesNoCopy: &buffer, length: buffer.count, freeWhenDone: false)
// send data
}
Related
I'm studying app development with swift and metal kit
I want to convert an int type array to Metalbuffer, because I have to cover that array data in Shader.h
Before operating the shader.h, the array's values have not problems.
But, in and after the shader.h, the buffer's that converted from array have some problems.
for example, I send [1, 2, 3, 4] to Metalbuffer and I call the renderEncoder.setVertexBuffer(Metalbuffer).
then, In the Shader.h it appears like [1, 0, 2, 0]
what's the problem???
this is my sample code
var int_array = Array(reapeating: 1, count: 100)
init(){
Buffers = MetalBuffer<Int>(device: device, array: int_array, index: kBuffers.rawValue, options: [])
}
func updateIntArray(){
for i in 0..<100 {
int_array[i] = i % 20
}
Buffers = MetalBuffer<Int>(device: device, array: int_array, index: kBuffers.rawValue, options: [])
(other codes about rendering)
renderEncoder.setVertexBuffer(Buffers)
}
the updateIntArray is called per every frame by view controller.
I printed the sizeof(Buffers[i]) after the shader call, it was 4!
How can I preserve the data without being inserted with 0?
Thank you!
There's a lot of code missing from your question, but I will show you how I pass an array as a parameter to my fragment shader.
I have a ShaderTypes.h file to create a custom value type that can be used with both Swift and MSL (Metal Shading Language). You will need a bridging header for this if you want to use it on the Swift side.
#ifndef ShaderTypes_h
#define ShaderTypes_h
#include <simd/simd.h>
typedef struct {
float intensity;
float time;
float width;
float height;
float originX;
float originY;
}KeyPointValues;
My goal is to pass an array of KeyPointValues as a parameter to my fragment shader. On the swift side I do something like this:
var keyPoints = [KeyPointValues()]
for index in 0...10 {
keyPoints.append(KeyPointValues())
}
var count = keyPoints.count
guard let keyPointBuffer = context.device.makeBuffer(bytes: keyPoints, length: MemoryLayout<KeyPointValues>.stride * count) else { return }
commandEncoder.setFragmentBuffer(keyPointBuffer, offset: 0, index: 0)
commandEncoder.setFragmentBytes(&count, length: MemoryLayout.size(ofValue: count), index: 1)
You need to pass in count as well because there is no keyPoints.count counterpart in MSL.
Then the fragment shader looks something like this:
#include <metal_stdlib>
#include <simd/simd.h>
#import "ShaderTypes.h"
using namespace metal;
fragment half4 rosyFilter(device const KeyPointValues *keyPoints [[buffer(0)]],
constant uint &count [[ buffer(1) ]]
) {
for(uint index = 0; index < count; index++) {
float intensity = keyPoints[index].intensity;
}
}
Hopefully this can get you started in the right direction. You are using an array of Int, so it should be easier as there is probably no need to define a custom struct to use between Swift and MSL.
I am also somewhat new to Metal so I'm not sure if this is the best way of doing things. I appreciate any feedback from people with more experience.
I have a block that is passing data in that I'd like to convert to an array of array of floats -- e.g. [[0.1,0.2,0.3, 1.0], [0.3, 0.4, 0.5, 1.0], [0.5, 0.6, 0.7, 1.0]]. This data is passed to me in the form of data:UnsafeMutablePointer<UnsafeMutableRawPointer> (The inner arrays are RGBA values)
fwiw -- the block parameters are from SCNParticleEventBlock
How can I dereference data into a [[Float]]? Once I have the array containing the inner arrays, I can reference the inner array (colorArray) data with:
let rgba: UnsafeMutablePointer<Float> = UnsafeMutablePointer(mutating: colorArray)
let count = 4
for i in 0..<count {
print((rgba+i).pointee)
}
fwiw -- this is Apple's example Objective-C code for referencing the data (from SCNParticleSystem handle(_:forProperties:handler:) )
[system handleEvent:SCNParticleEventBirth
forProperties:#[SCNParticlePropertyColor]
withBlock:^(void **data, size_t *dataStride, uint32_t *indices , NSInteger count) {
for (NSInteger i = 0; i < count; ++i) {
float *color = (float *)((char *)data[0] + dataStride[0] * i);
if (rand() & 0x1) { // Switch the green and red color components.
color[0] = color[1];
color[1] = 0;
}
}
}];
You can actually subscript the typed UnsafeMutablePointer without having to create an UnsafeMutableBufferPointer, as in:
let colorsPointer:UnsafeMutableRawPointer = data[0] + dataStride[0] * i
let rgbaBuffer = colorsPointer.bindMemory(to: Float.self, capacity: dataStride[0])
if(arc4random_uniform(2) == 1) {
rgbaBuffer[0] = rgbaBuffer[1]
rgbaBuffer[1] = 0
}
Were you ever able to get your solution to work? It appears only a handful of SCNParticleProperties can be used within an SCNParticleEventBlock block.
Based on this answer, I've written the particle system handler function in swift as:
ps.handle(SCNParticleEvent.birth, forProperties [SCNParticleSystem.ParticleProperty.color]) {
(data:UnsafeMutablePointer<UnsafeMutableRawPointer>, dataStride:UnsafeMutablePointer<Int>, indicies:UnsafeMutablePointer<UInt32>?, count:Int) in
for i in 0..<count {
// get an UnsafeMutableRawPointer to the i-th rgba element in the data
let colorsPointer:UnsafeMutableRawPointer = data[0] + dataStride[0] * i
// convert the UnsafeMutableRawPointer to a typed pointer by binding it to a type:
let floatPtr = colorsPointer.bindMemory(to: Float.self, capacity: dataStride[0])
// convert that to a an UnsafeMutableBufferPointer
var rgbaBuffer = UnsafeMutableBufferPointer(start: floatPtr, count: dataStride[0])
// At this point, I could convert the buffer to an Array, but doing so copies the data into the array and any changes made in the array are not reflected in the original data. UnsafeMutableBufferPointer are subscriptable, nice.
//var rgbaArray = Array(rgbaBuffer)
// about half the time, mess with the red and green components
if(arc4random_uniform(2) == 1) {
rgbaBuffer[0] = rgbaBuffer[1]
rgbaBuffer[1] = 0
}
}
}
I'm really not certain if this is the most direct way to go about this and seems rather cumbersome compared to the objective-C code (see above question). I'm certainly open to other solutions and/or comments on this solution.
I've actually googled this extensively, within stackoverflow and elsewhere.
Most questions are about [UInt8] to String or [UInt8] to type_a (not array).
To clarify, I'd like to take an array of type_a. Get its pointer and tell swift to treat the next n iterations of type_b (size_of) as array of type_b.
I've tried variations of https://stackoverflow.com/a/26954091/5276890 which didn't work. A comment there led me to https://stackoverflow.com/a/42255468/5276890.
withMemoryRebound seems like the right way but I couldn't find the right invocation.
Here's a sample code of what I'm doing instead to convert [UInt8] to [UInt32.bigEndian], both to clarify and in case it's useful (not likely)
var intData = [UInt32]()
let M = UInt32(256*256*256)
var m = M
var bigE:UInt32 = 0
for i in 0..<data.count {
bigE += UInt32(data[i]) * m
if m == 1 {
intData.append(bigE)
bigE = 0
m = M
} else {
m = m/256
}
}
<disclaimer+rant>
I have to admit I never could figure out the whole closures+withUnsafe* syntax and mostly used patterns online and modified them. I'd spend the time learning this, just as soon as the language authors decide and settle down on one specific syntax :(
</disclaimer+rant>
Use withUnsafeBufferPointer to get a pointer to the element
storage of the source array.
Use withMemoryRebound to "reinterpret" that pointer as pointing
to elements of the target type.
Use Array(UnsafeBufferPointer(...) to create an array of the
target type.
Example:
let source: [UInt16] = [1, 2, 3, 4]
let dest = source.withUnsafeBufferPointer {
$0.baseAddress!.withMemoryRebound(to: UInt32.self, capacity: 2) {
Array(UnsafeBufferPointer(start: $0, count: 2))
}
}
print(dest) // [131073, 262147]
Or as a generic function:
func convertArray<S, T>(_ source: [S], to: T.Type) -> [T] {
let count = source.count * MemoryLayout<S>.stride/MemoryLayout<T>.stride
return source.withUnsafeBufferPointer {
$0.baseAddress!.withMemoryRebound(to: T.self, capacity: count) {
Array(UnsafeBufferPointer(start: $0, count: count))
}
}
}
Example:
let source: [UInt16] = [1, 2, 3, 4]
let dest = convertArray(source, to: UInt32.self)
print(dest) // [131073, 262147]
If you only need a (temporary) view on the array storage interpreted
in another type then you can avoid the Array creation
and use the UnsafeBufferPointer (which is a Collection and
has array-like methods) without copying the data:
source.withUnsafeBufferPointer {
$0.baseAddress!.withMemoryRebound(to: UInt32.self, capacity: 2) {
let u32bufptr = UnsafeBufferPointer(start: $0, count: 2)
// ... Operate on u32bufptr ...
for elem in u32bufptr { print(elem) }
}
}
Im trying to create Data mask function.
I found two ways:
using data subscripts
very slow
creating array from data, change it and then convert it back
~70 times faster
uses 2 times more memory
Why Data subscripting is so slow?
Is there a better way to get/set uint8 at index without duplicating memory?
here is my test:
var data = Data(bytes: [UInt8](repeating: 123, count: 100_000_000))
let a = CFAbsoluteTimeGetCurrent()
// data masking
for i in 0..<data.count {
data[i] = data[i] &+ 1
}
let b = CFAbsoluteTimeGetCurrent()
// creating array
var bytes = data.withUnsafeBytes {
[UInt8](UnsafeBufferPointer(start: $0, count: data.count))
}
for i in 0..<bytes.count {
bytes[i] = bytes[i] &+ 1
}
data = Data(bytes: bytes)
let c = CFAbsoluteTimeGetCurrent()
print(b-a) // 8.8887130022049
print(c-b) // 0.12415999174118
I cannot tell you exactly why the first method (via subscripting the Data value) is so slow. According to Instruments, a lot of time
is spend in objc_msgSend, when calling methods on the
underlying NSMutableData object.
But you can mutate the bytes without copying the
data to an array:
data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
for i in 0..<data.count {
bytes[i] = bytes[i] &+ 1
}
}
which is even faster than your "copy to array" method.
On a MacBook I got the following results:
Data subscripting: 7.15 sec
Copy to array and back: 0.238 sec
withUnsafeMutableBytes: 0.0659 sec
Below is my Swift code so far. Unfortunately the array onlineDspys doesn't get updated by CGGetOnlineDisplayList. Help appreciated.
main.swift
import Foundation
import CoreGraphics
var mainID = CGMainDisplayID()
println("ID is \(mainID)")
var dErr: CGError
let maxDisplays: UInt32 = 16
var onlineDspys: [CGDirectDisplayID] = [mainID]
var dspyCnt: UInt32 = 0
dErr = CGGetOnlineDisplayList(maxDisplays, &onlineDspys, &dspyCnt)
println("dspyCnt is \(dspyCnt)")
for currentDisplay in onlineDspys {
println("currentDisplay is \(currentDisplay)")
println("CGDisplayPixelsHigh(currentDisplay) is \(CGDisplayPixelsHigh(currentDisplay))")
println("CGDisplayPixelsWide(currentDisplay) is \(CGDisplayPixelsWide(currentDisplay))")
}
There are a couple issues here that are misleading you into believing that your onlineDspys array is not being updated.
The first problem is that you're actually pre-populating the first index of that array with the exact value that the CGGetOnlineDisplayList method call would set it to. If you instead prepopulated it with a value like 0, you'd see it's filling it in:
var onlineDspys: [CGDirectDisplayID] = [0]
The second problem is that CGGetOnlineDisplayList won't allocate memory for us. It can't call append on our array. We have to give it a block of memory to fill in the values. So, when we set up the variables we're going to pass in to the the function, we need to set them up more like this:
let maxDisplays: UInt32 = 16
var onlineDisplays = [CGDirectDisplayID](count: Int(maxDisplays), repeatedValue: 0)
var displayCount: UInt32 = 0
Now, our onlineDisplays is an array full of 16 zeros for CGGetOnlineDisplayList to fill in.
We call the function:
let dErr = CGGetOnlineDisplayList(maxDisplays, & onlineDisplays, & displayCount)
Now, the tricky bit... we don't want to waste time iterating over the parts of the array that didn't get filled in, so we make use of what the function filled into the displayCount variable:
for currentDisplay in onlineDisplays[0..<Int(displayCount)] {
print("currentDisplay is \(currentDisplay)")
print("CGDisplayPixelsHigh(currentDisplay) is \(CGDisplayPixelsHigh(currentDisplay))")
print("CGDisplayPixelsWide(currentDisplay) is \(CGDisplayPixelsWide(currentDisplay))")
}
The whole code snippet all together:
import Foundation
import CoreGraphics
var mainID = CGMainDisplayID()
print("ID is \(mainID)")
let maxDisplays: UInt32 = 16
var onlineDisplays = [CGDirectDisplayID](count: Int(maxDisplays), repeatedValue: 0)
var displayCount: UInt32 = 0
let dErr = CGGetOnlineDisplayList(maxDisplays, &onlineDisplays, &displayCount)
print("dspyCnt is \(displayCount)")
for currentDisplay in onlineDisplays[0..<Int(displayCount)] {
print("currentDisplay is \(currentDisplay)")
print("CGDisplayPixelsHigh(currentDisplay) is \(CGDisplayPixelsHigh(currentDisplay))")
print("CGDisplayPixelsWide(currentDisplay) is \(CGDisplayPixelsWide(currentDisplay))")
}