The vocabulary of the character chooser, as a rete domain: what can be known, what a rule may do, and the
rules. The chooser is the main screen in an edit mode; this says what each thing she does to it means.
Facts are what is true at the moment of one input. Some the shell's state supplies (what she did, whether
the chooser is open, whether anything is unsaved, whether there are voices to step through); the rest are
taught by an effect once it is done, so a rule can follow the one before it (a character moved, then say
hello). The state itself (which character, which voice) is in [crate::ChooserDomain], because it is too wide
for a fact to hold; the rules decide only what to do.
10use rete::{Domain, Rule, Test, Then};
What she did to the chooser.
She tapped the character in the side panel.
16 Open,
The arrow left of the character.
18 Left,
The arrow right of the character.
20 Right,
The arrow left of the voice's name.
22 VoiceLeft,
The arrow right of the voice's name.
24 VoiceRight,
The check: save.
26 Check,
31impl Input { 32 pub const ALL: [Input; 7] = [Input::Open, Input::Left, Input::Right, Input::VoiceLeft, Input::VoiceRight, Input::Check, Input::Cancel]; 33 34 pub(crate) fn value(self) -> Value { 35 match self { 36 Input::Open => Value::Open, 37 Input::Left => Value::Left, 38 Input::Right => Value::Right, 39 Input::VoiceLeft => Value::VoiceLeft, 40 Input::VoiceRight => Value::VoiceRight, 41 Input::Check => Value::Check, 42 Input::Cancel => Value::Cancel, 43 } 44 } 45}
The marker type of the domain.
What she did, the one thing that starts a step.
54 Input,
The chooser is open (the main screen is in its edit mode).
56 Editing,
What is shown differs from what is saved: another character, or another voice for it.
58 Unsaved,
The service listed voices, so the arrows under the name have somewhere to go.
Which way an arrow steps. Both ways wrap around.
What a rule makes happen. Each teaches a fact once it is done.
Open the chooser on the saved character and its saved voice.
94 Enter,
Show the next or previous character, with the voice saved for it (not the one previewed).
96 StepCharacter(Step),
Show the next or previous voice.
98 StepVoice(Step),
Say "Hi <name>" in the shown character's saved voice.
100 SayHi,
Say "Hi <name>, this voice is called <voice>" in that voice.
102 SayVoice,
Keep the shown character and its voice, and nothing else.
104 Save,
Put the shown character and voice back to what is saved.
106 Discard,
A rule never ends the query outright here: there is nothing to refuse.
Why an input did nothing, so the trace says so rather than staying silent.
The input only means something while the chooser is open.
119 NotOpen,
The chooser is open already.
121 AlreadyOpen,
126impl Domain for ChooserDomain { 127 type Fact = Fact; 128 type Value = Value; 129 type Effect = Effect; 130 type End = End; 131 type Note = Note; 132 133 fn facts() -> &'static [Fact] { 134 &[ 135 Fact::Input, 136 Fact::Editing, 137 Fact::Unsaved, 138 Fact::Voices, 139 Fact::Entered, 140 Fact::CharacterMoved, 141 Fact::VoiceMoved, 142 Fact::Said, 143 Fact::Saved, 144 Fact::Discarded, 145 Fact::Closed, 146 ] 147 } 148 149 fn fact_name(fact: Fact) -> &'static str { 150 match fact { 151 Fact::Input => "what she did", 152 Fact::Editing => "chooser open", 153 Fact::Unsaved => "unsaved change", 154 Fact::Voices => "voices listed", 155 Fact::Entered => "opened", 156 Fact::CharacterMoved => "character moved", 157 Fact::VoiceMoved => "voice moved", 158 Fact::Said => "said", 159 Fact::Saved => "saved", 160 Fact::Discarded => "discarded", 161 Fact::Closed => "closed", 162 } 163 } 164 165 fn values(fact: Fact) -> &'static [Value] { 166 match fact { 167 Fact::Input => &[Value::Open, Value::Left, Value::Right, Value::VoiceLeft, Value::VoiceRight, Value::Check, Value::Cancel], 168 Fact::Editing | Fact::Unsaved | Fact::Voices => &[Value::Yes, Value::No], 169 _ => &[Value::Yes], 170 } 171 } 172 173 fn value_name(value: Value) -> &'static str { 174 match value { 175 Value::Yes => "yes", 176 Value::No => "no", 177 Value::Open => "open", 178 Value::Left => "left", 179 Value::Right => "right", 180 Value::VoiceLeft => "voice left", 181 Value::VoiceRight => "voice right", 182 Value::Check => "check", 183 Value::Cancel => "cancel", 184 } 185 } 186 187 fn asked_for(fact: Fact) -> bool { 188 matches!(fact, Fact::Input | Fact::Editing | Fact::Unsaved | Fact::Voices) 189 } 190 191 fn teaches(effect: Effect) -> Fact { 192 match effect { 193 Effect::Enter => Fact::Entered, 194 Effect::StepCharacter(_) => Fact::CharacterMoved, 195 Effect::StepVoice(_) => Fact::VoiceMoved, 196 Effect::SayHi | Effect::SayVoice => Fact::Said, 197 Effect::Save => Fact::Saved, 198 Effect::Discard => Fact::Discarded, 199 Effect::Leave => Fact::Closed, 200 } 201 } 202 203 fn effect_name(effect: Effect) -> String { 204 match effect { 205 Effect::Enter => "open", 206 Effect::StepCharacter(Step::Next) => "next character", 207 Effect::StepCharacter(Step::Previous) => "previous character", 208 Effect::StepVoice(Step::Next) => "next voice", 209 Effect::StepVoice(Step::Previous) => "previous voice", 210 Effect::SayHi => "say hi", 211 Effect::SayVoice => "say the voice's name", 212 Effect::Save => "save", 213 Effect::Discard => "discard", 214 Effect::Leave => "close", 215 } 216 .to_owned() 217 } 218 219 fn end_name(end: End) -> String { 220 match end {} 221 } 222 223 fn note_name(note: Note) -> String { 224 match note { 225 Note::NotOpen => "not open", 226 Note::AlreadyOpen => "already open", 227 Note::NoVoices => "no voices", 228 } 229 .to_owned() 230 } 231} 232 233use Fact::{Discarded, CharacterMoved, Editing, Input as What, Saved, Unsaved, VoiceMoved, Voices}; 234use Value::{No, Yes}; 235 236const fn when(fact: Fact, value: Value) -> Test<ChooserDomain> { 237 Test::Is(fact, value) 238} 239 240const fn then_do(effect: Effect) -> Then<ChooserDomain> { 241 Then::Do(effect) 242}
The rules, in priority order. Every transition of the chooser is here and nowhere else.
245pub const RULES: &[Rule<'static, ChooserDomain>] = &[ 246 Rule { name: "open", when: &[when(What, Value::Open), when(Editing, No)], then: then_do(Effect::Enter) }, 247 Rule { name: "next character", when: &[when(What, Value::Right), when(Editing, Yes)], then: then_do(Effect::StepCharacter(Step::Next)) }, 248 Rule { name: "previous character", when: &[when(What, Value::Left), when(Editing, Yes)], then: then_do(Effect::StepCharacter(Step::Previous)) }, 249 Rule { 250 name: "next voice", 251 when: &[when(What, Value::VoiceRight), when(Editing, Yes), when(Voices, Yes)], 252 then: then_do(Effect::StepVoice(Step::Next)), 253 }, 254 Rule { 255 name: "previous voice", 256 when: &[when(What, Value::VoiceLeft), when(Editing, Yes), when(Voices, Yes)], 257 then: then_do(Effect::StepVoice(Step::Previous)), 258 }, 259 Rule { name: "a character says hi", when: &[Test::Known(CharacterMoved)], then: then_do(Effect::SayHi) }, 260 Rule { name: "a voice says its name", when: &[Test::Known(VoiceMoved)], then: then_do(Effect::SayVoice) }, 261 Rule { name: "check saves", when: &[when(What, Value::Check), when(Editing, Yes), when(Unsaved, Yes)], then: then_do(Effect::Save) }, 262 Rule { name: "saved, close", when: &[Test::Known(Saved)], then: then_do(Effect::Leave) }, 263 Rule { name: "check with no change closes", when: &[when(What, Value::Check), when(Editing, Yes), when(Unsaved, No)], then: then_do(Effect::Leave) }, 264 Rule { name: "cross throws away", when: &[when(What, Value::Cancel), when(Editing, Yes)], then: then_do(Effect::Discard) }, 265 Rule { name: "discarded, close", when: &[Test::Known(Discarded)], then: then_do(Effect::Leave) }, 266 Rule { name: "no voices to step: right", when: &[when(What, Value::VoiceRight), when(Editing, Yes), when(Voices, No)], then: Then::Note(Note::NoVoices) }, 267 Rule { name: "no voices to step: left", when: &[when(What, Value::VoiceLeft), when(Editing, Yes), when(Voices, No)], then: Then::Note(Note::NoVoices) }, 268 Rule { name: "open already", when: &[when(What, Value::Open), when(Editing, Yes)], then: Then::Note(Note::AlreadyOpen) }, 269 Rule { name: "closed: right", when: &[when(What, Value::Right), when(Editing, No)], then: Then::Note(Note::NotOpen) }, 270 Rule { name: "closed: left", when: &[when(What, Value::Left), when(Editing, No)], then: Then::Note(Note::NotOpen) }, 271 Rule { name: "closed: voice right", when: &[when(What, Value::VoiceRight), when(Editing, No)], then: Then::Note(Note::NotOpen) }, 272 Rule { name: "closed: voice left", when: &[when(What, Value::VoiceLeft), when(Editing, No)], then: Then::Note(Note::NotOpen) }, 273 Rule { name: "closed: check", when: &[when(What, Value::Check), when(Editing, No)], then: Then::Note(Note::NotOpen) }, 274 Rule { name: "closed: cross", when: &[when(What, Value::Cancel), when(Editing, No)], then: Then::Note(Note::NotOpen) }, 275];
The network of [RULES]: the one that runs, and the one the drawing is made from.
282#[cfg(test)] 283mod tests { 284 use super::*; 285 286 #[test] 287 fn every_effect_a_rule_does_is_one_the_domain_names_and_teaches() { 288 let network = network(); 289 for terminal in &network.terminals { 290 if let Then::Do(effect) = terminal.then { 291 assert!(ChooserDomain::facts().contains(&ChooserDomain::teaches(effect)), "{}", terminal.name); 292 assert!(!ChooserDomain::effect_name(effect).is_empty()); 293 } 294 } 295 } 296 297 #[test] 298 fn every_input_has_a_rule_open_and_a_rule_closed() { 299 let network = network(); 300 for input in Input::ALL { 301 for editing in [Value::Yes, Value::No] { 302 let mut known = rete::Known::<ChooserDomain>::default(); 303 known.learn(Fact::Input, input.value()); 304 known.learn(Fact::Editing, editing); 305 known.learn(Fact::Unsaved, Value::Yes); 306 known.learn(Fact::Voices, Value::Yes); 307 assert!(!network.holding(&known).is_empty(), "{input:?} with editing {editing:?} means nothing: say so with a note"); 308 } 309 } 310 } 311}