【读书笔记-《30天自制操作系统》-23】Day24
本篇内容依然比较简单,主要是优化窗口功能以及开发定时器应用程序。首先是优化窗口的切换功能,实现通过键盘和鼠标切换窗口,然后是实现通过鼠标关闭窗口。接着实现不同窗口输入状态的切换,最后是实现定时器的API与应用程序。
1. 窗口优化
前面已经完成了窗口的基本功能,现在需要对窗口功能进行进一步优化。首先是增加窗口的切换功能。
1.1 通过按键实现窗口切换
先从简单的开始,实现按下F11后将最下面的窗口放在最上面。F11的按键编码为0x57,只需要在主程序中添加如下代码:
……
if (i == 256 + 0x57 && shtctl->top > 2)
{ /* F11 */sheet_updown(shtctl->sheets[1], shtctl->top - 1);
}
……
代码很简单,sheets[0]表示背景图层,sheets[1]即为最下层的窗口。top图层为鼠标图层,按下F11键后将最下层的窗口调整到鼠标图层下一层即可(因为不能覆盖鼠标)。
1.2 通过鼠标实现窗口切换
接下来实现通过鼠标点击实现的窗口切换。鼠标点击画面上的某个地方时,我们需要按照从上到下的顺序判断鼠标的位置落在哪个图层的范围内,并且还需要确保该位置不是透明色区域:
else if (512 <= i && i <= 767)
{ /* 鼠标数据 */if (mouse_decode(&mdec, i - 512) != 0) {/* 鼠标指针移动 */mx += mdec.x;my += mdec.y;if (mx < 0) {mx = 0;}if (my < 0) {my = 0;}if (mx > binfo->scrnx - 1) {mx = binfo->scrnx - 1;}if (my > binfo->scrny - 1) {my = binfo->scrny - 1;}sheet_slide(sht_mouse, mx, my);if ((mdec.btn & 0x01) != 0) {/* 按下左键 *//* 按照从上到下的顺序寻找鼠标所指向的图层 */for (j = shtctl->top - 1; j > 0; j--) {sht = shtctl->sheets[j];x = mx - sht->vx0;y = my - sht->vy0;if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {sheet_updown(sht, shtctl->top - 1);break;}}}}}
这一部分的逻辑也比较清楚。按照从上到下的顺序寻找鼠标点击的图层,如果鼠标点击的位置处于该图层的范围之内,就将该图层移动到最上面。如果将命令行窗口移动到最上面,则会遮挡其他窗口。不过这时又可以通过F11将下面的窗口切换上来。
1.3 窗口移动
窗口的切换功能基本完成了,接下来实现窗口的移动功能。之前单独实现了任务A窗口的实现功能,现在有了多个任务窗口,需要重新进行实现。
当鼠标左键点击窗口时,如果点击窗口的标题栏区域,则进入窗口移动模式,使窗口位置随着鼠标指针移动;而放开鼠标左键时,退出窗口移动模式,返回普通模式。实现窗口的移动需要记录鼠标移动的距离,这里添加了两个变量mmx和mmy,用于记录移动之前的坐标,并且规定mmx=-1时不处于窗口移动模式。
……
int j, x, y, mmx = -1, mmy = -1;
struct SHEET *sht = 0;else if (512 <= i && i <= 767)
{ /* 鼠标数据 */if (mouse_decode(&mdec, i - 512) != 0) {/*按下左键 */mx += mdec.x;my += mdec.y;if (mx < 0) {mx = 0;}if (my < 0) {my = 0;}if (mx > binfo->scrnx - 1) {mx = binfo->scrnx - 1;}if (my > binfo->scrny - 1) {my = binfo->scrny - 1;}sheet_slide(sht_mouse, mx, my);if ((mdec.btn & 0x01) != 0) {/* 按下左键 */if (mmx < 0) {/* 处于通常模式,不处于窗口移动模式 *//* 按照从上到下的顺序寻找鼠标指针所在的图层 */for (j = shtctl->top - 1; j > 0; j--) {sht = shtctl->sheets[j];x = mx - sht->vx0;y = my - sht->vy0;if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {sheet_updown(sht, shtctl->top - 1);if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {mmx = mx; /* 进入窗口移动模式 */mmy = my;}break;}}}} else {/* 如果处于窗口移动模式 */x = mx - mmx; /* 计算鼠标移动距离 */y = my - mmy;sheet_slide(sht, sht->vx0 + x, sht->vy0 + y);mmx = mx; /* 更新移动后窗口坐标*/mmy = my;}} else {/* 没有按下左键 */mmx = -1; /* 返回通常模式 */}}
}
虽然代码长了一些,但基本没有什么新东西,主要还是利用之前已经实现的内容与熟悉的方法。这样就可以通过鼠标移动窗口,更有操作系统的样子了。
1.4 用鼠标关闭窗口
有了前面用鼠标切换窗口的基础,用鼠标关闭窗口的实现也就顺理成章了。原理基本一致,只是需要增加判断鼠标点击的位置范围是否在窗口右上角的"X"号,并根据点击结束程序就可以了。
……
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize)
{if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {sheet_updown(sht, shtctl->top - 1);if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {mmx = mx; mmy = my;}if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {/* 点击"X"按钮 */if (sht->task != 0) { /* 该窗口是否为应用程序窗口 */cons = (struct CONSOLE *) *((int *) 0x0fec);cons_putstr0(cons, "\nBreak(mouse) :\n");io_cli(); /* 强制结束处理时禁止切换任务 */task_cons->tss.eax = (int) &(task_cons->tss.esp0);task_cons->tss.eip = (int) asm_end_app;io_sti();}}break;}
}
……
1.5 切换输入到应用窗口
现在有了多个应用程序窗口,并且像walk这样的应用程序已经可以接受键盘输入了。应该使应用程序的窗口能够切换到输入状态,然后通过键盘进行输入。这里仍然通过tab键来切换多个应用程序窗口,使用key_win变量保存当前处于输入状态的窗口地址。另外如果应用程序的窗口处于输入状态时被关闭,这是就让操作系统自动切换到最上层的窗口。
if (256 <= i && i <= 511)
{ /* 键盘数据 */
……if (s[0] != 0) { /* 一般字符 */if (key_win == sht_win) { /* 发送给任务A */if (cursor_x < 128) {/* 显示一个字符,光标后移一位 */s[1] = 0;putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);cursor_x += 8;}} else { /* 发送至命令行窗口 */fifo32_put(&key_win->task->fifo, s[0] + 256);}}if (i == 256 + 0x0e) { /* BackSpace键 */if (key_win == sht_win) { /* 发送给任务A */if (cursor_x > 8) {/* 空格擦除光标后后移一位 */putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);cursor_x -= 8;}} else { /* 发送至命令行窗口 */fifo32_put(&key_win->task->fifo, 8 + 256);}}if (i == 256 + 0x1c) { /* Enter */if (key_win != sht_win) { /* 发送至任务A */fifo32_put(&key_win->task->fifo, 10 + 256);}}if (i == 256 + 0x0f) { /* Tab */cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);j = key_win->height - 1;if (j == 0) {j = shtctl->top - 1;}key_win = shtctl->sheets[j];cursor_c = keywin_on(key_win, sht_win, cursor_c);}
……
}
key_winon与key_winoff两个函数用于控制串口标题栏的颜色与任务A窗口的光标:
int keywin_off(struct SHEET *key_win, struct SHEET *sht_win, int cur_c, int cur_x)
{change_wtitle8(key_win, 0);if (key_win == sht_win) {cur_c = -1; /* 删除光标 */boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cur_x, 28, cur_x + 7, 43);} else {if ((key_win->flags & 0x20) != 0) {fifo32_put(&key_win->task->fifo, 3); /* 命令行窗口光标OFF */}}return cur_c;
}int keywin_on(struct SHEET *key_win, struct SHEET *sht_win, int cur_c)
{change_wtitle8(key_win, 1);if (key_win == sht_win) {cur_c = COL8_000000; /* 显示光标 */} else {if ((key_win->flags & 0x20) != 0) {fifo32_put(&key_win->task->fifo, 2); /* 命令行窗口光标ON */}}return cur_c;
}
通过tab键可以实现窗口输入的切换:
1.6 鼠标切换输入窗口
实现鼠标的窗口输入切换,只需要再增加一点改动:
……
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize)
{if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {sheet_updown(sht, shtctl->top - 1);/*鼠标点击的图层不是当前输入的图层,切换*/if (sht != key_win) {cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);key_win = sht;cursor_c = keywin_on(key_win, sht_win, cursor_c);}if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {mmx = mx; mmy = my;}
……}
}
2. 定时器API与应用程序
2.1 定时器应用程序
为了编写定时器的应用程序,先编写了定时器的API:
_api_alloctimer: ; int api_alloctimer(void);MOV EDX,16INT 0x40RET_api_inittimer: ; void api_inittimer(int timer, int data);PUSH EBXMOV EDX,17MOV EBX,[ESP+ 8] ; timerMOV EAX,[ESP+12] ; dataINT 0x40POP EBXRET_api_settimer: ; void api_settimer(int timer, int time);PUSH EBXMOV EDX,18MOV EBX,[ESP+ 8] ; timerMOV EAX,[ESP+12] ; timeINT 0x40POP EBXRET_api_freetimer: ; void api_freetimer(int timer);PUSH EBXMOV EDX,19MOV EBX,[ESP+ 8] ; timerINT 0x40POP EBXRET
……
else if (edx == 16)
{reg[7] = (int) timer_alloc();
}
else if (edx == 17)
{timer_init((struct TIMER *) ebx, &task->fifo, eax + 256);
}
else if (edx == 18)
{timer_settime((struct TIMER *) ebx, eax);
}
else if (edx == 19)
{timer_free((struct TIMER *) ebx);
}
……
分别实现了获取定时器、设置定时器的发送数据、设置定时器时间、释放定时器的API。应用程序如下:
#include <stdio.h>int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
int api_getkey(int mode);
int api_alloctimer(void);
void api_inittimer(int timer, int data);
void api_settimer(int timer, int time);
void api_end(void);void HariMain(void)
{char *buf, s[12];int win, timer, sec = 0, min = 0, hou = 0;api_initmalloc();buf = api_malloc(150 * 50);win = api_openwin(buf, 150, 50, -1, "noodle");timer = api_alloctimer();api_inittimer(timer, 128);for (;;) {sprintf(s, "%5d:%02d:%02d", hou, min, sec);api_boxfilwin(win, 28, 27, 115, 41, 7 /* 白色*/);api_putstrwin(win, 28, 27, 0 /* 黑色 */, 11, s);api_settimer(timer, 100); /* 1秒 */if (api_getkey(1) != 128) {break;}sec++;if (sec == 60) {sec = 0;min++;if (min == 60) {min = 0;hou++;}}}api_end();
}
实现的效果就是显示时间:
2.2 取消定时器
定时器超时时,会发送设置好的数据。但如果定时器超时之前应用程序已经退出了,定时器超时时会产生什么效果呢?
运行定时器程序,关闭应用程序。过1秒钟后(产生超时),命令行窗口中会出现一个异常字符。
为了解决这种异常的情况,我们需要再应用程序关闭后取消定时器。
首先编写用于取消指定定时器的函数:
int timer_cancel(struct TIMER *timer)
{int e;struct TIMER *t;e = io_load_eflags();io_cli(); /*设置过程中禁止改变定时器状态 */if (timer->flags == TIMER_FLAGS_USING) { /* 是否需要取消? */if (timer == timerctl.t0) {/* 取消第一个定时器的处理 */t = timer->next;timerctl.t0 = t;timerctl.next = t->timeout;} else {/* 非第一个定时器的取消处理 *//* 找到timer前的一个定时器 */t = timerctl.t0;for (;;) {if (t->next == timer) {break;}t = t->next;}t->next = timer->next; }timer->flags = TIMER_FLAGS_ALLOC;io_store_eflags(e);return 1; /* 取消处理成功 */}io_store_eflags(e);return 0; /* 不需要取消处理 */
}
在此基础上,再来编写应用结束时取消定时器的函数。同时需要在定时器上增加一个标记,防止命令行窗口中的光标定时器也被取消:
struct TIMER {struct TIMER *next;unsigned int timeout;char flags, flags2;// 增加标志位struct FIFO32 *fifo;int data;
};
通常情况下将flags2置为0:
struct TIMER *timer_alloc(void)
{int i;for (i = 0; i < MAX_TIMER; i++) {if (timerctl.timers0[i].flags == 0) {timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;timerctl.timers0[i].flags2 = 0;return &timerctl.timers0[i];}}return 0;
}
对于应用程序申请的定时器,将flags2置为1:
else if (edx == 16)
{reg[7] = (int) timer_alloc();((struct TIMER *) reg[7])->flags2 = 1;
}
这样就可以在程序结束取消不需要的定时器了:
void timer_cancelall(struct FIFO32 *fifo)
{int e, i;struct TIMER *t;e = io_load_eflags();io_cli(); for (i = 0; i < MAX_TIMER; i++) {t = &timerctl.timers0[i];if (t->flags != 0 && t->flags2 != 0 && t->fifo == fifo) {timer_cancel(t);timer_free(t);}}io_store_eflags(e);return;
}
运行定时器程序并关闭,超时也不会再出现异常字符了。
本篇的内容逻辑清楚,代码简单,但却使操作系统看起来更像样了。下一篇继续优化命令行窗口,敬请期待。
相关文章:

【读书笔记-《30天自制操作系统》-23】Day24
本篇内容依然比较简单,主要是优化窗口功能以及开发定时器应用程序。首先是优化窗口的切换功能,实现通过键盘和鼠标切换窗口,然后是实现通过鼠标关闭窗口。接着实现不同窗口输入状态的切换,最后是实现定时器的API与应用程序。 1.…...

XML:DOM4j解析XML
XML简介: 什么是XML:XML 是独立于软件和硬件的信息传输工具。 XML 的设计宗旨是传输数据,而不是显示数据。XML 标签没有被预定义。您需要自行定义标签。XML不会做任何事情,XML被设计用来结构化、存储以及传输信息。 XML可以发明…...
15.5 创建监控控制平面的service
本节重点介绍 : k8s中service的作用和类型创建k8s控制平面的service 给prometheus采集用, 类型clusterIp kube-schedulerkube-controller-managerkube-etcd service的作用 Kubernetes Service定义了这样一种抽象: Service是一种可以访问 Pod逻辑分组…...

【Docker Nexus3】maven 私库
1.部署环境 window 11 x64Docker Desktop 4.34.1 (166053) Docker Engine v27.2.0 1.1.Docker 镜像源 1.1.1.Docker Engine 配置 {"builder": {"features": {"buildkit": true},"gc": {"defaultKeepStorage": "32…...

Docker本地部署Chatbot Ollama搭建AI聊天机器人并实现远程交互
文章目录 前言1. 拉取相关的Docker镜像2. 运行Ollama 镜像3. 运行Chatbot Ollama镜像4. 本地访问5. 群晖安装Cpolar6. 配置公网地址7. 公网访问8. 固定公网地址 前言 本文主要分享如何在群晖NAS本地部署并运行一个基于大语言模型Llama 2的个人本地聊天机器人并结合内网穿透工具…...
MySQL:用户管理
添加用户 create user usernamelocalhost identified by user_password;删除用户 drop user usernamelocalhost;查看所有用户 输入格式 select user,host from mysql.user; 输出 mysql> select user,host from mysql.user; ----------------------------- | user …...

论文《Mixture of Weak Strong Experts on Graphs》笔记
【Mowst 2024 ICLR】论文提出了一种新的图神经网络架构,称为Mixture of weak and strong experts(Mowst),通过将轻量级的多层感知机(MLP)作为弱专家和现成的GNN作为强专家相结合,以处理图中的节…...

【诉讼流程-健身房-违约-私教课-诉讼书提交流程-民事诉讼-自我学习-铺平通往法律的阶梯-讲解(3)】
【诉讼流程-健身房-违约-私教课-诉讼书提交流程-民事诉讼-自我学习-铺平通往法律的阶梯-讲解(3)】 1、前言说明2、流程说明3、现场提交(线下)4、网上提交1-起诉书样例2-起诉书编写(1)原告信息:&…...

数据结构(Day14)
一、学习内容 结构体 概念 引入:定义整数赋值为10 int a10; 定义小数赋值为3.14 float b3.14; 定义5个整数并赋值 int arr[5] {1 , 2 , 3 , 4 ,5}; 定义一个学生并赋值学号姓名成绩 定义一个雪糕并赋值名称产地单价 问题:没有学生、雪糕 数据类型 解决&…...

Paragon NTFS for Mac和Tuxera NTFS for Mac,那么两种工具有什么区别呢?
我们在使用Mac系统读取U盘的过程中往往会遇到一个问题,那就是U盘插进电脑无法显示,或者只能读取不能编辑。出现这种情况的原因就一般是格式错误。 很多小伙伴在解决这种问题的时候会选择使用U盘读写工具,那么哪一种读写工具比较好呢…...
HashTable结构体数组实现
写了个哈希表,底层逻辑基于结构体数组,核心结构:HashNode,结构外壳:HashTable_R,冲突处理以后会加的(QwQ)~ 目前代码: #ifndef PYIC_X #define PYIC_Xunsigned int PYIC_Hash(unsigned int Val, unsigned int Mov) {unsigned int Ht[4] { …...
Python 管理 AWS ElastiCache 告警
在 AWS 环境中,监控和管理 ElastiCache 集群的性能是至关重要的。本文将介绍如何使用 Python 和 AWS SDK (boto3) 来自动创建和删除 ElastiCache 集群的 CloudWatch 告警。我们将分两部分来讨论:创建告警和删除告警。 第一部分:创建 ElastiCache 告警 首先,让我们看看如何…...

【无人机设计与控制】四旋翼无人机俯仰姿态保持模糊PID控制(带说明报告)
摘要 为了克服常规PID控制方法在无人机俯仰姿态控制中的不足,本研究设计了一种基于模糊自适应PID控制的控制律。通过引入模糊控制器,实现了对输入输出论域的优化选择,同时解决了模糊规则数量与控制精度之间的矛盾。仿真结果表明,…...

[数据集][目标检测]不同颜色的安全帽检测数据集VOC+YOLO格式7574张5类别
重要说明:数据集里面有2/3是增强数据集,请仔细查看图片预览,确认符合要求在下载,分辨率均为640x640 数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件…...
确保 App 跟踪透明度权限:Flutter 中的实践
确保 App 跟踪透明度权限:Flutter 中的实践 在数字广告领域,用户隐私保护已成为一个重要议题。随着 iOS 14 的发布,Apple 引入了 App Tracking Transparency (ATT) 框架,要求开发者在跟踪用户行为以提供个性化广告之前必须获得用…...

李沐 过拟合和欠拟合【动手学深度学习v2】
模型容量 模型容量的影响 估计模型容量 难以在不同的种类算法之间比较,例如树模型和神经网络 给定一个模型种类,将有两个主要因素: 参数的个数参数值的选择范围 VC维...
Go Testify学习与使用
文章目录 1. **安装 Testify**2. **Testify 的主要功能**3. **Testify 使用示例**3.1 断言 (Assertions)3.2 使用 require3.3 使用 Mock3.4 测试套件 (Test Suites) 4. **Testify 的其他功能**5. **总结**常用资源 Testify 是 Go 语言中一个常用的单元测试库,它为标…...

Word中引用参考文献和公式编号的方法
文章目录 应用参考文献对于单个文献引用多于多个文献同时引用 公式编号手动编号自动编号 参考: 应用参考文献 对于单个文献引用 word中的参考文献用交叉应用实现。 首先,将参考文献编号: 然后,在需要引用的地方用交叉引用插入…...

[数据集][目标检测]智慧养殖场肉鸡目标检测数据集VOC+YOLO格式3548张1类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):3548 标注数量(xml文件个数):3548 标注数量(txt文件个数):3548 标注…...

Vision Transform—用于大规模图像分类的Transformers架构
VIT — 用于大规模图像识别的 Transformer 论文题目:AN IMAGE IS WORTH 16X16 WORDS:TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE。 官方代码:https://github.com/google-research/vision_transformer 引言与概述 Vision Transformer(ViT&…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...