Skip to content
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

Decide on the PartialEq issue for TimewarpComponent #2

Closed
RJ opened this issue Aug 30, 2023 · 2 comments
Closed

Decide on the PartialEq issue for TimewarpComponent #2

RJ opened this issue Aug 30, 2023 · 2 comments

Comments

@RJ
Copy link
Owner

RJ commented Aug 30, 2023

You register components for rollback like this:

app.register_component::<Position>()

and that is defined something like this:

fn register_component<T: Component>() { ... }

It would be useful to add + PartialEq to the trait bounds, because being able to compare components is useful for avoiding storing duplicates, detecting mispredictions after rollback, etc.

However, not all components from 3rd-party crates I want to rollback are PartialEq.

Easy enough to make my own components PartialEq, and the physics components are almost all
PartialEq, with the exception of Collider, which wraps up parry::SharedShape, see:

dimforge/parry#51

If i can get SharedShape to be PartialEq (and all the physics components) that would probably be my favourite option.

If it was possible to implement different versions of certain systems like this:

fn record_component_history<T: TimewarpComponent + PartialEq>(...) {
   // emit TimewarpCorrection only if oldval != newval
}
fn record_component_history<T: TimewarpComponent>(...) {
   // emit TimewarpCorrection regardless, make the game compare them
}

that would also be fine. Maybe possible with rust's min_specialization feature?

Another work-around would be to require PartialEq in the general case but allow registering non-partialeq components with a different function, on the basis they would always emit a TimewarpCorrection when resimulated. That would probably be a big mess of duplicated generic functions though, without specialization. Worth some investigation.

@RJ
Copy link
Owner Author

RJ commented Aug 30, 2023

on further inspection, there's a limited set of parry shapes that are actually used by Collider, so I just manually check everything, or delegate to PartialEq if it exists.

Jondolf/avian#139

ugly, but useful:

fn cmp_shared_shapes(this: &SharedShape, other: &SharedShape) -> bool {
    // shapes with different types can't be equal.
    if this.0.shape_type() != other.0.shape_type() {
        return false;
    }
    match this.as_typed_shape() {
        TypedShape::Ball(shape) => shape == other.as_ball().unwrap(),
        TypedShape::Cuboid(shape) => shape == other.as_cuboid().unwrap(),
        TypedShape::RoundCuboid(shape) => {
            shape.border_radius == other.as_round_cuboid().unwrap().border_radius
                && shape.inner_shape == other.as_round_cuboid().unwrap().inner_shape
        }
        TypedShape::Capsule(shape) => {
            shape.radius == other.as_capsule().unwrap().radius
                && shape.segment == other.as_capsule().unwrap().segment
        }
        TypedShape::Segment(shape) => shape == other.as_segment().unwrap(),
        TypedShape::Triangle(shape) => shape == other.as_triangle().unwrap(),
        TypedShape::RoundTriangle(shape) => {
            shape.border_radius == other.as_round_triangle().unwrap().border_radius
                && shape.inner_shape == other.as_round_triangle().unwrap().inner_shape
        }
        TypedShape::TriMesh(shape) => {
            shape.flat_indices() == other.as_trimesh().unwrap().flat_indices()
        }
        TypedShape::Polyline(shape) => {
            if shape.num_segments() != other.as_polyline().unwrap().num_segments() {
                return false;
            }
            for i in 0..shape.num_segments() as u32 {
                if shape.segment(i) != other.as_polyline().unwrap().segment(i) {
                    return false;
                }
            }
            true
        }
        TypedShape::HalfSpace(shape) => shape == other.as_halfspace().unwrap(),
        TypedShape::HeightField(_) => false,
        TypedShape::Compound(shape) => {
            let shapes = shape.shapes();
            let other_shapes = other.as_compound().unwrap().shapes();
            if shapes.len() != other_shapes.len() {
                return false;
            }
            for i in 0..shapes.len() {
                // isometries check
                if shapes[i].0 != other_shapes[i].0 {
                    return false;
                }
                // shapes check
                if !cmp_shared_shapes(&shapes[i].1, &other_shapes[i].1) {
                    return false;
                }
            }
            true
        }
        TypedShape::Custom(_) => false,
        #[cfg(feature = "3d")]
        TypedShape::ConvexPolyhedron(shape) => shape == other.as_convex_polyhedron().unwrap(),
        #[cfg(feature = "3d")]
        TypedShape::Cylinder(shape) => shape == other.as_cylinder().unwrap(),
        #[cfg(feature = "3d")]
        TypedShape::Cone(shape) => shape == other.as_cone().unwrap(),
        #[cfg(feature = "3d")]
        TypedShape::RoundCylinder(shape) => {
            shape.border_radius == other.as_round_cylinder().unwrap().border_radius
                && shape.inner_shape == other.as_round_cylinder().unwrap().inner_shape
        }
        #[cfg(feature = "3d")]
        TypedShape::RoundCone(shape) => {
            shape.border_radius == other.as_round_cone().unwrap().border_radius
                && shape.inner_shape == other.as_round_cone().unwrap().inner_shape
        }
        #[cfg(feature = "3d")]
        TypedShape::RoundConvexPolyhedron(shape) => {
            if shape.border_radius != other.as_round_convex_polyhedron().unwrap().border_radius {
                return false;
            }
            let points = shape.inner_shape.points();
            let other_points = other
                .as_round_convex_polyhedron()
                .unwrap()
                .inner_shape
                .points();
            // must have same number of points
            if points.len() != other_points.len() {
                return false;
            }
            // compare all points
            for i in 0..points.len() {
                if points[i] != other_points[i] {
                    return false;
                }
            }
            true
        }
        #[cfg(feature = "2d")]
        TypedShape::ConvexPolygon(shape) => {
            let points = shape.points();
            let other_points = other.as_convex_polygon().unwrap().points();
            if points.len() != other_points.len() {
                return false;
            }
            // compare all points
            for i in 0..points.len() {
                if points[i] != other_points[i] {
                    return false;
                }
            }
            true
        }
        #[cfg(feature = "2d")]
        TypedShape::RoundConvexPolygon(shape) => {
            if shape.border_radius != other.as_round_convex_polygon().unwrap().border_radius {
                return false;
            }
            let points = shape.inner_shape.points();
            let other_points = other
                .as_round_convex_polygon()
                .unwrap()
                .inner_shape
                .points();
            if points.len() != other_points.len() {
                return false;
            }
            // compare all points
            for i in 0..points.len() {
                if points[i] != other_points[i] {
                    return false;
                }
            }
            true
        }
    }
}

impl std::cmp::PartialEq for Collider {
    fn eq(&self, other: &Self) -> bool {
        cmp_shared_shapes(self, other)
    }
}

@RJ
Copy link
Owner Author

RJ commented Sep 7, 2023

closed because requiring partialeq.

@RJ RJ closed this as completed Sep 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant