In trying to serialize an array of float3 objects with the basic JSONEncoder, it's revealed that float3 does not conform to the Codable protocol, so this cannot be done.
I tried to write a basic extension as suggested in Encoding and Decoding Custom Types as seen below, but the error 'self' used before all stored properties are initialized
is rendered for each of the assignment lines in the init. I assume this is because the compiler is not sure that Float.self
is defined prior to the initialization of float3, but I'm not sure how to resolve this.
Further, the end of the init says Return from initializer without initializing all stored properties
which I assume means there are float3 properties in addition to x, y, and z, but I'm wondering if there is a way to default/ignore these, and/or how to find the full list of properties aside from digging through all the float3 extensions in simd.
If you have any thoughts about how to go about doing this, sharing them would be much appreciated. Thanks!
import SceneKit
extension float3: Codable {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
x = try values.decode(Float.self, forKey: .x)
y = try values.decode(Float.self, forKey: .y)
z = try values.decode(Float.self, forKey: .z)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(x, forKey: .x)
try container.encode(y, forKey: .y)
try container.encode(z, forKey: .z)
}
enum CodingKeys: String, CodingKey {
case x
case y
case z
}
}
Here is the (I think complete) float3 definitions from the simd file:
/// A vector of three `Float`. This corresponds to the C and
/// Obj-C type `vector_float3` and the C++ type `simd::float3`.
public struct float3 {
public var x: Float
public var y: Float
public var z: Float
/// Initialize to the zero vector.
public init()
/// Initialize a vector with the specified elements.
public init(_ x: Float, _ y: Float, _ z: Float)
/// Initialize a vector with the specified elements.
public init(x: Float, y: Float, z: Float)
/// Initialize to a vector with all elements equal to `scalar`.
public init(_ scalar: Float)
/// Initialize to a vector with elements taken from `array`.
///
/// - Precondition: `array` must have exactly three elements.
public init(_ array: [Float])
/// Access individual elements of the vector via subscript.
public subscript(index: Int) -> Float
}
extension float3 : Equatable {
/// True iff every element of lhs is equal to the corresponding element of
/// rhs.
public static func ==(lhs: float3, rhs: float3) -> Bool
}
extension float3 : CustomDebugStringConvertible {
/// Debug string representation
public var debugDescription: String { get }
}
extension float3 : ExpressibleByArrayLiteral {
/// Initialize using `arrayLiteral`.
///
/// - Precondition: the array literal must exactly three
/// elements.
public init(arrayLiteral elements: Float...)
}
extension float3 : Collection {
/// The position of the first element in a nonempty collection.
///
/// If the collection is empty, `startIndex` is equal to `endIndex`.
public var startIndex: Int { get }
/// The collection's "past the end" position---that is, the position one
/// greater than the last valid subscript argument.
///
/// When you need a range that includes the last element of a collection, use
/// the half-open range operator (`..<`) with `endIndex`. The `..<` operator
/// creates a range that doesn't include the upper bound, so it's always
/// safe to use with `endIndex`. For example:
///
/// let numbers = [10, 20, 30, 40, 50]
/// if let index = numbers.index(of: 30) {
/// print(numbers[index ..< numbers.endIndex])
/// }
/// // Prints "[30, 40, 50]"
///
/// If the collection is empty, `endIndex` is equal to `startIndex`.
public var endIndex: Int { get }
/// Returns the position immediately after the given index.
///
/// The successor of an index must be well defined. For an index `i` into a
/// collection `c`, calling `c.index(after: i)` returns the same index every
/// time.
///
/// - Parameter i: A valid index of the collection. `i` must be less than
/// `endIndex`.
/// - Returns: The index value immediately after `i`.
public func index(after i: Int) -> Int
}
extension float3 {
/// Vector (elementwise) sum of `lhs` and `rhs`.
public static func +(lhs: float3, rhs: float3) -> float3
/// Vector (elementwise) difference of `lhs` and `rhs`.
public static func -(lhs: float3, rhs: float3) -> float3
/// Negation of `rhs`.
prefix public static func -(rhs: float3) -> float3
/// Elementwise product of `lhs` and `rhs` (A.k.a. the Hadamard or Schur
/// vector product).
public static func *(lhs: float3, rhs: float3) -> float3
/// Scalar-Vector product.
public static func *(lhs: Float, rhs: float3) -> float3
/// Scalar-Vector product.
public static func *(lhs: float3, rhs: Float) -> float3
/// Elementwise quotient of `lhs` and `rhs`.
public static func /(lhs: float3, rhs: float3) -> float3
/// Divide vector by scalar.
public static func /(lhs: float3, rhs: Float) -> float3
/// Add `rhs` to `lhs`.
public static func +=(lhs: inout float3, rhs: float3)
/// Subtract `rhs` from `lhs`.
public static func -=(lhs: inout float3, rhs: float3)
/// Multiply `lhs` by `rhs` (elementwise).
public static func *=(lhs: inout float3, rhs: float3)
/// Divide `lhs` by `rhs` (elementwise).
public static func /=(lhs: inout float3, rhs: float3)
/// Scales `lhs` by `rhs`.
public static func *=(lhs: inout float3, rhs: Float)
/// Scales `lhs` by `1/rhs`.
public static func /=(lhs: inout float3, rhs: Float)
}
You can solve the compiler error by instead of trying to directly assign the decoded values to the fields of your type, storing the decoded values in local variables, then calling a designated initializer of float3
.
As Rob mentions in his answer, the cause of the issue has to do with x
, y
and z
being computed properties rather than stored ones, so they cannot be directly written during initialization.
extension float3: Codable {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let x = try values.decode(Float.self, forKey: .x)
let y = try values.decode(Float.self, forKey: .y)
let z = try values.decode(Float.self, forKey: .z)
self.init(x, y, z)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(x, forKey: .x)
try container.encode(y, forKey: .y)
try container.encode(z, forKey: .z)
}
private enum CodingKeys: String, CodingKey {
case x,y,z
}
}
You can test both encoding and decoding in a Playground using below code:
let vector = float3(3, 2.4, 1)
do {
let encodedVector = try JSONEncoder().encode(vector)
let jsonVector = String(data: encodedVector, encoding: .utf8) //"{"x":3,"y":2.4000000953674316,"z":1}"
let decodedVector = try JSONDecoder().decode(float3.self, from: encodedVector) //float3(3.0, 2.4, 1.0)
} catch {
print(error)
}
If you prefer more concise code, the init(from decoder:)
method can be shortened to:
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
try self.init(values.decode(Float.self, forKey: .x), values.decode(Float.self, forKey: .y), values.decode(Float.self, forKey: .z))
}
I think that it is worth mentioning that you can simply use an unkeyed container to encode/decode your float3 properties as an array:
extension float3: Codable {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
try self.init(container.decode([Float].self))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode([x,y,z])
}
}
Dávid is correct on how to fix the problem, but it's worth understanding why it's a problem (it's not actually designated vs convenience initializers; that only applies to classes).
If we created our own version of float3
, your code would work fine with an extension:
struct float3 {
var x: Float
var y: Float
var z: Float
}
extension float3: Codable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
x = try values.decode(Float.self, forKey: .x)
y = try values.decode(Float.self, forKey: .y)
z = try values.decode(Float.self, forKey: .z)
}
// ...
}
So that seems strange. Why doesn't our implementation of the simd
type work the same as the simd
type? Because simd
doesn't have an x
stored property (or y
or z
). Those are all computed properties.
The real interface is defined in simd.swift.gyb
. If you study that code, you'll see all the SIMD vector types use a generic _vector
storage:
public var _vector: Builtin.${llvm_vectype}
And then there are computed properties defined for each letter (component
is ['x','y','z','w']
):
% for i in xrange(count):
public var ${component[i]} : ${scalar} {
@_transparent
get {
let elt = Builtin.${extractelement}(_vector,
(${i} as Int32)._value)
return ${scalar}(_bits: elt)
}
@_transparent
set {
_vector = Builtin.${insertelement}(_vector,
newValue._value,
(${i} as Int32)._value)
}
}
% end
So if we were building our own float3
(without fancy builtins), it'd be something like this:
struct float3 {
private var vector: [Float]
var x: Float { get { return vector[0] } set { vector[0] = newValue } }
var y: Float { get { return vector[1] } set { vector[1] = newValue } }
var z: Float { get { return vector[2] } set { vector[2] = newValue } }
init(x: Float, y: Float, z: Float) {
vector = [x, y, z]
}
}
And if you write your extension against that, you'll get the same error.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With