fixed a bug in the emulator where halting would break the state.

This commit is contained in:
2026-08-09 09:16:30 +01:00
parent 8b7f29cd48
commit e3a87ca908
10 changed files with 159 additions and 154 deletions
+21 -39
View File
@@ -1,21 +1,21 @@
#![feature(likely_unlikely)] #![feature(likely_unlikely)]
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::thread;
use std::thread::{JoinHandle, Thread};
use std::time::Duration;
use common::prelude::{Instruction, Register};
use crate::config::{IoMap, MemoryMap};
use crate::InternalImplementation::{Handle, Owned}; use crate::InternalImplementation::{Handle, Owned};
use crate::config::{IoMap, MemoryMap};
use crate::memory::PhysAddr; use crate::memory::PhysAddr;
use crate::memory::ram::MemoryBank; use crate::memory::ram::MemoryBank;
use crate::processor::{Interrupt, RunningState, SharedState}; use crate::processor::{Interrupt, RunningState, SharedState};
use common::prelude::Register;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::thread;
use std::thread::JoinHandle;
use std::time::Duration;
pub mod processor;
pub mod memory;
pub mod config; pub mod config;
pub mod devices; pub mod devices;
pub mod memory;
pub mod processor;
#[derive(Debug)] #[derive(Debug)]
pub enum DsaError { pub enum DsaError {
@@ -64,7 +64,6 @@ pub struct EmulatorHandle {
} }
impl EmulatorHandle { impl EmulatorHandle {
pub fn read_byte(&self, addr: PhysAddr) -> u8 { pub fn read_byte(&self, addr: PhysAddr) -> u8 {
self.memory.read_byte(addr) self.memory.read_byte(addr)
} }
@@ -81,10 +80,11 @@ impl EmulatorHandle {
self.memory.write_bytes(addr, data); self.memory.write_bytes(addr, data);
} }
pub fn interrupt(&mut self, code: Interrupt) -> Result<(), DsaError> { pub fn interrupt(&mut self, code: Interrupt) -> Result<(), DsaError> {
self.state.interrupt_queue.send(code) self.state
.map_err(|_| DsaError::CommunicationError("interrupt failed".into()) ) .interrupt_queue
.send(code)
.map_err(|_| DsaError::CommunicationError("interrupt failed".into()))
} }
pub fn status(&self) -> RunningState { pub fn status(&self) -> RunningState {
@@ -97,7 +97,6 @@ impl EmulatorHandle {
self.state.proc.load().time self.state.proc.load().time
} }
/// Request that the dsa_emulator backend provide an updated copy of its current internal state to `state.proc` /// Request that the dsa_emulator backend provide an updated copy of its current internal state to `state.proc`
pub fn request_update(&mut self) { pub fn request_update(&mut self) {
self.state.update_requested.store(true, Ordering::Relaxed); self.state.update_requested.store(true, Ordering::Relaxed);
@@ -112,7 +111,9 @@ impl EmulatorHandle {
pub fn when_paused(&mut self, f: impl FnOnce(&mut Self)) { pub fn when_paused(&mut self, f: impl FnOnce(&mut Self)) {
self.request_pause(); self.request_pause();
while self.state.proc.load().running != RunningState::Paused { while self.state.proc.load().running != RunningState::Paused
&& self.state.proc.load().running != RunningState::Ready
{
continue; continue;
} }
@@ -162,7 +163,6 @@ impl AsMut<EmulatorHandle> for EmulatorController {
} }
impl EmulatorController { impl EmulatorController {
pub fn new(mut emulator: Box<dyn DsaImplementation>) -> Self { pub fn new(mut emulator: Box<dyn DsaImplementation>) -> Self {
Self { Self {
handle: EmulatorHandle { handle: EmulatorHandle {
@@ -177,14 +177,16 @@ impl EmulatorController {
const STACK_SIZE: usize = 1024 * 1024 * 16; const STACK_SIZE: usize = 1024 * 1024 * 16;
let Some(Owned(mut handle)) = self.internal.take() else { let Some(Owned(mut handle)) = self.internal.take() else {
return Err(DsaError::CriticalException(String::from("owned dsa_emulator returned none!"))) return Err(DsaError::CriticalException(String::from(
"owned dsa_emulator returned none!",
)));
}; };
self.internal = Some(Handle( self.internal = Some(Handle(
thread::Builder::new() thread::Builder::new()
.stack_size(STACK_SIZE) .stack_size(STACK_SIZE)
.spawn(move || {handle.run().unwrap() }) .spawn(move || handle.run().unwrap())
.unwrap() .unwrap(),
)); ));
Ok(()) Ok(())
@@ -212,24 +214,4 @@ impl EmulatorController {
} }
self.internal = None; self.internal = None;
} }
} }
@@ -3,8 +3,8 @@ use crate::config::{IoMap, MemoryMap, RegionType};
use crate::memory::mmu::MMU; use crate::memory::mmu::MMU;
use crate::memory::ram::{MemoryBank, Page}; use crate::memory::ram::{MemoryBank, Page};
use crate::processor::interrupts::Interrupt; use crate::processor::interrupts::Interrupt;
use crate::processor::state::RunningState::Running;
use crate::processor::state::ProcessorSnapshot; use crate::processor::state::ProcessorSnapshot;
use crate::processor::state::RunningState::Running;
use common::isa::instructions::Opcode; use common::isa::instructions::Opcode;
use common::prelude::{Instruction, Register}; use common::prelude::{Instruction, Register};
@@ -36,6 +36,8 @@ impl DsaProcessor {
self.mmio_region = None; self.mmio_region = None;
self.memory_map = None; self.memory_map = None;
self.__mmap_configured = false; self.__mmap_configured = false;
self.internal_state = ProcessorSnapshot::default();
} }
pub fn new() -> Self { pub fn new() -> Self {
@@ -209,8 +211,9 @@ impl DsaProcessor {
} }
Some(Opcode::Sub) => { Some(Opcode::Sub) => {
*self.mut_reg(word.dest_uc()) = *self.mut_reg(word.dest_uc()) = self
self.reg(word.src1_uc()) - self.reg(word.src2_uc()); .reg(word.src1_uc())
.wrapping_sub(self.reg(word.src2_uc()));
} }
Some(Opcode::Shl) => { Some(Opcode::Shl) => {
@@ -228,7 +231,8 @@ impl DsaProcessor {
} }
Some(Opcode::Subi) => { Some(Opcode::Subi) => {
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc()) - word.imm16() as u32; *self.mut_reg(word.dest_uc()) =
self.reg(word.src1_uc()).wrapping_sub(word.imm16() as u32);
} }
// Utility // Utility
@@ -1,5 +1,6 @@
use crate::config::{IoMap, MemoryMap}; use crate::config::{IoMap, MemoryMap};
use crate::memory::ram::MemoryBank; use crate::memory::ram::MemoryBank;
use crate::processor::RunningState::Ready;
use crate::processor::dsa_core::{DsaProcessor, ExecutionResult}; use crate::processor::dsa_core::{DsaProcessor, ExecutionResult};
use crate::processor::interrupts::Interrupt; use crate::processor::interrupts::Interrupt;
use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown}; use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown};
@@ -7,11 +8,9 @@ use crate::processor::state::{RunningState, SharedState};
use crate::{DsaError, DsaImplementation}; use crate::{DsaError, DsaImplementation};
use common::prelude::{Instruction, Register}; use common::prelude::{Instruction, Register};
use std::hint::unlikely; use std::hint::unlikely;
use std::ops::AddAssign;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, mpsc};
use std::sync::mpsc::TryRecvError; use std::sync::mpsc::TryRecvError;
use std::thread; use std::sync::{Arc, mpsc};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
pub struct Emulator { pub struct Emulator {
@@ -53,7 +52,6 @@ impl DsaImplementation for Emulator {
// do not change anything about this loop. it's fully optimised afaik. // do not change anything about this loop. it's fully optimised afaik.
// Check for commands or hardware Interrupts every 32k cycles // Check for commands or hardware Interrupts every 32k cycles
if unlikely(self.core.internal_state.clock & 0x7FFF == 0) { if unlikely(self.core.internal_state.clock & 0x7FFF == 0) {
// Responsibility: keep the UI thread up to date periodically // Responsibility: keep the UI thread up to date periodically
// Update UI thread (roughly every 512k cycles targeting 60 UPS) // Update UI thread (roughly every 512k cycles targeting 60 UPS)
if unlikely(self.core.internal_state.clock & 0x7FFFF == 0) { if unlikely(self.core.internal_state.clock & 0x7FFFF == 0) {
@@ -62,7 +60,9 @@ impl DsaImplementation for Emulator {
self.share_state(); self.share_state();
// Acknowledge update completed // Acknowledge update completed
self.shared_state.update_requested.store(false, Ordering::Relaxed); self.shared_state
.update_requested
.store(false, Ordering::Relaxed);
} }
} }
@@ -97,12 +97,15 @@ impl DsaImplementation for Emulator {
*self.core.mut_reg(Register::Pcx) = self.core.reg(Register::Pcx).wrapping_add(4); *self.core.mut_reg(Register::Pcx) = self.core.reg(Register::Pcx).wrapping_add(4);
match self.core.execute(Instruction(instruction)) { match self.core.execute(Instruction(instruction)) {
ExecutionResult::Continue => {}, ExecutionResult::Continue => {}
ExecutionResult::Halt => self.run_state_halted(Running), ExecutionResult::Halt => self.run_state_halted(Running),
ExecutionResult::Interrupt(interrupt) => { ExecutionResult::Interrupt(interrupt) => {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
println!("Interrupt: {:?}", interrupt); println!("Interrupt: {:?}", interrupt);
println!("Interrupt while at address {:04X}", self.core.reg(Register::Pcx)); println!(
"Interrupt while at address {:04X}",
self.core.reg(Register::Pcx)
);
} }
self.core.interrupt(interrupt); self.core.interrupt(interrupt);
@@ -188,6 +191,10 @@ impl Emulator {
// Transition: pause signal received // Transition: pause signal received
if self.shared_state.pause_requested.load(Ordering::Relaxed) { if self.shared_state.pause_requested.load(Ordering::Relaxed) {
self.run_state_paused(Halted); self.run_state_paused(Halted);
if self.core.internal_state.running == Ready {
break;
}
} }
// Responsibility: update UI when requested // Responsibility: update UI when requested
@@ -195,12 +202,15 @@ impl Emulator {
self.share_state(); self.share_state();
// Acknowledge update completed // Acknowledge update completed
self.shared_state.update_requested.store(false, Ordering::Relaxed); self.shared_state
.update_requested
.store(false, Ordering::Relaxed);
} }
} }
// Responsibility: reset `private.time` on state exit // Responsibility: reset `private.time` on state exit
self.time = Instant::now(); self.time = Instant::now();
self.core.internal_state.running = prior; self.core.internal_state.running = prior;
} }
@@ -218,13 +228,19 @@ impl Emulator {
self.share_state(); self.share_state();
// Acknowledge update completed // Acknowledge update completed
self.shared_state.update_requested.store(false, Ordering::Relaxed); self.shared_state
.update_requested
.store(false, Ordering::Relaxed);
} }
// Responsibility: reset when requested by the UI // Responsibility: reset when requested by the UI
if self.shared_state.reset_requested.load(Ordering::Relaxed) == true { if self.shared_state.reset_requested.load(Ordering::Relaxed) == true {
self.reset(); self.reset();
self.shared_state.reset_requested.store(false, Ordering::Relaxed); self.shared_state
.reset_requested
.store(false, Ordering::Relaxed);
self.core.internal_state.running = Ready;
} }
// Transition: run signal received // Transition: run signal received
@@ -238,6 +254,10 @@ impl Emulator {
self.core.internal_state.time = Duration::new(0, 0); self.core.internal_state.time = Duration::new(0, 0);
self.core.internal_state.clock = 0; self.core.internal_state.clock = 0;
if self.core.internal_state.running == Ready {
return;
}
self.core.internal_state.running = prior; self.core.internal_state.running = prior;
} }
} }
+3 -3
View File
@@ -1,8 +1,7 @@
use std::sync::{Arc, atomic::AtomicBool, mpsc};
use std::sync::atomic::AtomicIsize;
use std::time::Duration;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use common::prelude::Register; use common::prelude::Register;
use std::sync::{Arc, atomic::AtomicBool, mpsc};
use std::time::Duration;
use super::interrupts::Interrupt; use super::interrupts::Interrupt;
pub struct SharedState { pub struct SharedState {
@@ -32,6 +31,7 @@ pub enum RunningState {
Paused, Paused,
Halted, Halted,
Running, Running,
Ready,
Shutdown, Shutdown,
Breakpoint(u32), Breakpoint(u32),
} }
+1
View File
@@ -19,6 +19,7 @@ dsa_tasks = { path = "../../libraries/dsa_tasks" }
zxq5_egui = { path = "../../libraries/zxq5_egui" } zxq5_egui = { path = "../../libraries/zxq5_egui" }
assembler = { path = "../../tooling/assembler" } assembler = { path = "../../tooling/assembler" }
dsfl_compiler = { path = "../../tooling/dsfl_compiler" }
dsa_emulator = { version = "0.3.0", path = "../dsa_emulator" } dsa_emulator = { version = "0.3.0", path = "../dsa_emulator" }
disassembler = { version = "0.3.0", path = "../../tooling/disassembler" } disassembler = { version = "0.3.0", path = "../../tooling/disassembler" }
+71 -61
View File
@@ -1,9 +1,10 @@
use crate::ui::ui_helpers::StateWrapper;
use common::prelude::Register; use common::prelude::Register;
use dsa_emulator::EmulatorHandle; use dsa_emulator::EmulatorHandle;
use dsa_emulator::processor::RunningState; use dsa_emulator::processor::RunningState;
use dsa_emulator::processor::RunningState::Ready;
use eframe::epaint::Color32; use eframe::epaint::Color32;
use zxq5_egui::buttons::PaddedWidget; use zxq5_egui::buttons::PaddedWidget;
use crate::ui::ui_helpers::StateWrapper;
pub struct Controller { pub struct Controller {
emulator: EmulatorHandle, emulator: EmulatorHandle,
@@ -11,82 +12,91 @@ pub struct Controller {
impl Controller { impl Controller {
pub fn new(state: EmulatorHandle) -> Self { pub fn new(state: EmulatorHandle) -> Self {
Self { Self { emulator: state }
emulator: state,
}
} }
pub fn ui(&mut self, ui: &mut egui::Ui) { pub fn ui(&mut self, ui: &mut egui::Ui) {
self.emulator.request_update(); self.emulator.request_update();
egui::Frame::new() egui::Frame::new().inner_margin(10.0).show(ui, |ui| {
.inner_margin(10.0) ui.horizontal(|ui| {
.show(ui, |ui| { use RunningState::*;
ui.horizontal(|ui| { let status = self.emulator.status();
use RunningState::*;
let status = self.emulator.status();
match status { match status {
Paused | Breakpoint(_) => Paused | Breakpoint(_) | Ready => {
if PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui).clicked() { if PaddedWidget::button("▶ Run")
self.emulator.request_resume() .color(Color32::GREEN)
} .ui(ui)
Running | Halted => .clicked()
if PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui).clicked() { {
self.emulator.request_pause() self.emulator.request_resume()
}
Shutdown => {
ui.add_enabled_ui(false, |ui| {
PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui);
});
} }
} }
Running | Halted => {
PaddedWidget::indicator(StateWrapper(status)).ui(ui); if PaddedWidget::button("⏸ Pause")
.color(Color32::YELLOW)
if PaddedWidget::button("⟳ Reset All").color(Color32::ORANGE).ui(ui).clicked() { .ui(ui)
self.emulator.when_paused(|emu| emu.reset()); .clicked()
{
self.emulator.request_pause()
}
} }
Shutdown => {
ui.add_enabled_ui(false, |ui| {
PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui);
});
}
}
// Step PaddedWidget::indicator(StateWrapper(status)).ui(ui);
// if ui.button("Step").clicked() {
// state
// .cmd_sender
// .send(Command::Step(self.step_amount))
// .unwrap_or_else(|_| {
// state.error_log.push("Failed to send command".to_string());
// });
// }
// if ui if PaddedWidget::button("⟳ Reset All")
// .text_edit_singleline(&mut self.step_amount_input) .color(Color32::ORANGE)
// .changed() .ui(ui)
// { .clicked()
// self.step_amount = if let Ok(amount) = self.step_amount_input.parse() { {
// amount self.emulator.when_paused(|emu| emu.reset());
// } else { }
// state
// .error_log
// .push("Unable to parse step amount".to_string());
// 1
// }
// }
ui.separator(); // Step
// if ui.button("Step").clicked() {
// state
// .cmd_sender
// .send(Command::Step(self.step_amount))
// .unwrap_or_else(|_| {
// state.error_log.push("Failed to send command".to_string());
// });
// }
let clock = self.emulator.clock(); // if ui
PaddedWidget::label(format!("Clock: {clock}")).ui(ui); // .text_edit_singleline(&mut self.step_amount_input)
// .changed()
// {
// self.step_amount = if let Ok(amount) = self.step_amount_input.parse() {
// amount
// } else {
// state
// .error_log
// .push("Unable to parse step amount".to_string());
// 1
// }
// }
let pcx = self.emulator.get_register(Register::Pcx); ui.separator();
PaddedWidget::label( format!("Pcx: 0x{pcx:08X}")).ui(ui);
let time = self.emulator.time().as_micros(); let clock = self.emulator.clock();
PaddedWidget::label( format!("Elapsed: {} micros", time)).ui(ui); PaddedWidget::label(format!("Clock: {clock}")).ui(ui);
let mips = (clock as u128).checked_div(time).unwrap_or(0); let pcx = self.emulator.get_register(Register::Pcx);
PaddedWidget::label(format!("Freq: {}MHz", mips)).ui(ui); PaddedWidget::label(format!("Pcx: 0x{pcx:08X}")).ui(ui);
});
let time = self.emulator.time().as_micros();
PaddedWidget::label(format!("Elapsed: {} micros", time)).ui(ui);
let mips = (clock as u128).checked_div(time).unwrap_or(0);
PaddedWidget::label(format!("Freq: {}MHz", mips)).ui(ui);
}); });
});
} }
} }
+4 -2
View File
@@ -44,13 +44,14 @@ impl ProcessHandle {
id: usize, id: usize,
name: impl Into<String>, name: impl Into<String>,
process: P, process: P,
input: P::Input,
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static, on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static,
) -> Self { ) -> Self {
let (tx, rx) = mpsc::channel::<String>(); let (tx, rx) = mpsc::channel::<String>();
let agent = ProcessAgent::new(tx); let agent = ProcessAgent::new(tx);
let join_handle = std::thread::spawn(move || { let join_handle = std::thread::spawn(move || {
let result = TraceableProcess::run(Box::new(process), agent); let result = TraceableProcess::run(Box::new(process), input, agent);
on_complete(result) on_complete(result)
}); });
@@ -92,9 +93,10 @@ impl ProcessManager {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
process: P, process: P,
input: P::Input,
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static, on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static,
) { ) {
let handle = ProcessHandle::spawn(self.new_id(), name, process, on_complete); let handle = ProcessHandle::spawn(self.new_id(), name, process, input, on_complete);
self.handles.push(handle); self.handles.push(handle);
} }
+17 -11
View File
@@ -67,12 +67,13 @@ impl Editor {
&mut self.buffer &mut self.buffer
} }
pub fn get_build_command(filename: PathBuf) -> impl TraceableProcess { // pub fn get_build_command(file_path: PathBuf) -> impl TraceableProcess {
match filename.extension().unwrap().to_str().unwrap() { // let filename = file_path.file_name().unwrap().to_str().unwrap();
"dsa" => Assembler::new(filename), // match file_path.extension().unwrap().to_str().unwrap() {
_ => todo!(), // "dsa" => Assembler::new(filename),
} // "dsfl" => DsflCompiler::new(filename),
} // }
// }
pub fn create_build_action( pub fn create_build_action(
&self, &self,
@@ -80,11 +81,16 @@ impl Editor {
on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static, on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static,
) -> Action { ) -> Action {
ProcessSpawner::new(move |pm| { ProcessSpawner::new(move |pm| {
let builder = Assembler::new(filename); let builder = Assembler::new(filename.file_name().unwrap().to_str().unwrap());
pm.spawn(builder.label(), builder, move |result| match result { pm.spawn(
Ok(bin) => on_complete(bin), builder.label(),
Err(e) => Action::ShowError(e.to_string()), builder,
}); filename,
move |result| match result {
Ok(bin) => on_complete(bin),
Err(e) => Action::ShowError(e.to_string()),
},
);
}) })
.into_action() .into_action()
} }
+1 -21
View File
@@ -76,23 +76,15 @@ impl Behavior<Pane> for TileBehavior {
} }
fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, tile_id: TileId) -> bool { fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, tile_id: TileId) -> bool {
println!("on_tab_close_1");
let Some(Tile::Pane(pane)) = tiles.get_mut(tile_id) else { let Some(Tile::Pane(pane)) = tiles.get_mut(tile_id) else {
println!("on_tab_close_ret1");
return false; return false;
}; };
println!("on_tab_close_2");
if pane.close_requires_confirmation(tile_id) { if pane.close_requires_confirmation(tile_id) {
println!("on_tab_close_ret2");
self.pending_action = Some(RequestCloseTab(tile_id)); self.pending_action = Some(RequestCloseTab(tile_id));
return false; return false;
} }
println!("on_tab_close_3");
tiles.remove(tile_id); tiles.remove(tile_id);
true true
} }
@@ -220,20 +212,8 @@ impl CentralTiles {
} }
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<Action> { pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<Action> {
println!("centraltiles_ui_1");
// self.behavior.pending_action = None;
self.tree.ui(&mut self.behavior, ui); self.tree.ui(&mut self.behavior, ui);
self.behavior.pending_action.take()
println!("centraltiles_ui_2");
let action = self.behavior.pending_action.take();
println!("centraltiles_ui_3 {action:?}");
action
// self.behavior.pending_action.take()
} }
} }
+2 -2
View File
@@ -8,7 +8,7 @@ pub struct StateWrapper(pub RunningState);
impl StatusIndicator for StateWrapper { impl StatusIndicator for StateWrapper {
fn color(&self) -> egui::Color32 { fn color(&self) -> egui::Color32 {
match self.0 { match self.0 {
RunningState::Running => egui::Color32::GREEN, RunningState::Ready | RunningState::Running => egui::Color32::GREEN,
RunningState::Paused => egui::Color32::YELLOW, RunningState::Paused => egui::Color32::YELLOW,
RunningState::Halted => egui::Color32::ORANGE, RunningState::Halted => egui::Color32::ORANGE,
RunningState::Breakpoint(_) => egui::Color32::RED, RunningState::Breakpoint(_) => egui::Color32::RED,
@@ -18,6 +18,7 @@ impl StatusIndicator for StateWrapper {
fn label(&self) -> String { fn label(&self) -> String {
match self.0 { match self.0 {
RunningState::Ready => "Ready".to_string(),
RunningState::Running => "Running".into(), RunningState::Running => "Running".into(),
RunningState::Paused => "Paused".into(), RunningState::Paused => "Paused".into(),
RunningState::Halted => "Halted".into(), RunningState::Halted => "Halted".into(),
@@ -26,4 +27,3 @@ impl StatusIndicator for StateWrapper {
} }
} }
} }