Experiments in applying Entity-Component-System patterns to durable data storage APIs.
1use std::ops::{Deref, DerefMut}; 2 3pub use ecsdb_derive::Resource; 4 5use rusqlite::params; 6use tracing::debug; 7 8use crate::{Component, Ecs, Error}; 9 10pub trait Resource: Component { 11 fn resource_name() -> &'static str { 12 <Self as Component>::component_name() 13 } 14} 15 16impl Ecs { 17 pub fn resource<R: Resource>(&self) -> Option<R> { 18 self.try_resource::<R>().unwrap() 19 } 20 21 pub fn try_resource<R: Resource>(&self) -> Result<Option<R>, Error> { 22 let name = R::resource_name(); 23 let mut query = self 24 .conn 25 .prepare("select data from resources where name = ?1")?; 26 let row = query 27 .query_and_then(params![name], |row| { 28 row.get::<_, rusqlite::types::Value>("data") 29 })? 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 } 40 } 41 42 pub fn resource_mut<'a, R: Resource + Default>(&'a mut self) -> impl DerefMut<Target = R> + 'a { 43 self.try_resource_mut().unwrap() 44 } 45 46 pub fn try_resource_mut<'a, R: Resource + Default>( 47 &'a mut self, 48 ) -> Result<impl DerefMut<Target = R> + 'a, Error> { 49 let resource = self.try_resource()?.unwrap_or_default(); 50 Ok(ResourceProxy(self, resource)) 51 } 52 53 pub fn attach_resource<R: Resource>(&self, resource: R) { 54 self.try_attach_resource(resource).unwrap() 55 } 56 57 pub fn try_attach_resource<R: Resource>(&self, resource: R) -> Result<(), Error> { 58 let name = R::component_name(); 59 let data = R::to_rusqlite(resource)?; 60 61 self.conn.execute( 62 "insert or replace into resources (name, data) values (?1, ?2)", 63 params![name, data], 64 )?; 65 66 debug!(resource = name, "inserted"); 67 68 Ok(()) 69 } 70 71 pub fn detach_resource<R: Resource>(&self) { 72 self.try_detach_resource::<R>().unwrap() 73 } 74 75 pub fn try_detach_resource<R: Resource>(&self) -> Result<(), Error> { 76 let name = R::component_name(); 77 78 self.conn 79 .execute("delete from resources where name = ?1", params![name])?; 80 81 debug!(resource = name, "deleted"); 82 83 Ok(()) 84 } 85} 86 87pub struct ResourceProxy<'a, R: Resource + Default>(&'a mut Ecs, R); 88 89impl<'a, R: Resource + Default> AsMut<R> for ResourceProxy<'a, R> { 90 fn as_mut(&mut self) -> &mut R { 91 &mut self.1 92 } 93} 94 95impl<'a, R: Resource + Default> Deref for ResourceProxy<'a, R> { 96 type Target = R; 97 98 fn deref(&self) -> &Self::Target { 99 &self.1 100 } 101} 102 103impl<'a, R: Resource + Default> DerefMut for ResourceProxy<'a, R> { 104 fn deref_mut(&mut self) -> &mut Self::Target { 105 &mut self.1 106 } 107} 108 109impl<'a, R: Resource + Default> Drop for ResourceProxy<'a, R> { 110 fn drop(&mut self) { 111 let resource = std::mem::take(&mut self.1); 112 self.0.attach_resource(resource); 113 } 114} 115 116#[cfg(test)] 117mod tests { 118 use serde::{Deserialize, Serialize}; 119 120 use crate::{self as ecsdb}; 121 use crate::{Ecs, Resource}; // #[derive(Component)] derives `impl ecsdb::Component for ...` 122 123 #[derive(Debug, Serialize, Deserialize, Resource, PartialEq, Default)] 124 struct TestResource(pub i32); 125 126 #[test] 127 fn ecs_resource() { 128 let mut ecs = Ecs::open_in_memory().unwrap(); 129 130 assert!(ecs.resource::<TestResource>().is_none()); 131 132 ecs.attach_resource(TestResource(42)); 133 assert_eq!(ecs.resource::<TestResource>().unwrap(), TestResource(42)); 134 135 ecs.attach_resource(TestResource(23)); 136 assert_eq!(ecs.resource::<TestResource>().unwrap(), TestResource(23)); 137 138 ecs.detach_resource::<TestResource>(); 139 assert!(ecs.resource::<TestResource>().is_none()); 140 141 ecs.resource_mut::<TestResource>().0 = 42; 142 assert_eq!(ecs.resource::<TestResource>().unwrap(), TestResource(42)); 143 144 { 145 let mut proxy = ecs.resource_mut::<TestResource>(); 146 *proxy = TestResource(1234); 147 } 148 149 assert_eq!(ecs.resource::<TestResource>().unwrap(), TestResource(1234)); 150 } 151}