# 2025q1 Homework3 (kxo)
contributed by < `fcu-D0812998` >
## 實作 coroutine
> Commit [b0e3d58](https://github.com/sysprog21/kxo/commit/b0e3d588f553f686f1b4f94735c98fdbbd5e3661)
參考資料 : [並行程式設計: 排程器原理](https://hackmd.io/@sysprog/concurrency-sched)
移除原本的 select-based I/O 模式,將鍵盤事件與顯示棋盤重寫成 coroutine-based task switching 。
* 將 `listen_keyboard_handler()` 和 `draw_board()` 重寫為 coroutine-based task switching 。
* 在函式內加入:
```c
TASK_SCHEDULE();
while (!end_attr) {
...
TASK_YIELD();
}
```
* 使用 `TASK_SCHEDULE()` 初始化 coroutine 狀態,`TASK_YIELD()` 讓出控制權。
在原本使用 select-based I/O 的話,需要用多層的 if-else 條件式去做判斷,且當需求變多時, select 會越寫越複雜,在維護的時候會相當困難。這樣做更改的話,好處是 :
* 輕量 : 一個 coroutine 只是一組類似暫存器的暫存上下文。
* 便宜:在 user-space 直接 `setjmp() + longjmp()` 就可以很快就切換,不用進 kernel。
* 結構化:每個 coroutine 自己有獨立的邏輯,結合 `TASK_SCHEDULE() / TASK_YIELD()` ,整個流程清楚且可控。
## 把下棋畫面呈現的部分全部在使用者層級
> Commit [fbb00df](https://github.com/sysprog21/kxo/commit/fbb00dfe54b73b2dccc67554c1bb35781dc434f8)
:::danger
說好的進度呢?
:::
首先要先了解, kernal 端如何呈現棋盤畫面,看到原版 main.c 也就是 kernal 端中有 `draw_board` 的函式,它的用途是將棋盤的狀態也就是 table 這個陣列裡的值轉成 ASCII 畫面,也就是 draw_buffer 。
```c
static int draw_board(char *table)
.......
```
* 所以目前就是要把 draw_board 這個函式搬去 user space ,讓 kernel 專心處理遊戲決策跟勝負判斷,不做 UI。
* 搬完後,因為原本每次 user space 從 kernel 端 read 時,會讀取 `DRAWBUFFER_SIZE` 大小的資料(包括了 table 、格線符號、換行符號),總共為 66 bytes,現在我們已經在 coroutime 中把顯示棋盤的部分放在 user space了,所以 kernel 端只需傳入 table 就好也就是棋盤的狀態,總共 4*4 = 16 bytes。
* 在 `produce_board` ,它的用途是把目前 kernel 端中棋盤的狀態放入 kfifo 緩衝區,
讓 user space 可以用 `read()` 把這份資料讀走顯示,我們將原本的 draw_buffer 改成只傳 table 就可以了。
```diff
- kfifo_in(&rx_fifo, draw_buffer, sizeof(draw_buffer));
+ kfifo_in(&rx_fifo, table, sizeof(table));
```
:::danger
注意書寫規範,中英文以一個半形空白字元區隔
:::
## 降低核心和使用者層級之間的通訊成本
> Commit
在 ASCII 編碼中,需要 8bits 才能塞的下 `O` (79)跟 `X` (88),但我的棋盤其實只要呈現 `O` 跟 `X` 還有空白三個,那我的想法是把它轉換成 2Bits 就好。
```
空白 = 00
O = 01
X = 10
```
:::danger
提供更多數學分析
:::
棋盤大小為 4 * 4 = 16
原本設計的大小為 : 16 * 8 = 128 bits
改良設計的大小為 : 16 * 2 = 32 bits
節省了 96 bits 的空間