Skip to content

Commit

Permalink
Derive Instance
Browse files Browse the repository at this point in the history
  • Loading branch information
nanoqsh committed Jan 15, 2024
1 parent 0bc1d27 commit 2e5284d
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 32 deletions.
48 changes: 48 additions & 0 deletions dunge/src/instance.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use {
crate::{
sl::{ReadInstance, Ret},
state::State,
types::{self, VectorType},
uniform::{self, Value},
Instance,
},
Expand All @@ -10,6 +12,48 @@ use {

pub use dunge_shader::instance::Projection;

/// Describes a group member type projection.
///
/// The trait is sealed because the derive macro relies on no new types being used.
pub trait MemberProjection: private::Sealed {
const TYPE: VectorType;
type Field;
fn member_projection(id: u32) -> Self::Field;
}

impl private::Sealed for Row<[f32; 2]> {}

impl MemberProjection for Row<[f32; 2]> {
const TYPE: VectorType = VectorType::Vec2f;
type Field = Ret<ReadInstance, types::Vec2<f32>>;

fn member_projection(id: u32) -> Self::Field {
ReadInstance::new(id)
}
}

impl private::Sealed for Row<[f32; 3]> {}

impl MemberProjection for Row<[f32; 3]> {
const TYPE: VectorType = VectorType::Vec3f;
type Field = Ret<ReadInstance, types::Vec3<f32>>;

fn member_projection(id: u32) -> Self::Field {
ReadInstance::new(id)
}
}

impl private::Sealed for Row<[f32; 4]> {}

impl MemberProjection for Row<[f32; 4]> {
const TYPE: VectorType = VectorType::Vec4f;
type Field = Ret<ReadInstance, types::Vec4<f32>>;

fn member_projection(id: u32) -> Self::Field {
ReadInstance::new(id)
}
}

pub trait Set: Instance {
fn set<'p>(&'p self, setter: &mut Setter<'_, 'p>);
}
Expand Down Expand Up @@ -92,3 +136,7 @@ impl<U> Row<U> {
}
}
}

mod private {
pub trait Sealed {}
}
2 changes: 1 addition & 1 deletion dunge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub mod window;

pub use {
crate::{init::context, state::Frame},
dunge_macros::{Group, Vertex},
dunge_macros::{Group, Instance, Vertex},
dunge_shader::{group::Group, instance::Instance, sl, types, vertex::Vertex},
glam,
};
Expand Down
35 changes: 4 additions & 31 deletions dunge/tests/triangle_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ use {
color::Rgba,
draw,
format::Format,
instance::{Projection, Row, Set, SetMember, Setter},
sl::{self, Define, InInstance, Index, Out, ReadInstance, Ret},
instance::Row,
sl::{self, InInstance, Index, Out},
state::Render,
texture,
types::{self, VectorType},
Instance,
texture, Instance,
},
glam::Vec2,
helpers::Image,
Expand All @@ -26,37 +24,12 @@ fn render() -> Result<(), Error> {
const THIRD: f32 = consts::TAU / 3.;
const R_OFFSET: f32 = -consts::TAU / 4.;

#[derive(Instance)]
struct Transform {
pos: Row<[f32; 2]>,
col: Row<[f32; 3]>,
}

impl Instance for Transform {
type Projection = TransformProjection;
const DEF: Define<VectorType> = Define::new(&[VectorType::Vec2f, VectorType::Vec3f]);
}

impl Set for Transform {
fn set<'p>(&'p self, setter: &mut Setter<'_, 'p>) {
<Row<[f32; 2]> as SetMember>::set_member(&self.pos, setter);
<Row<[f32; 3]> as SetMember>::set_member(&self.col, setter);
}
}

struct TransformProjection {
pos: Ret<ReadInstance, types::Vec2<f32>>,
col: Ret<ReadInstance, types::Vec3<f32>>,
}

impl Projection for TransformProjection {
fn projection(id: u32) -> Self {
Self {
pos: ReadInstance::new(id),
col: ReadInstance::new(id + 1),
}
}
}

let triangle = |t: InInstance<Transform>, Index(index): Index| {
let [x, y] = sl::thunk(sl::f32(index) * THIRD + R_OFFSET);
let p = sl::vec2(sl::cos(x), sl::sin(y)) * TRIANGLE_SIZE + t.pos;
Expand Down
170 changes: 170 additions & 0 deletions dunge_macros/src/instance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
use {
crate::member,
proc_macro2::TokenStream,
syn::{spanned::Spanned, Data, DataStruct, DeriveInput},
};

pub(crate) fn derive(input: DeriveInput) -> TokenStream {
use std::iter;

let Data::Struct(DataStruct { fields, .. }) = input.data else {
return quote::quote_spanned! { input.ident.span() =>
::std::compile_error!("the instance type must be a struct");
};
};

if !input.generics.params.is_empty() {
return quote::quote_spanned! { input.generics.params.span() =>
::std::compile_error!("the instance struct cannot have generic parameters");
};
}

if fields.is_empty() {
return quote::quote_spanned! { fields.span() =>
::std::compile_error!("the instance struct must have some fields");
};
}

let name = input.ident;
let projection_name = quote::format_ident!("{name}Projection");
let instance_types = fields.iter().map(|field| {
let ty = &field.ty;
quote::quote! { <#ty as ::dunge::instance::MemberProjection>::TYPE }
});

let instance_set_members = iter::zip(0.., &fields).map(|(index, field)| {
let ident = member::make(index, field.ident.clone());
quote::quote! { ::dunge::instance::SetMember::set_member(&self.#ident, setter) }
});

let instance_fields = iter::zip(0.., &fields).map(|(index, field)| {
let ident = member::make(index, field.ident.clone());
let ty = &field.ty;
quote::quote! { #ident: <#ty as ::dunge::instance::MemberProjection>::Field }
});

let instance_member_projections = iter::zip(0.., &fields).map(|(index, field)| {
let ident = member::make(index, field.ident.clone());
let ty = &field.ty;
quote::quote! { #ident: <#ty as ::dunge::instance::MemberProjection>::member_projection(id + #index) }
});

quote::quote! {
impl ::dunge::Instance for #name {
type Projection = #projection_name;
const DEF: ::dunge::sl::Define<::dunge::types::VectorType> = ::dunge::sl::Define::new(&[
#(#instance_types),*,
]);
}

impl ::dunge::instance::Set for #name {
fn set<'p>(&'p self, setter: &mut ::dunge::instance::Setter<'_, 'p>) {
#(#instance_set_members);*;
}
}

struct #projection_name {
#(#instance_fields),*,
}

impl ::dunge::instance::Projection for #projection_name {
fn projection(id: ::core::primitive::u32) -> Self {
Self {
#(#instance_member_projections),*,
}
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn derive_instance() {
let input = quote::quote! {
struct Transform {
pos: Row<[f32; 2]>,
col: Row<[f32; 3]>,
}
};

let input = syn::parse2(input).expect("parse input");
let actual = derive(input);
let expected = quote::quote! {
impl ::dunge::Instance for Transform {
type Projection = TransformProjection;
const DEF: ::dunge::sl::Define<::dunge::types::VectorType> = ::dunge::sl::Define::new(&[
<Row<[f32; 2]> as ::dunge::instance::MemberProjection>::TYPE,
<Row<[f32; 3]> as ::dunge::instance::MemberProjection>::TYPE,
]);
}

impl ::dunge::instance::Set for Transform {
fn set<'p>(&'p self, setter: &mut ::dunge::instance::Setter<'_, 'p>) {
::dunge::instance::SetMember::set_member(&self.pos, setter);
::dunge::instance::SetMember::set_member(&self.col, setter);
}
}

struct TransformProjection {
pos: <Row<[f32; 2]> as ::dunge::instance::MemberProjection>::Field,
col: <Row<[f32; 3]> as ::dunge::instance::MemberProjection>::Field,
}

impl ::dunge::instance::Projection for TransformProjection {
fn projection(id: ::core::primitive::u32) -> Self {
Self {
pos: <Row<[f32; 2]> as ::dunge::instance::MemberProjection>::member_projection(id + 0u32),
col: <Row<[f32; 3]> as ::dunge::instance::MemberProjection>::member_projection(id + 1u32),
}
}
}
};

assert_eq!(actual.to_string(), expected.to_string());
}

#[test]
fn derive_tuple_instance() {
let input = quote::quote! {
struct Transform(Row<[f32; 2]>, Row<[f32; 3]>);
};

let input = syn::parse2(input).expect("parse input");
let actual = derive(input);
let expected = quote::quote! {
impl ::dunge::Instance for Transform {
type Projection = TransformProjection;
const DEF: ::dunge::sl::Define<::dunge::types::VectorType> = ::dunge::sl::Define::new(&[
<Row<[f32; 2]> as ::dunge::instance::MemberProjection>::TYPE,
<Row<[f32; 3]> as ::dunge::instance::MemberProjection>::TYPE,
]);
}

impl ::dunge::instance::Set for Transform {
fn set<'p>(&'p self, setter: &mut ::dunge::instance::Setter<'_, 'p>) {
::dunge::instance::SetMember::set_member(&self.0, setter);
::dunge::instance::SetMember::set_member(&self.1, setter);
}
}

struct TransformProjection {
0: <Row<[f32; 2]> as ::dunge::instance::MemberProjection>::Field,
1: <Row<[f32; 3]> as ::dunge::instance::MemberProjection>::Field,
}

impl ::dunge::instance::Projection for TransformProjection {
fn projection(id: ::core::primitive::u32) -> Self {
Self {
0: <Row<[f32; 2]> as ::dunge::instance::MemberProjection>::member_projection(id + 0u32),
1: <Row<[f32; 3]> as ::dunge::instance::MemberProjection>::member_projection(id + 1u32),
}
}
}
};

assert_eq!(actual.to_string(), expected.to_string());
}
}
8 changes: 8 additions & 0 deletions dunge_macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod group;
mod instance;
mod member;
mod vertex;

Expand All @@ -11,6 +12,13 @@ pub fn derive_group(input: TokenStream) -> TokenStream {
group::derive(input).into()
}

/// Derive implementation for the instance type.
#[proc_macro_derive(Instance)]
pub fn derive_instance(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input);
instance::derive(input).into()
}

/// Derive implementation for the vector type.
#[proc_macro_derive(Vertex)]
pub fn derive_vertex(input: TokenStream) -> TokenStream {
Expand Down

0 comments on commit 2e5284d

Please sign in to comment.