reactive_graph_graphql_schema/mutation/instances/
entity_instance.rs

1use std::sync::Arc;
2
3use async_graphql::*;
4use log::debug;
5use serde_json::json;
6use uuid::Uuid;
7
8use reactive_graph_behaviour_model_api::BehaviourTypeId;
9use reactive_graph_behaviour_service_api::EntityBehaviourManager;
10use reactive_graph_behaviour_service_api::EntityComponentBehaviourManager;
11use reactive_graph_graph::EntityInstance;
12use reactive_graph_graph::PropertyInstanceSetter;
13use reactive_graph_graph::PropertyType;
14use reactive_graph_graph::PropertyTypeDefinition;
15use reactive_graph_reactive_model_api::ReactiveInstance;
16use reactive_graph_reactive_model_api::ReactivePropertyContainer;
17use reactive_graph_reactive_service_api::ReactiveEntityManager;
18use reactive_graph_reactive_service_api::ReactiveRelationManager;
19use reactive_graph_runtime_model::ActionProperties::TRIGGER;
20use reactive_graph_type_system_api::EntityTypeManager;
21
22use crate::mutation::BehaviourTypeIdDefinition;
23use crate::mutation::ComponentTypeIdDefinition;
24use crate::mutation::EntityTypeIdDefinition;
25use crate::query::GraphQLEntityInstance;
26use crate::query::GraphQLPropertyInstance;
27
28#[derive(Default)]
29pub struct MutationEntityInstances;
30
31/// Mutation of entity instances.
32#[Object]
33impl MutationEntityInstances {
34    /// Creates a new entity instance of the given type.
35    ///
36    /// The entity type must exist.
37    ///
38    /// Optionally, a UUID can be specified. If no UUID is specified one will be generated
39    /// randomly.
40    ///
41    /// The given properties consists of a list of pairs of property name and property value.
42    /// If properties are not provided, default values will be used depending on the data type
43    /// of the property.
44    async fn create(
45        &self,
46        context: &Context<'_>,
47        #[graphql(name = "type", desc = "The entity type")] entity_ty: EntityTypeIdDefinition,
48        #[graphql(desc = "The id of the entity instance. If none is given a random uuid will be generated.")] id: Option<Uuid>,
49        #[graphql(desc = "Description of the entity instance.")] description: Option<String>,
50        #[graphql(desc = "Creates the entity instance with the given components.")] components: Option<Vec<ComponentTypeIdDefinition>>,
51        properties: Option<Vec<GraphQLPropertyInstance>>,
52    ) -> Result<GraphQLEntityInstance> {
53        let reactive_entity_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
54        let entity_type_manager = context.data::<Arc<dyn EntityTypeManager + Send + Sync>>()?;
55
56        let entity_ty = entity_ty.into();
57        let entity_type = entity_type_manager
58            .get(&entity_ty)
59            .ok_or(Error::new(format!("Entity type {entity_ty} does not exist")))?;
60
61        let properties = GraphQLPropertyInstance::to_property_instances_with_defaults(properties, entity_type.properties);
62
63        let entity_instance = EntityInstance::builder()
64            .ty(&entity_ty)
65            .id(id.unwrap_or(Uuid::new_v4()))
66            .description(description.unwrap_or_default())
67            .properties(properties)
68            .build();
69        let entity_instance = reactive_entity_manager.create_reactive_instance(entity_instance);
70        match entity_instance {
71            Ok(entity_instance) => {
72                if let Some(components) = components {
73                    for component in components {
74                        // TODO: handle the case when one or multiple components wasn't added
75                        // How to handle this?
76                        let component_ty = component.into();
77                        let _ = reactive_entity_manager.add_component(entity_instance.id, &component_ty);
78                    }
79                }
80                Ok(entity_instance.into())
81            }
82            Err(e) => Err(e.into()),
83        }
84    }
85
86    /// Updates the properties of the entity instance with the given id.
87    #[allow(clippy::too_many_arguments)]
88    async fn update(
89        &self,
90        context: &Context<'_>,
91        #[graphql(desc = "Updates the entity instance with the given id.")] id: Option<Uuid>,
92        #[graphql(desc = "Updates the entity instance with the given label.")] label: Option<String>,
93        #[graphql(desc = "Adds the given components.")] add_components: Option<Vec<ComponentTypeIdDefinition>>,
94        #[graphql(desc = "Removes the given components.")] remove_components: Option<Vec<ComponentTypeIdDefinition>>,
95        #[graphql(desc = "Updates the given properties")] properties: Option<Vec<GraphQLPropertyInstance>>,
96        #[graphql(desc = "Adds the given properties")] add_properties: Option<Vec<crate::mutation::PropertyTypeDefinition>>,
97        #[graphql(desc = "Removes the given properties")] remove_properties: Option<Vec<String>>,
98    ) -> Result<GraphQLEntityInstance> {
99        let reactive_entity_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
100        let entity_instance;
101        if let Some(id) = id {
102            entity_instance = reactive_entity_manager.get(id);
103        } else if let Some(label) = label {
104            entity_instance = reactive_entity_manager.get_by_label(label.as_str());
105        } else {
106            return Err("Either id or label must be given!".into());
107        }
108        if entity_instance.is_none() {
109            return Err("Entity instance not found!".into());
110        }
111        let entity_instance = entity_instance.unwrap();
112
113        if let Some(components) = add_components {
114            for component in components {
115                // TODO: handle the case when one or multiple components wasn't added
116                let component = component.into();
117                let _ = reactive_entity_manager.add_component(entity_instance.id, &component);
118            }
119        }
120        if let Some(components) = remove_components {
121            for component in components {
122                let component = component.into();
123                reactive_entity_manager.remove_component(entity_instance.id, &component);
124            }
125        }
126        if let Some(properties) = properties {
127            // fill all values first without propagation
128            for property in properties.clone() {
129                debug!("set property {} = {}", property.name.clone(), property.value.clone());
130                // Set with respect to the mutability state
131                entity_instance.set_no_propagate_checked(property.name.clone(), property.value.clone());
132            }
133            // tick every property that has been changed before, this is still not transactional
134            for property in properties {
135                debug!("tick property {} = {}", property.name.clone(), property.value.clone());
136                if let Some(property_instance) = entity_instance.properties.get(property.name.as_str()) {
137                    // Tick with respect to the mutability state
138                    property_instance.tick_checked();
139                }
140            }
141        }
142        if let Some(add_properties) = add_properties {
143            for property_type in add_properties.clone() {
144                let property_type: PropertyType = property_type.into();
145                debug!("add property {} ({})", &property_type.name, &property_type.data_type);
146                entity_instance.add_property_by_type(&property_type);
147            }
148        }
149        if let Some(remove_properties) = remove_properties {
150            for property_name in remove_properties.clone() {
151                debug!("remove property {}", &property_name);
152                entity_instance.remove_property(property_name);
153            }
154        }
155        // TODO: it's still not a transactional mutation
156        // entity_instance.tick();
157        Ok(entity_instance.into())
158    }
159
160    /// Triggers the entity instance with the given id.
161    async fn trigger(
162        &self,
163        context: &Context<'_>,
164        #[graphql(desc = "Triggers the entity instance with the given id.")] id: Option<Uuid>,
165        #[graphql(desc = "Triggers the entity instance with the given label.")] label: Option<String>,
166    ) -> Result<GraphQLEntityInstance> {
167        let reactive_entity_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
168        let Some(entity_instance) = (if let Some(id) = id {
169            reactive_entity_manager.get(id)
170        } else if let Some(label) = label {
171            reactive_entity_manager.get_by_label(label.as_str())
172        } else {
173            return Err("Either id or label must be given!".into());
174        }) else {
175            return Err("Entity instance not found!".into());
176        };
177        if entity_instance.has_property(&TRIGGER.property_name()) {
178            entity_instance.set_checked(TRIGGER.property_name(), json!(true));
179            Ok(entity_instance.into())
180        } else {
181            Err(Error::new(format!("Unable to trigger {}", entity_instance.id)))
182        }
183    }
184
185    /// Manually tick the entity instance. This means for each property of the entity instance
186    /// the corresponding reactive stream will be activated with it's last value.
187    ///
188    /// This leads to a recalculation if the entity instance is controlled by an behaviour which
189    /// consumes the reactive streams.
190    ///
191    /// Furthermore this leads to a new value propagation if the output property is connected
192    /// to other properties.
193    async fn tick(&self, context: &Context<'_>, id: Uuid) -> Result<GraphQLEntityInstance> {
194        let reactive_entity_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
195        let entity_instance = reactive_entity_manager.get(id);
196        if entity_instance.is_none() {
197            return Err(Error::new(format!("Entity instance {id} does not exist!")));
198        }
199        let entity_instance = entity_instance.unwrap();
200        entity_instance.tick();
201        Ok(entity_instance.into())
202    }
203
204    /// Deletes an entity instance.
205    async fn delete(
206        &self,
207        context: &Context<'_>,
208        #[graphql(desc = "The id of the entity instance")] id: Uuid,
209        #[graphql(desc = "If true, all relations to and from the entity instance will be deleted as well")] delete_relations: Option<bool>,
210    ) -> Result<bool> {
211        let reactive_entity_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
212        if delete_relations.is_some() && delete_relations.unwrap() {
213            let relation_instance_manager = context.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
214            relation_instance_manager.get_by_inbound_entity(id).iter().for_each(|reactive_relation| {
215                let id = reactive_relation.id();
216                relation_instance_manager.delete(&id);
217            });
218            relation_instance_manager.get_by_outbound_entity(id).iter().for_each(|reactive_relation| {
219                let id = reactive_relation.id();
220                relation_instance_manager.delete(&id);
221            });
222        }
223        Ok(reactive_entity_manager.delete(id))
224    }
225
226    async fn connect(
227        &self,
228        context: &Context<'_>,
229        id: Uuid,
230        #[graphql(name = "type")] behaviour_ty: BehaviourTypeIdDefinition,
231    ) -> Result<GraphQLEntityInstance> {
232        let reactive_entity_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
233        let entity_behaviour_manager = context.data::<Arc<dyn EntityBehaviourManager + Send + Sync>>()?;
234        let entity_component_behaviour_manager = context.data::<Arc<dyn EntityComponentBehaviourManager + Send + Sync>>()?;
235        let reactive_instance = reactive_entity_manager.get(id).ok_or(Error::new("Entity instance not found"))?;
236        let behaviour_ty = BehaviourTypeId::from(behaviour_ty);
237        if entity_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
238            entity_behaviour_manager
239                .connect(reactive_instance.clone(), &behaviour_ty)
240                .map_err(|e| Error::new(format!("Failed to connect entity behaviour {e:?}")))?;
241        }
242        if entity_component_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
243            entity_component_behaviour_manager
244                .connect(reactive_instance.clone(), &behaviour_ty)
245                .map_err(|e| Error::new(format!("Failed to connect entity component behaviour {e:?}")))?;
246        }
247        Ok(reactive_instance.into())
248    }
249
250    async fn disconnect(
251        &self,
252        context: &Context<'_>,
253        id: Uuid,
254        #[graphql(name = "type")] behaviour_ty: BehaviourTypeIdDefinition,
255    ) -> Result<GraphQLEntityInstance> {
256        let reactive_entity_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
257        let entity_behaviour_manager = context.data::<Arc<dyn EntityBehaviourManager + Send + Sync>>()?;
258        let entity_component_behaviour_manager = context.data::<Arc<dyn EntityComponentBehaviourManager + Send + Sync>>()?;
259        let reactive_instance = reactive_entity_manager.get(id).ok_or(Error::new("Entity instance not found"))?;
260        let behaviour_ty = BehaviourTypeId::from(behaviour_ty);
261        if entity_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
262            entity_behaviour_manager
263                .disconnect(reactive_instance.clone(), &behaviour_ty)
264                .map_err(|e| Error::new(format!("Failed to disconnect entity behaviour {e:?}")))?;
265        }
266        if entity_component_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
267            entity_component_behaviour_manager
268                .disconnect(reactive_instance.clone(), &behaviour_ty)
269                .map_err(|e| Error::new(format!("Failed to connect entity component behaviour {e:?}")))?;
270        }
271        Ok(reactive_instance.into())
272    }
273
274    async fn reconnect(
275        &self,
276        context: &Context<'_>,
277        id: Uuid,
278        #[graphql(name = "type")] behaviour_ty: BehaviourTypeIdDefinition,
279    ) -> Result<GraphQLEntityInstance> {
280        let reactive_entity_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
281        let entity_behaviour_manager = context.data::<Arc<dyn EntityBehaviourManager + Send + Sync>>()?;
282        let entity_component_behaviour_manager = context.data::<Arc<dyn EntityComponentBehaviourManager + Send + Sync>>()?;
283        let reactive_instance = reactive_entity_manager.get(id).ok_or(Error::new("Entity instance not found"))?;
284        let behaviour_ty = BehaviourTypeId::from(behaviour_ty);
285        if entity_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
286            entity_behaviour_manager
287                .reconnect(reactive_instance.clone(), &behaviour_ty)
288                .map_err(|e| Error::new(format!("Failed to reconnect entity behaviour {e:?}")))?;
289        }
290        if entity_component_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
291            entity_component_behaviour_manager
292                .reconnect(reactive_instance.clone(), &behaviour_ty)
293                .map_err(|e| Error::new(format!("Failed to connect entity component behaviour {e:?}")))?;
294        }
295        Ok(reactive_instance.into())
296    }
297}