The current state is not optimal for new users, because it introduces boilerplate in many areas.
if (ball_y <= Float::from(ball.radius) && ball.velocity < 0.0) || (ball_y >= Float::from(ARENA_HEIGHT - ball.radius) && ball.velocity > 0.0) ... let paddle_x = paddle_transform.translation().x - (paddle.width * 0.5).into(); let paddle_y = paddle_transform.translation().y - (paddle.height * 0.5).into();
let did_hit = if ball_x <= Float::from(ball.radius)
let did_hit = if ball_x.as_f32() <= ball.radius
A partial solution
Most of the weird in-calculation conversions can be avoided if the underlying data is implemented as
Float instead of
f32. In my opinion, that should be done for everything that is related to transforms. In the pong example that would be the ball radius, the paddle dimensions and the ball velocity.
However, we are still left with the usage of constants. Especially while prototyping it might be annoying to convert constants to
Float just so that we can multiply values together. Another problem that still exists is
Time::delta_seconds. Maybe it makes sense to provide
First some assumptions, so that we are on the same page.
Before discussing the proposed solution, please verify that we are holding the same assumptions. If that is not the case, please discuss the assumptions before discussing the solution.
- The game developer is only concerned about either using the
float64or not using it,
f64Transform support is exotic and not something most of the userbase is concerned with.
- In general, friction for users should be as low as possible.
- Users needing the
f64transform should be aware of floating precision arithmetic.
- Users not needing the
f64don’t have to be aware, at their own peril.
f32is preferable to
f64, due to memory and performance considerations, if precision doesn’t matter.
- One compilation should be sufficient to verify the correctness of the code.
Extend the traits on
Float so that they also take
PartialEq<Float> for f32
PartialOrd<Float> for f32
AbsDiffEq<Float> for f32
UlpsEq<Float> for f32
RelativeEq<Float> for f32
Add<Float> for f32
Sub<Float> for f32
Mul<Float> for f32
Div<Float> for f32
Rem<Float> for f32
AddAssign<Float> for f32
SubAssign<Float> for f32
MulAssign<Float> for f32
DivAssign<Float> for f32
RemAssign<Float> for f32
For normal users, those will be a noop, for
float64 users, the
f32 will be converted to
f64 before applying the operation.
This will enable
!float64 users to mostly ignore
Float. They can write
let did_hit = if ball_x <= ball.radius again without bothering about floating point precision.
If it compiles with the
float64 flag, it also compiles without it. I’m not sure if the other way around holds too, as there might be some weird corner cases…
- What is the type of
translate().x + na::zero()? The compiler doesn’t know, so you need to specify it (as either
let a: f32 = f32::MAX; // Result of a computation translation().x += a * 2;
float64, this should be a valid operation, however, the intermediate result
a * 2 is converted to
In my opinion this indicates carelessness of the
float64 user, as they should always be aware of overflow when working with
f32 in a
Floats be used in prefabs?
- Are there any other problems/concerns?
- Does it make sense to also implement the traits for
- I’m new to Rust so maybe I’m missing something obvious, sorry if that is the case so far and you read through all of this