Experiments in applying Entity-Component-System patterns to durable data storage APIs.

Compare changes

Choose any two refs to compare.

+3611 -1492
+146
README.md
··· 1 + # `ecsdb` 2 + 3 + Experiments in applying Entity-Component-System patterns to durable data storage APIs. 4 + 5 + ## Usage 6 + 7 + ```rust 8 + use ecsdb::*; 9 + use ecsdb::query::*; 10 + use serde::{Serialize, Deserialize}; 11 + 12 + #[derive(Debug, Component, Serialize, Deserialize)] 13 + struct Headline(String); 14 + 15 + #[derive(Debug, Component, Serialize, Deserialize)] 16 + struct Date(String); 17 + 18 + let ecs = Ecs::open_in_memory().unwrap(); 19 + ecs.new_entity() 20 + .attach(Headline("My Note".into())) 21 + .attach(Date(chrono::Utc::now().to_rfc3339())); 22 + 23 + ecs.new_entity().attach(Headline("My Note".into())); 24 + 25 + for (entity, headline) in ecs.query::<(Entity, Headline), Without<Date>>().into_iter() { 26 + println!( 27 + "Entity '{}' (id={}) is missing component 'Date'", 28 + headline.0, 29 + entity.id() 30 + ); 31 + 32 + entity.destroy(); 33 + } 34 + ``` 35 + 36 + ## Components 37 + 38 + A component is a singular piece of data, similar to a column in a relational 39 + database. 40 + 41 + They must implement `serde::Serialize`, `serde::Deserialize` and 42 + `ecsdb::Component`, all of which can be `#[derive]`'d. 43 + 44 + ```rust 45 + # use serde::{Serialize, Deserialize}; 46 + # use ecsdb::Component; 47 + 48 + #[derive(Serialize, Deserialize, Component)] 49 + pub struct Marker; 50 + 51 + #[derive(Serialize, Deserialize, Component)] 52 + pub struct Date(chrono::DateTime<chrono::Utc>); 53 + 54 + #[derive(Serialize, Deserialize, Component)] 55 + pub enum State { 56 + New, 57 + Processing, 58 + Finished 59 + } 60 + ``` 61 + 62 + ## Entities 63 + 64 + ```rust 65 + # use ecsdb::{Component, Ecs, query::*}; 66 + # use serde::{Serialize, Deserialize}; 67 + # use ecsdb::doctests::*; 68 + 69 + # let ecs = Ecs::open_in_memory().unwrap(); 70 + 71 + // Attach components via `Entity::attach`: 72 + let entity = ecs.new_entity() 73 + .attach(State::New); 74 + 75 + // To retrieve an attached component, use `Entity::component`: 76 + let date: Option<Date> = entity.component::<Date>(); 77 + 78 + // To detach a component, use `Entity::detach`. Detaching a non-attached component is a no-op: 79 + entity.detach::<Date>(); 80 + 81 + // Re-attaching a component of the same type overwrites the old. Attaching the 82 + // same value is a no-op: 83 + entity.attach(State::Finished); 84 + ``` 85 + 86 + ## Systems 87 + 88 + Systems are functions operating on an `Ecs`. They can be registerd via 89 + `Ecs::register` and run via `Ecs::tick`. They can take a set of 'magic' 90 + parameters to access data in the `Ecs`: 91 + 92 + ```rust 93 + # use ecsdb::doctests::*; 94 + use ecsdb::query::{Query, With, Without}; 95 + 96 + // This system will attach `State::New` to all entities that have a `Marker` but 97 + // no `State` component 98 + fn process_marked_system(marked_entities: Query<Entity, (With<Marker>, Without<State>)>) { 99 + for entity in marked_entities.iter() { 100 + entity 101 + .attach(State::New) 102 + .detach::<Marker>(); 103 + } 104 + } 105 + 106 + // This system logs all entities that have both `Date` and `Marker` but no 107 + // `State` 108 + fn log_system(entities: Query<(EntityId, Date, Marker), Without<State>>) { 109 + for (entity_id, Date(date), _marker) in entities.iter() { 110 + println!("{entity_id} {date}"); 111 + } 112 + } 113 + 114 + let ecs = Ecs::open_in_memory().unwrap(); 115 + ecs.run_system(process_marked_system).unwrap(); 116 + ecs.run_system(log_system).unwrap(); 117 + ``` 118 + 119 + ## Scheduling 120 + 121 + `ecsdb::Schedule` allows scheduling of different systems by different criterias: 122 + 123 + ```rust 124 + # use ecsdb::doctests::*; 125 + # let ecs = Ecs::open_in_memory().unwrap(); 126 + 127 + fn sys_a() {} 128 + fn sys_b() {} 129 + 130 + use ecsdb::schedule::*; 131 + let mut schedule = Schedule::new(); 132 + 133 + // Run `sys_a` every 15 minutes 134 + schedule.add(sys_a, Every(chrono::Duration::minutes(15))); 135 + 136 + // Run `sys_b` after `sys_a` 137 + schedule.add(sys_b, After::system(sys_a)); 138 + 139 + // Run all pending systems 140 + schedule.tick(&ecs); 141 + ``` 142 + 143 + - `schedule::Every(Duration)` runs a system periodically 144 + - `schedule::After` runs one system after another finished 145 + - `schedule::Once` runs a system once per database 146 + - `schedule::Always` runs a system on every `Schedule::tick`
+18 -36
examples/basic.rs
··· 1 - use ecsdb::Component; 1 + use ecsdb::{query::Without, Component, Ecs, Entity}; 2 2 use serde::{Deserialize, Serialize}; 3 3 4 - #[derive(Debug, Serialize, Deserialize, Component)] 5 - struct DiaryEntry; 6 - #[derive(Debug, Serialize, Deserialize, Component)] 7 - struct Contents(String); 8 - #[derive(Debug, Serialize, Deserialize, Component)] 9 - struct Date(chrono::NaiveDate); 10 - 11 4 pub fn main() -> Result<(), anyhow::Error> { 12 - tracing_subscriber::fmt::init(); 5 + #[derive(Debug, Component, Serialize, Deserialize)] 6 + struct Headline(String); 7 + 8 + #[derive(Debug, Component, Serialize, Deserialize)] 9 + struct Date(chrono::DateTime<chrono::Utc>); 10 + 11 + let ecs = Ecs::open_in_memory()?; 12 + ecs.new_entity() 13 + .attach(Headline("My Note".into())) 14 + .attach(Date(chrono::Utc::now())); 13 15 14 - let db = ecsdb::Ecs::open("basic.sqlite")?; 16 + ecs.new_entity().attach(Headline("My Note".into())); 15 17 16 - let _entry = db 17 - .new_entity() 18 - .attach(DiaryEntry) 19 - .attach(Contents("Lorem ipsum ...".into())) 20 - .attach(Date(chrono::Utc::now().date_naive())); 21 - 22 - use ecsdb::query::*; 23 - 24 - println!("Total: {} entities", db.query::<()>().count()); 25 - 26 - let _ = db.query::<( 27 - DiaryEntry, 28 - Contents, 29 - Without<Date>, 30 - Or<(DiaryEntry, Contents)>, 31 - )>(); 32 - 33 - for entry in db.query::<(DiaryEntry, Contents)>() { 34 - println!("DiaryEntry",); 35 - println!(" id:\t{}", entry.id(),); 18 + for (entity, headline) in ecs.query::<(Entity, Headline), Without<Date>>() { 36 19 println!( 37 - " date:\t{}", 38 - entry.component::<Date>().unwrap().0.to_string(), 20 + "Entity '{}' (id={}) is missing component 'Date'", 21 + headline.0, 22 + entity.id() 39 23 ); 40 - println!(" text:\t{}", entry.component::<Contents>().unwrap().0); 41 - println!() 42 - } 43 24 44 - db.close().unwrap(); 25 + entity.destroy(); 26 + } 45 27 46 28 Ok(()) 47 29 }
+45
examples/diary.rs
··· 1 + use ecsdb::{Component, Entity, EntityId}; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(Debug, Serialize, Deserialize, Component)] 5 + struct DiaryEntry; 6 + #[derive(Debug, Serialize, Deserialize, Component)] 7 + struct Contents(String); 8 + #[derive(Debug, Serialize, Deserialize, Component)] 9 + struct Date(chrono::NaiveDate); 10 + 11 + pub fn main() -> Result<(), anyhow::Error> { 12 + tracing_subscriber::fmt::init(); 13 + 14 + let db = ecsdb::Ecs::open("basic.sqlite")?; 15 + 16 + let _entry = db 17 + .new_entity() 18 + .attach(DiaryEntry) 19 + .attach(Contents("Lorem ipsum ...".into())) 20 + .attach(Date(chrono::Utc::now().date_naive())); 21 + 22 + use ecsdb::query::*; 23 + 24 + println!("Total: {} entities", db.query::<EntityId, ()>().count()); 25 + 26 + let _ = db.query::<Entity, ( 27 + With<(DiaryEntry, Contents)>, 28 + Without<Date>, 29 + Or<(With<DiaryEntry>, With<Contents>)>, 30 + )>(); 31 + 32 + for (id, _, Date(date), Contents(contents)) in 33 + db.query::<(EntityId, DiaryEntry, Date, Contents), ()>() 34 + { 35 + println!("DiaryEntry",); 36 + println!(" id:\t{}", id); 37 + println!(" date:\t{date}",); 38 + println!(" text:\t{contents}"); 39 + println!() 40 + } 41 + 42 + db.close().unwrap(); 43 + 44 + Ok(()) 45 + }
+70 -70
src/hierarchy.rs
··· 1 - use std::iter; 2 - 3 - use ecsdb_derive::Component; 4 - use serde::{Deserialize, Serialize}; 5 - 6 - use crate::{self as ecsdb, Ecs, Entity, EntityId}; 7 - 8 - #[derive(Component, Clone, Copy, Debug, Serialize, Deserialize)] 9 - pub struct BelongsTo(pub EntityId); 10 - 11 - impl Ecs { 12 - pub fn direct_children<'a>( 13 - &'a self, 14 - entity: EntityId, 15 - ) -> impl Iterator<Item = Entity<'a>> + 'a { 16 - self.find(BelongsTo(entity)) 17 - } 18 - 19 - pub fn all_children<'a>(&'a self, entity: EntityId) -> impl Iterator<Item = Entity<'a>> + 'a { 20 - let mut stack = self.direct_children(entity).collect::<Vec<_>>(); 21 - iter::from_fn(move || -> Option<Entity<'a>> { 22 - let Some(entity) = stack.pop() else { 23 - return None; 24 - }; 25 - 26 - for entity in self.direct_children(entity.id()) { 27 - stack.push(entity); 28 - } 29 - 30 - Some(entity) 31 - }) 32 - } 33 - } 34 - 35 - #[cfg(test)] 36 - mod tests { 37 - use super::*; 38 - 39 - #[test] 40 - fn belongs_to() { 41 - #[derive(Debug, Serialize, Deserialize, Component)] 42 - struct A; 43 - 44 - #[derive(Debug, Serialize, Deserialize, PartialEq, Component)] 45 - struct B; 46 - 47 - let db = Ecs::open_in_memory().unwrap(); 48 - 49 - let parent = db.new_entity().attach(A); 50 - let child1 = db.new_entity().attach(A).attach(BelongsTo(parent.id())); 51 - let child2 = db.new_entity().attach(A).attach(BelongsTo(child1.id())); 52 - 53 - assert_eq!( 54 - parent.direct_children().map(|e| e.id()).collect::<Vec<_>>(), 55 - vec![child1.id()] 56 - ); 57 - 58 - assert_eq!( 59 - parent.all_children().map(|e| e.id()).collect::<Vec<_>>(), 60 - vec![child1.id(), child2.id()] 61 - ); 62 - 63 - assert_eq!( 64 - child1.all_children().map(|e| e.id()).collect::<Vec<_>>(), 65 - vec![child2.id()] 66 - ); 67 - 68 - assert!(child2.all_children().next().is_none()); 69 - } 70 - } 1 + // use std::iter; 2 + 3 + // use ecsdb_derive::Component; 4 + // use serde::{Deserialize, Serialize}; 5 + 6 + // use crate::{self as ecsdb, Ecs, Entity, EntityId}; 7 + 8 + // #[derive(Component, Clone, Copy, Debug, Serialize, Deserialize)] 9 + // pub struct BelongsTo(pub EntityId); 10 + 11 + // impl Ecs { 12 + // pub fn direct_children<'a>( 13 + // &'a self, 14 + // entity: EntityId, 15 + // ) -> impl Iterator<Item = Entity<'a>> + 'a { 16 + // self.find(BelongsTo(entity)) 17 + // } 18 + 19 + // pub fn all_children<'a>(&'a self, entity: EntityId) -> impl Iterator<Item = Entity<'a>> + 'a { 20 + // let mut stack = self.direct_children(entity).collect::<Vec<_>>(); 21 + // iter::from_fn(move || -> Option<Entity<'a>> { 22 + // let Some(entity) = stack.pop() else { 23 + // return None; 24 + // }; 25 + 26 + // for entity in self.direct_children(entity.id()) { 27 + // stack.push(entity); 28 + // } 29 + 30 + // Some(entity) 31 + // }) 32 + // } 33 + // } 34 + 35 + // #[cfg(test)] 36 + // mod tests { 37 + // use super::*; 38 + 39 + // #[test] 40 + // fn belongs_to() { 41 + // #[derive(Debug, Serialize, Deserialize, Component)] 42 + // struct A; 43 + 44 + // #[derive(Debug, Serialize, Deserialize, PartialEq, Component)] 45 + // struct B; 46 + 47 + // let db = Ecs::open_in_memory().unwrap(); 48 + 49 + // let parent = db.new_entity().attach(A); 50 + // let child1 = db.new_entity().attach(A).attach(BelongsTo(parent.id())); 51 + // let child2 = db.new_entity().attach(A).attach(BelongsTo(child1.id())); 52 + 53 + // assert_eq!( 54 + // parent.direct_children().map(|e| e.id()).collect::<Vec<_>>(), 55 + // vec![child1.id()] 56 + // ); 57 + 58 + // assert_eq!( 59 + // parent.all_children().map(|e| e.id()).collect::<Vec<_>>(), 60 + // vec![child1.id(), child2.id()] 61 + // ); 62 + 63 + // assert_eq!( 64 + // child1.all_children().map(|e| e.id()).collect::<Vec<_>>(), 65 + // vec![child2.id()] 66 + // ); 67 + 68 + // assert!(child2.all_children().next().is_none()); 69 + // } 70 + // }
+19 -18
src/resource.rs
··· 19 19 20 20 21 21 22 - 23 - 24 - 25 - .prepare("select data from resources where name = ?1")?; 26 - let row = query 22 + let name = R::resource_name(); 23 + let mut query = self 24 + .conn 25 + .prepare_cached("select data from resources where name = ?1")?; 26 + query 27 27 .query_and_then(params![name], |row| { 28 - row.get::<_, rusqlite::types::Value>("data") 28 + let data = row.get_ref("data")?; 29 + Ok(R::from_rusqlite(&rusqlite::types::ToSqlOutput::Borrowed( 30 + data, 31 + ))?) 29 32 })? 30 - .next(); 31 - 32 - match row { 33 - None => Ok(None), 34 - Some(Ok(data)) => { 35 - let component = R::from_rusqlite(data)?; 36 - Ok(Some(component)) 37 - } 38 - _other => panic!(), 39 - } 33 + .next() 34 + .transpose() 40 35 } 41 36 42 37 pub fn resource_mut<'a, R: Resource + Default>(&'a mut self) -> impl DerefMut<Target = R> + 'a { ··· 56 51 57 52 pub fn try_attach_resource<R: Resource>(&self, resource: R) -> Result<(), Error> { 58 53 let name = R::component_name(); 59 - let data = R::to_rusqlite(resource)?; 54 + let data = R::to_rusqlite(&resource)?; 60 55 61 56 self.conn.execute( 62 - "insert or replace into resources (name, data) values (?1, ?2)", 57 + " 58 + insert into resources (name, data) values (?1, ?2) 59 + on conflict (name) do update set data = excluded.data 60 + ", 61 + params![name, data], 62 + )?; 63 +
+455
src/query/ir.rs
··· 1 + use std::{ 2 + collections::{BTreeMap, HashSet}, 3 + marker::PhantomData, 4 + }; 5 + 6 + use rusqlite::ToSql; 7 + 8 + use crate::EntityId; 9 + 10 + #[derive(Debug)] 11 + pub enum OrderBy { 12 + Asc, 13 + Desc, 14 + } 15 + 16 + #[derive(Debug)] 17 + pub struct Query { 18 + pub filter: FilterExpression, 19 + pub order_by: OrderBy, 20 + } 21 + 22 + pub(crate) type Sql = String; 23 + pub(crate) type SqlParameters = Vec<(String, Box<dyn ToSql>)>; 24 + 25 + impl Query { 26 + pub(crate) fn into_sql(self) -> (Sql, SqlParameters) { 27 + let mut select = self.filter.simplify().sql_query(); 28 + let order_by = match self.order_by { 29 + OrderBy::Asc => "order by entity asc", 30 + OrderBy::Desc => "order by entity desc", 31 + }; 32 + 33 + select.sql = format!("{} {}", select.sql, order_by); 34 + 35 + (select.sql, select.placeholders) 36 + } 37 + } 38 + 39 + #[derive(Debug, PartialEq)] 40 + pub enum FilterExpression { 41 + None, 42 + 43 + And(Vec<FilterExpression>), 44 + Or(Vec<FilterExpression>), 45 + 46 + EntityId(EntityId), 47 + WithComponent(String), 48 + WithoutComponent(String), 49 + 50 + WithComponentData(String, rusqlite::types::Value), 51 + WithComponentDataRange { 52 + component: String, 53 + start: rusqlite::types::Value, 54 + end: rusqlite::types::Value, 55 + }, 56 + } 57 + 58 + impl FilterExpression { 59 + pub fn none() -> Self { 60 + Self::None 61 + } 62 + 63 + pub fn with_component(c: &str) -> Self { 64 + Self::WithComponent(c.to_owned()) 65 + } 66 + 67 + pub fn without_component(c: &str) -> Self { 68 + Self::WithoutComponent(c.to_owned()) 69 + } 70 + 71 + pub fn with_component_data(c: &str, value: rusqlite::types::Value) -> Self { 72 + Self::WithComponentData(c.to_owned(), value) 73 + } 74 + 75 + pub fn entity(e: EntityId) -> Self { 76 + Self::EntityId(e) 77 + } 78 + 79 + pub fn and(exprs: impl IntoIterator<Item = FilterExpression>) -> Self { 80 + Self::And(exprs.into_iter().collect()) 81 + } 82 + 83 + pub fn or(exprs: impl IntoIterator<Item = FilterExpression>) -> Self { 84 + Self::Or(exprs.into_iter().collect()) 85 + } 86 + } 87 + 88 + impl FilterExpression { 89 + pub fn simplify(self) -> Self { 90 + use FilterExpression::*; 91 + 92 + match self { 93 + Or(exprs) => { 94 + let exprs = exprs 95 + .into_iter() 96 + .filter(|e| *e != None) 97 + // Flatten nested `Or` 98 + .flat_map(|e| match e { 99 + Or(exprs) => exprs, 100 + other => vec![other], 101 + }) 102 + .map(Self::simplify); 103 + 104 + let mut deduplicated = Vec::new(); 105 + for expr in exprs { 106 + if !deduplicated.contains(&expr) { 107 + deduplicated.push(expr) 108 + } 109 + } 110 + 111 + Or(deduplicated) 112 + } 113 + And(exprs) => { 114 + let exprs = exprs 115 + .into_iter() 116 + .filter(|e| *e != None) 117 + // Flatten nested `And` 118 + .flat_map(|e| match e { 119 + And(exprs) => exprs, 120 + other => vec![other], 121 + }) 122 + .map(Self::simplify); 123 + 124 + let mut deduplicated = Vec::new(); 125 + for expr in exprs { 126 + if !deduplicated.contains(&expr) { 127 + deduplicated.push(expr) 128 + } 129 + } 130 + 131 + And(deduplicated) 132 + } 133 + other => other, 134 + } 135 + } 136 + } 137 + 138 + impl FilterExpression { 139 + fn sql_query(&self) -> SqlFragment<Select> { 140 + let filter = self.where_clause(); 141 + let sql = format!( 142 + "select distinct entity from components where {}", 143 + filter.sql 144 + ); 145 + 146 + SqlFragment { 147 + kind: PhantomData, 148 + sql, 149 + placeholders: filter.placeholders, 150 + } 151 + } 152 + 153 + fn where_clause(&self) -> SqlFragment<Where> { 154 + match self { 155 + FilterExpression::None => SqlFragment::new("true", []), 156 + 157 + FilterExpression::WithComponent(c) => SqlFragment::new( 158 + "(select true from components c2 where c2.entity = components.entity and c2.component = ?1)", 159 + [("?1", Box::new(c.to_owned()) as _)], 160 + ), 161 + 162 + FilterExpression::WithoutComponent(c) => SqlFragment::new( 163 + "(select true from components c2 where c2.entity = components.entity and c2.component = ?1) is null", 164 + [("?1", Box::new(c.to_owned()) as _)], 165 + ), 166 + 167 + FilterExpression::EntityId(id) => { 168 + SqlFragment::new("entity = ?1", [("?1", Box::new(*id) as _)]) 169 + } 170 + 171 + FilterExpression::WithComponentData(component, data) => { 172 + if matches!(data, rusqlite::types::Value::Null) { 173 + SqlFragment::new( 174 + "(select true from components c2 where c2.entity = components.entity and c2.component = ?1 and c2.data is null)", 175 + [("?1", Box::new(component.to_owned()) as _)], 176 + ) 177 + } else { 178 + SqlFragment::new( 179 + "(select true from components c2 where c2.entity = components.entity and c2.component = ?1 and c2.data = ?2)", 180 + [ 181 + ("?1", Box::new(component.to_owned()) as _), 182 + ("?2", Box::new(data.to_owned()) as _), 183 + ], 184 + ) 185 + } 186 + } 187 + 188 + FilterExpression::WithComponentDataRange { 189 + component, 190 + start, 191 + end, 192 + } => { 193 + use rusqlite::types::Value; 194 + 195 + let (range_filter_condition, mut params) = match (start, end) { 196 + (Value::Null, Value::Null) => ( 197 + "c2.data is null", 198 + vec![] 199 + ), 200 + (Value::Null, end) => ( 201 + "velodb_extract_data(c2.data) <= velodb_extract_data(?2)", 202 + vec![ 203 + ("?2", Box::new(end.to_owned()) as _), 204 + ], 205 + ), 206 + (start, Value::Null) => ( 207 + "velodb_extract_data(c2.data) >= velodb_extract_data(?2)", 208 + vec![ 209 + ("?2", Box::new(start.to_owned()) as _), 210 + ], 211 + ), 212 + 213 + (start, end) => ( 214 + "velodb_extract_data(c2.data) between velodb_extract_data(?2) and velodb_extract_data(?3)", 215 + vec![ 216 + ("?2", Box::new(start.to_owned()) as _), 217 + ("?3", Box::new(end.to_owned()) as _), 218 + ], 219 + ), 220 + }; 221 + 222 + let sql = format!("(select true from components c2 where c2.entity = components.entity and c2.component = ?component and {range_filter_condition})"); 223 + params.push(("?component", Box::new(component.to_owned()) as _)); 224 + SqlFragment::new(&sql, params) 225 + } 226 + 227 + FilterExpression::And(exprs) => Self::combine_exprs("and", exprs), 228 + FilterExpression::Or(exprs) => Self::combine_exprs("or", exprs), 229 + } 230 + } 231 + 232 + fn combine_exprs(via: &str, exprs: &[FilterExpression]) -> SqlFragment<Where> { 233 + let mut exprs = exprs.iter().map(|e| e.where_clause()); 234 + 235 + let Some(fragment) = exprs.next() else { 236 + return FilterExpression::None.where_clause(); 237 + }; 238 + 239 + let mut last_placeholder = 0; 240 + 241 + let mut rename_fn = |_old| { 242 + last_placeholder += 1; 243 + let n = last_placeholder; 244 + format!(":{n}") 245 + }; 246 + 247 + let mut fragment = fragment.rename_identifier(&mut rename_fn); 248 + 249 + for expr in exprs { 250 + let expr = expr.rename_identifier(&mut rename_fn); 251 + fragment.sql = format!("{} {via} {}", fragment.sql, expr.sql); 252 + fragment.placeholders.extend(expr.placeholders.into_iter()); 253 + } 254 + 255 + fragment.sql = format!("({})", fragment.sql); 256 + 257 + assert_eq!( 258 + fragment.placeholders.len(), 259 + fragment 260 + .placeholders 261 + .iter() 262 + .map(|(p, _)| p) 263 + .collect::<HashSet<_>>() 264 + .len() 265 + ); 266 + 267 + fragment 268 + } 269 + } 270 + 271 + #[derive(Debug)] 272 + struct Where; 273 + #[derive(Debug)] 274 + struct Select; 275 + 276 + struct SqlFragment<T> { 277 + pub kind: PhantomData<T>, 278 + pub sql: String, 279 + pub placeholders: Vec<(String, Box<dyn ToSql>)>, 280 + } 281 + 282 + impl<T> SqlFragment<T> { 283 + pub fn new<'a>( 284 + sql: &str, 285 + placeholders: impl IntoIterator<Item = (&'a str, Box<dyn ToSql>)>, 286 + ) -> Self { 287 + Self { 288 + kind: PhantomData, 289 + sql: sql.to_owned(), 290 + placeholders: placeholders 291 + .into_iter() 292 + .map(|(p, v)| (p.to_string(), v)) 293 + .collect(), 294 + } 295 + } 296 + 297 + pub fn rename_identifier(mut self, mut fun: impl FnMut(String) -> String) -> Self { 298 + let mappings: BTreeMap<_, _> = self 299 + .placeholders 300 + .iter() 301 + .map(|(p, _)| (p.to_owned(), fun(p.to_owned()))) 302 + .collect(); 303 + 304 + for (idx, (a, _)) in mappings.iter().enumerate() { 305 + self.sql = self.sql.replace(a, &format!(":{idx}:")); 306 + } 307 + 308 + for (idx, (_, b)) in mappings.iter().enumerate() { 309 + self.sql = self.sql.replace(&format!(":{idx}:"), b); 310 + } 311 + 312 + for (placeholder, _value) in self.placeholders.iter_mut() { 313 + *placeholder = mappings[placeholder].clone(); 314 + } 315 + 316 + self 317 + } 318 + } 319 + 320 + impl<T: std::fmt::Debug> std::fmt::Debug for SqlFragment<T> { 321 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 322 + f.debug_struct(&format!("SqlFragment<{}>", std::any::type_name::<T>())) 323 + .field("sql", &self.sql) 324 + .field( 325 + "placeholders", 326 + &self 327 + .placeholders 328 + .iter() 329 + .map(|(p, v)| { 330 + use rusqlite::types::{ToSqlOutput, Value}; 331 + let v: Value = match v.to_sql().unwrap() { 332 + ToSqlOutput::Borrowed(v) => Value::from(v), 333 + ToSqlOutput::Owned(v) => v, 334 + other => unreachable!("Unexpected ToSqlOutput {other:?}"), 335 + }; 336 + 337 + (p, v) 338 + }) 339 + .collect::<Vec<_>>(), 340 + ) 341 + .finish() 342 + } 343 + } 344 + 345 + #[cfg(test)] 346 + mod test { 347 + use insta::assert_debug_snapshot; 348 + 349 + use crate::query::ir::FilterExpression; 350 + 351 + fn cases() -> Vec<FilterExpression> { 352 + vec![ 353 + FilterExpression::none(), 354 + FilterExpression::with_component("ecsdb::Test"), 355 + FilterExpression::without_component("ecsdb::Test"), 356 + FilterExpression::entity(42), 357 + FilterExpression::and([ 358 + FilterExpression::with_component("ecsdb::Test"), 359 + FilterExpression::entity(42), 360 + ]), 361 + FilterExpression::and([ 362 + FilterExpression::with_component("ecsdb::Test"), 363 + FilterExpression::entity(42), 364 + FilterExpression::with_component("ecsdb::Test"), 365 + FilterExpression::entity(42), 366 + ]), 367 + FilterExpression::and([ 368 + FilterExpression::with_component("ecsdb::Foo"), 369 + FilterExpression::without_component("ecsdb::Bar"), 370 + ]), 371 + FilterExpression::or([ 372 + FilterExpression::with_component("ecsdb::Test"), 373 + FilterExpression::entity(42), 374 + ]), 375 + FilterExpression::or([ 376 + FilterExpression::with_component("ecsdb::Test"), 377 + FilterExpression::entity(42), 378 + FilterExpression::with_component("ecsdb::Test"), 379 + FilterExpression::entity(42), 380 + ]), 381 + FilterExpression::or([ 382 + FilterExpression::with_component("ecsdb::Foo"), 383 + FilterExpression::without_component("ecsdb::Bar"), 384 + ]), 385 + FilterExpression::or([ 386 + FilterExpression::and([ 387 + FilterExpression::entity(42), 388 + FilterExpression::with_component("ecsdb::Test"), 389 + ]), 390 + FilterExpression::and([ 391 + FilterExpression::entity(23), 392 + FilterExpression::with_component("ecsdb::Foo"), 393 + FilterExpression::without_component("ecsdb::Bar"), 394 + ]), 395 + ]), 396 + FilterExpression::and([ 397 + FilterExpression::and([ 398 + FilterExpression::entity(42), 399 + FilterExpression::with_component("ecsdb::Test"), 400 + ]), 401 + FilterExpression::and([ 402 + FilterExpression::entity(23), 403 + FilterExpression::with_component("ecsdb::Foo"), 404 + FilterExpression::without_component("ecsdb::Bar"), 405 + ]), 406 + ]), 407 + FilterExpression::or([ 408 + FilterExpression::or([ 409 + FilterExpression::entity(42), 410 + FilterExpression::with_component("ecsdb::Test"), 411 + ]), 412 + FilterExpression::and([ 413 + FilterExpression::entity(23), 414 + FilterExpression::with_component("ecsdb::Foo"), 415 + FilterExpression::without_component("ecsdb::Bar"), 416 + ]), 417 + ]), 418 + ] 419 + } 420 + 421 + #[test] 422 + fn simplify() { 423 + for case in cases() { 424 + let expr = format!("{case:?}.simplify()"); 425 + insta::with_settings!({omit_expression => true, description => &expr, snapshot_suffix => &expr}, { 426 + assert_debug_snapshot!(case.simplify()); 427 + }); 428 + } 429 + } 430 + 431 + #[test] 432 + pub fn filter_expression_where_clause() { 433 + for case in cases() { 434 + let expr = format!("{case:?}.where_clause()"); 435 + insta::with_settings!({omit_expression => true, description => &expr, snapshot_suffix => &expr}, { 436 + assert_debug_snapshot!(case.where_clause()); 437 + }); 438 + } 439 + } 440 + 441 + #[test] 442 + pub fn filter_expression_sql_query() { 443 + for case in cases() { 444 + let expr = format!("{case:?}.sql_query()"); 445 + insta::with_settings!({omit_expression => true, description => &expr, snapshot_suffix => &expr}, { 446 + assert_debug_snapshot!(case.sql_query()); 447 + }); 448 + 449 + let expr = format!("{case:?}.simplify().sql_query()"); 450 + insta::with_settings!({omit_expression => true, description => &expr, snapshot_suffix => &expr}, { 451 + assert_debug_snapshot!(case.simplify().sql_query()); 452 + }); 453 + } 454 + } 455 + }
+468
src/query/mod.rs
··· 1 + use tracing::trace; 2 + 3 + use crate::{Entity, EntityId, component::Bundle}; 4 + 5 + use super::Component; 6 + use std::marker::PhantomData; 7 + 8 + pub(crate) mod ir; 9 + 10 + pub trait QueryData { 11 + type Output<'a>: Sized; 12 + fn from_entity<'a>(e: Entity<'a>) -> Option<Self::Output<'a>>; 13 + fn filter_expression() -> ir::FilterExpression; 14 + } 15 + 16 + pub trait QueryFilter { 17 + fn filter_expression() -> ir::FilterExpression; 18 + } 19 + 20 + /// Matches if any of the Filters in `C` matches 21 + pub struct AnyOf<C>(PhantomData<C>); 22 + 23 + /// Matches if Entity has all components in `C` 24 + pub struct With<C>(PhantomData<C>); 25 + 26 + /// Matches if Entity has none of the components in `C` 27 + pub struct Without<C>(PhantomData<C>); 28 + 29 + /// Matches if any of the filters in `F` match 30 + pub struct Or<F>(F); 31 + 32 + pub trait QueryFilterValue: Sized { 33 + fn filter_expression(&self) -> ir::FilterExpression; 34 + } 35 + 36 + pub struct Query<'a, D = Entity<'a>, F = (), V = ()> 37 + where 38 + F: ?Sized, 39 + { 40 + pub(crate) ecs: &'a crate::Ecs, 41 + pub(crate) data: PhantomData<D>, 42 + pub(crate) filter: PhantomData<F>, 43 + pub(crate) filter_value: V, 44 + } 45 + 46 + impl<'a, C, F> Query<'a, C, F, ()> { 47 + pub fn new(ecs: &'a crate::Ecs) -> Self { 48 + Self { 49 + ecs, 50 + data: PhantomData, 51 + filter: PhantomData, 52 + filter_value: (), 53 + } 54 + } 55 + } 56 + 57 + impl<'a, C, F, V> Query<'a, C, F, V> { 58 + pub fn with_filter(ecs: &'a crate::Ecs, filter_value: V) -> Self { 59 + Self { 60 + ecs, 61 + data: PhantomData, 62 + filter: PhantomData, 63 + filter_value, 64 + } 65 + } 66 + } 67 + 68 + impl<'a, D, F, V> Query<'a, D, F, V> 69 + where 70 + D: QueryData + 'a, 71 + F: QueryFilter, 72 + V: QueryFilterValue, 73 + { 74 + pub fn iter(&self) -> impl Iterator<Item = D::Output<'a>> + 'a + use<'a, D, F, V> { 75 + self.try_iter().unwrap() 76 + } 77 + 78 + pub fn reverse_iter(&self) -> impl Iterator<Item = D::Output<'a>> + 'a + use<'a, D, F, V> { 79 + self.try_reverse_iter().unwrap() 80 + } 81 + 82 + pub fn entities(&self) -> impl Iterator<Item = Entity<'a>> + 'a + use<'a, D, F, V> { 83 + self.try_entities().unwrap() 84 + } 85 + 86 + pub fn reverse_entities(&self) -> impl Iterator<Item = Entity<'a>> + 'a + use<'a, D, F, V> { 87 + self.try_reverse_entities().unwrap() 88 + } 89 + 90 + pub fn try_iter( 91 + &self, 92 + ) -> Result<impl Iterator<Item = D::Output<'a>> + 'a + use<'a, D, F, V>, crate::Error> { 93 + Ok(self.try_entities()?.filter_map(|e| D::from_entity(e))) 94 + } 95 + 96 + pub fn try_reverse_iter( 97 + &self, 98 + ) -> Result<impl Iterator<Item = D::Output<'a>> + 'a + use<'a, D, F, V>, crate::Error> { 99 + Ok(self 100 + .try_reverse_entities()? 101 + .filter_map(|e| D::from_entity(e))) 102 + } 103 + 104 + pub fn try_entities( 105 + &self, 106 + ) -> Result<impl Iterator<Item = Entity<'a>> + 'a + use<'a, D, F, V>, crate::Error> { 107 + let mut query = self.as_sql_query(); 108 + 109 + query.order_by = ir::OrderBy::Asc; 110 + self.ecs.fetch::<Entity>(query) 111 + } 112 + 113 + pub fn try_reverse_entities( 114 + &self, 115 + ) -> Result<impl Iterator<Item = Entity<'a>> + 'a + use<'a, D, F, V>, crate::Error> { 116 + let mut query = self.as_sql_query(); 117 + query.order_by = ir::OrderBy::Desc; 118 + self.ecs.fetch::<Entity>(query) 119 + } 120 + 121 + #[tracing::instrument(level = "debug", skip_all)] 122 + fn as_sql_query(&self) -> ir::Query { 123 + let filter = ir::FilterExpression::and([ 124 + D::filter_expression(), 125 + F::filter_expression(), 126 + self.filter_value.filter_expression(), 127 + ]); 128 + 129 + trace!(?filter); 130 + 131 + ir::Query { 132 + filter, 133 + order_by: ir::OrderBy::Asc, 134 + } 135 + } 136 + } 137 + 138 + impl QueryData for () { 139 + type Output<'a> = (); 140 + 141 + fn from_entity<'a>(_e: Entity<'a>) -> Option<Self::Output<'a>> { 142 + Some(()) 143 + } 144 + 145 + fn filter_expression() -> ir::FilterExpression { 146 + ir::FilterExpression::none() 147 + } 148 + } 149 + 150 + impl QueryData for Entity<'_> { 151 + type Output<'a> = Entity<'a>; 152 + 153 + fn from_entity<'a>(e: Entity<'a>) -> Option<Self::Output<'a>> { 154 + Some(e) 155 + } 156 + 157 + fn filter_expression() -> ir::FilterExpression { 158 + ir::FilterExpression::none() 159 + } 160 + } 161 + 162 + impl QueryData for EntityId { 163 + type Output<'a> = EntityId; 164 + 165 + fn from_entity<'a>(e: Entity<'a>) -> Option<Self::Output<'a>> { 166 + Some(e.id()) 167 + } 168 + 169 + fn filter_expression() -> ir::FilterExpression { 170 + ir::FilterExpression::none() 171 + } 172 + } 173 + 174 + impl<C: Component> QueryData for C { 175 + type Output<'a> = C; 176 + 177 + fn from_entity<'a>(e: Entity<'a>) -> Option<Self::Output<'a>> { 178 + e.component::<C>() 179 + } 180 + 181 + fn filter_expression() -> ir::FilterExpression { 182 + ir::FilterExpression::with_component(C::component_name()) 183 + } 184 + } 185 + 186 + impl QueryFilter for () { 187 + fn filter_expression() -> ir::FilterExpression { 188 + ir::FilterExpression::none() 189 + } 190 + } 191 + 192 + impl<C: Bundle> Default for AnyOf<C> { 193 + fn default() -> Self { 194 + Self(PhantomData) 195 + } 196 + } 197 + 198 + impl<C: Bundle> Default for With<C> { 199 + fn default() -> Self { 200 + Self(PhantomData) 201 + } 202 + } 203 + 204 + impl<C: Bundle> Default for Without<C> { 205 + fn default() -> Self { 206 + Self(PhantomData) 207 + } 208 + } 209 + 210 + impl<F: QueryFilter + Default> Default for Or<F> { 211 + fn default() -> Self { 212 + Self(F::default()) 213 + } 214 + } 215 + 216 + impl<C: Component> QueryFilter for C { 217 + fn filter_expression() -> ir::FilterExpression { 218 + ir::FilterExpression::with_component(C::component_name()) 219 + } 220 + } 221 + 222 + impl<C: Bundle> QueryFilter for AnyOf<C> { 223 + fn filter_expression() -> ir::FilterExpression { 224 + ir::FilterExpression::or( 225 + C::component_names() 226 + .iter() 227 + .map(|c| ir::FilterExpression::with_component(c)), 228 + ) 229 + } 230 + } 231 + 232 + impl<C: Component> QueryFilter for With<C> { 233 + fn filter_expression() -> ir::FilterExpression { 234 + ir::FilterExpression::with_component(C::component_name()) 235 + } 236 + } 237 + 238 + impl<C: Component> QueryFilter for Without<C> { 239 + fn filter_expression() -> ir::FilterExpression { 240 + ir::FilterExpression::without_component(C::component_name()) 241 + } 242 + } 243 + 244 + impl QueryFilterValue for () { 245 + fn filter_expression(&self) -> ir::FilterExpression { 246 + ir::FilterExpression::None 247 + } 248 + } 249 + 250 + impl QueryFilterValue for EntityId { 251 + fn filter_expression(&self) -> ir::FilterExpression { 252 + ir::FilterExpression::entity(*self) 253 + } 254 + } 255 + 256 + #[derive(PartialEq, Eq, Debug)] 257 + pub struct ComponentName(pub String); 258 + 259 + impl QueryFilterValue for ComponentName { 260 + fn filter_expression(&self) -> ir::FilterExpression { 261 + ir::FilterExpression::WithComponent(self.0.clone()) 262 + } 263 + } 264 + 265 + impl<V: QueryFilterValue> QueryFilterValue for &[V] { 266 + fn filter_expression(&self) -> ir::FilterExpression { 267 + ir::FilterExpression::And(self.iter().map(V::filter_expression).collect()) 268 + } 269 + } 270 + 271 + impl<C: Component> QueryFilterValue for C { 272 + fn filter_expression(&self) -> ir::FilterExpression { 273 + use rusqlite::types::ToSqlOutput; 274 + 275 + let value = match C::to_rusqlite(self).unwrap() { 276 + ToSqlOutput::Borrowed(v) => v.to_owned().into(), 277 + ToSqlOutput::Owned(v) => v, 278 + other => unreachable!("{other:?}"), 279 + }; 280 + 281 + ir::FilterExpression::with_component_data(C::component_name(), value) 282 + } 283 + } 284 + 285 + impl<C: QueryFilterValue + Component> QueryFilterValue for std::ops::Range<C> { 286 + fn filter_expression(&self) -> ir::FilterExpression { 287 + use rusqlite::types::ToSqlOutput; 288 + 289 + let start = match C::to_rusqlite(&self.start).unwrap() { 290 + ToSqlOutput::Borrowed(v) => v.to_owned().into(), 291 + ToSqlOutput::Owned(v) => v, 292 + other => unreachable!("{other:?}"), 293 + }; 294 + 295 + let end = match C::to_rusqlite(&self.end).unwrap() { 296 + ToSqlOutput::Borrowed(v) => v.to_owned().into(), 297 + ToSqlOutput::Owned(v) => v, 298 + other => unreachable!("{other:?}"), 299 + }; 300 + 301 + ir::FilterExpression::WithComponentDataRange { 302 + component: C::component_name().to_owned(), 303 + start, 304 + end, 305 + } 306 + } 307 + } 308 + 309 + impl<C: QueryFilterValue + Component> QueryFilterValue for std::ops::RangeTo<C> { 310 + fn filter_expression(&self) -> ir::FilterExpression { 311 + use rusqlite::types::ToSqlOutput; 312 + 313 + let end = match C::to_rusqlite(&self.end).unwrap() { 314 + ToSqlOutput::Borrowed(v) => v.to_owned().into(), 315 + ToSqlOutput::Owned(v) => v, 316 + other => unreachable!("{other:?}"), 317 + }; 318 + 319 + ir::FilterExpression::WithComponentDataRange { 320 + component: C::component_name().to_owned(), 321 + start: rusqlite::types::Value::Null, 322 + end, 323 + } 324 + } 325 + } 326 + 327 + impl<C: QueryFilterValue + Component> QueryFilterValue for std::ops::RangeFrom<C> { 328 + fn filter_expression(&self) -> ir::FilterExpression { 329 + use rusqlite::types::ToSqlOutput; 330 + 331 + let start = match C::to_rusqlite(&self.start).unwrap() { 332 + ToSqlOutput::Borrowed(v) => v.to_owned().into(), 333 + ToSqlOutput::Owned(v) => v, 334 + other => unreachable!("{other:?}"), 335 + }; 336 + 337 + ir::FilterExpression::WithComponentDataRange { 338 + component: C::component_name().to_owned(), 339 + start, 340 + end: rusqlite::types::Value::Null, 341 + } 342 + } 343 + } 344 + 345 + mod tuples { 346 + use super::*; 347 + 348 + macro_rules! query_data_impl { 349 + ( $($ts:ident)* ) => { 350 + impl<$($ts,)+> QueryData for ($($ts,)+) 351 + where 352 + $($ts: QueryData,)+ 353 + { 354 + type Output<'a> = ($($ts::Output<'a>,)+); 355 + 356 + fn from_entity<'a>(e: Entity<'a>) -> Option<Self::Output<'a>> { 357 + Some(($($ts::from_entity(e)?,)+)) 358 + } 359 + 360 + 361 + fn filter_expression() -> ir::FilterExpression{ 362 + ir::FilterExpression::and([ 363 + $(<$ts as QueryData>::filter_expression()),+ 364 + ]) 365 + } 366 + } 367 + } 368 + } 369 + 370 + macro_rules! filter_value_impl { 371 + ( $($ts:ident)* ) => { 372 + 373 + impl<$($ts,)+> QueryFilterValue for ($($ts,)+) 374 + where 375 + $($ts: QueryFilterValue,)+ 376 + { 377 + 378 + fn filter_expression(&self) -> ir::FilterExpression{ 379 + #[allow(non_snake_case)] 380 + let ($($ts,)+) = self; 381 + ir::FilterExpression::and([ 382 + $($ts.filter_expression(),)+ 383 + ]) 384 + } 385 + } 386 + } 387 + } 388 + 389 + macro_rules! impl_query_filter { 390 + ( $($ts:ident)* ) => { 391 + impl<$($ts,)+> QueryFilter for ($($ts,)+) 392 + where 393 + $($ts: QueryFilter,)+ 394 + { 395 + 396 + #[allow(non_snake_case)] 397 + fn filter_expression() -> ir::FilterExpression{ 398 + ir::FilterExpression::and([ 399 + $(<$ts as QueryFilter>::filter_expression()),+ 400 + ]) 401 + } 402 + } 403 + 404 + impl<$($ts,)+> QueryFilter for Or<($($ts,)+)> 405 + where 406 + $($ts: QueryFilter,)+ 407 + { 408 + 409 + #[allow(non_snake_case)] 410 + fn filter_expression() -> ir::FilterExpression{ 411 + ir::FilterExpression::or([ 412 + $(<$ts as QueryFilter>::filter_expression()),+ 413 + ]) 414 + } 415 + } 416 + 417 + impl<$($ts,)+> QueryFilter for With<($($ts,)+)> 418 + where 419 + $($ts: Component,)+ 420 + { 421 + 422 + fn filter_expression() -> ir::FilterExpression{ 423 + ir::FilterExpression::and([ 424 + $(ir::FilterExpression::with_component($ts::component_name()),)+ 425 + ]) 426 + } 427 + } 428 + 429 + impl<$($ts,)+> QueryFilter for Without<($($ts,)+)> 430 + where 431 + $($ts: Component,)+ 432 + { 433 + 434 + fn filter_expression() -> ir::FilterExpression{ 435 + ir::FilterExpression::and([ 436 + $(ir::FilterExpression::without_component($ts::component_name()),)+ 437 + ]) 438 + } 439 + } 440 + }; 441 + } 442 + 443 + crate::tuple_macros::for_each_tuple!(query_data_impl); 444 + crate::tuple_macros::for_each_tuple!(filter_value_impl); 445 + crate::tuple_macros::for_each_tuple!(impl_query_filter); 446 + } 447 + 448 + #[cfg(test)] 449 + mod tests { 450 + use serde::{Deserialize, Serialize}; 451 + 452 + use super::*; 453 + use crate as ecsdb; 454 + 455 + #[derive(Debug, Serialize, Deserialize, Component)] 456 + struct A; 457 + 458 + #[derive(Debug, Serialize, Deserialize, Component)] 459 + struct B; 460 + 461 + #[test] 462 + #[allow(unused)] 463 + fn system_fns() { 464 + fn sys_a(query: Query<A>) {} 465 + fn sys_b(query: Query<(A, Without<B>)>) {} 466 + fn sys_c(query: Query<Or<(A, B)>>) {} 467 + } 468 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@And([WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")]).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")]).sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) and (select true from components c2 where c2.entity = components.entity and c2.component = :2) is null)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Foo", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Bar", 18 + ), 19 + ), 20 + ], 21 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@And([WithComponent("ecsdb::Test"), EntityId(42)]).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Test\"), EntityId(42)]).sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) and entity = :2)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ], 21 + }
+15
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@EntityId(42).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: EntityId(42).sql_query() 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where entity = ?1", 7 + placeholders: [ 8 + ( 9 + "?1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ], 15 + }
+8
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@None.sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: None.sql_query() 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where true", 7 + placeholders: [], 8 + }
+39
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@Or([And([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([And([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((entity = :1 and (select true from components c2 where c2.entity = components.entity and c2.component = :2)) or (entity = :3 and (select true from components c2 where c2.entity = components.entity and c2.component = :4) and (select true from components c2 where c2.entity = components.entity and c2.component = :5) is null))", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Test", 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Integer( 23 + 23, 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Text( 29 + "ecsdb::Foo", 30 + ), 31 + ), 32 + ( 33 + ":5", 34 + Text( 35 + "ecsdb::Bar", 36 + ), 37 + ), 38 + ], 39 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@Or([WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")]).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")]).sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) or (select true from components c2 where c2.entity = components.entity and c2.component = :2) is null)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Foo", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Bar", 18 + ), 19 + ), 20 + ], 21 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@Or([WithComponent("ecsdb::Test"), EntityId(42)]).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Test\"), EntityId(42)]).sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) or entity = :2)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ], 21 + }
+15
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@WithComponent("ecsdb::Test").sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "WithComponent(\"ecsdb::Test\").sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where (select true from components c2 where c2.entity = components.entity and c2.component = ?1)", 7 + placeholders: [ 8 + ( 9 + "?1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ], 15 + }
+15
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@WithoutComponent("ecsdb::Test").sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "WithoutComponent(\"ecsdb::Test\").sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where (select true from components c2 where c2.entity = components.entity and c2.component = ?1) is null", 7 + placeholders: [ 8 + ( 9 + "?1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ], 15 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@And([WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")]).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")]).where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "((select true from components c2 where c2.entity = components.entity and c2.component = :1) and (select true from components c2 where c2.entity = components.entity and c2.component = :2) is null)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Foo", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Bar", 18 + ), 19 + ), 20 + ], 21 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@And([WithComponent("ecsdb::Test"), EntityId(42)]).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Test\"), EntityId(42)]).where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "((select true from components c2 where c2.entity = components.entity and c2.component = :1) and entity = :2)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ], 21 + }
+15
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@EntityId(42).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: EntityId(42).where_clause() 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "entity = ?1", 7 + placeholders: [ 8 + ( 9 + "?1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ], 15 + }
+8
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@None.where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: None.where_clause() 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "true", 7 + placeholders: [], 8 + }
+39
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@Or([And([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([And([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "((entity = :1 and (select true from components c2 where c2.entity = components.entity and c2.component = :2)) or (entity = :3 and (select true from components c2 where c2.entity = components.entity and c2.component = :4) and (select true from components c2 where c2.entity = components.entity and c2.component = :5) is null))", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Test", 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Integer( 23 + 23, 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Text( 29 + "ecsdb::Foo", 30 + ), 31 + ), 32 + ( 33 + ":5", 34 + Text( 35 + "ecsdb::Bar", 36 + ), 37 + ), 38 + ], 39 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@Or([WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")]).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")]).where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "((select true from components c2 where c2.entity = components.entity and c2.component = :1) or (select true from components c2 where c2.entity = components.entity and c2.component = :2) is null)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Foo", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Bar", 18 + ), 19 + ), 20 + ], 21 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@Or([WithComponent("ecsdb::Test"), EntityId(42)]).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Test\"), EntityId(42)]).where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "((select true from components c2 where c2.entity = components.entity and c2.component = :1) or entity = :2)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ], 21 + }
+15
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@WithComponent("ecsdb::Test").where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "WithComponent(\"ecsdb::Test\").where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "(select true from components c2 where c2.entity = components.entity and c2.component = ?1)", 7 + placeholders: [ 8 + ( 9 + "?1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ], 15 + }
+15
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@WithoutComponent("ecsdb::Test").where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "WithoutComponent(\"ecsdb::Test\").where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "(select true from components c2 where c2.entity = components.entity and c2.component = ?1) is null", 7 + placeholders: [ 8 + ( 9 + "?1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ], 15 + }
+14
src/tuple_macros.rs
··· 1 + macro_rules! for_each_tuple { 2 + ( $m:ident; ) => { }; 3 + 4 + ( $m:ident; $h:ident, $($t:ident,)* ) => ( 5 + $m!($h $($t)*); 6 + crate::tuple_macros::for_each_tuple! { $m; $($t,)* } 7 + ); 8 + 9 + ( $m:ident ) => { 10 + crate::tuple_macros::for_each_tuple! { $m; A, B, C, D, E, F, G, H, I, J, K, L, M, O, P, Q, } 11 + }; 12 + } 13 + 14 + pub(crate) use for_each_tuple;
+106 -7
src/schema.sql
··· 1 + -- Components 1 2 create table if not exists components ( 2 3 entity integer not null, 3 4 component text not null, 5 + data blob 6 + ); 4 7 5 - 6 - 7 - 8 - 8 + create unique index if not exists components_entity_component_unqiue_idx on components (entity, component); 9 9 10 10 create index if not exists components_component_idx on components (component); 11 11 12 - create trigger if not exists components_last_modified_trigger before 13 - update on components for each row begin 14 - update components 12 + create view if not exists entity_components (entity, components) as 13 + select 14 + entity, 15 + json_group_array (component) 16 + from 17 + components 18 + group by 19 + entity 20 + order by 21 + component asc; 22 + 23 + -- Set ecsdb::CreatedAt on initial insert 24 + create trigger if not exists components_created_insert_trigger 25 + after insert on components 26 + for each row 27 + when new.component != 'ecsdb::CreatedAt' begin 28 + insert into 29 + components (entity, component, data) 30 + values 31 + ( 32 + new.entity, 33 + 'ecsdb::CreatedAt', 34 + json_quote (strftime ('%Y-%m-%dT%H:%M:%fZ')) 35 + ) on conflict do nothing; 36 + end; 37 + 38 + -- Update ecsdb::LastUpdated on update 39 + create trigger if not exists components_last_modified_update_trigger after 40 + update on components for each row when new.component != 'ecsdb::LastUpdated' begin 41 + insert into 42 + components (entity, component, data) 43 + values 44 + ( 45 + new.entity, 46 + 'ecsdb::LastUpdated', 47 + json_quote (strftime ('%Y-%m-%dT%H:%M:%fZ')) 48 + ) on conflict (entity, component) do 49 + update 50 + set 51 + data = excluded.data; 52 + end; 53 + 54 + -- Update ecsdb::LastUpdated on insert 55 + create trigger if not exists components_last_modified_insert_trigger after insert on components for each row when new.component != 'ecsdb::LastUpdated' begin 56 + insert into 57 + components (entity, component, data) 58 + values 59 + ( 60 + new.entity, 61 + 'ecsdb::LastUpdated', 62 + json_quote (strftime ('%Y-%m-%dT%H:%M:%fZ')) 63 + ) on conflict (entity, component) do 64 + update 65 + set 66 + data = excluded.data; 67 + end; 68 + 69 + -- Update ecsdb::LastUpdated on delete, except when it's the last component 70 + create trigger if not exists components_last_modified_delete_trigger after delete on components for each row when old.component != 'ecsdb::LastUpdated' 71 + and ( 72 + select 73 + true 74 + from 75 + entity_components 76 + where 77 + entity = old.entity 78 + and components != json_array ('ecsdb::LastUpdated') 79 + ) begin 80 + insert into 81 + components (entity, component, data) 82 + values 83 + ( 84 + old.entity, 85 + 'ecsdb::LastUpdated', 86 + json_quote (strftime ('%Y-%m-%dT%H:%M:%fZ')) 87 + ) on conflict (entity, component) do 88 + update 89 + set 90 + data = excluded.data; 91 + 92 + end; 93 + 94 + -- Delete ecsdb::LastUpdated when it's the last remaining component 95 + create trigger if not exists components_last_modified_delete_last_component_trigger after delete on components for each row when ( 96 + select 97 + true 98 + from 99 + entity_components 100 + where 101 + entity = old.entity 102 + and components = json_array ('ecsdb::LastUpdated') 103 + ) begin 104 + delete from components 105 + where 106 + entity = old.entity 107 + and component = 'ecsdb::LastUpdated'; 108 + end; 109 + 110 + -- Resources 111 + create table if not exists resources ( 112 + name text not null unique, 113 + data blob,
+19
src/migrations/02_last_modified_component.sql
··· 1 + begin; 2 + 3 + drop trigger if exists components_last_modified_trigger; 4 + 5 + insert 6 + or ignore into components (entity, component, data) 7 + select 8 + entity, 9 + 'ecsdb::LastUpdated', 10 + json_quote (max(last_modified)) 11 + from 12 + components 13 + group by 14 + entity; 15 + 16 + alter table components 17 + drop last_modified; 18 + 19 + commit;
+51
src/dyn_component.rs
··· 1 + use tracing::warn; 2 + 3 + use crate::{component, Component}; 4 + 5 + #[derive(Debug)] 6 + pub struct DynComponent<'a>( 7 + pub(crate) &'a str, 8 + pub(crate) rusqlite::types::ToSqlOutput<'a>, 9 + ); 10 + 11 + impl<'a> DynComponent<'a> { 12 + pub fn name(&'a self) -> &'a str { 13 + self.0 14 + } 15 + 16 + pub fn into_typed<C: Component>(self) -> Result<C, component::StorageError> { 17 + C::from_rusqlite(&self.1) 18 + } 19 + 20 + pub fn as_typed<C: Component>(&self) -> Result<C, component::StorageError> { 21 + C::from_rusqlite(&self.1) 22 + } 23 + 24 + pub fn from_typed<C: Component + 'a>(c: &'a C) -> Result<Self, component::StorageError> { 25 + Ok(Self(C::component_name(), C::to_rusqlite(c)?)) 26 + } 27 + 28 + pub fn as_json(&self) -> Option<serde_json::value::Value> { 29 + use rusqlite::types::{ToSqlOutput, Value, ValueRef}; 30 + 31 + match self.1 { 32 + ToSqlOutput::Borrowed(ValueRef::Text(s)) => serde_json::from_slice(s).ok(), 33 + ToSqlOutput::Owned(Value::Text(ref s)) => serde_json::from_slice(s.as_bytes()).ok(), 34 + ToSqlOutput::Owned(Value::Null) | ToSqlOutput::Borrowed(ValueRef::Null) => { 35 + Some(serde_json::Value::Null) 36 + } 37 + ToSqlOutput::Owned(ref o) => { 38 + warn!(r#type = ?o.data_type(), "DynComponent::as_json unsupported"); 39 + None 40 + } 41 + ToSqlOutput::Borrowed(ref b) => { 42 + warn!(r#type = ?b.data_type(), "DynComponent::as_json unsupported"); 43 + None 44 + } 45 + ref x => { 46 + warn!(value = ?x, "DynComponent::as_json unsupported"); 47 + None 48 + } 49 + } 50 + } 51 + }
+42 -1
ecsdb_derive/Cargo.lock
··· 1 1 # This file is automatically @generated by Cargo. 2 2 # It is not intended for manual editing. 3 - version = 3 3 + version = 4 4 4 5 5 [[package]] 6 6 name = "ecsdb_derive" 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + [[package]] 15 + name = "proc-macro2" 16 + version = "1.0.103" 17 + source = "registry+https://github.com/rust-lang/crates.io-index" 18 + checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 19 + dependencies = [ 20 + "unicode-ident", 21 + ] 22 + 23 + [[package]] 24 + name = "quote" 25 + version = "1.0.41" 26 + source = "registry+https://github.com/rust-lang/crates.io-index" 27 + checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" 28 + dependencies = [ 29 + "proc-macro2", 30 + ] 31 + 32 + [[package]] 33 + name = "syn" 34 + version = "2.0.108" 35 + source = "registry+https://github.com/rust-lang/crates.io-index" 36 + checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" 37 + dependencies = [ 38 + "proc-macro2", 39 + "quote", 40 + 41 + 42 + 43 + [[package]] 44 + name = "unicode-ident" 45 + version = "1.0.22" 46 + source = "registry+https://github.com/rust-lang/crates.io-index" 47 + checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+103 -13
ecsdb_derive/tests/derive_test.rs
··· 1 + // Shims 1 2 pub mod component { 3 + use crate::rusqlite; 4 + 2 5 pub struct JsonStorage; 3 6 pub struct BlobStorage; 4 7 pub struct NullStorage; 8 + impl NullStorage { 9 + pub fn to_rusqlite<'a>( 10 + &'a self, 11 + ) -> Result<super::rusqlite::types::ToSqlOutput<'a>, StorageError> { 12 + todo!() 13 + } 14 + 15 + pub fn from_rusqlite( 16 + _value: &rusqlite::types::ToSqlOutput<'_>, 17 + ) -> Result<(), StorageError> { 18 + todo!() 19 + } 20 + } 21 + 5 22 pub trait Component { 6 23 type Storage; 24 + const NAME: &'static str; 25 + 26 + fn component_name() -> &'static str { 27 + Self::NAME 28 + } 29 + } 30 + 31 + pub type StorageError = (); 32 + pub type BundleData<'a> = Vec<(&'static str, rusqlite::types::ToSqlOutput<'a>)>; 33 + pub type BundleDataRef<'a> = &'a [(&'static str, rusqlite::types::ToSqlOutput<'a>)]; 34 + 35 + pub trait Bundle: Sized { 36 + const COMPONENTS: &'static [&'static str]; 37 + 38 + fn component_names() -> &'static [&'static str] { 39 + Self::COMPONENTS 40 + } 7 41 8 - fn component_name() -> &'static str; 42 + fn to_rusqlite<'a>(&'a self) -> Result<BundleData<'a>, StorageError>; 43 + // fn from_rusqlite<'a>(components: BundleDataRef<'a>) -> Result<Option<Self>, StorageError>; 9 44 } 45 + 46 + pub trait ComponentWrite {} 10 47 } 48 + 11 49 pub mod resource { 12 50 pub trait Resource: super::component::Component { 13 51 fn resource_name() -> &'static str { ··· 16 54 } 17 55 } 18 56 57 + pub mod rusqlite { 58 + pub mod types { 59 + use std::marker::PhantomData; 60 + pub type ToSqlOutput<'a> = PhantomData<&'a ()>; 61 + } 62 + } 63 + 64 + // Necessary for development as we derive `ecsdb::Component for ...` 65 + use crate as ecsdb; 66 + use ecsdb::component::Component; 67 + use ecsdb::resource::Resource; 68 + use ecsdb_derive::{Component, Resource}; 69 + 70 + #[allow(unused)] 19 71 #[test] 20 72 fn test_component() { 21 - // Necessary for development as we derive `ecsdb::Component for ...` 22 - use crate as ecsdb; 23 - use ecsdb::component::Component; 24 - 25 - #[derive(ecsdb_derive::Component)] 73 + #[derive(Component)] 26 74 #[component(storage = "json")] 27 75 struct Foo; 28 76 29 77 assert_eq!(Foo::component_name(), "derive_test::Foo".to_string()); 30 78 31 - #[derive(ecsdb_derive::Component)] 79 + #[derive(Component)] 32 80 #[component(storage = "blob")] 33 81 struct Foo2(pub Vec<u8>); 34 82 83 + impl Into<Vec<u8>> for Foo2 { 84 + fn into(self) -> Vec<u8> { 85 + self.0 86 + } 87 + } 88 + 89 + impl From<Vec<u8>> for Foo2 { 90 + fn from(value: Vec<u8>) -> Self { 91 + Self(value) 92 + } 93 + } 94 + 35 95 assert_eq!(Foo2::component_name(), "derive_test::Foo2".to_string()); 36 96 37 97 let _: Vec<u8> = Foo2(vec![]).into(); 38 98 let _ = Foo2::from(vec![]); 39 99 40 - #[derive(ecsdb_derive::Component)] 100 + #[derive(Component)] 41 101 struct Unit; 42 102 } 43 103 44 104 #[test] 45 - fn test_resource() { 46 - // Necessary for development as we derive `ecsdb::Component for ...` 47 - use crate as ecsdb; 48 - use ecsdb::resource::Resource; 105 + fn derive_name_attribute() { 106 + #[derive(Component)] 107 + #[component(name = "foo::Bar")] 108 + struct X; 109 + 110 + assert_eq!(X::component_name(), "foo::Bar"); 111 + 112 + #[derive(Resource)] 113 + #[component(name = "foo::Bar")] 114 + struct Y; 49 115 50 - #[derive(ecsdb_derive::Resource)] 116 + assert_eq!(Y::component_name(), "foo::Bar"); 117 + } 118 + 119 + #[test] 120 + fn test_resource() { 121 + #[derive(Resource)] 51 122 struct Foo; 52 123 53 124 assert_eq!(Foo::resource_name(), "derive_test::Foo".to_string()); 125 + } 126 + 127 + // #[test] 128 + // fn derive_bundle_struct() { 129 + // #[derive(Debug, Component)] 130 + // struct A; 131 + 132 + // #[derive(Debug, Component)] 133 + // struct B; 134 + 135 + // #[derive(Debug, Bundle)] 136 + // struct Composed { 137 + // a: A, 138 + // b: B, 139 + // } 140 + 141 + // // Create Bundle from tuple 142 + // let _ = Composed::from((A, B)); 143 + // }
+18 -28
ecsdb_derive/src/lib.rs
··· 1 1 2 2 3 + use proc_macro::TokenStream; 4 + use quote::{format_ident, quote}; 5 + use syn::{Attribute, Data, Expr, Fields, Lit, Meta, Token, punctuated::Punctuated}; 3 6 7 + #[proc_macro_derive(Component, attributes(component))] 8 + pub fn derive_component_fn(input: TokenStream) -> TokenStream { 4 9 5 10 6 11 ··· 72 77 73 78 74 79 80 + let component_derive: proc_macro2::TokenStream = impl_derive_component(ast).into(); 75 81 82 + quote! { 83 + #component_derive 76 84 85 + impl ecsdb::resource::Resource for #name { } 86 + } 87 + .into() 88 + } 77 89 78 - 79 - 80 - 81 - 82 - 83 - 84 - 85 - 86 - 87 - 88 - 89 - 90 - 91 - 90 + fn impl_derive_bundle(ast: syn::DeriveInput) -> TokenStream { 92 91 93 92 94 93 ··· 132 131 quote! { 133 132 impl ecsdb::component::Bundle for #name { 134 133 const COMPONENTS: &'static [&'static str] = &[ 135 - #(#types::NAME),* 134 + #(<#types as ecsdb::component::BundleComponent>::NAME),* 136 135 ]; 137 136 138 137 fn to_rusqlite<'a>( ··· 145 144 Ok(vec![ 146 145 #( 147 146 ( 148 - <#types as ecsdb::Component>::NAME, 149 - <#types as ecsdb::Component>::Storage::to_rusqlite(#field_vars)? 147 + <#types as ecsdb::component::BundleComponent>::NAME, 148 + <#types as ecsdb::component::BundleComponent>::to_rusqlite(#field_vars)? 150 149 ), 151 150 )* 152 151 ]) 153 152 } 154 - 155 - fn from_rusqlite<'a>( 156 - components: ecsdb::component::BundleDataRef<'a>, 157 - ) -> Result<Option<Self>, ecsdb::component::StorageError> { 158 - let (#(Some(#field_vars),)*) = (#(<#types as ecsdb::Bundle>::from_rusqlite(components)?),*) else { 159 - return Ok(None); 160 - }; 161 - 162 - Ok(Some(Self { #(#field_bindings,)* })) 163 - } 153 + } 164 154 } 165 - }.into() 155 + .into() 166 156 } 167 157 168 158 fn extract_attributes(attrs: &[Attribute]) -> Attributes {
+60
src/sqlite_ext.rs
··· 1 + use rusqlite::functions::FunctionFlags; 2 + use rusqlite::types::{Value, ValueRef}; 3 + use rusqlite::{Connection, Error, Result}; 4 + 5 + pub(crate) fn add_regexp_function(db: &Connection) -> Result<()> { 6 + db.create_scalar_function( 7 + "velodb_extract_data", 8 + 1, 9 + FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, 10 + move |ctx| { 11 + assert_eq!(ctx.len(), 1, "called with unexpected number of arguments"); 12 + 13 + match ctx.get_raw(0) { 14 + ValueRef::Null => Ok(Value::Null), 15 + ValueRef::Integer(i) => Ok(Value::Integer(i)), 16 + ValueRef::Real(r) => Ok(Value::Real(r)), 17 + // Return NULL for BLOB - no JSON extraction possible 18 + ValueRef::Blob(_blob) => Ok(Value::Null), 19 + // JSON 20 + ValueRef::Text(text) => { 21 + let value: serde_json::Value = serde_json::from_slice(text) 22 + .map_err(|e| Error::UserFunctionError(Box::new(e)))?; 23 + 24 + let sqlite_value = match value { 25 + serde_json::Value::Null => Value::Null, 26 + serde_json::Value::Bool(true) => Value::Integer(1), 27 + serde_json::Value::Bool(false) => Value::Integer(0), 28 + serde_json::Value::Number(n) => n 29 + .as_i64() 30 + .map(Value::Integer) 31 + .or(n.as_f64().map(Value::Real)) 32 + .unwrap(), 33 + serde_json::Value::String(s) => Value::Text(s), 34 + array @ serde_json::Value::Array(_) => Value::Text(array.to_string()), 35 + obj @ serde_json::Value::Object(_) => Value::Text(obj.to_string()), 36 + }; 37 + 38 + Ok(sqlite_value) 39 + } 40 + } 41 + }, 42 + ) 43 + } 44 + 45 + #[cfg(test)] 46 + mod tests { 47 + #[test] 48 + fn custom_fn_test() -> Result<(), anyhow::Error> { 49 + let db = crate::Ecs::open_in_memory()?; 50 + let result: bool = db.raw_sql().query_row( 51 + "select velodb_extract_data(json_quote(10)) > velodb_extract_data(json_quote(2))", 52 + [], 53 + |row| row.get(0), 54 + )?; 55 + 56 + assert!(result); 57 + 58 + Ok(()) 59 + } 60 + }
+39
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@And([And([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([And([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((entity = :1 and (select true from components c2 where c2.entity = components.entity and c2.component = :2)) and (entity = :3 and (select true from components c2 where c2.entity = components.entity and c2.component = :4) and (select true from components c2 where c2.entity = components.entity and c2.component = :5) is null))", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Test", 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Integer( 23 + 23, 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Text( 29 + "ecsdb::Foo", 30 + ), 31 + ), 32 + ( 33 + ":5", 34 + Text( 35 + "ecsdb::Bar", 36 + ), 37 + ), 38 + ], 39 + }
+39
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@Or([Or([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([Or([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((entity = :1 or (select true from components c2 where c2.entity = components.entity and c2.component = :2)) or (entity = :3 and (select true from components c2 where c2.entity = components.entity and c2.component = :4) and (select true from components c2 where c2.entity = components.entity and c2.component = :5) is null))", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Test", 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Integer( 23 + 23, 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Text( 29 + "ecsdb::Foo", 30 + ), 31 + ), 32 + ( 33 + ":5", 34 + Text( 35 + "ecsdb::Bar", 36 + ), 37 + ), 38 + ], 39 + }
+39
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@And([And([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([And([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "((entity = :1 and (select true from components c2 where c2.entity = components.entity and c2.component = :2)) and (entity = :3 and (select true from components c2 where c2.entity = components.entity and c2.component = :4) and (select true from components c2 where c2.entity = components.entity and c2.component = :5) is null))", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Test", 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Integer( 23 + 23, 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Text( 29 + "ecsdb::Foo", 30 + ), 31 + ), 32 + ( 33 + ":5", 34 + Text( 35 + "ecsdb::Bar", 36 + ), 37 + ), 38 + ], 39 + }
+39
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@Or([Or([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([Or([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "((entity = :1 or (select true from components c2 where c2.entity = components.entity and c2.component = :2)) or (entity = :3 and (select true from components c2 where c2.entity = components.entity and c2.component = :4) and (select true from components c2 where c2.entity = components.entity and c2.component = :5) is null))", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Test", 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Integer( 23 + 23, 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Text( 29 + "ecsdb::Foo", 30 + ), 31 + ), 32 + ( 33 + ":5", 34 + Text( 35 + "ecsdb::Bar", 36 + ), 37 + ), 38 + ], 39 + }
+23
src/query/snapshots/ecsdb__query__ir__test__simplify@And([And([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([And([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).simplify()" 4 + --- 5 + And( 6 + [ 7 + EntityId( 8 + 42, 9 + ), 10 + WithComponent( 11 + "ecsdb::Test", 12 + ), 13 + EntityId( 14 + 23, 15 + ), 16 + WithComponent( 17 + "ecsdb::Foo", 18 + ), 19 + WithoutComponent( 20 + "ecsdb::Bar", 21 + ), 22 + ], 23 + )
+14
src/query/snapshots/ecsdb__query__ir__test__simplify@And([WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")]).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")]).simplify()" 4 + --- 5 + And( 6 + [ 7 + WithComponent( 8 + "ecsdb::Foo", 9 + ), 10 + WithoutComponent( 11 + "ecsdb::Bar", 12 + ), 13 + ], 14 + )
+14
src/query/snapshots/ecsdb__query__ir__test__simplify@And([WithComponent("ecsdb::Test"), EntityId(42)]).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Test\"), EntityId(42)]).simplify()" 4 + --- 5 + And( 6 + [ 7 + WithComponent( 8 + "ecsdb::Test", 9 + ), 10 + EntityId( 11 + 42, 12 + ), 13 + ], 14 + )
+7
src/query/snapshots/ecsdb__query__ir__test__simplify@EntityId(42).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: EntityId(42).simplify() 4 + --- 5 + EntityId( 6 + 42, 7 + )
+5
src/query/snapshots/ecsdb__query__ir__test__simplify@None.simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: None.simplify() 4 + --- 5 + None
+31
src/query/snapshots/ecsdb__query__ir__test__simplify@Or([And([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([And([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).simplify()" 4 + --- 5 + Or( 6 + [ 7 + And( 8 + [ 9 + EntityId( 10 + 42, 11 + ), 12 + WithComponent( 13 + "ecsdb::Test", 14 + ), 15 + ], 16 + ), 17 + And( 18 + [ 19 + EntityId( 20 + 23, 21 + ), 22 + WithComponent( 23 + "ecsdb::Foo", 24 + ), 25 + WithoutComponent( 26 + "ecsdb::Bar", 27 + ), 28 + ], 29 + ), 30 + ], 31 + )
+27
src/query/snapshots/ecsdb__query__ir__test__simplify@Or([Or([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([Or([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).simplify()" 4 + --- 5 + Or( 6 + [ 7 + EntityId( 8 + 42, 9 + ), 10 + WithComponent( 11 + "ecsdb::Test", 12 + ), 13 + And( 14 + [ 15 + EntityId( 16 + 23, 17 + ), 18 + WithComponent( 19 + "ecsdb::Foo", 20 + ), 21 + WithoutComponent( 22 + "ecsdb::Bar", 23 + ), 24 + ], 25 + ), 26 + ], 27 + )
+14
src/query/snapshots/ecsdb__query__ir__test__simplify@Or([WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")]).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")]).simplify()" 4 + --- 5 + Or( 6 + [ 7 + WithComponent( 8 + "ecsdb::Foo", 9 + ), 10 + WithoutComponent( 11 + "ecsdb::Bar", 12 + ), 13 + ], 14 + )
+14
src/query/snapshots/ecsdb__query__ir__test__simplify@Or([WithComponent("ecsdb::Test"), EntityId(42)]).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Test\"), EntityId(42)]).simplify()" 4 + --- 5 + Or( 6 + [ 7 + WithComponent( 8 + "ecsdb::Test", 9 + ), 10 + EntityId( 11 + 42, 12 + ), 13 + ], 14 + )
+7
src/query/snapshots/ecsdb__query__ir__test__simplify@WithComponent("ecsdb::Test").simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "WithComponent(\"ecsdb::Test\").simplify()" 4 + --- 5 + WithComponent( 6 + "ecsdb::Test", 7 + )
+7
src/query/snapshots/ecsdb__query__ir__test__simplify@WithoutComponent("ecsdb::Test").simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "WithoutComponent(\"ecsdb::Test\").simplify()" 4 + --- 5 + WithoutComponent( 6 + "ecsdb::Test", 7 + )
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@And([EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where (entity = :1 and entity in (select entity from components where component = :2) and entity = :3)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + <dyn ToSql>, 11 + ), 12 + ( 13 + ":2", 14 + <dyn ToSql>, 15 + ), 16 + ( 17 + ":3", 18 + <dyn ToSql>, 19 + ), 20 + ], 21 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@And([EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "(entity = :1 and entity in (select entity from components where component = :2) and entity = :3)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + <dyn ToSql>, 11 + ), 12 + ( 13 + ":2", 14 + <dyn ToSql>, 15 + ), 16 + ( 17 + ":3", 18 + <dyn ToSql>, 19 + ), 20 + ], 21 + }
+14
src/query/snapshots/ecsdb__query__ir__test__simplify@And([EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).simplify()" 4 + --- 5 + And( 6 + [ 7 + EntityId( 8 + 42, 9 + ), 10 + WithComponent( 11 + "ecsdb::Test", 12 + ), 13 + ], 14 + )
+164
src/bin/ecsdb.rs
··· 1 + use std::{fmt::Display, path::PathBuf}; 2 + 3 + use clap::*; 4 + use ecsdb::*; 5 + use rustyline::error::ReadlineError; 6 + use tracing::debug; 7 + 8 + #[derive(clap::Parser, Debug)] 9 + struct Cli { 10 + filename: Option<PathBuf>, 11 + command: Option<String>, 12 + } 13 + 14 + type Commands<'a> = &'a [&'a dyn Command]; 15 + 16 + pub fn main() -> Result<(), anyhow::Error> { 17 + tracing_subscriber::fmt::init(); 18 + 19 + let cli = Cli::parse(); 20 + debug!(?cli); 21 + 22 + let _span = tracing::debug_span!( 23 + "db", 24 + path = cli 25 + .filename 26 + .as_ref() 27 + .map(|p| p.display().to_string()) 28 + .unwrap_or(":memory:".into()) 29 + ) 30 + .entered(); 31 + 32 + let db = match cli.filename { 33 + Some(ref path) => ecsdb::Ecs::open(path)?, 34 + None => ecsdb::Ecs::open_in_memory()?, 35 + }; 36 + 37 + debug!("Opened DB"); 38 + 39 + let mut rl = rustyline::DefaultEditor::new()?; 40 + 41 + const COMMANDS: Commands = &[&Info, &Sqlite]; 42 + 43 + if let Some(command) = cli.command { 44 + debug!(?command, "Executing"); 45 + 46 + eval(&COMMANDS, &db, &command)?; 47 + 48 + return Ok(()); 49 + } 50 + 51 + debug!("Entering REPL"); 52 + 53 + loop { 54 + let readline = rl.readline(">> "); 55 + match readline { 56 + Ok(line) => { 57 + eval(&COMMANDS, &db, &line)?; 58 + } 59 + Err(ReadlineError::Eof) => { 60 + println!("Exiting..."); 61 + return Ok(()); 62 + } 63 + Err(_) => println!("No input"), 64 + } 65 + } 66 + } 67 + 68 + fn eval(commands: &Commands, db: &Ecs, line: &str) -> Result<(), CommandError> { 69 + let Some(command) = line.split_whitespace().next() else { 70 + return Ok(()); 71 + }; 72 + 73 + let Some(command) = commands.iter().find(|c| c.name() == command) else { 74 + println!("Command '{command}' not found"); 75 + return Ok(()); 76 + }; 77 + 78 + command.execute(db, line)?; 79 + 80 + Ok(()) 81 + } 82 + 83 + #[derive(Debug, thiserror::Error)] 84 + pub enum CommandError { 85 + #[error(transparent)] 86 + Database(#[from] ecsdb::Error), 87 + } 88 + 89 + trait Command: std::fmt::Debug { 90 + fn name(&self) -> &'static str; 91 + fn execute(&self, db: &Ecs, input: &str) -> Result<(), CommandError>; 92 + } 93 + 94 + #[derive(Debug)] 95 + struct Info; 96 + 97 + impl Command for Info { 98 + fn name(&self) -> &'static str { 99 + ".info" 100 + } 101 + 102 + fn execute(&self, db: &Ecs, _input: &str) -> Result<(), CommandError> { 103 + let db_path = match db.raw_sql().path() { 104 + None => "???", 105 + Some("") => ":memory:", 106 + Some(path) => path, 107 + }; 108 + 109 + println!("Database {}, data_version {}", db_path, db.data_version()?); 110 + Ok(()) 111 + } 112 + } 113 + 114 + #[derive(Debug)] 115 + struct Sqlite; 116 + 117 + impl Sqlite { 118 + fn run(db: &rusqlite::Connection, sql: &str) -> Result<(), rusqlite::Error> { 119 + let mut stmt = db.prepare(sql)?; 120 + 121 + let cols = stmt 122 + .column_names() 123 + .into_iter() 124 + .map(String::from) 125 + .collect::<Vec<_>>(); 126 + 127 + debug!(?cols); 128 + println!("{}", cols.join("\t| ")); 129 + 130 + let mut rows = stmt.query([])?; 131 + 132 + while let Some(row) = rows.next()? { 133 + for col in &cols { 134 + let val = row.get_ref(col.as_str())?; 135 + let val: Box<dyn Display> = match val { 136 + rusqlite::types::ValueRef::Null => Box::new("NULL"), 137 + rusqlite::types::ValueRef::Integer(n) => Box::new(n), 138 + rusqlite::types::ValueRef::Real(r) => Box::new(r), 139 + rusqlite::types::ValueRef::Text(text) => Box::new(str::from_utf8(text)?), 140 + rusqlite::types::ValueRef::Blob(items) => { 141 + Box::new(format!("Blob<{} bytes>", items.len())) 142 + } 143 + }; 144 + print!("{val}\t") 145 + } 146 + 147 + println!(); 148 + } 149 + 150 + Ok(()) 151 + } 152 + } 153 + 154 + impl Command for Sqlite { 155 + fn name(&self) -> &'static str { 156 + ".sql" 157 + } 158 + 159 + fn execute(&self, db: &Ecs, input: &str) -> Result<(), CommandError> { 160 + let sql = input.trim_start_matches(self.name()).trim(); 161 + Self::run(db.raw_sql(), sql).map_err(ecsdb::Error::from)?; 162 + Ok(()) 163 + } 164 + }
+11
.zed/tasks.json
··· 1 + // Project tasks configuration. See https://zed.dev/docs/tasks for documentation. 2 + // 3 + // Example: 4 + [ 5 + { 6 + "label": "cargo test --doc", 7 + "command": "cargo", 8 + "args": ["test", "--doc"], 9 + "tags": ["rust-doc-test"] 10 + } 11 + ]
+226
src/schedule.rs
··· 1 + use std::borrow::Cow; 2 + 3 + use crate::{BoxedSystem, Ecs, IntoSystem, LastRun, System, system}; 4 + 5 + use tracing::{debug, debug_span, instrument, warn}; 6 + 7 + #[derive(Default)] 8 + pub struct Schedule(Vec<(BoxedSystem, Box<dyn SchedulingMode>)>); 9 + 10 + impl Schedule { 11 + pub fn new() -> Self { 12 + Self::default() 13 + } 14 + 15 + pub fn add<Marker, S, M>(&mut self, system: S, mode: M) -> &mut Self 16 + where 17 + S: IntoSystem<Marker>, 18 + S::System: 'static, 19 + M: SchedulingMode, 20 + { 21 + self.0.push((system.into_boxed_system(), Box::new(mode))); 22 + self 23 + } 24 + 25 + #[instrument(level = "debug", skip_all, ret, err)] 26 + pub fn tick<'a>(&'a self, ecs: &Ecs) -> Result<Vec<(Cow<'a, str>, TickResult)>, anyhow::Error> { 27 + let mut results = Vec::with_capacity(self.0.len()); 28 + 29 + for (system, schedule) in self.0.iter() { 30 + let _span = debug_span!("system", name = %system.name()).entered(); 31 + 32 + let result = if schedule.should_run(ecs, &system.name()) { 33 + match ecs.run_dyn_system(system) { 34 + Ok(()) => TickResult::Ok, 35 + Err(e) => { 36 + warn!(error = %e, "System failed"); 37 + TickResult::Error(e) 38 + } 39 + } 40 + } else { 41 + debug!("skipping"); 42 + TickResult::NotScheduled 43 + }; 44 + 45 + debug!(?result); 46 + results.push((system.name(), result)); 47 + } 48 + 49 + Ok(results) 50 + } 51 + 52 + pub fn iter(&self) -> impl Iterator<Item = &(BoxedSystem, Box<dyn SchedulingMode>)> { 53 + self.0.iter() 54 + } 55 + } 56 + 57 + #[derive(Debug)] 58 + pub enum TickResult { 59 + Ok, 60 + NotScheduled, 61 + Error(anyhow::Error), 62 + } 63 + 64 + pub trait SchedulingMode: std::fmt::Debug + 'static { 65 + fn should_run(&self, ecs: &crate::Ecs, system: &str) -> bool; 66 + fn did_run(&self, _ecs: &crate::Ecs, _system: &str) {} 67 + } 68 + 69 + #[derive(Debug)] 70 + pub struct Manually; 71 + 72 + impl SchedulingMode for Manually { 73 + #[instrument(level = "debug", skip_all, fields(self), ret)] 74 + fn should_run(&self, _ecs: &crate::Ecs, _system: &str) -> bool { 75 + false 76 + } 77 + } 78 + 79 + #[derive(Debug)] 80 + pub struct Always; 81 + 82 + impl SchedulingMode for Always { 83 + #[instrument(level = "debug", skip_all, fields(self), ret)] 84 + fn should_run(&self, _ecs: &crate::Ecs, _system: &str) -> bool { 85 + true 86 + } 87 + } 88 + 89 + #[derive(Debug)] 90 + pub struct Every(pub chrono::Duration); 91 + 92 + impl SchedulingMode for Every { 93 + #[instrument(level = "debug", skip_all, fields(self), ret)] 94 + fn should_run(&self, ecs: &crate::Ecs, system: &str) -> bool { 95 + ecs.system_entity(system) 96 + .and_then(|e| e.component::<system::LastRun>()) 97 + .map(|last_run| { 98 + debug!(?last_run); 99 + chrono::Utc::now().signed_duration_since(last_run.0) > self.0 100 + }) 101 + .unwrap_or(true) 102 + } 103 + } 104 + 105 + #[derive(Debug)] 106 + pub struct Once; 107 + 108 + impl SchedulingMode for Once { 109 + #[instrument(level = "debug", skip_all, fields(self), ret)] 110 + fn should_run(&self, ecs: &crate::Ecs, system: &str) -> bool { 111 + let entity = ecs.get_or_create_system_entity(system); 112 + entity.component::<system::LastRun>().is_none() 113 + } 114 + } 115 + 116 + #[derive(Debug)] 117 + pub struct After(String); 118 + 119 + impl After { 120 + pub fn system<Marker, S>(system: S) -> Self 121 + where 122 + S: IntoSystem<Marker>, 123 + { 124 + Self(system.into_system().name().into()) 125 + } 126 + } 127 + 128 + impl SchedulingMode for After { 129 + #[instrument(level = "debug", skip_all, fields(self), ret)] 130 + fn should_run(&self, ecs: &crate::Ecs, system: &str) -> bool { 131 + let predecessor_last_run = ecs.system_entity(&self.0).and_then(|e| e.component()); 132 + 133 + let our_last_run = ecs 134 + .system_entity(system) 135 + .and_then(|e| e.component::<LastRun>()); 136 + 137 + debug!(?our_last_run, ?predecessor_last_run); 138 + 139 + match (predecessor_last_run, our_last_run) { 140 + (None, _) => false, 141 + (Some(_), None) => true, 142 + (Some(LastRun(before)), Some(LastRun(after))) if before > after => true, 143 + (Some(_), Some(_)) => false, 144 + } 145 + } 146 + } 147 + 148 + #[cfg(test)] 149 + mod test { 150 + use crate::{self as ecsdb, SystemEntity}; 151 + use ecsdb_derive::Component; 152 + use serde::{Deserialize, Serialize}; 153 + 154 + use super::*; 155 + use crate::system_name; 156 + 157 + #[derive(Serialize, Deserialize, Component, Default, PartialEq, Debug)] 158 + struct Count(pub usize); 159 + 160 + #[test] 161 + fn schedules() { 162 + macro_rules! defsys { 163 + ($sys:ident) => { 164 + fn $sys(sys: SystemEntity<'_>) { 165 + sys.modify_component(|Count(c)| *c += 1); 166 + } 167 + }; 168 + } 169 + 170 + defsys!(sys_a); 171 + defsys!(sys_b); 172 + defsys!(sys_c); 173 + 174 + let mut schedule = Schedule::new(); 175 + schedule.add(sys_a, Once); 176 + schedule.add(sys_b, After::system(sys_a)); 177 + schedule.add(sys_c, Always); 178 + 179 + let ecs = Ecs::open_in_memory().unwrap(); 180 + schedule.tick(&ecs).unwrap(); 181 + schedule.tick(&ecs).unwrap(); 182 + 183 + fn sys_count<Marker>(ecs: &Ecs, sys: impl IntoSystem<Marker>) -> Count { 184 + ecs.system_entity(&system_name(sys)) 185 + .unwrap() 186 + .component() 187 + .unwrap() 188 + } 189 + 190 + // sys_a should have a count of 1 191 + assert_eq!(sys_count(&ecs, sys_a), Count(1)); 192 + 193 + // sys_b should also have a count of 1 194 + assert_eq!(sys_count(&ecs, sys_b), Count(1)); 195 + 196 + // sys_c should have a count of 2 197 + assert_eq!(sys_count(&ecs, sys_c), Count(2)); 198 + } 199 + 200 + #[test] 201 + fn tick_results() { 202 + #[rustfmt::skip] 203 + fn system_ok(sys: SystemEntity<'_>) { sys.modify_component(|Count( c)| *c += 1); } 204 + #[rustfmt::skip] 205 + fn system_error(_sys: SystemEntity<'_>) -> Result<(), anyhow::Error> { Err(anyhow::anyhow!("Expected test error")) } 206 + 207 + let mut schedule = Schedule::new(); 208 + schedule.add(system_ok, Always); 209 + schedule.add(system_error, Always); 210 + schedule.add(system_ok, Manually); 211 + 212 + let ecs = Ecs::open_in_memory().unwrap(); 213 + let results = schedule.tick(&ecs).unwrap(); 214 + 215 + assert_eq!(results.len(), 3); 216 + 217 + // First system should run successfully 218 + assert!(matches!(results[0].1, TickResult::Ok)); 219 + 220 + // Second system should return an error 221 + assert!(matches!(results[1].1, TickResult::Error(_))); 222 + 223 + // Third system should be skipped due to Manually scheduling 224 + assert!(matches!(results[2].1, TickResult::NotScheduled)); 225 + } 226 + }
+1
.gitignore
··· 1 1 target/ 2 + Cargo.lock
-1196
Cargo.lock
··· 1 - # This file is automatically @generated by Cargo. 2 - # It is not intended for manual editing. 3 - version = 4 4 - 5 - [[package]] 6 - name = "ahash" 7 - version = "0.8.12" 8 - source = "registry+https://github.com/rust-lang/crates.io-index" 9 - checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" 10 - dependencies = [ 11 - "cfg-if", 12 - "once_cell", 13 - "version_check", 14 - "zerocopy", 15 - ] 16 - 17 - [[package]] 18 - name = "aho-corasick" 19 - version = "1.1.3" 20 - source = "registry+https://github.com/rust-lang/crates.io-index" 21 - checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 22 - dependencies = [ 23 - "memchr", 24 - ] 25 - 26 - [[package]] 27 - name = "android-tzdata" 28 - version = "0.1.1" 29 - source = "registry+https://github.com/rust-lang/crates.io-index" 30 - checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 31 - 32 - [[package]] 33 - name = "android_system_properties" 34 - version = "0.1.5" 35 - source = "registry+https://github.com/rust-lang/crates.io-index" 36 - checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 37 - dependencies = [ 38 - "libc", 39 - ] 40 - 41 - [[package]] 42 - name = "anstream" 43 - version = "0.6.20" 44 - source = "registry+https://github.com/rust-lang/crates.io-index" 45 - checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" 46 - dependencies = [ 47 - "anstyle", 48 - "anstyle-parse", 49 - "anstyle-query", 50 - "anstyle-wincon", 51 - "colorchoice", 52 - "is_terminal_polyfill", 53 - "utf8parse", 54 - ] 55 - 56 - [[package]] 57 - name = "anstyle" 58 - version = "1.0.11" 59 - source = "registry+https://github.com/rust-lang/crates.io-index" 60 - checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 61 - 62 - [[package]] 63 - name = "anstyle-parse" 64 - version = "0.2.7" 65 - source = "registry+https://github.com/rust-lang/crates.io-index" 66 - checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 67 - dependencies = [ 68 - "utf8parse", 69 - ] 70 - 71 - [[package]] 72 - name = "anstyle-query" 73 - version = "1.1.4" 74 - source = "registry+https://github.com/rust-lang/crates.io-index" 75 - checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 76 - dependencies = [ 77 - "windows-sys 0.60.2", 78 - ] 79 - 80 - [[package]] 81 - name = "anstyle-wincon" 82 - version = "3.0.10" 83 - source = "registry+https://github.com/rust-lang/crates.io-index" 84 - checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 85 - dependencies = [ 86 - "anstyle", 87 - "once_cell_polyfill", 88 - "windows-sys 0.60.2", 89 - ] 90 - 91 - [[package]] 92 - name = "anyhow" 93 - version = "1.0.99" 94 - source = "registry+https://github.com/rust-lang/crates.io-index" 95 - checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" 96 - 97 - [[package]] 98 - name = "anymap" 99 - version = "0.12.1" 100 - source = "registry+https://github.com/rust-lang/crates.io-index" 101 - checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" 102 - 103 - [[package]] 104 - name = "autocfg" 105 - version = "1.5.0" 106 - source = "registry+https://github.com/rust-lang/crates.io-index" 107 - checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 108 - 109 - [[package]] 110 - name = "bitflags" 111 - version = "2.9.1" 112 - source = "registry+https://github.com/rust-lang/crates.io-index" 113 - checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 114 - 115 - [[package]] 116 - name = "bumpalo" 117 - version = "3.19.0" 118 - source = "registry+https://github.com/rust-lang/crates.io-index" 119 - checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 120 - 121 - [[package]] 122 - name = "cc" 123 - version = "1.2.32" 124 - source = "registry+https://github.com/rust-lang/crates.io-index" 125 - checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" 126 - dependencies = [ 127 - "shlex", 128 - ] 129 - 130 - [[package]] 131 - name = "cfg-if" 132 - version = "1.0.1" 133 - source = "registry+https://github.com/rust-lang/crates.io-index" 134 - checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 135 - 136 - [[package]] 137 - name = "cfg_aliases" 138 - version = "0.2.1" 139 - source = "registry+https://github.com/rust-lang/crates.io-index" 140 - checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 141 - 142 - [[package]] 143 - name = "chrono" 144 - version = "0.4.41" 145 - source = "registry+https://github.com/rust-lang/crates.io-index" 146 - checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 147 - dependencies = [ 148 - "android-tzdata", 149 - "iana-time-zone", 150 - "js-sys", 151 - "num-traits", 152 - "serde", 153 - "wasm-bindgen", 154 - "windows-link", 155 - ] 156 - 157 - [[package]] 158 - name = "clap" 159 - version = "4.5.45" 160 - source = "registry+https://github.com/rust-lang/crates.io-index" 161 - checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" 162 - dependencies = [ 163 - "clap_builder", 164 - "clap_derive", 165 - ] 166 - 167 - [[package]] 168 - name = "clap_builder" 169 - version = "4.5.44" 170 - source = "registry+https://github.com/rust-lang/crates.io-index" 171 - checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" 172 - dependencies = [ 173 - "anstream", 174 - "anstyle", 175 - "clap_lex", 176 - "strsim", 177 - ] 178 - 179 - [[package]] 180 - name = "clap_derive" 181 - version = "4.5.45" 182 - source = "registry+https://github.com/rust-lang/crates.io-index" 183 - checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" 184 - dependencies = [ 185 - "heck", 186 - "proc-macro2", 187 - "quote", 188 - "syn", 189 - ] 190 - 191 - [[package]] 192 - name = "clap_lex" 193 - version = "0.7.5" 194 - source = "registry+https://github.com/rust-lang/crates.io-index" 195 - checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 196 - 197 - [[package]] 198 - name = "clipboard-win" 199 - version = "5.4.1" 200 - source = "registry+https://github.com/rust-lang/crates.io-index" 201 - checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" 202 - dependencies = [ 203 - "error-code", 204 - ] 205 - 206 - [[package]] 207 - name = "colorchoice" 208 - version = "1.0.4" 209 - source = "registry+https://github.com/rust-lang/crates.io-index" 210 - checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 211 - 212 - [[package]] 213 - name = "console" 214 - version = "0.15.11" 215 - source = "registry+https://github.com/rust-lang/crates.io-index" 216 - checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 217 - dependencies = [ 218 - "encode_unicode", 219 - "libc", 220 - "once_cell", 221 - "windows-sys 0.59.0", 222 - ] 223 - 224 - [[package]] 225 - name = "core-foundation-sys" 226 - version = "0.8.7" 227 - source = "registry+https://github.com/rust-lang/crates.io-index" 228 - checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 229 - 230 - [[package]] 231 - name = "ecsdb" 232 - version = "0.1.0" 233 - dependencies = [ 234 - "anyhow", 235 - "anymap", 236 - "chrono", 237 - "clap", 238 - "ecsdb_derive", 239 - "eloquent", 240 - "hex", 241 - "insta", 242 - "lazy_static", 243 - "regex", 244 - "rusqlite", 245 - "rustyline", 246 - "self_cell", 247 - "serde", 248 - "serde_json", 249 - "thiserror", 250 - "tracing", 251 - "tracing-subscriber", 252 - ] 253 - 254 - [[package]] 255 - name = "ecsdb_derive" 256 - version = "0.1.0" 257 - dependencies = [ 258 - "proc-macro2", 259 - "quote", 260 - "syn", 261 - ] 262 - 263 - [[package]] 264 - name = "eloquent" 265 - version = "2.0.4" 266 - source = "registry+https://github.com/rust-lang/crates.io-index" 267 - checksum = "ebac9654d805c45acdc34fbd1662c9fb2f0f83212a5d1af5d535c378233ff1b9" 268 - dependencies = [ 269 - "eloquent_core", 270 - ] 271 - 272 - [[package]] 273 - name = "eloquent_core" 274 - version = "2.0.4" 275 - source = "registry+https://github.com/rust-lang/crates.io-index" 276 - checksum = "d29024f25aa28620ab42d49ab4d1ca77ec5fa8140389a9d3ad8b7ac980a7b640" 277 - dependencies = [ 278 - "log", 279 - "sqlformat", 280 - ] 281 - 282 - [[package]] 283 - name = "encode_unicode" 284 - version = "1.0.0" 285 - source = "registry+https://github.com/rust-lang/crates.io-index" 286 - checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 287 - 288 - [[package]] 289 - name = "endian-type" 290 - version = "0.1.2" 291 - source = "registry+https://github.com/rust-lang/crates.io-index" 292 - checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 293 - 294 - [[package]] 295 - name = "errno" 296 - version = "0.3.13" 297 - source = "registry+https://github.com/rust-lang/crates.io-index" 298 - checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" 299 - dependencies = [ 300 - "libc", 301 - "windows-sys 0.60.2", 302 - ] 303 - 304 - [[package]] 305 - name = "error-code" 306 - version = "3.3.2" 307 - source = "registry+https://github.com/rust-lang/crates.io-index" 308 - checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" 309 - 310 - [[package]] 311 - name = "fallible-iterator" 312 - version = "0.3.0" 313 - source = "registry+https://github.com/rust-lang/crates.io-index" 314 - checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" 315 - 316 - [[package]] 317 - name = "fallible-streaming-iterator" 318 - version = "0.1.9" 319 - source = "registry+https://github.com/rust-lang/crates.io-index" 320 - checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 321 - 322 - [[package]] 323 - name = "fd-lock" 324 - version = "4.0.4" 325 - source = "registry+https://github.com/rust-lang/crates.io-index" 326 - checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" 327 - dependencies = [ 328 - "cfg-if", 329 - "rustix", 330 - "windows-sys 0.59.0", 331 - ] 332 - 333 - [[package]] 334 - name = "hashbrown" 335 - version = "0.14.5" 336 - source = "registry+https://github.com/rust-lang/crates.io-index" 337 - checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 338 - dependencies = [ 339 - "ahash", 340 - ] 341 - 342 - [[package]] 343 - name = "hashlink" 344 - version = "0.9.1" 345 - source = "registry+https://github.com/rust-lang/crates.io-index" 346 - checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" 347 - dependencies = [ 348 - "hashbrown", 349 - ] 350 - 351 - [[package]] 352 - name = "heck" 353 - version = "0.5.0" 354 - source = "registry+https://github.com/rust-lang/crates.io-index" 355 - checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 356 - 357 - [[package]] 358 - name = "hex" 359 - version = "0.4.3" 360 - source = "registry+https://github.com/rust-lang/crates.io-index" 361 - checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 362 - 363 - [[package]] 364 - name = "home" 365 - version = "0.5.11" 366 - source = "registry+https://github.com/rust-lang/crates.io-index" 367 - checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 368 - dependencies = [ 369 - "windows-sys 0.59.0", 370 - ] 371 - 372 - [[package]] 373 - name = "iana-time-zone" 374 - version = "0.1.63" 375 - source = "registry+https://github.com/rust-lang/crates.io-index" 376 - checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 377 - dependencies = [ 378 - "android_system_properties", 379 - "core-foundation-sys", 380 - "iana-time-zone-haiku", 381 - "js-sys", 382 - "log", 383 - "wasm-bindgen", 384 - "windows-core", 385 - ] 386 - 387 - [[package]] 388 - name = "iana-time-zone-haiku" 389 - version = "0.1.2" 390 - source = "registry+https://github.com/rust-lang/crates.io-index" 391 - checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 392 - dependencies = [ 393 - "cc", 394 - ] 395 - 396 - [[package]] 397 - name = "insta" 398 - version = "1.43.1" 399 - source = "registry+https://github.com/rust-lang/crates.io-index" 400 - checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371" 401 - dependencies = [ 402 - "console", 403 - "once_cell", 404 - "similar", 405 - ] 406 - 407 - [[package]] 408 - name = "is_terminal_polyfill" 409 - version = "1.70.1" 410 - source = "registry+https://github.com/rust-lang/crates.io-index" 411 - checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 412 - 413 - [[package]] 414 - name = "itoa" 415 - version = "1.0.15" 416 - source = "registry+https://github.com/rust-lang/crates.io-index" 417 - checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 418 - 419 - [[package]] 420 - name = "js-sys" 421 - version = "0.3.77" 422 - source = "registry+https://github.com/rust-lang/crates.io-index" 423 - checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 424 - dependencies = [ 425 - "once_cell", 426 - "wasm-bindgen", 427 - ] 428 - 429 - [[package]] 430 - name = "lazy_static" 431 - version = "1.5.0" 432 - source = "registry+https://github.com/rust-lang/crates.io-index" 433 - checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 434 - 435 - [[package]] 436 - name = "libc" 437 - version = "0.2.175" 438 - source = "registry+https://github.com/rust-lang/crates.io-index" 439 - checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 440 - 441 - [[package]] 442 - name = "libsqlite3-sys" 443 - version = "0.30.1" 444 - source = "registry+https://github.com/rust-lang/crates.io-index" 445 - checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" 446 - dependencies = [ 447 - "pkg-config", 448 - "vcpkg", 449 - ] 450 - 451 - [[package]] 452 - name = "linux-raw-sys" 453 - version = "0.9.4" 454 - source = "registry+https://github.com/rust-lang/crates.io-index" 455 - checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 456 - 457 - [[package]] 458 - name = "log" 459 - version = "0.4.27" 460 - source = "registry+https://github.com/rust-lang/crates.io-index" 461 - checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 462 - 463 - [[package]] 464 - name = "memchr" 465 - version = "2.7.4" 466 - source = "registry+https://github.com/rust-lang/crates.io-index" 467 - checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 468 - 469 - [[package]] 470 - name = "nibble_vec" 471 - version = "0.1.0" 472 - source = "registry+https://github.com/rust-lang/crates.io-index" 473 - checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 474 - dependencies = [ 475 - "smallvec", 476 - ] 477 - 478 - [[package]] 479 - name = "nix" 480 - version = "0.30.1" 481 - source = "registry+https://github.com/rust-lang/crates.io-index" 482 - checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" 483 - dependencies = [ 484 - "bitflags", 485 - "cfg-if", 486 - "cfg_aliases", 487 - "libc", 488 - ] 489 - 490 - [[package]] 491 - name = "nu-ansi-term" 492 - version = "0.46.0" 493 - source = "registry+https://github.com/rust-lang/crates.io-index" 494 - checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 495 - dependencies = [ 496 - "overload", 497 - "winapi", 498 - ] 499 - 500 - [[package]] 501 - name = "num-traits" 502 - version = "0.2.19" 503 - source = "registry+https://github.com/rust-lang/crates.io-index" 504 - checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 505 - dependencies = [ 506 - "autocfg", 507 - ] 508 - 509 - [[package]] 510 - name = "once_cell" 511 - version = "1.21.3" 512 - source = "registry+https://github.com/rust-lang/crates.io-index" 513 - checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 514 - 515 - [[package]] 516 - name = "once_cell_polyfill" 517 - version = "1.70.1" 518 - source = "registry+https://github.com/rust-lang/crates.io-index" 519 - checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 520 - 521 - [[package]] 522 - name = "overload" 523 - version = "0.1.1" 524 - source = "registry+https://github.com/rust-lang/crates.io-index" 525 - checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 526 - 527 - [[package]] 528 - name = "pin-project-lite" 529 - version = "0.2.16" 530 - source = "registry+https://github.com/rust-lang/crates.io-index" 531 - checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 532 - 533 - [[package]] 534 - name = "pkg-config" 535 - version = "0.3.32" 536 - source = "registry+https://github.com/rust-lang/crates.io-index" 537 - checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 538 - 539 - [[package]] 540 - name = "proc-macro2" 541 - version = "1.0.97" 542 - source = "registry+https://github.com/rust-lang/crates.io-index" 543 - checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" 544 - dependencies = [ 545 - "unicode-ident", 546 - ] 547 - 548 - [[package]] 549 - name = "quote" 550 - version = "1.0.40" 551 - source = "registry+https://github.com/rust-lang/crates.io-index" 552 - checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 553 - dependencies = [ 554 - "proc-macro2", 555 - ] 556 - 557 - [[package]] 558 - name = "radix_trie" 559 - version = "0.2.1" 560 - source = "registry+https://github.com/rust-lang/crates.io-index" 561 - checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 562 - dependencies = [ 563 - "endian-type", 564 - "nibble_vec", 565 - ] 566 - 567 - [[package]] 568 - name = "regex" 569 - version = "1.11.1" 570 - source = "registry+https://github.com/rust-lang/crates.io-index" 571 - checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 572 - dependencies = [ 573 - "aho-corasick", 574 - "memchr", 575 - "regex-automata", 576 - "regex-syntax", 577 - ] 578 - 579 - [[package]] 580 - name = "regex-automata" 581 - version = "0.4.9" 582 - source = "registry+https://github.com/rust-lang/crates.io-index" 583 - checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 584 - dependencies = [ 585 - "aho-corasick", 586 - "memchr", 587 - "regex-syntax", 588 - ] 589 - 590 - [[package]] 591 - name = "regex-syntax" 592 - version = "0.8.5" 593 - source = "registry+https://github.com/rust-lang/crates.io-index" 594 - checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 595 - 596 - [[package]] 597 - name = "rusqlite" 598 - version = "0.32.1" 599 - source = "registry+https://github.com/rust-lang/crates.io-index" 600 - checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" 601 - dependencies = [ 602 - "bitflags", 603 - "fallible-iterator", 604 - "fallible-streaming-iterator", 605 - "hashlink", 606 - "libsqlite3-sys", 607 - "smallvec", 608 - ] 609 - 610 - [[package]] 611 - name = "rustix" 612 - version = "1.0.8" 613 - source = "registry+https://github.com/rust-lang/crates.io-index" 614 - checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" 615 - dependencies = [ 616 - "bitflags", 617 - "errno", 618 - "libc", 619 - "linux-raw-sys", 620 - "windows-sys 0.60.2", 621 - ] 622 - 623 - [[package]] 624 - name = "rustversion" 625 - version = "1.0.22" 626 - source = "registry+https://github.com/rust-lang/crates.io-index" 627 - checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 628 - 629 - [[package]] 630 - name = "rustyline" 631 - version = "17.0.1" 632 - source = "registry+https://github.com/rust-lang/crates.io-index" 633 - checksum = "a6614df0b6d4cfb20d1d5e295332921793ce499af3ebc011bf1e393380e1e492" 634 - dependencies = [ 635 - "bitflags", 636 - "cfg-if", 637 - "clipboard-win", 638 - "fd-lock", 639 - "home", 640 - "libc", 641 - "log", 642 - "memchr", 643 - "nix", 644 - "radix_trie", 645 - "unicode-segmentation", 646 - "unicode-width", 647 - "utf8parse", 648 - "windows-sys 0.60.2", 649 - ] 650 - 651 - [[package]] 652 - name = "ryu" 653 - version = "1.0.20" 654 - source = "registry+https://github.com/rust-lang/crates.io-index" 655 - checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 656 - 657 - [[package]] 658 - name = "self_cell" 659 - version = "1.2.0" 660 - source = "registry+https://github.com/rust-lang/crates.io-index" 661 - checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" 662 - 663 - [[package]] 664 - name = "serde" 665 - version = "1.0.219" 666 - source = "registry+https://github.com/rust-lang/crates.io-index" 667 - checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 668 - dependencies = [ 669 - "serde_derive", 670 - ] 671 - 672 - [[package]] 673 - name = "serde_derive" 674 - version = "1.0.219" 675 - source = "registry+https://github.com/rust-lang/crates.io-index" 676 - checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 677 - dependencies = [ 678 - "proc-macro2", 679 - "quote", 680 - "syn", 681 - ] 682 - 683 - [[package]] 684 - name = "serde_json" 685 - version = "1.0.142" 686 - source = "registry+https://github.com/rust-lang/crates.io-index" 687 - checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" 688 - dependencies = [ 689 - "itoa", 690 - "memchr", 691 - "ryu", 692 - "serde", 693 - ] 694 - 695 - [[package]] 696 - name = "sharded-slab" 697 - version = "0.1.7" 698 - source = "registry+https://github.com/rust-lang/crates.io-index" 699 - checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 700 - dependencies = [ 701 - "lazy_static", 702 - ] 703 - 704 - [[package]] 705 - name = "shlex" 706 - version = "1.3.0" 707 - source = "registry+https://github.com/rust-lang/crates.io-index" 708 - checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 709 - 710 - [[package]] 711 - name = "similar" 712 - version = "2.7.0" 713 - source = "registry+https://github.com/rust-lang/crates.io-index" 714 - checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" 715 - 716 - [[package]] 717 - name = "smallvec" 718 - version = "1.15.1" 719 - source = "registry+https://github.com/rust-lang/crates.io-index" 720 - checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 721 - 722 - [[package]] 723 - name = "sqlformat" 724 - version = "0.3.5" 725 - source = "registry+https://github.com/rust-lang/crates.io-index" 726 - checksum = "a0d7b3e8a3b6f2ee93ac391a0f757c13790caa0147892e3545cd549dd5b54bc0" 727 - dependencies = [ 728 - "unicode_categories", 729 - "winnow", 730 - ] 731 - 732 - [[package]] 733 - name = "strsim" 734 - version = "0.11.1" 735 - source = "registry+https://github.com/rust-lang/crates.io-index" 736 - checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 737 - 738 - [[package]] 739 - name = "syn" 740 - version = "2.0.104" 741 - source = "registry+https://github.com/rust-lang/crates.io-index" 742 - checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 743 - dependencies = [ 744 - "proc-macro2", 745 - "quote", 746 - "unicode-ident", 747 - ] 748 - 749 - [[package]] 750 - name = "thiserror" 751 - version = "2.0.14" 752 - source = "registry+https://github.com/rust-lang/crates.io-index" 753 - checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" 754 - dependencies = [ 755 - "thiserror-impl", 756 - ] 757 - 758 - [[package]] 759 - name = "thiserror-impl" 760 - version = "2.0.14" 761 - source = "registry+https://github.com/rust-lang/crates.io-index" 762 - checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" 763 - dependencies = [ 764 - "proc-macro2", 765 - "quote", 766 - "syn", 767 - ] 768 - 769 - [[package]] 770 - name = "thread_local" 771 - version = "1.1.9" 772 - source = "registry+https://github.com/rust-lang/crates.io-index" 773 - checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 774 - dependencies = [ 775 - "cfg-if", 776 - ] 777 - 778 - [[package]] 779 - name = "tracing" 780 - version = "0.1.41" 781 - source = "registry+https://github.com/rust-lang/crates.io-index" 782 - checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 783 - dependencies = [ 784 - "pin-project-lite", 785 - "tracing-attributes", 786 - "tracing-core", 787 - ] 788 - 789 - [[package]] 790 - name = "tracing-attributes" 791 - version = "0.1.30" 792 - source = "registry+https://github.com/rust-lang/crates.io-index" 793 - checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 794 - dependencies = [ 795 - "proc-macro2", 796 - "quote", 797 - "syn", 798 - ] 799 - 800 - [[package]] 801 - name = "tracing-core" 802 - version = "0.1.34" 803 - source = "registry+https://github.com/rust-lang/crates.io-index" 804 - checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 805 - dependencies = [ 806 - "once_cell", 807 - "valuable", 808 - ] 809 - 810 - [[package]] 811 - name = "tracing-log" 812 - version = "0.2.0" 813 - source = "registry+https://github.com/rust-lang/crates.io-index" 814 - checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 815 - dependencies = [ 816 - "log", 817 - "once_cell", 818 - "tracing-core", 819 - ] 820 - 821 - [[package]] 822 - name = "tracing-subscriber" 823 - version = "0.3.19" 824 - source = "registry+https://github.com/rust-lang/crates.io-index" 825 - checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 826 - dependencies = [ 827 - "nu-ansi-term", 828 - "sharded-slab", 829 - "smallvec", 830 - "thread_local", 831 - "tracing-core", 832 - "tracing-log", 833 - ] 834 - 835 - [[package]] 836 - name = "unicode-ident" 837 - version = "1.0.18" 838 - source = "registry+https://github.com/rust-lang/crates.io-index" 839 - checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 840 - 841 - [[package]] 842 - name = "unicode-segmentation" 843 - version = "1.12.0" 844 - source = "registry+https://github.com/rust-lang/crates.io-index" 845 - checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 846 - 847 - [[package]] 848 - name = "unicode-width" 849 - version = "0.2.1" 850 - source = "registry+https://github.com/rust-lang/crates.io-index" 851 - checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" 852 - 853 - [[package]] 854 - name = "unicode_categories" 855 - version = "0.1.1" 856 - source = "registry+https://github.com/rust-lang/crates.io-index" 857 - checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 858 - 859 - [[package]] 860 - name = "utf8parse" 861 - version = "0.2.2" 862 - source = "registry+https://github.com/rust-lang/crates.io-index" 863 - checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 864 - 865 - [[package]] 866 - name = "valuable" 867 - version = "0.1.1" 868 - source = "registry+https://github.com/rust-lang/crates.io-index" 869 - checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 870 - 871 - [[package]] 872 - name = "vcpkg" 873 - version = "0.2.15" 874 - source = "registry+https://github.com/rust-lang/crates.io-index" 875 - checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 876 - 877 - [[package]] 878 - name = "version_check" 879 - version = "0.9.5" 880 - source = "registry+https://github.com/rust-lang/crates.io-index" 881 - checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 882 - 883 - [[package]] 884 - name = "wasm-bindgen" 885 - version = "0.2.100" 886 - source = "registry+https://github.com/rust-lang/crates.io-index" 887 - checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 888 - dependencies = [ 889 - "cfg-if", 890 - "once_cell", 891 - "rustversion", 892 - "wasm-bindgen-macro", 893 - ] 894 - 895 - [[package]] 896 - name = "wasm-bindgen-backend" 897 - version = "0.2.100" 898 - source = "registry+https://github.com/rust-lang/crates.io-index" 899 - checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 900 - dependencies = [ 901 - "bumpalo", 902 - "log", 903 - "proc-macro2", 904 - "quote", 905 - "syn", 906 - "wasm-bindgen-shared", 907 - ] 908 - 909 - [[package]] 910 - name = "wasm-bindgen-macro" 911 - version = "0.2.100" 912 - source = "registry+https://github.com/rust-lang/crates.io-index" 913 - checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 914 - dependencies = [ 915 - "quote", 916 - "wasm-bindgen-macro-support", 917 - ] 918 - 919 - [[package]] 920 - name = "wasm-bindgen-macro-support" 921 - version = "0.2.100" 922 - source = "registry+https://github.com/rust-lang/crates.io-index" 923 - checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 924 - dependencies = [ 925 - "proc-macro2", 926 - "quote", 927 - "syn", 928 - "wasm-bindgen-backend", 929 - "wasm-bindgen-shared", 930 - ] 931 - 932 - [[package]] 933 - name = "wasm-bindgen-shared" 934 - version = "0.2.100" 935 - source = "registry+https://github.com/rust-lang/crates.io-index" 936 - checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 937 - dependencies = [ 938 - "unicode-ident", 939 - ] 940 - 941 - [[package]] 942 - name = "winapi" 943 - version = "0.3.9" 944 - source = "registry+https://github.com/rust-lang/crates.io-index" 945 - checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 946 - dependencies = [ 947 - "winapi-i686-pc-windows-gnu", 948 - "winapi-x86_64-pc-windows-gnu", 949 - ] 950 - 951 - [[package]] 952 - name = "winapi-i686-pc-windows-gnu" 953 - version = "0.4.0" 954 - source = "registry+https://github.com/rust-lang/crates.io-index" 955 - checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 956 - 957 - [[package]] 958 - name = "winapi-x86_64-pc-windows-gnu" 959 - version = "0.4.0" 960 - source = "registry+https://github.com/rust-lang/crates.io-index" 961 - checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 962 - 963 - [[package]] 964 - name = "windows-core" 965 - version = "0.61.2" 966 - source = "registry+https://github.com/rust-lang/crates.io-index" 967 - checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 968 - dependencies = [ 969 - "windows-implement", 970 - "windows-interface", 971 - "windows-link", 972 - "windows-result", 973 - "windows-strings", 974 - ] 975 - 976 - [[package]] 977 - name = "windows-implement" 978 - version = "0.60.0" 979 - source = "registry+https://github.com/rust-lang/crates.io-index" 980 - checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 981 - dependencies = [ 982 - "proc-macro2", 983 - "quote", 984 - "syn", 985 - ] 986 - 987 - [[package]] 988 - name = "windows-interface" 989 - version = "0.59.1" 990 - source = "registry+https://github.com/rust-lang/crates.io-index" 991 - checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 992 - dependencies = [ 993 - "proc-macro2", 994 - "quote", 995 - "syn", 996 - ] 997 - 998 - [[package]] 999 - name = "windows-link" 1000 - version = "0.1.3" 1001 - source = "registry+https://github.com/rust-lang/crates.io-index" 1002 - checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 1003 - 1004 - [[package]] 1005 - name = "windows-result" 1006 - version = "0.3.4" 1007 - source = "registry+https://github.com/rust-lang/crates.io-index" 1008 - checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 1009 - dependencies = [ 1010 - "windows-link", 1011 - ] 1012 - 1013 - [[package]] 1014 - name = "windows-strings" 1015 - version = "0.4.2" 1016 - source = "registry+https://github.com/rust-lang/crates.io-index" 1017 - checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 1018 - dependencies = [ 1019 - "windows-link", 1020 - ] 1021 - 1022 - [[package]] 1023 - name = "windows-sys" 1024 - version = "0.59.0" 1025 - source = "registry+https://github.com/rust-lang/crates.io-index" 1026 - checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1027 - dependencies = [ 1028 - "windows-targets 0.52.6", 1029 - ] 1030 - 1031 - [[package]] 1032 - name = "windows-sys" 1033 - version = "0.60.2" 1034 - source = "registry+https://github.com/rust-lang/crates.io-index" 1035 - checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 1036 - dependencies = [ 1037 - "windows-targets 0.53.3", 1038 - ] 1039 - 1040 - [[package]] 1041 - name = "windows-targets" 1042 - version = "0.52.6" 1043 - source = "registry+https://github.com/rust-lang/crates.io-index" 1044 - checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1045 - dependencies = [ 1046 - "windows_aarch64_gnullvm 0.52.6", 1047 - "windows_aarch64_msvc 0.52.6", 1048 - "windows_i686_gnu 0.52.6", 1049 - "windows_i686_gnullvm 0.52.6", 1050 - "windows_i686_msvc 0.52.6", 1051 - "windows_x86_64_gnu 0.52.6", 1052 - "windows_x86_64_gnullvm 0.52.6", 1053 - "windows_x86_64_msvc 0.52.6", 1054 - ] 1055 - 1056 - [[package]] 1057 - name = "windows-targets" 1058 - version = "0.53.3" 1059 - source = "registry+https://github.com/rust-lang/crates.io-index" 1060 - checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" 1061 - dependencies = [ 1062 - "windows-link", 1063 - "windows_aarch64_gnullvm 0.53.0", 1064 - "windows_aarch64_msvc 0.53.0", 1065 - "windows_i686_gnu 0.53.0", 1066 - "windows_i686_gnullvm 0.53.0", 1067 - "windows_i686_msvc 0.53.0", 1068 - "windows_x86_64_gnu 0.53.0", 1069 - "windows_x86_64_gnullvm 0.53.0", 1070 - "windows_x86_64_msvc 0.53.0", 1071 - ] 1072 - 1073 - [[package]] 1074 - name = "windows_aarch64_gnullvm" 1075 - version = "0.52.6" 1076 - source = "registry+https://github.com/rust-lang/crates.io-index" 1077 - checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1078 - 1079 - [[package]] 1080 - name = "windows_aarch64_gnullvm" 1081 - version = "0.53.0" 1082 - source = "registry+https://github.com/rust-lang/crates.io-index" 1083 - checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 1084 - 1085 - [[package]] 1086 - name = "windows_aarch64_msvc" 1087 - version = "0.52.6" 1088 - source = "registry+https://github.com/rust-lang/crates.io-index" 1089 - checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1090 - 1091 - [[package]] 1092 - name = "windows_aarch64_msvc" 1093 - version = "0.53.0" 1094 - source = "registry+https://github.com/rust-lang/crates.io-index" 1095 - checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 1096 - 1097 - [[package]] 1098 - name = "windows_i686_gnu" 1099 - version = "0.52.6" 1100 - source = "registry+https://github.com/rust-lang/crates.io-index" 1101 - checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1102 - 1103 - [[package]] 1104 - name = "windows_i686_gnu" 1105 - version = "0.53.0" 1106 - source = "registry+https://github.com/rust-lang/crates.io-index" 1107 - checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 1108 - 1109 - [[package]] 1110 - name = "windows_i686_gnullvm" 1111 - version = "0.52.6" 1112 - source = "registry+https://github.com/rust-lang/crates.io-index" 1113 - checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1114 - 1115 - [[package]] 1116 - name = "windows_i686_gnullvm" 1117 - version = "0.53.0" 1118 - source = "registry+https://github.com/rust-lang/crates.io-index" 1119 - checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 1120 - 1121 - [[package]] 1122 - name = "windows_i686_msvc" 1123 - version = "0.52.6" 1124 - source = "registry+https://github.com/rust-lang/crates.io-index" 1125 - checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1126 - 1127 - [[package]] 1128 - name = "windows_i686_msvc" 1129 - version = "0.53.0" 1130 - source = "registry+https://github.com/rust-lang/crates.io-index" 1131 - checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 1132 - 1133 - [[package]] 1134 - name = "windows_x86_64_gnu" 1135 - version = "0.52.6" 1136 - source = "registry+https://github.com/rust-lang/crates.io-index" 1137 - checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1138 - 1139 - [[package]] 1140 - name = "windows_x86_64_gnu" 1141 - version = "0.53.0" 1142 - source = "registry+https://github.com/rust-lang/crates.io-index" 1143 - checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 1144 - 1145 - [[package]] 1146 - name = "windows_x86_64_gnullvm" 1147 - version = "0.52.6" 1148 - source = "registry+https://github.com/rust-lang/crates.io-index" 1149 - checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1150 - 1151 - [[package]] 1152 - name = "windows_x86_64_gnullvm" 1153 - version = "0.53.0" 1154 - source = "registry+https://github.com/rust-lang/crates.io-index" 1155 - checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 1156 - 1157 - [[package]] 1158 - name = "windows_x86_64_msvc" 1159 - version = "0.52.6" 1160 - source = "registry+https://github.com/rust-lang/crates.io-index" 1161 - checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1162 - 1163 - [[package]] 1164 - name = "windows_x86_64_msvc" 1165 - version = "0.53.0" 1166 - source = "registry+https://github.com/rust-lang/crates.io-index" 1167 - checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 1168 - 1169 - [[package]] 1170 - name = "winnow" 1171 - version = "0.6.26" 1172 - source = "registry+https://github.com/rust-lang/crates.io-index" 1173 - checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" 1174 - dependencies = [ 1175 - "memchr", 1176 - ] 1177 - 1178 - [[package]] 1179 - name = "zerocopy" 1180 - version = "0.8.26" 1181 - source = "registry+https://github.com/rust-lang/crates.io-index" 1182 - checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" 1183 - dependencies = [ 1184 - "zerocopy-derive", 1185 - ] 1186 - 1187 - [[package]] 1188 - name = "zerocopy-derive" 1189 - version = "0.8.26" 1190 - source = "registry+https://github.com/rust-lang/crates.io-index" 1191 - checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" 1192 - dependencies = [ 1193 - "proc-macro2", 1194 - "quote", 1195 - "syn", 1196 - ]
+364 -113
src/lib.rs
··· 41 41 42 42 use tracing::{debug, instrument}; 43 43 44 + use crate::query::QueryFilterValue; 45 + 44 46 pub type EntityId = i64; 45 47 46 48 #[derive(Debug, thiserror::Error)] ··· 67 69 68 70 69 71 72 + conn.execute_batch(include_str!("schema.sql"))?; 73 + conn.set_transaction_behavior(::rusqlite::TransactionBehavior::Immediate); 70 74 75 + sqlite_ext::add_regexp_function(&conn)?; 71 76 72 - 73 - 74 - 75 - 76 - 77 + Ok(Self { 78 + conn, 77 79 78 80 79 81 ··· 111 113 } 112 114 113 115 impl Ecs { 114 - pub fn query<'a, D>(&'a self) -> impl Iterator<Item = D::Output<'a>> + 'a 116 + pub fn query<'a, D, F>(&'a self) -> impl Iterator<Item = D::Output<'a>> + 'a 115 117 where 116 118 D: query::QueryData + 'a, 119 + F: query::QueryFilter + 'a, 117 120 { 118 - self.try_query::<D>().unwrap() 121 + self.try_query::<D, F>().unwrap() 119 122 } 120 123 121 124 #[instrument(name = "query", level = "debug", skip_all)] 122 - pub fn try_query<'a, Q>(&'a self) -> Result<impl Iterator<Item = Q::Output<'a>> + 'a, Error> 125 + pub fn try_query<'a, D, F>(&'a self) -> Result<impl Iterator<Item = D::Output<'a>> + 'a, Error> 123 126 where 124 - Q: query::QueryData + 'a, 127 + D: query::QueryData + 'a, 128 + F: query::QueryFilter + 'a, 125 129 { 126 - debug!(query = std::any::type_name::<Q>()); 127 - let query = query::Query::<Q>::new(self); 130 + debug!( 131 + data = std::any::type_name::<D>(), 132 + filter = std::any::type_name::<F>() 133 + ); 134 + let query = query::Query::<D, F>::new(self); 128 135 query.try_iter() 129 136 } 130 137 131 - pub fn query_filtered<'a, D, F>(&'a self) -> impl Iterator<Item = D::Output<'a>> + 'a 138 + pub fn query_filtered<'a, D, F>( 139 + &'a self, 140 + filter: impl QueryFilterValue + 'a, 141 + ) -> impl Iterator<Item = D::Output<'a>> + 'a 132 142 where 133 143 D: query::QueryData + 'a, 134 144 F: query::QueryFilter + 'a, 135 145 { 136 - self.try_query_filtered::<D, F>().unwrap() 146 + self.try_query_fitered::<D, F>(filter).unwrap() 137 147 } 138 148 139 - #[instrument(name = "query_filtered", level = "debug", skip_all)] 140 - pub fn try_query_filtered<'a, Q, F>( 149 + #[instrument(name = "find", level = "debug", skip_all)] 150 + pub fn try_query_fitered<'a, D, F>( 141 151 &'a self, 142 - ) -> Result<impl Iterator<Item = Q::Output<'a>> + 'a, Error> 152 + filter_value: impl query::QueryFilterValue + 'a, 153 + ) -> Result<impl Iterator<Item = D::Output<'a>> + 'a, Error> 143 154 where 155 + D: query::QueryData + 'a, 144 156 F: query::QueryFilter + 'a, 145 - Q: query::QueryData + 'a, 146 157 { 147 - debug!( 148 - query = std::any::type_name::<Q>(), 149 - filter = std::any::type_name::<F>() 150 - ); 151 - let query = query::Query::<Q, F>::new(self); 158 + let query = query::Query::<D, F, _>::with_filter(self, filter_value); 152 159 query.try_iter() 153 160 } 154 161 } 155 162 156 163 impl Ecs { 157 - pub fn find<'a, V>(&'a self, filter: V) -> impl Iterator<Item = Entity<'a>> + 'a 158 - where 159 - V: query::QueryFilterValue, 160 - { 161 - self.try_find::<V>(filter).unwrap() 164 + pub fn find<'a>( 165 + &'a self, 166 + filter_value: impl query::QueryFilterValue + 'a, 167 + ) -> impl Iterator<Item = Entity<'a>> + 'a { 168 + self.try_find(filter_value).unwrap() 162 169 } 163 170 164 - #[instrument(name = "find", level = "debug", skip_all)] 165 - pub fn try_find<'a, V>( 171 + pub fn try_find<'a>( 166 172 &'a self, 167 - filter_value: V, 168 - ) -> Result<impl Iterator<Item = Entity<'a>> + 'a, Error> 169 - where 170 - V: query::QueryFilterValue, 171 - { 172 - let query = query::Query::<Entity, (), _>::with_filter(self, filter_value); 173 - query.try_iter() 173 + filter_value: impl query::QueryFilterValue + 'a, 174 + ) -> Result<impl Iterator<Item = Entity<'a>> + 'a, Error> { 175 + self.try_query_fitered::<Entity<'a>, ()>(filter_value) 174 176 } 175 177 } 176 178 179 + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 180 + pub struct CreatedAt(pub chrono::DateTime<chrono::Utc>); 177 181 178 182 179 183 ··· 186 190 187 191 188 192 193 + let rows = entity_ids 194 + .into_iter() 195 + .scan(self, |ecs, eid| Some(Entity::with_id(ecs, eid))) 196 + .map(|e| { 197 + debug!( 198 + data = std::any::type_name::<Q>(), 189 199 190 200 191 201 ··· 220 230 221 231 222 232 223 - 224 - 225 - 226 - 227 - 228 - 229 - 230 - 231 - 232 - 233 - 234 - 235 - 236 - 237 - 233 + ) -> Result<impl Iterator<Item = Q::Output<'a>> + 'a, Error> { 234 + let rows = self 235 + .fetch_entity_ids_lazy(sql_query)? 236 + .scan(self, |ecs, eid| Some(Entity::with_id(ecs, eid))) 237 + .map(|e| { 238 + debug!( 239 + data = std::any::type_name::<Q>(), 238 240 239 241 240 242 ··· 487 489 #[test] 488 490 fn query_tuples() { 489 491 let db = super::Ecs::open_in_memory().unwrap(); 490 - let _ = db.query::<MarkerComponent>(); 491 - let _ = db.query::<Entity>(); 492 - let _ = db.query::<(Entity, MarkerComponent)>(); 493 - 494 - let _ = db.query_filtered::<Entity, With<MarkerComponent>>(); 495 - let _ = db.query_filtered::<Entity, Without<MarkerComponent>>(); 496 - let _ = db.query_filtered::<MarkerComponent, Or<( 497 - Without<(MarkerComponent, MarkerComponent)>, 498 - With<(MarkerComponent, MarkerComponent)>, 499 - Or<(With<MarkerComponent>, Without<MarkerComponent>)>, 500 - )>>(); 501 - let _ = db.query_filtered::<MarkerComponent, ( 502 - With<(MarkerComponent, ComponentWithData)>, 503 - Without<(MarkerComponent, MarkerComponent)>, 504 - )>(); 505 - let _ = db.query_filtered::<MarkerComponent, Without<ComponentWithData>>(); 506 - let _ = db.query_filtered::<MarkerComponent, Without<ComponentWithData>>(); 507 - let _ = db.query_filtered::<(), ( 508 - With<MarkerComponent>, 509 - With<MarkerComponent>, 510 - With<MarkerComponent>, 511 - With<MarkerComponent>, 512 - With<MarkerComponent>, 513 - With<MarkerComponent>, 514 - Without<MarkerComponent>, 515 - With<MarkerComponent>, 516 - )>(); 492 + let _: Vec<MarkerComponent> = db.query::<MarkerComponent, ()>().collect(); 493 + 494 + let _: Vec<Entity> = db.query::<Entity, ()>().collect(); 495 + let _: Vec<(Entity, MarkerComponent)> = 496 + db.query::<(Entity, MarkerComponent), ()>().collect(); 497 + 498 + let _: Vec<Entity> = db.query::<Entity, With<MarkerComponent>>().collect(); 499 + let _: Vec<Entity> = db.query::<Entity, Without<MarkerComponent>>().collect(); 500 + let _: Vec<MarkerComponent> = db 501 + .query::<MarkerComponent, Or<( 502 + Without<(MarkerComponent, MarkerComponent)>, 503 + With<(MarkerComponent, MarkerComponent)>, 504 + Or<(With<MarkerComponent>, Without<MarkerComponent>)>, 505 + )>>() 506 + .collect(); 507 + 508 + let _: Vec<MarkerComponent> = db 509 + .query::<MarkerComponent, ( 510 + With<(MarkerComponent, ComponentWithData)>, 511 + Without<(MarkerComponent, MarkerComponent)>, 512 + )>() 513 + .collect(); 514 + 515 + let _: Vec<MarkerComponent> = db 516 + .query::<MarkerComponent, Without<ComponentWithData>>() 517 + .collect(); 518 + 519 + let _: Vec<()> = db 520 + .query::<(), ( 521 + With<MarkerComponent>, 522 + With<MarkerComponent>, 523 + With<MarkerComponent>, 524 + With<MarkerComponent>, 525 + With<MarkerComponent>, 526 + With<MarkerComponent>, 527 + Without<MarkerComponent>, 528 + With<MarkerComponent>, 529 + )>() 530 + .collect(); 517 531 } 518 532 519 533 #[test] ··· 526 540 527 541 db.new_entity().attach(ComponentWithData(1234)); 528 542 529 - assert_eq!(db.query::<()>().count(), 2); 530 - assert_eq!(db.query::<MarkerComponent>().count(), 1); 531 - assert_eq!(db.query::<MarkerComponent>().count(), 1); 543 + assert_eq!(db.query::<EntityId, ()>().count(), 2); 544 + assert_eq!(db.query::<EntityId, MarkerComponent>().count(), 1); 545 + assert_eq!(db.query::<EntityId, MarkerComponent>().count(), 1); 532 546 } 533 547 534 548 #[test] ··· 541 555 542 556 db.new_entity().attach(ComponentWithData(1234)); 543 557 558 + assert_eq!(db.query::<EntityId, Without<MarkerComponent>>().count(), 1); 544 559 assert_eq!( 545 - db.query_filtered::<EntityId, Without<MarkerComponent>>() 560 + db.query::<EntityId, (MarkerComponent, ComponentWithData)>() 546 561 .count(), 547 562 1 548 563 ); 549 564 assert_eq!( 550 - db.query::<(MarkerComponent, ComponentWithData)>().count(), 551 - 1 552 - ); 553 - assert_eq!( 554 - db.query_filtered::<MarkerComponent, Without<MarkerComponent>>() 565 + db.query::<MarkerComponent, Without<MarkerComponent>>() 555 566 .count(), 556 567 0 557 568 ); 569 + 558 570 assert_eq!( 559 - db.query_filtered::<MarkerComponent, ( 571 + db.query::<MarkerComponent, ( 560 572 Without<MarkerComponent>, 561 573 Or<(With<MarkerComponent>, With<ComponentWithData>)> 562 574 )>() 563 575 .count(), 564 576 0 565 577 ); 566 - assert_eq!(db.query::<ComponentWithData>().count(), 2); 578 + assert_eq!(db.query::<EntityId, ComponentWithData>().count(), 2); 567 579 } 568 580 569 581 #[test] ··· 574 586 let c = db.new_entity().attach(C).id(); 575 587 576 588 assert_eq!( 577 - db.query_filtered::<EntityId, Or<(With<A>, With<B>, With<C>)>>() 589 + db.query::<EntityId, Or<(With<A>, With<B>, With<C>)>>() 578 590 .collect::<Vec<_>>(), 579 591 vec![a, b, c] 580 592 ); 581 593 assert_eq!( 582 - db.query_filtered::<EntityId, Or<(With<A>, With<B>)>>() 594 + db.query::<EntityId, Or<(With<A>, With<B>)>>() 583 595 .collect::<Vec<_>>(), 584 596 vec![a, b] 585 597 ); 586 598 assert_eq!( 587 - db.query_filtered::<EntityId, Or<(With<A>,)>>() 588 - .collect::<Vec<_>>(), 599 + db.query::<EntityId, Or<(With<A>,)>>().collect::<Vec<_>>(), 589 600 vec![a] 590 601 ); 591 602 assert_eq!( 592 - db.query_filtered::<EntityId, Or<(With<B>,)>>() 593 - .collect::<Vec<_>>(), 603 + db.query::<EntityId, Or<(With<B>,)>>().collect::<Vec<_>>(), 594 604 vec![b] 595 605 ); 596 606 } ··· 603 613 let c = db.new_entity().attach(C).id(); 604 614 605 615 assert_eq!( 606 - db.query_filtered::<EntityId, AnyOf<(A, B)>>() 607 - .collect::<Vec<_>>(), 616 + db.query::<EntityId, AnyOf<(A, B)>>().collect::<Vec<_>>(), 608 617 vec![a, b] 609 618 ); 610 619 611 620 assert_eq!( 612 - db.query_filtered::<EntityId, AnyOf<(A, C)>>() 613 - .collect::<Vec<_>>(), 621 + db.query::<EntityId, AnyOf<(A, C)>>().collect::<Vec<_>>(), 614 622 vec![a, c] 615 623 ); 616 624 617 625 assert_eq!( 618 - db.query_filtered::<EntityId, AnyOf<(A,)>>() 619 - .collect::<Vec<_>>(), 626 + db.query::<EntityId, AnyOf<(A,)>>().collect::<Vec<_>>(), 620 627 vec![a] 621 628 ); 622 629 } 623 630 624 631 #[test] 625 - fn find() { 632 + fn query_filtered() { 626 633 let db = Ecs::open_in_memory().unwrap(); 627 634 let eid = db.new_entity().attach(ComponentWithData(123)).id(); 628 635 let _ = db.new_entity().attach(ComponentWithData(123)); 629 636 let _ = db.new_entity().attach(ComponentWithData(255)); 630 637 631 - assert_eq!(db.find(eid).count(), 1); 632 - assert_eq!(db.find(eid).next().unwrap().id(), eid); 633 - assert_eq!(db.find((eid, MarkerComponent)).count(), 0); 634 - assert_eq!(db.find(MarkerComponent).count(), 0); 635 - assert_eq!(db.find(ComponentWithData(0)).count(), 0); 636 - assert_eq!(db.find(ComponentWithData(123)).count(), 2); 637 - assert_eq!(db.find(ComponentWithData(255)).count(), 1); 638 + assert_eq!(db.query_filtered::<EntityId, ()>(eid).count(), 1); 639 + assert_eq!( 640 + db.query_filtered::<EntityId, ()>(eid).collect::<Vec<_>>(), 641 + vec![eid] 642 + ); 643 + assert_eq!( 644 + db.query_filtered::<EntityId, ()>((eid, MarkerComponent)) 645 + .count(), 646 + 0 647 + ); 648 + assert_eq!( 649 + db.query_filtered::<EntityId, ()>(MarkerComponent).count(), 650 + 0 651 + ); 652 + assert_eq!( 653 + db.query_filtered::<EntityId, ()>(ComponentWithData(0)) 654 + .count(), 655 + 0 656 + ); 657 + assert_eq!( 658 + db.query_filtered::<EntityId, ()>(ComponentWithData(123)) 659 + .count(), 660 + 2 661 + ); 662 + assert_eq!( 663 + db.query_filtered::<EntityId, ()>(ComponentWithData(255)) 664 + .count(), 665 + 1 666 + ); 638 667 639 668 let _ = db 640 669 .new_entity() 641 670 .attach(MarkerComponent) 642 671 .attach(ComponentWithData(12345)); 643 672 assert_eq!( 644 - db.find((MarkerComponent, ComponentWithData(12345))).count(), 673 + db.query_filtered::<EntityId, ()>((MarkerComponent, ComponentWithData(12345))) 674 + .count(), 645 675 1 646 676 ); 647 677 } ··· 654 684 } 655 685 656 686 let mut res = db 657 - .find(ComponentWithData(0)..ComponentWithData(10)) 687 + .query_filtered::<Entity, ()>(ComponentWithData(0)..ComponentWithData(10)) 658 688 .map(|e| e.component::<ComponentWithData>().unwrap()); 659 689 assert_eq!(res.next(), Some(ComponentWithData(0))); 660 690 assert_eq!(res.last(), Some(ComponentWithData(10))); 661 691 662 692 let mut res = db 663 - .find(ComponentWithData(10)..) 693 + .query_filtered::<Entity, ()>(ComponentWithData(10)..) 664 694 .map(|e| e.component::<ComponentWithData>().unwrap()); 665 695 assert_eq!(res.next(), Some(ComponentWithData(10))); 666 696 assert_eq!(res.last(), Some(ComponentWithData(99))); 667 697 668 698 let mut res = db 669 - .find(..ComponentWithData(10)) 699 + .query_filtered::<Entity, ()>(..ComponentWithData(10)) 670 700 .map(|e| e.component::<ComponentWithData>().unwrap()); 671 701 assert_eq!(res.next(), Some(ComponentWithData(0))); 672 702 assert_eq!(res.last(), Some(ComponentWithData(10))); 703 + 704 + 705 + 706 + 707 + 708 + 709 + 710 + 711 + 712 + 713 + 714 + 715 + 716 + 717 + 718 + 719 + 720 + 721 + 722 + 723 + 724 + 725 + 726 + 727 + 728 + 729 + 730 + 731 + 732 + 733 + 734 + 735 + 736 + 737 + 738 + 739 + 740 + 741 + 742 + 743 + 744 + 745 + 746 + 747 + 748 + 749 + 750 + 751 + 752 + 753 + 754 + 755 + 756 + 757 + 758 + 759 + 760 + 761 + 762 + 763 + 764 + 765 + 766 + assert!(a.has::<A>()); 767 + assert!(!a.has::<B>()); 768 + 769 + assert!(a.has::<(A,)>()); 770 + assert!(!a.has::<(A, B)>()); 771 + assert!(!a.has::<(A, B, A)>()); 772 + 773 + let ab = db.new_entity().attach(A).attach(B); 774 + assert!(ab.has::<(A, B)>()); 775 + assert!(ab.has::<(A, A)>()); 776 + assert!(ab.has::<(A, B, A)>()); 777 + } 778 + 779 + #[test] 780 + 781 + 782 + 783 + 784 + 785 + 786 + 787 + 788 + 789 + 790 + 791 + 792 + 793 + 794 + 795 + 796 + 797 + 798 + 799 + 800 + 801 + 802 + 803 + 804 + 805 + 806 + 807 + 808 + 809 + 810 + 811 + 812 + 813 + 814 + 815 + 816 + 817 + 818 + 819 + 820 + 821 + 822 + 823 + 824 + 825 + 826 + 827 + 828 + 829 + 830 + 831 + 832 + 833 + 834 + 835 + 836 + 837 + 838 + 839 + 840 + 841 + 842 + 843 + 844 + 845 + 846 + 847 + 848 + 849 + 850 + 851 + 852 + 853 + 854 + 855 + 856 + 857 + 858 + 859 + 860 + 861 + 862 + 863 + 864 + 865 + 866 + 867 + 868 + 869 + 870 + 871 + 872 + 873 + 874 + 875 + 876 + 877 + 878 + 879 + 880 + 881 + 882 + 883 + 884 + 885 + 886 + 887 + 888 + 889 + 890 + 891 + 892 + 893 + 894 + 895 + 896 + 897 + 898 + 899 + 900 + 901 + 902 + 903 + 904 + 905 + 906 + #[derive(Component, Debug, Default, Deserialize, Serialize, PartialEq)] 907 + struct Foo(Vec<u64>); 908 + 909 + assert!( 910 + ecs.new_entity() 911 + .try_modify_component(|_foo: &mut Foo| { Err(anyhow!("error")) }) 912 + .is_err() 913 + ); 914 + 915 + assert!( 916 + ecs.new_entity() 917 + .attach(Foo(vec![1, 2, 3])) 918 + .try_modify_component(|_foo: &mut Foo| { Err(anyhow!("error")) }) 919 + .is_err() 920 + ); 921 + 922 + Ok(()) 923 + }
+33
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@And([WithComponent("ecsdb::Test"), EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Test\"), EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) and entity = :2 and (select true from components c2 where c2.entity = components.entity and c2.component = :3) and entity = :4)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Text( 23 + "ecsdb::Test", 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Integer( 29 + 42, 30 + ), 31 + ), 32 + ], 33 + }
+33
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@Or([WithComponent("ecsdb::Test"), EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Test\"), EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) or entity = :2 or (select true from components c2 where c2.entity = components.entity and c2.component = :3) or entity = :4)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Text( 23 + "ecsdb::Test", 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Integer( 29 + 42, 30 + ), 31 + ), 32 + ], 33 + }
+33
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@And([WithComponent("ecsdb::Test"), EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Test\"), EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "((select true from components c2 where c2.entity = components.entity and c2.component = :1) and entity = :2 and (select true from components c2 where c2.entity = components.entity and c2.component = :3) and entity = :4)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Text( 23 + "ecsdb::Test", 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Integer( 29 + 42, 30 + ), 31 + ), 32 + ], 33 + }
+33
src/query/snapshots/ecsdb__query__ir__test__filter_expression_where_clause@Or([WithComponent("ecsdb::Test"), EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).where_clause().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Test\"), EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).where_clause()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Where> { 6 + sql: "((select true from components c2 where c2.entity = components.entity and c2.component = :1) or entity = :2 or (select true from components c2 where c2.entity = components.entity and c2.component = :3) or entity = :4)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Text( 23 + "ecsdb::Test", 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Integer( 29 + 42, 30 + ), 31 + ), 32 + ], 33 + }
+14
src/query/snapshots/ecsdb__query__ir__test__simplify@And([WithComponent("ecsdb::Test"), EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Test\"), EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).simplify()" 4 + --- 5 + And( 6 + [ 7 + WithComponent( 8 + "ecsdb::Test", 9 + ), 10 + EntityId( 11 + 42, 12 + ), 13 + ], 14 + )
+14
src/query/snapshots/ecsdb__query__ir__test__simplify@Or([WithComponent("ecsdb::Test"), EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).simplify().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Test\"), EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).simplify()" 4 + --- 5 + Or( 6 + [ 7 + WithComponent( 8 + "ecsdb::Test", 9 + ), 10 + EntityId( 11 + 42, 12 + ), 13 + ], 14 + )
+39
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@And([And([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([And([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where (entity = :1 and (select true from components c2 where c2.entity = components.entity and c2.component = :2) and entity = :3 and (select true from components c2 where c2.entity = components.entity and c2.component = :4) and (select true from components c2 where c2.entity = components.entity and c2.component = :5) is null)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Test", 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Integer( 23 + 23, 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Text( 29 + "ecsdb::Foo", 30 + ), 31 + ), 32 + ( 33 + ":5", 34 + Text( 35 + "ecsdb::Bar", 36 + ), 37 + ), 38 + ], 39 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@And([WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")]).simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")]).simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) and (select true from components c2 where c2.entity = components.entity and c2.component = :2) is null)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Foo", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Bar", 18 + ), 19 + ), 20 + ], 21 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@And([WithComponent("ecsdb::Test"), EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Test\"), EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) and entity = :2)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ], 21 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@And([WithComponent("ecsdb::Test"), EntityId(42)]).simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "And([WithComponent(\"ecsdb::Test\"), EntityId(42)]).simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) and entity = :2)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ], 21 + }
+15
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@EntityId(42).simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: EntityId(42).simplify().sql_query() 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where entity = ?1", 7 + placeholders: [ 8 + ( 9 + "?1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ], 15 + }
+8
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@None.simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: None.simplify().sql_query() 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where true", 7 + placeholders: [], 8 + }
+39
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@Or([And([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([And([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((entity = :1 and (select true from components c2 where c2.entity = components.entity and c2.component = :2)) or (entity = :3 and (select true from components c2 where c2.entity = components.entity and c2.component = :4) and (select true from components c2 where c2.entity = components.entity and c2.component = :5) is null))", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Test", 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Integer( 23 + 23, 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Text( 29 + "ecsdb::Foo", 30 + ), 31 + ), 32 + ( 33 + ":5", 34 + Text( 35 + "ecsdb::Bar", 36 + ), 37 + ), 38 + ], 39 + }
+39
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@Or([Or([EntityId(42), WithComponent("ecsdb::Test")]), And([EntityId(23), WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")])]).simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([Or([EntityId(42), WithComponent(\"ecsdb::Test\")]), And([EntityId(23), WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")])]).simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where (entity = :1 or (select true from components c2 where c2.entity = components.entity and c2.component = :2) or (entity = :3 and (select true from components c2 where c2.entity = components.entity and c2.component = :4) and (select true from components c2 where c2.entity = components.entity and c2.component = :5) is null))", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Integer( 11 + 42, 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Test", 18 + ), 19 + ), 20 + ( 21 + ":3", 22 + Integer( 23 + 23, 24 + ), 25 + ), 26 + ( 27 + ":4", 28 + Text( 29 + "ecsdb::Foo", 30 + ), 31 + ), 32 + ( 33 + ":5", 34 + Text( 35 + "ecsdb::Bar", 36 + ), 37 + ), 38 + ], 39 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@Or([WithComponent("ecsdb::Foo"), WithoutComponent("ecsdb::Bar")]).simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Foo\"), WithoutComponent(\"ecsdb::Bar\")]).simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) or (select true from components c2 where c2.entity = components.entity and c2.component = :2) is null)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Foo", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Text( 17 + "ecsdb::Bar", 18 + ), 19 + ), 20 + ], 21 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@Or([WithComponent("ecsdb::Test"), EntityId(42), WithComponent("ecsdb::Test"), EntityId(42)]).simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Test\"), EntityId(42), WithComponent(\"ecsdb::Test\"), EntityId(42)]).simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) or entity = :2)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ], 21 + }
+21
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@Or([WithComponent("ecsdb::Test"), EntityId(42)]).simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "Or([WithComponent(\"ecsdb::Test\"), EntityId(42)]).simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where ((select true from components c2 where c2.entity = components.entity and c2.component = :1) or entity = :2)", 7 + placeholders: [ 8 + ( 9 + ":1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ( 15 + ":2", 16 + Integer( 17 + 42, 18 + ), 19 + ), 20 + ], 21 + }
+15
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@WithComponent("ecsdb::Test").simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "WithComponent(\"ecsdb::Test\").simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where (select true from components c2 where c2.entity = components.entity and c2.component = ?1)", 7 + placeholders: [ 8 + ( 9 + "?1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ], 15 + }
+15
src/query/snapshots/ecsdb__query__ir__test__filter_expression_sql_query@WithoutComponent("ecsdb::Test").simplify().sql_query().snap
··· 1 + --- 2 + source: src/query/ir.rs 3 + description: "WithoutComponent(\"ecsdb::Test\").simplify().sql_query()" 4 + --- 5 + SqlFragment<ecsdb::query::ir::Select> { 6 + sql: "select distinct entity from components where (select true from components c2 where c2.entity = components.entity and c2.component = ?1) is null", 7 + placeholders: [ 8 + ( 9 + "?1", 10 + Text( 11 + "ecsdb::Test", 12 + ), 13 + ), 14 + ], 15 + }
+15
.zed/settings.json
··· 1 + // Folder-specific settings 2 + // 3 + // For a full list of overridable settings, and general information on folder-specific settings, 4 + // see the documentation: https://zed.dev/docs/configuring-zed#settings-files 5 + { 6 + "lsp": { 7 + "rust-analyzer": { 8 + "initialization_options": { 9 + "check": { 10 + "command": "clippy" // rust-analyzer.checkOnSave.command 11 + } 12 + } 13 + } 14 + } 15 + }
+5 -7
src/component.rs
··· 1 1 use std::any::Any; 2 2 3 - use serde::{de::DeserializeOwned, Serialize}; 3 + use serde::{Serialize, de::DeserializeOwned}; 4 4 5 5 pub use ecsdb_derive::{Bundle, Component}; 6 6 ··· 39 39 fn to_rusqlite<'a>( 40 40 component: &'a Self, 41 41 ) -> Result<rusqlite::types::ToSqlOutput<'a>, StorageError> { 42 - S::to_rusqlite(&component) 42 + S::to_rusqlite(component) 43 43 } 44 44 } 45 45 ··· 56 56 fn from_rusqlite(value: &rusqlite::types::ToSqlOutput<'_>) -> Result<C, StorageError> { 57 57 let s = match value { 58 58 rusqlite::types::ToSqlOutput::Borrowed(rusqlite::types::ValueRef::Text(s)) => s, 59 - rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Text(ref s)) => { 60 - s.as_bytes() 61 - } 59 + rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Text(s)) => s.as_bytes(), 62 60 other => return Err(StorageError(format!("Unexpected type {other:?}"))), 63 61 }; 64 62 ··· 178 176 const NAME: &'static str = C::NAME; 179 177 180 178 fn to_rusqlite<'a>(&'a self) -> Result<Option<rusqlite::types::ToSqlOutput<'a>>, StorageError> { 181 - Ok(Some(C::to_rusqlite(&self)?)) 179 + Ok(Some(C::to_rusqlite(self)?)) 182 180 } 183 181 } 184 182 ··· 197 195 const COMPONENTS: &'static [&'static str] = &[C::NAME]; 198 196 199 197 fn to_rusqlite<'a>(&'a self) -> Result<BundleData<'a>, StorageError> { 200 - Ok(vec![(C::NAME, Some(C::to_rusqlite(&self)?))]) 198 + Ok(vec![(C::NAME, Some(C::to_rusqlite(self)?))]) 201 199 } 202 200 } 203 201
+6 -1
ecsdb_derive/Cargo.toml
··· 1 1 [package] 2 2 name = "ecsdb_derive" 3 3 version = "0.1.0" 4 - edition = "2021" 4 + edition = "2024" 5 5 6 6 [lib] 7 7 proc-macro = true 8 + 9 + [dependencies] 10 + syn = "2.0" 11 + quote = "1.0" 12 + proc-macro2 = "1.0.103"
+2 -2
src/system.rs
··· 1 1 use serde::{Deserialize, Serialize}; 2 2 use tracing::{debug, error, info, instrument}; 3 3 4 - use crate::{self as ecsdb, query, Component, Ecs, Entity}; 4 + use crate::{self as ecsdb, Component, Ecs, Entity, query}; 5 5 6 6 use core::marker::PhantomData; 7 7 use std::{ ··· 317 317 use std::marker::PhantomData; 318 318 319 319 use crate::query::With; 320 - use crate::{query, Ecs, Entity, IntoSystem, System, SystemEntity}; 320 + use crate::{Ecs, Entity, IntoSystem, System, SystemEntity, query}; 321 321 322 322 #[test] 323 323 fn run_system() {