「30日でできる!OS自作入門」をRustで。11日目

Posted on June 26, 2019 , Tags: OS自作入門, OS, Rust

「30日でできる!OS自作入門 」のC言語の部分をできるだけRustですすめてみる。今回は11日目の内容。

マウスカーソルを画面端まで移動可能にする

現状、マウスカーソルを画面端のほうに移動すると以下のようにカーソルの右端が画面に接したところでとまってしまう。

マウスカーソルが右端までいけない

これをカーソルが隠れるくらいまで移動可能にする。

上記が主要な修正であるが、マイナスの値をうけつけるにあたって、必要な箇所を u32 から i32 にかえるなどの変更も行った。

結果

以下のようにマウスカーソルが画面端に隠れるまで移動可能になった

マウスカーソルが右端まで移動可能

ウィンドウの表示

次にウィンドウを表示する。

make_window については vga.rs に以下のように実装した。

// vga.rs
pub fn make_window(buf: usize, xsize: i32, ysize: i32, title: &str) {
    let xsize = xsize as isize;
    let ysize = ysize as isize;
    let closebtn: [&[u8; 16]; 14] = [
        b"[email protected]",
        b"[email protected]",
        b"[email protected]",
        b"[email protected]@[email protected]@[email protected]",
        b"[email protected]@[email protected]@[email protected]",
        b"[email protected]@@@[email protected]",
        b"[email protected]@[email protected]",
        b"[email protected]@@@[email protected]",
        b"[email protected]@[email protected]@[email protected]",
        b"[email protected]@[email protected]@[email protected]",
        b"[email protected]",
        b"[email protected]",
        b"[email protected]",
        b"@@@@@@@@@@@@@@@@",
    ];
    boxfill(buf, xsize, Color::LightGray, 0, 0, xsize - 1, 0);
    boxfill(buf, xsize, Color::White, 1, 1, xsize - 2, 1);
    boxfill(buf, xsize, Color::LightGray, 0, 0, 0, ysize - 1);
    boxfill(buf, xsize, Color::White, 1, 1, 1, ysize - 2);
    boxfill(
        buf,
        xsize,
        Color::DarkGray,
        xsize - 2,
        1,
        xsize - 2,
        ysize - 2,
    );
    boxfill(buf, xsize, Color::Black, xsize - 1, 0, xsize - 1, ysize - 1);
    boxfill(buf, xsize, Color::LightGray, 2, 2, xsize - 3, ysize - 3);
    boxfill(buf, xsize, Color::DarkBlue, 3, 3, xsize - 4, 20);
    boxfill(
        buf,
        xsize,
        Color::DarkGray,
        1,
        ysize - 2,
        xsize - 2,
        ysize - 2,
    );
    boxfill(buf, xsize, Color::Black, 0, ysize - 1, xsize - 1, ysize - 1);
    let mut writer = ScreenWriter::new(
        Some(buf),
        Color::White,
        24,
        4,
        xsize as usize,
        ysize as usize,
    );
    write!(writer, "{}", title).unwrap();
    for y in 0..14 {
        let y = y as usize;
        for x in 0..16 {
            let x = x as usize;
            let c = closebtn[y][x];
            let color: Color;
            if c == b'@' {
                color = Color::Black
            } else if c == b'$' {
                color = Color::DarkGray;
            } else if c == b'Q' {
                color = Color::LightGray;
            } else {
                color = Color::White
            }
            let ptr = unsafe {
                &mut *((buf + (5 + y) * xsize as usize + (xsize as usize - 21 + x)) as *mut Color)
            };
            *ptr = color;
        }
    }
}

この他、boxfillprint_char が画面幅固定での描画になってしまっていたのを修正した。

結果

以下のようにウィンドウが描画できた。

ウィンドウの描画

カウンターの表示

ウィンドウ内にカウンターを表示する。

ただ、このままだと高速にレンダリングの重ね描きが発生し、画面がチラついてしまう。 そのため、本ではこの後、レンダリングを最低限にするようにしている。

チラつきの抑制

SheetManagerに一番上のSheet番号をもつようなmapを導入して、そのSheetのみ描画するように変更する。

//sheet.rs

pub struct SheetManager {
    pub z_max: Option<usize>,             // 一番上のSheetのz
    pub map_addr: i32,                    // 追加: 重ね合わせ計算用のマップをもつ
    pub sheets: [usize; MAX_SHEETS],      // sheets_data上のindexを保持する
    pub sheets_data: [Sheet; MAX_SHEETS], // sheetデータの実体
}

impl SheetManager {
    pub fn new(map_addr: i32) -> SheetManager {
        SheetManager {
            z_max: None,
            map_addr, // <- 追加
            sheets: [0; MAX_SHEETS],
            sheets_data: [Sheet::new(); MAX_SHEETS],
        }
    }
    // 省略
     pub fn refresh_map(&self, x0: i32, y0: i32, x1: i32, y1: i32, z0: i32) {
        if self.z_max.is_none() {
            return;
        }
        let x0 = max(0, x0);
        let y0 = max(0, y0);
        let x1 = min(x1, *SCREEN_WIDTH as i32);
        let y1 = min(y1, *SCREEN_HEIGHT as i32);
        for h in (z0 as usize)..=self.z_max.unwrap() {
            let si = self.sheets[h as usize];
            let sheet = &self.sheets_data[si];
            let bx0 = if x0 > sheet.x { x0 - sheet.x } else { 0 } as usize;
            let by0 = if y0 > sheet.y { y0 - sheet.y } else { 0 } as usize;
            let bx1 = if x1 > sheet.x {
                min(x1 - sheet.x, sheet.width)
            } else {
                0
            } as usize;
            let by1 = if y1 > sheet.y {
                min(y1 - sheet.y, sheet.height)
            } else {
                0
            } as usize;
            for by in by0..by1 {
                let vy = sheet.y as usize + by;
                for bx in bx0..bx1 {
                    let vx = sheet.x as usize + bx;
                    let width = sheet.width as usize;
                    let c = unsafe { *((sheet.buf_addr + by * width + bx) as *const Color) };
                    if Some(c) != sheet.transparent {
                        let ptr = unsafe {
                            &mut *((self.map_addr as *mut u8)
                                .offset(vy as isize * *SCREEN_WIDTH as isize + vx as isize))
                        };
                        *ptr = si as u8;
                    }
                }
            }
        }
    }
    // 省略
    pub fn refresh_part(&self, x0: i32, y0: i32, x1: i32, y1: i32, z0: i32, z1: i32) {
        // 省略
            for by in by0..by1 {
                let vy = sheet.y as usize + by;
                for bx in bx0..bx1 {
                    let vx = sheet.x as usize + bx;
                    let width = sheet.width as usize;
                    let map_si = unsafe {
                        *((self.map_addr as isize
                            + vy as isize * *SCREEN_WIDTH as isize
                            + vx as isize) as *const u8)
                    };
                    // 一番上のSheetだったらレンダリングするようにする
                    if si as u8 == map_si {
                        let c = unsafe { *((sheet.buf_addr + by * width + bx) as *const Color) };
                        let ptr = unsafe {
                            &mut *((*VRAM_ADDR as *mut u8)
                                .offset(vy as isize * *SCREEN_WIDTH as isize + vx as isize))
                        };
                        *ptr = c as u8;
                    }
                }
            }

実行結果

実行してみると以下の通り、マウスを重ねてもチラつかずにカウンターが表示される。(GIFのフレームレートの影響で若干歪んでみえるが、実際はもっときれいに見えている。)

カウンタ

だいぶ i32, u32, isize, usize が乱立してきて型変換が面倒なのでそろそろ整理したほうがいいかもしれない。。

11日目は以上となる。ここまでの内容のコードはyoshitsugu/hariboteos_in_rustのday11としてタグを打ってある。