2025-04-09 05:25:23 +00:00
|
|
|
use proc_macro::TokenStream;
|
|
|
|
use proc_macro2::TokenStream as TokenStream2;
|
|
|
|
use quote::{ToTokens, format_ident, quote};
|
|
|
|
use syn::Ident;
|
|
|
|
|
|
|
|
struct Query {
|
|
|
|
components: Vec<Ident>,
|
|
|
|
filter: Ident,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Query {
|
|
|
|
pub fn new(components: impl Iterator<Item = Ident>, filter_id: usize) -> Self {
|
|
|
|
Self {
|
|
|
|
components: components.collect(),
|
|
|
|
filter: format_ident!("Filter{filter_id}"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
pub fn types(&self) -> TokenStream2 {
|
2025-04-09 05:25:23 +00:00
|
|
|
let component_list = self
|
|
|
|
.components
|
|
|
|
.iter()
|
2025-04-09 07:00:42 +00:00
|
|
|
.map(|c| quote! { #c })
|
2025-04-09 05:25:23 +00:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let components = if component_list.len() == 1 {
|
|
|
|
let component = &component_list[0];
|
|
|
|
quote! { #component }
|
|
|
|
} else {
|
2025-04-09 07:00:42 +00:00
|
|
|
quote! { ( #( #component_list, )* ) }
|
2025-04-09 05:25:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
components
|
|
|
|
}
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
pub fn dedup_checks(&self) -> TokenStream2 {
|
|
|
|
let components = &self.components;
|
|
|
|
let filter = &self.filter;
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#( #filter::verify_dedup::<#components>(); )*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_entity(&self, index: usize) -> TokenStream2 {
|
|
|
|
let components = &self.components;
|
|
|
|
let filter = &self.filter;
|
|
|
|
|
|
|
|
quote! {
|
2025-04-09 09:15:06 +00:00
|
|
|
{
|
2025-04-09 12:40:25 +00:00
|
|
|
let mut complies = true;
|
2025-04-09 07:00:42 +00:00
|
|
|
|
|
|
|
#(
|
|
|
|
if !entity.components.contains::<#components>() {
|
2025-04-09 12:40:25 +00:00
|
|
|
complies = false;
|
2025-04-09 07:00:42 +00:00
|
|
|
}
|
|
|
|
)*
|
|
|
|
|
|
|
|
if !#filter::check(entity) {
|
2025-04-09 12:40:25 +00:00
|
|
|
complies = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if complies {
|
|
|
|
comply_queries.push(#index);
|
2025-04-09 07:00:42 +00:00
|
|
|
}
|
2025-04-09 09:15:06 +00:00
|
|
|
}
|
2025-04-09 07:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn component_store_and_query(&self, index: usize) -> (TokenStream2, TokenStream2, Ident) {
|
|
|
|
let components = &self.components;
|
|
|
|
let entity_name = format_ident!("entity_{index}");
|
2025-04-09 11:32:50 +00:00
|
|
|
let component_names = self
|
2025-04-09 07:00:42 +00:00
|
|
|
.components
|
|
|
|
.iter()
|
|
|
|
.map(|component| format_ident!("q_{index}_{component}"))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
let query_ident = format_ident!("query_{index}");
|
2025-04-09 09:15:06 +00:00
|
|
|
let entity_ident = format_ident!("e_{index}");
|
2025-04-09 07:00:42 +00:00
|
|
|
|
|
|
|
(
|
|
|
|
quote! {
|
|
|
|
let #entity_name = &entities[#index];
|
2025-04-09 09:15:06 +00:00
|
|
|
let #entity_ident = #entity_name.as_entity();
|
2025-04-09 07:00:42 +00:00
|
|
|
|
|
|
|
#(
|
2025-04-09 11:32:50 +00:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
let #component_names = UnsafeComponentStore::from(
|
2025-04-09 09:15:06 +00:00
|
|
|
#entity_name.get_component::<#components>()?
|
2025-04-09 07:00:42 +00:00
|
|
|
);
|
|
|
|
)*
|
|
|
|
},
|
2025-04-09 11:32:50 +00:00
|
|
|
if component_names.len() == 1 {
|
|
|
|
let component_name = &component_names[0];
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
let #query_ident = Query {
|
|
|
|
entity: #entity_ident,
|
|
|
|
c: unsafe { #component_name.as_mut() },
|
|
|
|
filter: PhantomData,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quote! {
|
|
|
|
let #query_ident = Query {
|
|
|
|
entity: #entity_ident,
|
|
|
|
c: unsafe { ( #( #component_names.as_mut(), )* ) },
|
|
|
|
filter: PhantomData,
|
|
|
|
};
|
|
|
|
}
|
2025-04-09 07:00:42 +00:00
|
|
|
},
|
|
|
|
query_ident,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn components(&self) -> &Vec<Ident> {
|
|
|
|
&self.components
|
|
|
|
}
|
|
|
|
|
2025-04-09 05:25:23 +00:00
|
|
|
pub fn filter(&self) -> &Ident {
|
|
|
|
&self.filter
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ToTokens for Query {
|
2025-04-09 07:00:42 +00:00
|
|
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
2025-04-09 05:25:23 +00:00
|
|
|
let component_list = self
|
|
|
|
.components
|
|
|
|
.iter()
|
2025-04-09 07:00:42 +00:00
|
|
|
.map(|c| quote! { &mut #c })
|
2025-04-09 05:25:23 +00:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let components = if component_list.len() == 1 {
|
|
|
|
let component = &component_list[0];
|
|
|
|
quote! { #component }
|
|
|
|
} else {
|
2025-04-09 07:00:42 +00:00
|
|
|
quote! { ( #( #component_list, )* ) }
|
2025-04-09 05:25:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let filter = self.filter.clone();
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
TokenStream2::from(quote! {
|
2025-04-09 05:25:23 +00:00
|
|
|
Query<#components, #filter>
|
|
|
|
})
|
|
|
|
.to_tokens(tokens);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Update {
|
|
|
|
queries: Vec<Query>,
|
|
|
|
resources: Vec<Ident>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Update {
|
2025-04-09 07:00:42 +00:00
|
|
|
const MIN_RESOURCE: usize = 0;
|
|
|
|
const MIN_COMPONENTS: usize = 1;
|
|
|
|
|
2025-04-09 05:25:23 +00:00
|
|
|
pub fn new(queries: impl Iterator<Item = usize>, resources: usize) -> Self {
|
|
|
|
Self {
|
|
|
|
queries: queries
|
|
|
|
.enumerate()
|
|
|
|
.map(|(query, component_count)| {
|
|
|
|
Query::new(
|
2025-04-09 09:15:06 +00:00
|
|
|
(0..component_count).map(|component| format_ident!("C{component}Q{query}")),
|
2025-04-09 05:25:23 +00:00
|
|
|
query,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect(),
|
|
|
|
resources: (0..resources)
|
|
|
|
.map(|resource| format_ident!("Res{resource}"))
|
|
|
|
.collect(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
pub fn filter_types(&self) -> TokenStream2 {
|
|
|
|
let filter_types = self
|
|
|
|
.queries
|
|
|
|
.iter()
|
|
|
|
.map(|query| query.filter())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
if filter_types.len() == 1 {
|
|
|
|
let filter = &filter_types[0];
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#filter
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quote! {
|
|
|
|
( #( #filter_types, )* )
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 09:15:06 +00:00
|
|
|
pub fn filter_type_impls(&self) -> TokenStream2 {
|
|
|
|
let filter_types = self
|
|
|
|
.queries
|
|
|
|
.iter()
|
|
|
|
.map(|query| query.filter())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#( #filter_types, )*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
pub fn filter_requirements(&self) -> TokenStream2 {
|
|
|
|
let filter_requirements = self
|
|
|
|
.queries
|
|
|
|
.iter()
|
|
|
|
.map(|query| query.filter())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#( #filter_requirements: CheckFilter + 'static, )*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn query_types(&self) -> TokenStream2 {
|
|
|
|
let query_types = self
|
|
|
|
.queries
|
|
|
|
.iter()
|
|
|
|
.map(|query| query.types())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
( #( #query_types, )* )
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 09:15:06 +00:00
|
|
|
pub fn query_type_impls(&self) -> TokenStream2 {
|
|
|
|
let query_types = self
|
|
|
|
.queries
|
|
|
|
.iter()
|
|
|
|
.map(|query| query.components())
|
|
|
|
.flatten()
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#( #query_types, )*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
pub fn component_requirements(&self) -> TokenStream2 {
|
|
|
|
let component_requirements = self
|
|
|
|
.queries
|
|
|
|
.iter()
|
|
|
|
.map(|query| {
|
|
|
|
query.components.iter().map(|component| {
|
|
|
|
quote! {
|
|
|
|
#component: EntityComponent + ComponentDebug,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.flatten()
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
quote! {
|
2025-04-09 09:15:06 +00:00
|
|
|
#( #component_requirements )*
|
2025-04-09 07:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 09:15:06 +00:00
|
|
|
pub fn resource_store(&self) -> (TokenStream2, Vec<Ident>) {
|
|
|
|
let resource_types = &self.resources;
|
|
|
|
let resource_idents = self
|
|
|
|
.resources
|
|
|
|
.iter()
|
|
|
|
.map(|resource| format_ident!("res_{resource}"))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2025-04-09 12:40:25 +00:00
|
|
|
if self.resources.is_empty() {
|
|
|
|
(quote! {}, Vec::new())
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
quote! {
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
let (
|
|
|
|
#( #resource_idents, )*
|
|
|
|
): (
|
|
|
|
#( &mut #resource_types, )*
|
|
|
|
) = world.resources.get_mut()?;
|
|
|
|
},
|
|
|
|
resource_idents,
|
|
|
|
)
|
|
|
|
}
|
2025-04-09 09:15:06 +00:00
|
|
|
}
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
pub fn resource_types(&self) -> TokenStream2 {
|
|
|
|
let resource_types = &self.resources;
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
( #( #resource_types, )* )
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 09:15:06 +00:00
|
|
|
pub fn resource_type_impls(&self) -> TokenStream2 {
|
|
|
|
let resource_types = &self.resources;
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#( #resource_types, )*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
pub fn reource_requirement(&self) -> TokenStream2 {
|
|
|
|
let resource_types = &self.resources;
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#( #resource_types: ResourceTrait, )*
|
|
|
|
}
|
|
|
|
}
|
2025-04-09 05:25:23 +00:00
|
|
|
}
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
impl ToTokens for Update {
|
|
|
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
|
|
// generics to specify trait implementations
|
|
|
|
let filter_types = self.filter_types();
|
2025-04-09 09:15:06 +00:00
|
|
|
let filter_type_impls = self.filter_type_impls();
|
2025-04-09 07:00:42 +00:00
|
|
|
let query_types = self.query_types();
|
2025-04-09 09:15:06 +00:00
|
|
|
let query_type_impls = self.query_type_impls();
|
2025-04-09 07:00:42 +00:00
|
|
|
let resource_types = self.resource_types();
|
2025-04-09 09:15:06 +00:00
|
|
|
let resource_type_impls = self.resource_type_impls();
|
|
|
|
|
|
|
|
// panic!("{resource_type_impls}");
|
2025-04-09 07:00:42 +00:00
|
|
|
|
|
|
|
// function parameter
|
|
|
|
let queries = &self.queries;
|
|
|
|
let resources = if self.resources.is_empty() {
|
|
|
|
quote! {}
|
|
|
|
} else {
|
|
|
|
let resources = &self.resources;
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#( &mut #resources, )*
|
|
|
|
}
|
|
|
|
};
|
2025-04-09 05:25:23 +00:00
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
// trait requirements
|
|
|
|
let filter_requirements = self.filter_requirements();
|
|
|
|
let component_requirements = self.component_requirements();
|
|
|
|
let reource_requirement = self.reource_requirement();
|
2025-04-09 05:25:23 +00:00
|
|
|
|
2025-04-09 12:40:25 +00:00
|
|
|
let query_count = self.queries.len();
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
let verify_dedup = self
|
|
|
|
.queries
|
|
|
|
.iter()
|
|
|
|
.map(|query| query.dedup_checks())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let check_entities = self
|
|
|
|
.queries
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.map(|(index, query)| query.check_entity(index))
|
|
|
|
.collect::<Vec<_>>();
|
2025-04-09 05:25:23 +00:00
|
|
|
|
2025-04-09 09:15:06 +00:00
|
|
|
let component_stores_and_queries = self
|
2025-04-09 07:00:42 +00:00
|
|
|
.queries
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.map(|(index, query)| query.component_store_and_query(index))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2025-04-09 09:15:06 +00:00
|
|
|
let mut component_stores = Vec::new();
|
|
|
|
let mut query_structs = Vec::new();
|
|
|
|
let mut query_idents = Vec::new();
|
|
|
|
|
|
|
|
for (component_store, query_struct, query_ident) in component_stores_and_queries {
|
|
|
|
component_stores.push(component_store);
|
|
|
|
query_structs.push(query_struct);
|
|
|
|
query_idents.push(query_ident);
|
|
|
|
}
|
|
|
|
|
|
|
|
let (resource_store, resource_idents) = self.resource_store();
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
TokenStream2::from(quote! {
|
2025-04-09 09:15:06 +00:00
|
|
|
impl<Func, #filter_type_impls #query_type_impls #resource_type_impls> CreateArchetype<#query_types, #resource_types, Func, #filter_types> for Archetype
|
2025-04-09 07:00:42 +00:00
|
|
|
where
|
|
|
|
Func: Fn(&mut Commands, #( #queries, )* #resources ) -> Result<()> + Send + Sync + Clone + 'static,
|
|
|
|
#filter_requirements
|
|
|
|
#component_requirements
|
|
|
|
#reource_requirement
|
|
|
|
{
|
|
|
|
fn create(f: Func) -> Self {
|
|
|
|
#( #verify_dedup )*
|
|
|
|
|
|
|
|
Self {
|
2025-04-09 12:40:25 +00:00
|
|
|
queries: #query_count,
|
|
|
|
|
|
|
|
check_entity: Box::new(move |entity| {
|
|
|
|
let mut comply_queries = Vec::new();
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
#( #check_entities )*
|
|
|
|
|
2025-04-09 12:40:25 +00:00
|
|
|
if comply_queries.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(comply_queries)
|
|
|
|
}
|
2025-04-09 07:00:42 +00:00
|
|
|
}),
|
|
|
|
|
|
|
|
create_callback: Box::new(move |entities| {
|
2025-04-09 09:15:06 +00:00
|
|
|
#( #component_stores )*
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
let f = f.clone();
|
|
|
|
|
|
|
|
Ok(Box::new(move |commands, world| {
|
2025-04-09 09:15:06 +00:00
|
|
|
#( #query_structs )*
|
|
|
|
#resource_store
|
2025-04-09 07:00:42 +00:00
|
|
|
|
2025-04-09 11:32:50 +00:00
|
|
|
f(commands, #( #query_idents, )* #( #resource_idents, )* )
|
2025-04-09 07:00:42 +00:00
|
|
|
}))
|
|
|
|
}),
|
|
|
|
|
|
|
|
entities: IndexMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 09:15:06 +00:00
|
|
|
impl<Func, #filter_type_impls #query_type_impls #resource_type_impls> AddUpdates<#query_types, #resource_types, Func, #filter_types> for Updates
|
2025-04-09 07:00:42 +00:00
|
|
|
where
|
|
|
|
Func: Fn(&mut Commands, #( #queries, )* #resources ) -> Result<()> + Send + Sync + Clone + 'static,
|
|
|
|
#filter_requirements
|
|
|
|
#component_requirements
|
|
|
|
#reource_requirement
|
|
|
|
{
|
|
|
|
fn add_update(
|
|
|
|
&mut self,
|
|
|
|
priority: u32,
|
|
|
|
func: Func,
|
|
|
|
) -> Result<()> {
|
|
|
|
self.add(priority, Archetype::create(func))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 09:15:06 +00:00
|
|
|
impl<Func, #filter_type_impls #query_type_impls #resource_type_impls> AddUpdates<#query_types, #resource_types, Func, #filter_types> for WorldBuilder
|
2025-04-09 07:00:42 +00:00
|
|
|
where
|
|
|
|
Func: Fn(&mut Commands, #( #queries, )* #resources ) -> Result<()> + Send + Sync + Clone + 'static,
|
|
|
|
#filter_requirements
|
|
|
|
#component_requirements
|
|
|
|
#reource_requirement
|
|
|
|
{
|
|
|
|
fn add_update(
|
|
|
|
&mut self,
|
|
|
|
priority: u32,
|
|
|
|
func: Func,
|
|
|
|
) -> Result<()> {
|
|
|
|
self.updates.add_update(priority, func)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.to_tokens(tokens);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-09 12:58:43 +00:00
|
|
|
pub fn update(max_components: usize, max_resources: usize) -> TokenStream {
|
2025-04-09 11:32:50 +00:00
|
|
|
let mut queries: Vec<Vec<usize>> = Vec::new();
|
2025-04-09 09:15:06 +00:00
|
|
|
|
2025-04-09 11:32:50 +00:00
|
|
|
for c in Update::MIN_COMPONENTS..=max_components {
|
|
|
|
queries.push(vec![c]);
|
|
|
|
}
|
2025-04-09 09:15:06 +00:00
|
|
|
|
2025-04-09 11:32:50 +00:00
|
|
|
for x in Update::MIN_COMPONENTS..=max_components {
|
|
|
|
for y in Update::MIN_COMPONENTS..=max_components {
|
|
|
|
queries.push(vec![x, y]);
|
|
|
|
}
|
|
|
|
}
|
2025-04-09 09:15:06 +00:00
|
|
|
|
2025-04-09 11:32:50 +00:00
|
|
|
for x in Update::MIN_COMPONENTS..=max_components {
|
|
|
|
for y in Update::MIN_COMPONENTS..=max_components {
|
|
|
|
for z in Update::MIN_COMPONENTS..=max_components {
|
|
|
|
queries.push(vec![x, y, z]);
|
|
|
|
}
|
|
|
|
}
|
2025-04-09 09:15:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut resources = Vec::new();
|
|
|
|
|
2025-04-09 12:58:43 +00:00
|
|
|
for resource in Update::MIN_RESOURCE..=max_resources {
|
2025-04-09 09:15:06 +00:00
|
|
|
resources.push(resource);
|
|
|
|
}
|
|
|
|
|
2025-04-09 11:32:50 +00:00
|
|
|
let mut updates = Vec::new();
|
|
|
|
|
|
|
|
for query in queries.iter() {
|
|
|
|
for resource_count in resources.iter() {
|
|
|
|
updates.push(Update::new(query.iter().cloned(), *resource_count));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// updates.push(Update::new(vec![1].into_iter(), 0));
|
2025-04-09 07:00:42 +00:00
|
|
|
|
2025-04-09 12:40:25 +00:00
|
|
|
// let q = quote! {
|
|
|
|
// #( #updates )*
|
|
|
|
// };
|
|
|
|
|
|
|
|
// panic!("{q}");
|
|
|
|
|
2025-04-09 07:00:42 +00:00
|
|
|
TokenStream::from(quote! {
|
2025-04-09 09:15:06 +00:00
|
|
|
#( #updates )*
|
2025-04-09 05:25:23 +00:00
|
|
|
})
|
|
|
|
}
|