1use rustc_hash::FxHashSet;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5use super::relationship::Relationship;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct EntityId(pub u64);
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct VoiceId(pub u64);
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
18pub enum Pronouns {
19 SheHer,
21 HeHim,
23 #[default]
25 TheyThem,
26 ItIts,
28}
29
30impl Pronouns {
31 pub fn subject(&self) -> &'static str {
33 match self {
34 Self::SheHer => "she",
35 Self::HeHim => "he",
36 Self::TheyThem => "they",
37 Self::ItIts => "it",
38 }
39 }
40
41 pub fn object(&self) -> &'static str {
43 match self {
44 Self::SheHer => "her",
45 Self::HeHim => "him",
46 Self::TheyThem => "them",
47 Self::ItIts => "it",
48 }
49 }
50
51 pub fn possessive(&self) -> &'static str {
53 match self {
54 Self::SheHer => "her",
55 Self::HeHim => "his",
56 Self::TheyThem => "their",
57 Self::ItIts => "its",
58 }
59 }
60
61 pub fn possessive_standalone(&self) -> &'static str {
63 match self {
64 Self::SheHer => "hers",
65 Self::HeHim => "his",
66 Self::TheyThem => "theirs",
67 Self::ItIts => "its",
68 }
69 }
70
71 pub fn reflexive(&self) -> &'static str {
73 match self {
74 Self::SheHer => "herself",
75 Self::HeHim => "himself",
76 Self::TheyThem => "themselves",
77 Self::ItIts => "itself",
78 }
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub enum Value {
85 String(String),
86 Float(f64),
87 Int(i64),
88 Bool(bool),
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct Entity {
98 pub id: EntityId,
99 pub name: String,
100 pub pronouns: Pronouns,
101 pub tags: FxHashSet<String>,
102 pub relationships: Vec<Relationship>,
103 pub voice_id: Option<VoiceId>,
104 pub properties: HashMap<String, Value>,
105}
106
107impl Entity {
108 pub fn has_tag(&self, tag: &str) -> bool {
110 self.tags.contains(tag)
111 }
112
113 pub fn has_all_tags(&self, tags: &[&str]) -> bool {
115 tags.iter().all(|tag| self.tags.contains(*tag))
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 fn make_entity(tags: &[&str]) -> Entity {
124 let mut tag_set = FxHashSet::default();
125 for t in tags {
126 tag_set.insert(t.to_string());
127 }
128 Entity {
129 id: EntityId(1),
130 name: "Margaret".to_string(),
131 pronouns: Pronouns::SheHer,
132 tags: tag_set,
133 relationships: Vec::new(),
134 voice_id: Some(VoiceId(10)),
135 properties: HashMap::from([
136 ("title".to_string(), Value::String("Duchess".to_string())),
137 ("age".to_string(), Value::Int(45)),
138 ("composure".to_string(), Value::Float(0.85)),
139 ("is_host".to_string(), Value::Bool(true)),
140 ]),
141 }
142 }
143
144 #[test]
145 fn entity_creation() {
146 let entity = make_entity(&["host", "anxious", "wealthy"]);
147 assert_eq!(entity.name, "Margaret");
148 assert_eq!(entity.id, EntityId(1));
149 assert_eq!(entity.voice_id, Some(VoiceId(10)));
150 }
151
152 #[test]
153 fn has_tag_positive() {
154 let entity = make_entity(&["host", "anxious", "wealthy"]);
155 assert!(entity.has_tag("host"));
156 assert!(entity.has_tag("anxious"));
157 assert!(entity.has_tag("wealthy"));
158 }
159
160 #[test]
161 fn has_tag_negative() {
162 let entity = make_entity(&["host", "anxious"]);
163 assert!(!entity.has_tag("calm"));
164 assert!(!entity.has_tag(""));
165 }
166
167 #[test]
168 fn has_all_tags_positive() {
169 let entity = make_entity(&["host", "anxious", "wealthy"]);
170 assert!(entity.has_all_tags(&["host", "anxious"]));
171 assert!(entity.has_all_tags(&["host", "anxious", "wealthy"]));
172 assert!(entity.has_all_tags(&[]));
173 }
174
175 #[test]
176 fn has_all_tags_negative() {
177 let entity = make_entity(&["host", "anxious"]);
178 assert!(!entity.has_all_tags(&["host", "calm"]));
179 assert!(!entity.has_all_tags(&["missing"]));
180 }
181
182 #[test]
183 fn entity_properties() {
184 let entity = make_entity(&[]);
185 assert!(matches!(entity.properties.get("title"), Some(Value::String(s)) if s == "Duchess"));
186 assert!(matches!(entity.properties.get("age"), Some(Value::Int(45))));
187 assert!(
188 matches!(entity.properties.get("composure"), Some(Value::Float(f)) if (*f - 0.85).abs() < f64::EPSILON)
189 );
190 assert!(matches!(
191 entity.properties.get("is_host"),
192 Some(Value::Bool(true))
193 ));
194 }
195}