legion: remove foreach system functions
this is a bit sad, but upstream legion's new lifetimes appear to be incompatible with our foreach approach
This commit is contained in:
parent
981687ae41
commit
1f12964026
@ -1,6 +1,6 @@
|
||||
use crate::time::Time;
|
||||
use bevy_property::Properties;
|
||||
use legion::prelude::{ComMut, Res};
|
||||
use legion::prelude::{Query, Res, SubWorld, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Debug, Default, Properties)]
|
||||
@ -37,6 +37,8 @@ impl Timer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn timer_system(time: Res<Time>, mut timer: ComMut<Timer>) {
|
||||
timer.tick(time.delta_seconds);
|
||||
pub fn timer_system(time: Res<Time>, world: &mut SubWorld, query: &mut Query<Write<Timer>>) {
|
||||
for mut timer in query.iter_mut(world) {
|
||||
timer.tick(time.delta_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,13 +17,11 @@ pub mod world;
|
||||
#[cfg(feature = "serialize")]
|
||||
pub mod serialize;
|
||||
|
||||
mod system_fn_types;
|
||||
mod tuple;
|
||||
mod zip;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{
|
||||
borrow::{Ref as Com, RefMut as ComMut},
|
||||
command::CommandBuffer,
|
||||
entity::Entity,
|
||||
event::Event,
|
||||
|
||||
@ -1,113 +0,0 @@
|
||||
use crate::{
|
||||
borrow::{Ref, RefIter, RefIterMut, RefMut},
|
||||
filter::{ComponentFilter, EntityFilterTuple, Passthrough},
|
||||
index::{ChunkIndex, SetIndex},
|
||||
query::{DefaultFilter, View, ViewElement},
|
||||
storage::{ArchetypeData, Component, ComponentStorage, ComponentTypeId},
|
||||
};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
slice::{Iter, IterMut},
|
||||
};
|
||||
|
||||
impl<'a, T: Component> DefaultFilter for RefMut<'static, T> {
|
||||
type Filter = EntityFilterTuple<ComponentFilter<T>, Passthrough, Passthrough>;
|
||||
|
||||
fn filter() -> Self::Filter { super::filter::filter_fns::component() }
|
||||
}
|
||||
|
||||
impl<'a, T: Component> View<'a> for RefMut<'static, T> {
|
||||
type Iter = RefIterMut<'a, T, IterMut<'a, T>>;
|
||||
|
||||
#[inline]
|
||||
fn fetch(
|
||||
_: &'a ArchetypeData,
|
||||
chunk: &'a ComponentStorage,
|
||||
_: ChunkIndex,
|
||||
_: SetIndex,
|
||||
) -> Self::Iter {
|
||||
let (slice_borrow, slice) = unsafe {
|
||||
chunk
|
||||
.components(ComponentTypeId::of::<T>())
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Component of type {:?} not found in chunk when fetching Write view",
|
||||
std::any::type_name::<T>()
|
||||
)
|
||||
})
|
||||
.data_slice_mut::<T>()
|
||||
.deconstruct()
|
||||
};
|
||||
RefIterMut::new(slice_borrow, slice.iter_mut())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn validate() -> bool { true }
|
||||
|
||||
#[inline]
|
||||
fn reads<D: Component>() -> bool { TypeId::of::<T>() == TypeId::of::<D>() }
|
||||
|
||||
#[inline]
|
||||
fn writes<D: Component>() -> bool { TypeId::of::<T>() == TypeId::of::<D>() }
|
||||
|
||||
#[inline]
|
||||
fn read_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
|
||||
|
||||
#[inline]
|
||||
fn write_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
|
||||
}
|
||||
|
||||
impl<'a, T: Component> ViewElement for RefMut<'static, T> {
|
||||
type Component = T;
|
||||
}
|
||||
|
||||
impl<'a, T: Component> DefaultFilter for Ref<'static, T> {
|
||||
type Filter = EntityFilterTuple<ComponentFilter<T>, Passthrough, Passthrough>;
|
||||
|
||||
fn filter() -> Self::Filter { super::filter::filter_fns::component() }
|
||||
}
|
||||
|
||||
impl<'a, T: Component> View<'a> for Ref<'static, T> {
|
||||
type Iter = RefIter<'a, T, Iter<'a, T>>;
|
||||
|
||||
#[inline]
|
||||
fn fetch(
|
||||
_: &'a ArchetypeData,
|
||||
chunk: &'a ComponentStorage,
|
||||
_: ChunkIndex,
|
||||
_: SetIndex,
|
||||
) -> Self::Iter {
|
||||
let (slice_borrow, slice) = unsafe {
|
||||
chunk
|
||||
.components(ComponentTypeId::of::<T>())
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Component of type {:?} not found in chunk when fetching Write view",
|
||||
std::any::type_name::<T>()
|
||||
)
|
||||
})
|
||||
.data_slice::<T>()
|
||||
.deconstruct()
|
||||
};
|
||||
RefIter::new(slice_borrow, slice.iter())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn validate() -> bool { true }
|
||||
|
||||
#[inline]
|
||||
fn reads<D: Component>() -> bool { TypeId::of::<T>() == TypeId::of::<D>() }
|
||||
|
||||
#[inline]
|
||||
fn writes<D: Component>() -> bool { false }
|
||||
|
||||
#[inline]
|
||||
fn read_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
|
||||
|
||||
#[inline]
|
||||
fn write_types() -> Vec<ComponentTypeId> { Vec::new() }
|
||||
}
|
||||
|
||||
impl<'a, T: Component> ViewElement for Ref<'static, T> {
|
||||
type Component = T;
|
||||
}
|
||||
@ -21,164 +21,6 @@ fn get_idents(fmt_string: fn(usize) -> String, count: usize) -> Vec<Ident> {
|
||||
.collect::<Vec<Ident>>()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn impl_fn_systems(_input: TokenStream) -> TokenStream {
|
||||
let max_resources = 8;
|
||||
let max_views = 8;
|
||||
let resources = get_idents(|i| format!("R{}", i), max_resources);
|
||||
let resource_vars = get_idents(|i| format!("r{}", i), max_resources);
|
||||
let views = get_idents(|i| format!("V{}", i), max_views);
|
||||
let view_vars = get_idents(|i| format!("v{}", i), max_views);
|
||||
|
||||
let mut tokens = TokenStream::new();
|
||||
|
||||
let command_buffer = vec![Ident::new("CommandBuffer", Span::call_site())];
|
||||
let command_buffer_var = vec![Ident::new("_command_buffer", Span::call_site())];
|
||||
for resource_count in 0..=max_resources {
|
||||
let resource = &resources[0..resource_count];
|
||||
let resource_var = &resource_vars[0..resource_count];
|
||||
|
||||
let resource_tuple = tuple(resource);
|
||||
let resource_var_tuple = tuple(resource_var);
|
||||
|
||||
let resource_access = if resource_count == 0 {
|
||||
quote! { Access::default() }
|
||||
} else {
|
||||
quote! {{
|
||||
let mut resource_access: Access<ResourceTypeId> = Access::default();
|
||||
resource_access
|
||||
.reads
|
||||
.extend(<#resource_tuple as ResourceSet>::read_types().iter());
|
||||
resource_access
|
||||
.writes
|
||||
.extend(<#resource_tuple as ResourceSet>::write_types().iter());
|
||||
resource_access
|
||||
}}
|
||||
};
|
||||
|
||||
for view_count in 0..=max_views {
|
||||
let view = &views[0..view_count];
|
||||
let view_var = &view_vars[0..view_count];
|
||||
|
||||
let view_tuple = tuple(view);
|
||||
|
||||
let component_access = if view_count == 0 {
|
||||
quote! { Access::default() }
|
||||
} else {
|
||||
quote! {{
|
||||
let mut component_access: Access<ComponentTypeId> = Access::default();
|
||||
component_access
|
||||
.reads
|
||||
.extend(<#view_tuple as View>::read_types().iter());
|
||||
component_access
|
||||
.writes
|
||||
.extend(<#view_tuple as View>::write_types().iter());
|
||||
component_access
|
||||
}}
|
||||
};
|
||||
|
||||
let system_query = if view_count == 0 {
|
||||
quote! { () }
|
||||
} else if view_count == 1 {
|
||||
quote! { SystemQuery<
|
||||
#(#view),*,
|
||||
#(<#view as DefaultFilter>::Filter),*,
|
||||
> }
|
||||
} else {
|
||||
quote! { SystemQuery<
|
||||
(#(#view),*),
|
||||
EntityFilterTuple<
|
||||
And<(
|
||||
#(<<#view as DefaultFilter>::Filter as EntityFilter>::ArchetypeFilter),*
|
||||
)>,
|
||||
And<(
|
||||
#(<<#view as DefaultFilter>::Filter as EntityFilter>::ChunksetFilter),*
|
||||
)>,
|
||||
And<(
|
||||
#(<<#view as DefaultFilter>::Filter as EntityFilter>::ChunkFilter),*
|
||||
)>,
|
||||
>
|
||||
> }
|
||||
};
|
||||
|
||||
let query = if view_count == 0 {
|
||||
quote! {()}
|
||||
} else {
|
||||
quote! {<#view_tuple>::query()}
|
||||
};
|
||||
|
||||
for command_buffer_index in 0..2 {
|
||||
let command_buffer = &command_buffer[0..command_buffer_index];
|
||||
let command_buffer_var = &command_buffer_var[0..command_buffer_index];
|
||||
|
||||
let run_fn = if view_count == 0 {
|
||||
quote! { self(#(#resource_var,)* #(#command_buffer_var,)*) }
|
||||
} else {
|
||||
quote! {
|
||||
for (#(#view_var),*) in _query.iter_mut(_world) {
|
||||
self(#(#resource_var.clone(),)* #(#command_buffer_var,)* #(#view_var),*);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokens.extend(TokenStream::from(quote! {
|
||||
impl<'a,
|
||||
Func,
|
||||
#(#resource: ResourceSet<PreparedResources = #resource> + 'static + Clone,)*
|
||||
#(#view: for<'b> View<'b> + DefaultFilter + ViewElement,)*
|
||||
> IntoSystem<(#(#command_buffer)*), (#(#resource,)*), (#(#view,)*), ()> for Func
|
||||
where
|
||||
Func: FnMut(#(#resource,)* #(&mut #command_buffer,)* #(#view),*) + Send + Sync + 'static,
|
||||
#(<#view as View<'a>>::Iter: Iterator<Item = #view>,
|
||||
<#view as DefaultFilter>::Filter: Sync),*
|
||||
{
|
||||
fn system_id(mut self, id: SystemId) -> Box<dyn Schedulable> {
|
||||
let resource_access: Access<ResourceTypeId> = #resource_access;
|
||||
let component_access: Access<ComponentTypeId> = #component_access;
|
||||
|
||||
let run_fn = FuncSystemFnWrapper(
|
||||
move |_command_buffer,
|
||||
_world,
|
||||
_resources: #resource_tuple,
|
||||
_query: &mut #system_query
|
||||
| {
|
||||
let #resource_var_tuple = _resources;
|
||||
#run_fn
|
||||
},
|
||||
PhantomData,
|
||||
);
|
||||
|
||||
Box::new(FuncSystem {
|
||||
name: id,
|
||||
queries: AtomicRefCell::new(#query),
|
||||
access: SystemAccess {
|
||||
resources: resource_access,
|
||||
components: component_access,
|
||||
tags: Access::default(),
|
||||
},
|
||||
archetypes: ArchetypeAccess::Some(BitSet::default()),
|
||||
_resources: PhantomData::<#resource_tuple>,
|
||||
command_buffer: FxHashMap::default(),
|
||||
run_fn: AtomicRefCell::new(run_fn),
|
||||
})
|
||||
}
|
||||
|
||||
fn system_named(self, name: &'static str) -> Box<dyn Schedulable> {
|
||||
self.system_id(name.into())
|
||||
}
|
||||
|
||||
fn system(self) -> Box<dyn Schedulable> {
|
||||
self.system_id(std::any::type_name::<Self>().to_string().into())
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn impl_fn_query_systems(_input: TokenStream) -> TokenStream {
|
||||
let max_resources = 8;
|
||||
@ -193,6 +35,8 @@ pub fn impl_fn_query_systems(_input: TokenStream) -> TokenStream {
|
||||
|
||||
let command_buffer = vec![Ident::new("CommandBuffer", Span::call_site())];
|
||||
let command_buffer_var = vec![Ident::new("_command_buffer", Span::call_site())];
|
||||
let subworld = vec![Ident::new("SubWorld", Span::call_site())];
|
||||
let subworld_var = vec![Ident::new("_world", Span::call_site())];
|
||||
for resource_count in 0..=max_resources {
|
||||
let resource = &resources[0..resource_count];
|
||||
let resource_var = &resource_vars[0..resource_count];
|
||||
@ -215,12 +59,14 @@ pub fn impl_fn_query_systems(_input: TokenStream) -> TokenStream {
|
||||
}}
|
||||
};
|
||||
|
||||
for query_count in 1..=max_queries {
|
||||
for query_count in 0..=max_queries {
|
||||
let view = &views[0..query_count];
|
||||
let query_var = &query_vars[0..query_count];
|
||||
|
||||
let view_tuple = tuple(view);
|
||||
let query_var_tuple = tuple(query_var);
|
||||
let subworld = &subworld[0..query_count.min(1)];
|
||||
let subworld_var = &subworld_var[0..query_count.min(1)];
|
||||
|
||||
let component_access = if query_count == 0 {
|
||||
quote! { Access::default() }
|
||||
@ -241,19 +87,13 @@ pub fn impl_fn_query_systems(_input: TokenStream) -> TokenStream {
|
||||
let command_buffer = &command_buffer[0..command_buffer_index];
|
||||
let command_buffer_var = &command_buffer_var[0..command_buffer_index];
|
||||
|
||||
let view_tuple_avoid_type_collision = if query_count == 1 {
|
||||
quote! {(#(#view)*,)}
|
||||
} else {
|
||||
quote! {(#(#view,)*)}
|
||||
};
|
||||
|
||||
tokens.extend(TokenStream::from(quote! {
|
||||
impl<Func,
|
||||
#(#resource: ResourceSet<PreparedResources = #resource> + 'static + Clone,)*
|
||||
#(#view: for<'b> View<'b> + DefaultFilter + ViewElement),*
|
||||
> IntoSystem<(#(#command_buffer)*), (#(#resource,)*), (), #view_tuple_avoid_type_collision> for Func
|
||||
> IntoSystem<(#(#command_buffer)*), (#(#resource,)*), (#(#view,)*)> for Func
|
||||
where
|
||||
Func: FnMut(#(#resource,)*#(&mut #command_buffer,)* &mut SubWorld, #(&mut SystemQuery<#view, <#view as DefaultFilter>::Filter>),*) + Send + Sync + 'static,
|
||||
Func: FnMut(#(#resource,)*#(&mut #command_buffer,)* #(&mut #subworld,)* #(&mut SystemQuery<#view, <#view as DefaultFilter>::Filter>),*) + Send + Sync + 'static,
|
||||
#(<#view as DefaultFilter>::Filter: Sync),*
|
||||
{
|
||||
fn system_id(mut self, id: SystemId) -> Box<dyn Schedulable> {
|
||||
@ -268,7 +108,7 @@ pub fn impl_fn_query_systems(_input: TokenStream) -> TokenStream {
|
||||
| {
|
||||
let #resource_var_tuple = _resources;
|
||||
let #query_var_tuple = _queries;
|
||||
self(#(#resource_var,)*#(#command_buffer_var,)*_world,#(#query_var),*)
|
||||
self(#(#resource_var,)*#(#command_buffer_var,)*#(#subworld_var,)*#(#query_var),*)
|
||||
},
|
||||
PhantomData,
|
||||
);
|
||||
|
||||
@ -9,20 +9,18 @@ use fxhash::FxHashMap;
|
||||
use legion_core::{
|
||||
borrow::AtomicRefCell,
|
||||
command::CommandBuffer,
|
||||
filter::{And, EntityFilter, EntityFilterTuple},
|
||||
query::{DefaultFilter, IntoQuery, View, ViewElement},
|
||||
storage::ComponentTypeId,
|
||||
};
|
||||
use legion_fn_system_macro::{impl_fn_query_systems, impl_fn_systems};
|
||||
use legion_fn_system_macro::impl_fn_query_systems;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub trait IntoSystem<CommandBuffer, Resources, Views, Queries> {
|
||||
pub trait IntoSystem<CommandBuffer, Resources, Queries> {
|
||||
fn system_id(self, id: SystemId) -> Box<dyn Schedulable>;
|
||||
fn system_named(self, name: &'static str) -> Box<dyn Schedulable>;
|
||||
fn system(self) -> Box<dyn Schedulable>;
|
||||
}
|
||||
|
||||
impl_fn_systems!();
|
||||
impl_fn_query_systems!();
|
||||
|
||||
#[allow(type_alias_bounds)]
|
||||
@ -33,14 +31,8 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
resource::Resources,
|
||||
system_fn_types::{Res, ResMut},
|
||||
IntoSystem, Query, SubWorld,
|
||||
};
|
||||
use crate::{resource::Resources, system_fn_types::Res, IntoSystem, Query, SubWorld};
|
||||
use legion_core::{
|
||||
borrow::{Ref, RefMut},
|
||||
command::CommandBuffer,
|
||||
query::{Read, Write},
|
||||
world::World,
|
||||
};
|
||||
@ -68,10 +60,12 @@ mod tests {
|
||||
println!("{:?}", x);
|
||||
}
|
||||
}
|
||||
let mut system = query_system.system();
|
||||
system.run(&mut world, &mut resources);
|
||||
|
||||
fn query_system2(
|
||||
world: &mut SubWorld,
|
||||
a: Res<A>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<(Read<X>, Write<Y>)>,
|
||||
query2: &mut Query<Read<X>>,
|
||||
) {
|
||||
@ -86,98 +80,14 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let mut system = query_system.system();
|
||||
let mut system2 = query_system2.system();
|
||||
system.run(&mut world, &mut resources);
|
||||
system2.run(&mut world, &mut resources);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_system() {
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
resources.insert(A(0));
|
||||
world.insert((), vec![(X(1), Y(1)), (X(2), Y(2))]);
|
||||
|
||||
fn single_read_system(x: Ref<X>) {
|
||||
println!("{}", x.0);
|
||||
}
|
||||
let mut system = single_read_system.system();
|
||||
system.run(&mut world, &mut resources);
|
||||
|
||||
fn read_write_system(x: Ref<X>, y: Ref<Y>, mut z: RefMut<A>) {
|
||||
z.0 += 1;
|
||||
println!("{} {} {}", x.0, y.0, z.0);
|
||||
fn query_system3(a: Res<A>) {
|
||||
println!("{:?}", *a);
|
||||
}
|
||||
|
||||
({
|
||||
|x: Res<A>, y: Ref<Y>, mut z: RefMut<A>| {
|
||||
z.0 += 1;
|
||||
println!("{} {} {}", x.0, y.0, z.0);
|
||||
}
|
||||
})
|
||||
.system();
|
||||
|
||||
let mut system = read_write_system.system();
|
||||
system.run(&mut world, &mut resources);
|
||||
|
||||
fn resource_system(a: Res<A>, x: Ref<X>, y: Ref<Y>) {
|
||||
println!("{} {} {}", a.0, x.0, y.0);
|
||||
}
|
||||
|
||||
let mut system = resource_system.system();
|
||||
system.run(&mut world, &mut resources);
|
||||
|
||||
fn empty_system_mut() {
|
||||
println!("hello world");
|
||||
}
|
||||
|
||||
let mut system = empty_system_mut.system();
|
||||
system.run(&mut world, &mut resources);
|
||||
|
||||
fn resource_system_mut(mut a: ResMut<A>, x: Ref<X>, y: Ref<Y>) {
|
||||
a.0 += 1;
|
||||
println!("{} {} {}", a.0, x.0, y.0);
|
||||
}
|
||||
let mut system = resource_system_mut.system();
|
||||
system.run(&mut world, &mut resources);
|
||||
|
||||
fn command_buffer_system(command_buffer: &mut CommandBuffer, mut a: ResMut<A>) {
|
||||
a.0 += 1;
|
||||
command_buffer.insert((), vec![(X(1), Y(1)), (X(2), Y(2))]);
|
||||
println!("{}", a.0);
|
||||
}
|
||||
let mut system = command_buffer_system.system();
|
||||
system.run(&mut world, &mut resources);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_system_fn() {
|
||||
fn my_system(mut a: ResMut<A>, x: Ref<X>, mut y: RefMut<Y>) {
|
||||
if a.0 == 0 {
|
||||
assert_eq!(*a, A(0));
|
||||
assert_eq!(*x, X(2));
|
||||
assert_eq!(*y, Y(3));
|
||||
} else if a.0 == 1 {
|
||||
assert_eq!(*a, A(1));
|
||||
assert_eq!(*x, X(4));
|
||||
assert_eq!(*y, Y(5));
|
||||
y.0 += 1;
|
||||
assert_eq!(*y, Y(6));
|
||||
} else {
|
||||
panic!("unexpected value");
|
||||
}
|
||||
|
||||
a.0 += 1;
|
||||
}
|
||||
|
||||
let mut world = World::new();
|
||||
let mut resources = Resources::default();
|
||||
|
||||
resources.insert(A(0));
|
||||
resources.insert(B(1));
|
||||
world.insert((), vec![(X(2), Y(3)), (X(4), Y(5))]);
|
||||
let mut my_system = my_system.system();
|
||||
my_system.run(&mut world, &mut resources);
|
||||
let mut system3 = query_system3.system();
|
||||
system3.run(&mut world, &mut resources);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,8 +13,8 @@ use crate::{
|
||||
use bevy_asset::{Assets, Handle};
|
||||
use bevy_property::Properties;
|
||||
use legion::{
|
||||
prelude::{ComMut, Res, ResourceSet},
|
||||
systems::{resource::ResourceTypeId, ResMut},
|
||||
prelude::{Res, ResourceSet, Write},
|
||||
systems::{resource::ResourceTypeId, ResMut, SubWorld, Query},
|
||||
};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut, Range},
|
||||
@ -340,6 +340,8 @@ pub trait Drawable {
|
||||
fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError>;
|
||||
}
|
||||
|
||||
pub fn clear_draw_system(mut draw: ComMut<Draw>) {
|
||||
draw.clear_render_commands();
|
||||
pub fn clear_draw_system(world: &mut SubWorld, query: &mut Query<Write<Draw>>) {
|
||||
for mut draw in query.iter_mut(world) {
|
||||
draw.clear_render_commands();
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,10 @@ use crate::{
|
||||
};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_property::Properties;
|
||||
use legion::{prelude::ComMut, systems::ResMut};
|
||||
use legion::{
|
||||
prelude::Write,
|
||||
systems::{Query, ResMut, SubWorld},
|
||||
};
|
||||
#[derive(Properties, Default, Clone)]
|
||||
pub struct RenderPipeline {
|
||||
pub pipeline: Handle<PipelineDescriptor>,
|
||||
@ -104,12 +107,14 @@ impl<'a> Drawable for DrawableRenderPipelines<'a> {
|
||||
pub fn draw_render_pipelines_system(
|
||||
mut draw_context: DrawContext,
|
||||
mut render_resource_bindings: ResMut<RenderResourceBindings>,
|
||||
mut draw: ComMut<Draw>,
|
||||
mut render_pipelines: ComMut<RenderPipelines>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<(Write<Draw>, Write<RenderPipelines>)>,
|
||||
) {
|
||||
let mut drawable = DrawableRenderPipelines {
|
||||
render_pipelines: &mut render_pipelines,
|
||||
render_resource_bindings: &mut render_resource_bindings,
|
||||
};
|
||||
drawable.draw(&mut draw, &mut draw_context).unwrap();
|
||||
for (mut draw, mut render_pipelines) in query.iter_mut(world) {
|
||||
let mut drawable = DrawableRenderPipelines {
|
||||
render_pipelines: &mut render_pipelines,
|
||||
render_resource_bindings: &mut render_resource_bindings,
|
||||
};
|
||||
drawable.draw(&mut draw, &mut draw_context).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
use crate::{texture::Texture, RenderPipelines};
|
||||
use bevy_asset::{Assets, Handle};
|
||||
use legion::prelude::{Com, ComMut, Res};
|
||||
use legion::{
|
||||
prelude::{Read, Res, Write},
|
||||
systems::{Query, SubWorld},
|
||||
};
|
||||
|
||||
pub use bevy_derive::ShaderDefs;
|
||||
|
||||
@ -55,46 +58,52 @@ impl ShaderDef for Option<Handle<Texture>> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shader_defs_system<T>(shader_defs: Com<T>, mut render_pipelines: ComMut<RenderPipelines>)
|
||||
pub fn shader_defs_system<T>(world: &mut SubWorld, query: Query<(Read<T>, Write<RenderPipelines>)>)
|
||||
where
|
||||
T: ShaderDefs + Send + Sync + 'static,
|
||||
{
|
||||
for shader_def in shader_defs.iter_shader_defs() {
|
||||
for (shader_defs, mut render_pipelines) in query.iter_mut(world) {
|
||||
for shader_def in shader_defs.iter_shader_defs() {
|
||||
for render_pipeline in render_pipelines.pipelines.iter_mut() {
|
||||
render_pipeline
|
||||
.specialization
|
||||
.shader_specialization
|
||||
.shader_defs
|
||||
.insert(shader_def.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_shader_defs_system(world: &mut SubWorld, query: &mut Query<Write<RenderPipelines>>) {
|
||||
for mut render_pipelines in query.iter_mut(world) {
|
||||
for render_pipeline in render_pipelines.pipelines.iter_mut() {
|
||||
render_pipeline
|
||||
.specialization
|
||||
.shader_specialization
|
||||
.shader_defs
|
||||
.insert(shader_def.to_string());
|
||||
.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_shader_defs_system(mut render_pipelines: ComMut<RenderPipelines>) {
|
||||
for render_pipeline in render_pipelines.pipelines.iter_mut() {
|
||||
render_pipeline
|
||||
.specialization
|
||||
.shader_specialization
|
||||
.shader_defs
|
||||
.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn asset_shader_defs_system<T>(
|
||||
assets: Res<Assets<T>>,
|
||||
asset_handle: Com<Handle<T>>,
|
||||
mut render_pipelines: ComMut<RenderPipelines>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<(Read<Handle<T>>, Write<RenderPipelines>)>,
|
||||
) where
|
||||
T: ShaderDefs + Send + Sync + 'static,
|
||||
{
|
||||
let shader_defs = assets.get(&asset_handle).unwrap();
|
||||
for shader_def in shader_defs.iter_shader_defs() {
|
||||
for render_pipeline in render_pipelines.pipelines.iter_mut() {
|
||||
render_pipeline
|
||||
.specialization
|
||||
.shader_specialization
|
||||
.shader_defs
|
||||
.insert(shader_def.to_string());
|
||||
for (asset_handle, mut render_pipelines) in query.iter_mut(world) {
|
||||
let shader_defs = assets.get(&asset_handle).unwrap();
|
||||
for shader_def in shader_defs.iter_shader_defs() {
|
||||
for render_pipeline in render_pipelines.pipelines.iter_mut() {
|
||||
render_pipeline
|
||||
.specialization
|
||||
.shader_specialization
|
||||
.shader_defs
|
||||
.insert(shader_def.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,10 +6,10 @@ use bevy_render::{
|
||||
texture::Texture,
|
||||
Color,
|
||||
};
|
||||
use bevy_sprite::{ComMut, TextureAtlas};
|
||||
use bevy_sprite::TextureAtlas;
|
||||
use bevy_text::{DrawableText, Font, FontAtlasSet, TextStyle};
|
||||
use bevy_transform::prelude::Transform;
|
||||
use legion::prelude::{Com, Res, ResMut};
|
||||
use legion::prelude::*;
|
||||
|
||||
pub struct Label {
|
||||
pub text: String,
|
||||
@ -37,25 +37,28 @@ impl Label {
|
||||
fonts: Res<Assets<Font>>,
|
||||
mut font_atlas_sets: ResMut<Assets<FontAtlasSet>>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||
label: Com<Label>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<Read<Label>>,
|
||||
) {
|
||||
let font_atlases = font_atlas_sets
|
||||
.get_or_insert_with(Handle::from_id(label.font.id), || {
|
||||
FontAtlasSet::new(label.font)
|
||||
});
|
||||
// TODO: this call results in one or more TextureAtlases, whose render resources are created in the RENDER_GRAPH_SYSTEMS
|
||||
// stage. That logic runs _before_ the DRAW stage, which means we cant call add_glyphs_to_atlas in the draw stage
|
||||
// without our render resources being a frame behind. Therefore glyph atlasing either needs its own system or the TextureAtlas
|
||||
// resource generation needs to happen AFTER the render graph systems. maybe draw systems should execute within the
|
||||
// render graph so ordering like this can be taken into account? Maybe the RENDER_GRAPH_SYSTEMS stage should be removed entirely
|
||||
// in favor of node.update()? Regardless, in the immediate short term the current approach is fine.
|
||||
font_atlases.add_glyphs_to_atlas(
|
||||
&fonts,
|
||||
&mut texture_atlases,
|
||||
&mut textures,
|
||||
label.style.font_size,
|
||||
&label.text,
|
||||
);
|
||||
for label in query.iter(world) {
|
||||
let font_atlases = font_atlas_sets
|
||||
.get_or_insert_with(Handle::from_id(label.font.id), || {
|
||||
FontAtlasSet::new(label.font)
|
||||
});
|
||||
// TODO: this call results in one or more TextureAtlases, whose render resources are created in the RENDER_GRAPH_SYSTEMS
|
||||
// stage. That logic runs _before_ the DRAW stage, which means we cant call add_glyphs_to_atlas in the draw stage
|
||||
// without our render resources being a frame behind. Therefore glyph atlasing either needs its own system or the TextureAtlas
|
||||
// resource generation needs to happen AFTER the render graph systems. maybe draw systems should execute within the
|
||||
// render graph so ordering like this can be taken into account? Maybe the RENDER_GRAPH_SYSTEMS stage should be removed entirely
|
||||
// in favor of node.update()? Regardless, in the immediate short term the current approach is fine.
|
||||
font_atlases.add_glyphs_to_atlas(
|
||||
&fonts,
|
||||
&mut texture_atlases,
|
||||
&mut textures,
|
||||
label.style.font_size,
|
||||
&label.text,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_label_system(
|
||||
@ -65,26 +68,26 @@ impl Label {
|
||||
texture_atlases: Res<Assets<TextureAtlas>>,
|
||||
mut render_resource_bindings: ResMut<RenderResourceBindings>,
|
||||
mut asset_render_resource_bindings: ResMut<AssetRenderResourceBindings>,
|
||||
mut draw: ComMut<Draw>,
|
||||
label: Com<Label>,
|
||||
node: Com<Node>,
|
||||
transform: Com<Transform>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<(Write<Draw>, Read<Label>, Read<Node>, Read<Transform>)>,
|
||||
) {
|
||||
// let position = transform.0 - quad.size / 2.0;
|
||||
let position = transform.value.w_axis().truncate() - (node.size / 2.0).extend(0.0);
|
||||
for (mut draw, label, node, transform) in query.iter_mut(world) {
|
||||
// let position = transform.0 - quad.size / 2.0;
|
||||
let position = transform.value.w_axis().truncate() - (node.size / 2.0).extend(0.0);
|
||||
|
||||
let mut drawable_text = DrawableText::new(
|
||||
fonts.get(&label.font).unwrap(),
|
||||
font_atlas_sets
|
||||
.get(&label.font.as_handle::<FontAtlasSet>())
|
||||
.unwrap(),
|
||||
&texture_atlases,
|
||||
&mut render_resource_bindings,
|
||||
&mut asset_render_resource_bindings,
|
||||
position,
|
||||
&label.style,
|
||||
&label.text,
|
||||
);
|
||||
drawable_text.draw(&mut draw, &mut draw_context).unwrap();
|
||||
let mut drawable_text = DrawableText::new(
|
||||
fonts.get(&label.font).unwrap(),
|
||||
font_atlas_sets
|
||||
.get(&label.font.as_handle::<FontAtlasSet>())
|
||||
.unwrap(),
|
||||
&texture_atlases,
|
||||
&mut render_resource_bindings,
|
||||
&mut asset_render_resource_bindings,
|
||||
position,
|
||||
&label.style,
|
||||
&label.text,
|
||||
);
|
||||
drawable_text.draw(&mut draw, &mut draw_context).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,14 +10,19 @@ fn main() {
|
||||
|
||||
fn animate_sprite_system(
|
||||
texture_atlases: Res<Assets<TextureAtlas>>,
|
||||
mut timer: ComMut<Timer>,
|
||||
mut sprite: ComMut<TextureAtlasSprite>,
|
||||
texture_atlas_handle: Com<Handle<TextureAtlas>>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<(
|
||||
Write<Timer>,
|
||||
Write<TextureAtlasSprite>,
|
||||
Read<Handle<TextureAtlas>>,
|
||||
)>,
|
||||
) {
|
||||
if timer.finished {
|
||||
let texture_atlas = texture_atlases.get(&texture_atlas_handle).unwrap();
|
||||
sprite.index = ((sprite.index as usize + 1) % texture_atlas.textures.len()) as u32;
|
||||
timer.reset();
|
||||
for (mut timer, mut sprite, texture_atlas_handle) in query.iter_mut(world) {
|
||||
if timer.finished {
|
||||
let texture_atlas = texture_atlases.get(&texture_atlas_handle).unwrap();
|
||||
sprite.index = ((sprite.index as usize + 1) % texture_atlas.textures.len()) as u32;
|
||||
timer.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,8 +11,14 @@ fn main() {
|
||||
}
|
||||
|
||||
/// rotates the parent, which will result in the child also rotating
|
||||
fn rotator_system(time: Res<Time>, _rotator: ComMut<Rotator>, mut rotation: ComMut<Rotation>) {
|
||||
rotation.0 = rotation.0 * Quat::from_rotation_x(3.0 * time.delta_seconds);
|
||||
fn rotator_system(
|
||||
time: Res<Time>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<(Read<Rotator>, Write<Rotation>)>,
|
||||
) {
|
||||
for (_rotator, mut rotation) in query.iter_mut(world) {
|
||||
rotation.0 = rotation.0 * Quat::from_rotation_x(3.0 * time.delta_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// set up a simple scene with a "parent" cube and a "child" cube
|
||||
|
||||
@ -17,12 +17,14 @@ fn main() {
|
||||
fn move_cubes(
|
||||
time: Res<Time>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
mut translation: ComMut<Translation>,
|
||||
material_handle: Com<Handle<StandardMaterial>>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<(Write<Translation>, Read<Handle<StandardMaterial>>)>,
|
||||
) {
|
||||
let material = materials.get_mut(&material_handle).unwrap();
|
||||
translation.0 += math::vec3(1.0, 0.0, 0.0) * time.delta_seconds;
|
||||
material.albedo += Color::rgb(-time.delta_seconds, -time.delta_seconds, time.delta_seconds);
|
||||
for (mut translation, material_handle) in query.iter_mut(world) {
|
||||
let material = materials.get_mut(&material_handle).unwrap();
|
||||
translation.0 += math::vec3(1.0, 0.0, 0.0) * time.delta_seconds;
|
||||
material.albedo += Color::rgb(-time.delta_seconds, -time.delta_seconds, time.delta_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(
|
||||
|
||||
@ -12,8 +12,14 @@ fn main() {
|
||||
}
|
||||
|
||||
/// rotates the parent, which will result in the child also rotating
|
||||
fn rotator_system(time: Res<Time>, _rotator: ComMut<Rotator>, mut rotation: ComMut<Rotation>) {
|
||||
rotation.0 = rotation.0 * Quat::from_rotation_x(3.0 * time.delta_seconds);
|
||||
fn rotator_system(
|
||||
time: Res<Time>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<(Read<Rotator>, Write<Rotation>)>,
|
||||
) {
|
||||
for (_rotator, mut rotation) in query.iter_mut(world) {
|
||||
rotation.0 = rotation.0 * Quat::from_rotation_x(3.0 * time.delta_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
fn camera_order_color_system(
|
||||
|
||||
@ -80,21 +80,22 @@ fn new_round_system(game_rules: Res<GameRules>, mut game_state: ResMut<GameState
|
||||
);
|
||||
}
|
||||
|
||||
// This system runs once for each entity with both the "Player" and "Score" component.
|
||||
// NOTE: Com<Player> is a read-only component reference, whereas ComMut<Score> can modify the component
|
||||
fn score_system(player: Com<Player>, mut score: ComMut<Score>) {
|
||||
let scored_a_point = random::<bool>();
|
||||
if scored_a_point {
|
||||
score.value += 1;
|
||||
println!(
|
||||
"{} scored a point! Their score is: {}",
|
||||
player.name, score.value
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"{} did not score a point! Their score is: {}",
|
||||
player.name, score.value
|
||||
);
|
||||
// This system updates the score for each entity with the "Player" and "Score" component.
|
||||
fn score_system(world: &mut SubWorld, query: &mut Query<(Read<Player>, Write<Score>)>) {
|
||||
for (player, mut score) in query.iter_mut(world) {
|
||||
let scored_a_point = random::<bool>();
|
||||
if scored_a_point {
|
||||
score.value += 1;
|
||||
println!(
|
||||
"{} scored a point! Their score is: {}",
|
||||
player.name, score.value
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"{} did not score a point! Their score is: {}",
|
||||
player.name, score.value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// this game isn't very fun is it :)
|
||||
@ -102,26 +103,11 @@ fn score_system(player: Com<Player>, mut score: ComMut<Score>) {
|
||||
|
||||
// This system runs on all entities with the "Player" and "Score" components, but it also
|
||||
// accesses the "GameRules" resource to determine if a player has won.
|
||||
// NOTE: resources must always come before components in system functions
|
||||
// NOTE: resources must always come before worlds/queries in system functions
|
||||
fn score_check_system(
|
||||
game_rules: Res<GameRules>,
|
||||
mut game_state: ResMut<GameState>,
|
||||
player: Com<Player>,
|
||||
score: Com<Score>,
|
||||
) {
|
||||
if score.value == game_rules.winning_score {
|
||||
game_state.winning_player = Some(player.name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// If you need more control over iteration or direct access to SubWorld, you can also use "query systems"
|
||||
// This is how you would represent the system above with a "query system"
|
||||
// NOTE: You can add as many queries as you want, but they must come after all resources (Res/ResMut).
|
||||
#[allow(dead_code)]
|
||||
fn query_score_check_system(
|
||||
world: &mut SubWorld,
|
||||
game_rules: Res<GameRules>,
|
||||
mut game_state: ResMut<GameState>,
|
||||
query: &mut Query<(Read<Player>, Read<Score>)>,
|
||||
) {
|
||||
for (player, score) in query.iter(world) {
|
||||
@ -191,7 +177,7 @@ fn startup_system(world: &mut World, resources: &mut Resources) {
|
||||
// Normal systems cannot safely access the World instance directly because they run in parallel.
|
||||
// Our World contains all of our components, so accessing it in parallel is not thread safe.
|
||||
// Command buffers give us the ability to queue up changes to our World without directly accessing it
|
||||
// NOTE: Command buffers must always come before resources and components in system functions
|
||||
// NOTE: Command buffers must always come after resources and before queries in system functions
|
||||
fn new_player_system(
|
||||
game_rules: Res<GameRules>,
|
||||
mut game_state: ResMut<GameState>,
|
||||
@ -252,9 +238,11 @@ fn thread_local_system(world: &mut World, resources: &mut Resources) {
|
||||
#[allow(dead_code)]
|
||||
fn closure_system() -> Box<dyn Schedulable> {
|
||||
let mut counter = 0;
|
||||
(move |player: Com<Player>, score: Com<Score>| {
|
||||
println!("processed: {} {}", player.name, score.value);
|
||||
println!("this ran {} times", counter);
|
||||
(move |world: &mut SubWorld, query: &mut Query<(Read<Player>, Read<Score>)>| {
|
||||
for (player, score) in query.iter(world) {
|
||||
println!("processed: {} {}", player.name, score.value);
|
||||
}
|
||||
println!("this system ran {} times", counter);
|
||||
counter += 1;
|
||||
})
|
||||
.system()
|
||||
@ -270,9 +258,15 @@ struct State {
|
||||
|
||||
// NOTE: this doesn't do anything relevant to our game, it is just here for illustrative purposes
|
||||
#[allow(dead_code)]
|
||||
fn stateful_system(mut state: ComMut<State>, player: Com<Player>, score: ComMut<Score>) {
|
||||
println!("processed: {} {}", player.name, score.value);
|
||||
println!("this ran {} times", state.counter);
|
||||
fn stateful_system(
|
||||
mut state: ResMut<State>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<(Read<Player>, Read<Score>)>,
|
||||
) {
|
||||
for (player, score) in query.iter(world) {
|
||||
println!("processed: {} {}", player.name, score.value);
|
||||
}
|
||||
println!("this system ran {} times", state.counter);
|
||||
state.counter += 1;
|
||||
}
|
||||
|
||||
|
||||
@ -50,11 +50,18 @@ fn atlas_render_system(
|
||||
}
|
||||
}
|
||||
|
||||
fn text_update_system(mut state: ResMut<State>, time: Res<Time>, mut label: ComMut<Label>) {
|
||||
state.timer.tick(time.delta_seconds);
|
||||
if state.timer.finished {
|
||||
label.text = format!("{}", rand::random::<u8>() as char);
|
||||
state.timer.reset();
|
||||
fn text_update_system(
|
||||
mut state: ResMut<State>,
|
||||
time: Res<Time>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<Write<Label>>,
|
||||
) {
|
||||
for mut label in query.iter_mut(world) {
|
||||
state.timer.tick(time.delta_seconds);
|
||||
if state.timer.finished {
|
||||
label.text = format!("{}", rand::random::<u8>() as char);
|
||||
state.timer.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,10 +78,7 @@ fn setup(
|
||||
.entity_with(OrthographicCameraComponents::default())
|
||||
// texture
|
||||
.entity_with(LabelComponents {
|
||||
node: Node::new(
|
||||
Anchors::TOP_LEFT,
|
||||
Margins::new(0.0, 250.0, 0.0, 60.0),
|
||||
),
|
||||
node: Node::new(Anchors::TOP_LEFT, Margins::new(0.0, 250.0, 0.0, 60.0)),
|
||||
label: Label {
|
||||
text: "a".to_string(),
|
||||
font: font_handle,
|
||||
|
||||
@ -12,10 +12,16 @@ fn main() {
|
||||
.run();
|
||||
}
|
||||
|
||||
fn text_update_system(diagnostics: Res<Diagnostics>, mut label: ComMut<Label>) {
|
||||
if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) {
|
||||
if let Some(average) = fps.average() {
|
||||
label.text = format!("FPS: {:.2}", average);
|
||||
fn text_update_system(
|
||||
diagnostics: Res<Diagnostics>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<Write<Label>>,
|
||||
) {
|
||||
for mut label in query.iter_mut(world) {
|
||||
if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) {
|
||||
if let Some(average) = fps.average() {
|
||||
label.text = format!("FPS: {:.2}", average);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,10 +34,7 @@ fn setup(asset_server: Res<AssetServer>, command_buffer: &mut CommandBuffer) {
|
||||
.entity_with(OrthographicCameraComponents::default())
|
||||
// texture
|
||||
.entity_with(LabelComponents {
|
||||
node: Node::new(
|
||||
Anchors::TOP_LEFT,
|
||||
Margins::new(0.0, 250.0, 0.0, 60.0),
|
||||
),
|
||||
node: Node::new(Anchors::TOP_LEFT, Margins::new(0.0, 250.0, 0.0, 60.0)),
|
||||
label: Label {
|
||||
text: "FPS:".to_string(),
|
||||
font: font_handle,
|
||||
|
||||
@ -12,12 +12,14 @@ fn main() {
|
||||
fn placement_system(
|
||||
time: Res<Time>,
|
||||
materials: Res<Assets<ColorMaterial>>,
|
||||
mut node: ComMut<Node>,
|
||||
material_handle: Com<Handle<ColorMaterial>>,
|
||||
world: &mut SubWorld,
|
||||
query: &mut Query<(Write<Node>, Read<Handle<ColorMaterial>>)>,
|
||||
) {
|
||||
let material = materials.get(&material_handle).unwrap();
|
||||
if material.color.r > 0.2 {
|
||||
node.position += Vec2::new(0.1 * time.delta_seconds, 0.0);
|
||||
for (mut node, material_handle) in query.iter_mut(world) {
|
||||
let material = materials.get(&material_handle).unwrap();
|
||||
if material.color.r > 0.2 {
|
||||
node.position += Vec2::new(0.1 * time.delta_seconds, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user