viewer: fix a startup panic in the Save File browser (Bevy B0002)

draw_save_ui held both an EventReader<RequestSaveOpen> (to know when the View
menu opened it) and an EventWriter<RequestSaveOpen> (so its own "Open savedata…"
button could re-trigger the file dialog). Bevy rejects a system that accesses
one event type both ways, and does so at schedule-validation time -- so the app
panicked on startup, before any window content.

The button now sets `SaveBrowser::request_open` and handle_save_open_request
treats that flag as equivalent to the event, with no path (dialog).

Audited every system in the viewer for the same shape; this was the only one.
Verified by running the binary to steady state rather than by compiling alone,
since a param conflict is invisible to the type checker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-08-17 21:34:10 +02:00
parent 221702ef6b
commit d921ed2602
2 changed files with 16 additions and 4 deletions

View File

@@ -786,6 +786,11 @@ pub struct SaveBrowser {
pub result: Option<Box<SaveResult>>,
/// Show the fields that are still unidentified.
pub show_unknown: bool,
/// Set by the window's own "Open savedata…" button. It cannot send
/// [`RequestSaveOpen`] itself: a system may not hold an `EventWriter` and an
/// `EventReader` for the same event type, and `draw_save_ui` already reads
/// this one (Bevy B0002).
pub request_open: bool,
}
/// Ask the loader to open + parse a `savedata` file. `None` = show a file
@@ -4267,7 +4272,14 @@ fn handle_save_open_request(
channels: Res<IsoChannels>,
mut saves: ResMut<SaveBrowser>,
) {
let Some(req) = events.read().last() else {
// Either the View-menu event, or the in-window button's flag.
let want = match events.read().last() {
Some(req) => Some(req.path.clone()),
None if saves.request_open => Some(None),
None => None,
};
saves.request_open = false;
let Some(want) = want else {
return;
};
if saves.loading {
@@ -4275,7 +4287,6 @@ fn handle_save_open_request(
}
saves.loading = true;
saves.open = true;
let want = req.path.clone();
let sender = channels.sender.clone();
std::thread::spawn(move || {
let path = match want {

View File

@@ -2122,7 +2122,6 @@ fn draw_save_ui(
mut contexts: EguiContexts,
mut saves: ResMut<SaveBrowser>,
mut requests: EventReader<RequestSaveOpen>,
mut open_save: EventWriter<RequestSaveOpen>,
) {
if requests.read().next().is_some() {
saves.open = true;
@@ -2289,8 +2288,10 @@ fn draw_save_ui(
});
});
// A flag, not a self-send: holding both an EventReader and an EventWriter
// for RequestSaveOpen in one system is a Bevy B0002 panic at startup.
if reopen {
open_save.send(RequestSaveOpen::default());
saves.request_open = true;
}
saves.open &= open;
}