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

Introducing ViewHolder macro and Visibility attribute #211

Open
khebabchi opened this issue Nov 13, 2024 · 10 comments
Open

Introducing ViewHolder macro and Visibility attribute #211

khebabchi opened this issue Nov 13, 2024 · 10 comments

Comments

@khebabchi
Copy link

khebabchi commented Nov 13, 2024

1. ViewHolder

In order to make the shipyard crate more general porpuse and easly programable without going into source code, we can introduce :

  • ViewHolder : a derive macro that can be applied to structs containing only views and UniqueViews as their members

  • Use example :

let world=World::Default();
world.add_unique(Device::default());
world.add_unique(Queue::default());


#[derive(ViewHolder)]
/// this is the custom struct
struct CommandProvider<'a>{   
    device : UniqueViewMut<'a,Device>,
    queue : UniqueViewMut<'a,Queue>,
    shaders : ViewMut<'a,Shader,T::Tracking>
}


impl CommandProvider{
   fn createSprite(&mut self){
    // provide custom logic to abstract directly using device and queue structs
   }
//other custom logic
}

then you can just do :

world.run(|commands:ViewHolderMut<CommandProvider>|{
let sprite = commands.createSprite(); // custom abstraction for example
})

2. Visibility

#[derive(Component(Visible/Private))]
  • is it usefull where sometimes i dont want the user to be able to request a view to device, but instead request commandProvider* struct instead (* in the example)

Benefits

  • make the crate more extensible, and general purpose : Commands in bevy is the perfect example
  • this can make it easier to implement custom views from separated components
    • of corse the alternative solution is to do a struct that owns the device and queue, then add it to the world,
    • but its not really flexible, if you want to create a handle for (device and Surface) later, then you can't because device is owned by the first struct

Validation

  • we can request multiple ViewHolders, as we can just map the ViewHolders members and determine the requested components
  • (the only condition is that the ViewHolders can only contain Views or UniqueViews)
@khebabchi khebabchi changed the title ability to request a custom struct made of unique entities Introducing ViewHolder macro and Visibility attribute Nov 13, 2024
@leudz
Copy link
Owner

leudz commented Nov 14, 2024

This is all already available, and more.

The relevant derives are Borrow (to create the view) and BorrowInfo (to use it in workloads).
The documentation is sparse but there is a chapter on it.

#[derive(Borrow, BorrowInfo)]
struct CommandProvider<'v> {
    device: UniqueViewMut<'v, Device>,
    queue: UniqueViewMut<'v, Queue>,
    shaders: ViewMut<'v, Shader>,
}

impl CommandProvider<'_> {
    fn createSprite(&mut self) {
        // provide custom logic to abstract directly using device and queue structs
    }
    //other custom logic
}

You can use regular privacy syntax, both on the custom view itself and its fields.

Non view fields are allowed as long as they implement Default. It would be possible to provide an initialize function but this is not implemented yet.

#[derive(Borrow, BorrowInfo)]
struct CommandProvider<'v> {
    device: UniqueViewMut<'v, Device>,
    queue: UniqueViewMut<'v, Queue>,
    shaders: ViewMut<'v, Shader>,

    #[shipyard(default)]
    created_sprites: u32,
}

You can also easily derive an iterator from it. (It's a bit ridiculous on this example but this just to show the syntax)

fn main() {
    let world = World::new();

    world.run(|mut commands: CommandProvider| {
        let sprite = commands.createSprite(); // custom abstraction for example

        for command in commands.iter() {
            command.shader;
        }
    })
}

#[derive(Borrow, BorrowInfo, IntoIter)]
struct CommandProvider<'v> {
    #[shipyard(item_field_skip)]
    device: UniqueViewMut<'v, Device>,
    #[shipyard(item_field_skip)]
    queue: UniqueViewMut<'v, Queue>,
    #[shipyard(item_field_name = "shader")]
    shaders: ViewMut<'v, Shader>,

    #[shipyard(item_field_skip)]
    #[shipyard(default)]
    created_sprites: u32,
}

@khebabchi
Copy link
Author

khebabchi commented Nov 14, 2024

ah thats really good !!
what about the visibility part ?

@leudz
Copy link
Owner

leudz commented Nov 14, 2024

You can use regular visibility syntax, both on the custom view itself and its fields.

@khebabchi
Copy link
Author

You can use regular visibility syntax, both on the custom view itself and its fields.

can you give a small example on it

@leudz
Copy link
Owner

leudz commented Nov 14, 2024

#[derive(Borrow, BorrowInfo)]
pub struct CommandProvider<'v> {
    pub(super) device: UniqueViewMut<'v, Device>,
    pub(crate) queue: UniqueViewMut<'v, Queue>,
    shaders: ViewMut<'v, Shader>,
}

@khebabchi
Copy link
Author

fair enought

@khebabchi
Copy link
Author

khebabchi commented Nov 14, 2024

It would be possible to provide an initialize function but this is not implemented yet.

it would be realy helpfull if there is an initialize function like this to querry and then filter the results on creation without doing that on the systems :

trait BorrowInit
{   
    fn initialise(world:&mut World)->Self; 
}

is this what you were refering to here ?

@khebabchi khebabchi reopened this Nov 14, 2024
@leudz
Copy link
Owner

leudz commented Nov 14, 2024

What do you mean by "filter the results"?
If you want to add logic to the view creation and not only borrow the inner views then you can implement Borrow/WorldBorrow manually. This is what the concrete example does in the guide.

I was referring to something like this, like serde:

#[derive(Borrow, BorrowInfo)]
struct CommandProvider<'v> {
    device: UniqueViewMut<'v, Device>,
    queue: UniqueViewMut<'v, Queue>,
    shaders: ViewMut<'v, Shader>,

    #[shipyard(default = non_default_type_init_fn)]
    created_sprites: NonDefaultType,
}

fn non_default_type_init_fn() -> NonDefaultType {}

@khebabchi
Copy link
Author

khebabchi commented Nov 14, 2024

in bevy there is querry filters (i am talking about With, Wihtout, and Or), i know that modified and inserted exist (correct me if i am wrong)
does it exist in the shipyard ?

@leudz
Copy link
Owner

leudz commented Nov 15, 2024

Query filters don't really make sense for shipyard.

Bevy's default storage are archetypes, which can filter component "combinations" without looking in the storage. Because components are stored separately from the start.
So if you want the entities that have both A and B components and only care about B, you can filter in O(1) and never actually access A.
This saves on memory access but also allow more parallelism. Another system interested in A but not B could run at the same time.

Now for shipyard, the default storage is sparse set. It cannot filter without looking in the storage.
So even if query filter was available, the storage would still be locked for the entire system.

Like everything, it's a trade-off. With shipyard there is no conflicts possible on queries. Arguably it makes things simpler, at the cost of potential parallelism.

Technically, you can also run some filtering in run_if. It's a lot slower than query filters but you get back the potential parallelism to some extent.

Groups In theory, one day, shipyard will have groups again. Which are basically archetypes and could benefit from query filters.

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

2 participants