From bfb615cfe797ce693b68624bdf5b61bbf64792cc Mon Sep 17 00:00:00 2001 From: Trevor Merritt Date: Sat, 12 Oct 2024 10:51:57 -0400 Subject: [PATCH] more dump of "Emma" for Gemma Video reset now tests ok gemma egui interface now implements stupid workaround for video reset --- Cargo.lock | 215 +----- Cargo.toml | 1 - coverage/tarpaulin-report.html | 671 ------------------ gemma/Cargo.toml | 10 +- gemma/src/chip8/computer.rs | 20 +- gemma/src/chip8/instructions.rs | 17 +- gemma/src/chip8/stack.rs | 4 +- gemma/src/chip8/video.rs | 121 ++-- gemma/tests/computer_tests.rs | 21 +- gemmaegui/src/bin/gemmaegui.rs | 21 +- gemmaegui/src/bin/support/GemmaEGuiSupport.rs | 75 -- .../{EmmaEGuiState.rs => gemma_egui_state.rs} | 8 +- .../src/bin/support/gemma_egui_support.rs | 129 ++++ gemmaegui/src/bin/support/mod.rs | 4 +- gemmaimgui/src/bin/gemmaimgui.rs | 24 +- gemmaimgui/src/bin/support/emmagui_support.rs | 14 +- gemmaimgui/src/bin/support/ui_state.rs | 15 +- resources/test/test_reset_clears_video.asc | 32 + .../test/test_video_write_checkerboard.asc | 32 + 19 files changed, 352 insertions(+), 1082 deletions(-) delete mode 100644 coverage/tarpaulin-report.html delete mode 100644 gemmaegui/src/bin/support/GemmaEGuiSupport.rs rename gemmaegui/src/bin/support/{EmmaEGuiState.rs => gemma_egui_state.rs} (57%) create mode 100644 gemmaegui/src/bin/support/gemma_egui_support.rs create mode 100644 resources/test/test_reset_clears_video.asc create mode 100644 resources/test/test_video_write_checkerboard.asc diff --git a/Cargo.lock b/Cargo.lock index accce1b..f29271f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,21 +692,6 @@ dependencies = [ "wayland-client 0.31.5", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" version = "1.1.6" @@ -901,20 +886,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "compact_str" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1069,31 +1040,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags 2.6.0", - "crossterm_winapi", - "mio 1.0.2", - "parking_lot", - "rustix", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -1649,20 +1595,11 @@ dependencies = [ name = "gemma" version = "0.1.0" dependencies = [ - "beep", "chrono", - "copypasta", "dimensioned", - "glium", - "image 0.23.14", - "imgui", - "imgui-glium-renderer", - "imgui-winit-support", "log", "pretty_env_logger", "rand 0.9.0-alpha.2", - "ratatui", - "winit 0.27.5", ] [[package]] @@ -2013,18 +1950,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.4.0" @@ -2203,16 +2128,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "instability" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" -dependencies = [ - "quote", - "syn 2.0.71", -] - [[package]] name = "instant" version = "0.1.13" @@ -2231,26 +2146,11 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi", "libc", "windows-sys 0.52.0", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - [[package]] name = "jni" version = "0.21.1" @@ -2384,15 +2284,6 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -[[package]] -name = "lru" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" -dependencies = [ - "hashbrown", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -2506,19 +2397,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "log", - "wasi", - "windows-sys 0.52.0", -] - [[package]] name = "naga" version = "22.1.0" @@ -3200,7 +3078,7 @@ checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", "rustix", "tracing", @@ -3356,27 +3234,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "ratatui" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba6a365afbe5615999275bea2446b970b10a41102500e27ce7678d50d978303" -dependencies = [ - "bitflags 2.6.0", - "cassowary", - "compact_str", - "crossterm", - "instability", - "itertools", - "lru", - "paste", - "strum", - "strum_macros", - "unicode-segmentation", - "unicode-truncate", - "unicode-width", -] - [[package]] name = "raw-window-handle" version = "0.4.3" @@ -3505,18 +3362,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - [[package]] name = "safe_arch" version = "0.5.2" @@ -3654,27 +3499,6 @@ dependencies = [ "digest", ] -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" -dependencies = [ - "libc", - "mio 1.0.2", - "signal-hook", -] - [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3840,28 +3664,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.71", -] - [[package]] name = "syn" version = "1.0.109" @@ -4126,17 +3928,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" -[[package]] -name = "unicode-truncate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" -dependencies = [ - "itertools", - "unicode-segmentation", - "unicode-width", -] - [[package]] name = "unicode-width" version = "0.1.13" @@ -5031,7 +4822,7 @@ dependencies = [ "libc", "log", "mint", - "mio 0.8.11", + "mio", "ndk 0.7.0", "ndk-glue", "objc", diff --git a/Cargo.toml b/Cargo.toml index 8f72452..11f8f25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,3 @@ [workspace] members = ["gemma", "gemmaegui", "gemmaimgui" ] resolver = "2" - diff --git a/coverage/tarpaulin-report.html b/coverage/tarpaulin-report.html deleted file mode 100644 index ca41565..0000000 --- a/coverage/tarpaulin-report.html +++ /dev/null @@ -1,671 +0,0 @@ - - - - - - - -
- - - - - - \ No newline at end of file diff --git a/gemma/Cargo.toml b/gemma/Cargo.toml index 288e83b..e92eebc 100644 --- a/gemma/Cargo.toml +++ b/gemma/Cargo.toml @@ -5,17 +5,9 @@ edition = "2021" autobenches = true [dependencies] -ratatui = "0.28.0" -glium = { version = "0.34.0", default-features = true } -image = "0.23" -imgui = { version ="0.12.0", features = ["tables-api"] } -imgui-glium-renderer = { version = "0.12.0" } -imgui-winit-support = { version = "0.12.0" } -winit = { version = "0.27", features = ["x11", "mint"] } pretty_env_logger = "0.5.0" -copypasta = "0.8" rand = "0.9.0-alpha.2" log = "0.4.22" -beep = "0.3.0" +# beep = "0.3.0" chrono = "0.4.38" dimensioned = "0.8.0" diff --git a/gemma/src/chip8/computer.rs b/gemma/src/chip8/computer.rs index 45c03d6..40af58c 100644 --- a/gemma/src/chip8/computer.rs +++ b/gemma/src/chip8/computer.rs @@ -37,7 +37,9 @@ impl Default for Chip8Computer { impl Chip8Computer { pub fn reset(&mut self) -> Self{ - Self::default() + let mut working = Chip8Computer::new(); + working.video_memory.reset(); + working } pub fn dump_keypad_to_string(&self) -> String { @@ -67,7 +69,7 @@ impl Chip8Computer { Chip8Computer::default() } - pub fn load_bytes_to_memory(&mut self, offset: u16, to_load: Box>) { + pub fn load_bytes_to_memory(&mut self, offset: u16, to_load: &Vec) { let total_len = to_load.len() as u16; for current_index in 0..total_len { let new_value = to_load[current_index as usize]; @@ -103,17 +105,3 @@ impl Chip8Computer { self } } - -#[cfg(test)] -mod test { - use rand::random; - use crate::constants::CHIP8_VIDEO_MEMORY; - use super::*; - - #[test] - fn smoke() { - assert!(true) - } - -} - diff --git a/gemma/src/chip8/instructions.rs b/gemma/src/chip8/instructions.rs index 1032ecd..bbeb528 100644 --- a/gemma/src/chip8/instructions.rs +++ b/gemma/src/chip8/instructions.rs @@ -1,4 +1,4 @@ -use std::ops::{BitAnd, Shl, Shr}; +use std::ops::{BitAnd, Shr}; use log::debug; use rand::random; use crate::chip8::computer::{Chip8Computer}; @@ -596,7 +596,7 @@ impl Chip8CpuInstructions { 1 }; - println!("SUB CARRY -> REGISTER 1 = [0x{x:02x}] / [{x_register}] REGISTER 2 = [0x{y:02x}] / [{y_register}] SUB = {value_to_poke} CARRY = {new_value}"); + debug!("SUB CARRY -> REGISTER 1 = [0x{x:02x}] / [{x_register}] REGISTER 2 = [0x{y:02x}] / [{y_register}] SUB = {value_to_poke} CARRY = {new_value}"); input.registers.poke(*x, value_to_poke as u8); input.registers.poke(0xf, new_value); @@ -656,7 +656,7 @@ impl Chip8CpuInstructions { let new_value: u8 = random(); let and_value: u8 = *byte; let result = new_value & and_value; - println!("RANDOM: [{new_value:02x}] AND: [{and_value:02x} Result: [{result:02x}]"); + debug!("RANDOM: [{new_value:02x}] AND: [{and_value:02x} Result: [{result:02x}]"); input.registers.poke(*x as u8, new_value & *byte as u8) } Chip8CpuInstructions::DrawVxVyNibble(y, x, n) => { @@ -837,9 +837,6 @@ impl Chip8CpuInstructions { #[cfg(test)] mod test { - use dimensioned::typenum::assert_type; - use ratatui::crossterm::execute; - use crate::chip8::cpu_states::Chip8CpuStates; use crate::chip8::system_memory::{CHIP8FONT_0, CHIP8FONT_1, CHIP8FONT_2, CHIP8FONT_9}; use super::*; @@ -1297,12 +1294,12 @@ mod test { x.registers.poke(0x1, 0x2); Chip8CpuInstructions::LdFVx(0x1).execute(&mut x); - assert_eq!(x.registers.peek_i(), 0x5); + assert_eq!(x.registers.peek_i(), 10); let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x06); Chip8CpuInstructions::LdFVx(0x1).execute(&mut x); - assert_eq!(x.registers.peek_i(), 25); + assert_eq!(x.registers.peek_i(), 30); } #[test] @@ -1444,8 +1441,8 @@ mod test { let real_bit_in_byte = 7 - bit_in_byte; let shifted_one = 0x01 << real_bit_in_byte; let one_shift_set = (shifted_one & row_data) > 0; - println!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]"); - println!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}", + debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]"); + debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}", bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data); } } diff --git a/gemma/src/chip8/stack.rs b/gemma/src/chip8/stack.rs index 3ef4331..43259c8 100644 --- a/gemma/src/chip8/stack.rs +++ b/gemma/src/chip8/stack.rs @@ -14,8 +14,8 @@ impl Default for Chip8Stack { impl Chip8Stack { pub fn push(&mut self, new_value: &u16) { if self.depth() == 16 { - println!("Deep deep stack?"); - // panic!("Stack Overflow"); + // println!("Deep deep stack?"); + panic!("Stack Overflow"); } self.items.push(*new_value ); } diff --git a/gemma/src/chip8/video.rs b/gemma/src/chip8/video.rs index 09b9e80..c9b5d7d 100644 --- a/gemma/src/chip8/video.rs +++ b/gemma/src/chip8/video.rs @@ -1,14 +1,26 @@ use log::{debug, trace}; -use crate::constants::CHIP8_VIDEO_MEMORY; +use crate::constants::{CHIP8_VIDEO_MEMORY, CHIP8_VIDEO_WIDTH}; #[derive(Clone, Copy)] pub struct Chip8Video { memory: [bool; CHIP8_VIDEO_MEMORY], - pub has_frame_changed: bool + pub has_frame_changed: bool, } impl Chip8Video { - pub fn cls(&mut self) { + fn int_cls(&self) -> Chip8Video { + let mut x = Chip8Video::default(); + for i in 0..CHIP8_VIDEO_WIDTH { + x.poke(i as u16, false); + } + x + } + + pub fn reset(&mut self) -> Self { + self.int_cls() + } + + pub fn cls(&mut self) { for i in 0..CHIP8_VIDEO_MEMORY { self.memory[i] = false; } @@ -21,7 +33,7 @@ impl Chip8Video { pub fn new(initial_configuration: [bool; CHIP8_VIDEO_MEMORY]) -> Self { Self { memory: initial_configuration, - has_frame_changed: false + has_frame_changed: false, } } @@ -47,13 +59,13 @@ impl Chip8Video { self.to_owned() } - pub fn poke_byte(&mut self, first_address: u16, to_write: u8) -> Self { + pub fn poke_byte(&mut self, first_address: u16, to_write: u8) -> Self { for i in (0..8).rev() { - let shifted = ((1 << i) & to_write) >> i; + let shifted = ((1 << i) & to_write) >> i; // let target_address = first_address + (7 - i); let is_set = shifted == 1; - self.poke(target_address, is_set); + self.poke(target_address, is_set); } self.to_owned() } @@ -89,18 +101,38 @@ impl Chip8Video { impl Default for Chip8Video { fn default() -> Self { - Self { memory: [false; CHIP8_VIDEO_MEMORY as usize], has_frame_changed: false } + debug!("DEFAULT VIDEO PREPARED"); + + let new_struct = Chip8Video { memory: [false; CHIP8_VIDEO_MEMORY], has_frame_changed: false }; + println!("NEW DEFAULT MEMORY : {}", new_struct.format_as_string()); + + new_struct } } #[cfg(test)] mod test { - use std::fs::File; use std::io::Read; use crate::chip8::system_memory::{CHIP8FONT_0, CHIP8FONT_1, CHIP8FONT_2, CHIP8FONT_3, CHIP8FONT_4, CHIP8FONT_5, CHIP8FONT_6, CHIP8FONT_7}; - use crate::constants::{CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH}; use super::*; + const TEST_OUTPUT_SAMPLE_DIR: &str = "../resources/test/"; + + fn build_checkerboard() -> Chip8Video { + let mut r = Chip8Video::default(); + + for i in 0..CHIP8_VIDEO_MEMORY { + r.poke(i as u16, i % 2 == 0); + } + + r + } + + fn read_test_result(suffix: &str) -> String { + std::fs::read_to_string(TEST_OUTPUT_SAMPLE_DIR.to_owned() + suffix) + .unwrap() + } + #[test] fn smoke() { assert!(true) } @@ -183,7 +215,7 @@ mod test { fn cls() { let mut initial_memory = [false; CHIP8_VIDEO_MEMORY]; let mut ws = String::new(); - // set our checkerboard + for cbr in 0..32 { for cbc in 0..64 { let dof = cbr * 64 + cbc; @@ -205,12 +237,12 @@ mod test { let mut v = Chip8Video::default(); v.poke_byte(0x00, to_poke); assert!(v.peek(0x00)); - assert!(v.peek(0x02)); - assert!(v.peek(0x04)); - assert!(v.peek(0x06)); assert!(!v.peek(0x01)); + assert!(v.peek(0x02)); assert!(!v.peek(0x03)); + assert!(v.peek(0x04)); assert!(!v.peek(0x05)); + assert!(v.peek(0x06)); assert!(!v.peek(0x07)); for i in 0x8..CHIP8_VIDEO_MEMORY { assert!(!v.peek(i as u16)); @@ -229,7 +261,7 @@ mod test { for (byte_in_set, byte_to_poke) in to_poke.iter().enumerate() { let base_offset = byte_in_set * 64; - v.poke_byte(base_offset as u16, *byte_to_poke); + v.poke_byte(base_offset as u16, *byte_to_poke); } // row 2 column 1 @@ -284,7 +316,7 @@ mod test { assert!(!v.peek(test_offset + 6)); assert!(!v.peek(test_offset + 7)); - let test_offset= test_offset + 0x40; + let test_offset = test_offset + 0x40; assert!(v.peek(test_offset)); assert!(v.peek(test_offset + 1)); assert!(v.peek(test_offset + 2)); @@ -339,53 +371,50 @@ mod test { #[test] fn write_checkboard() { - let mut v = Chip8Video::default(); - for current_row in 0..CHIP8_VIDEO_WIDTH { - for current_col in 0..(CHIP8_VIDEO_HEIGHT / 8){ - let offset = current_row * CHIP8_VIDEO_HEIGHT + current_col; - println!("CHECKBOARD OFFSET = {offset}"); - v.poke(offset as u16, offset % 2 == 0); - } - } - println!("{}", v.format_as_string()); - println!("fsck is a cool tool"); + let mut v = build_checkerboard(); + + assert_eq!(v.format_as_string(), read_test_result("test_video_write_checkerboard.asc")); } #[test] fn zero_test() { let mut x = Chip8Video::default(); - x.poke_byte(0x00, CHIP8FONT_0[0]); - x.poke_byte(0x40, CHIP8FONT_0[1]); - x.poke_byte(0x80, CHIP8FONT_0[2]); - x.poke_byte(0xC0, CHIP8FONT_0[3]); - x.poke_byte(0x100, CHIP8FONT_0[4]); + for (byte_index, data_offset) in (0..=0x100).step_by(0x40).enumerate() { + x.poke_byte(data_offset as u16, CHIP8FONT_0[byte_index]); + } - assert_eq!(std::fs::read_to_string("../resources/test/test_video_zero.asc") - .unwrap(), - x.format_as_string()); + assert_eq!(read_test_result("test_video_zero.asc"), x.format_as_string()); } #[test] fn multi_sprite_test() { let mut x = Chip8Video::default(); // draw a row of digits 01234567 - - let start_offset = 0x00; - let per_row_skip = 0x40; let to_draw = [CHIP8FONT_0, CHIP8FONT_1, CHIP8FONT_2, CHIP8FONT_3, CHIP8FONT_4, CHIP8FONT_5, CHIP8FONT_6, CHIP8FONT_7]; for (index, sprite) in to_draw.iter().enumerate() { let data_base_offset = index * 0x8; - println!("STARTING {index} at 0x{data_base_offset:04x} ({data_base_offset})"); - x.poke_byte(data_base_offset as u16, sprite[0]); - x.poke_byte((data_base_offset + 0x40) as u16, sprite[1]); - x.poke_byte((data_base_offset + 0x80) as u16, sprite[2]); - x.poke_byte((data_base_offset + 0xC0) as u16, sprite[3]); - x.poke_byte((data_base_offset + 0x100) as u16, sprite[4]); + for (index, offset) in (0..=0x100).step_by(0x40).enumerate() { + x.poke_byte((data_base_offset + offset) as u16, sprite[index]); + } } + assert_eq!(read_test_result("test_multi_sprite.asc"), x.format_as_string()); + } - assert_eq!(std::fs::read_to_string("../resources/test/test_multi_sprite.asc") - .unwrap(), - x.format_as_string()); } + #[test] + fn reset_test() { + let mut x = Chip8Video::default(); + + let to_draw = [CHIP8FONT_0, CHIP8FONT_1, CHIP8FONT_2, CHIP8FONT_3, CHIP8FONT_4, CHIP8FONT_5, CHIP8FONT_6, CHIP8FONT_7]; + for (index, sprite) in to_draw.iter().enumerate() { + let data_base_offset = index * 0x8; + for (index, offset) in (0..=0x100).step_by(0x40).enumerate() { + x.poke_byte((data_base_offset + offset) as u16, sprite[index]); + } + } + + x = x.reset(); + assert_eq!(x.format_as_string(), read_test_result("test_reset_clears_video.asc")); + } } diff --git a/gemma/tests/computer_tests.rs b/gemma/tests/computer_tests.rs index 90f3210..3b90bf8 100644 --- a/gemma/tests/computer_tests.rs +++ b/gemma/tests/computer_tests.rs @@ -1,4 +1,5 @@ use gemma::chip8::computer::Chip8Computer; +use gemma::constants::CHIP8_VIDEO_MEMORY; #[test] fn smoke() { assert!(true) } @@ -10,7 +11,7 @@ fn test_rom_1_works() { // it takes 39 cycles to get to the end so lets run it 40. let test_rom_to_run = std::fs::read("../resources/roms/2-ibm-logo.ch8").unwrap(); - x.load_bytes_to_memory(0x200, test_rom_to_run.into()); + x.load_bytes_to_memory(0x200, (&test_rom_to_run).into()); for i in 0..40 { x.step_system(); @@ -20,3 +21,21 @@ fn test_rom_1_works() { assert_eq!(x.dump_video_to_string(), std::fs::read_to_string("../resources/test/gemma_integration_ibm_rom_output.asc").unwrap()); } + +#[test] +fn reset_clears_video() { + let mut x = Chip8Computer::new(); + + for i in 0..CHIP8_VIDEO_MEMORY { + x.video_memory.poke(i as u16, i % 2 == 0); + } + + x.reset(); + x.video_memory = x.video_memory.reset(); + + assert_eq!(x.dump_video_to_string(), x.video_memory.format_as_string()); + + for i in 0..CHIP8_VIDEO_MEMORY { + assert!(!x.video_memory.peek(i as u16)); + } +} diff --git a/gemmaegui/src/bin/gemmaegui.rs b/gemmaegui/src/bin/gemmaegui.rs index deae0f1..10deeb8 100644 --- a/gemmaegui/src/bin/gemmaegui.rs +++ b/gemmaegui/src/bin/gemmaegui.rs @@ -1,5 +1,5 @@ -use crate::support::GemmaEGuiSupport::GemmaEGui; -use crate::support::EmmaEGuiState::EmmaEGuiState; +use crate::support::gemma_egui_support::GemmaEguiSupport; +use crate::support::gemma_egui_state::GemmaEGuiState; use eframe::egui; use egui::Ui; use gemma::chip8::computer::Chip8Computer; @@ -14,25 +14,24 @@ fn main() -> eframe::Result { ..Default::default() }; - let mut state = EmmaEGuiState::default(); + let mut state = GemmaEGuiState::default(); let mut computer = Chip8Computer::new(); eframe::run_simple_native("EGUI Emma", options, move |ctx, _frame| { egui::CentralPanel::default().show(ctx, |ui| { - ui.heading("Gemma"); + ui.heading("EGUI Gemma"); - GemmaEGui::controls_view(&mut computer, &mut state, ui); - - if state.display_memory { - GemmaEGui::memory_view(&computer, ui); - } + GemmaEguiSupport::controls_view(&mut computer, &mut state, ui); if state.display_video { - GemmaEGui::video_view(&computer, ui); + GemmaEguiSupport::video_view(&computer, ui); + } + if state.display_memory { + GemmaEguiSupport::memory_view(&computer, &mut state, ui); } if state.display_registers { - GemmaEGui::registers_view(&computer, ui); + GemmaEguiSupport::registers_view(&computer, ui); } }); }) diff --git a/gemmaegui/src/bin/support/GemmaEGuiSupport.rs b/gemmaegui/src/bin/support/GemmaEGuiSupport.rs deleted file mode 100644 index e3522e3..0000000 --- a/gemmaegui/src/bin/support/GemmaEGuiSupport.rs +++ /dev/null @@ -1,75 +0,0 @@ -use egui::Color32; -use egui::Rect; -use egui::Pos2; -use egui::Vec2; -use egui::Ui; -use crate::support::EmmaEGuiState::EmmaEGuiState; -use crate::Chip8Computer; - -pub struct GemmaEGui {} -impl GemmaEGui { - pub fn controls_view(mut system: &mut Chip8Computer, state: &mut EmmaEGuiState, ui: &mut Ui) { - if ui.button("Step").clicked() { - system.step_system(); - } - if ui.button("Start").clicked() { - println!("Start"); - } - if ui.button("Stop").clicked() { - println!("STOP"); - } - if ui.button("Reset").clicked() {} - - ui.checkbox(&mut state.display_memory, "Display Memory"); - ui.checkbox(&mut state.display_video, "Display Video"); - ui.checkbox(&mut state.display_registers, "Display Registers"); - } - - pub fn registers_view(system: &Chip8Computer, ui: &mut Ui) { - ui.label(format!("V0-7: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ", - system.registers.peek(0x00), - system.registers.peek(0x01), - system.registers.peek(0x02), - system.registers.peek(0x03), - system.registers.peek(0x04), - system.registers.peek(0x05), - system.registers.peek(0x06), - system.registers.peek(0x07) - )); - ui.label(format!("V8-F: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ", - system.registers.peek(0x08), - system.registers.peek(0x09), - system.registers.peek(0x0A), - system.registers.peek(0x0B), - system.registers.peek(0x0C), - system.registers.peek(0x0D), - system.registers.peek(0x0E), - system.registers.peek(0x0F) - )); - ui.label(format!("PC: {:04x}\tI: {:04x}", system.registers.peek_pc(), system.registers.peek_i())); - } - - pub fn video_view(system: &Chip8Computer, ui: &mut Ui) { - ui.label("Video goes here"); - let (resp, painter) = ui.allocate_painter(Vec2::new(400.0, 500.0), egui::Sense::hover()); - for current_row in 0..32 { - for current_col in 0..64 { - let data_offset = current_row * 32 + current_col; - let origin = ui.next_widget_position(); - let colour = if system.video_memory.peek(data_offset) { - Color32::RED - } else { - Color32::BLUE - }; - let rect = Rect::from_min_size(origin, Vec2::new(10.0, 10.0)); - painter.rect_filled(rect, 0.0, colour); - } - } - } - - - - pub fn memory_view(system: &Chip8Computer, ui: &mut Ui) { - ui.label("Memory View"); - } -} diff --git a/gemmaegui/src/bin/support/EmmaEGuiState.rs b/gemmaegui/src/bin/support/gemma_egui_state.rs similarity index 57% rename from gemmaegui/src/bin/support/EmmaEGuiState.rs rename to gemmaegui/src/bin/support/gemma_egui_state.rs index 111203f..5503281 100644 --- a/gemmaegui/src/bin/support/EmmaEGuiState.rs +++ b/gemmaegui/src/bin/support/gemma_egui_state.rs @@ -1,16 +1,20 @@ -pub struct EmmaEGuiState { +pub struct GemmaEGuiState { pub display_video: bool, pub display_memory: bool, pub display_registers: bool, + pub memory_view_min: i32, + pub memory_view_max: i32 } -impl Default for EmmaEGuiState { +impl Default for GemmaEGuiState { fn default() -> Self { Self { display_video: true, display_memory: true, display_registers: true, + memory_view_min: 0x00, + memory_view_max: 0x100 } } } diff --git a/gemmaegui/src/bin/support/gemma_egui_support.rs b/gemmaegui/src/bin/support/gemma_egui_support.rs new file mode 100644 index 0000000..f5f81c6 --- /dev/null +++ b/gemmaegui/src/bin/support/gemma_egui_support.rs @@ -0,0 +1,129 @@ +use std::fs::read_dir; +use std::ops::Index; +use std::path::{Display, PathBuf}; +use egui::{Align, Color32, ComboBox, Direction, Pos2}; +use egui::Rect; +use egui::Vec2; +use egui::Ui; +use crate::support::gemma_egui_state::GemmaEGuiState; +use crate::Chip8Computer; + +const CELL_WIDTH: f32 = 5.0; +const CELL_HEIGHT: f32 = 5.0; + +pub struct EGuiFileList {} +impl EGuiFileList { + pub fn display_path(root: PathBuf, selected_filename: &mut String, ui: &mut Ui) { + let mut working_filename = selected_filename.clone(); + ui.label(format!("Displaying {}", root.to_str().unwrap_or("Unable to Load Path"))); + + egui::ComboBox::from_label(format!( + "Currently selected string: {}", + selected_filename + )) + .selected_text(selected_filename.clone()) + .show_ui(ui, |ui| { + for option in read_dir(root.as_path()).unwrap() { + ui.label(format!("{:?}", option.unwrap().file_name())); + } + }); + } +} + +pub struct GemmaEguiSupport {} + +impl GemmaEguiSupport { + pub fn controls_view(mut system: &mut Chip8Computer, state: &mut GemmaEGuiState, ui: &mut Ui) { + ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { + if ui.button("Start").clicked() { + println!("Start"); + + } + if ui.button("Step").clicked() { + system.step_system(); + } + + if ui.button("Stop").clicked() { + println!("STOP"); + } + if ui.button("Reset").clicked() { + system.reset(); + system.video_memory = system.video_memory.reset(); + } + + if ui.button("Load initial rom").clicked() { + println!("CLICK ON LOAD"); + // load the bin... + let read_bin = std::fs::read(PathBuf::from("resources/roms/1-chip8-logo.ch8")).unwrap(); + // ...then feed the system. + system.load_bytes_to_memory(0x200, &read_bin); + } + }); + + ui.checkbox(&mut state.display_memory, "Display Memory"); + ui.checkbox(&mut state.display_video, "Display Video"); + ui.checkbox(&mut state.display_registers, "Display Registers"); + } + + pub fn registers_view(system: &Chip8Computer, ui: &mut Ui) { + ui.label(format!("V0-7: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ", + system.registers.peek(0x00), + system.registers.peek(0x01), + system.registers.peek(0x02), + system.registers.peek(0x03), + system.registers.peek(0x04), + system.registers.peek(0x05), + system.registers.peek(0x06), + system.registers.peek(0x07) + )); + ui.label(format!("V8-F: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ", + system.registers.peek(0x08), + system.registers.peek(0x09), + system.registers.peek(0x0A), + system.registers.peek(0x0B), + system.registers.peek(0x0C), + system.registers.peek(0x0D), + system.registers.peek(0x0E), + system.registers.peek(0x0F) + )); + ui.label(format!("PC: {:04x}\tI: {:04x}", system.registers.peek_pc(), system.registers.peek_i())); + } + + pub fn video_view(system: &Chip8Computer, ui: &mut Ui) { + ui.label("Video goes here"); + let (resp, painter) = ui.allocate_painter(Vec2::new(400.0, 500.0), egui::Sense::hover()); + for current_row in 0..64 { + for current_col in 0..32 { + let data_offset = current_row * 32 + current_col; + let x_offset = current_col as f32 * CELL_WIDTH; + let y_offset = current_row as f32 * CELL_HEIGHT; + let origin = Pos2::new(x_offset, y_offset); + let colour = if system.video_memory.peek(data_offset) { + Color32::RED + } else { + Color32::WHITE + }; + let rect = Rect::from_min_size(origin, Vec2::new(CELL_WIDTH, CELL_HEIGHT)); + painter.rect_filled(rect, 0.0, colour); + // println!("DataOffset: [{:04x}] X_Offset: [{:04x}] Y_Offset: [{:04x}] Origin: [{:04x}]x[{:04x}]", + // data_offset as u16, + // x_offset as u16, + // y_offset as u16, + // origin.x as u16, origin.y as u16); + } + } + } + + pub fn memory_view(system: &Chip8Computer, gui_state: &mut GemmaEGuiState, ui: &mut Ui) { + ui.label("Memory View"); + + for i in (gui_state.memory_view_min..gui_state.memory_view_max).step_by(16) { + ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { + for y in 0..16 { + ui.label(format!("{:02x}", system.memory.peek((i + y) as u16)).as_str()); + } + }); + } + ui.label("Should have **something** to adjust the 'memory window'"); + } +} diff --git a/gemmaegui/src/bin/support/mod.rs b/gemmaegui/src/bin/support/mod.rs index a20c907..7c7b1dd 100644 --- a/gemmaegui/src/bin/support/mod.rs +++ b/gemmaegui/src/bin/support/mod.rs @@ -1,2 +1,2 @@ -pub mod EmmaEGuiState; -pub mod GemmaEGuiSupport; \ No newline at end of file +pub mod gemma_egui_state; +pub mod gemma_egui_support; diff --git a/gemmaimgui/src/bin/gemmaimgui.rs b/gemmaimgui/src/bin/gemmaimgui.rs index 30a7171..bc77279 100644 --- a/gemmaimgui/src/bin/gemmaimgui.rs +++ b/gemmaimgui/src/bin/gemmaimgui.rs @@ -9,7 +9,7 @@ use imgui::*; use sys::{ImColor, ImVec2, ImVector_ImU32}; use rand::random; use gemma::chip8::system_memory::Chip8SystemMemory; -use support::{emmagui_support::EmmaGui, ui_state::UiState}; +use support::{emmagui_support::GemmaImguiSupport, ui_state::ImGuiUiState}; mod support; @@ -27,12 +27,12 @@ const LIN_KEYS: [(u16, u8); 0x10] = [(537, 0x01),(538, 0x02),(539, 0x03),(540, 0 fn main() { pretty_env_logger::init(); let mut system = Chip8Computer::default(); - let mut ui_state = UiState::default(); - let mut last_tick_time = Instant::now(); + let mut ui_state = ImGuiUiState::default(); support::simple_init(file!(), move |_, ui| { let current_time = Instant::now(); + // Key Checks let down_keys = ui.io().keys_down; for (key_code, key_reg) in LIN_KEYS { if down_keys[key_code as usize] { @@ -44,8 +44,9 @@ fn main() { } } } - // println!("KEYS DOWN = {:?}", ui.io().keys_down); - let time_since_last_tick = current_time.duration_since(last_tick_time).as_millis(); + + // Next Tick Check + let time_since_last_tick = current_time.duration_since(ui_state.last_frame_instant).as_millis(); if ui_state.is_running && time_since_last_tick > ui_state.frame_time as u128 { match system.state { gemma::chip8::cpu_states::Chip8CpuStates::WaitingForInstruction => { @@ -61,26 +62,27 @@ fn main() { panic!("System in undefined state."); }, } - last_tick_time = current_time; + ui_state.last_frame_instant = current_time; } - EmmaGui::system_controls(&mut system, &mut ui_state, ui); + // GUI Parts + GemmaImguiSupport::system_controls(&mut system, &mut ui_state, ui); if ui_state.show_registers { - EmmaGui::registers_view(&system, ui); + GemmaImguiSupport::registers_view(&system, ui); } if ui_state.show_video { - EmmaGui::video_display(&system, &ui_state, ui); + GemmaImguiSupport::video_display(&system, &ui_state, ui); } if ui_state.show_memory { let active_instruction = system.registers.peek_pc(); - EmmaGui::hex_memory_display(system.memory.clone(), (0x100, 0x10), active_instruction as i16, ui); + GemmaImguiSupport::hex_memory_display(system.memory.clone(), (0x100, 0x10), active_instruction as i16, ui); } if ui_state.show_keypad { - EmmaGui::keypad_display(&system, ui); + GemmaImguiSupport::keypad_display(&system, ui); } }); } diff --git a/gemmaimgui/src/bin/support/emmagui_support.rs b/gemmaimgui/src/bin/support/emmagui_support.rs index 7d942dc..0f6bc88 100644 --- a/gemmaimgui/src/bin/support/emmagui_support.rs +++ b/gemmaimgui/src/bin/support/emmagui_support.rs @@ -9,11 +9,11 @@ use log::debug; use gemma::chip8::computer::Chip8Computer; use gemma::chip8::system_memory::Chip8SystemMemory; use gemma::constants::{CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH}; -use crate::UiState; +use crate::ImGuiUiState; use super::ui_state; -pub struct EmmaGui {} +pub struct GemmaImguiSupport {} const CELL_WIDTH: i32 = 5i32; const CELL_HEIGHT: i32 = 5i32; @@ -40,8 +40,7 @@ impl GuiFileList { } } - -impl EmmaGui { +impl GemmaImguiSupport { pub fn keypad_display(system_to_display: &Chip8Computer, ui: &Ui) { ui.text("Keypad"); @@ -59,9 +58,10 @@ impl EmmaGui { } } - pub fn video_display(system_to_control: &Chip8Computer, gui_state: &UiState, ui: &Ui) { + pub fn video_display(system_to_control: &Chip8Computer, gui_state: &ImGuiUiState, ui: &Ui) { // draw area size let draw_area_size = ui.io().display_size; + println!("DRAW_AREA_SIZE = {}x{}", draw_area_size[0], draw_area_size[1]); let cell_width = ((draw_area_size[0] as i32 / 64) * 6) / 10; let cell_height = ((draw_area_size[1] as i32 / 32) * 6) / 10; @@ -89,7 +89,7 @@ impl EmmaGui { } }); } - pub fn system_controls(system_to_control: &mut Chip8Computer, gui_state: &mut UiState, ui: &Ui) { + pub fn system_controls(system_to_control: &mut Chip8Computer, gui_state: &mut ImGuiUiState, ui: &Ui) { ui.window("!!!! CONTROLS !!!!") .size([345.0, 200.0], Condition::FirstUseEver) .build(|| { @@ -107,7 +107,7 @@ impl EmmaGui { // let mut input_file = File::open(Path::new("./1-chip8-logo.ch8")).expect("put 1-chip8-logo.ch8 in this directory"); let mut input_file = File::open(Path::new(&("resources/roms/".to_string() + &gui_state.filename_to_load))).expect("put 1-chip8-logo.ch8 in this directory"); input_file.read_to_end(&mut buffer).expect("unable to read file"); - system_to_control.load_bytes_to_memory(0x200, buffer.into()); + system_to_control.load_bytes_to_memory(0x200, (&buffer).into()); } } ui.separator(); diff --git a/gemmaimgui/src/bin/support/ui_state.rs b/gemmaimgui/src/bin/support/ui_state.rs index dd1bb71..a585ab0 100644 --- a/gemmaimgui/src/bin/support/ui_state.rs +++ b/gemmaimgui/src/bin/support/ui_state.rs @@ -1,7 +1,7 @@ +use std::time::Instant; use imgui::ImColor32; - -pub struct UiState { +pub struct ImGuiUiState { pub show_registers: bool, pub show_memory: bool, pub show_video: bool, @@ -11,11 +11,12 @@ pub struct UiState { pub off_colour: ImColor32, pub is_running: bool, pub frame_time: f32, + pub last_frame_instant: Instant } -impl Clone for UiState { +impl Clone for ImGuiUiState { fn clone(&self) -> Self { - UiState { + ImGuiUiState { show_registers: self.show_registers, show_memory: self.show_memory, show_video: self.show_video, @@ -25,13 +26,14 @@ impl Clone for UiState { off_colour: self.off_colour, is_running: self.is_running, frame_time: self.frame_time, + last_frame_instant: self.last_frame_instant } } } -impl Default for UiState { +impl Default for ImGuiUiState { fn default() -> Self { - UiState { + ImGuiUiState { show_registers: false, show_memory: false, show_video: true, @@ -41,6 +43,7 @@ impl Default for UiState { off_colour: ImColor32::from_rgb(0x00, 0xff, 0xff), is_running: false, frame_time: 10.0, + last_frame_instant: Instant::now() } } } diff --git a/resources/test/test_reset_clears_video.asc b/resources/test/test_reset_clears_video.asc new file mode 100644 index 0000000..5594ea9 --- /dev/null +++ b/resources/test/test_reset_clears_video.asc @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/test/test_video_write_checkerboard.asc b/resources/test/test_video_write_checkerboard.asc new file mode 100644 index 0000000..5e866e2 --- /dev/null +++ b/resources/test/test_video_write_checkerboard.asc @@ -0,0 +1,32 @@ +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *