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

[fontdrasil] Typed coordinate spaces #632

Merged
merged 2 commits into from
Dec 14, 2023
Merged

[fontdrasil] Typed coordinate spaces #632

merged 2 commits into from
Dec 14, 2023

Conversation

cmyr
Copy link
Member

@cmyr cmyr commented Dec 6, 2023

This started out as just an experiment, but I ended up fleshing it out to the point where it is mergeable, if we wish.

The basic idea is that instead of having separate types for each coordinate in each space, we actually use the space as a type parameter, and then have a single Coord type that is generic over that parameter. This means that we define all conversions in terms of the coordinate space, and then we can define methods on a single Coord type that are available everywhere. (We can also define methods that are specific to a coordinate in a particular space.)

So... I'm anticipating a lot of tell-me-whys here, and I fundamentally agree that this is not a hugely important change. The main advantages as I see them is that this version has slightly better ergonomics and is a bit more maintainable, while generating slightly less code. The downside is that it uses an advanced language feature, PhantomData.

Finally, if we decide this is not actually an improvement then I hope at least it is a useful illustration of an idiomatic approach to this problem.

edit:

Having played with this a bit more, I am more convinced this (or something in this vein) is the right design. The basic rationale is that doing things this way lets you make other code more generic; for instance it would now be very easy to make our PiecewiseLinearMap be generic over a pair of coordinate spaces, like:

struct PiecewiseLinearMap<FromSpace, ToSpace> {
    from: Vec<Coord<FromSpace>>,
    to: Vec<Coord<ToSpace>>,
}

and because we know that both types are Coords, we can know there is always some available set of methods. This would not work if we were using the current coord types and doing PiecewiseLinearMap<T, U> since we don't now anything about T and U in that case.

basically: The advantage of Coord is that it encapsulates all of the general behaviour of a coordinate, regardless of the coordinate space, and that is actually a useful distinction in some cases.

@cmyr cmyr force-pushed the add-sparse-dspace branch from 4eb0767 to 241f7e9 Compare December 6, 2023 18:39
@rsheeter
Copy link
Contributor

rsheeter commented Dec 6, 2023

At a glance I like it, will do an actual review later.

@cmyr
Copy link
Member Author

cmyr commented Dec 6, 2023

I've added a second commit here that impls PartialEq<f32> for the Coord type, which lets us write much simpler tests (but you still cannot compare a coord to another coord in a different space, so this doesn't undermine our typing)

@cmyr cmyr force-pushed the add-sparse-dspace branch from 227ba0a to 4e37c45 Compare December 7, 2023 00:05
Comment on lines 79 to 82
//TODO: Code review: this constructor can create invalid values,
//particularly in the case of NormalizedCoords. We could fix this
//a number of ways, for instance by clamping the space or else by making
//it fallible. Do we care? What solution would we prefer?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leave it like this, sometimes it is useful to be able to make a NormalizedCoord that exceeds -1 or +1 (but is still within the F2Dot14 bounds) e.g. restricting axis min/max duing VF partial instancing.
It's clear already what needs to be done when the NormalizedCoord is used; 0 is default by definition, -1 is min, +1 is max, anything beyond will never be accessed because instance input location must be clamped.

@anthrotype
Copy link
Member

looks neat though I am still unsure what the PhantomData is actually doing

@anthrotype
Copy link
Member

Oh I see. The generic struct Coord doesn't store a Space type but needs to behave differently based on it; so the zero-sized PhantomData here acts as a "type marker".
Still not clear why it isn't just PhantomData<Space>, I see you added a comment with a link, but i'm scared of the rustonomicon

@cmyr
Copy link
Member Author

cmyr commented Dec 7, 2023

So yea, PhantomData is a way of making a type behave as if it contains some type or lifetime without actually containing that type or lifetime, which is useful for things like this.

The reason you don't do PhantomData<T> (at least in this case) is that for that type the compiler behaves as if you actually contain the given type. In particular it means that the compiler will assume that the type may drop a value of T when it goes out of scope, which we don't want. We also want to make sure that coords can always be passed between threads, so I just looked at the chart in the nomincon and picked the type that offered covariance + send + sync. 🤷

@cmyr cmyr force-pushed the add-sparse-dspace branch from 4e37c45 to 02f535b Compare December 7, 2023 17:23
}
}
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish there wasn't a macro but I don't have a better idea.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative is just to write essentially the same code three times. Is your concern based on the insanity of macros in C? Rust's macros are much more constrained, and it makes it easier to refactor things in the future, since we'd only need to edit in one place.

cmyr added 2 commits December 14, 2023 14:18
This started out as just an experiment, but I ended up fleshing it out
to the point where it is mergeable, if we wish.

The basic motivation was seeing the duplication in methods defined on
the various `Coords`, and wanting to unify that.
This is just about making tests easier to read.
@cmyr cmyr force-pushed the add-sparse-dspace branch from 02f535b to 135a2ed Compare December 14, 2023 19:19
@cmyr cmyr added this pull request to the merge queue Dec 14, 2023
Merged via the queue into main with commit 8731d84 Dec 14, 2023
10 checks passed
@cmyr cmyr deleted the add-sparse-dspace branch December 14, 2023 19:59
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

Successfully merging this pull request may close these issues.

3 participants