gled_core/engine/
outbox.rs1use std::sync::Arc;
9
10use gled_model::{collection::Collections as AssetCollections, timing::BeatState};
11use gled_protocol::{
12 assets::{AssetChanges, all_assets},
13 bytes::SharedBytes,
14 message::{ServerMessage, Subscriptions},
15 project_update::ProjectUpdate,
16};
17
18use super::{ClientState, EngineState, EventLog, RenderStats, SoundSnapshot};
19
20#[derive(Debug, Default)]
22pub struct Outbox {
23 subscriptions: Subscriptions,
24 preview: Option<Arc<Vec<u8>>>,
25 sound: Option<Arc<SoundSnapshot>>,
26 assets: Option<Arc<AssetCollections>>,
27 state: Option<Arc<EngineState>>,
28 applied_sequence: u64,
29 timing: Option<(BeatState, RenderStats)>,
30 input_events_from: Option<u64>,
33 midi_from: Option<u64>,
34 learn_from: Option<u64>,
35}
36
37impl Outbox {
38 pub fn subscribe(&mut self, subscriptions: Subscriptions) {
40 self.subscriptions = subscriptions;
41 if !subscriptions.preview {
42 self.preview = None;
43 }
44 if !subscriptions.sound {
45 self.sound = None;
46 }
47 if !subscriptions.input_events {
48 self.input_events_from = None;
49 }
50 if subscriptions.midi {
51 self.midi_from.get_or_insert(0);
53 } else {
54 self.midi_from = None;
55 self.learn_from = None;
56 }
57 }
58
59 pub fn messages(&mut self, state: ClientState) -> Vec<ServerMessage> {
62 let mut messages = Vec::new();
63 if self.subscriptions.preview
64 && let Some(preview) = &state.preview
65 && !self
66 .preview
67 .as_ref()
68 .is_some_and(|previous| Arc::ptr_eq(previous, preview))
69 {
70 messages.push(ServerMessage::Preview(SharedBytes(preview.clone())));
71 self.preview = Some(preview.clone());
72 }
73 if self.subscriptions.sound
74 && !self
75 .sound
76 .as_ref()
77 .is_some_and(|previous| Arc::ptr_eq(previous, &state.sound))
78 {
79 messages.push(ServerMessage::Sound(state.sound.clone()));
80 self.sound = Some(state.sound.clone());
81 }
82
83 if self.subscriptions.input_events
84 && let Some(events) = new_events(&state.input_log, &mut self.input_events_from)
85 {
86 messages.push(ServerMessage::InputEvents(events));
87 }
88 if self.subscriptions.midi {
89 if let Some(events) = new_events(&state.midi_log, &mut self.midi_from) {
90 messages.push(ServerMessage::MidiMonitor(events));
91 }
92 if let Some(events) = new_events(&state.learn_log, &mut self.learn_from) {
93 messages.push(ServerMessage::MidiLearn(events));
94 }
95 }
96
97 match &self.assets {
99 None => messages.push(ServerMessage::Assets(all_assets(&state.assets))),
100 Some(previous) if !Arc::ptr_eq(previous, &state.assets) => {
101 let changes = AssetChanges::between(previous, &state.assets);
102 if !changes.is_empty() {
103 messages.push(ServerMessage::AssetsChanged(changes));
104 }
105 }
106 Some(_) => {}
107 }
108 self.assets = Some(state.assets.clone());
109
110 let project = self.state.as_ref().map(|previous| {
113 ProjectUpdate::between(previous.project.as_deref(), state.shared.project.as_deref())
114 });
115 let changed = self.applied_sequence != state.applied_sequence
116 || match (&self.state, &project) {
117 (Some(previous), Some(project)) => {
118 !previous.same_but_timing_and_project(&state.shared)
119 || !matches!(project, ProjectUpdate::Unchanged)
120 }
121 _ => true,
122 };
123 let timing = (state.shared.beat, state.shared.render);
124 if changed {
125 messages.push(match project {
127 None => ServerMessage::State {
128 state: state.shared.clone(),
129 applied_sequence: state.applied_sequence,
130 },
131 Some(project) => ServerMessage::Update {
132 state: Arc::new(state.shared.without_project()),
133 project,
134 applied_sequence: state.applied_sequence,
135 },
136 });
137 self.state = Some(state.shared);
138 self.applied_sequence = state.applied_sequence;
139 self.timing = Some(timing);
140 } else if self.timing != Some(timing) {
141 let (beat, render) = timing;
142 messages.push(ServerMessage::Timing { beat, render });
143 self.timing = Some(timing);
144 }
145 messages
146 }
147}
148
149fn new_events<T: Clone>(log: &EventLog<T>, from: &mut Option<u64>) -> Option<Vec<T>> {
152 let events = from.map(|from| log.since(from));
153 *from = Some(log.next());
154 events.filter(|events| !events.is_empty())
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::{
161 engine::{InputLog, MidiLog},
162 input::key_code::KeyCode,
163 };
164 use gled_model::input::event::InputEvent;
165 use gled_protocol::midi::MidiMonitorEvent;
166
167 fn client_state(log: &InputLog) -> ClientState {
168 ClientState {
169 shared: Arc::default(),
170 applied_sequence: 0,
171 assets: Arc::default(),
172 preview: None,
173 sound: Arc::default(),
174 input_log: Arc::new(log.clone()),
175 midi_log: Arc::default(),
176 learn_log: Arc::default(),
177 }
178 }
179
180 fn input_events(messages: &[ServerMessage]) -> Vec<InputEvent> {
181 messages
182 .iter()
183 .filter_map(|message| match message {
184 ServerMessage::InputEvents(events) => Some(events.clone()),
185 _ => None,
186 })
187 .flatten()
188 .collect()
189 }
190
191 #[test]
192 fn input_events_start_with_the_subscription_and_none_is_lost() {
193 let mut log = InputLog::default();
194 log.push(InputEvent::Key(KeyCode::A));
195 let mut outbox = Outbox::default();
196 outbox.subscribe(Subscriptions {
197 input_events: true,
198 ..Subscriptions::default()
199 });
200 assert!(input_events(&outbox.messages(client_state(&log))).is_empty());
202
203 log.push(InputEvent::Key(KeyCode::B));
205 log.push(InputEvent::Key(KeyCode::C));
206 assert_eq!(
207 input_events(&outbox.messages(client_state(&log))),
208 vec![InputEvent::Key(KeyCode::B), InputEvent::Key(KeyCode::C)]
209 );
210 assert!(input_events(&outbox.messages(client_state(&log))).is_empty());
211 }
212
213 #[test]
214 fn the_midi_monitor_starts_with_the_kept_history() {
215 let mut midi = MidiLog::default();
216 midi.push(MidiMonitorEvent {
217 timestamp_ms: 1,
218 port_name: "APC40".to_owned(),
219 label: "input connected".to_owned(),
220 bytes: Vec::new(),
221 });
222 let mut state = client_state(&InputLog::default());
223 state.midi_log = Arc::new(midi.clone());
224 let mut outbox = Outbox::default();
225 outbox.subscribe(Subscriptions {
226 midi: true,
227 ..Subscriptions::default()
228 });
229 let messages = outbox.messages(state);
230 assert!(messages.iter().any(
231 |message| matches!(message, ServerMessage::MidiMonitor(events) if events.len() == 1)
232 ));
233 }
234}