Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to extend float3 or any other built-in type to conform to the Codable protocol?

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)
}
like image 317
Laser Avatar asked Jan 29 '18 01:01

Laser


3 Answers

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))
}
like image 119
Dávid Pásztor Avatar answered Oct 13 '22 23:10

Dávid Pásztor


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])
    }
}
like image 3
Leo Dabus Avatar answered Oct 13 '22 21:10

Leo Dabus


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.

like image 4
Rob Napier Avatar answered Oct 13 '22 23:10

Rob Napier