1use crate::alloc_prelude::*;
14use alloc::collections::{BTreeMap, VecDeque};
15use alloc::vec;
16
17#[cfg(feature = "std")]
18use shuyi_assets::bundle::Layer;
19use shuyi_ui::audio::{MusicCommand, SharedSamples, SoundId};
20use shuyi_ui::frame::Rgb;
21use shuyi_ui::frame::glyph_buffer::GlyphBuffer;
22use shuyi_ui::frame_cap::FrameCap;
23use shuyi_ui::perf::{
24 HeapCounts, HeapSnapshot, MemoryReading, MemoryScopes, PerfClock, TagClass, TagRow,
25};
26use shuyi_ui::storage::{MemoryStorage, Storage};
27use shuyi_ui::{PlatformFacts, RawInputEvent};
28
29use crate::app::Card;
30use crate::audio::{Audio, AudioFrame, SoundRequest};
31use crate::platform::{DisplayInfo, Frame, Millis, Platform, Show, SystemEvent};
32use crate::session::{FrameRecord, KeyboardFrame, SessionRecord};
33use crate::text_input::{TextAnswer, TextAsk, TextInput};
34
35#[derive(Debug, Clone, PartialEq)]
37pub enum Presented {
38 Card(Card),
40 Errors(String),
42 Game {
44 buffer: GlyphBuffer,
46 background: Rgb,
48 fullscreen: bool,
50 },
51}
52
53impl Presented {
54 pub fn buffer(&self) -> Option<&GlyphBuffer> {
56 match self {
57 Self::Game { buffer, .. } => Some(buffer),
58 Self::Card(_) | Self::Errors(_) => None,
59 }
60 }
61
62 fn keep(old: &mut Option<Self>, frame: &Frame<'_>) {
65 match (old.as_mut(), frame.show) {
66 (
67 Some(Self::Game {
68 buffer,
69 background,
70 fullscreen,
71 }),
72 Show::Game {
73 buffer: new,
74 background: new_background,
75 ..
76 },
77 ) => {
78 if buffer != new {
79 buffer.clone_from(new);
80 }
81 *background = new_background;
82 *fullscreen = frame.fullscreen;
83 }
84 (
85 _,
86 Show::Game {
87 buffer, background, ..
88 },
89 ) => {
90 *old = Some(Self::Game {
91 buffer: buffer.clone(),
92 background,
93 fullscreen: frame.fullscreen,
94 });
95 }
96 (_, Show::Card(card)) => *old = Some(Self::Card(card.clone())),
97 (_, Show::Errors(errors)) => *old = Some(Self::Errors(errors.to_owned())),
98 }
99 }
100}
101
102#[derive(Debug, Clone, PartialEq)]
105#[non_exhaustive]
106pub struct PlayingSound {
107 pub id: SoundId,
109 pub cue: String,
111 pub paused: bool,
113}
114
115#[derive(Debug, Clone, PartialEq)]
118#[non_exhaustive]
119pub struct AddedPlay {
120 pub cue: String,
122 pub samples: SharedSamples,
124 pub volume: f32,
126 pub id: Option<SoundId>,
128}
129
130#[derive(Debug, Clone, Default)]
141pub struct Speaker {
142 load_delay: f64,
145 silent: bool,
147 started: Option<(String, Millis)>,
149 voice_length: Option<f64>,
152 voice: Option<(String, Millis)>,
154 requests: Vec<SoundRequest>,
156 music: Vec<MusicCommand>,
158 volumes: Option<(f32, f32, f32)>,
160 stopped: bool,
162 playing: Vec<PlayingSound>,
164 suspended: bool,
166 recorded: Option<RecordedMusic>,
169 added: BTreeMap<String, SharedSamples>,
171 added_plays: Vec<AddedPlay>,
173}
174
175#[derive(Debug, Clone, Default)]
177struct RecordedMusic {
178 track: String,
180 position: Option<f64>,
182 voice: String,
185}
186
187impl Speaker {
188 pub fn set_load_delay(&mut self, seconds: f64) {
192 self.load_delay = seconds.max(0.0);
193 }
194
195 pub fn set_voice_length(&mut self, seconds: f64) {
199 self.voice_length = Some(seconds.max(0.0));
200 }
201
202 pub fn set_silent(&mut self) {
204 self.silent = true;
205 }
206
207 pub fn requests(&self) -> &[SoundRequest] {
210 &self.requests
211 }
212
213 pub fn music(&self) -> &[MusicCommand] {
215 &self.music
216 }
217
218 pub fn volumes(&self) -> (f32, f32, f32) {
221 self.volumes.unwrap_or((1.0, 1.0, 1.0))
222 }
223
224 pub fn stopped(&self) -> bool {
226 self.stopped
227 }
228
229 pub fn sounds_playing(&self) -> &[PlayingSound] {
231 &self.playing
232 }
233
234 pub fn suspended(&self) -> bool {
236 self.suspended
237 }
238
239 pub fn added_sounds(&self) -> &BTreeMap<String, SharedSamples> {
241 &self.added
242 }
243
244 pub fn added_plays(&self) -> &[AddedPlay] {
247 &self.added_plays
248 }
249
250 fn hear_added(&mut self, cue: &str, volume: f32, id: Option<SoundId>) {
252 if let Some(samples) = self.added.get(cue) {
253 self.added_plays.push(AddedPlay {
254 cue: cue.to_owned(),
255 samples: samples.clone(),
256 volume,
257 id,
258 });
259 }
260 }
261
262 fn play_recorded(&mut self, frame: &FrameRecord) {
265 let recorded = self.recorded.get_or_insert_default();
266 if let Some(track) = &frame.track {
267 track.clone_into(&mut recorded.track);
268 }
269 recorded.position = frame.music;
270 if let Some(voice) = &frame.voice {
271 voice.clone_into(&mut recorded.voice);
272 }
273 }
274
275 fn hear(&mut self, request: &SoundRequest, now: Millis) {
279 match request {
280 SoundRequest::Voice { clip } => self.voice = Some((clip.clone(), now)),
281 SoundRequest::StopVoice => self.voice = None,
282 SoundRequest::Sound { cue, volume } => self.hear_added(cue, *volume, None),
283 SoundRequest::Start {
284 id, cue, volume, ..
285 } => {
286 self.hear_added(cue, *volume, Some(*id));
287 let sound = PlayingSound {
288 id: *id,
289 cue: cue.clone(),
290 paused: false,
291 };
292 match self.playing.binary_search_by_key(id, |p| p.id) {
293 Ok(at) => self.playing[at] = sound,
294 Err(at) => self.playing.insert(at, sound),
295 }
296 }
297 SoundRequest::Stop { id } => self.playing.retain(|p| p.id != *id),
298 SoundRequest::PauseSounds => self.playing.iter_mut().for_each(|p| p.paused = true),
299 SoundRequest::ResumeSounds => self.playing.iter_mut().for_each(|p| p.paused = false),
300 SoundRequest::Add { cue, samples, .. } => {
301 self.added.insert(cue.clone(), samples.clone());
302 }
303 SoundRequest::Remove { cue } if self.added.remove(cue).is_some() => {
304 self.playing.retain(|p| p.cue != *cue);
305 }
306 _ => {}
307 }
308 }
309
310 fn apply(&mut self, command: &MusicCommand, now: Millis) {
313 match command {
314 MusicCommand::Start { cue, .. } => self.started = Some((cue.clone(), now)),
315 MusicCommand::Stop { cue } | MusicCommand::Load { cue } => {
316 if self.started.as_ref().is_some_and(|(c, _)| c == cue) {
317 self.started = None;
318 }
319 }
320 MusicCommand::Gain { .. } => {}
321 }
322 }
323}
324
325impl Audio for Speaker {
326 fn music_playing(&self, now: Millis) -> Option<(&str, f64)> {
327 if let Some(recorded) = &self.recorded {
328 return recorded.position.map(|at| (recorded.track.as_str(), at));
329 }
330 let (cue, at) = self.started.as_ref().filter(|_| !self.silent)?;
331 let elapsed = now.since(*at).as_secs_f64() - self.load_delay;
332 (elapsed >= 0.0).then_some((cue.as_str(), elapsed))
333 }
334
335 fn voice_playing(&self, now: Millis) -> Option<&str> {
336 if let Some(recorded) = &self.recorded {
337 return Some(recorded.voice.as_str()).filter(|voice| !voice.is_empty());
338 }
339 let (clip, at) = self.voice.as_ref()?;
340 let on = self
341 .voice_length
342 .is_none_or(|length| now.since(*at).as_secs_f64() < length);
343 on.then_some(clip.as_str())
344 }
345
346 fn play(&mut self, frame: &AudioFrame<'_>, now: Millis) {
347 self.volumes = Some((frame.music_volume, frame.sound_volume, frame.voice_volume));
348 self.requests.clear();
349 self.requests.extend_from_slice(frame.requests);
350 for request in frame.requests {
351 self.hear(request, now);
352 }
353 self.music.clear();
354 self.music.extend_from_slice(frame.music);
355 for command in frame.music {
356 self.apply(command, now);
357 }
358 }
359
360 fn stop_all(&mut self) {
361 self.started = None;
362 self.voice = None;
363 self.playing.clear();
364 self.stopped = true;
365 }
366
367 fn suspend(&mut self) {
368 self.suspended = true;
369 }
370
371 fn resume(&mut self) {
372 self.suspended = false;
373 }
374}
375
376#[derive(Debug, Clone, Default)]
379pub struct ScriptedKeyboard {
380 answers: VecDeque<TextAnswer>,
382 asks: Vec<TextAsk>,
384 pending: Option<TextAnswer>,
386}
387
388impl ScriptedKeyboard {
389 pub fn new(answers: impl IntoIterator<Item = TextAnswer>) -> Self {
391 Self {
392 answers: answers.into_iter().collect(),
393 ..Self::default()
394 }
395 }
396
397 pub fn asks(&self) -> &[TextAsk] {
399 &self.asks
400 }
401}
402
403impl TextInput for ScriptedKeyboard {
404 fn open(&mut self, ask: &TextAsk) -> bool {
405 if self.pending.is_some() {
406 return false;
407 }
408 self.asks.push(ask.clone());
409 self.pending = Some(self.answers.pop_front().unwrap_or(TextAnswer::Cancelled));
410 true
411 }
412
413 fn poll(&mut self) -> Option<TextAnswer> {
414 self.pending.take()
415 }
416}
417
418#[derive(Debug, Clone, Default)]
421struct RecordedKeyboard {
422 present: bool,
423 answer: Option<TextAnswer>,
424 opened: Option<bool>,
425}
426
427impl RecordedKeyboard {
428 fn play(&mut self, frame: Option<&KeyboardFrame>) {
430 self.answer = None;
431 self.opened = None;
432 if let Some(frame) = frame {
433 self.present = frame.present.unwrap_or(self.present);
434 self.answer.clone_from(&frame.answer);
435 self.opened = frame.opened;
436 }
437 }
438}
439
440impl TextInput for RecordedKeyboard {
441 fn open(&mut self, _ask: &TextAsk) -> bool {
442 self.opened.take().unwrap_or(false)
443 }
444
445 fn poll(&mut self) -> Option<TextAnswer> {
446 self.answer.take()
447 }
448}
449
450#[derive(Debug, Clone, Copy)]
452enum Memory {
453 None,
455 Fake,
458 Real(fn() -> HeapCounts),
460}
461
462fn fake_heap(n: u32) -> HeapCounts {
466 const BASE: u64 = 1 << 20;
467 const STEP: u64 = 16 * 1024;
468 let wide = u64::from(n);
469 HeapCounts::new(BASE + STEP * (wide % 30), BASE + STEP * wide.min(29), n % 7)
470}
471
472fn fake_snapshot(n: u32) -> HeapSnapshot {
480 const STEP: u64 = 16 * 1024;
481 let heap = fake_heap(n);
482 let row = |class, name: &'static str, live_bytes, live_blocks, peak_bytes| {
483 let mut row = TagRow::new(class, name, live_bytes, live_blocks);
484 row.allocated_bytes = peak_bytes;
485 row.peak_bytes = peak_bytes;
486 row
487 };
488 let fixed = |class, name, bytes, blocks| row(class, name, bytes, blocks, bytes);
489 let mut rows = vec![
490 fixed(TagClass::Asset, "image", 49_152, 3),
491 fixed(TagClass::Phase, "load", 524_288, 40),
492 fixed(TagClass::Other, "other", 475_136, 12),
493 ];
494 if n > 0 {
495 let (wide, peak) = (u64::from(n), STEP * u64::from(n.min(29)));
496 rows.push(row(
497 TagClass::Phase,
498 "update",
499 STEP * (wide % 30),
500 n % 30,
501 peak,
502 ));
503 }
504 HeapSnapshot::new(heap.live_bytes, heap.peak_bytes, rows)
505}
506
507#[derive(Debug)]
509pub struct HeadlessPlatform {
510 display: DisplayInfo,
511 input: VecDeque<Vec<RawInputEvent>>,
513 system: Vec<SystemEvent>,
515 storage: Option<Box<dyn Storage>>,
517 speaker: Speaker,
518 keyboard: Option<ScriptedKeyboard>,
519 recorded_keyboard: Option<RecordedKeyboard>,
522 facts: PlatformFacts,
524 clock: Option<PerfClock>,
526 memory: Memory,
528 memory_calls: u32,
530 scopes: Option<MemoryScopes>,
532 snapshots: Option<fn() -> HeapSnapshot>,
535 presented: u32,
537 last: Option<Presented>,
539 frame_cap: Option<(FrameCap, u32)>,
541 reload_errors: Vec<String>,
543 #[cfg(feature = "std")]
545 changes: Vec<(Layer, String, Vec<u8>)>,
546 changed_asks: u32,
548 present_hook: Option<Hook>,
550}
551
552pub type PresentHook = Box<dyn FnMut(&Frame<'_>)>;
557
558struct Hook(PresentHook);
560
561impl core::fmt::Debug for Hook {
562 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
563 f.write_str("PresentHook")
564 }
565}
566
567impl Default for HeadlessPlatform {
568 fn default() -> Self {
569 Self::new()
570 }
571}
572
573impl HeadlessPlatform {
574 pub const DISPLAY: DisplayInfo = DisplayInfo::new(1280, 720);
576
577 pub fn new() -> Self {
580 Self {
581 display: Self::DISPLAY,
582 input: VecDeque::new(),
583 system: Vec::new(),
584 storage: None,
585 speaker: Speaker::default(),
586 keyboard: None,
587 recorded_keyboard: None,
588 facts: PlatformFacts::default(),
589 clock: None,
590 memory: Memory::None,
591 memory_calls: 0,
592 scopes: None,
593 snapshots: None,
594 presented: 0,
595 last: None,
596 frame_cap: None,
597 reload_errors: Vec::new(),
598 #[cfg(feature = "std")]
599 changes: Vec::new(),
600 changed_asks: 0,
601 present_hook: None,
602 }
603 }
604
605 pub fn from_session(record: &SessionRecord) -> Self {
610 let mut storage = MemoryStorage::new();
611 for (key, value) in &record.storage {
612 let _ = storage.write(key, value);
614 }
615 Self::new()
616 .with_display(record.display)
617 .with_facts(record.platform.clone())
618 .with_storage(Box::new(storage))
619 }
620
621 pub fn push_frame(&mut self, frame: &FrameRecord) {
624 self.system.extend_from_slice(&frame.system);
625 self.push_input(&frame.input);
626 self.speaker.play_recorded(frame);
627 self.recorded_keyboard
628 .get_or_insert_default()
629 .play(frame.keyboard.as_ref());
630 }
631
632 pub fn end_session(&mut self) {
635 self.speaker.recorded = None;
636 self.recorded_keyboard = None;
637 }
638
639 #[must_use]
641 pub fn with_display(mut self, display: DisplayInfo) -> Self {
642 self.display = display;
643 self
644 }
645
646 #[must_use]
648 pub fn with_storage(mut self, storage: Box<dyn Storage>) -> Self {
649 self.storage = Some(storage);
650 self
651 }
652
653 #[must_use]
657 pub fn with_facts(mut self, facts: PlatformFacts) -> Self {
658 self.facts = facts;
659 self
660 }
661
662 #[must_use]
664 pub fn with_keyboard(mut self, keyboard: ScriptedKeyboard) -> Self {
665 self.keyboard = Some(keyboard);
666 self
667 }
668
669 #[must_use]
673 pub fn with_clock(mut self, clock: PerfClock) -> Self {
674 self.clock = Some(clock);
675 self
676 }
677
678 pub fn set_clock(&mut self, clock: Option<PerfClock>) {
681 self.clock = clock;
682 }
683
684 #[must_use]
697 pub fn with_fake_memory(mut self) -> Self {
698 self.memory = Memory::Fake;
699 self
700 }
701
702 pub fn set_memory(&mut self, memory: Option<fn() -> HeapCounts>) {
705 self.memory = memory.map_or(Memory::None, Memory::Real);
706 }
707
708 pub fn set_heap_snapshots(&mut self, snapshot: Option<fn() -> HeapSnapshot>) {
711 self.snapshots = snapshot;
712 }
713
714 pub fn set_present_hook(&mut self, hook: Option<PresentHook>) {
717 self.present_hook = hook.map(Hook);
718 }
719
720 pub fn has_memory(&self) -> bool {
722 !matches!(self.memory, Memory::None)
723 }
724
725 pub fn memory_calls(&self) -> u32 {
727 self.memory_calls
728 }
729
730 #[must_use]
734 pub fn with_scopes(mut self, scopes: MemoryScopes) -> Self {
735 self.scopes = Some(scopes);
736 self
737 }
738
739 pub fn set_scopes(&mut self, scopes: Option<MemoryScopes>) {
742 self.scopes = scopes;
743 }
744
745 pub fn push_input(&mut self, events: &[RawInputEvent]) {
748 self.input.push_back(events.to_vec());
749 }
750
751 pub fn push_system(&mut self, event: SystemEvent) {
753 self.system.push(event);
754 }
755
756 #[cfg(feature = "std")]
761 pub fn change_file(&mut self, layer: Layer, path: &str, bytes: Vec<u8>) {
762 self.changes.push((layer, path.to_owned(), bytes));
763 }
764
765 pub fn changed_asks(&self) -> u32 {
768 self.changed_asks
769 }
770
771 pub fn reload_errors(&self) -> &[String] {
774 &self.reload_errors
775 }
776
777 pub fn presented(&self) -> u32 {
779 self.presented
780 }
781
782 pub fn last_frame(&self) -> Option<&Presented> {
784 self.last.as_ref()
785 }
786
787 pub fn frame_cap(&self) -> Option<(FrameCap, u32)> {
790 self.frame_cap
791 }
792
793 pub fn speaker(&self) -> &Speaker {
795 &self.speaker
796 }
797
798 pub fn speaker_mut(&mut self) -> &mut Speaker {
800 &mut self.speaker
801 }
802
803 pub fn keyboard(&self) -> Option<&ScriptedKeyboard> {
805 self.keyboard.as_ref()
806 }
807}
808
809impl Platform for HeadlessPlatform {
810 fn display(&self) -> DisplayInfo {
811 self.display
812 }
813
814 fn present(&mut self, frame: &Frame<'_>) {
815 self.presented += 1;
816 self.frame_cap = Some((frame.frame_cap, frame.frame_cap_serial));
817 self.reload_errors.clear();
818 self.reload_errors.extend_from_slice(frame.reload_errors);
819 Presented::keep(&mut self.last, frame);
820 if let Some(Hook(hook)) = &mut self.present_hook {
821 hook(frame);
822 }
823 }
824
825 fn poll_input(&mut self, out: &mut Vec<RawInputEvent>) {
826 if let Some(events) = self.input.pop_front() {
827 out.extend(events);
828 }
829 }
830
831 fn system_events(&mut self, out: &mut Vec<SystemEvent>) {
832 out.append(&mut self.system);
833 }
834
835 fn storage(&mut self) -> Box<dyn Storage> {
836 self.storage
837 .take()
838 .unwrap_or_else(|| Box::new(MemoryStorage::new()))
839 }
840
841 fn audio(&mut self) -> &mut dyn Audio {
842 &mut self.speaker
843 }
844
845 fn text_input(&mut self) -> Option<&mut dyn TextInput> {
846 if let Some(recorded) = &mut self.recorded_keyboard {
847 return recorded.present.then_some(recorded as &mut dyn TextInput);
848 }
849 self.keyboard.as_mut().map(|k| k as &mut dyn TextInput)
850 }
851
852 fn facts(&self) -> PlatformFacts {
853 self.facts.clone()
854 }
855
856 fn clock(&self) -> Option<PerfClock> {
857 self.clock
858 }
859
860 fn memory_scopes(&self) -> Option<MemoryScopes> {
861 self.scopes
862 }
863
864 fn memory(&mut self) -> Option<MemoryReading> {
865 self.memory_calls = self.memory_calls.saturating_add(1);
866 let heap = match self.memory {
867 Memory::None => return None,
868 Memory::Fake => fake_heap(self.presented),
869 Memory::Real(read) => read(),
870 };
871 Some(MemoryReading::new(heap))
872 }
873
874 fn heap_snapshot(&mut self) -> Option<HeapSnapshot> {
875 match self.memory {
876 Memory::Fake => Some(fake_snapshot(self.presented)),
877 Memory::None | Memory::Real(_) => self.snapshots.map(|snapshot| snapshot()),
878 }
879 }
880
881 #[cfg(feature = "std")]
882 fn changed_files(&mut self, out: &mut Vec<String>) {
883 self.changed_asks = self.changed_asks.saturating_add(1);
884 for (layer, path, bytes) in self.changes.drain(..) {
885 if layer.replace(&path, bytes) {
886 out.push(path);
887 }
888 }
889 }
890}
891
892#[cfg(test)]
893mod tests;