domain.rsannotateddomain.rssource311 lines · 11.6 KB · raw

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.

13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub enum Input {

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,

The X: throw the change away and leave.

28    Cancel,
29}
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.

48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub struct ChooserDomain;
51#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
52pub enum Fact {

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.

60    Voices,
61    Entered,
62    CharacterMoved,
63    VoiceMoved,
64    Said,
65    Saved,
66    Discarded,
67    Closed,
68}
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub enum Value {
72    Yes,
73    No,
74    Open,
75    Left,
76    Right,
77    VoiceLeft,
78    VoiceRight,
79    Check,
80    Cancel,
81}

Which way an arrow steps. Both ways wrap around.

84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub enum Step {
86    Next,
87    Previous,
88}

What a rule makes happen. Each teaches a fact once it is done.

91#[derive(Clone, Copy, Debug, PartialEq, Eq)]
92pub enum Effect {

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,

Close the chooser.

108    Leave,
109}

A rule never ends the query outright here: there is nothing to refuse.

112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113pub enum End {}

Why an input did nothing, so the trace says so rather than staying silent.

116#[derive(Clone, Copy, Debug, PartialEq, Eq)]
117pub enum Note {

The input only means something while the chooser is open.

119    NotOpen,

The chooser is open already.

121    AlreadyOpen,

There is no voice list to step through, so she keeps the voice she has.

123    NoVoices,
124}
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.

278pub fn network() -> rete::Network<ChooserDomain> {
279    rete::Network::compile(RULES)
280}
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}