Skip to content

Commit c069fb0

Browse files
committed
prog
1 parent 29ca163 commit c069fb0

5 files changed

Lines changed: 124 additions & 35 deletions

File tree

core/src/app/player/rendering.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ where
143143

144144
// Console debug panel visibility flag and pointer
145145
let console_debug_visible = self.console_debug_panel_visible;
146+
147+
// Sync debug UI state before rendering (enables EPU lock mode, etc.)
148+
if console_debug_visible
149+
&& let Some(session) = runner.session_mut()
150+
{
151+
let (console, state_opt) = session.runtime.console_and_state_mut();
152+
if let Some(state) = state_opt {
153+
console.sync_debug_ui_state(state);
154+
}
155+
}
156+
146157
// SAFETY: We use a raw pointer to avoid borrow conflicts between
147158
// console (in session) and graphics (separate field). The pointer
148159
// is only used during egui_ctx.run(), before any graphics access.

core/src/console.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,17 @@ pub trait Console: Send + 'static {
252252
fn has_debug_panel(&self) -> bool {
253253
false
254254
}
255+
256+
/// Sync state between console and debug UI before rendering.
257+
///
258+
/// Called each frame when the debug panel may need state access.
259+
/// This allows the debug UI to read from and write to console state,
260+
/// enabling features like EPU lock mode where debug edits override game values.
261+
///
262+
/// Default implementation does nothing.
263+
fn sync_debug_ui_state(&mut self, _state: &mut Self::State) {
264+
// Default: no-op for consoles without debug UI state sync
265+
}
255266
}
256267

257268
/// Trait for console-specific resource management

core/src/runtime/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,16 @@ impl<C: Console> Runtime<C> {
239239
pub fn game_and_audio_mut(&mut self) -> GameAndAudioMut<'_, C> {
240240
(self.game.as_mut(), self.audio.as_mut())
241241
}
242+
243+
/// Get mutable references to console and game state for debug UI syncing.
244+
///
245+
/// Returns (console, Option<state>) where state is the game's console state if loaded.
246+
/// This allows debug UI to sync state between the console and game.
247+
pub fn console_and_state_mut(&mut self) -> (&mut C, Option<&mut C::State>) {
248+
let state = self
249+
.game
250+
.as_mut()
251+
.map(|game| game.console_state_mut());
252+
(&mut self.console, state)
253+
}
242254
}

nethercore-zx/src/console.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -390,17 +390,26 @@ impl Console for NethercoreZX {
390390
self.epu_debug_panel.set_visible(true);
391391
}
392392
if self.epu_debug_panel.is_visible() {
393-
// Note: We don't have access to EpuConfig here since it's in State.
394-
// For now, render with empty configs. The panel can still show
395-
// the opcode browser and metadata.
396-
let empty_configs = hashbrown::HashMap::new();
397-
let _changed = self.epu_debug_panel.render(ctx, &empty_configs);
393+
// Configs are synced via sync_debug_ui_state before this call
394+
let _changed = self.epu_debug_panel.render(ctx);
398395
}
399396
}
400397

401398
fn has_debug_panel(&self) -> bool {
402399
true
403400
}
401+
402+
fn sync_debug_ui_state(&mut self, state: &mut ZXFFIState) {
403+
// Copy game configs to panel for display (even when locked, for reference)
404+
self.epu_debug_panel.update_snapshot(&state.epu_frame_configs);
405+
406+
// If locked: clear game configs and insert our single override config for env_id 0
407+
if self.epu_debug_panel.is_locked() {
408+
let override_config = self.epu_debug_panel.get_override_config();
409+
state.epu_frame_configs.clear();
410+
state.epu_frame_configs.insert(0, override_config);
411+
}
412+
}
404413
}
405414

406415
#[cfg(test)]

nethercore-zx/src/debug/epu_panel/mod.rs

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ pub struct EpuDebugPanel {
5959
preset_manager: PresetManager,
6060
/// Preset UI state
6161
preset_ui_state: PresetUiState,
62+
/// Snapshot of game configs (for display when locked)
63+
pub snapshot_configs: hashbrown::HashMap<u32, EpuConfig>,
64+
/// Whether lock mode is active (debugger config replaces ALL game configs)
65+
pub locked: bool,
6266
}
6367

6468
impl Default for EpuDebugPanel {
@@ -86,9 +90,26 @@ impl EpuDebugPanel {
8690
editing_env_id: None,
8791
preset_manager: PresetManager::default(),
8892
preset_ui_state: PresetUiState::default(),
93+
snapshot_configs: hashbrown::HashMap::new(),
94+
locked: false,
8995
}
9096
}
9197

98+
/// Update snapshot with current game configs
99+
pub fn update_snapshot(&mut self, game_configs: &hashbrown::HashMap<u32, EpuConfig>) {
100+
self.snapshot_configs.clone_from(game_configs);
101+
}
102+
103+
/// Check if lock mode is active
104+
pub fn is_locked(&self) -> bool {
105+
self.locked
106+
}
107+
108+
/// Get the debugger's override config (from editor state)
109+
pub fn get_override_config(&self) -> EpuConfig {
110+
self.editor.export_config()
111+
}
112+
92113
/// Toggle panel visibility
93114
pub fn toggle(&mut self) {
94115
self.visible = !self.visible;
@@ -107,28 +128,51 @@ impl EpuDebugPanel {
107128
/// Render the EPU debug panel
108129
///
109130
/// Returns true if any value was changed (for future integration with live editing)
110-
pub fn render(
111-
&mut self,
112-
ctx: &egui::Context,
113-
epu_configs: &hashbrown::HashMap<u32, EpuConfig>,
114-
) -> bool {
131+
pub fn render(&mut self, ctx: &egui::Context) -> bool {
115132
if !self.visible {
116133
return false;
117134
}
118135

119136
let mut changed = false;
120137

138+
// Capture config count before closure to avoid borrow conflicts
139+
let config_count = self.snapshot_configs.len();
140+
121141
egui::Window::new("EPU Debug Panel")
122142
.id(egui::Id::new("epu_debug_panel"))
123143
.default_pos([10.0, 300.0])
124144
.default_size([500.0, 600.0])
125145
.resizable(true)
126146
.collapsible(true)
127147
.show(ctx, |ui| {
128-
// Header with view mode tabs
148+
// Lock mode banner (prominent when active)
149+
if self.locked {
150+
ui.horizontal(|ui| {
151+
ui.add_space(4.0);
152+
let banner = egui::RichText::new("LOCKED - Game EPU disabled, using debugger config")
153+
.color(egui::Color32::WHITE)
154+
.strong();
155+
ui.colored_label(egui::Color32::from_rgb(200, 80, 40), banner);
156+
});
157+
ui.add_space(2.0);
158+
}
159+
160+
// Header with view mode tabs and lock toggle
129161
ui.horizontal(|ui| {
130162
ui.heading("Environment Processing Unit");
131163
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
164+
// Lock toggle (prominent)
165+
let lock_text = if self.locked {
166+
egui::RichText::new("LOCK").color(egui::Color32::from_rgb(255, 120, 60)).strong()
167+
} else {
168+
egui::RichText::new("LOCK").color(egui::Color32::GRAY)
169+
};
170+
if ui.checkbox(&mut self.locked, lock_text).changed() {
171+
changed = true;
172+
}
173+
174+
ui.separator();
175+
132176
if ui
133177
.selectable_label(self.view_mode == PanelView::Editor, "Editor")
134178
.clicked()
@@ -150,7 +194,12 @@ impl EpuDebugPanel {
150194
ui.horizontal(|ui| {
151195
ui.label(format!("Defined opcodes: {}", OPCODE_COUNT));
152196
ui.separator();
153-
ui.label(format!("Active configs: {}", epu_configs.len()));
197+
let config_label = if self.locked {
198+
"Game configs (snapshot): ".to_string()
199+
} else {
200+
"Active configs: ".to_string()
201+
};
202+
ui.label(format!("{}{}", config_label, config_count));
154203
if self.editor.dirty {
155204
ui.separator();
156205
ui.colored_label(egui::Color32::YELLOW, "* Modified");
@@ -177,16 +226,13 @@ impl EpuDebugPanel {
177226
self.render_opcode_details(&mut columns[1], opcode);
178227
} else {
179228
columns[1].heading("Active Configs");
180-
self.render_active_configs_with_edit(
181-
&mut columns[1],
182-
epu_configs,
183-
);
229+
self.render_active_configs_with_edit(&mut columns[1]);
184230
}
185231
});
186232
}
187233
PanelView::Editor => {
188234
// Semantic editor view
189-
changed |= self.render_editor_view(ui, epu_configs);
235+
changed |= self.render_editor_view(ui);
190236
}
191237
}
192238
});
@@ -195,14 +241,10 @@ impl EpuDebugPanel {
195241
}
196242

197243
/// Render the semantic editor view
198-
fn render_editor_view(
199-
&mut self,
200-
ui: &mut egui::Ui,
201-
epu_configs: &hashbrown::HashMap<u32, EpuConfig>,
202-
) -> bool {
244+
fn render_editor_view(&mut self, ui: &mut egui::Ui) -> bool {
203245
let mut changed = false;
204246

205-
// Environment selector
247+
// Environment selector (uses snapshot configs)
206248
ui.horizontal(|ui| {
207249
ui.label("Environment:");
208250

@@ -211,6 +253,7 @@ impl EpuDebugPanel {
211253
None => "New Config".to_string(),
212254
};
213255

256+
let env_ids: Vec<u32> = self.snapshot_configs.keys().copied().collect();
214257
egui::ComboBox::from_id_salt("env_selector")
215258
.selected_text(current_text)
216259
.show_ui(ui, |ui| {
@@ -224,8 +267,8 @@ impl EpuDebugPanel {
224267

225268
ui.separator();
226269

227-
// Existing configs
228-
for &env_id in epu_configs.keys() {
270+
// Existing configs from snapshot
271+
for env_id in env_ids {
229272
if ui
230273
.selectable_value(
231274
&mut self.editing_env_id,
@@ -234,7 +277,7 @@ impl EpuDebugPanel {
234277
)
235278
.clicked()
236279
{
237-
if let Some(config) = epu_configs.get(&env_id) {
280+
if let Some(config) = self.snapshot_configs.get(&env_id) {
238281
self.editor.load_config(config);
239282
}
240283
}
@@ -244,7 +287,7 @@ impl EpuDebugPanel {
244287
// Load button
245288
if let Some(env_id) = self.editing_env_id {
246289
if ui.button("Reload").clicked() {
247-
if let Some(config) = epu_configs.get(&env_id) {
290+
if let Some(config) = self.snapshot_configs.get(&env_id) {
248291
self.editor.load_config(config);
249292
}
250293
}
@@ -284,13 +327,9 @@ impl EpuDebugPanel {
284327
changed
285328
}
286329

287-
/// Render active configs with edit buttons
288-
fn render_active_configs_with_edit(
289-
&mut self,
290-
ui: &mut egui::Ui,
291-
configs: &hashbrown::HashMap<u32, EpuConfig>,
292-
) {
293-
if configs.is_empty() {
330+
/// Render active configs with edit buttons (uses snapshot_configs)
331+
fn render_active_configs_with_edit(&mut self, ui: &mut egui::Ui) {
332+
if self.snapshot_configs.is_empty() {
294333
ui.label("No active EPU configs this frame");
295334
ui.label("");
296335
ui.label("EPU configs are created via epu_set() FFI calls.");
@@ -305,10 +344,17 @@ impl EpuDebugPanel {
305344
return;
306345
}
307346

347+
// Collect config data to avoid borrow conflicts
348+
let config_entries: Vec<(u32, EpuConfig)> = self
349+
.snapshot_configs
350+
.iter()
351+
.map(|(&id, cfg)| (id, cfg.clone()))
352+
.collect();
353+
308354
egui::ScrollArea::vertical()
309355
.id_salt("epu_active_configs")
310356
.show(ui, |ui| {
311-
for (env_id, config) in configs.iter() {
357+
for (env_id, config) in config_entries.iter() {
312358
ui.horizontal(|ui| {
313359
ui.collapsing(format!("Environment {}", env_id), |ui| {
314360
self.render_config_summary(ui, config);

0 commit comments

Comments
 (0)