タイプセーフを使用してAPIを改善する他の方法

みなさんこんにちは! 私は主任開発者cocos2d-objcです。 現在、Swiftへの移植を進めています。 開発プロセスをカバーし、建築ソリューションなどについて話し合う予定です。 これまでのところ、プロジェクトはまだ概念実証段階にあるため、今日は小さなトリックについてのみお話します。これは、数学ライブラリを少し良くしたと思います。 興味があれば-猫の下でお願いします。



画像






Swiftでエンジンの書き換えを開始する前に、数学的なニーズに対応する最新のライブラリの必要性が明らかになりました。 エンジンはもともとxプラットフォームを念頭に置いていたため、CG *タイプを使用できず、CoreGraphics APIは私たちにとって十分な速さではありません。 既存のソリューションは私たちを満足させるものではなかったので、特定の禁欲主義を守りながらバイクを書くことにしました。



Vector2f、Vector3f、Vector4f、Matrix3f、Matrix4f、Rectという控えめなタイプのセットに限定しました。 ARCのオーバーヘッドを完全に除外し、 SIMDを完全にサポートすることを強く決定しました(少なくともDarwinプラットフォームでは、 Glibcの場合 、simdが公開されるまでアルゴリズムは手動で記述されます)。このため、放棄する必要がありました。ジェネリックを使用し、 Doubleサポートなしで、ライブラリ全体をFloat型に結び付けます。



わかりました、あなたは興味がないことに気づきました、これは序論です。 についての記事は何ですか?



ある時点で、コーナーで機能するAPI(回転行列の作成方法など)を改善する必要があることに気付きました。 私たちにとって重要だと思われた問題は、メソッドが受け取ることを期待しているドキュメント(ラジアンまたは度)についてユーザーがドキュメントを見る必要性を取り除くことでした。



最初は、次の形式のFloatでエイリアスを作成したいと考えていました。



typealias Radians = Float; typealias Degrees = Float;
      
      





これがあまり節約にならないことは明らかです。なぜなら、 すべて同じですが、間違った値の値をメソッドに渡す可能性があります。



また、目的の値を返すrad()およびdeg()関数の作成も検討しました。 拡張機能IntおよびFloatのオプション、およびその形式のリテラル



 45.degrees 180.radians
      
      





気に入らなかった。 できること:



 180.radians.radians
      
      





その結果、角度タイプ用に別の構造を作成することが決定されました。



 /// A floating point value that represents an angle public struct Angle { /// The value of the angle in degrees public let degrees: Float /// The value of the angle in radians public var radians: Float { return degrees * Float.pi / 180.0 } /// Creates an instance using the value in radians @inline(__always) public init(radians val: Float) { degrees = val / Float.pi * 180.0 } /// Creates an instance using the value in degrees @inline(__always) public init(degrees val: Float) { degrees = val } @inline(__always) internal init(_ val: Float) { degrees = val } }
      
      





それはどうですか? これで、メソッドが角度をパラメーターとして期待していることをユーザーに伝えることができ、ユーザーはラジアン単位または度単位のどちらの形式でも心配する必要はありません。

彼がアングル構造を通過した場合、彼はすべてが正しく機能することを確信しています。



Angleを操作するためのすべての標準演算子を定義しました(スカラー値と同じです。Angle/ AngleのみがAngleではなくFloatを返しますが、Angle * Angleはまったくありません)



また、Intの拡張機能を残すことにしました。



 extension Int { /// Returns the integer value as an angle in degrees public var degrees: Angle { return Angle(degrees: Float(self)) } }
      
      





したがって、角度は度単位で操作し、必要のない場合は精度を損なうことなく、必要な場合にのみ(通常は最終計算中に)ラジアンに変換します。

精度をさらに確保するために、次のようにAngleのsinとcosを定義しました。



 @inline(__always) internal func sinf(_ a: Angle) -> Float { return __sinpif(a.degrees / 180.0) } @inline(__always) internal func cosf(_ a: Angle) -> Float { return __cospif(a.degrees / 180.0) }
      
      





確かに、Glibcの場合、これらの関数の通常の実装を記述する必要がありました。 精度を高める機能はありません。



最後に、コードでのUnicodeの使用は常に議論の余地のあるトピックです。 個人的には、これはまったく歓迎しません。 最初に、楽しみのために、この演算子を追加しました:



 /// The degree operator constructs an `Angle` from the specified floating point value in degrees /// /// - remark: /// * Degree operator is the unicode symbol U+00B0 DEGREE SIGN /// * macOS shortcut is ⌘+⇧+8 @inline(__always) public postfix func °(lhs: Float) -> Angle { return Angle(degrees: lhs) } /// Constructs an `Angle` from the specified `Int` value in degrees @inline(__always) public postfix func °(lhs: Int) -> Angle { return Angle(degrees: Float(lhs)) }
      
      





そして、定数は次のように定義されました。



  // MARK: Constants public static let zero = 0° public static let pi_6 = 30° public static let pi_4 = 45° public static let pi_3 = 60° public static let pi_2 = 90° public static let pi2_3 = 120° public static let pi = 180° public static let pi3_2 = 270° public static let pi2 = 360°
      
      





その結果、エンジン自体のコードでこの演算子を使用していることに気づきました 。読みやすさは向上しますが、乱用しないでください。誰もがショートカットを覚えておらず、時には腹立たしいこともあります。



記事は些細な解決策には大きすぎることが判明しましたが、あなたにとって興味深いものであったことを願っています。 この手法を適用して、値をやり取りする必要がある場合の問題を解決し、予想される値についてのドキュメントを読みました。



ここでエンジンの移植を監視できます

数学ライブラリへのリンク。



All Articles