Replies: 2 comments 1 reply
-
I think that this is probably out of scope for glam right now. It's not something I would have time to work on myself and I'm not sure I'd want to commit to maintaining it either although that would depend on what the feature looked like in reality. However, it seems like there isn't any reason this couldn't be experimented with in a separate crate. Or are there glam internals that this would need to access? I don't think I have a complete understanding of what this does right now so a separate proof of concept would help there as well. I'm guessing a generic version of glam wouldn't solve your problem as you still have the issue of needing to implement necessary traits on the newtype? Although that might be a simpler problem to solve. |
Beta Was this translation helpful? Give feedback.
-
Sorry for the long delay in my reply. I wrote a proof-of-concept implementation of the newtyping systen. Available in this repository is an implementation of the // Nothing from `typed-glam` needs to be imported here. These behave as if they truly were their own
// types.
pub use decl::{ChunkPos, WorldPos, WorldPosExt};
mod decl {
use typed_glam::{FlavorCastFrom, TypedVector, VecFlavor};
// `TypedVector` takes a dummy struct implementing `VecFlavor` (also called a "flavor") and produces
// a strongly typed wrapper around the flavor's `Backing` vector. Newtyping a vector takes 7 lines
// instead of just under 700.
pub type WorldPos = TypedVector<WorldPosFlavor>;
pub struct WorldPosFlavor;
impl VecFlavor for WorldPosFlavor {
type Backing = glam::Vec3;
}
pub type ChunkPos = TypedVector<ChunkPosFlavor>;
pub struct ChunkPosFlavor;
impl VecFlavor for ChunkPosFlavor {
type Backing = glam::Vec3;
}
// Users can still implement extension methods on their vector "newtypes."
pub trait WorldPosExt {
fn do_something(&self);
}
impl WorldPosExt for WorldPos {
fn do_something(&self) {
println!("Something! {self}");
}
}
// Users can define custom casts from one newtype vector flavor to another. This works because
// we're defining the conversion on the flavor rather than on the vector directly.
impl FlavorCastFrom<ChunkPosFlavor> for WorldPosFlavor {
fn vec_from(vec: ChunkPos) -> WorldPos {
(vec * 16.).raw_cast()
}
}
}
fn main() {
// The interface of newtyped vectors and regular `glam` vectors are almost identical. The only
// exceptions to this parity are:
// - Swizzling is disabled because it doesn't really make sense in this context.
// - Users cannot use the bare struct constructor because we need to introduce a `PhantomData`.
// This is also the reason for which we introduced a newtype wrapper instead of just adding the
// flavor parameter directly to the vector types.
let mut world_pos = WorldPos::new(1., 1., 1.);
world_pos += 0.4 * (WorldPos::X + WorldPos::NEG_Y).normalize();
let mut chunk_pos = ChunkPos::new(3., 2., 4.);
// Raw vectors can always be used with newtyped vectors.
chunk_pos *= 3. * glam::Vec3::X;
chunk_pos -= ChunkPos::new(1., 3., 4.);
// Oh look, contextual extension methods.
world_pos.do_something();
// Users can `cast` flavors directly if they need to.
chunk_pos += world_pos.raw_cast();
// But they really should be using the safe casts provided by the flavor.
chunk_pos.cast::<WorldPos>().do_something();
} This snippet was taken directly from The following features still need to be implemented:
In my opinion, the |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Users will oftentimes have various coordinate systems in their applications and many would like to enforce certain additional usage semantics on logically distinct vector types at compile time. To address this problem, we could expose a simple macro to generate a new type and provide some mechanism for defining implicit conversions between several vector newtypes. In doing so, users could benefit from better type safety and could expose relevant methods directly on the vector newtype. Here's an example of a potential interface for this system demonstrated with an example use case:
There are a few ways we could implement this. The naïve approach would be to forward every single method from the user's new type vector struct to the underlying backing vector. This will likely have a detrimental impact on compile times, however. We can improve on compile times by expressing newtypes as a dummy "flavor" generic parameter on some central
NewVectorType
struct maintained byglam
. To illustrate how this could help us, here's a proof-of-concept of what the macro could produce:In essence,
new_vec_type!
just becomes a fancy macro for defining a type alias (we might not even need/want this macro anymore). Because of trait coherence rules, users are not allowed to introduce inherentimpl
s onto their newtypes. This is actually a blessing in disguise, because we can now ensure that new inherentimpl
s introduced byglam
will not conflict with a user's existing inherentimpl
s, ensuring that adding new methods is always non-breaking.You might have also noticed the
#[fundamental]
attribute. This is a nightly-only attribute that allows users to implement traits from upstream crates onVector<T>
so long as they are the owner ofT
. This is a useful attribute to expose but it isn't strictly necessary for the actual system.impl
s affecting the behavior of the newtype (e.g. vector conversions) are handled by moving these traits to the "flavor"/VecType
struct (notice how we implementVecConvertFrom
on the struct implementingVecType
directly, rather than using the STL'sFrom
trait?) and the user's inherentimpl
s are turned into extension methods.Open Questions
glam
or should it be implemented in a different crate that's maintained under the same source tree. If so, how shouldglam
's "raw" vectors interact with newtyped ones?.checked_add
or a.saturating_add
method, how would this interact with newtypes? Should users be allowed to deny/override specific methods?impl
composition framework for Rust that this should interact with?macro
actually worth it?Beta Was this translation helpful? Give feedback.
All reactions