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}