Experiments in applying Entity-Component-System patterns to durable data storage APIs.
1use std::iter; 2 3use rusqlite::params; 4use tracing::debug; 5 6use crate::{ 7 component::Bundle, 8 query::{self, Filter}, 9 BelongsTo, Component, Ecs, EntityId, Error, 10}; 11 12#[derive(Debug, Copy, Clone)] 13pub struct WithoutEntityId; 14#[derive(Debug, Copy, Clone)] 15pub struct WithEntityId(EntityId); 16 17pub type Entity<'a> = GenericEntity<'a, WithEntityId>; 18pub type NewEntity<'a> = GenericEntity<'a, WithoutEntityId>; 19 20#[derive(Copy, Clone)] 21pub struct GenericEntity<'a, S>(&'a Ecs, S); 22 23impl<'a, T> GenericEntity<'a, T> { 24 pub(crate) fn without_id(ecs: &'a Ecs) -> NewEntity<'a> { 25 GenericEntity(ecs, WithoutEntityId) 26 } 27 28 pub(crate) fn with_id(ecs: &'a Ecs, eid: EntityId) -> Entity<'a> { 29 GenericEntity(ecs, WithEntityId(eid)) 30 } 31} 32 33impl<'a> Entity<'a> { 34 pub fn id(&self) -> EntityId { 35 (self.1).0 36 } 37 38 pub fn db(&'a self) -> &'a Ecs { 39 self.0 40 } 41 42 pub fn last_modified(&self) -> chrono::DateTime<chrono::Utc> { 43 self.try_last_modified().expect("Non-Error") 44 } 45 46 #[tracing::instrument(name = "last_modified", level = "debug")] 47 pub fn try_last_modified(&self) -> Result<chrono::DateTime<chrono::Utc>, Error> { 48 self.0.try_last_modified(self.id()).map_err(Error::from) 49 } 50 51 pub fn component_names(&self) -> impl Iterator<Item = String> { 52 self.try_component_names().unwrap() 53 } 54 55 #[tracing::instrument(name = "component_names", level = "debug")] 56 pub fn try_component_names(&self) -> Result<impl Iterator<Item = String>, Error> { 57 let mut stmt = self 58 .0 59 .conn 60 .prepare("select component from components where entity = ?1")?; 61 let names = stmt 62 .query_map(params![self.id()], |row| row.get(0))? 63 .collect::<Result<Vec<_>, _>>()?; 64 Ok(names.into_iter()) 65 } 66 67 pub fn has<B: Bundle>(&self) -> bool { 68 self.try_has::<B>().unwrap() 69 } 70 71 pub fn try_has<B: Bundle>(&self) -> Result<bool, Error> { 72 self.has_all_dynamic(B::COMPONENTS) 73 } 74 75 fn has_all_dynamic(&self, component_names: &[&str]) -> Result<bool, Error> { 76 let mut stmt = self 77 .0 78 .conn 79 .prepare("select true from components where entity = ?1 and component = ?2")?; 80 for name in component_names { 81 if !stmt.exists(params![self.id(), name])? { 82 return Ok(false); 83 } 84 } 85 86 Ok(true) 87 } 88} 89 90impl<'a> Entity<'a> { 91 pub fn destroy(self) { 92 self.try_destroy().unwrap(); 93 } 94 95 #[tracing::instrument(name = "destroy", level = "debug")] 96 pub fn try_destroy(self) -> Result<(), Error> { 97 self.0 98 .conn 99 .execute("delete from components where entity = ?1", [self.id()])?; 100 debug!(entity = self.id(), "destroyed"); 101 Ok(()) 102 } 103} 104 105impl<'a> Entity<'a> { 106 pub fn component<T: Component>(&self) -> Option<T> { 107 self.try_component::<T>().unwrap() 108 } 109 110 pub fn try_component<T: Component>(&self) -> Result<Option<T>, Error> { 111 let name = T::component_name(); 112 let mut query = self 113 .0 114 .conn 115 .prepare("select data from components where entity = ?1 and component = ?2")?; 116 let row = query 117 .query_and_then(params![self.id(), name], |row| { 118 row.get::<_, rusqlite::types::Value>("data") 119 })? 120 .next(); 121 122 match row { 123 None => Ok(None), 124 Some(Ok(data)) => { 125 let component = T::from_rusqlite(data)?; 126 Ok(Some(component)) 127 } 128 _other => panic!(), 129 } 130 } 131} 132 133impl<'a> Entity<'a> { 134 pub fn modify_component<C: Component + Default>(&self, f: impl FnOnce(&mut C)) -> Self { 135 self.try_modify_component(|c| { 136 f(c); 137 Ok(()) 138 }) 139 .unwrap() 140 } 141 142 // TODO: Race Condition; needs refactoring to make Entity generic over 143 // `rusqlite::Connection` and `rusqlite::Transaction` 144 pub fn try_modify_component<C: Component + Default>( 145 &self, 146 f: impl FnOnce(&mut C) -> Result<(), anyhow::Error>, 147 ) -> Result<Self, ModifyComponentError> { 148 let mut component = self.try_component()?.unwrap_or_default(); 149 f(&mut component).map_err(ModifyComponentError::Fn)?; 150 Ok(self.try_attach(component)?) 151 } 152} 153 154#[derive(thiserror::Error, Debug)] 155pub enum ModifyComponentError { 156 #[error(transparent)] 157 Ecs(#[from] Error), 158 #[error("Error in modify-fun: {0}")] 159 Fn(anyhow::Error), 160} 161 162impl<'a> Entity<'a> { 163 pub fn try_matches<F: Filter>(&self) -> Result<bool, Error> { 164 let q = query::Query::<F, _>::new(self.db(), self.id()); 165 Ok(q.try_into_iter()?.next().is_some()) 166 } 167 168 pub fn matches<F: Filter>(&self) -> bool { 169 self.try_matches::<F>().unwrap() 170 } 171} 172 173impl<'a> Entity<'a> { 174 pub fn attach<B: Bundle>(self, component: B) -> Self { 175 self.try_attach::<B>(component).unwrap() 176 } 177 178 pub fn detach<B: Bundle>(self) -> Self { 179 self.try_detach::<B>().unwrap() 180 } 181 182 #[tracing::instrument(name = "attach", level = "debug", skip_all)] 183 pub fn try_attach<B: Bundle>(self, component: B) -> Result<Self, Error> { 184 let components = B::to_rusqlite(component)?; 185 186 let mut stmt = self.0.conn.prepare( 187 r#" 188 insert into components (entity, component, data) 189 values (?1, ?2, ?3) 190 on conflict (entity, component) do update 191 set data = excluded.data where data is not excluded.data; 192 "#, 193 )?; 194 195 for (component, data) in components { 196 let attached_rows = stmt.execute(params![self.id(), component, data])?; 197 if attached_rows > 0 { 198 debug!(entity = self.id(), component, "attached"); 199 } else { 200 debug!(entity = self.id(), component, "no-op") 201 } 202 } 203 204 Ok(self) 205 } 206 207 #[tracing::instrument(name = "detach", level = "debug")] 208 pub fn try_detach<B: Bundle>(self) -> Result<Self, Error> { 209 let mut stmt = self 210 .0 211 .conn 212 .prepare("delete from components where entity = ?1 and component = ?2")?; 213 214 for component in B::COMPONENTS { 215 let deleted_rows = stmt.execute(params![self.id(), component])?; 216 if deleted_rows > 0 { 217 debug!(entity = self.id(), component, "detached"); 218 } else { 219 debug!(entity = self.id(), component, "no-op") 220 } 221 } 222 223 Ok(self) 224 } 225} 226 227impl<'a> NewEntity<'a> { 228 pub fn or_none(self) -> Option<Self> { 229 None 230 } 231} 232 233impl<'a> Entity<'a> { 234 pub fn or_none(self) -> Option<Self> { 235 Some(self) 236 } 237} 238 239impl<'a> NewEntity<'a> { 240 pub fn attach<B: Bundle>(self, component: B) -> GenericEntity<'a, WithEntityId> { 241 self.try_attach::<B>(component).unwrap() 242 } 243 244 pub fn detach<B: Bundle>(&mut self) -> &mut Self { 245 self 246 } 247 248 pub fn component_names(&self) -> impl Iterator<Item = String> { 249 std::iter::empty() 250 } 251 252 #[tracing::instrument(name = "attach", level = "debug", skip_all)] 253 pub fn try_attach<B: Bundle>( 254 self, 255 bundle: B, 256 ) -> Result<GenericEntity<'a, WithEntityId>, Error> { 257 let data = B::to_rusqlite(bundle)?; 258 assert!(!data.is_empty()); 259 260 let mut stmt = self.0.conn.prepare( 261 r#" 262 insert or replace into components (entity, component, data) 263 values ((select coalesce(?1, max(entity)+1, 100) from components), ?2, ?3) 264 returning entity 265 "#, 266 )?; 267 268 let mut eid = None; 269 for (component, data) in data { 270 eid = Some(stmt.query_row(params![eid, component, data], |row| { 271 row.get::<_, EntityId>("entity") 272 })?); 273 274 debug!(entity = eid.unwrap(), component, "attached"); 275 } 276 277 let Some(eid) = eid else { 278 panic!("Bundle::to_rusqlite returned zero rows. That shouldn't happen.") 279 }; 280 281 let entity = GenericEntity(self.0, WithEntityId(eid)); 282 283 Ok(entity) 284 } 285 286 #[tracing::instrument(name = "detach", level = "debug", skip_all)] 287 pub fn try_detach<B: Bundle>(&mut self) -> Result<&mut Self, Error> { 288 Ok(self) 289 } 290 291 #[tracing::instrument(name = "component_names", level = "debug")] 292 pub fn try_component_names(&self) -> Result<impl Iterator<Item = String>, Error> { 293 Ok(std::iter::empty()) 294 } 295} 296 297impl<'a> Entity<'a> { 298 pub fn direct_children(&'a self) -> impl Iterator<Item = Entity<'a>> { 299 self.db().direct_children(self.id()) 300 } 301 302 pub fn all_children(&'a self) -> impl Iterator<Item = Entity<'a>> + 'a { 303 self.db().all_children(self.id()) 304 } 305 306 pub fn parent(&'a self) -> Option<Entity<'a>> { 307 self.component::<BelongsTo>() 308 .map(|BelongsTo(parent)| self.db().entity(parent)) 309 } 310 311 pub fn parents(&'a self) -> impl Iterator<Item = Entity<'a>> + 'a { 312 let parent = self 313 .component::<BelongsTo>() 314 .map(|BelongsTo(parent)| self.db().entity(parent)); 315 316 iter::successors(parent, |x| { 317 // For some reasons the lifetimes don't work out when we just call 318 // `x.parent()` here 319 x.component::<BelongsTo>() 320 .map(|BelongsTo(parent)| self.db().entity(parent)) 321 }) 322 } 323 324 #[tracing::instrument(level = "debug")] 325 pub fn destroy_recursive(&'a self) { 326 for entity in iter::once(*self).chain(self.all_children()) { 327 entity.destroy() 328 } 329 } 330} 331 332impl<'a> NewEntity<'a> { 333 pub fn direct_children(&'a self) -> impl Iterator<Item = Entity<'a>> + 'a { 334 iter::empty() 335 } 336 337 pub fn all_children(&'a self) -> impl Iterator<Item = Entity<'a>> + 'a { 338 iter::empty() 339 } 340 341 pub fn parent(&'a self) -> Option<Entity<'a>> { 342 None 343 } 344 345 pub fn parents(&'a self) -> impl Iterator<Item = Entity<'a>> + 'a { 346 iter::empty() 347 } 348} 349 350impl<'a> NewEntity<'a> { 351 pub fn modify_component<C: Component + Default>(&self, f: impl FnOnce(&mut C)) -> Entity<'a> { 352 self.try_modify_component(|c| { 353 f(c); 354 Ok(()) 355 }) 356 .unwrap() 357 } 358 359 // TODO: Race Condition; needs refactoring to make Entity generic over 360 // `rusqlite::Connection` and `rusqlite::Transaction` 361 pub fn try_modify_component<C: Component + Default>( 362 &self, 363 f: impl FnOnce(&mut C) -> Result<(), anyhow::Error>, 364 ) -> Result<Entity<'a>, ModifyComponentError> { 365 let mut component = C::default(); 366 f(&mut component).map_err(ModifyComponentError::Fn)?; 367 Ok(self.try_attach(component)?) 368 } 369} 370 371impl<'a> std::fmt::Display for NewEntity<'a> { 372 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 373 f.debug_tuple("Entity").field(&"nil").finish() 374 } 375} 376 377impl<'a> std::fmt::Display for Entity<'a> { 378 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 379 f.debug_tuple("Entity").field(&(self.1).0).finish() 380 } 381} 382 383impl<'a> std::fmt::Debug for NewEntity<'a> { 384 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 385 f.debug_tuple("Entity").field(&"nil").finish() 386 } 387} 388 389impl<'a> std::fmt::Debug for Entity<'a> { 390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 391 f.debug_tuple("Entity").field(&(self.1).0).finish() 392 } 393}