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}