我们持续为我们的操作系统写入图形驱动程序和事件驱动程序。我们还添加了几个系统调用来处理绘图基元以及处理键盘和鼠标输入。我们现在将使用那些动作乒乓球的简单游戏。就像Hello World是每个编程语言的测试,Pong都是我们所有的图形和事件系统的考验。
由于我们在用户空间中编写,我们需要使用我们开发的系统调用(1)枚举帧缓冲器,(2)无效的帧缓冲器的部分,(3)读取键盘/单击事件,(4)读取动作事件 - 鼠标或平板电脑。
对于Rust,我们仍在使用RISCV64GC-Unknown-None-Elf目标,其不包含运行时。现在,RISCV64GC-Unknown-Linux-GNU确实存在,但它使用Linux系统调用,我们还没有实现。因此,我们需要创建自己的系统调用界面以及运行时!
许多程序员知道ELF允许我们的入口点开始任何地方。但是,大多数链接器都会查找称为_start的符号。所以,在Rust中,我们必须创建这个符号。不幸的是,经过多次尝试,我无法让它完全锈蚀。所以,相反,我使用了global_asm!宏导入汇编文件。所有它所做的就是调用Main并在主返回时调用退出系统调用。
.text.init.global _start_start:.option push.option norelax la gp,__global_pointer $ .option pop li a0,0 li a1,0致电主#退出系统调用主li a7,93 ecall.type _start,function .size _start,。-_开始
我没有提到全球指针。由于全局变量远处存储,因此我们使用寄存器来存储此位置的顶部。幸运的是,大多数链接器都有一个名为__global_pointer $的符号(是的,即使是$ $),我们投入GP(全局指针)寄存器。我不会覆盖放松,但这是必要的。
这就是我们需要进行生锈的全部。但是,由于我们仍处于公理生锈,我们必须定义我们为OS为OS的相同符号,包括恐慌和中止处理程序。
#![no_std]#![功能(asm,panic_info_message,lang_items,start,global_asm)]#[lang =" eh_personality"] extern" c" fn eh_personality(){}#[panic_handler] fn panic(信息:& core :: panic :: panicinfo) - > !! {打印!("中止:");如果让某些(p)= info.location(){println!(" line {},文件{}:{}}" p.line(),p.file(),info.message ().unwrap()); } else {println!("没有信息。"); abort();}#[no_mangle] extern" c" Fn abort() - > !! {循环{不安全{ASM!(" WFI"); }}}
这允许我们的代码至少编译,但尽可能看到我们需要导入程序集文件以及定义主要。
现在我们有一个生锈的入学点。现在,我们可以创建Println和打印宏以使我们生锈更生锈。
#[宏_export] macro_rules!打印{($($($ args:tt)+)=> ({使用核心:: fmt ::写;让_ =写!(箱子:: syscall :: writer,$($ args)+);});}#[宏_export]宏_rules! println {()=> ({打印!(" \ r \ n")}); ($ fmt:expr)=> ({打印!(concat!($ fmt," \ r \ n")); ($ fmt:expr,$($ args:tt)+)=> ({打印!(concat!($ fmt," \ r \ n"),$($ args)+)});}
我们需要为系统调用创建一个API,因为这将是我们如何获得和将某些数据放到我们的操作系统。一般来说,运行时将为我们建立这个,但再次,我们在aremetal生锈中。
使用核心:: fmt :: {write,错误};使用crate :: event ::事件; Pub struct作家; ichrit for Writer {fn write_str(& mut self,Out:& str) - >结果<(),错误> {for c out.bytes(){PUTCHAR(C); } OK(())}} PUB FN PUTCHAR(C:U8) - > USIZION {SYSCALL(2,C为USIZE,0,0,0,0,0,0)} PUB FN睡眠(TM:USIZE){LET _ = SYSCALL(10,TM,0,0,0,0,0,0 ,0); PUB FN GET_FB(哪个_FB:USIZIZE) - > USIZIZE {SYSCALL(1000个,哪一个,0,0,0,0,0,0,0)} PUB FN INV_RECT(D:USIZE,X:USIZE,Y:USIZE,W:USIZE,H:USIZIZE){LET _ = SYSCALL (1001,d,x,y,w,h,0,0); pub fn get_keys(x:* mut事件,y:me:measize) - > Usize {Syscall(1002,x为Usize,Y,0,0,0,0,0)} Pub Fn Get_abs(x:* mut事件,Y:USIZE) - > USIZION {SYSCALL(1004,X为USIZE,Y,0,0,0,0,0)} PUB FN GET_TIME() - > Usize {Syscall(1062,0,0,0,0,0,0,0,0)} Pub Fn Syscall(Sysno:Usize,A0:Usize,A1:Usize,A2:Usize,A3:Usize,A4:USIZE,A5:USIZE,A5 :Usize,A6:Usize) - > USIZIZE {让RET;不安全{asm!(" ecall"(" a7")sysno,在(" a0")a0,在(" a1" )A1,IN(" A2")A2,IN(" A3")A3,IN(" A4")A4,IN(" A5&# 34;)A5,IN(" A6")A6,乳顿(" A0")RET); } ret}
男人,我喜欢新的铁锈ASM!很明显,当你看着它时发生了什么。通常在我使用内联组合时,寄存器选择非常重要。这使它变得相当万无一失 - 我真的这么说吗?
就像我们为我们的操作系统所做的那样,我们使用写性特性来钩住ruct给我们给我们的格式宏。
好的,通过系统调用和启动代码开始,我们现在拥有开始编程游戏所需的一切。所以,让我们制作一些绘图原语。由于这是乒乓球,我们真的非常关心绘图矩形。
#[ReP(c)]#[派生(克隆,复制)] Pub结构像素{Pub R:U8,Pub G:U8,Pub B:U8,Pub A:U8,} Pub类型Color = Pixel; iclilc映像{ PUB FN NEW(R:U8,G:U8,B:U8) - >自我{self {r,g,b,a:255}} pub struct矢量{pub x:i32,pub y:i32} isc矢量{pub fn new(x:i32,y:i32) - >自我{self {x,y}}} pub struct矩形{pub x:i32,pub y:i32,pub宽度:i32,pub height:i32,} iscland矩形{pub fn new(x:i32,y:i32,宽度:i32,高度:i32) - > self {self {x,y,宽度,高度}}} pub struct framebuffer {pixels:* mut pixel} impl framebuffer {pub fn new(像素:* mut pixel) - > self {self {pixels}} pub fn set(& mut self,x:i32,y:i32,pixel:& pixel){不安全{如果x< 640&& y< 480 {让v =(y * 640 + x)如isize; self.pixels.offset(v).write(* pixel); PUB FN Fill_Rect(& mut self,rect:&矩形,颜色:& pixel){let row_start = rect.y;让Row_Finish = Row_Start + Rect.Height;让Col_Start = Rect.x;让col_finish = col_start + rect.width;在row_start的行。{col_start的{for col .col_finish {self.set(col,行,颜色); PUB FN LERP(值:I32,MX1:I32,MX2:I32) - > i32 {设r =(值为f64)/(mx1作为f64);返回r为i32 * mx2;}
现在我们有一个像素结构,矢量,矩形和帧缓冲。像素来自操作系统,因此我们控制该结构是重要的,这需要#[Recon(c)]。
我们可以画出,所以现在我们需要能够处理输入。我们可以创建一个事件结构来处理此功能。
#[RePr(c)]#[派生(复制,克隆)] Pub struct事件{Pub Event_type:U16,Pub代码:U16,Pub值:U32} Alcm Event {Pub Fn空() - > self {self {event_type:0,代码:0,值:0}} PUB FN New(event_type:U16,代码:U16,Value:U32) - > self {self {event_type,code,value}}}} //键码码}}}} //键码码ucont key_reserved:u16 = 0; pub const key_esc:u16 = 1; pub const key_1:u16 = 2; pub const key_2:u16 = 3; //。 ..剪辑... Pub Const Key_end:Pub Const Key_down:U16 = 108; //鼠标按钮Pub Const BTN_Mouse:U16 = 0x110; Pub Const BTN_LEFT:U16 = 0x110; PUB CONST BTN_RIGHT:U16 = 0x111; PUB const btn_middle:u16 = 0x112; //鼠标移动pubs cand abs_x:u16 = 0x00; pub consac abs_y:u16 = 0x01; pub const abs_z:u16 = 0x02;
许多常量来自Libevdev的输入-iffic-codes.h。不幸的是,这是在c中,所以一点python脚本可以使它生锈。
就像像素结构一样,事件结构由我们的操作系统定义,因此我们必须自己控制它(因此#[Repre(C)])。
const max_events:musize = 25; const game_frame_timer:measize = 1000;#[开始] fn main(_argc:isize,_argv:* const * const u8) - > isize {使用绘图:: FrameBuffer;使用绘图:: pixel;让UFB = SYSCALL :: GET_FB(6)作为* mut像素;让MUT FB = FrameBuffer :: New(UFB);让Backgrounds_color =绘图:: Pixel :: New(25,36,100);让mut event_list = [事件:: event :: fight(); max_events];让event_list_ptr = event_list.as_mut_ptr();让玩家_color =绘图:: pixel :: new(255,0,0);让npc_color =绘图:: pixel :: new(0,255,0);让Ball_Color =绘图:: Pixel :: New(255,255,255);让mut游戏= pong :: pong :: new(player_color,npc_color,ball_color,background_color); //游戏循环在这里println!("再见:)"); 0}
你可以看到我们所做的第一件事是抓取框架缓冲。如果您从操作系统教程中记得,我们的操作系统将将像素映射到我们的应用程序的内存空间中。我们可以在屏幕上实际实现它时更新像素,但要在屏幕上实现它,我们必须使给定的像素无效,这是我们操作系统中的单独系统调用。
你可以看到我们有一个名为pong的结构。这种结构包含我们需要做到这一点的例程。我们有一个名为authand_frame的时序函数,我们有一种方法可以使用绘制功能将游戏获取到屏幕上。
这是非常粗糙的,但这是Pong Sristure的提前框架和绘制例程(其余的可以在GitHub上找到):
icharm pong {pub fn advance_frame(& mut self){if!self.paused {self.ball_direction.x,self.ball_direction.y);让小姐=如果self.ball.location.x< 40 {//这意味着我们'重新参加玩家'桨位置。让' s //看看这是一个打击还是错过了!让Paddle =(self.player.location.y,self.player.location.y + self.player.location.height);让ball =(self.ball.location.y,self.ball.location.y + self.ball.location.height);如果paddle.0< = ball.0&& Paddle.1> = ball.0 {false}如果paddle.0< ball.1&&& Paddle.1> = ball.1 {false} else {true}} else {false};如果小姐{self.reset(); self.paused = true; } else {如果self.ball.location.x< 40 || self.ball.location.x> 580 {self.ball_direction.x = -self.ball_direction.x;如果self.ball.ball.location.y< 20 || self.ball.location.y> 430 {self.ball_direction.y = -self.ball_direction.y; Let new_loc = self.ball.location.y - self.npc.location.height / 2; self.npc.location.y =如果new_loc> 0 {new_loc} else {0}; PUB FN Draw(& self,fb:& mut framebuffer){fb.fill_rect(& rectangle :: new(0,0,640,480),& self.bgcolor); fb.fill_rect(& self.player.location,& self.player.color); fb.fill_rect(& self.npc.location,& self.npc.color); fb.fill_rect(& self.ball.location,& self.ball.color); }}
所以我们的游戏将把球移动到每个框架的指导矢量,因为从我们的游戏循环调用了admain_frame。我们还有能力暂停游戏(或暂停)。最后,我们对玩家的桨具有非常基本的碰撞检测。
现在我们需要使用游戏循环将所有这一切放在一起,这将处理输入事件以及提前动画。
//游戏循环游戏弹灵木:循环{//处理鼠标按钮和键盘输入// println!("尝试获取钥匙");假设num_events = syscall :: get_keys(event_list_ptr,max_events);在0中为e .NUM_Events {让REF EV = EVEN_LIST [E]; // println!(" key {}值{}",ev.code,ev.value); // value = 1如果按下键或0如果发布匹配ev.code {event :: key_q =>休息' gameloop,event :: key_r => Game.Reset(),事件:: key_w |事件:: key_up => Game.move_Player(-20),Event :: Key_s |事件:: key_down => game.move_player(20),事件:: key_space =>如果ev.Value == 1 {game.toggle_pause();如果game.is_paused(){println!("游戏暂停&#34); } else {println!("游戏未授予")},_ => game.advance_frame(); Game.draw(& mut fb); syscall :: inv_rect(6,0,0,640,480); syscall ::睡眠(game_frame_timer); }
我们的事件循环使用W移动划桨或D移动拨片。空间切换暂停,R重置游戏。
嗯。当我开始写这篇文章时,我的脑海似乎更令人印象深刻。哦,好吧,玩得开心!