change messages / add emulator pause / refactor threading for gb_emu
This commit is contained in:
parent
7cfe34fe82
commit
0a66d2cb86
7 changed files with 248 additions and 212 deletions
|
@ -105,7 +105,7 @@ impl Debugger {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.core.run();
|
self.core.run(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_pause(&mut self) -> bool {
|
fn should_pause(&mut self) -> bool {
|
||||||
|
|
|
@ -92,38 +92,26 @@ impl Default for StandaloneConfig {
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
EmulatorHandler::run(args);
|
run(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EmulatorTypes {
|
fn run(args: Args) -> ! {
|
||||||
Debug(Debugger),
|
|
||||||
Normal(Box<dyn EmulatorCoreTrait>),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EmulatorHandler {
|
|
||||||
emu: EmulatorTypes,
|
|
||||||
window_manager: WindowManager,
|
|
||||||
since: Instant,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EmulatorHandler {
|
|
||||||
fn run(args: Args) -> ! {
|
|
||||||
let (sender, receiver) = channel::<EmulatorMessage>();
|
let (sender, receiver) = channel::<EmulatorMessage>();
|
||||||
|
|
||||||
{
|
{
|
||||||
let sender = sender.clone();
|
let sender = sender.clone();
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
sender.send(EmulatorMessage::Stop).unwrap();
|
sender.send(EmulatorMessage::Exit).unwrap();
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let config_manager = ConfigManager::get().expect("Could not open config folder");
|
let config_manager = ConfigManager::get().expect("Could not open config folder");
|
||||||
let config = config_manager.load_or_create_base_config();
|
let config = config_manager.load_or_create_base_config();
|
||||||
let standalone_config: StandaloneConfig =
|
let standalone_config: StandaloneConfig = config_manager.load_or_create_config("standalone");
|
||||||
config_manager.load_or_create_config("standalone");
|
|
||||||
|
let (output, stream) = audio::create_output(args.mute);
|
||||||
|
|
||||||
let (output, _stream) = audio::create_output(args.mute);
|
|
||||||
let rom_file = RomFile::Path(PathBuf::from(args.rom));
|
let rom_file = RomFile::Path(PathBuf::from(args.rom));
|
||||||
|
|
||||||
let (rom, camera) = rom_file
|
let (rom, camera) = rom_file
|
||||||
|
@ -159,7 +147,7 @@ impl EmulatorHandler {
|
||||||
}
|
}
|
||||||
.unwrap_or(standalone_config.scale_factor);
|
.unwrap_or(standalone_config.scale_factor);
|
||||||
|
|
||||||
let mut window_manager = WindowManager::new(sender);
|
let mut window_manager = WindowManager::new(sender, stream);
|
||||||
|
|
||||||
let window = window_manager.add(
|
let window = window_manager.add(
|
||||||
scale_override,
|
scale_override,
|
||||||
|
@ -199,39 +187,36 @@ impl EmulatorHandler {
|
||||||
// Box::new(EmulatorCore::init(receiver, options, NoCamera::default()))
|
// Box::new(EmulatorCore::init(receiver, options, NoCamera::default()))
|
||||||
// };
|
// };
|
||||||
|
|
||||||
#[cfg(not(feature = "camera"))]
|
// #[cfg(not(feature = "camera"))]
|
||||||
let core: Box<dyn EmulatorCoreTrait> =
|
// let core: Box<dyn EmulatorCoreTrait> =
|
||||||
Box::new(EmulatorCore::init(receiver, options, camera));
|
// Box::new(EmulatorCore::init(receiver, options, camera));
|
||||||
#[cfg(feature = "camera")]
|
// #[cfg(feature = "camera")]
|
||||||
let core = Box::new(EmulatorCore::init(receiver, options, Webcam::new()));
|
// let core = Box::new(EmulatorCore::init(receiver, options, Webcam::new()));
|
||||||
|
|
||||||
let emu = if args.debug {
|
// let emu = if args.debug {
|
||||||
EmulatorTypes::Debug(Debugger::new(core))
|
// EmulatorTypes::Debug(Debugger::new(core))
|
||||||
} else {
|
// } else {
|
||||||
EmulatorTypes::Normal(core)
|
// EmulatorTypes::Normal(core)
|
||||||
};
|
// };
|
||||||
|
|
||||||
let since = Instant::now();
|
let mut core = EmulatorCore::init(true, receiver, options, camera);
|
||||||
|
|
||||||
let mut h = Self {
|
if args.debug {
|
||||||
emu,
|
let mut debugger = Debugger::new(Box::new(core));
|
||||||
window_manager,
|
let mut since = Instant::now();
|
||||||
since,
|
|
||||||
};
|
|
||||||
loop {
|
loop {
|
||||||
h.cycle();
|
if since.elapsed() >= UPDATE_INTERVAL {
|
||||||
|
window_manager.update_events();
|
||||||
|
since = Instant::now();
|
||||||
}
|
}
|
||||||
|
debugger.step();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
std::thread::spawn(move || loop {
|
||||||
|
core.run(10000);
|
||||||
|
});
|
||||||
|
|
||||||
fn cycle(&mut self) {
|
window_manager.run_events_blocking();
|
||||||
if self.since.elapsed() >= UPDATE_INTERVAL {
|
|
||||||
self.window_manager.update_events();
|
|
||||||
self.since = Instant::now();
|
|
||||||
}
|
|
||||||
match self.emu {
|
|
||||||
EmulatorTypes::Debug(ref mut debugger) => debugger.step(),
|
|
||||||
EmulatorTypes::Normal(ref mut core) => core.run(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::{
|
||||||
sync::{mpsc::Sender, Arc, Mutex, RwLock},
|
sync::{mpsc::Sender, Arc, Mutex, RwLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use cpal::Stream;
|
||||||
use gb_emu_lib::{
|
use gb_emu_lib::{
|
||||||
connect::{EmulatorMessage, JoypadState, Renderer, ResolutionData},
|
connect::{EmulatorMessage, JoypadState, Renderer, ResolutionData},
|
||||||
renderer::{RendererBackend, RendererBackendManager, WindowOptions},
|
renderer::{RendererBackend, RendererBackendManager, WindowOptions},
|
||||||
|
@ -17,7 +18,7 @@ use raw_window_handle::HasRawDisplayHandle;
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::PhysicalSize,
|
dpi::PhysicalSize,
|
||||||
event::{Event, VirtualKeyCode, WindowEvent},
|
event::{Event, VirtualKeyCode, WindowEvent},
|
||||||
event_loop::EventLoop,
|
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||||
platform::run_return::EventLoopExtRunReturn,
|
platform::run_return::EventLoopExtRunReturn,
|
||||||
window::{Window, WindowBuilder, WindowId},
|
window::{Window, WindowBuilder, WindowId},
|
||||||
};
|
};
|
||||||
|
@ -36,14 +37,19 @@ struct WindowData {
|
||||||
|
|
||||||
pub struct WindowManager {
|
pub struct WindowManager {
|
||||||
event_loop: EventLoop<()>,
|
event_loop: EventLoop<()>,
|
||||||
|
data: WindowManagerData,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WindowManagerData {
|
||||||
windows: HashMap<WindowId, Arc<WindowData>>,
|
windows: HashMap<WindowId, Arc<WindowData>>,
|
||||||
window_data_manager: Arc<RendererBackendManager>,
|
window_data_manager: Arc<RendererBackendManager>,
|
||||||
input: Arc<Mutex<WinitInputHelper>>,
|
input: Arc<Mutex<WinitInputHelper>>,
|
||||||
sender: Sender<EmulatorMessage>,
|
sender: Sender<EmulatorMessage>,
|
||||||
|
_stream: Stream,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowManager {
|
impl WindowManager {
|
||||||
pub(crate) fn new(sender: Sender<EmulatorMessage>) -> Self {
|
pub(crate) fn new(sender: Sender<EmulatorMessage>, _stream: Stream) -> Self {
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
#[cfg(feature = "vulkan")]
|
#[cfg(feature = "vulkan")]
|
||||||
let window_data_manager =
|
let window_data_manager =
|
||||||
|
@ -52,10 +58,13 @@ impl WindowManager {
|
||||||
let window_data_manager = Arc::new(RendererBackendManager::new());
|
let window_data_manager = Arc::new(RendererBackendManager::new());
|
||||||
Self {
|
Self {
|
||||||
event_loop,
|
event_loop,
|
||||||
|
data: WindowManagerData {
|
||||||
windows: HashMap::new(),
|
windows: HashMap::new(),
|
||||||
window_data_manager,
|
window_data_manager,
|
||||||
input: Arc::new(Mutex::new(WinitInputHelper::new())),
|
input: Arc::new(Mutex::new(WinitInputHelper::new())),
|
||||||
sender,
|
sender,
|
||||||
|
_stream,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,12 +74,43 @@ impl WindowManager {
|
||||||
gamepad_handler: Option<Gilrs>,
|
gamepad_handler: Option<Gilrs>,
|
||||||
shader_path: Option<PathBuf>,
|
shader_path: Option<PathBuf>,
|
||||||
resizable: bool,
|
resizable: bool,
|
||||||
|
) -> WindowRenderer {
|
||||||
|
self.data.add(
|
||||||
|
factor,
|
||||||
|
gamepad_handler,
|
||||||
|
shader_path,
|
||||||
|
resizable,
|
||||||
|
&self.event_loop,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_events(&mut self) {
|
||||||
|
self.event_loop.run_return(|event, target, control_flow| {
|
||||||
|
self.data.handler(true, event, target, control_flow)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_events_blocking(self) -> ! {
|
||||||
|
self.event_loop.run(move |event, target, control_flow| {
|
||||||
|
self.data.handler(false, event, target, control_flow)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowManagerData {
|
||||||
|
fn add(
|
||||||
|
&mut self,
|
||||||
|
factor: usize,
|
||||||
|
gamepad_handler: Option<Gilrs>,
|
||||||
|
shader_path: Option<PathBuf>,
|
||||||
|
resizable: bool,
|
||||||
|
event_loop: &EventLoop<()>,
|
||||||
) -> WindowRenderer {
|
) -> WindowRenderer {
|
||||||
let (r, info) = WindowRenderer::new(
|
let (r, info) = WindowRenderer::new(
|
||||||
factor,
|
factor,
|
||||||
gamepad_handler,
|
gamepad_handler,
|
||||||
self.input.clone(),
|
self.input.clone(),
|
||||||
&self.event_loop,
|
event_loop,
|
||||||
self.window_data_manager.clone(),
|
self.window_data_manager.clone(),
|
||||||
shader_path,
|
shader_path,
|
||||||
resizable,
|
resizable,
|
||||||
|
@ -79,23 +119,34 @@ impl WindowManager {
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_events(&mut self) {
|
fn handler(
|
||||||
self.event_loop.run_return(|event, _, control_flow| {
|
&self,
|
||||||
|
run_return: bool,
|
||||||
|
event: Event<'_, ()>,
|
||||||
|
_target: &EventLoopWindowTarget<()>,
|
||||||
|
control_flow: &mut ControlFlow,
|
||||||
|
) {
|
||||||
control_flow.set_wait();
|
control_flow.set_wait();
|
||||||
|
|
||||||
if let Ok(mut i) = self.input.lock() {
|
if let Ok(mut i) = self.input.lock() {
|
||||||
i.update(&event);
|
i.update(&event);
|
||||||
}
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
Event::Resumed => {
|
||||||
|
self.sender.send(EmulatorMessage::Start).unwrap();
|
||||||
|
}
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event: WindowEvent::CloseRequested,
|
event: WindowEvent::CloseRequested,
|
||||||
window_id: _,
|
window_id: _,
|
||||||
} => {
|
} => {
|
||||||
self.sender.send(EmulatorMessage::Stop).unwrap();
|
self.sender.send(EmulatorMessage::Exit).unwrap();
|
||||||
}
|
}
|
||||||
Event::MainEventsCleared => {
|
Event::MainEventsCleared => {
|
||||||
|
if run_return {
|
||||||
control_flow.set_exit();
|
control_flow.set_exit();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Event::RedrawRequested(window_id) => {
|
Event::RedrawRequested(window_id) => {
|
||||||
if let Some(w) = self.windows.get(&window_id) {
|
if let Some(w) = self.windows.get(&window_id) {
|
||||||
if let Ok(mut renderer) = w.renderer.lock() {
|
if let Ok(mut renderer) = w.renderer.lock() {
|
||||||
|
@ -116,7 +167,6 @@ impl WindowManager {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -329,7 +329,7 @@ impl Plugin for GameboyEmu {
|
||||||
.with_sram_buffer(self.params.sram_save.state.clone())
|
.with_sram_buffer(self.params.sram_save.state.clone())
|
||||||
.with_show_bootrom(!will_skip_bootrom);
|
.with_show_bootrom(!will_skip_bootrom);
|
||||||
|
|
||||||
EmulatorCore::init(receiver, options, camera)
|
EmulatorCore::init(false, receiver, options, camera)
|
||||||
};
|
};
|
||||||
|
|
||||||
emulator_core.run_until_buffer_full();
|
emulator_core.run_until_buffer_full();
|
||||||
|
@ -346,9 +346,8 @@ impl Plugin for GameboyEmu {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivate(&mut self) {
|
fn deactivate(&mut self) {
|
||||||
eprintln!("DEACTIVATE FUNCTION");
|
|
||||||
if let Some(ref mut vars) = self.vars {
|
if let Some(ref mut vars) = self.vars {
|
||||||
match vars.sender.send(EmulatorMessage::Stop) {
|
match vars.sender.send(EmulatorMessage::Exit) {
|
||||||
Ok(_) => self.vars = None,
|
Ok(_) => self.vars = None,
|
||||||
Err(e) => nih_log!("error {e} sending message to emulator"),
|
Err(e) => nih_log!("error {e} sending message to emulator"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,11 @@ use crate::processor::memory::Rom;
|
||||||
pub use crate::{HEIGHT, WIDTH};
|
pub use crate::{HEIGHT, WIDTH};
|
||||||
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
|
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum EmulatorMessage {
|
pub enum EmulatorMessage {
|
||||||
Stop,
|
Start,
|
||||||
|
Pause,
|
||||||
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -356,8 +359,7 @@ pub trait EmulatorCoreTrait {
|
||||||
fn pc(&self) -> u16;
|
fn pc(&self) -> u16;
|
||||||
fn print_reg(&self) -> String;
|
fn print_reg(&self) -> String;
|
||||||
fn get_memory(&self, address: u16) -> u8;
|
fn get_memory(&self, address: u16) -> u8;
|
||||||
fn run(&mut self);
|
fn run(&mut self, cycles: usize);
|
||||||
fn run_stepped(&mut self, step_size: usize);
|
|
||||||
fn run_until_buffer_full(&mut self);
|
fn run_until_buffer_full(&mut self);
|
||||||
fn process_messages(&mut self);
|
fn process_messages(&mut self);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
#![feature(exclusive_range_pattern, let_chains, bigint_helper_methods)]
|
#![feature(exclusive_range_pattern, let_chains, bigint_helper_methods)]
|
||||||
|
|
||||||
use crate::{
|
use crate::processor::{memory::Memory, Flags};
|
||||||
processor::{memory::Memory, Flags},
|
|
||||||
util::pause,
|
|
||||||
};
|
|
||||||
use connect::{
|
use connect::{
|
||||||
AudioOutput, CameraWrapper, EmulatorCoreTrait, EmulatorMessage, EmulatorOptions, PocketCamera,
|
AudioOutput, CameraWrapper, EmulatorCoreTrait, EmulatorMessage, EmulatorOptions, PocketCamera,
|
||||||
Renderer, RomFile,
|
Renderer, RomFile,
|
||||||
|
@ -13,7 +10,6 @@ use processor::{
|
||||||
Cpu,
|
Cpu,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdout, Write},
|
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
process::exit,
|
process::exit,
|
||||||
sync::{mpsc::Receiver, Arc, Mutex},
|
sync::{mpsc::Receiver, Arc, Mutex},
|
||||||
|
@ -46,6 +42,7 @@ where
|
||||||
{
|
{
|
||||||
receiver: Receiver<EmulatorMessage>,
|
receiver: Receiver<EmulatorMessage>,
|
||||||
cpu: Cpu<ColourFormat, R, C>,
|
cpu: Cpu<ColourFormat, R, C>,
|
||||||
|
paused: bool,
|
||||||
spooky: PhantomData<C>,
|
spooky: PhantomData<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +53,7 @@ where
|
||||||
C: PocketCamera + Send + 'static,
|
C: PocketCamera + Send + 'static,
|
||||||
{
|
{
|
||||||
pub fn init(
|
pub fn init(
|
||||||
|
paused: bool,
|
||||||
receiver: Receiver<EmulatorMessage>,
|
receiver: Receiver<EmulatorMessage>,
|
||||||
mut options: EmulatorOptions<ColourFormat, R, C>,
|
mut options: EmulatorOptions<ColourFormat, R, C>,
|
||||||
camera: Arc<Mutex<CameraWrapper<C>>>,
|
camera: Arc<Mutex<CameraWrapper<C>>>,
|
||||||
|
@ -84,6 +82,7 @@ where
|
||||||
));
|
));
|
||||||
|
|
||||||
Self::new(
|
Self::new(
|
||||||
|
paused,
|
||||||
receiver,
|
receiver,
|
||||||
Cpu::new(
|
Cpu::new(
|
||||||
Memory::init(
|
Memory::init(
|
||||||
|
@ -104,10 +103,15 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(receiver: Receiver<EmulatorMessage>, cpu: Cpu<ColourFormat, R, C>) -> Self {
|
fn new(
|
||||||
|
paused: bool,
|
||||||
|
receiver: Receiver<EmulatorMessage>,
|
||||||
|
cpu: Cpu<ColourFormat, R, C>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
receiver,
|
receiver,
|
||||||
cpu,
|
cpu,
|
||||||
|
paused,
|
||||||
spooky: PhantomData,
|
spooky: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,14 +148,24 @@ where
|
||||||
|
|
||||||
fn process_messages(&mut self) {
|
fn process_messages(&mut self) {
|
||||||
while let Ok(msg) = self.receiver.try_recv() {
|
while let Ok(msg) = self.receiver.try_recv() {
|
||||||
#[allow(clippy::single_match, unreachable_patterns)]
|
self.process_message(msg);
|
||||||
|
}
|
||||||
|
while self.paused {
|
||||||
|
match self.receiver.recv() {
|
||||||
|
Ok(msg) => self.process_message(msg),
|
||||||
|
Err(e) => panic!("no message sender! error {e:#?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_message(&mut self, msg: EmulatorMessage) {
|
||||||
match msg {
|
match msg {
|
||||||
EmulatorMessage::Stop => {
|
EmulatorMessage::Exit => {
|
||||||
self.cpu.memory.flush_rom();
|
self.cpu.memory.flush_rom();
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
_ => {}
|
EmulatorMessage::Start => self.paused = false,
|
||||||
}
|
EmulatorMessage::Pause => self.paused = true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,25 +207,19 @@ where
|
||||||
self.cpu.memory.get(address)
|
self.cpu.memory.get(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&mut self) {
|
fn run(&mut self, cycles: usize) {
|
||||||
self.process_messages();
|
self.process_messages();
|
||||||
|
if !self.paused {
|
||||||
|
for _ in 0..cycles {
|
||||||
self.run_cycle();
|
self.run_cycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_stepped(&mut self, step_size: usize) {
|
|
||||||
loop {
|
|
||||||
self.process_messages();
|
|
||||||
for _ in 0..step_size {
|
|
||||||
self.run_cycle();
|
|
||||||
}
|
|
||||||
stdout().flush().unwrap();
|
|
||||||
pause();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_until_buffer_full(&mut self) {
|
fn run_until_buffer_full(&mut self) {
|
||||||
|
self.process_messages();
|
||||||
while !self.cpu.memory.is_audio_buffer_full() {
|
while !self.cpu.memory.is_audio_buffer_full() {
|
||||||
self.run();
|
self.run_cycle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
use num_traits::{PrimInt, Unsigned};
|
use num_traits::{PrimInt, Unsigned};
|
||||||
|
|
||||||
use crate::processor::{memory::mmio::gpu::Colour, Direction};
|
use crate::processor::{memory::mmio::gpu::Colour, Direction};
|
||||||
use std::{io, mem::transmute};
|
use std::mem::transmute;
|
||||||
|
|
||||||
pub(crate) fn pause() -> String {
|
|
||||||
let mut line = String::new();
|
|
||||||
match io::stdin().read_line(&mut line) {
|
|
||||||
Ok(_) => line,
|
|
||||||
Err(_) => String::from(""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn as_signed(unsigned: u8) -> i8 {
|
pub(crate) fn as_signed(unsigned: u8) -> i8 {
|
||||||
unsafe { transmute(unsigned) }
|
unsafe { transmute(unsigned) }
|
||||||
|
|
Loading…
Add table
Reference in a new issue