① Enter 洪流:usb_kbd._on_int_complete 的按下沿依赖 kbd_prev_keycodes,而状态提交在函数末尾第三趟循环;kbd_report 是常驻 DMA 缓冲,按键保持/NAK/重复完成都会让回调反复看到同一份报告 ⇒ 每次中断都注入一次 Enter,Terminal 刷出 [cmd] enter,explorer 被拖死。新增 kbd_prev_sig(modifier+6 keycode 的标量签名)整份报告去重;同一趟循环里先提交 prev 再判定;全 0 报告只更新状态;抽出 _rearm() 保证所有出口都重排中断 IN 且 DMA 读取先于重排。② explorer 限流:kb_pend[64] + _deliver_kb,单轮最多投递 64 字节,ring_push 返回 -1 时停在当前字节(不再当成功丢弃),pending 清空才补读;鼠标取包同样 64 包/轮上限。③ 关窗卡死根因:process.vp 的 _tramp_code 是 mov eax,34; int 0x80; jmp \$,而 Syscall.THREAD_EXIT=60 ⇒ 用户线程 return 后走 syscall 34(无匹配分支)返回 Ring3 在 jmp \$ 空转,空转不再产生 syscall,而内核只在 syscall 返回路径消费 needs_reschedule ⇒ 协作式调度彻底停摆,整机冻结。sched.create_user_thread 在跳板映射后把立即数低字节改为 60(幂等)。④ 次生:主线程先退会立即回收自己的用户栈,而 Scene3D 渲染线程还引用其栈上对象。Scene3D 渲染线程退出改为显式 process.thread_exit() 并置 _render_done;主线程 mainloop 返回后先 yield 等渲染线程退出(上限 10000 次)再 exit。⑤ window.flush 在槽位 used==0 时直接返回;vpui.Tk.redraw 入口判 win_id 与 is_active,窗口已回收时不碰 arena 不 flush。
104 lines
3.8 KiB
Plaintext
104 lines
3.8 KiB
Plaintext
import vpsdk.window as window
|
||
import vpsdk.process as process
|
||
import vpsdk.vpui as vpui
|
||
import vpsdk.devfs as devfs
|
||
import string
|
||
from stdint import *
|
||
import viperlib
|
||
import gfx
|
||
|
||
define SCENE_W = 640
|
||
define SCENE_H = 360
|
||
|
||
_zbuf_mem: [SCENE_W * SCENE_H * 8]uint8 = []
|
||
_fb_mem: [SCENE_W * SCENE_H]uint32 = []
|
||
|
||
_root_ptr: vpui.Tk*
|
||
_canvas_ptr: vpui.FBCanvas*
|
||
_s3d_ptr: gfx.Scene3D*
|
||
_time_val: float = float(0.0)
|
||
# 渲染线程的 tid / 已退出标记:主线程必须等它退完才能 process.exit。
|
||
# 主线程栈上有 root/canvas/s3d,渲染线程还在引用;process.exit 会立刻回收主线程用户栈,
|
||
# 先退主线程 ⇒ 渲染线程在已解映射的栈上取数 ⇒ #PF。
|
||
_render_tid: int = -1
|
||
_render_done: int = 0
|
||
# 时间回绕周期 = 4π:Render 里 time*1 / time*2 / time*1.5 的 sin/cos 参数整体平移 2π 的整数倍
|
||
# ⇒ 动画严格连续;同时让相位半径永远很小,避免 float32 精度随运行时间退化
|
||
# (x 变大后相邻采样点的相位差被量化 ⇒ 曲线塌成一条平线、热力图颜色失去层次)。
|
||
define TIME_WRAP = float(12.566370614359172)
|
||
|
||
def _dbg(msg: const str):
|
||
# 【诊断·临时】与 explorer 同路径的串口输出。app 在建窗失败时会**静默退出**
|
||
# (Tk.mainloop 的循环条件是 win_id 有效,win_id=-1 时一次都不循环),所以必须分步打点。
|
||
devfs.serial_puts(msg)
|
||
|
||
def _render_thread(arg: t.CPtr):
|
||
global _time_val, _render_done
|
||
root: vpui.Tk* = _root_ptr
|
||
canvas: vpui.FBCanvas* = _canvas_ptr
|
||
s3d: gfx.Scene3D* = _s3d_ptr
|
||
while root.winfo_exists():
|
||
s3d.Render(_time_val)
|
||
_time_val += float(0.02)
|
||
if _time_val >= TIME_WRAP: _time_val = _time_val - TIME_WRAP
|
||
root.update()
|
||
process.yield_cpu()
|
||
_render_done = 1
|
||
# 窗口已被 explorer 回收:显式 THREAD_EXIT,绝不 return —— 用户线程的 return 会落到
|
||
# 出口跳板(旧调用号 34,而 THREAD_EXIT=60)⇒ Ring3 `jmp $` 空转、全机不再调度。
|
||
process.thread_exit()
|
||
|
||
@__attribute__(section(".text.startup"), aligned(16))
|
||
export def _start() -> int:
|
||
_dbg("[s3d] start\n")
|
||
root: vpui.Tk = vpui.Tk()
|
||
_dbg("[s3d] tk ok\n")
|
||
root.title("3D Scene")
|
||
root.set_style(window.STYLE_WIN11)
|
||
root.bg_color = uint32(0x0F0F19)
|
||
master: vpui.Tk* = &root
|
||
|
||
canvas: vpui.FBCanvas = vpui.FBCanvas(master, SCENE_W, SCENE_H)
|
||
_dbg("[s3d] canvas ok\n")
|
||
root.pack(0, vpui.PACK_TOP, vpui.FILL_BOTH, 1, 0, 0)
|
||
|
||
zbuf: float* = &_zbuf_mem
|
||
string.memset(zbuf, 0, SCENE_W * SCENE_H * 8)
|
||
|
||
fb: UINT32PTR = &_fb_mem
|
||
string.memset(fb, 0, SCENE_W * SCENE_H * 4)
|
||
canvas.set_fb(fb, SCENE_W, SCENE_H)
|
||
|
||
s3d: gfx.Scene3D = gfx.Scene3D(fb, zbuf, SCENE_W, SCENE_H)
|
||
_dbg("[s3d] scene ok\n")
|
||
|
||
root.geometry(100, 30, SCENE_W, SCENE_H + 32)
|
||
_wbuf: [128]char
|
||
viperlib.snprintf(&_wbuf, 128, "[s3d] geometry win_id=%d buf=0x%lx\n",
|
||
root.win_id, uint64(root.buf))
|
||
_dbg(&_wbuf)
|
||
|
||
global _root_ptr, _canvas_ptr, _s3d_ptr, _render_tid
|
||
|
||
_root_ptr = &root
|
||
_canvas_ptr = &canvas
|
||
_s3d_ptr = &s3d
|
||
|
||
_render_tid = process.thread_create(_render_thread, None)
|
||
_dbg("[s3d] render thread created\n")
|
||
|
||
root.mainloop()
|
||
_dbg("[s3d] mainloop returned\n")
|
||
|
||
# 窗口已关闭:先等渲染线程退出,才允许回收主线程用户栈(root/canvas/s3d 都在其上)。
|
||
# 渲染线程每轮都会 yield 且只差一次循环判定,正常情况下几百次切换内即退出;
|
||
# 设上限只是防止它已异常终止却没置标记时把主线程永远卡在这里。
|
||
if _render_tid > 0:
|
||
wait_n: int = 0
|
||
while _render_done == 0 and wait_n < 10000:
|
||
wait_n += 1
|
||
process.yield_cpu()
|
||
|
||
process.exit(0)
|
||
return 0
|