「30天自制操作系统」Day26 - 为窗口移动提速

提高窗口移动速度

现在我们想办法提高窗口移动的速度,首先考虑优化 sheet_refreshmap 函数。

在之前的 sheet_refreshmap 函数中,我们在最内层循环执行 if 语句,来判断图层是否为透明部分,要执行上万次。而实际上,我们的透明图层是很少的,因此我们在循环之前先判断是否有透明图层,然后分别执行两个版本的刷新操作。程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0)
{
for (h = h0; h <= ctl->top; h++) {
if (sht->col_inv == -1) {
/* 无透明色图层 */
for (by = by0; by < by1; by++) {
vy = sht->vy0 + by;
for (bx = bx0; bx < bx1; bx++) {
vx = sht->vx0 + bx;
map[vy * ctl->xsize + vx] = sid;
}
}
} else {
/* 有透明色图层 */
for (by = by0; by < by1; by++) {
vy = sht->vy0 + by;
for (bx = bx0; bx < bx1; bx++) {
vx = sht->vx0 + bx;
if (buf[by * sht->bxsize + bx] != sht->col_inv) {
map[vy * ctl->xsize + vx] = sid;
}
}
}
}
}
return;
}

继续优化。在我们的 sheet_refreshmap 里还有这样一句:

1
map[vy * ctl->xsize + vx] = sid;

也就是向内存中某个地址写入 sid 的值,而如果我们用 32 位寄存器来操作,一条指令就可以同时向相邻的 4 个地址写入值了。修改后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0)
{
for (h = h0; h <= ctl->top; h++) {
if (sht->col_inv == -1) {
if ((sht->vx0 & 3) == 0 && (bx0 & 3) == 0 && (bx1 & 3) == 0) {
/* 无透明色图层(4 字节型) */
bx1 = (bx1 - bx0) / 4;
sid4 = sid | sid << 8 | sid << 16 | sid << 24;
for (by = by0; by < by1; by++) {
vy = sht->vy0 + by;
vx = sht->vx0 + bx0;
p = (int *) &map[vy * ctl->xsize + vx];
for (bx = 0; bx < bx1; bx++) {
p[bx] = sid4;
}
}
} else {
/* 无透明色图层(1 字节型) */
for (by = by0; by < by1; by++) {
vy = sht->vy0 + by;
for (bx = bx0; bx < bx1; bx++) {
vx = sht->vx0 + bx;
map[vy * ctl->xsize + vx] = sid;
}
}
}
} else {
/* 有透明色图层 */
for (by = by0; by < by1; by++) {
vy = sht->vy0 + by;
for (bx = bx0; bx < bx1; bx++) {
vx = sht->vx0 + bx;
if (buf[by * sht->bxsize + bx] != sht->col_inv) {
map[vy * ctl->xsize + vx] = sid;
}
}
}
}
}
return;
}

为了保证效果,我们要使窗口在 x 方向上的大小、窗口的 x 坐标、以及目的地的坐标都为 4 的倍数,具体操作就是 &(~3),也就是将最后两个比特位都置为 0。

同理,刚刚的办法我们可以应用到 sheet_refreshsub 中,同理必须要求为 4 的倍数,注意这里对余数部分要特殊处理。

最后,由于我们的绘图操作非常消耗时间,导致系统来不及处理 FIFO 中的鼠标移动数据。那么我们修改一下,当 FIFO 为空时再进行绘图操作就可以了。

进行了这么多优化,我们的窗口移动速度有了很大的提升。

启动时只打开一个命令行窗口

现在我们来做一个快捷键,当按下 shift + F2 的时候就打开一个新的命令行窗口。并且修改为启动时只打开一个命令行窗口。

快捷键处理部分的代码如下:

1
2
3
4
5
6
7
8
9
if (i == 256 + 0x3c && key_shift != 0 && sht_cons[1] == 0) {	/* Shift+F2 */
sht_cons[1] = open_console(shtctl, memtotal);
sheet_slide(sht_cons[1], 32, 4);
sheet_updown(sht_cons[1], shtctl->top);
/* 自动将输入焦点切换到新打开的命令行窗口 */
keywin_off(key_win);
key_win = sht_cons[1];
keywin_on(key_win);
}

这里的 open_console 是一个新的函数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct SHEET *open_console(struct SHTCTL *shtctl, unsigned int memtotal)
{
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
struct SHEET *sht = sheet_alloc(shtctl);
unsigned char *buf = (unsigned char *) memman_alloc_4k(memman, 256 * 165);
struct TASK *task = task_alloc();
int *cons_fifo = (int *) memman_alloc_4k(memman, 128 * 4);
sheet_setbuf(sht, buf, 256, 165, -1); /* 无透明色 */
make_window8(buf, 256, 165, "console", 0);
make_textbox8(sht, 8, 28, 240, 128, COL8_000000);
task->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 12;
task->tss.eip = (int) &console_task;
task->tss.es = 1 * 8;
task->tss.cs = 2 * 8;
task->tss.ss = 1 * 8;
task->tss.ds = 1 * 8;
task->tss.fs = 1 * 8;
task->tss.gs = 1 * 8;
*((int *) (task->tss.esp + 4)) = (int) sht;
*((int *) (task->tss.esp + 8)) = memtotal;
task_run(task, 2, 2); /* level=2, priority=2 */
sht->task = task;
sht->flags |= 0x20; /* 有光标 */
fifo32_init(&task->fifo, 128, cons_fifo, task);
return sht;
}

我们运行一下看看效果:

增加更多的命令行窗口

事实上我们的 sht_cons 好像并没什么用,我们修改一下代码,直接给 key_win 赋值,就可以实现多个命令行窗口了。代码如下:

1
2
3
4
5
6
7
if (i == 256 + 0x3c && key_shift != 0) {	/* Shift+F2 */
keywin_off(key_win);
key_win = open_console(shtctl, memtotal);
sheet_slide(key_win, 32, 4);
sheet_updown(key_win, shtctl->top);
keywin_on(key_win);
}

效果如下:

关闭命令行窗口

关闭一个命令行窗口时,我们需要将创建该窗口时所占用的内存空间全部释放,因此我们需要在 TASK 结构中添加一个 cons_stack 成员来保存栈的地址。

我们写一个用来结束命令行窗口任务的函数 close_constask,以及先关闭图层后关闭任务的 close_console,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void close_constask(struct TASK *task)
{
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
task_sleep(task);
memman_free_4k(memman, task->cons_stack, 64 * 1024);
memman_free_4k(memman, (int) task->fifo.buf, 128 * 4);
task->flags = 0;
return;
}

void close_console(struct SHEET *sht)
{
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
struct TASK *task = sht->task;
memman_free_4k(memman, (int) sht->buf, 256 * 165);
sheet_free(sht);
close_constask(task);
return;
}

close_constask 里先让任务进入休眠状态的目的是将任务从等待切换列表中拿出来,防止切换到该任务。

然后我们来编写 exit 命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void cmd_exit(struct CONSOLE *cons, int *fat)
{
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
struct TASK *task = task_now();
struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);
struct FIFO32 *fifo = (struct FIFO32 *) *((int *) 0x0fec);
timer_cancel(cons->timer);
memman_free_4k(memman, (int) fat, 4 * 2880);
io_cli();
fifo32_put(fifo, cons->sht - shtctl->sheets0 + 768); /* 768 ~ 1023 */
io_sti();
for (;;) {
task_sleep(task);
}
}

我们不能在 cmd_exit 中调用 close_console,否则就相当于自己让自己休眠,就无法继续执行下去了。我们可以把关闭命令行窗口的任务交给 task_a。

然后我们修改 HariMain,使其能够处理来自命令行窗口的 768 ~ 1023 的数据,也就是如果从 FIFO 中接收到 768 以上的数字,就将其对应的窗口(-768)调用 close_console 即可。此外,当一个窗口没有的情况下,我们将 key_win 置为 0。鼠标和键盘等检测到 key_win 为 0 时要进行特殊处理。

接下来我们再来实现用鼠标关闭命令行窗口的功能,当鼠标点击向命令行窗口任务发送一个 4,命令行窗口接收到后则开始执行 exit 命令。

这样我们就完成了,现在可以用两种方式来关闭命令行窗口了。

start 命令

start 命令的功能是打开一个新的命令行窗口并运行指定的应用程序,程序编写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void cmd_start(struct CONSOLE *cons, char *cmdline, int memtotal)
{
struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);
struct SHEET *sht = open_console(shtctl, memtotal);
struct FIFO32 *fifo = &sht->task->fifo;
int i;
sheet_slide(sht, 32, 4);
sheet_updown(sht, shtctl->top);
/* 将命令行输入的字符串逐字复制到新的命令行窗口中 */
for (i = 6; cmdline[i] != 0; i++) {
fifo32_put(fifo, cmdline[i] + 256);
}
fifo32_put(fifo, 10 + 256); /* Enter */
cons_newline(cons);
return;
}

效果如下:

ncst 命令

ncst 命令就是不打开新命令行窗口的 start 命令。想要不打开命令行窗口直接运行应用程序,我们可以想办法来禁止向命令行显示内容。

首先实现 cmd_ncst 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void cmd_ncst(struct CONSOLE *cons, char *cmdline, int memtotal)
{
struct TASK *task = open_constask(0, memtotal);
struct FIFO32 *fifo = &task->fifo;
int i;
/* 将命令行输入的字符串逐字复制到新的命令行窗口中 */
for (i = 5; cmdline[i] != 0; i++) {
fifo32_put(fifo, cmdline[i] + 256);
}
fifo32_put(fifo, 10 + 256); /* Enter */
cons_newline(cons);
return;
}

当 cons->sht 为 0 时,要禁用命令行窗口的字符显示等所有操作。

然后我们修改 console_task,当不显示命令行窗口时,禁用一些不必要的处理。当命令执行完毕时,立即结束命令行窗口任务。

cmd_exit 也要修改一下,当没有命令行窗口时,我们就没有图层地址了,所以把 TASK 结构的地址告诉 task_a。同理对应 bootpack 中也要修改。

最后我们运行一下看看效果吧。