-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Swift Covers Native Methods for Mathy Godot Types, Add Testing Framework #628
base: main
Are you sure you want to change the base?
Conversation
…otTests too This lets the tests for Swift cover implementations be self-updating when using Godot engine implementations.
…use it everywhere
This will let me implement TESTABLE_SWIFT_COVERS.
seems to be slow to compute ulps sometimes
Thank you so much, this is wonderful! I have one question about the covers, is there a reason to use covers and the system that goes with it injecting source code, rather than writing Swift extensions for those types? We could have the generator have a list of "Do not generate this code". |
I believe that is ultimately where SwiftGodot should end up. The design we implemented here is a step in that direction. If we hand-write implementations for the geometric types, we need a way to test that those implementations are correct. What we've done here is treat the Godot engine as a test oracle. We can guarantee that we match its behavior as precisely as we want. We generate a bunch of random inputs for the tests, including weird edge cases with coordinate values like infinity, NaN, On the other hand, that does involve a bunch of infrastructure that we would ultimately want to discard, so it's entirely reasonable to keep this work in a separate fork where we can write and prove the Swift implementations, and them just copy them over to the main project without the test infrastructure. |
I see, I see. I love the direction then, nothing else to add at this point, and I appreciate the strong focus on testing and testing this. |
Overview
We have been working on a general solution to issue #509 (Optimization: Move more vector operations to Swift). Although it’s not finished, we want to share our experiments to get some feedback.
We had two main goals:
.swift
files, not inside string literals embedded inGenerator
’s source code.This draft PR shows our ideas for implementing these two goals. It’s not intended for merging; there are failing tests and some incorrect implementations. It does show how our ideas let us easily write cover implementations and corresponding tests for all of the built-in geometric types, including vectors, transforms, basis, quaternion, plane, and the math utilities in GD.
Changes
Generator
reads the Swift source files inSources/SwiftCovers
and extracts method bodies from anyextension
s found in those files. Each method body is keyed by the extended type’s name and the name and type signature of the method. For example:Vector3, Transform3D,
etc.),Generator
looks for an extracted method body with a matching key. If it finds one, it emits that method body as an alternative implementation of the method. This replaces the existing use ofcustomBuiltinMethodImplementations
andcustomBuiltinMethodImplementations
. So the example above becomes the following:TESTABLE_SWIFT_COVERS
(set inPackage.swift
),Generator
replaces the use of#if CUSTOM_BUILTIN_IMPLEMENTATIONS
in the emitted methods with a normal if statement that checks a runtime-settable flag to decide whether to use the cover implementation or to call through to the engine.Each Swift cover method draws its implementation details from the Godot Engine code and the
godot-cpp
generated codebase.. Where helper methods for an extension are needed, they’ve been placed inSwiftCoverSupport.swift
, including these:Each file in
SwiftCovers
has an associated test file inTests/SwiftGodotTests/BuiltIn
. For each method that has a Swift cover, we generate (pseudo-)random inputs to feed to the method. We then call the method twice, once with theuseSwiftCovers
runtime flag set (to use the cover implementation) and again with the flag unset (to use the engine implementation). We then compare the outputs (with some tunable fuzziness for floating-point comparisons) to verify that each cover closely matches the engine.We use a little combinator system named
TinyGen
(inTinyGen.swift
) to generate the random inputs for testing.TinyGen
is modeled on property-based testing frameworks likeQuickCheck
andHedgehog
, but without shrinking.We use
@TaskLocal
properties foruseSwiftCovers
and for the floating-point fuzziness parametersFloat.closeEnoughUlps
andDouble.closeEnoughUlps
, because@TaskLocal
lets us set up a global variable with an easy way to temporarily and dynamically override its value.